Files
craft-libs/crates/odf/README.md
Dylan M. Taylor 712e6fa5e3 craft-odf: Manifest::parse_strict and Package::manifest_strict (#972)
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).
2026-11-11 19:31:29 -05:00

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.