1use std::collections::HashSet;
13
14use super::{u64_from_index, DecodeContext, DepthGuard};
15use crate::CodecError;
16
17const MAX_XML_TAG_ATTRIBUTES: u64 = 256;
20const MAX_XML_NAMESPACE_PREFIXES: u64 = 256;
24const XML_NODE_BYTES: u64 = 72;
27const XML_ATTRIBUTE_BYTES: u64 = 72;
29const XML_TEMP_ATTRIBUTE_BYTES: u64 = 80;
31const XML_NAMESPACE_BYTES: u64 = 64;
33const XML_LEVEL_BYTES: u64 = 3 * (16 + 4 + 8);
38const XML_TEXT_PIECE_BYTES: u64 = 3 * 24;
41const XML_COPY_BYTES: u64 = 32 + 24;
44const XML_COPY_FACTOR: u64 = 5;
49const XML_FIXED_BYTES: u64 =
52 1024 + 16 * 1024 + 3 * MAX_XML_TAG_ATTRIBUTES / 2 * XML_TEMP_ATTRIBUTE_BYTES;
53const JSON_VALUE_BYTES: u64 = 64;
56const JSON_MAP_NODE_BYTES: u64 = 768;
59const JSON_STRING_BYTES: u64 = 32;
61const JSON_FIXED_BYTES: u64 = 1024;
63
64impl DecodeContext<'_> {
65 fn at_depth<T>(
67 &self,
68 depth: u64,
69 operation: &'static str,
70 run: impl FnOnce() -> Result<T, CodecError>,
71 ) -> Result<T, CodecError> {
72 let mut guards: Vec<DepthGuard<'_>> = Vec::new();
73 for _ in 0..depth {
74 guards.push(self.enter_nested(operation)?);
75 }
76 let result = run();
77 drop(guards);
78 result
79 }
80
81 fn tree_malformed(error: impl std::fmt::Display) -> CodecError {
82 CodecError::malformed(format_args!("{error}"))
83 }
84
85 fn deserialize_json<T: serde::de::DeserializeOwned>(text: &str) -> Result<T, CodecError> {
88 let mut deserializer = serde_json::Deserializer::from_str(text);
89 deserializer.disable_recursion_limit();
90 T::deserialize(&mut deserializer)
91 .and_then(|value| deserializer.end().map(|()| value))
92 .map_err(Self::tree_malformed)
93 }
94
95 fn admit_json(&self, text: &str, operation: &'static str) -> Result<u64, CodecError> {
98 let shape = json_shape(text);
99 let allowance = self.per_expand_allowance();
100 if json_storage_bytes(u64_from_index(text.len()), &shape)
101 .is_none_or(|bytes| bytes > allowance)
102 {
103 return Err(tree_too_large("JSON", allowance, operation));
104 }
105 Ok(shape.depth)
106 }
107
108 pub fn parse_xml<'input>(
112 &self,
113 text: &'input str,
114 operation: &'static str,
115 ) -> Result<roxmltree::Document<'input>, CodecError> {
116 let shape = xml_shape(text);
117 if shape.tag_attributes > MAX_XML_TAG_ATTRIBUTES {
118 return Err(CodecError::NotImplemented(format!(
119 "{operation}: an XML tag has more than {MAX_XML_TAG_ATTRIBUTES} attributes"
120 )));
121 }
122 let prefixes = u64_from_index(shape.prefixes.len());
123 if prefixes > MAX_XML_NAMESPACE_PREFIXES {
124 return Err(CodecError::NotImplemented(format!(
125 "{operation}: the XML text declares more than \
126 {MAX_XML_NAMESPACE_PREFIXES} namespace prefixes"
127 )));
128 }
129 let allowance = self.per_expand_allowance();
130 let nodes_limit = xml_storage_bytes(&shape, prefixes)
131 .and_then(|bytes| allowance.checked_sub(bytes))
132 .and_then(|rest| xml_nodes_limit(rest, shape.less_than))
133 .ok_or_else(|| tree_too_large("XML", allowance, operation))?;
134 let mut options = roxmltree::ParsingOptions::default();
136 if let Ok(limit) = u32::try_from(nodes_limit) {
137 options.nodes_limit = limit;
138 }
139 self.at_depth(shape.depth, operation, || {
141 roxmltree::Document::parse_with_options(text, options).map_err(|error| match error {
142 roxmltree::Error::NodesLimitReached => tree_too_large("XML", allowance, operation),
143 error => Self::tree_malformed(error),
144 })
145 })
146 }
147
148 pub fn parse_json_value(
150 &self,
151 text: &str,
152 operation: &'static str,
153 ) -> Result<serde_json::Value, CodecError> {
154 let depth = self.admit_json(text, operation)?;
155 self.at_depth(depth, operation, || Self::deserialize_json(text))
156 }
157
158 pub fn parse_json<T: serde::de::DeserializeOwned>(
162 &self,
163 text: &str,
164 operation: &'static str,
165 ) -> Result<T, CodecError> {
166 let depth = self.admit_json(text, operation)?;
167 self.at_depth(depth, operation, || Self::deserialize_json(text))
168 }
169}
170
171fn tree_too_large(kind: &str, allowance: u64, operation: &str) -> CodecError {
172 CodecError::NotImplemented(format!(
173 "{operation}: the {kind} tree could need more than the \
174 {allowance}-byte per-expansion allowance"
175 ))
176}
177
178#[derive(Clone, Copy)]
179enum XmlScan {
180 Text,
181 Tag { quote: u8 },
182 Comment,
183 Cdata,
184 Instruction,
185 Declaration,
186}
187
188#[derive(Default)]
191struct XmlShape<'text> {
192 less_than: u64,
194 equals: u64,
196 attributes: u64,
198 tag_attributes: u64,
200 declarations: u64,
202 prefixes: HashSet<&'text [u8]>,
205 depth: u64,
209 cdata: u64,
211 copies: u64,
215 copied: u64,
217}
218
219impl<'text> XmlShape<'text> {
220 fn copy(&mut self, copied: bool, source: &[u8]) {
221 if copied {
222 self.copies += 1;
223 self.copied += u64_from_index(source.len());
224 }
225 }
226
227 fn contains(&mut self, open: u64) {
229 self.depth = self.depth.max(open + 1);
230 }
231
232 fn prefix(&mut self, name: &'text [u8]) {
234 if u64_from_index(self.prefixes.len()) <= MAX_XML_NAMESPACE_PREFIXES {
235 self.prefixes.insert(name);
236 }
237 }
238}
239
240fn xml_shape(text: &str) -> XmlShape<'_> {
245 let bytes = text.as_bytes();
246 let mut shape = XmlShape::default();
247 let mut state = XmlScan::Text;
248 let mut tag_attributes = 0_u64;
249 let mut prefix_start = None::<usize>;
250 let mut open = 0_u64;
253 let mut closing = false;
254 let mut last = 0_u8;
255 let mut run_start = 0_usize;
258 let mut copied = false;
259 for (index, &byte) in bytes.iter().enumerate() {
260 shape.less_than += u64::from(byte == b'<');
261 shape.equals += u64::from(byte == b'=');
262 let tail = bytes.get(index..).unwrap_or_default();
263 state = match state {
264 XmlScan::Text if byte == b'<' => {
265 if tail.starts_with(b"<![CDATA[") {
266 shape.cdata += 1;
267 shape.contains(open);
268 copied = true;
269 XmlScan::Cdata
270 } else {
271 shape.copy(copied, bytes.get(run_start..index).unwrap_or_default());
272 copied = false;
273 if tail.starts_with(b"<!--") {
274 shape.contains(open);
275 XmlScan::Comment
276 } else if tail.starts_with(b"<?") {
277 shape.contains(open);
278 XmlScan::Instruction
279 } else if tail.starts_with(b"<!") {
280 XmlScan::Declaration
281 } else {
282 tag_attributes = 0;
283 closing = tail.get(1) == Some(&b'/');
284 XmlScan::Tag { quote: 0 }
285 }
286 }
287 }
288 XmlScan::Text => {
289 copied |= matches!(byte, b'&' | b'\r');
290 if open != 0 {
292 shape.contains(open);
293 }
294 XmlScan::Text
295 }
296 XmlScan::Tag { quote: 0 } => {
297 let delimiter =
298 byte.is_ascii_whitespace() || matches!(byte, b'=' | b'/' | b'>' | b'\'' | b'"');
299 if let Some(start) = prefix_start.filter(|_| delimiter) {
300 shape.prefix(bytes.get(start..index).unwrap_or_default());
301 prefix_start = None;
302 }
303 if tail.starts_with(b"xmlns") {
304 shape.declarations += 1;
305 if prefix_start.is_none() && tail.get(5) == Some(&b':') {
306 prefix_start = Some(index + 6);
307 }
308 }
309 let next = match byte {
310 b'\'' | b'"' => {
311 run_start = index + 1;
312 copied = false;
313 XmlScan::Tag { quote: byte }
314 }
315 b'>' => {
316 if closing {
317 if let Some(outer) = open.checked_sub(1) {
318 open = outer;
319 }
320 } else {
321 shape.contains(open);
322 if last != b'/' {
323 open += 1;
324 }
325 }
326 run_start = index + 1;
327 XmlScan::Text
328 }
329 b'=' => {
330 tag_attributes += 1;
331 shape.attributes += 1;
332 shape.tag_attributes = shape.tag_attributes.max(tag_attributes);
333 XmlScan::Tag { quote: 0 }
334 }
335 _ => XmlScan::Tag { quote: 0 },
336 };
337 if !byte.is_ascii_whitespace() {
338 last = byte;
339 }
340 next
341 }
342 XmlScan::Tag { quote } if byte == quote => {
343 shape.copy(copied, bytes.get(run_start..index).unwrap_or_default());
344 copied = false;
345 last = byte;
346 XmlScan::Tag { quote: 0 }
347 }
348 XmlScan::Tag { quote } => {
349 copied |= matches!(byte, b'&' | b'\t' | b'\n' | b'\r');
350 XmlScan::Tag { quote }
351 }
352 XmlScan::Comment if tail.starts_with(b"-->") => {
353 run_start = index + 3;
354 XmlScan::Text
355 }
356 XmlScan::Cdata if tail.starts_with(b"]]>") => XmlScan::Text,
357 XmlScan::Instruction if tail.starts_with(b"?>") => {
358 run_start = index + 2;
359 XmlScan::Text
360 }
361 XmlScan::Declaration if byte == b'>' => {
362 run_start = index + 1;
363 XmlScan::Text
364 }
365 other => other,
366 };
367 }
368 shape.copy(copied, bytes.get(run_start..).unwrap_or_default());
370 if let Some(start) = prefix_start {
371 shape.prefix(bytes.get(start..).unwrap_or_default());
372 }
373 shape
374}
375
376fn xml_storage_bytes(shape: &XmlShape<'_>, prefixes: u64) -> Option<u64> {
384 let namespace_indices = shape
385 .declarations
386 .checked_mul(prefixes.checked_add(3)?)?
387 .checked_add(1)?;
388 XML_FIXED_BYTES
389 .checked_add(shape.less_than.checked_mul(XML_NODE_BYTES)?)?
390 .checked_add(
391 shape
392 .equals
393 .checked_add(shape.attributes)?
394 .checked_mul(XML_ATTRIBUTE_BYTES)?,
395 )?
396 .checked_add(shape.depth.checked_add(2)?.checked_mul(XML_LEVEL_BYTES)?)?
397 .checked_add(
398 shape
399 .cdata
400 .checked_mul(2)?
401 .checked_add(1)?
402 .checked_mul(XML_TEXT_PIECE_BYTES)?,
403 )?
404 .checked_add(shape.copied.checked_mul(XML_COPY_FACTOR)?)?
405 .checked_add(shape.copies.checked_mul(XML_COPY_BYTES)?)?
406 .checked_add(
407 shape
408 .declarations
409 .checked_add(1)?
410 .checked_mul(2 * XML_NAMESPACE_BYTES)?,
411 )?
412 .checked_add(namespace_indices.checked_mul(3 * 2)?)
413}
414
415fn xml_nodes_limit(rest: u64, reserved: u64) -> Option<u64> {
420 let grown = rest.checked_add(reserved.checked_mul(XML_NODE_BYTES)?)? / (3 * XML_NODE_BYTES);
421 Some(if grown > reserved {
422 grown
423 } else {
424 reserved.min(rest / XML_NODE_BYTES)
425 })
426}
427
428#[derive(Default)]
431struct JsonShape {
432 depth: u64,
433 values: u64,
435 members: u64,
437 maps: u64,
438 strings: u64,
439}
440
441fn json_shape(text: &str) -> JsonShape {
447 let mut depth = 0_u64;
448 let mut visible = JsonShape {
449 values: 1,
450 ..JsonShape::default()
451 };
452 let mut every = JsonShape {
453 values: 1,
454 ..JsonShape::default()
455 };
456 let mut raw = false;
457 let mut string = JsonStringScan::default();
458 for &byte in text.as_bytes() {
459 let opening = u64::from(matches!(byte, b'[' | b'{'));
460 let escape = u64::from(byte == b'\\');
461 every.depth += opening + escape;
462 every.values += u64::from(byte == b',') + opening + escape;
463 every.members += u64::from(byte == b':') + escape;
464 every.maps += u64::from(byte == b'{') + escape;
465 every.strings += u64::from(byte == b'"') + escape;
466 if string.active {
467 raw |= string.byte(byte);
468 continue;
469 }
470 match byte {
471 b'"' => {
472 visible.strings += 1;
473 string = JsonStringScan {
474 active: true,
475 possible: true,
476 ..JsonStringScan::default()
477 }
478 }
479 b'[' | b'{' => {
480 depth += 1;
481 visible.depth = visible.depth.max(depth);
482 visible.values += 1;
483 visible.maps += u64::from(byte == b'{');
484 }
485 b']' | b'}' if depth != 0 => depth -= 1,
486 b',' => visible.values += 1,
487 b':' => visible.members += 1,
488 _ => {}
489 }
490 }
491 if raw {
492 every
493 } else {
494 visible
495 }
496}
497
498fn json_storage_bytes(length: u64, shape: &JsonShape) -> Option<u64> {
503 let map_nodes = shape.maps.checked_add(shape.members / 5)?;
504 JSON_FIXED_BYTES
505 .checked_add(length.checked_mul(4)?)?
506 .checked_add(shape.values.checked_mul(JSON_VALUE_BYTES)?)?
507 .checked_add(map_nodes.checked_mul(JSON_MAP_NODE_BYTES)?)?
508 .checked_add(shape.strings.checked_mul(JSON_STRING_BYTES)?)
509}
510
511const RAW_VALUE_TOKEN: &[u8] = b"$serde_json::private::RawValue";
512
513#[derive(Default)]
514struct JsonStringScan {
515 active: bool,
516 escaped: bool,
517 unicode_digits: u8,
518 unicode_value: u32,
519 matched: usize,
520 possible: bool,
521}
522
523impl JsonStringScan {
524 fn decoded(&mut self, byte: Option<u8>) {
525 if byte.is_some() && byte == RAW_VALUE_TOKEN.get(self.matched).copied() {
526 self.matched += 1;
527 } else {
528 self.possible = false;
529 }
530 }
531
532 fn byte(&mut self, byte: u8) -> bool {
534 if self.unicode_digits != 0 {
535 if let Some(digit) = char::from(byte).to_digit(16) {
536 self.unicode_value = self.unicode_value * 16 + digit;
537 } else {
538 self.possible = false;
539 }
540 self.unicode_digits -= 1;
541 if self.unicode_digits == 0 {
542 self.decoded(u8::try_from(self.unicode_value).ok());
543 }
544 } else if self.escaped {
545 self.escaped = false;
546 if byte == b'u' {
547 self.unicode_digits = 4;
548 self.unicode_value = 0;
549 } else {
550 self.decoded(match byte {
551 b'"' | b'\\' | b'/' => Some(byte),
552 b'b' => Some(8),
553 b'f' => Some(12),
554 b'n' => Some(b'\n'),
555 b'r' => Some(b'\r'),
556 b't' => Some(b'\t'),
557 _ => None,
558 });
559 }
560 } else if byte == b'\\' {
561 self.escaped = true;
562 } else if byte == b'"' {
563 self.active = false;
564 return self.possible && self.matched == RAW_VALUE_TOKEN.len();
565 } else {
566 self.decoded(Some(byte));
567 }
568 false
569 }
570}
571
572#[cfg(test)]
573mod tests {
574 use std::fmt::Write as _;
575
576 use super::{
577 json_shape, xml_nodes_limit, xml_shape, xml_storage_bytes, MAX_XML_NAMESPACE_PREFIXES,
578 MAX_XML_TAG_ATTRIBUTES, XML_NODE_BYTES,
579 };
580 use crate::decode::{
581 u64_from_index, DecodeArena, DecodeContext, DecodePolicy, ResourceDimension,
582 };
583 use crate::CodecError;
584
585 mod heap {
587 use std::alloc::{GlobalAlloc, Layout, System};
588 use std::cell::Cell;
589
590 use crate::decode::u64_from_index;
591
592 thread_local! {
593 static TRACK: Cell<bool> = const { Cell::new(false) };
594 static LIVE: Cell<i128> = const { Cell::new(0) };
595 static PEAK: Cell<i128> = const { Cell::new(0) };
596 }
597
598 struct PeakAllocator;
599
600 #[global_allocator]
601 static ALLOCATOR: PeakAllocator = PeakAllocator;
602
603 fn record(acquired: usize, released: usize) {
605 if TRACK.try_with(Cell::get).unwrap_or(false) {
606 let _recorded = LIVE.try_with(|live| {
607 let held = live.get() + i128::from(u64_from_index(acquired));
608 let _peaked = PEAK.try_with(|peak| peak.set(peak.get().max(held)));
609 live.set(held - i128::from(u64_from_index(released)));
610 });
611 }
612 }
613
614 unsafe impl GlobalAlloc for PeakAllocator {
615 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
616 let pointer = unsafe { System.alloc(layout) };
617 if !pointer.is_null() {
618 record(layout.size(), 0);
619 }
620 pointer
621 }
622
623 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
624 let pointer = unsafe { System.alloc_zeroed(layout) };
625 if !pointer.is_null() {
626 record(layout.size(), 0);
627 }
628 pointer
629 }
630
631 unsafe fn realloc(&self, pointer: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
632 let resized = unsafe { System.realloc(pointer, layout, new_size) };
633 if !resized.is_null() {
634 record(new_size, layout.size());
635 }
636 resized
637 }
638
639 unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
640 unsafe { System.dealloc(pointer, layout) };
641 record(0, layout.size());
642 }
643 }
644
645 pub(super) fn peak<R>(action: impl FnOnce() -> R) -> (R, u64) {
648 LIVE.with(|live| live.set(0));
649 PEAK.with(|peak| peak.set(0));
650 TRACK.with(|track| track.set(true));
651 let result = action();
652 TRACK.with(|track| track.set(false));
653 let peak = u64::try_from(PEAK.with(Cell::get)).expect("peak fits u64");
654 (result, peak)
655 }
656 }
657
658 fn xml_node_storage(nodes: u64, reserved: u64) -> u64 {
660 if nodes <= reserved {
661 nodes * XML_NODE_BYTES
662 } else {
663 3 * nodes * XML_NODE_BYTES - reserved * XML_NODE_BYTES
664 }
665 }
666
667 fn xml_estimate(text: &str, nodes: u64) -> u64 {
669 let shape = xml_shape(text);
670 let storage = xml_storage_bytes(&shape, u64_from_index(shape.prefixes.len()))
671 .expect("storage fits u64");
672 storage + xml_node_storage(nodes, shape.less_than)
673 }
674
675 fn repeated(count: usize, part: impl Fn(usize) -> String) -> String {
676 (0..count).map(part).collect()
677 }
678
679 fn realistic_document(objects: usize) -> String {
681 let declarations = repeated(objects, |index| {
682 format!("\n <Object type=\"Part::Feature\" name=\"Box{index}\" id=\"{index}\"/>")
683 });
684 let data = repeated(objects, |index| {
685 format!(
686 "\n <Object name=\"Box{index}\">\n <Properties Count=\"2\">\
687 \n <Property name=\"Label\" type=\"App::PropertyString\">\
688 \n <String value=\"Box & {index}\"/>\
689 \n </Property>\
690 \n <Property name=\"Length\" type=\"App::PropertyLength\">\
691 \n <Float value=\"10.0\"/>\
692 \n </Property>\n </Properties>\n </Object>"
693 )
694 });
695 format!(
696 "<?xml version='1.0' encoding='utf-8'?>\n<Document SchemaVersion=\"4\">\
697 \n <Objects Count=\"{objects}\">{declarations}\n </Objects>\
698 \n <ObjectData Count=\"{objects}\">{data}\n </ObjectData>\n</Document>\n"
699 )
700 }
701
702 #[derive(Debug, serde::Deserialize, PartialEq)]
703 struct JsonRecord {
704 name: String,
705 values: Vec<u64>,
706 }
707
708 fn with_depth<T>(depth: u64, run: impl FnOnce(&DecodeContext<'_>) -> T) -> T {
709 let arena = DecodeArena::new();
710 let mut policy = DecodePolicy::default();
711 policy.limits.max_recursion_depth = depth;
712 let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
713 run(&ctx)
714 }
715
716 fn with_expand_ceiling<T>(bytes: u64, run: impl FnOnce(&DecodeContext<'_>) -> T) -> T {
717 let arena = DecodeArena::new();
718 let mut policy = DecodePolicy::default();
719 policy.limits.max_decompressed_bytes_per_expand = bytes;
720 let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
721 run(&ctx)
722 }
723
724 fn is_depth_refusal<T: std::fmt::Debug>(result: &Result<T, CodecError>) -> bool {
725 matches!(result, Err(CodecError::ResourceLimit(limit)) if limit.dimension == ResourceDimension::RecursionDepth)
726 }
727
728 fn is_unsupported<T: std::fmt::Debug>(result: &Result<T, CodecError>, text: &str) -> bool {
729 matches!(result, Err(CodecError::NotImplemented(message)) if message.contains(text))
730 }
731
732 #[test]
733 fn xml_parses_and_rejects_malformed_text() {
734 with_depth(256, |ctx| {
735 let document = ctx
736 .parse_xml("<r a=\"1\"><c/></r>", "XML")
737 .expect("valid XML");
738 assert_eq!(document.root_element().attribute("a"), Some("1"));
739 assert!(matches!(
740 ctx.parse_xml("<r>", "XML"),
741 Err(CodecError::Malformed(_))
742 ));
743 });
744 }
745
746 #[test]
747 fn xml_depth_is_charged_to_the_session() {
748 let nested = "<a><b><c/></b></a>";
749 with_depth(3, |ctx| assert!(ctx.parse_xml(nested, "XML").is_ok()));
750 with_depth(2, |ctx| {
751 assert!(is_depth_refusal(&ctx.parse_xml(nested, "XML")));
752 });
753 }
754
755 #[test]
756 fn xml_shape_counts_tag_attributes_and_declared_prefixes() {
757 let shape = xml_shape(
758 "<?xml version=\"1.0\"?><r xmlns=\"u\" xmlns:p=\"u\" a=\"x=y\"><!-- b=c xmlns:q='u' -->\
759 <p:c xmlns:p ='v' d='1'/><![CDATA[e=f]]>g=h</r>",
760 );
761 assert_eq!(shape.attributes, 5);
762 assert_eq!(shape.tag_attributes, 3);
763 assert_eq!(shape.declarations, 3);
764 assert_eq!(shape.prefixes.into_iter().collect::<Vec<_>>(), [&b"p"[..]]);
765 }
766
767 #[test]
768 fn xml_shape_counts_reserved_slots_and_copied_text() {
769 let shape = xml_shape(
770 "<r a='x&y' b='p\tq' c='z=w'><!-- < = -->t<u<![CDATA[v]]>w<s/>\r\n</r>",
771 );
772 assert_eq!(shape.less_than, 6);
773 assert_eq!(shape.equals, 5);
774 assert_eq!(shape.cdata, 1);
775 assert_eq!(shape.copies, 4);
777 assert_eq!(shape.copied, 7 + 3 + 20 + 2);
778 }
779
780 #[test]
781 fn xml_shape_depth_matches_the_parsed_tree() {
782 for text in [
783 "<a/>",
784 "<a>x</a>",
785 "<a></a>",
786 "<a><b> </b></a>",
787 "<a><b/></a>",
788 "<a><b></b><c><d>e</d></c></a>",
789 "<!--c--><a/><?p?>",
790 "<?xml version='1.0'?><a><b><!--c--></b></a>",
791 "<a><b><?p x?></b></a>",
792 "<a><b><![CDATA[x]]></b></a>",
793 "<a b='/' c=\">\"><d e='1' /></a >",
794 "<p:a xmlns:p='u'><p:b>t</p:b></p:a>",
795 "<a>\r\n<b>\r\n</b>\r\n</a>\n",
796 ] {
797 let document = roxmltree::Document::parse(text).expect(text);
798 let depth = document
799 .descendants()
800 .map(|node| u64_from_index(node.ancestors().count()) - 1)
801 .max();
802 assert_eq!(Some(xml_shape(text).depth), depth, "{text}");
803 }
804 }
805
806 #[test]
807 fn xml_nodes_limit_inverts_the_node_storage() {
808 for reserved in [0, 1, 7, 1000] {
809 for rest in [
810 0,
811 71,
812 72,
813 215,
814 216,
815 1000,
816 144_000,
817 144_215,
818 144_216,
819 1 << 20,
820 ] {
821 let limit = xml_nodes_limit(rest, reserved).expect("limit fits u64");
822 assert!(
823 xml_node_storage(limit, reserved) <= rest,
824 "{reserved} {rest}"
825 );
826 assert!(
827 xml_node_storage(limit + 1, reserved) > rest,
828 "{reserved} {rest}"
829 );
830 }
831 }
832 }
833
834 #[test]
835 fn xml_storage_bound_covers_the_parse_peak() {
836 let count = 1 << 12;
837 let root_prefixes = repeated(100, |index| format!(" xmlns:q{index}='u'"));
838 let documents = [
839 ("elements", format!("<r>{}</r>", "<a/>".repeat(count))),
840 (
841 "attributes",
842 format!("<r>{}</r>", "<a b='1' c='2' d='3' e='4'/>".repeat(count)),
843 ),
844 (
845 "equals in text",
846 format!("<r>{}</r>", "=".repeat(16 * count)),
847 ),
848 (
849 "less-than in a comment",
850 format!("<r><!--{}--></r>", "<".repeat(16 * count)),
851 ),
852 (
853 "text around CDATA",
854 format!("<r>{}</r>", "x<![CDATA[y]]>".repeat(count)),
855 ),
856 (
857 "references in text",
858 format!("<r>{}</r>", "<a>&<</a>".repeat(count)),
859 ),
860 (
861 "references in values",
862 format!("<r>{}</r>", "<a b='&x '/>".repeat(count)),
863 ),
864 (
865 "carriage returns",
866 format!("<r>{}</r>", "a\r\n".repeat(4 * count)),
867 ),
868 (
869 "nesting",
870 format!("{}{}", "<a>".repeat(count), "</a>".repeat(count)),
871 ),
872 (
873 "namespaces in scope",
874 format!(
875 "<r xmlns='u'{root_prefixes}>{}</r>",
876 "<p:a xmlns:p='v' p:b='1'/>".repeat(count / 4)
877 ),
878 ),
879 ("realistic", realistic_document(count / 4)),
880 ];
881 std::thread::Builder::new()
883 .stack_size(64 << 20)
884 .spawn(move || {
885 with_depth(1 << 12, |ctx| {
886 for (label, text) in &documents {
887 let (document, peak) = heap::peak(|| ctx.parse_xml(text, "XML"));
888 let nodes = u64_from_index(document.expect(label).descendants().count());
889 let estimate = xml_estimate(text, nodes);
890 assert!(
891 peak <= estimate,
892 "{label}: peak {peak} > estimate {estimate}"
893 );
894 }
895 });
896 })
897 .expect("spawn the parsing thread")
898 .join()
899 .expect("every peak within its estimate");
900 }
901
902 #[cfg(target_pointer_width = "64")]
903 #[test]
904 fn xml_storage_bound_stays_near_a_realistic_parse_peak() {
905 let text = realistic_document(1 << 10);
906 with_depth(256, |ctx| {
907 let (document, peak) = heap::peak(|| ctx.parse_xml(&text, "XML"));
908 let nodes = u64_from_index(document.expect("realistic XML").descendants().count());
909 let estimate = xml_estimate(&text, nodes);
910 assert!(
911 estimate <= 2 * peak,
912 "estimate {estimate} > twice peak {peak}"
913 );
914 });
915 }
916
917 #[test]
918 fn xml_depth_refuses_before_the_parser_recurses() {
919 let levels = 1 << 15;
920 let nested = format!("{}{}", "<a>".repeat(levels), "</a>".repeat(levels));
921 with_depth(256, |ctx| {
922 assert!(is_depth_refusal(&ctx.parse_xml(&nested, "XML")));
923 });
924 }
925
926 #[test]
927 fn xml_tree_past_the_per_expand_allowance_is_unsupported() {
928 let document = |elements: usize| format!("<r>{}</r>", "<a/>".repeat(elements));
929 with_expand_ceiling(1 << 20, |ctx| {
930 assert!(ctx.parse_xml(&document(1 << 10), "XML").is_ok());
931 assert!(is_unsupported(
932 &ctx.parse_xml(&document(1 << 14), "XML"),
933 "the XML tree could need more than the 1048576-byte per-expansion allowance"
934 ));
935 assert_eq!(ctx.resource_refusal(), None);
936 });
937 }
938
939 #[test]
940 fn xml_tag_attributes_past_their_limit_are_unsupported() {
941 let tag = |attributes: u64| {
942 (0..attributes).fold(String::from("<r"), |mut tag, index| {
943 write!(tag, " a{index}=''").expect("write attribute");
944 tag + if index + 1 == attributes { "/>" } else { "" }
945 })
946 };
947 with_depth(256, |ctx| {
948 assert!(ctx.parse_xml(&tag(MAX_XML_TAG_ATTRIBUTES), "XML").is_ok());
949 assert!(is_unsupported(
950 &ctx.parse_xml(&tag(MAX_XML_TAG_ATTRIBUTES + 1), "XML"),
951 "an XML tag has more than 256 attributes"
952 ));
953 assert_eq!(ctx.resource_refusal(), None);
954 });
955 }
956
957 #[test]
958 fn xml_namespace_prefixes_past_their_limit_are_unsupported() {
959 let document = |prefixes: u64| {
960 let mut text = String::from("<r>");
961 for index in 0..prefixes {
962 write!(text, "<c xmlns:p{index}='u'/>").expect("write declaration");
963 }
964 text + "</r>"
965 };
966 with_depth(256, |ctx| {
967 assert!(ctx
968 .parse_xml(&document(MAX_XML_NAMESPACE_PREFIXES), "XML")
969 .is_ok());
970 assert!(is_unsupported(
971 &ctx.parse_xml(&document(MAX_XML_NAMESPACE_PREFIXES + 1), "XML"),
972 "the XML text declares more than 256 namespace prefixes"
973 ));
974 assert_eq!(ctx.resource_refusal(), None);
975 });
976 }
977
978 #[test]
979 fn json_depth_follows_the_session_past_the_parser_limit() {
980 let nested = format!("{}{}", "[".repeat(200), "]".repeat(200));
981 with_depth(256, |ctx| {
982 assert!(ctx.parse_json_value(&nested, "JSON").is_ok());
983 assert!(ctx.parse_json::<serde_json::Value>(&nested, "JSON").is_ok());
984 });
985 with_depth(199, |ctx| {
986 assert!(is_depth_refusal(&ctx.parse_json_value(&nested, "JSON")));
987 });
988 }
989
990 #[test]
991 fn json_tree_past_the_per_expand_allowance_is_unsupported() {
992 let document = |maps: usize| format!("[{}]", vec![r#"{"a":0}"#; maps].join(","));
993 with_expand_ceiling(1 << 20, |ctx| {
994 assert!(ctx.parse_json_value(&document(1 << 8), "JSON").is_ok());
995 let message =
996 "the JSON tree could need more than the 1048576-byte per-expansion allowance";
997 assert!(is_unsupported(
998 &ctx.parse_json_value(&document(1 << 11), "JSON"),
999 message
1000 ));
1001 assert!(is_unsupported(
1002 &ctx.parse_json::<serde_json::Value>(&document(1 << 11), "JSON"),
1003 message
1004 ));
1005 assert_eq!(ctx.resource_refusal(), None);
1006 });
1007 }
1008
1009 #[test]
1010 fn json_value_and_typed_parse() {
1011 with_depth(256, |ctx| {
1012 let value = ctx
1013 .parse_json_value(r#"{"a":[1,2]}"#, "JSON")
1014 .expect("valid JSON");
1015 assert_eq!(value, serde_json::json!({"a": [1, 2]}));
1016 let record: JsonRecord = ctx
1017 .parse_json(r#"{"name":"r","values":[3]}"#, "JSON")
1018 .expect("valid JSON");
1019 assert_eq!(
1020 record,
1021 JsonRecord {
1022 name: "r".into(),
1023 values: vec![3],
1024 }
1025 );
1026 assert!(matches!(
1027 ctx.parse_json_value("{", "JSON"),
1028 Err(CodecError::Malformed(_))
1029 ));
1030 });
1031 }
1032
1033 #[test]
1034 fn typed_json_keeps_duplicate_field_errors() {
1035 with_depth(256, |ctx| {
1036 let text = r#"{"name":"a","name":"b","values":[]}"#;
1037 assert!(matches!(
1038 ctx.parse_json::<JsonRecord>(text, "JSON"),
1039 Err(CodecError::Malformed(_))
1040 ));
1041 let value = ctx.parse_json_value(text, "JSON").expect("valid JSON");
1042 assert_eq!(value["name"], "b");
1043 });
1044 }
1045
1046 #[test]
1047 fn typed_json_ignores_raw_carrier_extensions() {
1048 with_depth(256, |ctx| {
1049 let text = r#"{"name":"r","values":[],"extension":{"$serde_json::private::RawValue":"not-json"}}"#;
1050 let record: JsonRecord = ctx.parse_json(text, "JSON").expect("valid JSON");
1051 assert_eq!(record.name, "r");
1052 });
1053 }
1054
1055 #[test]
1056 fn json_shape_counts_delimiters_outside_strings_and_raw_replay() {
1057 assert_eq!(json_shape(r#"["[[["]"#).depth, 1);
1058 let shape = json_shape(r#"{"a":[[1,2]],"b":"x,y:z"}"#);
1059 assert_eq!(shape.depth, 3);
1060 assert_eq!(shape.values, 6);
1061 assert_eq!(shape.members, 2);
1062 assert_eq!(shape.maps, 1);
1063 assert_eq!(shape.strings, 3);
1064 let raw = r#"{"$serde_json::private::RawValue":"\u005b0\u005d"}"#;
1065 assert!(json_shape(raw).depth >= 2);
1066 let escaped_key = r#"{"\u0024serde_json::private::RawValue":"[0]"}"#;
1067 assert!(json_shape(escaped_key).depth >= 2);
1068 with_depth(1, |ctx| {
1069 assert!(is_depth_refusal(&ctx.parse_json_value(raw, "JSON")));
1070 });
1071 with_depth(256, |ctx| {
1072 let value = ctx
1073 .parse_json_value(raw, "JSON")
1074 .expect("valid raw carrier");
1075 assert_eq!(value, serde_json::json!([0]));
1076 });
1077 }
1078}