///|
priv struct Node {
element : @xml.NamespaceElement
depth : Int
text_fragments : Array[String]
}
///|
// Literal DTD declarations are rejected before upstream parser allocation.
fn read_xml(
data : BytesView,
limits : Limits,
metadata? : Bool = false,
) -> Array[Node] raise {
read_xml_checked(data, limits, metadata~) catch {
Refused(reason) => raise Refused(reason)
Incomplete(reason) => raise Incomplete(reason)
error =>
raise Incomplete("XML parsing did not complete: " + error.to_string())
}
}
///|
fn read_xml_checked(
data : BytesView,
limits : Limits,
metadata~ : Bool,
) -> Array[Node] raise {
guard data.length() <= limits.xml_bytes else {
raise Incomplete("XML byte limit")
}
let text = @utf8.decode(data)
guard !text.contains(""
} else if text[i:].has_prefix(""
} else {
"?>"
}
let token_start = i
i += 2
while i < text.length() && !text[i:].has_prefix(end) {
i += 1
guard i - token_start + end.length() <= 65536 else {
raise Incomplete("XML markup token limit")
}
}
i += end.length()
guard i - token_start <= 65536 else {
raise Incomplete("XML markup token limit")
}
text_start = i
continue
}
let closing = i + 1 < text.length() && text[i + 1] == '/'
let mut quote : UInt16 = 0
let mut j = i + 1
let mut tag_attributes = 0
while j < text.length() {
let c = text[j]
if quote != 0 {
if c == quote {
quote = 0
}
} else if c == '"' || c == '\'' {
quote = c
} else if c == '=' {
attrs += 1
tag_attributes += 1
guard tag_attributes <= 256 else {
raise Incomplete("per-element attribute ceiling")
}
guard attrs <= limits.xml_attributes else {
raise Incomplete("XML attribute limit")
}
} else if c == '>' {
break
}
j += 1
guard j - i + 1 <= 65536 else {
raise Incomplete("XML markup token limit")
}
}
if !closing {
nodes += 1
guard nodes <= limits.xml_nodes && depth + 1 <= limits.xml_depth else {
raise Incomplete("XML node/depth limit")
}
if j == 0 || text[j - 1] != '/' {
depth += 1
}
} else {
depth -= 1
}
i = j + 1
text_start = i
}
let reader = @xml.NamespaceReader::from_string(text)
let result : Array[Node] = []
let mut current = 0
let open_nodes : Array[Int] = []
while true {
let event = reader.read_event()
match event.kind {
Start(element) => {
open_nodes.push(result.length())
result.push({ element, depth: current, text_fragments: [], })
current += 1
}
Empty(element) =>
result.push({ element, depth: current, text_fragments: [], })
End(_) => {
current -= 1
ignore(open_nodes.pop())
}
DocType(_) => raise Refused("DTD forbidden")
Decl(encoding~, ..) =>
if encoding is Some(e) && e != "UTF-8" && e != "utf-8" {
raise Refused("only UTF-8 XML supported")
}
PI(..) => raise Refused("processing instructions unsupported")
Text(value) => {
if metadata && value.trim() != "" {
raise Refused("text in package metadata")
}
if !metadata && open_nodes.length() > 0 {
result[open_nodes[open_nodes.length() - 1]].text_fragments.push(value)
}
}
CData(value) => {
if metadata {
raise Refused("CDATA in package metadata")
}
if open_nodes.length() > 0 {
result[open_nodes[open_nodes.length() - 1]].text_fragments.push(value)
}
}
Eof => break
_ => ()
}
}
result
}
///|
fn attr(node : Node, name : String) -> String raise {
for a in node.element.attributes {
if a.name.namespace_uri is None && a.name.local_name == name {
return a.value
}
}
raise Refused("required XML attribute missing: " + name)
}
///|
fn attr_opt(node : Node, name : String) -> String? {
for a in node.element.attributes {
if a.name.namespace_uri is None && a.name.local_name == name {
return Some(a.value)
}
}
None
}
///|
fn metadata_nodes(
data : BytesView,
limits : Limits,
ns : String,
root : String,
children : Array[String],
) -> Array[Node] raise {
let nodes = read_xml(data, limits, metadata=true)
guard nodes.length() > 0 &&
nodes[0].depth == 0 &&
nodes[0].element.name.namespace_uri == Some(ns) &&
nodes[0].element.name.local_name == root else {
raise Refused("invalid metadata root")
}
validate_metadata_attributes(nodes[0], [])
for i in 1..