///|
let mc_ns = "http://schemas.openxmlformats.org/markup-compatibility/2006"
///|
let word14_ns = "http://schemas.microsoft.com/office/word/2010/wordml"
///|
let word15_ns = "http://schemas.microsoft.com/office/word/2012/wordml"
///|
let theme15_ns = "http://schemas.microsoft.com/office/thememl/2012/main"
///|
fn valid_guid(value : String) -> Bool {
if value.length() != 38 || value[0] != '{' || value[37] != '}' {
return false
}
for i in 1..<37 {
let c = value[i]
if [9, 14, 19, 24].contains(i) {
if c != '-' {
return false
}
} else if !((c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') ||
(c >= 'a' && c <= 'f')) {
return false
}
}
true
}
///|
fn empty_node(node : Node) -> Bool {
node.text_fragments.all(fn(t) { t.trim() == "" })
}
///|
fn hex_text(value : String, length : Int32) -> Bool {
value.length() == length &&
value
.code_units()
.all(fn(c) {
(c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')
})
}
///|
fn exact_node_attributes(
node : Node,
ns : String?,
names : Array[String],
) -> Bool {
node.element.attributes.all(fn(a) {
a.name.namespace_uri == ns && names.contains(a.name.local_name)
})
}
///|
fn word_ignorable_attribute(node : Node, a : @xml.NamespaceAttribute) -> Bool {
if a.name.namespace_uri != Some(mc_ns) ||
a.name.local_name != "Ignorable" ||
node.depth != 0 ||
node.element.name.namespace_uri != Some(word_ns) ||
a.value.length() > 256 {
return false
}
let prefixes = a.value.split(" ").filter(fn(p) { p != "" }).collect()
prefixes.length() > 0 &&
prefixes.length() <= 16 &&
prefixes.all(fn(prefix) {
node.element.namespace_declarations.any(fn(d) {
d.prefix == Some(prefix.to_owned()) &&
[
word14_ns, word15_ns, "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
].contains(d.namespace_uri)
})
})
}
///|
fn word_modern_attribute(
part : PartRecord,
node : Node,
a : @xml.NamespaceAttribute,
) -> Bool {
word_ignorable_attribute(node, a) ||
(
part.content_type ==
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml" &&
node.element.name.namespace_uri == Some(word_ns) &&
node.element.name.local_name == "abstractNum" &&
node.depth == 1 &&
a.name.namespace_uri == Some(word15_ns) &&
a.name.local_name == "restartNumberingAfterBreak" &&
["0", "1", "true", "false", "on", "off"].contains(a.value)
)
}
///|
fn namespace_at(
nodes : Array[Node],
index : Int32,
prefix : String,
control : WorkControl,
) -> String? raise {
let mut depth = nodes[index].depth + 1
let mut j = index
while j >= 0 {
control.checkpoint("word-namespace-binding", 1)
if nodes[j].depth < depth {
for d in nodes[j].element.namespace_declarations {
if d.prefix == Some(prefix) {
return Some(d.namespace_uri)
}
}
depth = nodes[j].depth
}
j -= 1
}
None
}
///|
// A catalog of fully checked passive extensions. It never selects a branch:
// both alternatives are validated, and every original byte is retained.
fn word_modern_catalog(
part : PartRecord,
nodes : Array[Node],
control : WorkControl,
) -> Map[Int32, Bool] raise {
let allowed : Map[Int32, Bool] = Map([])
if ![
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
"application/vnd.openxmlformats-officedocument.theme+xml",
].contains(part.content_type) {
return allowed
}
for i, node in nodes {
control.checkpoint(
"word-modern-catalog",
1 + node.element.attributes.length(),
)
if allowed.contains(i) {
continue
}
let ns = node.element.name.namespace_uri.unwrap_or("")
let name = node.element.name.local_name
if ns == word_ns &&
name == "stylePaneFormatFilter" &&
node.depth == 1 &&
part.content_type ==
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" {
let flags = [
"allStyles", "customStyles", "latentStyles", "stylesInUse", "headingStyles",
"numberingStyles", "tableStyles", "directFormattingOnRuns", "directFormattingOnParagraphs",
"directFormattingOnNumbering", "directFormattingOnTables", "clearFormatting",
"top3HeadingStyles", "visibleStyles", "alternateStyleNames",
]
guard empty_node(node) &&
(i + 1 == nodes.length() || nodes[i + 1].depth <= node.depth) &&
node.element.attributes.all(fn(a) {
a.name.namespace_uri == Some(word_ns) &&
(if a.name.local_name == "val" {
hex_text(a.value, 4)
} else {
flags.contains(a.name.local_name) &&
["0", "1", "true", "false", "on", "off"].contains(a.value)
})
}) else {
raise Refused("unsupported style pane display filter")
}
allowed[i] = true
} else if ns == word15_ns &&
part.content_type ==
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" &&
node.depth == 1 &&
["docId", "chartTrackingRefBased"].contains(name) {
guard (i + 1 == nodes.length() || nodes[i + 1].depth <= node.depth) &&
empty_node(node) else {
raise Refused("modern settings metadata must be a leaf")
}
if name == "docId" {
guard exact_node_attributes(node, Some(word15_ns), ["val"]) &&
node.element.attributes.length() == 1 &&
valid_guid(node.element.attributes[0].value) else {
raise Invalid("invalid modern document GUID")
}
} else {
guard exact_node_attributes(node, Some(word_ns), ["val"]) &&
node.element.attributes.length() <= 1 &&
node.element.attributes.all(fn(a) {
["0", "1", "true", "false", "on", "off"].contains(a.value)
}) else {
raise Refused("invalid chart tracking flag")
}
}
allowed[i] = true
} else if ns == mc_ns &&
name == "AlternateContent" &&
part.content_type ==
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml" {
let mut j = i - 1
while j >= 0 && nodes[j].depth >= node.depth {
control.checkpoint("word-alternate-parent", 1)
j -= 1
}
guard j >= 0 &&
nodes[j].depth == node.depth - 1 &&
nodes[j].element.name.namespace_uri == Some(word_ns) &&
nodes[j].element.name.local_name == "lvl" else {
raise Refused("alternate numbering content has unsupported parent")
}
let mut sibling = j + 1
while sibling < nodes.length() && nodes[sibling].depth > nodes[j].depth {
control.checkpoint("word-alternate-sibling", 1)
let other = nodes[sibling]
guard sibling == i ||
other.depth != node.depth ||
!["numFmt", "AlternateContent"].contains(
other.element.name.local_name,
) else {
raise Invalid("ambiguous numbering format alternatives")
}
sibling += 1
}
guard i + 4 < nodes.length() &&
(i + 5 == nodes.length() || nodes[i + 5].depth <= node.depth) else {
raise Refused("unsupported alternate numbering shape")
}
let choice = nodes[i + 1]
let primary = nodes[i + 2]
let fallback = nodes[i + 3]
let secondary = nodes[i + 4]
guard node.element.attributes.length() == 0 &&
empty_node(node) &&
choice.depth == node.depth + 1 &&
choice.element.name.namespace_uri == Some(mc_ns) &&
choice.element.name.local_name == "Choice" &&
exact_node_attributes(choice, None, ["Requires"]) &&
choice.element.attributes.length() == 1 &&
empty_node(choice) &&
namespace_at(nodes, i + 1, attr(choice, "Requires"), control) ==
Some(word14_ns) &&
fallback.depth == node.depth + 1 &&
fallback.element.name.namespace_uri == Some(mc_ns) &&
fallback.element.name.local_name == "Fallback" &&
fallback.element.attributes.length() == 0 &&
empty_node(fallback) else {
raise Refused("unsupported alternate numbering branches")
}
for pair in [(primary, true), (secondary, false)] {
let (format, modern) = pair
guard format.depth == node.depth + 2 &&
format.element.name.namespace_uri == Some(word_ns) &&
format.element.name.local_name == "numFmt" &&
empty_node(format) &&
exact_node_attributes(
format,
Some(word_ns),
if modern {
["val", "format"]
} else {
["val"]
},
) else {
raise Refused("unsupported alternate numbering payload")
}
if modern {
let pattern = word_attr(format, "format")
guard word_attr(format, "val") == "custom" &&
pattern.length() > 0 &&
pattern.length() <= 512 &&
pattern.code_units().all(fn(c) { c >= 32 && c != 127 }) else {
raise Refused("unsupported custom numbering format")
}
} else {
guard word_attr(format, "val") == "decimal" else {
raise Refused("unsupported numbering fallback")
}
}
}
for offset in 0..<5 {
allowed[i + offset] = true
}
} else if ns == drawing_ns &&
name == "extLst" &&
node.depth == 1 &&
part.content_type ==
"application/vnd.openxmlformats-officedocument.theme+xml" {
guard i + 2 < nodes.length() &&
(i + 3 == nodes.length() || nodes[i + 3].depth <= node.depth) &&
node.element.attributes.length() == 0 &&
empty_node(node) else {
raise Refused("unsupported theme extension list")
}
let ext = nodes[i + 1]
let family = nodes[i + 2]
guard ext.depth == 2 &&
ext.element.name.namespace_uri == Some(drawing_ns) &&
ext.element.name.local_name == "ext" &&
exact_node_attributes(ext, None, ["uri"]) &&
ext.element.attributes.length() == 1 &&
attr(ext, "uri") == "{05A4C25C-085E-4340-85A3-A5531E510DB2}" &&
empty_node(ext) &&
family.depth == 3 &&
family.element.name.namespace_uri == Some(theme15_ns) &&
family.element.name.local_name == "themeFamily" &&
exact_node_attributes(family, None, ["name", "id", "vid"]) &&
family.element.attributes.length() == 3 &&
valid_guid(attr(family, "id")) &&
valid_guid(attr(family, "vid")) &&
attr(family, "name").length() <= 256 &&
empty_node(family) else {
raise Refused("unsupported theme family metadata")
}
for offset in 0..<3 {
allowed[i + offset] = true
}
}
}
allowed
}