Files
wordcraft/crates/doc/src/lib.rs
T
242e850aaa #3660: refuse a package that declares too many zip entries before zip parses them (#5260)
* #3660: refuse a package that declares too many zip entries before zip parses them

zip::ZipArchive::new parses and keeps every central directory header
the end record declares before its caller can look at len(). A ZIP64
end record can declare one per 46 bytes of the file: a 47 MB package of
one stored file listed 1,000,000 times takes zip 3.0.0 about half a
second and a large transient allocation, and len() then says 1, since
the copies share a name, so the docx reader's MAX_ENTRIES cap never
fires. ODT and spreadsheet packages had no cap at all.

wordcraft_doc::zip_guard::check finds the end records the way zip finds
them (backwards; the ZIP64 record from the locator's offset on; a plain
record when it has no entries or a central header before it) and refuses
a package declaring more than the reader's limit. The docx package
reader, the ODT reader, the spreadsheet reader (mail-merge data) and the
template-title probe call it first, with 20,000 entries as the limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* formats: keep the extensions doc line on EXTENSIONS

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-02 04:58:18 -05:00

136 lines
4.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! WordCraft document model.
//!
//! A [`Document`] holds the main story (`body`) and secondary stories (`parts`: headers,
//! footers, notes, comments, text boxes). A story is a list of [`Block`]s: paragraphs and tables;
//! table cells hold block lists of their own. Blocks are `Arc`-shared, so cloning a document is
//! cheap (undo snapshots, handing a frozen copy to the renderer) and edits copy only what they
//! touch (`Arc::make_mut`).
//!
//! Positions ([`Pos`]) name a story, a [`Path`] to a paragraph and a byte offset in it. Paths
//! compare in document order.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
pub mod bidi;
mod byte_store;
pub mod cfb_guard;
pub mod chart;
mod cjk_numbers;
pub mod cluster;
pub mod collate;
pub mod connector;
pub mod control;
pub mod edit;
pub mod effects;
pub mod encoding;
pub mod eq;
pub mod fields;
pub mod fill;
pub mod freeform;
pub mod graphic;
pub mod ime_text;
pub mod legacy_form;
pub mod line_dash;
pub mod link;
pub mod locale_number;
pub mod math;
pub mod math_edit;
pub mod math_latex;
pub mod math_linear;
pub mod math_symbols;
pub mod numbering;
pub mod para;
pub mod props;
pub mod resolve;
pub mod section;
pub mod smart_art;
pub mod styles;
pub mod symbol_fonts;
pub mod table;
pub mod wordart;
pub mod wrap;
pub mod zip_guard;
use std::collections::BTreeMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
pub use control::{ContentControl, ControlKind, ControlLock, ControlRange, ControlWrap};
pub use fields::FieldRange;
pub use numbering::{ListKind, Numbering};
pub use para::{InlineObject, Paragraph, Run};
pub use props::{Align, CharProps, ParaProps, Rgb, TextColor};
pub use section::SectionProps;
pub use styles::{Style, StyleKind, StyleSheet};
pub use table::{Cell, Row, Table};
mod review;
pub use review::{AUTO_TEXT_GALLERY, BuildingBlock, Comment, QUICK_PARTS_GALLERY, Revision, RevisionKind};
mod block;
mod compare;
mod document;
mod error;
mod parts;
mod position;
mod settings;
mod text_walk;
pub use block::{Block, Blocks, BoxBudget, MAX_TEXT_BOX_DEPTH};
pub use compare::para_block;
use document::MediaIndex;
pub use error::{DocError, Result};
pub use parts::{MAX_LINKED_BOXES, MAX_TEXT_BOX_INSET, NoteSeparators, Part, PartKind, PermRange, TEXT_BOX_INSET, TextBody, free_id};
pub use position::{Path, Pos, StoryRef};
pub use settings::{
COMPAT_MODE_CURRENT, CoreProps, CustomProp, DEFAULT_GRID, DEFAULT_HYPHENATION_ZONE, LEGACY_COMPAT_MODE, PagePicture, Settings, Source,
THEME_COLORS, Watermark, Zoom, ZoomFit,
};
pub use text_walk::count_words;
pub(crate) use text_walk::{PARA_WALK_DEPTH, walk_between};
/// A document.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Document {
pub body: Blocks,
/// Properties of the last section (OOXML's body-level `w:sectPr`).
pub last_section: SectionProps,
pub parts: BTreeMap<u32, Part>,
pub styles: StyleSheet,
pub numbering: Numbering,
pub comments: BTreeMap<u32, Comment>,
pub revisions: Vec<Revision>,
pub settings: Settings,
pub core: CoreProps,
/// Bibliography sources (References › Manage Sources).
pub sources: Vec<Source>,
/// Custom document properties, in file order.
pub custom_props: Vec<CustomProp>,
/// The document's own footnote separators and continuation notice.
#[serde(skip_serializing_if = "NoteSeparators::is_empty")]
pub footnote_separators: NoteSeparators,
/// The document's own endnote separators and continuation notice.
#[serde(skip_serializing_if = "NoteSeparators::is_empty")]
pub endnote_separators: NoteSeparators,
/// Embedded media (images) by key. Native JSON writes each as base64 (`byte_store.rs`).
#[serde(skip_serializing_if = "BTreeMap::is_empty", with = "byte_store")]
pub media: BTreeMap<String, Arc<Vec<u8>>>,
/// Source-format parts we keep verbatim for round-trip (e.g. custom XML), by part name.
#[serde(skip_serializing_if = "BTreeMap::is_empty", with = "byte_store")]
pub passthrough: BTreeMap<String, Arc<Vec<u8>>>,
/// The document's own building blocks (its glossary document), in file order.
#[serde(skip_serializing_if = "Vec::is_empty")]
pub glossary: Vec<BuildingBlock>,
/// Lookup cache for [`Document::add_media`]; not part of the document (never saved, ignored
/// by `==`).
#[serde(skip)]
media_index: MediaIndex,
}
#[cfg(test)]
mod test_log;
#[cfg(test)]
mod tests;