1use rusty_xml_sax::{SaxAttr, SaxHandler};
4use rusty_xml_tree::{NodeId, NodeKind, XmlDoc};
5
6use crate::chvalid::{xml_is_char, xml_is_name_char, xml_is_name_start_char};
7use crate::error::*;
8
9pub const XML_PARSE_RECOVER: i32 = 1 << 0;
11pub const XML_PARSE_NOENT: i32 = 1 << 1;
12pub const XML_PARSE_DTDLOAD: i32 = 1 << 2;
13pub const XML_PARSE_DTDATTR: i32 = 1 << 3;
14pub const XML_PARSE_DTDVALID: i32 = 1 << 4;
15pub const XML_PARSE_NOERROR: i32 = 1 << 5;
16pub const XML_PARSE_NOWARNING: i32 = 1 << 6;
17pub const XML_PARSE_PEDANTIC: i32 = 1 << 7;
18pub const XML_PARSE_NOBLANKS: i32 = 1 << 8;
19pub const XML_PARSE_SAX1: i32 = 1 << 9;
20pub const XML_PARSE_XINCLUDE: i32 = 1 << 10;
21pub const XML_PARSE_NONET: i32 = 1 << 11;
22pub const XML_PARSE_NODICT: i32 = 1 << 12;
23pub const XML_PARSE_NSCLEAN: i32 = 1 << 13;
24pub const XML_PARSE_NOCDATA: i32 = 1 << 14;
25pub const XML_PARSE_NOXINCNODE: i32 = 1 << 15;
26pub const XML_PARSE_COMPACT: i32 = 1 << 16;
27pub const XML_PARSE_OLD10: i32 = 1 << 17;
28pub const XML_PARSE_NOBASEFIX: i32 = 1 << 18;
29pub const XML_PARSE_HUGE: i32 = 1 << 19;
30pub const XML_PARSE_OLDSAX: i32 = 1 << 20;
31pub const XML_PARSE_IGNORE_ENC: i32 = 1 << 21;
32pub const XML_PARSE_BIG_LINES: i32 = 1 << 22;
33pub const XML_PARSE_NO_XXE: i32 = 1 << 23;
34pub const XML_PARSE_UNZIP: i32 = 1 << 24;
35
36pub const XML_PARSE_NO_TREE: i32 = 1 << 30;
50pub const XML_PARSE_NO_SYS_CATALOG: i32 = 1 << 25;
51pub const XML_PARSE_CATALOG_PI: i32 = 1 << 26;
52pub const XML_PARSE_SKIP_IDS: i32 = 1 << 27;
53
54const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
55const XMLNS_NS: &str = "http://www.w3.org/2000/xmlns/";
56
57const MAX_DEPTH: u32 = 5_000;
68
69const MAX_DEPTH_HUGE: u32 = 1_000_000;
72
73const MAX_NAME: usize = 50_000;
87const MAX_TEXT: usize = 10_000_000;
88
89pub fn default_parse_options() -> i32 {
91 XML_PARSE_NONET | XML_PARSE_NO_XXE
92}
93
94#[doc(alias = "xmlInitParser")]
96pub fn xml_init_parser() {}
97
98#[doc(alias = "xmlCleanupParser")]
100pub fn xml_cleanup_parser() {}
101
102#[derive(Debug, Default)]
104pub struct XmlParserCtxt {
105 pub options: i32,
106 pub last_error: Option<XmlError>,
107 pub doc: Option<XmlDoc>,
108}
109
110#[doc(alias = "xmlNewParserCtxt")]
112pub fn xml_new_parser_ctxt() -> XmlParserCtxt {
113 XmlParserCtxt {
114 options: default_parse_options(),
115 last_error: None,
116 doc: None,
117 }
118}
119
120#[doc(alias = "xmlCtxtUseOptions")]
122pub fn xml_ctxt_use_options(ctxt: &mut XmlParserCtxt, options: i32) -> i32 {
123 ctxt.options = options | XML_PARSE_NONET | XML_PARSE_NO_XXE;
124 0
125}
126
127#[doc(alias = "xmlCtxtSetOptions")]
129pub fn xml_ctxt_set_options(ctxt: &mut XmlParserCtxt, options: i32) -> i32 {
130 xml_ctxt_use_options(ctxt, options)
131}
132
133#[doc(alias = "xmlCtxtGetOptions")]
135pub fn xml_ctxt_get_options(ctxt: &XmlParserCtxt) -> i32 {
136 ctxt.options
137}
138
139#[doc(alias = "xmlCtxtGetLastError")]
141pub fn xml_ctxt_get_last_error(ctxt: &XmlParserCtxt) -> Option<&XmlError> {
142 ctxt.last_error.as_ref()
143}
144
145#[doc(alias = "xmlCtxtGetDocument")]
147pub fn xml_ctxt_get_document(ctxt: &XmlParserCtxt) -> Option<&XmlDoc> {
148 ctxt.doc.as_ref()
149}
150
151#[doc(alias = "xmlReadMemory")]
153pub fn xml_read_memory(
154 buffer: &[u8],
155 url: Option<&str>,
156 encoding: Option<&str>,
157 options: i32,
158) -> Result<XmlDoc, XmlError> {
159 let mut sink = rusty_xml_sax::NullSax;
160 parse_doc(buffer, url, encoding, options, &mut sink)
161}
162
163#[doc(alias = "xmlReadDoc")]
165pub fn xml_read_doc(
166 cur: &str,
167 url: Option<&str>,
168 encoding: Option<&str>,
169 options: i32,
170) -> Result<XmlDoc, XmlError> {
171 xml_read_memory(cur.as_bytes(), url, encoding, options)
172}
173
174#[doc(alias = "xmlReadFile")]
176pub fn xml_read_file(
177 filename: &str,
178 encoding: Option<&str>,
179 options: i32,
180) -> Result<XmlDoc, XmlError> {
181 let bytes = std::fs::read(filename).map_err(|e| {
182 XmlError::new(XML_ERR_DOCUMENT_START, e.to_string(), 0, 0)
183 })?;
184 xml_read_memory(&bytes, Some(filename), encoding, options)
185}
186
187#[doc(alias = "xmlCtxtReadMemory")]
189pub fn xml_ctxt_read_memory(
190 ctxt: &mut XmlParserCtxt,
191 buffer: &[u8],
192 url: Option<&str>,
193 encoding: Option<&str>,
194 options: i32,
195) -> Result<XmlDoc, XmlError> {
196 let opts = if options != 0 { options } else { ctxt.options };
197 match xml_read_memory(buffer, url, encoding, opts) {
198 Ok(doc) => {
199 ctxt.doc = Some(doc.clone());
200 ctxt.last_error = None;
201 Ok(doc)
202 }
203 Err(e) => {
204 ctxt.last_error = Some(e.clone());
205 Err(e)
206 }
207 }
208}
209
210pub fn xml_sax_parse_memory(
212 buffer: &[u8],
213 options: i32,
214 sax: &mut dyn SaxHandler,
215) -> Result<XmlDoc, XmlError> {
216 parse_doc(buffer, None, None, options, sax)
217}
218
219pub struct XmlPushParserCtxt {
221 buf: Vec<u8>,
222 options: i32,
223 url: Option<String>,
224 encoding: Option<String>,
225 last_error: Option<XmlError>,
226 state: Option<PushState>,
229 consumed: usize,
230 no_stream: bool,
234}
235
236impl XmlPushParserCtxt {
237 pub fn last_error(&self) -> Option<&XmlError> {
239 self.last_error.as_ref()
240 }
241
242 pub fn buffered(&self) -> usize {
247 self.buf.len()
248 }
249
250 pub fn consumed(&self) -> usize {
252 self.consumed
253 }
254}
255
256#[doc(alias = "xmlCreatePushParserCtxt")]
258pub fn xml_create_push_parser_ctxt(
259 chunk: &[u8],
260 url: Option<&str>,
261 encoding: Option<&str>,
262 options: i32,
263) -> XmlPushParserCtxt {
264 XmlPushParserCtxt {
265 buf: chunk.to_vec(),
266 options: options | XML_PARSE_NONET | XML_PARSE_NO_XXE,
267 url: url.map(str::to_string),
268 encoding: encoding.map(str::to_string),
269 last_error: None,
270 state: None,
271 consumed: 0,
272 no_stream: false,
273 }
274}
275
276#[doc(alias = "xmlParseChunk")]
278pub fn xml_parse_chunk(
279 ctxt: &mut XmlPushParserCtxt,
280 chunk: &[u8],
281 terminate: i32,
282) -> Result<Option<XmlDoc>, XmlError> {
283 ctxt.buf.extend_from_slice(chunk);
284 let terminate = terminate != 0;
285 let opts = ctxt.options;
286
287 if ctxt.state.is_none() && !ctxt.no_stream {
291 match crate::encoding::xml_convert_to_utf8_cow(&ctxt.buf, ctxt.encoding.as_deref()) {
294 Ok((std::borrow::Cow::Borrowed(b), _)) if b.len() == ctxt.buf.len() => {}
295 _ => {
296 ctxt.no_stream = true;
297 }
298 }
299 }
300 if ctxt.state.is_none() && !ctxt.no_stream {
301 let mut sink = rusty_xml_sax::NullSax;
302 let started = {
303 let mut p = fresh_parser(&ctxt.buf, opts, &mut sink);
304 match p.parse_prolog().and_then(|()| p.open_element(NodeId::DOCUMENT)) {
305 Ok(Some(root)) => {
306 let at = p.pos;
307 Some((p.suspend(vec![root], false), at))
308 }
309 _ => None,
313 }
314 };
315 match started {
316 Some((st, at)) => {
317 ctxt.state = Some(st);
318 ctxt.buf.drain(..at);
319 ctxt.consumed += at;
320 }
321 None => {
322 if !terminate {
323 return Ok(None);
324 }
325 return finish_whole(ctxt);
326 }
327 }
328 }
329
330 if ctxt.no_stream {
331 if !terminate {
332 return Ok(None);
333 }
334 return finish_whole(ctxt);
335 }
336
337 let mut sink = rusty_xml_sax::NullSax;
341 let mut st = ctxt.state.take().expect("state is present past the prolog");
342 let mut open = std::mem::take(&mut st.open);
343 let was_closed = st.root_closed;
344 let mut p = Parser::resume(&ctxt.buf, opts, &mut sink, st);
345
346 let safe = if was_closed {
349 0
350 } else {
351 match p.parse_content_inner(NodeId::DOCUMENT, &mut open, !terminate, true) {
352 Ok(at) => at,
353 Err(e) => {
354 ctxt.last_error = Some(e.clone());
355 return Err(e);
356 }
357 }
358 };
359 let root_closed = was_closed || open.is_empty();
360
361 if !terminate {
362 let at = safe.min(ctxt.buf.len());
363 ctxt.state = Some(p.suspend(open, root_closed));
364 ctxt.buf.drain(..at);
365 ctxt.buf.shrink_to_fit();
366 ctxt.consumed += at;
367 return Ok(None);
368 }
369
370 if let Some(o) = open.last() {
371 let (_, local) = Parser::split_qname(&o.qname).unwrap_or((None, &o.qname));
372 let e = p.err(
373 XML_ERR_TAG_NOT_FINISHED,
374 format!("Premature end of data in tag {local}"),
375 );
376 ctxt.last_error = Some(e.clone());
377 return Err(e);
378 }
379
380 if let Err(e) = p.parse_epilog() {
381 ctxt.last_error = Some(e.clone());
382 return Err(e);
383 }
384 let total = ctxt.consumed + ctxt.buf.len();
385 let mut doc = p.suspend(open, true).doc;
386 apply_dtd_defaults(&mut doc, total, ctxt.options)?;
387 normalize_tokenized_attrs(&mut doc);
388 ctxt.buf = Vec::new();
389 ctxt.buf.shrink_to_fit();
390 ctxt.last_error = None;
391 Ok(Some(doc))
392}
393
394fn fresh_parser<'a>(
396 input: &'a [u8],
397 options: i32,
398 sax: &'a mut dyn SaxHandler,
399) -> Parser<'a> {
400 Parser {
401 input,
402 pos: 0,
403 line: 1,
404 col: 1,
405 options,
406 old10: (options & XML_PARSE_OLD10) != 0,
407 depth: 0,
408 ns_stack: Vec::new(),
409 sax,
410 doc: XmlDoc::with_node_capacity(
411 Some("1.0"),
412 if (options & XML_PARSE_NO_TREE) != 0 {
413 input.len() / 32
414 } else {
415 input.len() / 10
416 },
417 ),
418 stack: Vec::new(),
419 char_buf: String::new(),
420 char_buf_from_reference: false,
421 no_tree: (options & XML_PARSE_NO_TREE) != 0,
422 recover: (options & XML_PARSE_RECOVER) != 0,
423 entity_budget: input.len().saturating_mul(10).max(1 << 16),
426 scratch_raw: Vec::new(),
427 scratch_sax: Vec::new(),
428 started: false,
429 }
430}
431
432fn finish_whole(ctxt: &mut XmlPushParserCtxt) -> Result<Option<XmlDoc>, XmlError> {
434 match xml_read_memory(
435 &ctxt.buf,
436 ctxt.url.as_deref(),
437 ctxt.encoding.as_deref(),
438 ctxt.options,
439 ) {
440 Ok(doc) => {
441 ctxt.buf = Vec::new();
442 ctxt.buf.shrink_to_fit();
443 ctxt.last_error = None;
444 Ok(Some(doc))
445 }
446 Err(e) => {
447 ctxt.last_error = Some(e.clone());
448 Err(e)
449 }
450 }
451}
452
453#[doc(alias = "xmlReadIO")]
455pub fn xml_read_io<F>(
456 mut read: F,
457 url: Option<&str>,
458 encoding: Option<&str>,
459 options: i32,
460) -> Result<XmlDoc, XmlError>
461where
462 F: FnMut(&mut [u8]) -> Result<usize, std::io::Error>,
463{
464 let mut buf = Vec::new();
465 let mut tmp = [0u8; 4096];
466 loop {
467 let n = read(&mut tmp).map_err(|e| XmlError::new(XML_ERR_DOCUMENT_START, e.to_string(), 0, 0))?;
468 if n == 0 {
469 break;
470 }
471 buf.extend_from_slice(&tmp[..n]);
472 }
473 xml_read_memory(&buf, url, encoding, options)
474}
475
476#[doc(alias = "xmlCtxtReset")]
478pub fn xml_ctxt_reset(ctxt: &mut XmlParserCtxt) {
479 ctxt.doc = None;
480 ctxt.last_error = None;
481}
482
483struct PushState {
489 doc: XmlDoc,
490 ns_stack: Vec<Vec<(Option<String>, String)>>,
491 stack: Vec<NodeId>,
492 open: Vec<OpenElem>,
493 char_buf: String,
494 line: u32,
495 col: u32,
496 depth: u32,
497 root_closed: bool,
501}
502
503struct OpenElem {
505 qname: String,
507 elem: NodeId,
508}
509
510struct RawAttr {
512 qname: String,
513 value: String,
514 value_off: usize,
515 colon: Option<usize>,
517}
518
519impl RawAttr {
520 fn parts(&self) -> (Option<&str>, &str) {
521 match self.colon {
522 None => (None, self.qname.as_str()),
523 Some(i) => (Some(&self.qname[..i]), &self.qname[i + 1..]),
524 }
525 }
526}
527
528struct Parser<'a> {
529 input: &'a [u8],
530 pos: usize,
531 line: u32,
532 col: u32,
533 options: i32,
534 old10: bool,
535 depth: u32,
536 ns_stack: Vec<Vec<(Option<String>, String)>>,
537 sax: &'a mut dyn SaxHandler,
538 doc: XmlDoc,
539 stack: Vec<NodeId>,
540 char_buf: String,
541 scratch_raw: Vec<RawAttr>,
542 scratch_sax: Vec<SaxAttr>,
543 started: bool,
544 no_tree: bool,
545 recover: bool,
546 char_buf_from_reference: bool,
550 entity_budget: usize,
553}
554
555impl<'a> Parser<'a> {
556 fn check_qname(&mut self, name: &str) {
559 let mut it = name.split(':');
560 let a = it.next().unwrap_or("");
561 if let Some(b) = it.next() {
562 if it.next().is_some() || a.is_empty() || b.is_empty() {
563 let msg = format!("Failed to parse QName '{name}'");
564 self.ns_error(&msg);
565 }
566 }
567 }
568
569 fn ns_error(&mut self, msg: &str) {
574 self.sax.error(msg);
575 self.doc.namespace_errors.push(msg.to_string());
576 }
577
578 fn err(&self, code: i32, msg: impl Into<String>) -> XmlError {
579 XmlError::new(code, msg, self.line, self.col)
580 }
581
582 fn eof(&self) -> bool {
583 self.pos >= self.input.len()
584 }
585
586 fn peek_byte(&self) -> Option<u8> {
587 self.input.get(self.pos).copied()
588 }
589
590 fn starts_with(&self, s: &[u8]) -> bool {
591 self.input[self.pos..].starts_with(s)
592 }
593
594 fn bump_byte(&mut self) -> Option<u8> {
595 let b = self.peek_byte()?;
596 self.pos += 1;
597 if b == b'\n' {
598 self.line += 1;
599 self.col = 1;
600 } else {
601 self.col += 1;
602 }
603 Some(b)
604 }
605
606 fn peek_char(&self) -> Result<Option<char>, XmlError> {
608 let Some(&b0) = self.input.get(self.pos) else {
611 return Ok(None);
612 };
613 if b0 == b'\r' {
614 return Ok(Some('\n'));
615 }
616 if b0 < 0x80 {
617 return Ok(Some(b0 as char));
618 }
619 let rest = &self.input[self.pos..];
620 let head = &rest[..rest.len().min(4)];
624 match std::str::from_utf8(head) {
625 Ok(s) => Ok(s.chars().next()),
626 Err(e) if e.valid_up_to() > 0 => Ok(std::str::from_utf8(&head[..e.valid_up_to()])
629 .ok()
630 .and_then(|s| s.chars().next())),
631 Err(_) => Err(XmlError::new(
632 XML_ERR_INVALID_CHAR,
633 "Invalid UTF-8",
634 self.line,
635 self.col,
636 )),
637 }
638 }
639
640 fn bump_char(&mut self) -> Result<Option<char>, XmlError> {
641 match self.input.get(self.pos) {
644 None => return Ok(None),
645 Some(&b) if b == b'\r' => {
646 self.pos += 1;
647 self.col += 1;
648 if self.input.get(self.pos) == Some(&b'\n') {
649 self.pos += 1;
650 self.line += 1;
651 self.col = 1;
652 }
653 return Ok(Some('\n'));
654 }
655 Some(&b) if b < 0x80 => {
656 self.pos += 1;
657 if b == b'\n' {
658 self.line += 1;
659 self.col = 1;
660 } else {
661 self.col += 1;
662 }
663 return Ok(Some(b as char));
664 }
665 _ => {}
666 }
667 let c = match self.peek_char()? {
668 None => return Ok(None),
669 Some(c) => c,
670 };
671 let n = c.len_utf8();
675 self.pos += n;
676 if c as u32 == 0x0A {
677 self.line += 1;
678 self.col = 1;
679 } else {
680 self.col += n as u32;
683 }
684 Ok(Some(c))
685 }
686
687 fn require_s(&mut self) -> bool {
689 let before = self.pos;
690 let _ = self.skip_s();
691 self.pos > before
692 }
693
694 fn skip_s(&mut self) -> Result<(), XmlError> {
695 while let Some(b) = self.peek_byte() {
698 if b >= 0x80 || !crate::chvalid::xml_is_blank(b as u32) {
699 break;
700 }
701 self.bump_byte();
702 }
703 Ok(())
704 }
705
706 fn expect_byte(&mut self, b: u8, code: i32, msg: &str) -> Result<(), XmlError> {
707 if self.peek_byte() != Some(b) {
708 return Err(self.err(code, msg));
709 }
710 self.bump_byte();
711 Ok(())
712 }
713
714 fn parse_name_span(&mut self) -> Result<(usize, usize), XmlError> {
715 match self.input.get(self.pos) {
718 Some(&b) if b < 0x80 && b != b'\r' => {
719 if !xml_is_name_start_char(b as u32, self.old10) {
720 return Err(self.err(XML_ERR_NAME_REQUIRED, "Name expected"));
721 }
722 }
723 _ => {
724 let c = self
725 .peek_char()?
726 .ok_or_else(|| self.err(XML_ERR_NAME_REQUIRED, "Name expected"))?;
727 if !xml_is_name_start_char(c as u32, self.old10) {
728 return Err(self.err(XML_ERR_NAME_REQUIRED, "Name expected"));
729 }
730 }
731 }
732 let start = self.pos;
737 self.bump_char()?;
738 loop {
739 let Some(b) = self.peek_byte() else { break };
740 if b < 0x80 {
741 if !xml_is_name_char(b as u32, self.old10) {
742 break;
743 }
744 if self.pos - start >= MAX_NAME && (self.options & XML_PARSE_HUGE) == 0 {
745 return Err(self.err(XML_ERR_NAME_REQUIRED, "Name too long"));
746 }
747 self.bump_byte();
748 } else {
749 let Some(c) = self.peek_char()? else { break };
750 if !xml_is_name_char(c as u32, self.old10) {
751 break;
752 }
753 if self.pos - start >= MAX_NAME && (self.options & XML_PARSE_HUGE) == 0 {
754 return Err(self.err(XML_ERR_NAME_REQUIRED, "Name too long"));
755 }
756 self.bump_char()?;
757 }
758 }
759 Ok((start, self.pos))
762 }
763
764 fn parse_name(&mut self) -> Result<String, XmlError> {
767 let (a, b) = self.parse_name_span()?;
768 match std::str::from_utf8(&self.input[a..b]) {
769 Ok(name) => Ok(name.to_string()),
770 Err(_) => Err(self.err(XML_ERR_INVALID_CHAR, "Invalid UTF-8")),
771 }
772 }
773
774 fn split_qname(name: &str) -> Result<(Option<&str>, &str), XmlError> {
775 let mut parts = name.split(':');
776 let a = parts.next().unwrap();
777 match parts.next() {
778 None => Ok((None, a)),
779 Some(b) => {
780 if parts.next().is_some() || a.is_empty() || b.is_empty() {
789 return Ok((None, name));
790 }
791 Ok((Some(a), b))
792 }
793 }
794 }
795
796 fn lookup_ns(&self, prefix: Option<&str>) -> Option<String> {
797 if prefix == Some("xml") {
798 return Some(XML_NS.into());
799 }
800 if prefix == Some("xmlns") {
801 return Some(XMLNS_NS.into());
802 }
803 for frame in self.ns_stack.iter().rev() {
804 for (p, uri) in frame.iter().rev() {
805 if p.as_deref() == prefix {
806 return Some(uri.clone());
807 }
808 }
809 }
810 None
811 }
812
813 fn uri_has_scheme(uri: &str) -> bool {
814 let bytes = uri.as_bytes();
815 if bytes.is_empty() {
816 return false;
817 }
818 if !bytes[0].is_ascii_alphabetic() {
819 return false;
820 }
821 let mut i = 1;
822 while i < bytes.len() {
823 let b = bytes[i];
824 if b == b':' {
825 return true;
826 }
827 if b.is_ascii_alphanumeric() || b == b'+' || b == b'-' || b == b'.' {
828 i += 1;
829 } else {
830 return false;
831 }
832 }
833 false
834 }
835
836 fn flush_chars(&mut self, parent: Option<NodeId>) -> Result<(), XmlError> {
837 if self.char_buf.is_empty() {
838 return Ok(());
839 }
840 if self.char_buf.len() > MAX_TEXT && (self.options & XML_PARSE_HUGE) == 0 {
841 return Err(self.err(XML_ERR_INVALID_CHAR, "Text too long"));
842 }
843 let skip_blank = (self.options & XML_PARSE_NOBLANKS) != 0
844 && self.char_buf.chars().all(|c| crate::chvalid::xml_is_blank(c as u32));
845 if !skip_blank {
846 self.sax.characters(&self.char_buf);
847 if let Some(p) = parent.filter(|_| !self.no_tree) {
848 let t = self.doc.alloc_unnamed(NodeKind::Text);
849 self.doc.node_mut(t).content = std::mem::take(&mut self.char_buf);
852 self.doc.xml_add_child(p, t);
853 if self.char_buf_from_reference {
854 self.doc.reference_text.insert(t);
855 }
856 }
857 }
858 self.char_buf.clear();
859 self.char_buf_from_reference = false;
860 Ok(())
861 }
862
863 fn parse_comment(&mut self, parent: Option<NodeId>) -> Result<(), XmlError> {
864 let mut body = String::new();
866 loop {
867 if self.starts_with(b"-->") {
868 self.pos += 3;
869 self.col += 3;
870 break;
871 }
872 if self.eof() {
873 return Err(self.err(XML_ERR_COMMENT_NOT_FINISHED, "Comment not finished"));
874 }
875 if self.starts_with(b"--") {
876 return Err(self.err(XML_ERR_HYPHEN_IN_COMMENT, "Double hyphen in comment"));
877 }
878 let c = self.bump_char()?.unwrap();
879 if !xml_is_char(c as u32) {
880 return Err(self.err(XML_ERR_INVALID_CHAR, "Invalid character"));
881 }
882 body.push(c);
883 }
884 self.sax.comment(&body);
885 if let Some(p) = parent.filter(|_| !self.no_tree) {
886 let n = self.doc.alloc_unnamed(NodeKind::Comment);
887 self.doc.node_mut(n).content = body;
888 self.doc.xml_add_child(p, n);
889 }
890 Ok(())
891 }
892
893 fn parse_pi(&mut self, parent: Option<NodeId>, xml_decl_ok: bool) -> Result<bool, XmlError> {
894 let target = self.parse_name()?;
896 if target.eq_ignore_ascii_case("xml") {
897 if xml_decl_ok {
898 return self.parse_xml_decl_rest().map(|_| true);
899 }
900 return Err(self.err(XML_ERR_RESERVED_XML_NAME, "Reserved PI target xml"));
901 }
902 if target.contains(':') {
907 let msg = format!("colons are forbidden from PI names '{target}'");
908 self.ns_error(&msg);
909 }
910 let data = if matches!(self.peek_byte(), Some(b) if b < 0x80 && crate::chvalid::xml_is_blank(b as u32)) {
911 self.skip_s()?;
912 let mut d = String::new();
913 loop {
914 if self.starts_with(b"?>") {
915 self.pos += 2;
916 self.col += 2;
917 break;
918 }
919 if self.eof() {
920 return Err(self.err(XML_ERR_PI_NOT_FINISHED, "PI not finished"));
921 }
922 let c = self.bump_char()?.unwrap();
923 if !xml_is_char(c as u32) {
926 return Err(self.err(XML_ERR_INVALID_CHAR, "Invalid character in PI"));
927 }
928 d.push(c);
929 }
930 Some(d)
931 } else {
932 if !self.starts_with(b"?>") {
933 return Err(self.err(XML_ERR_PI_NOT_FINISHED, "PI not finished"));
934 }
935 self.pos += 2;
936 self.col += 2;
937 None
938 };
939 self.sax.processing_instruction(&target, data.as_deref());
940 if let Some(p) = parent.filter(|_| !self.no_tree) {
941 let n = self.doc.alloc(NodeKind::Pi, target);
942 self.doc.node_mut(n).content = data.unwrap_or_default();
943 self.doc.xml_add_child(p, n);
944 }
945 Ok(false)
946 }
947
948 fn parse_xml_decl_rest(&mut self) -> Result<(), XmlError> {
949 self.skip_s()?;
950 if !self.starts_with(b"version") {
952 return Err(self.err(XML_ERR_XMLDECL_NOT_FINISHED, "XML declaration version required"));
953 }
954 self.pos += 7;
955 self.col += 7;
956 self.skip_s()?;
957 self.expect_byte(b'=', XML_ERR_EQUAL_REQUIRED, "'=' required")?;
958 self.skip_s()?;
959 let ver = self.parse_quoted()?;
960 if ver.is_empty()
963 || !ver
964 .chars()
965 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '.' | ':' | '-'))
966 {
967 return Err(self.err(XML_ERR_XMLDECL_NOT_FINISHED, "Invalid XML version value"));
968 }
969 self.doc.version = ver;
970 let had_s = self.require_s();
973 if self.starts_with(b"encoding") {
974 if !had_s {
975 return Err(self.err(XML_ERR_SPACE_REQUIRED, "Blank needed here"));
976 }
977 self.pos += 8;
978 self.col += 8;
979 self.skip_s()?;
980 self.expect_byte(b'=', XML_ERR_EQUAL_REQUIRED, "'=' required")?;
981 self.skip_s()?;
982 let enc = self.parse_quoted()?;
983 let mut cs = enc.chars();
986 let ok = cs.next().is_some_and(|c| c.is_ascii_alphabetic())
987 && cs.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'));
988 if !ok {
989 return Err(self.err(XML_ERR_ENCODING_NAME, "Invalid XML encoding name"));
990 }
991 self.doc.encoding = Some(enc);
992 self.skip_s()?;
993 }
994 if self.starts_with(b"standalone") {
995 if !had_s {
996 return Err(self.err(XML_ERR_SPACE_REQUIRED, "Blank needed here"));
997 }
998 self.pos += 10;
999 self.col += 10;
1000 self.skip_s()?;
1001 self.expect_byte(b'=', XML_ERR_EQUAL_REQUIRED, "'=' required")?;
1002 self.skip_s()?;
1003 let st = self.parse_quoted()?;
1004 self.doc.standalone = match st.as_str() {
1005 "yes" => Some(true),
1006 "no" => Some(false),
1007 _ => return Err(self.err(XML_ERR_XMLDECL_NOT_FINISHED, "standalone must be yes or no")),
1008 };
1009 self.skip_s()?;
1010 }
1011 if !self.starts_with(b"?>") {
1012 return Err(self.err(XML_ERR_XMLDECL_NOT_FINISHED, "XML declaration not finished"));
1013 }
1014 self.pos += 2;
1015 self.col += 2;
1016 Ok(())
1017 }
1018
1019 fn parse_quoted(&mut self) -> Result<String, XmlError> {
1020 let q = self.peek_byte().ok_or_else(|| self.err(XML_ERR_LITERAL_NOT_FINISHED, "Quote expected"))?;
1021 if q != b'\'' && q != b'"' {
1022 return Err(self.err(XML_ERR_LITERAL_NOT_FINISHED, "Quote expected"));
1023 }
1024 self.bump_byte();
1025 let mut s = String::new();
1026 loop {
1027 let c = self.bump_char()?.ok_or_else(|| self.err(XML_ERR_LITERAL_NOT_FINISHED, "Unterminated literal"))?;
1028 if c as u8 == q && c.is_ascii() {
1029 break;
1030 }
1031 if !xml_is_char(c as u32) {
1038 return Err(self.err(XML_ERR_INVALID_CHAR, "invalid character in literal"));
1039 }
1040 s.push(c);
1041 }
1042 Ok(s)
1043 }
1044
1045 fn parse_cdata(&mut self, parent: Option<NodeId>) -> Result<(), XmlError> {
1046 let mut body = String::new();
1048 loop {
1049 if self.starts_with(b"]]>") {
1050 self.pos += 3;
1051 self.col += 3;
1052 break;
1053 }
1054 if self.eof() {
1055 return Err(self.err(XML_ERR_CDATA_NOT_FINISHED, "CDATA not finished"));
1056 }
1057 let c = self.bump_char()?.unwrap();
1058 if !xml_is_char(c as u32) {
1061 return Err(self.err(XML_ERR_INVALID_CHAR, "invalid character in CDATA"));
1062 }
1063 body.push(c);
1064 }
1065 if (self.options & XML_PARSE_NOCDATA) != 0 {
1066 self.sax.characters(&body);
1067 if let Some(p) = parent.filter(|_| !self.no_tree) {
1068 let t = self.doc.alloc_unnamed(NodeKind::Text);
1069 self.doc.node_mut(t).content = body;
1070 self.doc.xml_add_child(p, t);
1071 }
1072 } else {
1073 self.sax.cdata_block(&body);
1074 if let Some(p) = parent.filter(|_| !self.no_tree) {
1075 let t = self.doc.alloc_unnamed(NodeKind::CData);
1076 self.doc.node_mut(t).content = body;
1077 self.doc.xml_add_child(p, t);
1078 }
1079 }
1080 Ok(())
1081 }
1082
1083 fn parse_reference(&mut self) -> Result<String, XmlError> {
1084 self.expect_byte(b'&', XML_ERR_ENTITYREF_NO_NAME, "& expected")?;
1085 if self.peek_byte() == Some(b'#') {
1086 self.bump_byte();
1087 let hex = self.peek_byte() == Some(b'x');
1091 if hex {
1092 self.bump_byte();
1093 } else if self.peek_byte() == Some(b'X') {
1094 return Err(self.err(
1095 XML_ERR_INVALID_DEC_CHARREF,
1096 "CharRef: invalid decimal value",
1097 ));
1098 }
1099 let mut digits = String::new();
1100 while let Some(b) = self.peek_byte() {
1101 let ok = if hex {
1102 b.is_ascii_hexdigit()
1103 } else {
1104 b.is_ascii_digit()
1105 };
1106 if !ok {
1107 break;
1108 }
1109 digits.push(b as char);
1110 self.bump_byte();
1111 }
1112 if digits.is_empty() {
1113 return Err(self.err(
1114 if hex { XML_ERR_INVALID_HEX_CHARREF } else { XML_ERR_INVALID_DEC_CHARREF },
1115 "Invalid character reference",
1116 ));
1117 }
1118 self.expect_byte(b';', XML_ERR_ENTITYREF_SEMICOL_MISSING, "';' required")?;
1119 let val = if hex {
1120 u32::from_str_radix(&digits, 16).map_err(|_| {
1121 self.err(XML_ERR_INVALID_HEX_CHARREF, "Invalid hex charref")
1122 })?
1123 } else {
1124 digits.parse::<u32>().map_err(|_| {
1125 self.err(XML_ERR_INVALID_DEC_CHARREF, "Invalid decimal charref")
1126 })?
1127 };
1128 if !xml_is_char(val) {
1129 return Err(self.err(XML_ERR_INVALID_CHARREF, "Invalid character reference"));
1130 }
1131 return Ok(char::from_u32(val).unwrap().to_string());
1132 }
1133 let name = self.parse_name()?;
1134 self.expect_byte(b';', XML_ERR_ENTITYREF_SEMICOL_MISSING, "';' required")?;
1135 match name.as_str() {
1136 "lt" => Ok("<".into()),
1137 "gt" => Ok(">".into()),
1138 "amp" => Ok("&".into()),
1139 "apos" => Ok("'".into()),
1140 "quot" => Ok("\"".into()),
1141 _ => {
1142 let raw = self
1143 .doc
1144 .dtd
1145 .as_ref()
1146 .and_then(|d| d.entities.get(&name))
1147 .cloned();
1148 if let Some(raw) = raw {
1149 return self.expand_entity(&name, &raw, 0);
1153 }
1154 let subset_incomplete = self
1161 .doc
1162 .dtd
1163 .as_ref()
1164 .is_some_and(|d| d.has_parameter_entity_refs);
1165 if self.recover || subset_incomplete {
1166 self.sax
1167 .error(&format!("Entity '{name}' not defined"));
1168 self.doc.undeclared_entity_refs.push(name.clone());
1169 return Ok(format!("&{name};"));
1170 }
1171 Err(self.err(
1172 XML_ERR_UNDECLARED_ENTITY,
1173 format!("Entity '{name}' not defined"),
1174 ))
1175 }
1176 }
1177 }
1178
1179 fn reference_raw_value(&self) -> Option<String> {
1190 let rest = self.input.get((self.pos + 1).min(self.input.len())..)?;
1191 let end = rest.iter().position(|b| *b == b';')?;
1192 let name = std::str::from_utf8(&rest[..end]).ok()?;
1193 self.doc.dtd.as_ref()?.entities.get(name).cloned()
1194 }
1195
1196 fn consume_reference(&mut self) -> Result<(), XmlError> {
1198 self.expect_byte(b'&', XML_ERR_ENTITYREF_NO_NAME, "& expected")?;
1199 let _ = self.parse_name()?;
1200 self.expect_byte(b';', XML_ERR_ENTITYREF_SEMICOL_MISSING, "';' required")?;
1201 Ok(())
1202 }
1203
1204 fn splice_entity(&mut self, repl: &str, parent: NodeId) -> Result<(), XmlError> {
1215 self.entity_budget = match self.entity_budget.checked_sub(repl.len()) {
1221 Some(n) => n,
1222 None => {
1223 return Err(self.err(
1224 XML_ERR_UNDECLARED_ENTITY,
1225 "Entity expansion budget exceeded",
1226 ));
1227 }
1228 };
1229 let wrapped = format!("<rusty-xml-entity>{repl}</rusty-xml-entity>");
1230 let mut null = rusty_xml_sax::NullSax;
1231 let mut sub = Parser {
1232 input: wrapped.as_bytes(),
1233 pos: 0,
1234 line: self.line,
1235 col: self.col,
1236 options: self.options,
1237 old10: self.old10,
1238 depth: self.depth + 1,
1239 ns_stack: Vec::new(),
1240 sax: &mut null,
1241 doc: XmlDoc::with_node_capacity(Some("1.0"), 8),
1242 stack: Vec::new(),
1243 char_buf: String::new(),
1244 char_buf_from_reference: false,
1245 scratch_raw: Vec::new(),
1246 scratch_sax: Vec::new(),
1247 started: false,
1248 no_tree: false,
1249 recover: self.recover,
1250 entity_budget: self.entity_budget,
1253 };
1254 sub.doc.dtd = self.doc.dtd.clone();
1255 let r = sub.parse_document();
1256 self.entity_budget = sub.entity_budget;
1257 match r {
1258 Ok(()) => {
1259 if let Some(root) = sub.doc.xml_doc_get_root_element() {
1260 self.doc.xml_copy_children_from(&sub.doc, root, parent);
1261 }
1262 Ok(())
1263 }
1264 Err(e) if e.code == XML_NS_ERR_UNDEFINED_NAMESPACE => {
1265 self.char_buf.push_str(repl);
1266 self.flush_chars(Some(parent))
1267 }
1268 Err(e) => Err(e),
1269 }
1270 }
1271
1272 fn expand_entity(&mut self, name: &str, raw: &str, depth: u32) -> Result<String, XmlError> {
1277 const MAX_ENTITY_DEPTH: u32 = 40;
1278 if depth > MAX_ENTITY_DEPTH {
1279 return Err(self.err(
1280 XML_ERR_UNDECLARED_ENTITY,
1281 format!("Entity '{name}' nested too deeply"),
1282 ));
1283 }
1284 let b = raw.as_bytes();
1285 let mut out = String::with_capacity(raw.len());
1286 let mut i = 0usize;
1287 while i < b.len() {
1288 if b[i] != b'&' {
1289 let start = i;
1290 while i < b.len() && b[i] != b'&' {
1291 i += 1;
1292 }
1293 out.push_str(&raw[start..i]);
1294 continue;
1295 }
1296 let Some(semi) = raw[i..].find(';').map(|k| i + k) else {
1297 out.push('&');
1298 i += 1;
1299 continue;
1300 };
1301 let inner = raw[i + 1..semi].to_string();
1302 if let Some(rest) = inner.strip_prefix('#') {
1303 let (radix, digits) = match rest.strip_prefix(['x', 'X']) {
1304 Some(h) => (16u32, h),
1305 None => (10u32, rest),
1306 };
1307 match u32::from_str_radix(digits, radix).ok().and_then(char::from_u32) {
1308 Some(c) => out.push(c),
1309 None => {
1310 return Err(self.err(
1311 XML_ERR_INVALID_CHAR,
1312 format!("Invalid character reference in entity '{name}'"),
1313 ))
1314 }
1315 }
1316 i = semi + 1;
1317 continue;
1318 }
1319 let replacement: Option<String> = match inner.as_str() {
1320 "lt" => Some("<".into()),
1321 "gt" => Some(">".into()),
1322 "amp" => Some("&".into()),
1323 "apos" => Some("'".into()),
1324 "quot" => Some('"'.to_string()),
1325 other => {
1326 let nested = self
1327 .doc
1328 .dtd
1329 .as_ref()
1330 .and_then(|d| d.entities.get(other))
1331 .cloned();
1332 match nested {
1333 Some(r) => Some(self.expand_entity(other, &r, depth + 1)?),
1334 None => None,
1335 }
1336 }
1337 };
1338 match replacement {
1339 Some(r) => {
1340 if r.len() > self.entity_budget {
1341 return Err(self.err(
1342 XML_ERR_INTERNAL_ERROR,
1343 "Maximum entity amplification exceeded",
1344 ));
1345 }
1346 self.entity_budget -= r.len();
1347 out.push_str(&r);
1348 }
1349 None if self.recover => out.push_str(&raw[i..=semi]),
1350 None => {
1351 return Err(self.err(
1352 XML_ERR_UNDECLARED_ENTITY,
1353 format!("Entity '{inner}' not defined"),
1354 ))
1355 }
1356 }
1357 i = semi + 1;
1358 }
1359 Ok(out)
1360 }
1361
1362 fn parse_att_value(&mut self) -> Result<(String, usize), XmlError> {
1363 let q = self.peek_byte().ok_or_else(|| {
1364 self.err(XML_ERR_ATTRIBUTE_WITHOUT_VALUE, "Attribute value expected")
1365 })?;
1366 if q != b'\'' && q != b'"' {
1367 return Err(self.err(XML_ERR_ATTRIBUTE_WITHOUT_VALUE, "Attribute value expected"));
1368 }
1369 self.bump_byte();
1370 let start = self.pos;
1371 let mut val = String::new();
1372 loop {
1373 {
1376 let rs = self.pos;
1377 let mut i = rs;
1378 while i < self.input.len() {
1379 let b = self.input[i];
1380 if b == q || b == b'<' || b == b'&' || b < 0x20 || b >= 0x80 {
1381 break;
1382 }
1383 i += 1;
1384 }
1385 if i > rs {
1386 if let Ok(run) = std::str::from_utf8(&self.input[rs..i]) {
1387 if val.is_empty() {
1390 val.reserve_exact(i - rs);
1391 }
1392 val.push_str(run);
1393 self.col += (i - rs) as u32;
1394 self.pos = i;
1395 continue;
1396 }
1397 }
1398 }
1399 if self.peek_byte() == Some(q) {
1400 self.bump_byte();
1401 break;
1402 }
1403 if self.eof() {
1404 return Err(self.err(XML_ERR_LITERAL_NOT_FINISHED, "Unterminated attribute"));
1405 }
1406 if self.peek_byte() == Some(b'<') {
1407 return Err(self.err(XML_ERR_LT_IN_ATTRIBUTE, "'<' in attribute value"));
1408 }
1409 if self.peek_byte() == Some(b'&') {
1410 let raw = self.reference_raw_value();
1411 let repl = self.parse_reference()?;
1412 if raw.is_some() && (raw.as_deref().unwrap().contains('<') || repl.contains('<')) {
1422 return Err(self.err(
1423 XML_ERR_LT_IN_ATTRIBUTE,
1424 "'<' in entity is not allowed in attribute values",
1425 ));
1426 }
1427 if raw.as_deref().is_some_and(|r| has_bare_ampersand(r)) {
1431 return Err(self.err(
1432 XML_ERR_ENTITYREF_NO_NAME,
1433 "entity reference in attribute value is not well formed",
1434 ));
1435 }
1436 val.push_str(&repl);
1437 continue;
1438 }
1439 let c = self.bump_char()?.unwrap();
1440 if !xml_is_char(c as u32) {
1447 return Err(self.err(
1448 XML_ERR_INVALID_CHAR,
1449 "invalid character in attribute value",
1450 ));
1451 }
1452 if c == '\n' || c == '\t' {
1454 val.push(' ');
1455 } else {
1456 val.push(c);
1457 }
1458 }
1459 Ok((val, start))
1460 }
1461
1462 fn skip_doctype(&mut self) -> Result<(), XmlError> {
1463 self.skip_s()?;
1465 let name = self.parse_name()?;
1466 self.skip_s()?;
1467 let mut public_id = None;
1468 let mut system_id = None;
1469 if self.starts_with(b"SYSTEM") {
1470 self.pos += 6;
1471 self.col += 6;
1472 if !self.require_s() {
1473 return Err(self.err(XML_ERR_SPACE_REQUIRED, "Space required after 'SYSTEM'"));
1474 }
1475 if !matches!(self.peek_byte(), Some(b'"') | Some(b'\'')) {
1476 return Err(self.err(
1477 XML_ERR_LITERAL_NOT_FINISHED,
1478 "SystemLiteral \" or ' expected",
1479 ));
1480 }
1481 system_id = Some(self.parse_quoted()?);
1482 } else if self.starts_with(b"PUBLIC") {
1483 self.pos += 6;
1484 self.col += 6;
1485 if !self.require_s() {
1486 return Err(self.err(XML_ERR_SPACE_REQUIRED, "Space required after 'PUBLIC'"));
1487 }
1488 let pid = self.parse_quoted()?;
1489 if let Some(bad) = pid.chars().find(|c| !crate::dtd::is_pubid_char(*c)) {
1491 return Err(self.err(
1492 XML_ERR_INVALID_CHAR,
1493 format!("Invalid character 0x{:X} in public identifier", bad as u32),
1494 ));
1495 }
1496 public_id = Some(pid);
1497 if !self.require_s() {
1501 return Err(self.err(
1502 XML_ERR_SPACE_REQUIRED,
1503 "Space required after the Public Identifier",
1504 ));
1505 }
1506 if !matches!(self.peek_byte(), Some(b'"') | Some(b'\'')) {
1507 return Err(self.err(
1508 XML_ERR_LITERAL_NOT_FINISHED,
1509 "SystemLiteral \" or ' expected",
1510 ));
1511 }
1512 system_id = Some(self.parse_quoted()?);
1513 }
1514 self.skip_s()?;
1515 let mut int_subset = None;
1516 if self.peek_byte() == Some(b'[') {
1517 self.bump_byte();
1518 let start = self.pos;
1519 let mut depth = 1i32;
1520 let mut in_quote: Option<u8> = None;
1521 while depth > 0 {
1522 if in_quote.is_none() && self.starts_with(b"<!--") {
1528 match self.input[self.pos..]
1529 .windows(3)
1530 .position(|w| w == b"-->")
1531 {
1532 Some(off) => {
1533 for _ in 0..off + 3 {
1534 self.bump_byte();
1535 }
1536 continue;
1537 }
1538 None => {
1539 return Err(
1540 self.err(XML_ERR_COMMENT_NOT_FINISHED, "Comment not finished")
1541 );
1542 }
1543 }
1544 }
1545 let b = self.bump_byte().ok_or_else(|| {
1546 self.err(XML_ERR_DOCUMENT_END, "Unterminated DOCTYPE")
1547 })?;
1548 if let Some(q) = in_quote {
1549 if b == q {
1550 in_quote = None;
1551 }
1552 continue;
1553 }
1554 match b {
1555 b'\'' | b'"' => in_quote = Some(b),
1556 b'[' => depth += 1,
1557 b']' => depth -= 1,
1558 _ => {}
1559 }
1560 }
1561 int_subset = Some(String::from_utf8_lossy(&self.input[start..self.pos.saturating_sub(1)]).into_owned());
1563 }
1564 self.skip_s()?;
1565 self.expect_byte(b'>', XML_ERR_GT_REQUIRED, "'>' required")?;
1566 let mut dtd = if let Some(ref subset) = int_subset {
1567 match crate::dtd::parse_dtd_subset(subset, self.old10) {
1573 Ok(d) => d,
1574 Err(_) if self.recover => rusty_xml_tree::XmlDtd::default(),
1575 Err(e) => return Err(e),
1576 }
1577 } else {
1578 rusty_xml_tree::XmlDtd::default()
1579 };
1580 let subset_ns_errors = std::mem::take(&mut dtd.namespace_errors);
1581 self.doc.namespace_errors.extend(subset_ns_errors);
1582 dtd.name = Some(name);
1583 dtd.public_id = public_id;
1584 dtd.system_id = system_id;
1585 dtd.int_subset = int_subset;
1586 self.doc.dtd = Some(dtd);
1587 Ok(())
1588 }
1589
1590 fn open_element(&mut self, parent: NodeId) -> Result<Option<OpenElem>, XmlError> {
1597 self.depth += 1;
1598 let cap = if (self.options & XML_PARSE_HUGE) != 0 {
1599 MAX_DEPTH_HUGE
1600 } else {
1601 MAX_DEPTH
1602 };
1603 if self.depth > cap {
1604 return Err(self.err(XML_ERR_INTERNAL_ERROR, "Excessive element nesting"));
1605 }
1606 self.expect_byte(b'<', XML_ERR_LT_REQUIRED, "'<' required")?;
1607 let qname = self.parse_name()?;
1608 self.check_qname(&qname);
1609 if qname.starts_with("xmlns:") {
1611 self.ns_error("Elements must not have the prefix xmlns");
1612 }
1613 let (prefix, local) = Self::split_qname(&qname).map_err(|mut e| {
1614 e.line = self.line;
1615 e.col = self.col;
1616 e
1617 })?;
1618
1619 let mut raw_attrs: Vec<RawAttr> = std::mem::take(&mut self.scratch_raw);
1622 raw_attrs.clear();
1623 loop {
1624 let before_ws = self.pos;
1625 self.skip_s()?;
1626 let had_ws = self.pos > before_ws;
1627 if self.starts_with(b"/>") || self.peek_byte() == Some(b'>') {
1628 break;
1629 }
1630 if !had_ws {
1634 return Err(self.err(
1635 XML_ERR_SPACE_REQUIRED,
1636 "attributes construct error",
1637 ));
1638 }
1639 let an = self.parse_name()?;
1640 self.skip_s()?;
1641 self.expect_byte(b'=', XML_ERR_EQUAL_REQUIRED, "'=' required")?;
1642 self.skip_s()?;
1643 let (value, value_off) = self.parse_att_value()?;
1644 self.check_qname(&an);
1645 let colon = match Self::split_qname(&an).map_err(|mut e| {
1646 e.line = self.line;
1647 e.col = self.col;
1648 e
1649 })? {
1650 (None, _) => None,
1651 (Some(pfx), _) => Some(pfx.len()),
1652 };
1653 raw_attrs.push(RawAttr {
1654 qname: an,
1655 value,
1656 value_off,
1657 colon,
1658 });
1659 }
1660 let empty = if self.starts_with(b"/>") {
1661 self.pos += 2;
1662 self.col += 2;
1663 true
1664 } else {
1665 self.expect_byte(b'>', XML_ERR_GT_REQUIRED, "'>' required")?;
1666 false
1667 };
1668
1669 let mut ns_frame: Vec<(Option<String>, String)> = Vec::new();
1670 let normalize_ns = |p: &Parser, name: &str, value: &str| -> String {
1677 let tokenized = p.doc.dtd.as_ref().is_some_and(|d| {
1678 d.attributes
1679 .get(&(qname.to_string(), name.to_string()))
1680 .is_some_and(|a| a.att_type != "CDATA")
1681 });
1682 if tokenized {
1683 value.split(' ').filter(|t| !t.is_empty()).collect::<Vec<_>>().join(" ")
1684 } else {
1685 value.to_string()
1686 }
1687 };
1688 for a in &raw_attrs {
1689 let (ap, al) = a.parts();
1690 if ap.is_none() && al == "xmlns" {
1691 if !a.value.is_empty() && !Self::uri_has_scheme(&a.value) {
1692 let msg = format!("xmlns: URI {} is not absolute\n", a.value);
1693 self.sax.warning(&msg);
1694 }
1695 if a.value == XML_NS {
1698 self.ns_error("xml namespace URI cannot be the default namespace");
1699 } else if a.value == XMLNS_NS {
1700 self.ns_error("xmlns namespace URI cannot be the default namespace");
1701 }
1702 ns_frame.push((None, normalize_ns(self, &a.qname, &a.value)));
1703 } else if ap == Some("xmlns") {
1704 if !a.value.is_empty()
1705 && !Self::uri_has_scheme(&a.value)
1706 && (self.options & XML_PARSE_PEDANTIC) != 0
1707 {
1708 let msg = format!("xmlns:{}: URI {} is not absolute\n", al, a.value);
1709 self.sax.warning(&msg);
1710 }
1711 if al == "xml" {
1720 if a.value != XML_NS {
1721 self.ns_error("xml namespace prefix mapped to wrong URI");
1722 }
1723 } else if a.value == XML_NS {
1724 self.ns_error("xml namespace URI mapped to wrong prefix");
1725 }
1726 if al == "xmlns" {
1727 self.ns_error("redefinition of the xmlns prefix is forbidden");
1728 }
1729 if a.value == XMLNS_NS {
1730 self.ns_error("reuse of the xmlns namespace name is forbidden");
1731 }
1732 if a.value.is_empty() {
1734 self.ns_error("Empty XML namespace is not allowed");
1735 }
1736 ns_frame.push((Some(al.to_string()), normalize_ns(self, &a.qname, &a.value)));
1737 }
1738 }
1739 self.ns_stack.push(ns_frame);
1742
1743 let elem_uri = self.lookup_ns(prefix);
1744 if prefix.is_some() && elem_uri.is_none() {
1745 let msg = format!("Namespace prefix {} is not defined", prefix.unwrap_or_default());
1753 self.ns_error(&msg);
1754 }
1755
1756 let mut seen_keys: std::collections::HashSet<(Option<String>, String)> =
1757 std::collections::HashSet::new();
1758 let mut sax_attrs: Vec<SaxAttr> = std::mem::take(&mut self.scratch_sax);
1759 sax_attrs.clear();
1760 for idx in 0..raw_attrs.len() {
1761 let (ap_owned, al_owned, is_ns, value_off) = {
1764 let a = &mut raw_attrs[idx];
1765 let voff = a.value_off;
1766 match a.colon {
1767 None => {
1770 let is_ns = a.qname == "xmlns";
1771 (None, std::mem::take(&mut a.qname), is_ns, voff)
1772 }
1773 Some(i) => {
1774 let is_ns = &a.qname[..i] == "xmlns";
1775 (
1776 Some(a.qname[..i].to_string()),
1777 a.qname[i + 1..].to_string(),
1778 is_ns,
1779 voff,
1780 )
1781 }
1782 }
1783 };
1784 if is_ns {
1785 continue;
1786 }
1787 let uri = if ap_owned.is_some() {
1788 let u = self.lookup_ns(ap_owned.as_deref());
1789 if u.is_none() {
1790 let msg = format!(
1794 "Namespace prefix {} for {} on ... is not defined",
1795 ap_owned.clone().unwrap_or_default(),
1796 al_owned
1797 );
1798 self.ns_error(&msg);
1799 }
1800 u
1801 } else {
1802 None
1803 };
1804 if sax_attrs
1816 .iter()
1817 .any(|s| s.prefix == ap_owned && s.local == al_owned)
1818 {
1819 return Err(self.err(XML_ERR_ATTRIBUTE_REDEFINED, "Attribute redefined"));
1820 }
1821 if sax_attrs.len() < 32 {
1822 if uri.is_some()
1823 && sax_attrs
1824 .iter()
1825 .any(|s| s.uri.as_deref() == uri.as_deref() && s.local == al_owned)
1826 {
1827 self.ns_error("Attribute redefined after namespace expansion");
1828 }
1829 } else {
1830 if seen_keys.is_empty() {
1831 for a in sax_attrs.iter() {
1832 seen_keys.insert((a.uri.clone(), a.local.clone()));
1833 }
1834 }
1835 if !seen_keys.insert((uri.clone(), al_owned.clone())) && uri.is_some() {
1836 self.ns_error("Attribute redefined after namespace expansion");
1837 }
1838 }
1839 sax_attrs.push(SaxAttr {
1840 local: al_owned,
1841 prefix: ap_owned,
1842 uri,
1843 value: std::mem::take(&mut raw_attrs[idx].value),
1846 value_input_off: Some(value_off),
1847 });
1848 }
1849
1850 let frame: &[(Option<String>, String)] =
1851 self.ns_stack.last().map(Vec::as_slice).unwrap_or(&[]);
1852 self.sax.start_element_ns(
1853 local,
1854 prefix,
1855 elem_uri.as_deref(),
1856 frame,
1857 &sax_attrs,
1858 0,
1859 );
1860
1861 let elem = self.doc.alloc(NodeKind::Element, local);
1862 self.doc.node_mut(elem).prefix = prefix.map(str::to_string);
1863 self.doc.node_mut(elem).ns_uri = elem_uri;
1864 for i in 0..self.ns_stack.last().map_or(0, Vec::len) {
1865 let (p, u) = {
1866 let f = self.ns_stack.last().unwrap();
1867 (f[i].0.clone(), f[i].1.clone())
1868 };
1869 self.doc.push_ns_def(elem, p, u);
1870 }
1871 if self.no_tree {
1872 sax_attrs.clear();
1873 } else {
1874 for a in sax_attrs.drain(..) {
1875 let uri = a.uri;
1876 let aid = self.doc.add_attr_owned(elem, a.local, a.prefix, a.value);
1877 self.doc.node_mut(aid).ns_uri = uri;
1878 }
1879 }
1880 self.doc.xml_add_child(parent, elem);
1881
1882 raw_attrs.clear();
1883 sax_attrs.clear();
1884 self.scratch_raw = raw_attrs;
1885 self.scratch_sax = sax_attrs;
1886
1887 if empty {
1888 let uri = self.doc.node(elem).ns_uri.as_deref();
1889 self.sax.end_element_ns(local, prefix, uri);
1890 self.ns_stack.pop();
1891 self.depth -= 1;
1892 return Ok(None);
1893 }
1894
1895 self.stack.push(elem);
1896 Ok(Some(OpenElem { qname, elem }))
1897 }
1898
1899 fn close_element(&mut self, open: &OpenElem) -> Result<(), XmlError> {
1905 let (prefix, local) = Self::split_qname(&open.qname).map_err(|mut e| {
1906 e.line = self.line;
1907 e.col = self.col;
1908 e
1909 })?;
1910 if !self.starts_with(b"</") {
1911 return Err(self.err(
1912 XML_ERR_TAG_NOT_FINISHED,
1913 format!("Premature end of data in tag {local}"),
1914 ));
1915 }
1916 self.pos += 2;
1917 self.col += 2;
1918 let (ea, eb) = self.parse_name_span()?;
1919 self.skip_s()?;
1920 self.expect_byte(b'>', XML_ERR_GT_REQUIRED, "'>' required")?;
1921 if &self.input[ea..eb] != open.qname.as_bytes() {
1922 let end_name = String::from_utf8_lossy(&self.input[ea..eb]).into_owned();
1923 let qname = &open.qname;
1924 return Err(self.err(
1925 XML_ERR_TAG_NAME_MISMATCH,
1926 format!("Opening and ending tag mismatch: {qname} and {end_name}"),
1927 ));
1928 }
1929 let uri = self.doc.node(open.elem).ns_uri.as_deref();
1930 self.sax.end_element_ns(local, prefix, uri);
1931 self.ns_stack.pop();
1932 self.stack.pop();
1933 self.depth -= 1;
1934 Ok(())
1935 }
1936
1937 fn parse_element(&mut self, parent: NodeId) -> Result<(), XmlError> {
1940 let Some(open) = self.open_element(parent)? else {
1941 return Ok(());
1942 };
1943 self.parse_content(open.elem)?;
1944 self.close_element(&open)
1945 }
1946
1947 fn parse_content(&mut self, parent: NodeId) -> Result<(), XmlError> {
1957 let mut open: Vec<OpenElem> = Vec::new();
1958 self.parse_content_inner(parent, &mut open, false, false)?;
1959 Ok(())
1960 }
1961
1962 fn parse_document(&mut self) -> Result<(), XmlError> {
1963 self.parse_prolog()?;
1964 self.parse_element(NodeId::DOCUMENT)?;
1965 self.parse_epilog()
1966 }
1967
1968 fn resume(
1970 input: &'a [u8],
1971 options: i32,
1972 sax: &'a mut dyn SaxHandler,
1973 st: PushState,
1974 ) -> Self {
1975 let _ = st.root_closed;
1976 Parser {
1977 input,
1978 pos: 0,
1979 line: st.line,
1980 col: st.col,
1981 options,
1982 old10: (options & XML_PARSE_OLD10) != 0,
1983 depth: st.depth,
1984 ns_stack: st.ns_stack,
1985 sax,
1986 doc: st.doc,
1987 stack: st.stack,
1988 char_buf: st.char_buf,
1989 char_buf_from_reference: false,
1990 no_tree: (options & XML_PARSE_NO_TREE) != 0,
1991 recover: (options & XML_PARSE_RECOVER) != 0,
1992 entity_budget: input.len().saturating_mul(10).max(1 << 16),
1995 scratch_raw: Vec::new(),
1996 scratch_sax: Vec::new(),
1997 started: true,
1998 }
1999 }
2000
2001 fn suspend(self, open: Vec<OpenElem>, root_closed: bool) -> PushState {
2002 PushState {
2003 root_closed,
2004 doc: self.doc,
2005 ns_stack: self.ns_stack,
2006 stack: self.stack,
2007 open,
2008 char_buf: self.char_buf,
2009 line: self.line,
2010 col: self.col,
2011 depth: self.depth,
2012 }
2013 }
2014
2015 fn incomplete_construct(&self) -> bool {
2022 let r = &self.input[self.pos..];
2023 fn has(h: &[u8], n: &[u8]) -> bool {
2024 h.len() >= n.len() && h.windows(n.len()).any(|w| w == n)
2025 }
2026 fn tag_complete(r: &[u8]) -> bool {
2028 let mut quote: Option<u8> = None;
2029 for &b in &r[1..] {
2030 match quote {
2031 Some(q) if b == q => quote = None,
2032 Some(_) => {}
2033 None => match b {
2034 b'"' | 0x27 => quote = Some(b),
2035 b'>' => return true,
2036 _ => {}
2037 },
2038 }
2039 }
2040 false
2041 }
2042 match r.first() {
2043 Some(b'<') => {
2044 if r.len() < 2 {
2045 return true;
2046 }
2047 if r.starts_with(b"<!--") {
2048 return !has(&r[4..], b"-->");
2049 }
2050 if r.starts_with(b"<![CDATA[") {
2051 return !has(&r[9..], b"]]>");
2052 }
2053 if r.starts_with(b"<?") {
2054 return !has(&r[2..], b"?>");
2055 }
2056 if r[1] == b'!' && r.len() < 9 {
2058 return true;
2059 }
2060 !tag_complete(r)
2061 }
2062 Some(b'&') => !r.contains(&b';'),
2063 Some(b']') => r.len() < 3,
2065 Some(0x0D) => r.len() < 2,
2069 Some(&b0) => {
2073 let need = if b0 < 0x80 {
2074 1
2075 } else if b0 >> 5 == 0b110 {
2076 2
2077 } else if b0 >> 4 == 0b1110 {
2078 3
2079 } else if b0 >> 3 == 0b11110 {
2080 4
2081 } else {
2082 1
2083 };
2084 r.len() < need
2085 }
2086 None => false,
2087 }
2088 }
2089
2090 fn parse_content_inner(
2100 &mut self,
2101 parent: NodeId,
2102 open: &mut Vec<OpenElem>,
2103 stop_at_eof: bool,
2104 stop_when_empty: bool,
2105 ) -> Result<usize, XmlError> {
2106 let outer = parent;
2111 let mut parent = open.last().map(|f| f.elem).unwrap_or(parent);
2112 loop {
2113 let safe = self.pos;
2114 if self.eof() {
2115 if stop_at_eof {
2116 return Ok(safe);
2117 }
2118 self.flush_chars(Some(parent))?;
2119 if let Some(o) = open.last() {
2120 let (_, local) = Self::split_qname(&o.qname).unwrap_or((None, &o.qname));
2121 return Err(self.err(
2122 XML_ERR_TAG_NOT_FINISHED,
2123 format!("Premature end of data in tag {local}"),
2124 ));
2125 }
2126 return Ok(safe);
2127 }
2128 if stop_at_eof && self.incomplete_construct() {
2131 return Ok(safe);
2132 }
2133 if self.starts_with(b"</") {
2134 self.flush_chars(Some(parent))?;
2135 match open.pop() {
2138 Some(o) => {
2139 self.close_element(&o)?;
2140 if stop_when_empty && open.is_empty() {
2144 return Ok(self.pos);
2145 }
2146 parent = open.last().map(|f| f.elem).unwrap_or(outer);
2147 continue;
2148 }
2149 None => return Ok(safe),
2150 }
2151 }
2152 if self.starts_with(b"<!--") {
2153 self.flush_chars(Some(parent))?;
2154 self.pos += 4;
2155 self.col += 4;
2156 self.parse_comment(Some(parent))?;
2157 continue;
2158 }
2159 if self.starts_with(b"<![CDATA[") {
2160 self.flush_chars(Some(parent))?;
2161 self.pos += 9;
2162 self.col += 9;
2163 self.parse_cdata(Some(parent))?;
2164 continue;
2165 }
2166 if self.starts_with(b"<?") {
2167 self.flush_chars(Some(parent))?;
2168 self.pos += 2;
2169 self.col += 2;
2170 self.parse_pi(Some(parent), false)?;
2171 continue;
2172 }
2173 let lead = self.peek_byte();
2174 if lead == Some(b'<') {
2175 self.flush_chars(Some(parent))?;
2176 if let Some(o) = self.open_element(parent)? {
2177 parent = o.elem;
2178 open.push(o);
2179 }
2180 continue;
2181 }
2182 if lead == Some(b'&') {
2183 let is_charref = self.input.get(self.pos + 1) == Some(&b'#');
2196 if is_charref {
2197 let repl = self.parse_reference()?;
2198 self.char_buf.push_str(&repl);
2199 self.char_buf_from_reference = true;
2200 } else {
2201 self.flush_chars(Some(parent))?;
2202 let raw = self.reference_raw_value();
2203 let will_splice = raw
2209 .as_deref()
2210 .is_some_and(|r| r.contains('<') || r.contains('&'))
2211 && !self.no_tree;
2212 let repl = if will_splice {
2213 self.consume_reference()?;
2214 String::new()
2215 } else {
2216 self.parse_reference()?
2217 };
2218 if !self.no_tree {
2234 self.doc.elements_with_entity_refs.insert(parent);
2235 }
2236 if will_splice {
2237 self.splice_entity(raw.as_deref().unwrap(), parent)?;
2238 } else {
2239 self.char_buf.push_str(&repl);
2249 self.flush_chars(Some(parent))?;
2250 }
2251 }
2252 continue;
2253 }
2254 if self.starts_with(b"]]>") {
2255 return Err(self.err(XML_ERR_MISPLACED_CDATA_END, "Misplaced CDATA end"));
2256 }
2257 {
2261 let start = self.pos;
2262 let mut i = start;
2263 while i < self.input.len() {
2264 let b = self.input[i];
2265 let plain = b == 0x09 || (0x20..0x80).contains(&b);
2266 if !plain || b == b'<' || b == b'&' || b == b']' {
2267 break;
2268 }
2269 i += 1;
2270 }
2271 if i > start {
2272 match std::str::from_utf8(&self.input[start..i]) {
2274 Ok(run) => {
2275 self.char_buf.push_str(run);
2276 self.col += (i - start) as u32;
2277 self.pos = i;
2278 continue;
2279 }
2280 Err(_) => {}
2281 }
2282 }
2283 }
2284 let c = self.bump_char()?.unwrap();
2285 if !xml_is_char(c as u32) {
2286 return Err(self.err(XML_ERR_INVALID_CHAR, "Invalid character"));
2287 }
2288 self.char_buf.push(c);
2289 }
2290 }
2291
2292 fn parse_misc(&mut self, parent: NodeId) -> Result<(), XmlError> {
2293 loop {
2294 self.skip_s()?;
2295 if self.starts_with(b"<!--") {
2296 self.pos += 4;
2297 self.col += 4;
2298 self.parse_comment(Some(parent))?;
2299 continue;
2300 }
2301 if self.starts_with(b"<?") {
2302 self.pos += 2;
2303 self.col += 2;
2304 self.parse_pi(Some(parent), false)?;
2305 continue;
2306 }
2307 break;
2308 }
2309 Ok(())
2310 }
2311
2312 fn parse_prolog(&mut self) -> Result<(), XmlError> {
2316 if self.starts_with(&[0xef, 0xbb, 0xbf]) {
2317 self.pos += 3;
2318 }
2319 self.sax.set_document_locator();
2320 self.sax.start_document();
2321 self.started = true;
2322
2323 if self.starts_with(b"<?xml") {
2325 let save_pos = self.pos;
2326 let save_col = self.col;
2327 let save_line = self.line;
2328 self.pos += 5;
2329 self.col += 5;
2330 match self.peek_byte() {
2331 Some(b) if b < 0x80 && crate::chvalid::xml_is_blank(b as u32) => {
2332 self.parse_xml_decl_rest()?;
2333 }
2334 _ => {
2335 self.pos = save_pos;
2336 self.col = save_col;
2337 self.line = save_line;
2338 self.pos += 2;
2339 self.col += 2;
2340 self.parse_pi(Some(NodeId::DOCUMENT), false)?;
2341 }
2342 }
2343 }
2344
2345 self.parse_misc(NodeId::DOCUMENT)?;
2346 if self.starts_with(b"<!DOCTYPE") {
2347 self.pos += 9;
2348 self.col += 9;
2349 self.skip_doctype()?;
2350 self.parse_misc(NodeId::DOCUMENT)?;
2351 }
2352
2353 if self.peek_byte() != Some(b'<') {
2354 return Err(self.err(XML_ERR_DOCUMENT_EMPTY, "Document is empty"));
2355 }
2356 Ok(())
2357 }
2358
2359 fn parse_epilog(&mut self) -> Result<(), XmlError> {
2361 self.parse_misc(NodeId::DOCUMENT)?;
2362 self.skip_s()?;
2363 if !self.eof() {
2364 return Err(self.err(XML_ERR_EXTRA_CONTENT, "Extra content at the end of the document"));
2365 }
2366 self.sax.end_document();
2367 Ok(())
2368 }
2369}
2370
2371fn parse_doc(
2372 buffer: &[u8],
2373 _url: Option<&str>,
2374 encoding: Option<&str>,
2375 options: i32,
2376 sax: &mut dyn SaxHandler,
2377) -> Result<XmlDoc, XmlError> {
2378 let (converted, enc_name) = crate::encoding::xml_convert_to_utf8_cow(buffer, encoding)?;
2379 parse_utf8(&converted, enc_name.as_deref(), options, sax)
2380}
2381
2382fn parse_utf8(
2383 buffer: &[u8],
2384 enc_name: Option<&str>,
2385 options: i32,
2386 sax: &mut dyn SaxHandler,
2387) -> Result<XmlDoc, XmlError> {
2388 let options = options | XML_PARSE_NONET | XML_PARSE_NO_XXE;
2389 let mut p = Parser {
2390 input: buffer,
2391 pos: 0,
2392 line: 1,
2393 col: 1,
2394 options,
2395 old10: (options & XML_PARSE_OLD10) != 0,
2396 depth: 0,
2397 ns_stack: Vec::new(),
2398 sax,
2399 doc: XmlDoc::with_node_capacity(
2402 Some("1.0"),
2403 if (options & XML_PARSE_NO_TREE) != 0 {
2404 buffer.len() / 32
2408 } else {
2409 buffer.len() / 10
2410 },
2411 ),
2412 stack: Vec::new(),
2413 char_buf: String::new(),
2414 char_buf_from_reference: false,
2415 no_tree: (options & XML_PARSE_NO_TREE) != 0,
2416 recover: (options & XML_PARSE_RECOVER) != 0,
2417 entity_budget: buffer.len().saturating_mul(10).max(1 << 16),
2420 scratch_raw: Vec::new(),
2421 scratch_sax: Vec::new(),
2422 started: false,
2423 };
2424 match p.parse_document() {
2425 Ok(()) => {
2426 apply_dtd_defaults(&mut p.doc, buffer.len(), options)?;
2427 normalize_tokenized_attrs(&mut p.doc);
2428 match (&p.doc.encoding, enc_name) {
2429 (None, Some(n)) => {
2430 if !n.eq_ignore_ascii_case("UTF-8") && !n.eq_ignore_ascii_case("US-ASCII") {
2431 p.doc.encoding = Some(n.to_string());
2432 }
2433 }
2434 (Some(declared), Some(detected)) => {
2435 if !encodings_agree(declared, detected) {
2442 let msg = format!(
2443 "Encoding '{declared}' doesn't match auto-detected '{detected}'"
2444 );
2445 p.sax.error(&msg);
2446 p.doc.warnings.push(msg);
2447 }
2448 }
2449 _ => {}
2450 }
2451 Ok(p.doc)
2452 }
2453 Err(e) => {
2454 if p.started {
2455 p.sax.end_document();
2456 }
2457 if (options & XML_PARSE_RECOVER) != 0 {
2458 p.sax.error(&e.message);
2461 return Ok(p.doc);
2462 }
2463 Err(e)
2464 }
2465 }
2466}
2467
2468fn apply_dtd_defaults(
2469 doc: &mut XmlDoc,
2470 input_len: usize,
2471 options: i32,
2472) -> Result<(), XmlError> {
2473 if (options & XML_PARSE_DTDATTR) == 0 {
2479 return Ok(());
2480 }
2481 match &doc.dtd {
2485 None => return Ok(()),
2486 Some(d) => {
2487 if !d.attributes.values().any(|a| a.default_value.is_some()) {
2488 return Ok(());
2489 }
2490 }
2491 }
2492 let mut budget = input_len.max(65_536).min(5_000_000);
2502 let dtd = match doc.dtd.clone() {
2503 Some(d) => d,
2504 None => return Ok(()),
2505 };
2506 let mut by_elem: std::collections::HashMap<&str, Vec<(&str, &str)>> =
2510 std::collections::HashMap::new();
2511 for ((elem, aname), ad) in &dtd.attributes {
2512 if let Some(v) = &ad.default_value {
2513 by_elem
2514 .entry(elem.as_str())
2515 .or_default()
2516 .push((aname.as_str(), v.as_str()));
2517 }
2518 }
2519 for list in by_elem.values_mut() {
2524 list.sort_unstable_by(|a, b| a.0.cmp(b.0));
2525 }
2526 let n = doc.len();
2527 for i in 0..n {
2528 let id = NodeId(i as u32);
2529 if doc.kind(id) != NodeKind::Element {
2530 continue;
2531 }
2532 let Some(list) = by_elem.get(doc.name(id)) else {
2533 continue;
2534 };
2535 for (aname, v) in list.iter() {
2536 if doc.xml_get_prop(id, aname).is_none() {
2537 if budget == 0 {
2538 return Err(XmlError::new(
2539 XML_ERR_INTERNAL_ERROR,
2540 "Maximum attribute-default amplification exceeded",
2541 0,
2542 0,
2543 ));
2544 }
2545 budget -= 1;
2546 doc.xml_set_prop(id, aname, v);
2547 }
2548 }
2549 }
2550 Ok(())
2551}
2552
2553#[cfg(test)]
2554mod chvalid_tests {
2555 use crate::xml_is_char;
2556 use std::path::PathBuf;
2557
2558 #[test]
2559 fn xml_is_char_matches_c_bmp_dump() {
2560 let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
2561 p.pop();
2562 p.pop();
2563 p.push("corpora");
2564 p.push("xmlIsChar-bmp.bin");
2565 if !p.exists() {
2566 return;
2567 }
2568 let dump = std::fs::read(&p).expect("corpora/xmlIsChar-bmp.bin");
2569 assert_eq!(dump.len(), 65536);
2570 for i in 0u32..=0xffff {
2571 let want = dump[i as usize] != 0;
2572 let got = xml_is_char(i);
2573 assert_eq!(got, want, "xml_is_char({i:#x}) = {got}, C dump = {want}");
2574 }
2575 }
2576}
2577
2578fn has_bare_ampersand(s: &str) -> bool {
2583 let mut it = s.chars().peekable();
2584 while let Some(c) = it.next() {
2585 if c != '&' {
2586 continue;
2587 }
2588 if it.peek() == Some(&'#') {
2589 it.next();
2590 let hex = it.peek() == Some(&'x');
2591 if hex {
2592 it.next();
2593 }
2594 let mut any = false;
2595 while let Some(&d) = it.peek() {
2596 if (hex && d.is_ascii_hexdigit()) || (!hex && d.is_ascii_digit()) {
2597 any = true;
2598 it.next();
2599 } else {
2600 break;
2601 }
2602 }
2603 if !any || it.next() != Some(';') {
2604 return true;
2605 }
2606 continue;
2607 }
2608 let mut any = false;
2609 while let Some(&d) = it.peek() {
2610 if crate::chvalid::xml_is_name_char(d as u32, false) {
2611 any = true;
2612 it.next();
2613 } else {
2614 break;
2615 }
2616 }
2617 if !any || it.next() != Some(';') {
2618 return true;
2619 }
2620 }
2621 false
2622}
2623
2624fn normalize_tokenized_attrs(doc: &mut XmlDoc) {
2635 let Some(dtd) = doc.dtd.as_ref() else { return };
2636 if !dtd.attributes.values().any(|a| a.att_type != "CDATA") {
2638 return;
2639 }
2640 let tokenized: std::collections::HashSet<(String, String)> = dtd
2641 .attributes
2642 .iter()
2643 .filter(|(_, a)| a.att_type != "CDATA")
2644 .map(|((e, a), _)| (e.clone(), a.clone()))
2645 .collect();
2646
2647 let mut stack = vec![NodeId::DOCUMENT];
2648 let mut edits: Vec<(NodeId, String)> = Vec::new();
2649 while let Some(id) = stack.pop() {
2650 let mut c = doc.first_child(id);
2651 while let Some(x) = c {
2652 if doc.kind(x) == NodeKind::Element {
2653 let elem = doc.qname(x);
2654 let mut a = doc.first_attr(x);
2655 while let Some(at) = a {
2656 if tokenized.contains(&(elem.clone(), doc.qname(at))) {
2657 let v = doc.content(at);
2658 let norm = v
2665 .split(' ')
2666 .filter(|t| !t.is_empty())
2667 .collect::<Vec<_>>()
2668 .join(" ");
2669 if norm != v {
2670 edits.push((at, norm));
2671 }
2672 }
2673 a = doc.next_sibling(at);
2674 }
2675 stack.push(x);
2676 }
2677 c = doc.next_sibling(x);
2678 }
2679 }
2680 for (at, v) in edits {
2681 doc.node_mut(at).content = v;
2682 }
2683}
2684
2685fn encodings_agree(declared: &str, detected: &str) -> bool {
2690 fn family(n: &str) -> &'static str {
2691 let n = n.to_ascii_uppercase();
2692 if n.starts_with("UTF-16") || n.starts_with("UTF16") {
2693 "16"
2694 } else if n.starts_with("UTF-32") || n.starts_with("UTF32") {
2695 "32"
2696 } else if n.starts_with("UTF-8") || n.starts_with("UTF8") || n == "US-ASCII" {
2697 "8"
2698 } else {
2699 "other"
2700 }
2701 }
2702 let (d, a) = (family(declared), family(detected));
2703 d == a || d == "other" || a == "other"
2704}