mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-29 12:57:09 -06:00
This commit is contained in:
@@ -35,6 +35,8 @@ use story::StoryCtx;
|
||||
|
||||
/// Most parts (headers, notes, comments, text boxes) we create from one file.
|
||||
const MAX_PARTS: usize = 50_000;
|
||||
/// Deepest chain of headers/footers whose own sections reference further headers/footers.
|
||||
const MAX_HF_DEPTH: usize = 4;
|
||||
/// Most graphic work (items plus path segments, see `story::graphic_work`) built for one file's
|
||||
/// charts and diagrams; later ones are left empty.
|
||||
const MAX_GRAPHIC_WORK: usize = 2_000_000;
|
||||
@@ -64,6 +66,8 @@ pub(crate) struct Reader<'p> {
|
||||
pub moves: MoveCtx,
|
||||
media_by_path: HashMap<String, String>,
|
||||
hf_by_path: HashMap<String, u32>,
|
||||
/// Header/footer parts being read right now (see [`MAX_HF_DEPTH`]).
|
||||
hf_depth: usize,
|
||||
pub footnotes: HashMap<i64, u32>,
|
||||
pub endnotes: HashMap<i64, u32>,
|
||||
/// The note being read (kind, part id): its `w:footnoteRef` / `w:endnoteRef` mark is a
|
||||
@@ -141,6 +145,7 @@ pub(crate) fn read_opened(pkg: &Package) -> Result<Document, DocxError> {
|
||||
moves: MoveCtx::default(),
|
||||
media_by_path: HashMap::new(),
|
||||
hf_by_path: HashMap::new(),
|
||||
hf_depth: 0,
|
||||
footnotes: HashMap::new(),
|
||||
endnotes: HashMap::new(),
|
||||
current_note: None,
|
||||
@@ -643,7 +648,7 @@ impl Reader<'_> {
|
||||
if let Some(id) = self.hf_by_path.get(path) {
|
||||
return Some(*id);
|
||||
}
|
||||
if self.doc.parts.len() >= MAX_PARTS {
|
||||
if self.doc.parts.len() >= MAX_PARTS || self.hf_depth >= MAX_HF_DEPTH {
|
||||
return None;
|
||||
}
|
||||
// Reserve the id first so a (malformed) self-reference can't recurse.
|
||||
@@ -658,7 +663,9 @@ impl Reader<'_> {
|
||||
let mut sc = StoryCtx::default();
|
||||
sc.in_hf = true;
|
||||
let mut blocks = Blocks::new();
|
||||
self.hf_depth += 1;
|
||||
self.read_blocks(&mut sc, &root, &rels, &mut blocks, 0);
|
||||
self.hf_depth = self.hf_depth.saturating_sub(1);
|
||||
self.flush_pending(&mut sc, &mut blocks);
|
||||
if let Some(p) = self.doc.parts.get_mut(&id)
|
||||
&& !blocks.is_empty()
|
||||
|
||||
@@ -238,3 +238,30 @@ proptest! {
|
||||
let _ = read(&z);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_header_reference_chain_is_cut_off() {
|
||||
const HDR_REL: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header";
|
||||
const RELS_NS: &str = "http://schemas.openxmlformats.org/package/2006/relationships";
|
||||
let rels_for = |next: usize| {
|
||||
let rel = format!(r#"<Relationship Id="rId1" Type="{HDR_REL}" Target="header{next}.xml"/>"#);
|
||||
format!(r#"<Relationships xmlns="{RELS_NS}">{rel}</Relationships>"#)
|
||||
};
|
||||
let sect = r#"<w:sectPr><w:headerReference w:type="default" r:id="rId1"/></w:sectPr>"#;
|
||||
let doc = format!("<w:document {W_NS}><w:body><w:p><w:r><w:t>body</w:t></w:r></w:p>{sect}</w:body></w:document>");
|
||||
// header0 -> header1 -> ... -> header59: every header's last paragraph holds a section that points at the next one.
|
||||
let mut entries: Vec<(String, Vec<u8>)> = vec![
|
||||
("_rels/.rels".into(), ROOT_RELS.as_bytes().to_vec()),
|
||||
("word/document.xml".into(), doc.into_bytes()),
|
||||
("word/_rels/document.xml.rels".into(), rels_for(0).into_bytes()),
|
||||
];
|
||||
for i in 0..60 {
|
||||
let hdr = format!("<w:hdr {W_NS}><w:p><w:pPr>{sect}</w:pPr><w:r><w:t>h{i}</w:t></w:r></w:p></w:hdr>");
|
||||
entries.push((format!("word/header{i}.xml"), hdr.into_bytes()));
|
||||
entries.push((format!("word/_rels/header{i}.xml.rels"), rels_for(i + 1).into_bytes()));
|
||||
}
|
||||
let refs: Vec<(&str, &[u8])> = entries.iter().map(|(n, b)| (n.as_str(), b.as_slice())).collect();
|
||||
let d = read(&zip(&refs)).unwrap();
|
||||
assert!(!d.parts.is_empty());
|
||||
assert!(d.parts.len() <= 8, "followed {} chained headers", d.parts.len());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user