1pub mod container;
8pub mod gainmap;
9
10use alloc::{
11 collections::BTreeMap,
12 format,
13 string::{String, ToString},
14 vec::Vec,
15};
16use roxmltree::{Document, Node, ParsingOptions};
17
18pub(crate) const RDF: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
19
20const MAX_NESTING: usize = 64;
22
23fn nests_deeper_than(xml: &str, limit: usize) -> bool {
29 fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
30 haystack.windows(needle.len()).position(|w| w == needle)
31 }
32 let b = xml.as_bytes();
33 let mut i = 0;
34 let mut depth = 0usize;
35 while let Some(lt) = b[i..].iter().position(|&c| c == b'<') {
36 i += lt;
37 let rest = &b[i..];
38 let skip = if rest.starts_with(b"<!--") {
39 find(rest, b"-->").map(|e| e + 3)
40 } else if rest.starts_with(b"<![CDATA[") {
41 find(rest, b"]]>").map(|e| e + 3)
42 } else if rest.starts_with(b"<?") {
43 find(rest, b"?>").map(|e| e + 2)
44 } else if rest.starts_with(b"<!") {
45 find(rest, b">").map(|e| e + 1)
46 } else {
47 None
48 };
49 if rest.starts_with(b"<!") || rest.starts_with(b"<?") {
50 match skip {
51 Some(n) => {
52 i += n;
53 continue;
54 }
55 None => return false,
56 }
57 }
58 let mut quote = None;
60 let mut end = None;
61 for (j, &c) in rest.iter().enumerate().skip(1) {
62 match quote {
63 Some(q) if c == q => quote = None,
64 Some(_) => {}
65 None if c == b'"' || c == b'\'' => quote = Some(c),
66 None if c == b'>' => {
67 end = Some(j);
68 break;
69 }
70 None => {}
71 }
72 }
73 let Some(end) = end else {
74 return false;
75 };
76 if rest.get(1) == Some(&b'/') {
77 depth = depth.saturating_sub(1);
78 } else if rest[end - 1] != b'/' {
79 depth += 1;
80 if depth > limit {
81 return true;
82 }
83 }
84 i += end + 1;
85 }
86 false
87}
88
89#[derive(Clone, Debug, PartialEq, Eq)]
91#[non_exhaustive]
92pub enum Error {
93 Limit,
95 Xml(String),
97 Duplicate(String),
99 Structure(String),
101}
102impl core::fmt::Display for Error {
103 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104 write!(f, "XMP: {self:?}")
105 }
106}
107impl core::error::Error for Error {}
108
109#[derive(Clone, Debug, PartialEq, Eq)]
112#[non_exhaustive]
113pub struct Field {
114 pub(crate) path: String,
115 pub(crate) value: String,
116}
117impl Field {
118 pub fn path(&self) -> &str {
120 &self.path
121 }
122 pub fn value(&self) -> &str {
124 &self.value
125 }
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
130pub struct Property {
131 namespace: String,
132 name: String,
133 value: String,
134}
135impl Property {
136 pub fn namespace(&self) -> &str {
138 &self.namespace
139 }
140 pub fn name(&self) -> &str {
142 &self.name
143 }
144 pub fn value(&self) -> &str {
146 &self.value
147 }
148}
149
150pub(crate) struct SpannedProperty {
152 pub property: Property,
153 pub source: core::ops::Range<usize>,
154 pub attribute: bool,
155}
156
157#[derive(Debug)]
159pub struct Packet<'a> {
160 doc: Document<'a>,
161}
162impl<'a> Packet<'a> {
163 pub fn parse(xml: &'a str) -> Result<Self, Error> {
166 if xml.len() > 16 * 1024 * 1024 {
167 return Err(Error::Limit);
168 }
169 if nests_deeper_than(xml, MAX_NESTING) {
174 return Err(Error::Limit);
175 }
176 let doc = Document::parse_with_options(
177 xml,
178 ParsingOptions {
179 allow_dtd: false,
180 nodes_limit: 65_536,
181 ..Default::default()
182 },
183 )
184 .map_err(|e| Error::Xml(e.to_string()))?;
185 if doc
186 .descendants()
187 .any(|n| n.ancestors().take(66).count() > 65)
188 {
189 return Err(Error::Limit);
190 }
191 Ok(Self { doc })
192 }
193
194 pub fn property(&self, namespace: &str, name: &str) -> Result<Option<Vec<String>>, Error> {
199 let mut result = None;
200 for d in self.descriptions() {
201 let attribute = d.attribute((namespace, name));
202 let children: Vec<_> = d
203 .children()
204 .filter(|n| n.has_tag_name((namespace, name)))
205 .collect();
206 if attribute.is_none() && children.is_empty() {
207 continue;
208 }
209 empty_subject(d)?;
210 if let Some(value) = attribute
211 && result.replace(alloc::vec![value.into()]).is_some()
212 {
213 return Err(Error::Duplicate(name.into()));
214 }
215 for n in children {
216 if result.replace(property_values(n)?).is_some() {
217 return Err(Error::Duplicate(name.into()));
218 }
219 }
220 }
221 Ok(result)
222 }
223
224 fn descriptions(&self) -> impl Iterator<Item = Node<'_, 'a>> {
225 self.doc.descendants().filter(|n| {
226 n.has_tag_name((RDF, "Description"))
227 && n.parent().is_some_and(|p| {
228 p.has_tag_name((RDF, "RDF"))
229 && (p == self.doc.root_element()
230 || p.parent().is_some_and(|root| {
231 root == self.doc.root_element()
232 && root.has_tag_name(("adobe:ns:meta/", "xmpmeta"))
233 }))
234 })
235 })
236 }
237
238 pub fn resource_sequence(
242 &self,
243 namespace: &str,
244 name: &str,
245 item_namespace: &str,
246 item_name: &str,
247 ) -> Result<Vec<Vec<Property>>, Error> {
248 self.resource_sequence_spanned(namespace, name, item_namespace, item_name, 4096)
249 .map(|items| {
250 items
251 .into_iter()
252 .map(|fields| fields.into_iter().map(|f| f.property).collect())
253 .collect()
254 })
255 }
256
257 pub(crate) fn resource_sequence_spanned(
258 &self,
259 namespace: &str,
260 name: &str,
261 item_namespace: &str,
262 item_name: &str,
263 max_items: usize,
264 ) -> Result<Vec<Vec<SpannedProperty>>, Error> {
265 if self
266 .descriptions()
267 .any(|d| d.attribute((namespace, name)).is_some())
268 {
269 return Err(Error::Structure(name.into()));
270 }
271 let nodes: Vec<_> = self
272 .descriptions()
273 .flat_map(|d| d.children())
274 .filter(|n| n.has_tag_name((namespace, name)))
275 .collect();
276 if nodes.len() > 1 {
277 return Err(Error::Duplicate(name.into()));
278 }
279 let Some(node) = nodes.first() else {
280 return Ok(Vec::new());
281 };
282 empty_subject(node.parent().ok_or_else(|| Error::Structure(name.into()))?)?;
283 structural_node(*node, false)?;
284 let seq = only_child(*node)
285 .filter(|n| n.has_tag_name((RDF, "Seq")))
286 .ok_or_else(|| Error::Structure(name.into()))?;
287 structural_node(seq, false)?;
288 let mut items = Vec::new();
289 for li in seq.children().filter(Node::is_element) {
290 if !li.has_tag_name((RDF, "li")) {
291 return Err(Error::Structure(name.into()));
292 }
293 if items.len() >= max_items {
294 return Err(Error::Limit);
295 }
296 structural_node(li, true)?;
297 let item = only_child(li).ok_or_else(|| Error::Structure(name.into()))?;
298 if !item.has_tag_name((item_namespace, item_name))
299 || item
300 .children()
301 .any(|c| c.is_text() && c.text().is_some_and(|s| !s.trim().is_empty()))
302 {
303 return Err(Error::Structure(name.into()));
304 }
305 let mut fields = Vec::new();
306 for a in item.attributes() {
307 if a.namespace() == Some(RDF) {
308 return Err(Error::Structure(name.into()));
309 }
310 fields.push(SpannedProperty {
311 property: Property {
312 namespace: a.namespace().unwrap_or("").into(),
313 name: a.name().into(),
314 value: a.value().into(),
315 },
316 source: a.range(),
317 attribute: true,
318 });
319 }
320 for child in item.children().filter(Node::is_element) {
321 fields.push(SpannedProperty {
322 property: Property {
323 namespace: child.tag_name().namespace().unwrap_or("").into(),
324 name: child.tag_name().name().into(),
325 value: plain_text(child)?,
326 },
327 source: child.range(),
328 attribute: false,
329 });
330 }
331 if fields.len() > 256 {
332 return Err(Error::Limit);
333 }
334 for (i, a) in fields.iter().enumerate() {
335 if fields[..i].iter().any(|b| {
336 a.property.namespace == b.property.namespace
337 && a.property.name == b.property.name
338 }) {
339 return Err(Error::Duplicate(a.property.name.clone()));
340 }
341 }
342 items.push(fields);
343 }
344 Ok(items)
345 }
346
347 pub fn fields(&self) -> Result<Vec<Field>, Error> {
352 let mut out = Vec::new();
353 let mut paths = BTreeMap::<u32, String>::new();
354 let mut occurrences = BTreeMap::<(u32, String), usize>::new();
355 let mut budget = 0usize;
356 for n in self.doc.descendants().filter(|n| !n.is_root()) {
357 let parent = n.parent().map(|p| p.id().get()).unwrap_or(0);
358 let name = if n.is_element() {
359 format!(
360 "{{{}}}{}",
361 n.tag_name().namespace().unwrap_or(""),
362 n.tag_name().name()
363 )
364 } else if n.is_comment() {
365 "#comment".into()
366 } else if n.is_pi() {
367 "#pi".into()
368 } else {
369 "#text".into()
370 };
371 let index = occurrences.entry((parent, name.clone())).or_default();
372 let path = format!(
373 "{}/{name}[{index}]",
374 paths.get(&parent).map(String::as_str).unwrap_or("")
375 );
376 *index += 1;
377 budget = budget.saturating_add(path.len() * 2);
378 if budget > 16 * 1024 * 1024 {
379 return Err(Error::Limit);
380 }
381 paths.insert(n.id().get(), path.clone());
382 if n.is_element() {
383 out.push(Field {
384 path: path.clone(),
385 value: String::new(),
386 });
387 for a in n.attributes() {
388 budget = budget.saturating_add(
389 path.len()
390 + a.namespace().unwrap_or("").len()
391 + a.name().len()
392 + a.value().len(),
393 );
394 if budget > 16 * 1024 * 1024 {
395 return Err(Error::Limit);
396 }
397 out.push(Field {
398 path: format!("{path}/@{{{}}}{}", a.namespace().unwrap_or(""), a.name()),
399 value: a.value().into(),
400 });
401 }
402 } else if let Some(pi) = n.pi() {
403 out.push(Field {
404 path,
405 value: format!("{} {}", pi.target, pi.value.unwrap_or("")),
406 });
407 } else if let Some(text) = n.text() {
408 if n.is_comment()
410 || !text.trim().is_empty()
411 || n.parent()
412 .is_some_and(|p| !p.children().any(|c| c.is_element()))
413 {
414 out.push(Field {
415 path,
416 value: text.into(),
417 });
418 }
419 }
420 }
421 out.sort_by(|a, b| a.path.cmp(&b.path));
422 Ok(out)
423 }
424}
425fn only_child<'a, 'input>(n: Node<'a, 'input>) -> Option<Node<'a, 'input>> {
426 let mut it = n.children().filter(Node::is_element);
427 let first = it.next()?;
428 it.next().is_none().then_some(first)
429}
430fn plain_text(n: Node<'_, '_>) -> Result<String, Error> {
431 if n.children().any(|c| c.is_element()) || n.attributes().len() != 0 {
432 return Err(Error::Structure(n.tag_name().name().into()));
433 }
434 Ok(n.children()
435 .filter(Node::is_text)
436 .filter_map(|c| c.text())
437 .collect())
438}
439fn property_values(n: Node<'_, '_>) -> Result<Vec<String>, Error> {
440 if n.children().any(|c| c.is_element()) {
441 if n.attributes().len() != 0
442 || n.children()
443 .any(|c| c.is_text() && c.text().is_some_and(|s| !s.trim().is_empty()))
444 {
445 return Err(Error::Structure(n.tag_name().name().into()));
446 }
447 let seq = only_child(n)
448 .filter(|c| c.has_tag_name((RDF, "Seq")))
449 .ok_or_else(|| Error::Structure(n.tag_name().name().into()))?;
450 structural_node(seq, false)?;
451 let mut values = Vec::new();
452 for li in seq.children().filter(Node::is_element) {
453 if !li.has_tag_name((RDF, "li")) {
454 return Err(Error::Structure(n.tag_name().name().into()));
455 }
456 values.push(plain_text(li)?);
457 }
458 Ok(values)
459 } else {
460 Ok(alloc::vec![plain_text(n)?])
461 }
462}
463
464fn structural_node(n: Node<'_, '_>, allow_resource: bool) -> Result<(), Error> {
465 if n.attributes().any(|a| {
466 !(allow_resource
467 && a.namespace() == Some(RDF)
468 && a.name() == "parseType"
469 && a.value() == "Resource")
470 }) || n
471 .children()
472 .any(|c| c.is_text() && c.text().is_some_and(|s| !s.trim().is_empty()))
473 {
474 return Err(Error::Structure(n.tag_name().name().into()));
475 }
476 Ok(())
477}
478
479fn empty_subject(n: Node<'_, '_>) -> Result<(), Error> {
480 if n.attributes()
481 .any(|a| a.namespace() == Some(RDF) && (a.name() != "about" || !a.value().is_empty()))
482 {
483 return Err(Error::Structure("RDF subject/qualifier".into()));
484 }
485 Ok(())
486}
487
488#[cfg(test)]
489mod nesting_tests {
490 extern crate std;
491 use super::*;
492
493 fn parse_on_small_stack(xml: String) -> Result<(), Error> {
495 std::thread::Builder::new()
496 .stack_size(256 * 1024)
497 .spawn(move || Packet::parse(&xml).map(|_| ()))
498 .unwrap()
499 .join()
500 .expect("parse overflowed a 256 KiB stack")
501 }
502
503 #[test]
504 fn deep_nesting_is_rejected_without_exhausting_the_stack() {
505 for depth in [100usize, 8_848, 200_000] {
508 let open = "<a>".repeat(depth);
509 let closed = format!("<x>{open}{}</x>", "</a>".repeat(depth));
510 assert_eq!(parse_on_small_stack(closed), Err(Error::Limit), "{depth}");
511 assert_eq!(
512 parse_on_small_stack(format!("<x>{open}")),
513 Err(Error::Limit),
514 "{depth}"
515 );
516 }
517 }
518
519 #[test]
520 fn the_scan_counts_elements_not_markup_that_looks_like_them() {
521 let inner = r#"<b c='x > y' d="a/>b"/><!-- <c><c> --><![CDATA[<d><d>]]><?pi <e>?>"#;
524 let mut xml = String::from("<a>");
525 for _ in 0..62 {
526 xml.push_str("<n>");
527 }
528 xml.push_str(inner);
529 for _ in 0..62 {
530 xml.push_str("</n>");
531 }
532 xml.push_str("</a>");
533 assert!(!nests_deeper_than(&xml, MAX_NESTING));
534 assert!(nests_deeper_than(&xml, 62));
535 assert!(Packet::parse(&xml).is_ok());
536 }
537}