mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 08:52:32 +00:00
A strict read of the manifest for a reader that refuses a damaged package instead of opening what it can, as GridCraft's ODS reader does today (crates/xlsx/src/ods.rs check_manifest): malformed or unbalanced XML, any document type declaration, undefined entities, content outside the root, a root that is not manifest:manifest by namespace, a file entry without a full-path, or more entries than the reader keeps are errors (Error::Manifest).
70 lines
3.7 KiB
Markdown
70 lines
3.7 KiB
Markdown
# craft-odf
|
|
|
|
OpenDocument packages (ODF 1.3 Part 2): what every OpenDocument file has in common, whatever it
|
|
holds. Text documents, spreadsheets, presentations and drawings all sit in the same container;
|
|
this crate reads and writes that container and nothing inside it.
|
|
|
|
| Module | What it does |
|
|
| --- | --- |
|
|
| `Package`, `PackageWriter` | The ZIP container: the `mimetype` file, the manifest, the files, within `Limits`. |
|
|
| `Manifest` | `META-INF/manifest.xml`: each file's media type, and how an encrypted file is encrypted. Read by namespace, whatever prefix the file uses; `parse_strict` / `Package::manifest_strict` refuse a damaged one instead of reading what they can. |
|
|
| `crypt` | Password-protected packages: AES-256-CBC, SHA-256 start key, PBKDF2 (HMAC-SHA-1), SHA-256 or SHA-1 checksums. A package in, a package out. |
|
|
| `flat` | Flat single-file documents (`.fodt`, `.fods`, `.fodp`, `.fodg`): one `office:document` taken apart into a package's files, and a package put together into one. |
|
|
| `zip_guard` | Refuses a ZIP whose end record declares too many entries, before `zip` reserves room for them. |
|
|
|
|
```rust
|
|
use craft_odf::{Limits, Package, PackageWriter, crypt, flat, mime};
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut w = PackageWriter::new(mime::SPREADSHEET);
|
|
w.file("content.xml", "text/xml", b"<office:document-content/>");
|
|
w.stored("Pictures/chart.png", "image/png", b"...");
|
|
let plain = w.finish()?;
|
|
|
|
let locked = crypt::encrypt(&plain, "password")?;
|
|
assert!(crypt::is_encrypted(&locked));
|
|
let opened = crypt::decrypt(&locked, "password")?;
|
|
|
|
let mut p = Package::open(&opened, &Limits::DEFAULT)?;
|
|
assert_eq!(p.mimetype().as_deref(), Some(mime::SPREADSHEET));
|
|
assert_eq!(p.read("content.xml")?.as_deref(), Some(b"<office:document-content/>".as_slice()));
|
|
|
|
let one_file = flat::from_package(&opened, &Limits::DEFAULT)?;
|
|
let again = flat::to_package(&one_file, mime::SPREADSHEET, &Limits::DEFAULT)?;
|
|
assert_eq!(Package::open(&again, &Limits::DEFAULT)?.mimetype().as_deref(), Some(mime::SPREADSHEET));
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
## Limits
|
|
|
|
Input is hostile. Nothing here panics on any bytes.
|
|
|
|
| Limit | Default | Where |
|
|
| --- | --- | --- |
|
|
| Entries a ZIP may declare | 20,000 | `Limits::max_entries`, checked on the raw bytes before `zip` parses the directory |
|
|
| Bytes one file inflates to | 256 MiB | `Limits::max_entry`; the size a file declares is not trusted |
|
|
| Bytes read from one package | 2 GiB | `Limits::max_total` |
|
|
| `mimetype` file | 255 bytes | longer is no media type |
|
|
| Manifest entries | 20,000 | the rest are dropped |
|
|
| PBKDF2 iterations for one file | 10,000,000 | more is refused |
|
|
| Key-derivation work for a package | 200,000,000 iteration-blocks | a thousand files as LibreOffice writes them |
|
|
| Files decrypted | 10,000 | |
|
|
| Pictures and objects taken out of a flat document | 10,000 | the rest are dropped |
|
|
| Nesting of one element name in a flat document | 512 | deeper is treated as unclosed |
|
|
|
|
## Not supported
|
|
|
|
- Blowfish CFB (the default before ODF 1.2) and OpenPGP encryption: such packages are refused with
|
|
a message that names the algorithm.
|
|
- Digital signatures (`META-INF/documentsignatures.xml`) are not checked or written; the file is
|
|
kept like any other.
|
|
- The meaning of `content.xml`, `styles.xml`, `meta.xml` and `settings.xml`.
|
|
|
|
## Provenance
|
|
|
|
Moved from WordCraft (`crates/formats/src/odt/crypt.rs`, `odt/flat.rs`, the package reading and
|
|
writing in `odt/import.rs` and `odt/package.rs`, and `crates/doc/src/zip_guard.rs`), with
|
|
GridCraft's namespace-aware manifest reading. Written from the OASIS OpenDocument 1.3
|
|
specification, W3C XML Encryption (padding), RFC 2898 (PBKDF2) and PKWARE's APPNOTE.TXT.
|