1use rusty_xml_tree::{AttrDecl, AttrDefault, ElementDecl, XmlDtd};
4use crate::error::XmlError;
5
6#[doc(alias = "xmlParseDTD")]
8pub fn xml_parse_dtd(
9 buffer: &[u8],
10 public_id: Option<&str>,
11 system_id: Option<&str>,
12) -> Result<XmlDtd, XmlError> {
13 let text = String::from_utf8_lossy(buffer);
14 let mut dtd = parse_external_subset(&text, false)?;
15 dtd.public_id = public_id.map(str::to_string);
16 dtd.system_id = system_id.map(str::to_string);
17 Ok(dtd)
18}
19
20pub fn parse_dtd_subset(src: &str, old10: bool) -> Result<XmlDtd, XmlError> {
22 parse_subset(src, true, old10)
23}
24
25pub fn parse_external_subset(src: &str, old10: bool) -> Result<XmlDtd, XmlError> {
28 parse_subset(src, false, old10)
29}
30
31fn parse_subset(src: &str, internal: bool, old10: bool) -> Result<XmlDtd, XmlError> {
32 let expanded = expand_pe(src, internal)?;
33 let mut dtd = XmlDtd::default();
34 dtd.int_subset = Some(src.to_string());
35 let mut p = DtdParser {
36 src: expanded.as_str(),
37 pos: 0,
38 dtd: &mut dtd,
39 internal,
40 old10,
41 };
42 p.parse_markup()?;
43 dtd.has_parameter_entity_refs = expanded != src
47 || src.chars().zip(src.chars().skip(1)).any(|(a, b)| {
48 a == '%' && crate::chvalid::xml_is_name_start_char(b as u32, false)
49 });
50 check_entity_graph(&dtd)?;
51 Ok(dtd)
52}
53
54fn expand_pe(src: &str, internal: bool) -> Result<String, XmlError> {
55 let mut cur = src.to_string();
57 for _ in 0..16 {
58 let mut pes: std::collections::HashMap<String, String> = std::collections::HashMap::new();
59 harvest_pe(&cur, &mut pes);
60 let next = subst_pe(&cur, &pes, internal)?;
61 if next == cur {
62 return Ok(cur);
63 }
64 cur = next;
65 }
66 Ok(cur)
67}
68
69fn harvest_pe(src: &str, pes: &mut std::collections::HashMap<String, String>) {
70 let bytes = src.as_bytes();
71 let mut i = 0;
72 while i + 8 < bytes.len() {
73 if bytes[i] == b'<' && bytes.get(i..i + 9) == Some(b"<!ENTITY ") {
74 i += 9;
75 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
76 i += 1;
77 }
78 if i < bytes.len() && bytes[i] == b'%' {
79 i += 1;
80 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
81 i += 1;
82 }
83 let start = i;
84 while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b'"' && bytes[i] != b'\'' {
85 i += 1;
86 }
87 let name = src[start..i].to_string();
88 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
89 i += 1;
90 }
91 if i < bytes.len() && (bytes[i] == b'"' || bytes[i] == b'\'') {
92 let q = bytes[i];
93 i += 1;
94 let vs = i;
95 while i < bytes.len() && bytes[i] != q {
96 i += 1;
97 }
98 let Ok(val) = decode_charrefs(&src[vs..i]) else {
101 continue;
102 };
103 pes.insert(name, val);
104 }
105 }
106 } else {
107 i += 1;
108 }
109 }
110}
111
112fn subst_pe(
113 src: &str,
114 pes: &std::collections::HashMap<String, String>,
115 internal: bool,
116) -> Result<String, XmlError> {
117 let mut out = String::new();
118 let mut chars = src.chars().peekable();
119 let mut in_comment = false;
120 let mut in_decl = false;
124 let mut decl_is_entity = false;
128 let mut in_quote: Option<char> = None;
129 while let Some(c) = chars.next() {
130 if in_comment {
131 out.push(c);
132 if c == '-' && chars.peek() == Some(&'-') {
133 out.push(chars.next().unwrap());
134 if chars.peek() == Some(&'>') {
135 out.push(chars.next().unwrap());
136 in_comment = false;
137 }
138 }
139 continue;
140 }
141 if c == '>' && in_decl {
142 in_decl = false;
143 out.push(c);
144 continue;
145 }
146 if in_decl {
147 match in_quote {
148 Some(q) if c == q => in_quote = None,
149 None if c == '"' || c == '\'' => in_quote = Some(c),
150 _ => {}
151 }
152 }
153 if c == '<' && chars.peek() == Some(&'?') {
157 in_decl = true;
158 decl_is_entity = false;
159 in_quote = None;
160 out.push(c);
161 out.push(chars.next().unwrap());
162 continue;
163 }
164 if c == '?' && in_decl && chars.peek() == Some(&'>') {
165 in_decl = false;
166 out.push(c);
167 out.push(chars.next().unwrap());
168 continue;
169 }
170 if c == '<' && chars.peek() == Some(&'!') {
171 in_decl = true;
172 decl_is_entity = false;
173 in_quote = None;
174 out.push(c);
175 out.push(chars.next().unwrap());
176 if chars.peek() == Some(&'-') {
177 out.push(chars.next().unwrap());
178 if chars.peek() == Some(&'-') {
179 out.push(chars.next().unwrap());
180 in_comment = true;
181 in_decl = false;
182 }
183 } else {
184 let rest: String = chars.clone().take(6).collect();
187 decl_is_entity = rest.starts_with("ENTITY");
188 }
189 continue;
190 }
191 if c == '%' {
192 let is_ref = chars
195 .peek()
196 .is_some_and(|n| crate::chvalid::xml_is_name_start_char(*n as u32, false));
197 if !is_ref {
198 if chars.peek() == Some(&';') {
200 return Err(XmlError::new(
201 crate::error::XML_ERR_ENTITYREF_NO_NAME,
202 "PEReference: no name",
203 0,
204 0,
205 ));
206 }
207 out.push('%');
208 continue;
209 }
210 if in_decl && in_quote.is_some() && !decl_is_entity {
212 out.push('%');
213 continue;
214 }
215 if internal && in_decl {
216 return Err(XmlError::new(
217 crate::error::XML_ERR_ENTITYREF_NO_NAME,
218 "PEReferences forbidden in internal subset",
219 0,
220 0,
221 ));
222 }
223 let mut name = String::new();
224 let mut terminated = false;
225 while let Some(&n) = chars.peek() {
226 if n == ';' {
227 chars.next();
228 terminated = true;
229 break;
230 }
231 if !crate::chvalid::xml_is_name_char(n as u32, false) {
232 break;
233 }
234 name.push(n);
235 chars.next();
236 }
237 if !terminated {
240 return Err(XmlError::new(
241 crate::error::XML_ERR_ENTITYREF_SEMICOL_MISSING,
242 "PEReference: expecting ';'",
243 0,
244 0,
245 ));
246 }
247 if let Some(v) = pes.get(&name) {
248 out.push_str(v);
249 } else {
250 out.push('%');
251 out.push_str(&name);
252 out.push(';');
253 }
254 continue;
255 }
256 out.push(c);
257 }
258 Ok(out)
259}
260
261fn decode_charrefs(s: &str) -> Result<String, &'static str> {
271 let mut out = String::new();
272 let mut it = s.chars().peekable();
273 while let Some(c) = it.next() {
274 if c != '&' {
275 out.push(c);
276 continue;
277 }
278 if it.peek() == Some(&'#') {
279 it.next();
280 let hex = matches!(it.peek(), Some('x') | Some('X'));
281 let upper_x = it.peek() == Some(&'X');
282 if hex {
283 it.next();
284 }
285 let mut digits = String::new();
286 while let Some(&d) = it.peek() {
287 if hex && d.is_ascii_hexdigit() || !hex && d.is_ascii_digit() {
288 digits.push(d);
289 it.next();
290 } else {
291 break;
292 }
293 }
294 if upper_x || digits.is_empty() || it.next() != Some(';') {
296 return Err(if hex {
297 "CharRef: invalid hexadecimal value"
298 } else {
299 "CharRef: invalid decimal value"
300 });
301 }
302 let radix = if hex { 16 } else { 10 };
303 let v = u32::from_str_radix(&digits, radix)
304 .map_err(|_| "CharRef: value out of range")?;
305 match char::from_u32(v).filter(|ch| crate::chvalid::xml_is_char(*ch as u32)) {
306 Some(ch) => out.push(ch),
307 None => return Err("CharRef: invalid XML character"),
308 }
309 continue;
310 }
311 let mut name = String::new();
313 while let Some(&d) = it.peek() {
314 if crate::chvalid::xml_is_name_char(d as u32, false) {
315 name.push(d);
316 it.next();
317 } else {
318 break;
319 }
320 }
321 if name.is_empty() || it.next() != Some(';') {
322 return Err("EntityValue: '&' forbidden except for entities references");
323 }
324 out.push('&');
325 out.push_str(&name);
326 out.push(';');
327 }
328 Ok(out)
329}
330
331struct DtdParser<'a> {
332 src: &'a str,
333 pos: usize,
334 dtd: &'a mut XmlDtd,
335 internal: bool,
339 old10: bool,
344}
345
346impl<'a> DtdParser<'a> {
347 fn rest(&self) -> &'a str {
348 &self.src[self.pos..]
349 }
350 fn skip_ws(&mut self) {
355 let r = self.rest();
356 let trimmed = r.trim_start_matches([' ', '\t', '\r', '\n']);
357 self.pos += r.len() - trimmed.len();
358 }
359
360 fn skip_ws_and_comments(&mut self) -> Result<(), XmlError> {
361 loop {
362 let r = self.rest();
363 let trimmed = r.trim_start();
364 let n = r.len() - trimmed.len();
365 self.pos += n;
366 if self.rest().starts_with("<!--") {
367 if let Some(e) = self.rest().find("-->") {
368 self.pos += e + 3;
369 continue;
370 }
371 }
372 if self.rest().starts_with("<?") {
373 let after = &self.rest()[2..];
377 let is_xml_decl = after
381 .get(..3)
382 .is_some_and(|k| k.eq_ignore_ascii_case("xml"))
383 && after[3..]
384 .chars()
385 .next()
386 .is_none_or(|c| c.is_whitespace() || c == '?');
387 if is_xml_decl {
388 return Err(self.err(
389 "XML declaration allowed only at the start of the document",
390 ));
391 }
392 let after = &self.rest()[2..];
396 let mut target_len = 0usize;
397 for (i, ch) in after.char_indices() {
398 let ok = if i == 0 {
399 crate::chvalid::xml_is_name_start_char(ch as u32, self.old10)
400 } else {
401 crate::chvalid::xml_is_name_char(ch as u32, self.old10)
402 };
403 if !ok {
404 break;
405 }
406 target_len = i + ch.len_utf8();
407 }
408 if target_len == 0 {
409 return Err(self.err("xmlParsePI : no target name"));
410 }
411 let tail = &after[target_len..];
416 let ends_cleanly = tail.is_empty()
417 || tail.starts_with("?>")
418 || tail.chars().next().is_some_and(char::is_whitespace);
419 if !ends_cleanly {
420 return Err(self.err("xmlParsePI : invalid character in target name"));
421 }
422 if let Some(e) = self.rest().find("?>") {
423 self.pos += e + 2;
424 continue;
425 }
426 }
427 break;
428 }
429 Ok(())
430 }
431 fn parse_markup(&mut self) -> Result<(), XmlError> {
432 loop {
433 self.skip_ws_and_comments()?;
434 if self.pos >= self.src.len() {
435 break;
436 }
437 if self.rest().starts_with("<!ELEMENT") {
438 self.parse_element()?;
439 } else if self.rest().starts_with("<!ATTLIST") {
440 self.parse_attlist()?;
441 } else if self.rest().starts_with("<!ENTITY") {
442 self.parse_entity()?;
443 } else if self.rest().starts_with("<!NOTATION") {
444 self.parse_notation()?;
445 } else if self.rest().starts_with("<![") {
446 if self.internal {
448 return Err(self.err("Content error in the internal subset"));
449 }
450 self.skip_cond()?;
451 } else if self.rest().starts_with('<') {
452 return Err(self.err("Content error in the internal subset"));
457 } else if self.rest().starts_with('%') {
458 return Err(self.err("PEReference: expecting ';'"));
462 } else {
463 self.pos += self.rest().chars().next().unwrap().len_utf8();
464 }
465 }
466 Ok(())
467 }
468 fn skip_decl(&mut self) -> Result<(), XmlError> {
469 if let Some(i) = self.rest().find('>') {
470 self.pos += i + 1;
471 Ok(())
472 } else {
473 self.pos = self.src.len();
474 Ok(())
475 }
476 }
477 fn skip_cond(&mut self) -> Result<(), XmlError> {
478 let mut depth = 0i32;
479 let bytes = self.rest().as_bytes();
480 let mut i = 0;
481 while i < bytes.len() {
482 if bytes[i] == b'<' && bytes.get(i..i + 3) == Some(b"<![") {
483 depth += 1;
484 i += 3;
485 continue;
486 }
487 if bytes[i] == b']' && bytes.get(i..i + 3) == Some(b"]]>") {
488 depth -= 1;
489 i += 3;
490 if depth == 0 {
491 self.pos += i;
492 return Ok(());
493 }
494 continue;
495 }
496 i += 1;
497 }
498 self.pos = self.src.len();
499 Ok(())
500 }
501 fn bump(&mut self, n: usize) {
502 self.pos += n;
503 }
504 fn parse_name(&mut self) -> String {
505 let _ = self.skip_ws_and_comments();
508 let r = self.rest();
509 let mut n = 0;
510 for (i, c) in r.char_indices() {
515 let ok = if i == 0 {
516 crate::chvalid::xml_is_name_start_char(c as u32, self.old10)
517 } else {
518 crate::chvalid::xml_is_name_char(c as u32, self.old10)
519 };
520 if !ok {
521 break;
522 }
523 n = i + c.len_utf8();
524 }
525 let s = r[..n].to_string();
526 self.bump(n);
527 s
528 }
529 fn parse_quoted(&mut self) -> Result<String, XmlError> {
539 self.skip_ws_and_comments()?;
540 let r = self.rest();
541 if r.starts_with('"') || r.starts_with('\'') {
542 let q = r.as_bytes()[0] as char;
543 self.bump(1);
544 if let Some(e) = self.rest().find(q) {
545 let s = decode_charrefs(&self.rest()[..e]).map_err(|m| self.err(m))?;
546 self.bump(e + 1);
547 if let Some(bad) =
548 s.chars().find(|c| !crate::chvalid::xml_is_char(*c as u32))
549 {
550 return Err(XmlError::new(
551 crate::error::XML_ERR_INVALID_CHAR,
552 format!("invalid character 0x{:X} in entity value", bad as u32),
553 0,
554 0,
555 ));
556 }
557 return Ok(s);
558 }
559 }
560 Ok(String::new())
561 }
562 fn parse_element(&mut self) -> Result<(), XmlError> {
563 self.bump("<!ELEMENT".len());
564 if !self.require_ws() {
565 return Err(self.err("Space required after '<!ELEMENT'"));
566 }
567 let name = self.parse_name();
568 if name.is_empty() {
569 return Err(self.err("Element name expected"));
570 }
571 if !self.require_ws() {
572 return Err(self.err("Space required after the element name"));
573 }
574 let decl = if self.rest().starts_with("EMPTY") {
575 self.bump(5);
576 ElementDecl::Empty
577 } else if self.rest().starts_with("ANY") {
578 self.bump(3);
579 ElementDecl::Any
580 } else if self.rest().starts_with('(') {
581 let mut spec = self.take_until_gt_paren();
582 if let Some(q @ ('?' | '*' | '+')) = self.rest().chars().next() {
586 self.bump(1);
587 spec.push(q);
588 }
589 let mixed = validate_contentspec(&spec).map_err(|e| self.err(e))?;
595 if mixed {
596 let mut names = Vec::new();
597 for part in spec.split('|') {
598 let t = part.trim().trim_matches(|c: char| c == '(' || c == ')' || c == '*');
599 if t != "#PCDATA" && !t.is_empty() {
600 names.push(t.to_string());
601 }
602 }
603 ElementDecl::Mixed(names)
604 } else {
605 ElementDecl::Children(spec)
606 }
607 } else {
608 return Err(self.err("xmlParseElementDecl: 'EMPTY', 'ANY' or '(' expected"));
609 };
610 if self.dtd.elements.contains_key(&name) {
611 self.dtd.duplicate_elements.push(name.clone());
612 }
613 self.dtd.elements.insert(name, decl);
614 self.expect_decl_end("Element")
615 }
616 fn take_until_gt_paren(&mut self) -> String {
617 let r = self.rest();
618 let mut depth = 0i32;
619 let mut i = 0;
620 for (off, c) in r.char_indices() {
621 match c {
622 '(' => depth += 1,
623 ')' => {
624 depth -= 1;
625 if depth == 0 {
626 i = off + 1;
627 break;
628 }
629 }
630 '>' if depth == 0 => {
631 i = off;
632 break;
633 }
634 _ => {}
635 }
636 i = off + c.len_utf8();
637 }
638 let s = r[..i].to_string();
639 self.bump(i);
640 s
641 }
642 fn parse_attlist(&mut self) -> Result<(), XmlError> {
643 self.bump("<!ATTLIST".len());
644 if !self.require_ws() {
645 return Err(self.err("Space required after '<!ATTLIST'"));
646 }
647 let elem = self.parse_name();
648 if elem.is_empty() {
649 return Err(self.err("Element name expected in ATTLIST"));
650 }
651 loop {
652 let had_ws = self.require_ws();
655 self.skip_ws_and_comments()?;
656 if self.rest().starts_with('>') {
657 self.bump(1);
658 break;
659 }
660 if self.pos >= self.src.len() {
661 return Err(self.err("xmlParseAttributeListDecl: not terminated"));
662 }
663 if !had_ws {
664 return Err(self.err("Space required after the attribute name"));
665 }
666 let aname = self.parse_name();
667 if aname.is_empty() {
668 return Err(self.err("Attribute name expected"));
669 }
670 if !self.require_ws() {
671 return Err(self.err("Space required after the attribute name"));
672 }
673 self.skip_ws_and_comments()?;
674 let mut enumerated = Vec::new();
675 let att_type = if self.rest().starts_with('(') {
676 let spec = self.take_until_gt_paren();
677 let body = spec.trim();
681 if !body.starts_with('(') || !body.ends_with(')') {
682 return Err(self.err("')' required to finish ATTLIST enumeration"));
683 }
684 for part in body[1..body.len() - 1].split('|') {
685 let t = part.trim();
686 if t.is_empty() || !t.chars().all(|c| crate::chvalid::xml_is_name_char(c as u32, false)) {
687 return Err(self.err("')' required to finish ATTLIST enumeration"));
688 }
689 enumerated.push(t.to_string());
690 }
691 "ENUMERATION".into()
692 } else {
693 let t = self.parse_name();
694 const TYPES: &[&str] = &[
697 "CDATA", "ID", "IDREF", "IDREFS", "ENTITY", "ENTITIES", "NMTOKEN",
698 "NMTOKENS", "NOTATION",
699 ];
700 if !TYPES.contains(&t.as_str()) {
701 return Err(self.err("'(' required to start ATTLIST enumeration"));
702 }
703 if t == "NOTATION" {
704 if !self.require_ws() {
705 return Err(self.err("Space required after 'NOTATION'"));
706 }
707 if !self.rest().starts_with('(') {
708 return Err(self.err("'(' required to start ATTLIST enumeration"));
709 }
710 let spec = self.take_until_gt_paren();
711 for part in spec.trim().trim_matches(['(', ')']).split('|') {
712 let n = part.trim();
713 let ok = !n.is_empty()
717 && n.chars().enumerate().all(|(i, c)| {
718 if i == 0 {
719 crate::chvalid::xml_is_name_start_char(c as u32, self.old10)
720 } else {
721 crate::chvalid::xml_is_name_char(c as u32, self.old10)
722 }
723 });
724 if !ok {
725 return Err(self.err("Name expected in NOTATION declaration"));
726 }
727 enumerated.push(n.to_string());
728 }
729 }
730 t
731 };
732 if !self.require_ws() {
733 return Err(self.err("Space required after the attribute type"));
734 }
735 self.skip_ws_and_comments()?;
736 let (default, default_value) = if self.rest().starts_with("#REQUIRED") {
737 self.bump(9);
738 (AttrDefault::Required, None)
739 } else if self.rest().starts_with("#IMPLIED") {
740 self.bump(8);
741 (AttrDefault::Implied, None)
742 } else if self.rest().starts_with("#FIXED") {
743 self.bump(6);
744 if !self.require_ws() {
745 return Err(self.err("Space required after '#FIXED'"));
746 }
747 if !self.at_quote() {
748 return Err(self.err("AttValue: \" or ' expected"));
749 }
750 (AttrDefault::Fixed, Some(self.parse_quoted()?))
751 } else {
752 if !self.at_quote() {
755 return Err(self.err("AttValue: \" or ' expected"));
756 }
757 (AttrDefault::Value, Some(self.parse_quoted()?))
758 };
759 let default_value = default_value.map(|v: String| {
770 v.chars()
771 .map(|c| if matches!(c, '\t' | '\n' | '\r') { ' ' } else { c })
772 .collect::<String>()
773 });
774 if let Some(v) = default_value.as_deref() {
780 const PREDEFINED: &[&str] = &["lt", "gt", "amp", "apos", "quot"];
781 for r in entity_refs_in(v) {
782 if !PREDEFINED.contains(&r.as_str()) && !self.dtd.entities.contains_key(&r) {
783 return Err(self.err(&format!("Entity '{r}' not defined")));
784 }
785 }
786 }
787 self.dtd.attributes.entry((elem.clone(), aname)).or_insert(
788 AttrDecl {
789 att_type,
790 default,
791 default_value,
792 enumerated,
793 },
794 );
795 }
796 Ok(())
797 }
798 fn parse_notation(&mut self) -> Result<(), XmlError> {
804 self.bump("<!NOTATION".len());
805 if !self.require_ws() {
806 return Err(self.err("Space required after '<!NOTATION'"));
807 }
808 let name = self.parse_name();
809 if name.is_empty() {
810 return Err(self.err("Notation name expected"));
811 }
812 if !self.require_ws() {
813 return Err(self.err("Space required after the notation name"));
814 }
815 if name.contains(':') {
816 self.dtd
817 .namespace_errors
818 .push(format!("colons are forbidden from notation names '{name}'"));
819 }
820 self.dtd.notations.insert(name.clone());
821 let public = if self.rest().starts_with("PUBLIC") {
822 true
823 } else if self.rest().starts_with("SYSTEM") {
824 false
825 } else {
826 return Err(self.err("'PUBLIC' or 'SYSTEM' expected in NOTATION"));
827 };
828 self.bump(6);
829 if !self.require_ws() {
830 return Err(self.err("Space required after the external ID keyword"));
831 }
832 if !self.at_quote() {
833 return Err(self.err("Unfinished System or Public ID \" or ' expected"));
834 }
835 let first = self.parse_quoted()?;
836 if public {
837 if let Some(bad) = first.chars().find(|c| !is_pubid_char(*c)) {
838 return Err(self.err(&format!(
839 "Invalid character 0x{:X} in public identifier",
840 bad as u32
841 )));
842 }
843 let before = self.pos;
846 self.skip_ws();
847 if self.at_quote() {
848 if self.pos == before {
849 return Err(self.err("Space required after the Public Identifier"));
850 }
851 self.parse_quoted()?;
852 }
853 }
854 self.expect_decl_end("Notation")
855 }
856
857 fn parse_entity(&mut self) -> Result<(), XmlError> {
858 self.bump("<!ENTITY".len());
859 if !self.require_ws() {
860 return Err(self.err("Space required after '<!ENTITY'"));
861 }
862 let pe = self.rest().starts_with('%');
863 if pe {
864 self.bump(1);
865 if !self.require_ws() {
866 return Err(self.err("Space required after '%'"));
867 }
868 }
869 let name = self.parse_name();
870 if name.is_empty() {
871 return Err(self.err("Entity name expected"));
872 }
873 if name.contains(':') {
878 self.dtd
879 .namespace_errors
880 .push(format!("colons are forbidden from entities names '{name}'"));
881 }
882 if !self.require_ws() {
883 return Err(self.err("Space required after the entity name"));
884 }
885 if self.rest().starts_with("SYSTEM") || self.rest().starts_with("PUBLIC") {
886 let public = self.rest().starts_with("PUBLIC");
887 self.bump(6);
888 if !self.require_ws() {
889 return Err(self.err("Space required after the external ID keyword"));
890 }
891 if public {
892 let pid = self.parse_quoted()?;
896 if let Some(bad) = pid.chars().find(|c| !is_pubid_char(*c)) {
897 return Err(self.err(&format!(
898 "Invalid character 0x{:X} in public identifier",
899 bad as u32
900 )));
901 }
902 if !self.require_ws() {
903 return Err(self.err("Space required after the Public Identifier"));
904 }
905 if !self.at_quote() {
906 return Err(self.err("SystemLiteral expected"));
907 }
908 }
909 if !self.at_quote() {
913 return Err(self.err("SystemLiteral \" or ' expected"));
914 }
915 self.parse_quoted()?;
916 let ws_before_ndata = {
920 let before = self.pos;
921 self.skip_ws();
922 self.pos > before
923 };
924 self.skip_ws_and_comments()?;
925 if self.rest().starts_with("NDATA") {
926 if pe {
929 return Err(self.err("xmlParseEntityDecl: entity not terminated"));
930 }
931 if !ws_before_ndata {
932 return Err(self.err("Space required before 'NDATA'"));
933 }
934 self.bump(5);
935 if !self.require_ws() {
936 return Err(self.err("Space required after 'NDATA'"));
937 }
938 let notation = self.parse_name();
939 if notation.is_empty() {
940 return Err(self.err("Notation name expected after 'NDATA'"));
941 }
942 self.dtd.ndata_notations.push(notation);
943 self.dtd.unparsed_entities.insert(name.clone());
946 self.skip_ws();
947 }
948 return self.expect_decl_end("entity");
949 }
950 if !self.at_quote() {
951 return Err(self.err("Entity value expected"));
952 }
953 let val = self.parse_quoted()?;
954 if pe {
959 self.dtd.parameter_entities.entry(name).or_insert(val);
960 } else {
961 self.dtd.entities.entry(name).or_insert(val);
962 }
963 self.skip_ws();
964 self.expect_decl_end("entity")
965 }
966
967 fn line_col(&self) -> (u32, u32) {
970 let mut line = 1u32;
971 let mut col = 1u32;
972 for c in self.src[..self.pos.min(self.src.len())].chars() {
973 if c == '\n' {
974 line += 1;
975 col = 1;
976 } else {
977 col += 1;
978 }
979 }
980 (line, col)
981 }
982
983 fn err(&self, msg: &str) -> XmlError {
984 let (line, col) = self.line_col();
985 XmlError::new(crate::error::XML_ERR_SPACE_REQUIRED, msg, line, col)
986 }
987
988 fn require_ws(&mut self) -> bool {
990 let before = self.pos;
991 self.skip_ws();
992 self.pos > before || self.pos >= self.src.len()
993 }
994
995 fn at_quote(&self) -> bool {
996 self.rest().starts_with('"') || self.rest().starts_with('\'')
997 }
998
999 fn expect_decl_end(&mut self, what: &str) -> Result<(), XmlError> {
1003 self.skip_ws();
1004 if self.rest().starts_with('>') {
1005 self.bump(1);
1006 Ok(())
1007 } else {
1008 Err(self.err(&format!("xmlParse{what}Decl: not terminated")))
1009 }
1010 }
1011}
1012
1013pub fn merge_dtd(dst: &mut XmlDtd, src: XmlDtd) {
1015 dst.entities.extend(src.entities);
1016 dst.unparsed_entities.extend(src.unparsed_entities);
1017 dst.duplicate_elements.extend(src.duplicate_elements);
1018 dst.notations.extend(src.notations);
1019 dst.namespace_errors.extend(src.namespace_errors);
1020 dst.ndata_notations.extend(src.ndata_notations);
1021 dst.has_parameter_entity_refs |= src.has_parameter_entity_refs;
1022 dst.parameter_entities.extend(src.parameter_entities);
1023 dst.elements.extend(src.elements);
1024 dst.attributes.extend(src.attributes);
1025 if dst.public_id.is_none() {
1026 dst.public_id = src.public_id;
1027 }
1028 if dst.system_id.is_none() {
1029 dst.system_id = src.system_id;
1030 }
1031}
1032
1033fn validate_contentspec(spec: &str) -> Result<bool, &'static str> {
1050 let mut p = SpecParser {
1051 b: spec.as_bytes(),
1052 i: 0,
1053 depth: 0,
1054 };
1055 p.ws();
1056 if !p.eat(b'(') {
1057 return Err("ContentDecl : '(' expected");
1058 }
1059 p.ws();
1060 if p.b[p.i..].starts_with(b"#PCDATA") {
1061 p.i += 7;
1062 let mut named = false;
1063 loop {
1064 p.ws();
1065 if p.eat(b')') {
1066 break;
1067 }
1068 if !p.eat(b'|') {
1069 return Err("ContentDecl : ',' '|' or ')' expected");
1070 }
1071 p.ws();
1072 p.name()?;
1073 named = true;
1074 }
1075 let star = p.eat(b'*');
1077 if named && !star {
1078 return Err("Element content model is not finished with ')*'");
1079 }
1080 p.ws();
1081 return if p.i == p.b.len() {
1082 Ok(true)
1083 } else {
1084 Err("trailing content after the content model")
1085 };
1086 }
1087 p.i = 0;
1089 p.ws();
1090 p.group()?;
1091 p.quant();
1092 p.ws();
1093 if p.i != p.b.len() {
1094 return Err("ContentDecl : garbage after the content model");
1095 }
1096 Ok(false)
1097}
1098
1099struct SpecParser<'a> {
1100 b: &'a [u8],
1101 i: usize,
1102 depth: u32,
1103}
1104
1105impl SpecParser<'_> {
1106 fn ws(&mut self) {
1107 while matches!(self.b.get(self.i), Some(b' ' | b'\t' | b'\r' | b'\n')) {
1108 self.i += 1;
1109 }
1110 }
1111 fn peek(&self) -> Option<u8> {
1112 self.b.get(self.i).copied()
1113 }
1114 fn eat(&mut self, c: u8) -> bool {
1115 if self.peek() == Some(c) {
1116 self.i += 1;
1117 true
1118 } else {
1119 false
1120 }
1121 }
1122 fn quant(&mut self) {
1123 if matches!(self.peek(), Some(b'?' | b'*' | b'+')) {
1124 self.i += 1;
1125 }
1126 }
1127 fn name(&mut self) -> Result<(), &'static str> {
1128 let start = self.i;
1129 let rest = match std::str::from_utf8(&self.b[self.i..]) {
1132 Ok(r) => r,
1133 Err(_) => return Err("invalid UTF-8 in the content model"),
1134 };
1135 let mut chars = rest.char_indices();
1136 match chars.next() {
1137 Some((_, c)) if crate::chvalid::xml_is_name_start_char(c as u32, false) => {
1138 self.i += c.len_utf8();
1139 }
1140 _ => return Err("Name expected in the content model"),
1141 }
1142 for (off, c) in chars {
1143 if !crate::chvalid::xml_is_name_char(c as u32, false) {
1144 self.i = start + off;
1145 return Ok(());
1146 }
1147 self.i = start + off + c.len_utf8();
1148 }
1149 Ok(())
1150 }
1151 fn group(&mut self) -> Result<(), &'static str> {
1154 self.depth += 1;
1156 if self.depth > 256 {
1157 return Err("content model nested too deeply");
1158 }
1159 if !self.eat(b'(') {
1160 return Err("ContentDecl : '(' expected");
1161 }
1162 self.ws();
1163 self.cp()?;
1164 self.ws();
1165 let sep = match self.peek() {
1166 Some(b')') => {
1167 self.i += 1;
1168 self.depth -= 1;
1169 return Ok(());
1170 }
1171 Some(c @ (b'|' | b',')) => c,
1172 _ => return Err("ContentDecl : ',' '|' or ')' expected"),
1173 };
1174 loop {
1175 if !self.eat(sep) {
1176 return Err("ContentDecl : ',' '|' or ')' expected");
1178 }
1179 self.ws();
1180 self.cp()?;
1181 self.ws();
1182 match self.peek() {
1183 Some(b')') => {
1184 self.i += 1;
1185 self.depth -= 1;
1186 return Ok(());
1187 }
1188 Some(c) if c == sep => continue,
1189 _ => return Err("ContentDecl : ',' '|' or ')' expected"),
1190 }
1191 }
1192 }
1193 fn cp(&mut self) -> Result<(), &'static str> {
1194 if self.peek() == Some(b'(') {
1195 self.group()?;
1196 } else {
1197 self.name()?;
1198 }
1199 self.quant();
1200 Ok(())
1201 }
1202}
1203
1204pub fn is_pubid_char(c: char) -> bool {
1209 matches!(c, ' ' | '\r' | '\n')
1210 || c.is_ascii_alphanumeric()
1211 || matches!(
1212 c,
1213 '-' | '\'' | '(' | ')' | '+' | ',' | '.' | '/' | ':'
1214 | '=' | '?' | ';' | '!' | '*' | '#' | '@' | '$' | '_' | '%'
1215 )
1216}
1217
1218fn check_entity_graph(dtd: &XmlDtd) -> Result<(), XmlError> {
1228 const PREDEFINED: &[&str] = &["lt", "gt", "amp", "apos", "quot"];
1229 let err = |m: String| XmlError::new(crate::error::XML_ERR_UNDECLARED_ENTITY, m, 0, 0);
1230
1231 let mut refs: std::collections::HashMap<&str, Vec<String>> = Default::default();
1233 let literals = dtd
1234 .entities
1235 .iter()
1236 .map(|(k, v)| (k.as_str(), v.as_str()))
1237 .chain(
1238 dtd.attributes
1239 .iter()
1240 .filter_map(|((_, a), d)| d.default_value.as_deref().map(|v| (a.as_str(), v))),
1241 );
1242 for (owner, text) in literals {
1243 for name in entity_refs_in(text) {
1244 if PREDEFINED.contains(&name.as_str()) {
1245 continue;
1246 }
1247 if dtd.unparsed_entities.contains(&name) {
1248 return Err(err(format!("Entity reference to unparsed entity {name}")));
1249 }
1250 if !dtd.entities.contains_key(&name) {
1251 return Err(err(format!("Entity '{name}' not defined")));
1252 }
1253 refs.entry(owner).or_default().push(name);
1254 }
1255 }
1256
1257 for start in dtd.entities.keys() {
1260 let mut seen = std::collections::HashSet::new();
1261 let mut stack = vec![start.as_str()];
1262 while let Some(cur) = stack.pop() {
1263 if !seen.insert(cur) {
1264 continue;
1265 }
1266 for next in refs.get(cur).into_iter().flatten() {
1267 if next == start {
1268 return Err(err("Detected an entity reference loop".into()));
1269 }
1270 stack.push(next.as_str());
1271 }
1272 }
1273 }
1274 Ok(())
1275}
1276
1277fn entity_refs_in(text: &str) -> Vec<String> {
1279 let mut out = Vec::new();
1280 let mut rest = text;
1284 let mut scan = String::new();
1285 while let Some(i) = rest.find("<![CDATA[") {
1286 scan.push_str(&rest[..i]);
1287 rest = &rest[i + 9..];
1288 match rest.find("]]>") {
1289 Some(e) => rest = &rest[e + 3..],
1290 None => {
1291 rest = "";
1292 break;
1293 }
1294 }
1295 }
1296 scan.push_str(rest);
1297 let text = scan.as_str();
1298 let mut it = text.chars().peekable();
1299 while let Some(c) = it.next() {
1300 if c != '&' || it.peek() == Some(&'#') {
1301 continue;
1302 }
1303 let mut name = String::new();
1304 while let Some(&d) = it.peek() {
1305 if crate::chvalid::xml_is_name_char(d as u32, false) {
1306 name.push(d);
1307 it.next();
1308 } else {
1309 break;
1310 }
1311 }
1312 if !name.is_empty() && it.peek() == Some(&';') {
1313 it.next();
1314 out.push(name);
1315 }
1316 }
1317 out
1318}