1#![allow(
12 missing_docs,
13 non_snake_case,
14 non_camel_case_types,
15 non_upper_case_globals,
16 clippy::cast_possible_truncation,
17 clippy::cast_sign_loss,
18 clippy::cast_ptr_alignment,
19 clippy::missing_safety_doc,
20 clippy::too_many_lines,
21 clippy::type_complexity
22)]
23
24use core::ffi::c_void;
25use core::ptr;
26use std::collections::HashSet;
27use std::os::raw::{c_char, c_int};
28
29use crate::abi::structs::*;
30use crate::abi::types::xmlElementType::*;
31use crate::abi::types::*;
32use crate::xml::io;
33use crate::xml::tree;
34
35const XML_XML_PREFIX: &[xmlChar] = b"xml\0";
41
42const XML_XML_NS_URI: &[xmlChar] = b"http://www.w3.org/XML/1998/namespace\0";
44
45const _XMLNS_NS_URI: &[xmlChar] = b"http://www.w3.org/2000/xmlns/\0";
47
48const _XMLNS_PREFIX: &[xmlChar] = b"xmlns\0";
50
51#[repr(C)]
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum C14nMode {
72 XML_C14N_1_0 = 0,
73 XML_C14N_EXCLUSIVE_1_0 = 1,
74 XML_C14N_1_1 = 2,
75 XML_C14N_1_0_WITH_COMMENTS = 3,
76 XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS = 4,
77 XML_C14N_1_1_WITH_COMMENTS = 5,
78}
79
80impl C14nMode {
81 fn with_comments(self) -> bool {
83 matches!(
84 self,
85 C14nMode::XML_C14N_1_0_WITH_COMMENTS
86 | C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS
87 | C14nMode::XML_C14N_1_1_WITH_COMMENTS
88 )
89 }
90
91 fn is_exclusive(self) -> bool {
93 matches!(
94 self,
95 C14nMode::XML_C14N_EXCLUSIVE_1_0 | C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS
96 )
97 }
98}
99
100#[allow(dead_code)]
106#[derive(Debug, Clone)]
107struct NsEntry {
108 prefix: *const xmlChar,
110 href: *const xmlChar,
112 rendered: bool,
114}
115
116#[derive(Debug)]
121pub struct C14nContext {
122 pub mode: C14nMode,
124 ns_stack: Vec<Vec<NsEntry>>,
126 #[allow(dead_code)]
128 inclusive_ns_prefixes: Option<HashSet<String>>,
129 #[allow(dead_code)]
131 doc: *mut _xmlDoc,
132}
133
134impl C14nContext {
135 pub unsafe fn new(
141 doc: *mut _xmlDoc,
142 mode: C14nMode,
143 inclusive_ns_prefixes: Option<HashSet<String>>,
144 ) -> Self {
145 let mut ctx = C14nContext {
146 mode,
147 ns_stack: Vec::new(),
148 inclusive_ns_prefixes,
149 doc,
150 };
151 let xml_prefix = XML_XML_PREFIX.as_ptr() as *const xmlChar;
154 let xml_href = XML_XML_NS_URI.as_ptr() as *const xmlChar;
155 ctx.ns_stack.push(vec![NsEntry {
156 prefix: xml_prefix,
157 href: xml_href,
158 rendered: false,
159 }]);
160 ctx
161 }
162
163 #[allow(dead_code)]
165 fn push_scope(&mut self) {
166 let base = if let Some(top) = self.ns_stack.last() {
168 top.clone()
169 } else {
170 Vec::new()
171 };
172 self.ns_stack.push(base);
173 }
174
175 #[allow(dead_code)]
177 fn pop_scope(&mut self) {
178 self.ns_stack.pop();
179 }
180
181 #[allow(dead_code)]
183 fn add_namespace(&mut self, prefix: *const xmlChar, href: *const xmlChar) {
184 if let Some(top) = self.ns_stack.last_mut() {
185 if !top
187 .iter()
188 .any(|e| unsafe { crate::abi::exports_xml2::xmlStrEqual(e.prefix, prefix) != 0 })
189 {
190 top.push(NsEntry {
191 prefix,
192 href,
193 rendered: false,
194 });
195 } else {
196 if let Some(existing) = top.iter_mut().find(|e| unsafe {
198 crate::abi::exports_xml2::xmlStrEqual(e.prefix, prefix) != 0
199 }) {
200 existing.href = href;
201 existing.rendered = false;
202 }
203 }
204 }
205 }
206
207 #[allow(dead_code)]
209 fn is_prefix_in_scope(&self, prefix: *const xmlChar) -> bool {
210 self.ns_stack.iter().rev().any(|scope| {
211 scope
212 .iter()
213 .any(|e| unsafe { crate::abi::exports_xml2::xmlStrEqual(e.prefix, prefix) != 0 })
214 })
215 }
216
217 #[allow(dead_code)]
219 fn get_href_for_prefix(&self, prefix: *const xmlChar) -> *const xmlChar {
220 for scope in self.ns_stack.iter().rev() {
221 for entry in scope.iter() {
222 if unsafe { crate::abi::exports_xml2::xmlStrEqual(entry.prefix, prefix) != 0 } {
223 return entry.href;
224 }
225 }
226 }
227 ptr::null()
228 }
229
230 #[allow(dead_code)]
232 fn is_inclusive_prefix(&self, prefix: *const xmlChar) -> bool {
233 if let Some(ref set) = self.inclusive_ns_prefixes {
234 if prefix.is_null() {
235 return set.contains("");
236 }
237 let prefix_str = unsafe {
238 let c_str = core::ffi::CStr::from_ptr(prefix as *const c_char);
239 match c_str.to_str() {
240 Ok(s) => s.to_string(),
241 Err(_) => return false,
242 }
243 };
244 set.contains(&prefix_str)
245 } else {
246 false
247 }
248 }
249
250 #[allow(dead_code)]
252 fn mark_rendered(&mut self, prefix: *const xmlChar) {
253 for scope in self.ns_stack.iter_mut().rev() {
254 for entry in scope.iter_mut() {
255 if unsafe { crate::abi::exports_xml2::xmlStrEqual(entry.prefix, prefix) != 0 } {
256 entry.rendered = true;
257 return;
258 }
259 }
260 }
261 }
262
263 #[allow(dead_code)]
265 fn is_rendered(&self, prefix: *const xmlChar) -> bool {
266 for scope in self.ns_stack.iter().rev() {
267 for entry in scope.iter() {
268 if unsafe { crate::abi::exports_xml2::xmlStrEqual(entry.prefix, prefix) != 0 } {
269 return entry.rendered;
270 }
271 }
272 }
273 false
274 }
275}
276
277unsafe fn c14n_escape_text(buf: *mut _xmlBuffer, text: *const xmlChar, len: c_int) {
295 if buf.is_null() || text.is_null() || len <= 0 {
296 return;
297 }
298
299 let mut i: c_int = 0;
300 while i < len {
301 let ch = unsafe { *text.add(i as usize) };
302
303 if ch == b']'
305 && i + 2 < len
306 && unsafe { *text.add(i as usize + 1) == b']' }
307 && unsafe { *text.add(i as usize + 2) == b'>' }
308 {
309 io::buf_add(buf, b"]]" as *const u8, 2); io::buf_add(buf, b">" as *const u8, 4);
312 i += 3;
313 continue;
314 }
315
316 match ch {
317 b'<' => {
318 io::buf_add(buf, b"<" as *const u8, 4);
319 }
320 b'>' => {
321 io::buf_add(buf, b">" as *const u8, 4);
322 }
323 b'&' => {
324 io::buf_add(buf, b"&" as *const u8, 5);
325 }
326 0x0D => {
327 io::buf_add(buf, b"
" as *const u8, 5);
329 }
330 _ => {
331 io::buf_add(buf, &ch as *const u8, 1);
332 }
333 }
334 i += 1;
335 }
336}
337
338unsafe fn c14n_escape_attr(buf: *mut _xmlBuffer, text: *const xmlChar) {
354 if buf.is_null() || text.is_null() {
355 return;
356 }
357
358 let len = tree::xml_strlen(text);
359 let mut i: c_int = 0;
360 while i < len {
361 let ch = unsafe { *text.add(i as usize) };
362
363 if ch == b']'
365 && i + 2 < len
366 && unsafe { *text.add(i as usize + 1) == b']' }
367 && unsafe { *text.add(i as usize + 2) == b'>' }
368 {
369 io::buf_add(buf, b"]]" as *const u8, 2); io::buf_add(buf, b">" as *const u8, 4);
372 i += 3;
373 continue;
374 }
375
376 match ch {
377 b'<' => {
378 io::buf_add(buf, b"<" as *const u8, 4);
379 }
380 b'&' => {
381 io::buf_add(buf, b"&" as *const u8, 5);
382 }
383 b'"' => {
384 io::buf_add(buf, b""" as *const u8, 6);
385 }
386 0x09 => {
387 io::buf_add(buf, b"	" as *const u8, 5);
389 }
390 0x0A => {
391 io::buf_add(buf, b"
" as *const u8, 5);
393 }
394 0x0D => {
395 io::buf_add(buf, b"
" as *const u8, 5);
397 }
398 _ => {
399 io::buf_add(buf, &ch as *const u8, 1);
400 }
401 }
402 i += 1;
403 }
404}
405
406#[derive(Debug, Clone)]
412struct CollectedNs {
413 prefix: *const xmlChar,
415 href: *const xmlChar,
417}
418
419unsafe fn c14n_collect_namespaces(node: *mut _xmlNode, ctx: &mut C14nContext) -> Vec<CollectedNs> {
435 if node.is_null() {
436 return Vec::new();
437 }
438
439 let n = unsafe { &*node };
440 if n.type_ != XML_ELEMENT_NODE as c_int {
441 return Vec::new();
442 }
443
444 let mut collected: Vec<CollectedNs> = Vec::new();
445 let mut seen_prefixes: Vec<*const xmlChar> = Vec::new();
446
447 if ctx.mode.is_exclusive() {
453 let mut used_prefixes: Vec<*const xmlChar> = Vec::new();
460
461 if !n.ns.is_null() {
463 let ns = unsafe { &*n.ns };
464 used_prefixes.push(ns.prefix);
465 }
466
467 let mut attr = n.properties;
469 while !attr.is_null() {
470 let a = unsafe { &*attr };
471 if !a.ns.is_null() {
472 let ans = unsafe { &*a.ns };
473 if !ans.prefix.is_null()
474 && !used_prefixes.iter().any(|p| unsafe {
475 crate::abi::exports_xml2::xmlStrEqual(*p, ans.prefix) != 0
476 })
477 {
478 used_prefixes.push(ans.prefix);
479 }
480 }
481 attr = a.next;
482 }
483
484 for &used_prefix in &used_prefixes {
487 let ns = find_ns_declaration(node, used_prefix);
488 if !ns.is_null() {
489 let ns_ref = unsafe { &*ns };
490 if !seen_prefixes.iter().any(|p| unsafe {
491 crate::abi::exports_xml2::xmlStrEqual(*p, ns_ref.prefix) != 0
492 }) {
493 collected.push(CollectedNs {
494 prefix: ns_ref.prefix,
495 href: ns_ref.href,
496 });
497 seen_prefixes.push(ns_ref.prefix);
498 }
499 }
500 }
501
502 if let Some(ref inclusive_set) = ctx.inclusive_ns_prefixes {
504 for inc_prefix_str in inclusive_set.iter() {
505 let inc_prefix = if inc_prefix_str.is_empty() {
506 ptr::null()
507 } else {
508 let c_str = format!("{}\0", inc_prefix_str);
509 c_str.as_ptr() as *const xmlChar
510 };
511
512 if !seen_prefixes.iter().any(|p| {
513 if inc_prefix.is_null() {
514 p.is_null()
515 } else {
516 !p.is_null()
517 && unsafe { crate::abi::exports_xml2::xmlStrEqual(*p, inc_prefix) != 0 }
518 }
519 }) {
520 let ns = find_ns_declaration(node, inc_prefix);
521 if !ns.is_null() {
522 let ns_ref = unsafe { &*ns };
523 collected.push(CollectedNs {
524 prefix: ns_ref.prefix,
525 href: ns_ref.href,
526 });
527 seen_prefixes.push(ns_ref.prefix);
528 }
529 }
530 }
531 }
532 } else {
533 let mut cur: *mut _xmlNode = node;
538 while !cur.is_null() {
539 let cur_node = unsafe { &*cur };
540 let mut ns_def = cur_node.nsDef;
541 while !ns_def.is_null() {
542 let ns = unsafe { &*ns_def };
543 let ns_prefix = ns.prefix;
544
545 if !seen_prefixes.iter().any(|p| {
546 if ns_prefix.is_null() && p.is_null() {
547 return true;
548 }
549 if ns_prefix.is_null() || p.is_null() {
550 return false;
551 }
552 unsafe { crate::abi::exports_xml2::xmlStrEqual(*p, ns_prefix) != 0 }
553 }) {
554 collected.push(CollectedNs {
555 prefix: ns_prefix,
556 href: ns.href,
557 });
558 seen_prefixes.push(ns_prefix);
559 }
560 ns_def = ns.next;
561 }
562 cur = cur_node.parent;
563 }
564 }
565
566 collected
567}
568
569unsafe fn find_ns_declaration(node: *mut _xmlNode, prefix: *const xmlChar) -> *mut _xmlNs {
575 if node.is_null() {
576 return ptr::null_mut();
577 }
578
579 let mut cur: *mut _xmlNode = node;
580 while !cur.is_null() {
581 let cur_node = unsafe { &*cur };
582 let mut ns_def = cur_node.nsDef;
583 while !ns_def.is_null() {
584 let ns = unsafe { &*ns_def };
585 let match_found = if prefix.is_null() {
586 ns.prefix.is_null()
588 } else if ns.prefix.is_null() {
589 false
590 } else {
591 unsafe { crate::abi::exports_xml2::xmlStrEqual(ns.prefix, prefix) != 0 }
592 };
593 if match_found {
594 return ns_def;
595 }
596 ns_def = ns.next;
597 }
598 cur = cur_node.parent;
599 }
600
601 ptr::null_mut()
602}
603
604unsafe fn c14n_serialize_namespaces(buf: *mut _xmlBuffer, ns_list: &[CollectedNs]) {
617 if buf.is_null() || ns_list.is_empty() {
618 return;
619 }
620
621 for ns in ns_list {
622 io::buf_add(buf, b" xmlns" as *const u8, 6);
623 if !ns.prefix.is_null() {
624 io::buf_ccat(buf, b':');
625 io::buf_cat(buf, ns.prefix);
626 }
627 io::buf_add(buf, b"=\"" as *const u8, 2);
628 if !ns.href.is_null() {
629 c14n_escape_attr(buf, ns.href);
630 }
631 io::buf_ccat(buf, b'"');
632 }
633}
634
635unsafe fn compare_attrs(a: *const _xmlAttr, b: *const _xmlAttr) -> std::cmp::Ordering {
650 let attr_a = unsafe { &*a };
651 let attr_b = unsafe { &*b };
652
653 let ns_uri_a = if !attr_a.ns.is_null() {
655 unsafe { &*attr_a.ns }.href
656 } else {
657 ptr::null()
658 };
659 let ns_uri_b = if !attr_b.ns.is_null() {
660 unsafe { &*attr_b.ns }.href
661 } else {
662 ptr::null()
663 };
664
665 if ns_uri_a.is_null() && !ns_uri_b.is_null() {
667 return std::cmp::Ordering::Less;
668 }
669 if !ns_uri_a.is_null() && ns_uri_b.is_null() {
670 return std::cmp::Ordering::Greater;
671 }
672 if !ns_uri_a.is_null() && !ns_uri_b.is_null() {
673 let cmp = unsafe { crate::abi::exports_xml2::xmlStrcmp(ns_uri_a, ns_uri_b) };
674 if cmp != 0 {
675 return cmp.cmp(&0);
676 }
677 }
678
679 let name_a = attr_a.name;
681 let name_b = attr_b.name;
682 if name_a.is_null() && name_b.is_null() {
683 return std::cmp::Ordering::Equal;
684 }
685 if name_a.is_null() {
686 return std::cmp::Ordering::Less;
687 }
688 if name_b.is_null() {
689 return std::cmp::Ordering::Greater;
690 }
691 let cmp = unsafe { crate::abi::exports_xml2::xmlStrcmp(name_a, name_b) };
692 cmp.cmp(&0)
693}
694
695unsafe fn c14n_serialize_attributes(node: *mut _xmlNode, buf: *mut _xmlBuffer) {
706 if node.is_null() || buf.is_null() {
707 return;
708 }
709
710 let n = unsafe { &*node };
711 if n.type_ != XML_ELEMENT_NODE as c_int {
712 return;
713 }
714
715 let mut attrs: Vec<*mut _xmlAttr> = Vec::new();
717 let mut cur_attr = n.properties;
718 while !cur_attr.is_null() {
719 attrs.push(cur_attr);
720 cur_attr = unsafe { (*cur_attr).next };
721 }
722
723 attrs.sort_by(|a, b| unsafe { compare_attrs(*a, *b) });
725
726 for &attr in &attrs {
728 let a = unsafe { &*attr };
729
730 io::buf_ccat(buf, b' ');
731
732 if !a.ns.is_null() {
734 let ans = unsafe { &*a.ns };
735 if !ans.prefix.is_null() {
736 io::buf_cat(buf, ans.prefix);
737 io::buf_ccat(buf, b':');
738 }
739 }
740 if !a.name.is_null() {
741 io::buf_cat(buf, a.name);
742 }
743
744 io::buf_add(buf, b"=\"" as *const u8, 2);
745
746 if !a.children.is_null() {
748 let child = unsafe { &*a.children };
749 if child.type_ == XML_TEXT_NODE as c_int && !child.content.is_null() {
750 c14n_escape_attr(buf, child.content);
751 }
752 }
753
754 io::buf_ccat(buf, b'"');
755 }
756}
757
758unsafe fn c14n_serialize_node(node: *mut _xmlNode, ctx: &mut C14nContext, buf: *mut _xmlBuffer) {
772 if node.is_null() || buf.is_null() {
773 return;
774 }
775
776 let n = unsafe { &*node };
777
778 match n.type_ {
779 t if t == XML_ELEMENT_NODE as c_int => {
780 c14n_serialize_element(node, ctx, buf);
781 }
782 t if t == XML_TEXT_NODE as c_int => {
783 if !n.content.is_null() {
784 c14n_escape_text(buf, n.content, tree::xml_strlen(n.content));
785 }
786 }
787 t if t == XML_CDATA_SECTION_NODE as c_int => {
788 if !n.content.is_null() {
790 c14n_escape_text(buf, n.content, tree::xml_strlen(n.content));
791 }
792 }
793 t if t == XML_COMMENT_NODE as c_int => {
794 if ctx.mode.with_comments() {
795 io::buf_add(buf, b"<!--" as *const u8, 4);
796 if !n.content.is_null() {
797 io::buf_cat(buf, n.content);
798 }
799 io::buf_add(buf, b"-->" as *const u8, 3);
800 }
801 }
802 t if t == XML_PI_NODE as c_int => {
803 io::buf_add(buf, b"<?" as *const u8, 2);
804 if !n.name.is_null() {
805 io::buf_cat(buf, n.name);
806 }
807 if !n.content.is_null() && unsafe { *n.content != 0 } {
808 io::buf_ccat(buf, b' ');
809 io::buf_cat(buf, n.content);
810 }
811 io::buf_add(buf, b"?>" as *const u8, 2);
812 }
813 t if t == XML_DOCUMENT_NODE as c_int || t == XML_HTML_DOCUMENT_NODE as c_int => {
814 let mut child = n.children;
816 while !child.is_null() {
817 c14n_serialize_node(child, ctx, buf);
818 child = unsafe { (*child).next };
819 }
820 }
821 t if t == XML_DTD_NODE as c_int || t == XML_DOCUMENT_TYPE_NODE as c_int => {
822 }
824 t if t == XML_ENTITY_REF_NODE as c_int => {
825 if !n.name.is_null() {
828 io::buf_ccat(buf, b'&');
829 io::buf_cat(buf, n.name);
830 io::buf_ccat(buf, b';');
831 }
832 }
833 _ => {
834 if !n.content.is_null() {
836 c14n_escape_text(buf, n.content, tree::xml_strlen(n.content));
837 }
838 }
839 }
840}
841
842unsafe fn c14n_serialize_element(node: *mut _xmlNode, ctx: &mut C14nContext, buf: *mut _xmlBuffer) {
853 if node.is_null() || buf.is_null() {
854 return;
855 }
856
857 let n = unsafe { &*node };
858
859 ctx.push_scope();
861
862 let ns_list = c14n_collect_namespaces(node, ctx);
864
865 io::buf_ccat(buf, b'<');
867
868 if !n.ns.is_null() {
870 let ns = unsafe { &*n.ns };
871 if !ns.prefix.is_null() {
872 io::buf_cat(buf, ns.prefix);
873 io::buf_ccat(buf, b':');
874 }
875 }
876 if !n.name.is_null() {
877 io::buf_cat(buf, n.name);
878 }
879
880 c14n_serialize_namespaces(buf, &ns_list);
882
883 c14n_serialize_attributes(node, buf);
885
886 if n.children.is_null() {
887 io::buf_add(buf, b"/>" as *const u8, 2);
889 } else {
890 io::buf_ccat(buf, b'>');
891
892 let mut child = n.children;
894 while !child.is_null() {
895 c14n_serialize_node(child, ctx, buf);
896 child = unsafe { (*child).next };
897 }
898
899 io::buf_add(buf, b"</" as *const u8, 2);
901 if !n.ns.is_null() {
902 let ns = unsafe { &*n.ns };
903 if !ns.prefix.is_null() {
904 io::buf_cat(buf, ns.prefix);
905 io::buf_ccat(buf, b':');
906 }
907 }
908 if !n.name.is_null() {
909 io::buf_cat(buf, n.name);
910 }
911 io::buf_ccat(buf, b'>');
912 }
913
914 ctx.pop_scope();
916}
917
918pub unsafe fn c14n_doc_dump_memory(
936 doc: *mut _xmlDoc,
937 nodes: *mut *mut _xmlNode,
938 mode: C14nMode,
939 inclusive_ns_prefixes: *const xmlChar,
940 with_comments: c_int,
941 result: *mut *mut xmlChar,
942) -> c_int {
943 if doc.is_null() || result.is_null() {
944 return -1;
945 }
946
947 let effective_mode = if with_comments != 0 {
949 match mode {
950 C14nMode::XML_C14N_1_0 => C14nMode::XML_C14N_1_0_WITH_COMMENTS,
951 C14nMode::XML_C14N_EXCLUSIVE_1_0 => C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS,
952 C14nMode::XML_C14N_1_1 => C14nMode::XML_C14N_1_1_WITH_COMMENTS,
953 _ => mode,
954 }
955 } else {
956 mode
957 };
958
959 let inclusive_set = parse_inclusive_prefixes(inclusive_ns_prefixes);
961
962 let mut ctx = C14nContext::new(doc, effective_mode, inclusive_set);
963
964 let buf = io::buf_create(-1);
966 if buf.is_null() {
967 return -1;
968 }
969
970 if nodes.is_null() {
971 let doc_node = doc as *mut _xmlNode;
973 let d = unsafe { &*doc_node };
974 let mut child = d.children;
975 while !child.is_null() {
976 c14n_serialize_node(child, &mut ctx, buf);
977 child = unsafe { (*child).next };
978 }
979 } else {
980 let mut node_vec: Vec<*mut _xmlNode> = Vec::new();
983 let mut i = 0;
984 loop {
985 let n = unsafe { *nodes.add(i) };
986 if n.is_null() {
987 break;
988 }
989 node_vec.push(n);
990 i += 1;
991 }
992
993 node_vec.sort_by(|a, b| unsafe { cmp_document_order(*a, *b) });
995
996 for &n in &node_vec {
997 c14n_serialize_node(n, &mut ctx, buf);
998 }
999 }
1000
1001 let content = io::buf_content(buf);
1003 let len = io::buf_length(buf);
1004 if content.is_null() || len < 0 {
1005 io::buf_free(buf);
1006 return -1;
1007 }
1008
1009 let result_str = crate::abi::exports_xml2::xmlStrdup(content);
1011 io::buf_free(buf);
1012
1013 if result_str.is_null() {
1014 return -1;
1015 }
1016
1017 unsafe {
1018 *result = result_str;
1019 }
1020
1021 len
1022}
1023
1024pub unsafe fn c14n_execute(
1032 doc: *mut _xmlDoc,
1033 mode: C14nMode,
1034 inclusive_ns_prefixes: *const xmlChar,
1035 with_comments: c_int,
1036 callback: Option<
1037 unsafe extern "C" fn(ctx: *mut c_void, data: *const c_char, len: c_int) -> c_int,
1038 >,
1039 callback_data: *mut c_void,
1040) -> c_int {
1041 if doc.is_null() || callback.is_none() {
1042 return -1;
1043 }
1044
1045 let callback = callback.unwrap();
1046
1047 let effective_mode = if with_comments != 0 {
1049 match mode {
1050 C14nMode::XML_C14N_1_0 => C14nMode::XML_C14N_1_0_WITH_COMMENTS,
1051 C14nMode::XML_C14N_EXCLUSIVE_1_0 => C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS,
1052 C14nMode::XML_C14N_1_1 => C14nMode::XML_C14N_1_1_WITH_COMMENTS,
1053 _ => mode,
1054 }
1055 } else {
1056 mode
1057 };
1058
1059 let inclusive_set = parse_inclusive_prefixes(inclusive_ns_prefixes);
1061
1062 let mut ctx = C14nContext::new(doc, effective_mode, inclusive_set);
1063
1064 let buf = io::buf_create(-1);
1066 if buf.is_null() {
1067 return -1;
1068 }
1069
1070 let doc_node = doc as *mut _xmlNode;
1072 let d = unsafe { &*doc_node };
1073 let mut child = d.children;
1074 while !child.is_null() {
1075 c14n_serialize_node(child, &mut ctx, buf);
1076 child = unsafe { (*child).next };
1077 }
1078
1079 let content = io::buf_content(buf);
1081 let len = io::buf_length(buf);
1082 if content.is_null() || len < 0 {
1083 io::buf_free(buf);
1084 return -1;
1085 }
1086
1087 let ret = unsafe { callback(callback_data, content as *const c_char, len) };
1088
1089 io::buf_free(buf);
1090 ret
1091}
1092
1093pub unsafe fn c14n_doc_save_to(
1101 doc: *mut _xmlDoc,
1102 nodes: *mut *mut _xmlNode,
1103 mode: C14nMode,
1104 inclusive_ns_prefixes: *const xmlChar,
1105 with_comments: c_int,
1106 output: *mut _xmlOutputBuffer,
1107) -> c_int {
1108 if doc.is_null() || output.is_null() {
1109 return -1;
1110 }
1111
1112 let effective_mode = if with_comments != 0 {
1114 match mode {
1115 C14nMode::XML_C14N_1_0 => C14nMode::XML_C14N_1_0_WITH_COMMENTS,
1116 C14nMode::XML_C14N_EXCLUSIVE_1_0 => C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS,
1117 C14nMode::XML_C14N_1_1 => C14nMode::XML_C14N_1_1_WITH_COMMENTS,
1118 _ => mode,
1119 }
1120 } else {
1121 mode
1122 };
1123
1124 let inclusive_set = parse_inclusive_prefixes(inclusive_ns_prefixes);
1126
1127 let mut ctx = C14nContext::new(doc, effective_mode, inclusive_set);
1128
1129 let buf = io::buf_create(-1);
1131 if buf.is_null() {
1132 return -1;
1133 }
1134
1135 if nodes.is_null() {
1136 let doc_node = doc as *mut _xmlNode;
1138 let d = unsafe { &*doc_node };
1139 let mut child = d.children;
1140 while !child.is_null() {
1141 c14n_serialize_node(child, &mut ctx, buf);
1142 child = unsafe { (*child).next };
1143 }
1144 } else {
1145 let mut node_vec: Vec<*mut _xmlNode> = Vec::new();
1147 let mut i = 0;
1148 loop {
1149 let n = unsafe { *nodes.add(i) };
1150 if n.is_null() {
1151 break;
1152 }
1153 node_vec.push(n);
1154 i += 1;
1155 }
1156
1157 node_vec.sort_by(|a, b| unsafe { cmp_document_order(*a, *b) });
1158
1159 for &n in &node_vec {
1160 c14n_serialize_node(n, &mut ctx, buf);
1161 }
1162 }
1163
1164 let content = io::buf_content(buf);
1166 let len = io::buf_length(buf);
1167 if content.is_null() || len < 0 {
1168 io::buf_free(buf);
1169 return -1;
1170 }
1171
1172 let written = io::output_buffer_write(output, len, content as *const c_char);
1173 io::buf_free(buf);
1174
1175 let flush_ret = io::output_buffer_flush(output);
1177 if flush_ret < 0 {
1178 return written;
1179 }
1180
1181 written
1182}
1183
1184fn parse_inclusive_prefixes(input: *const xmlChar) -> Option<HashSet<String>> {
1192 if input.is_null() {
1193 return None;
1194 }
1195
1196 let input_str = unsafe {
1197 let c_str = core::ffi::CStr::from_ptr(input as *const c_char);
1198 match c_str.to_str() {
1199 Ok(s) => s.to_string(),
1200 Err(_) => return None,
1201 }
1202 };
1203
1204 if input_str.is_empty() {
1205 return None;
1206 }
1207
1208 let mut set = HashSet::new();
1209 for prefix in input_str.split(',') {
1210 let trimmed = prefix.trim();
1211 if !trimmed.is_empty() {
1212 set.insert(trimmed.to_string());
1213 }
1214 }
1215
1216 if set.is_empty() {
1217 None
1218 } else {
1219 Some(set)
1220 }
1221}
1222
1223unsafe fn cmp_document_order(a: *mut _xmlNode, b: *mut _xmlNode) -> std::cmp::Ordering {
1231 if a == b {
1232 return std::cmp::Ordering::Equal;
1233 }
1234
1235 let mut ancestors_a: Vec<*mut _xmlNode> = Vec::new();
1237 let mut cur = a;
1238 while !cur.is_null() {
1239 ancestors_a.push(cur);
1240 cur = unsafe { (*cur).parent };
1241 }
1242
1243 let mut ancestors_b: Vec<*mut _xmlNode> = Vec::new();
1244 let mut cur = b;
1245 while !cur.is_null() {
1246 ancestors_b.push(cur);
1247 cur = unsafe { (*cur).parent };
1248 }
1249
1250 let mut i = ancestors_a.len();
1252 let mut j = ancestors_b.len();
1253
1254 while i > 0 && j > 0 && ancestors_a[i - 1] == ancestors_b[j - 1] {
1255 i -= 1;
1256 j -= 1;
1257 }
1258
1259 if i == 0 || j == 0 {
1260 if i == 0 {
1262 return std::cmp::Ordering::Less;
1263 }
1264 return std::cmp::Ordering::Greater;
1265 }
1266
1267 let sibling_a = ancestors_a[i - 1];
1270 let sibling_b = ancestors_b[j - 1];
1271
1272 let mut walk = sibling_a;
1274 while !walk.is_null() {
1275 if walk == sibling_b {
1276 return std::cmp::Ordering::Less;
1277 }
1278 walk = unsafe { (*walk).next };
1279 }
1280
1281 std::cmp::Ordering::Greater
1283}
1284
1285#[no_mangle]
1316pub unsafe extern "C" fn xmlC14NDocDumpMemory(
1317 doc: *mut _xmlDoc,
1318 nodes: *mut *mut _xmlNode,
1319 mode: c_int,
1320 inclusive_ns_prefixes: *mut *mut xmlChar,
1321 with_comments: c_int,
1322 result: *mut *mut xmlChar,
1323) -> c_int {
1324 let c14n_mode = match mode {
1328 0 => C14nMode::XML_C14N_1_0,
1329 1 => C14nMode::XML_C14N_EXCLUSIVE_1_0,
1330 2 => C14nMode::XML_C14N_1_1,
1331 3 => C14nMode::XML_C14N_1_0_WITH_COMMENTS,
1332 4 => C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS,
1333 5 => C14nMode::XML_C14N_1_1_WITH_COMMENTS,
1334 _ => return -1,
1335 };
1336
1337 let joined_prefixes = if !inclusive_ns_prefixes.is_null() {
1341 let mut parts: Vec<*mut xmlChar> = Vec::new();
1342 let mut i = 0;
1343 loop {
1344 let p = unsafe { *inclusive_ns_prefixes.add(i) };
1345 if p.is_null() {
1346 break;
1347 }
1348 parts.push(p);
1349 i += 1;
1350 }
1351
1352 if parts.is_empty() {
1353 ptr::null()
1354 } else {
1355 let mut result_str = Vec::<u8>::new();
1357 for (idx, &part) in parts.iter().enumerate() {
1358 if idx > 0 {
1359 result_str.push(b',');
1360 }
1361 let len = tree::xml_strlen(part);
1362 let part_slice = unsafe { core::slice::from_raw_parts(part, len as usize) };
1363 result_str.extend_from_slice(part_slice);
1364 }
1365 result_str.push(0); result_str.as_ptr() as *const xmlChar
1367 }
1368 } else {
1369 ptr::null()
1370 };
1371
1372 unsafe {
1373 c14n_doc_dump_memory(
1374 doc,
1375 nodes,
1376 c14n_mode,
1377 joined_prefixes,
1378 with_comments,
1379 result,
1380 )
1381 }
1382}
1383
1384#[no_mangle]
1404pub unsafe extern "C" fn xmlC14NExecute(
1405 doc: *mut _xmlDoc,
1406 mode: c_int,
1407 inclusive_ns_prefixes: *mut *mut xmlChar,
1408 with_comments: c_int,
1409 callback: Option<
1410 unsafe extern "C" fn(ctx: *mut c_void, data: *const c_char, len: c_int) -> c_int,
1411 >,
1412 callback_data: *mut c_void,
1413) -> c_int {
1414 let c14n_mode = match mode {
1417 0 => C14nMode::XML_C14N_1_0,
1418 1 => C14nMode::XML_C14N_EXCLUSIVE_1_0,
1419 2 => C14nMode::XML_C14N_1_1,
1420 3 => C14nMode::XML_C14N_1_0_WITH_COMMENTS,
1421 4 => C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS,
1422 5 => C14nMode::XML_C14N_1_1_WITH_COMMENTS,
1423 _ => return -1,
1424 };
1425
1426 let joined_prefixes = if !inclusive_ns_prefixes.is_null() {
1427 let mut parts: Vec<*mut xmlChar> = Vec::new();
1428 let mut i = 0;
1429 loop {
1430 let p = unsafe { *inclusive_ns_prefixes.add(i) };
1431 if p.is_null() {
1432 break;
1433 }
1434 parts.push(p);
1435 i += 1;
1436 }
1437
1438 if parts.is_empty() {
1439 ptr::null()
1440 } else {
1441 let mut result_str = Vec::<u8>::new();
1442 for (idx, &part) in parts.iter().enumerate() {
1443 if idx > 0 {
1444 result_str.push(b',');
1445 }
1446 let len = tree::xml_strlen(part);
1447 let part_slice = unsafe { core::slice::from_raw_parts(part, len as usize) };
1448 result_str.extend_from_slice(part_slice);
1449 }
1450 result_str.push(0);
1451 result_str.as_ptr() as *const xmlChar
1452 }
1453 } else {
1454 ptr::null()
1455 };
1456
1457 unsafe {
1458 c14n_execute(
1459 doc,
1460 c14n_mode,
1461 joined_prefixes,
1462 with_comments,
1463 callback,
1464 callback_data,
1465 )
1466 }
1467}
1468
1469#[no_mangle]
1489pub unsafe extern "C" fn xmlC14NDocSaveTo(
1490 doc: *mut _xmlDoc,
1491 nodes: *mut *mut _xmlNode,
1492 mode: c_int,
1493 inclusive_ns_prefixes: *mut *mut xmlChar,
1494 with_comments: c_int,
1495 output: *mut _xmlOutputBuffer,
1496) -> c_int {
1497 let c14n_mode = match mode {
1500 0 => C14nMode::XML_C14N_1_0,
1501 1 => C14nMode::XML_C14N_EXCLUSIVE_1_0,
1502 2 => C14nMode::XML_C14N_1_1,
1503 3 => C14nMode::XML_C14N_1_0_WITH_COMMENTS,
1504 4 => C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS,
1505 5 => C14nMode::XML_C14N_1_1_WITH_COMMENTS,
1506 _ => return -1,
1507 };
1508
1509 let joined_prefixes: Option<Vec<u8>> = if !inclusive_ns_prefixes.is_null() {
1510 let mut parts: Vec<*mut xmlChar> = Vec::new();
1511 let mut i = 0;
1512 loop {
1513 let p = unsafe { *inclusive_ns_prefixes.add(i) };
1514 if p.is_null() {
1515 break;
1516 }
1517 parts.push(p);
1518 i += 1;
1519 }
1520
1521 if parts.is_empty() {
1522 None
1523 } else {
1524 let mut result_str = Vec::<u8>::new();
1525 for (idx, &part) in parts.iter().enumerate() {
1526 if idx > 0 {
1527 result_str.push(b',');
1528 }
1529 let len = tree::xml_strlen(part);
1530 let part_slice = unsafe { core::slice::from_raw_parts(part, len as usize) };
1531 result_str.extend_from_slice(part_slice);
1532 }
1533 result_str.push(0);
1534 Some(result_str)
1535 }
1536 } else {
1537 None
1538 };
1539
1540 let joined_ptr = joined_prefixes
1541 .as_ref()
1542 .map(|v| v.as_ptr() as *const xmlChar)
1543 .unwrap_or(ptr::null());
1544
1545 unsafe { c14n_doc_save_to(doc, nodes, c14n_mode, joined_ptr, with_comments, output) }
1546}
1547
1548#[cfg(test)]
1553mod tests {
1554 use super::*;
1555 use crate::abi::allocator::xmlFreeImpl;
1556 use crate::xml::io;
1557 use crate::xml::tree;
1558 use core::ptr;
1559 use std::os::raw::c_int;
1560
1561 unsafe fn create_simple_doc() -> *mut _xmlDoc {
1565 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
1566 assert!(!doc.is_null());
1567
1568 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
1569 assert!(!root.is_null());
1570 tree::doc_set_root_element(doc, root);
1571
1572 let child = tree::new_node(ptr::null_mut(), b"child\0" as *const u8 as *const xmlChar);
1573 assert!(!child.is_null());
1574 tree::add_child(root, child);
1575
1576 tree::set_prop(
1578 child,
1579 b"attr\0" as *const u8 as *const xmlChar,
1580 b"value\0" as *const u8 as *const xmlChar,
1581 );
1582
1583 let text = tree::new_text(b"text\0" as *const u8 as *const xmlChar);
1585 assert!(!text.is_null());
1586 tree::add_child(child, text);
1587
1588 doc
1589 }
1590
1591 unsafe fn canonicalize_doc(doc: *mut _xmlDoc, mode: C14nMode, with_comments: c_int) -> String {
1593 let mut result: *mut xmlChar = ptr::null_mut();
1594 let len = c14n_doc_dump_memory(
1595 doc,
1596 ptr::null_mut(),
1597 mode,
1598 ptr::null(),
1599 with_comments,
1600 &mut result as *mut *mut xmlChar,
1601 );
1602 assert!(len >= 0);
1603 assert!(!result.is_null());
1604
1605 let s = {
1606 let slice = core::slice::from_raw_parts(result, len as usize);
1607 String::from_utf8_lossy(slice).to_string()
1608 };
1609 xmlFreeImpl(result as *mut c_void);
1610 s
1611 }
1612
1613 #[test]
1616 fn test_c14n_basic_document() {
1617 unsafe {
1618 let doc = create_simple_doc();
1619 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1620 assert!(
1621 result.contains("<root>"),
1622 "Result should contain <root>, got: {}",
1623 result
1624 );
1625 assert!(
1626 result.contains("<child"),
1627 "Result should contain <child>, got: {}",
1628 result
1629 );
1630 assert!(
1631 result.contains("attr=\"value\""),
1632 "Result should contain attr=\"value\", got: {}",
1633 result
1634 );
1635 assert!(
1636 result.contains("text"),
1637 "Result should contain text, got: {}",
1638 result
1639 );
1640 assert!(
1641 result.contains("</child>"),
1642 "Result should contain </child>, got: {}",
1643 result
1644 );
1645 assert!(
1646 result.contains("</root>"),
1647 "Result should contain </root>, got: {}",
1648 result
1649 );
1650 tree::free_doc(doc);
1651 }
1652 }
1653
1654 #[test]
1655 fn test_c14n_basic_empty_element() {
1656 unsafe {
1657 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
1658 assert!(!doc.is_null());
1659 let root = tree::new_node(ptr::null_mut(), b"empty\0" as *const u8 as *const xmlChar);
1660 assert!(!root.is_null());
1661 tree::doc_set_root_element(doc, root);
1662
1663 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1664 assert!(
1666 result.contains("<empty/>"),
1667 "Empty element should be self-closing, got: {}",
1668 result
1669 );
1670 tree::free_doc(doc);
1671 }
1672 }
1673
1674 #[test]
1677 fn test_c14n_namespace_propagation() {
1678 unsafe {
1679 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
1680 assert!(!doc.is_null());
1681
1682 let ns = tree::new_ns(
1683 ptr::null_mut(),
1684 b"http://example.com/ns\0" as *const u8 as *const xmlChar,
1685 b"ex\0" as *const u8 as *const xmlChar,
1686 );
1687 assert!(!ns.is_null());
1688
1689 let root = tree::new_node(ns, b"root\0" as *const u8 as *const xmlChar);
1690 assert!(!root.is_null());
1691 tree::doc_set_root_element(doc, root);
1692
1693 tree::new_ns(
1695 root,
1696 b"http://example.com/ns\0" as *const u8 as *const xmlChar,
1697 b"ex\0" as *const u8 as *const xmlChar,
1698 );
1699
1700 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1701 assert!(
1702 result.contains("xmlns:ex=\"http://example.com/ns\""),
1703 "Result should contain namespace declaration, got: {}",
1704 result
1705 );
1706 assert!(
1707 result.contains("<ex:root"),
1708 "Result should contain <ex:root, got: {}",
1709 result
1710 );
1711 tree::free_doc(doc);
1712 }
1713 }
1714
1715 #[test]
1718 fn test_c14n_attribute_ordering() {
1719 unsafe {
1720 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
1721 assert!(!doc.is_null());
1722
1723 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
1724 assert!(!root.is_null());
1725 tree::doc_set_root_element(doc, root);
1726
1727 tree::set_prop(
1729 root,
1730 b"zeta\0" as *const u8 as *const xmlChar,
1731 b"1\0" as *const u8 as *const xmlChar,
1732 );
1733 tree::set_prop(
1734 root,
1735 b"alpha\0" as *const u8 as *const xmlChar,
1736 b"2\0" as *const u8 as *const xmlChar,
1737 );
1738 tree::set_prop(
1739 root,
1740 b"beta\0" as *const u8 as *const xmlChar,
1741 b"3\0" as *const u8 as *const xmlChar,
1742 );
1743
1744 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1745
1746 let alpha_pos = result.find("alpha=\"2\"");
1748 let beta_pos = result.find("beta=\"3\"");
1749 let zeta_pos = result.find("zeta=\"1\"");
1750
1751 assert!(alpha_pos.is_some(), "alpha attribute should be present");
1752 assert!(beta_pos.is_some(), "beta attribute should be present");
1753 assert!(zeta_pos.is_some(), "zeta attribute should be present");
1754
1755 assert!(
1757 alpha_pos.unwrap() < beta_pos.unwrap(),
1758 "alpha should come before beta"
1759 );
1760 assert!(
1761 beta_pos.unwrap() < zeta_pos.unwrap(),
1762 "beta should come before zeta"
1763 );
1764
1765 tree::free_doc(doc);
1766 }
1767 }
1768
1769 #[test]
1772 fn test_c14n_character_escaping_text() {
1773 unsafe {
1774 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
1775 assert!(!doc.is_null());
1776
1777 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
1778 assert!(!root.is_null());
1779 tree::doc_set_root_element(doc, root);
1780
1781 let text = tree::new_text(b"a < b & c > d\r\0" as *const u8 as *const xmlChar);
1783 assert!(!text.is_null());
1784 tree::add_child(root, text);
1785
1786 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1787 assert!(result.contains("<"), "Should escape <, got: {}", result);
1788 assert!(result.contains("&"), "Should escape &, got: {}", result);
1789 assert!(result.contains(">"), "Should escape >, got: {}", result);
1790 assert!(
1791 result.contains("
"),
1792 "Should escape CR, got: {}",
1793 result
1794 );
1795
1796 tree::free_doc(doc);
1797 }
1798 }
1799
1800 #[test]
1801 fn test_c14n_character_escaping_attr() {
1802 unsafe {
1803 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
1804 assert!(!doc.is_null());
1805
1806 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
1807 assert!(!root.is_null());
1808 tree::doc_set_root_element(doc, root);
1809
1810 tree::set_prop(
1812 root,
1813 b"test\0" as *const u8 as *const xmlChar,
1814 b"a < b & c \" d\t\n\r\0" as *const u8 as *const xmlChar,
1815 );
1816
1817 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1818 assert!(
1819 result.contains("<"),
1820 "Should escape < in attr, got: {}",
1821 result
1822 );
1823 assert!(
1824 result.contains("&"),
1825 "Should escape & in attr, got: {}",
1826 result
1827 );
1828 assert!(
1829 result.contains("""),
1830 "Should escape \" in attr, got: {}",
1831 result
1832 );
1833 assert!(
1834 result.contains("	"),
1835 "Should escape tab in attr, got: {}",
1836 result
1837 );
1838 assert!(
1839 result.contains("
"),
1840 "Should escape newline in attr, got: {}",
1841 result
1842 );
1843 assert!(
1844 result.contains("
"),
1845 "Should escape CR in attr, got: {}",
1846 result
1847 );
1848
1849 tree::free_doc(doc);
1850 }
1851 }
1852
1853 #[test]
1856 fn test_c14n_with_comments() {
1857 unsafe {
1858 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
1859 assert!(!doc.is_null());
1860
1861 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
1862 assert!(!root.is_null());
1863 tree::doc_set_root_element(doc, root);
1864
1865 let comment = tree::new_comment(b" a comment \0" as *const u8 as *const xmlChar);
1866 assert!(!comment.is_null());
1867 tree::add_child(root, comment);
1868
1869 let result_no_comments = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1871 assert!(
1872 !result_no_comments.contains("<!--"),
1873 "Without comments: should not contain comments, got: {}",
1874 result_no_comments
1875 );
1876
1877 let result_with_comments =
1879 canonicalize_doc(doc, C14nMode::XML_C14N_1_0_WITH_COMMENTS, 0);
1880 assert!(
1881 result_with_comments.contains("<!--"),
1882 "With comments: should contain comments, got: {}",
1883 result_with_comments
1884 );
1885
1886 tree::free_doc(doc);
1887 }
1888 }
1889
1890 #[test]
1893 fn test_c14n_exclusive_vs_inclusive() {
1894 unsafe {
1895 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
1896 assert!(!doc.is_null());
1897
1898 tree::new_ns(
1900 ptr::null_mut(),
1901 b"http://example.com/ns1\0" as *const u8 as *const xmlChar,
1902 b"ns1\0" as *const u8 as *const xmlChar,
1903 );
1904 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
1905 assert!(!root.is_null());
1906 tree::doc_set_root_element(doc, root);
1907 tree::new_ns(
1908 root,
1909 b"http://example.com/ns1\0" as *const u8 as *const xmlChar,
1910 b"ns1\0" as *const u8 as *const xmlChar,
1911 );
1912
1913 let child = tree::new_node(ptr::null_mut(), b"child\0" as *const u8 as *const xmlChar);
1915 assert!(!child.is_null());
1916 tree::add_child(root, child);
1917
1918 let result_inclusive = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1920 let _result_exclusive = canonicalize_doc(doc, C14nMode::XML_C14N_EXCLUSIVE_1_0, 0);
1926
1927 assert!(
1933 result_inclusive.contains("ns1"),
1934 "Inclusive should have ns1, got: {}",
1935 result_inclusive
1936 );
1937
1938 tree::free_doc(doc);
1939 }
1940 }
1941
1942 #[test]
1945 fn test_c14n_no_xml_declaration() {
1946 unsafe {
1947 let doc = create_simple_doc();
1948 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1949 assert!(
1951 !result.contains("<?xml"),
1952 "C14N output should not contain XML declaration, got: {}",
1953 result
1954 );
1955 tree::free_doc(doc);
1956 }
1957 }
1958
1959 #[test]
1962 fn test_c14n_empty_document() {
1963 unsafe {
1964 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
1965 assert!(!doc.is_null());
1966
1967 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
1968 assert!(
1969 result.is_empty(),
1970 "Empty document should produce empty output, got: {}",
1971 result
1972 );
1973
1974 tree::free_doc(doc);
1975 }
1976 }
1977
1978 #[test]
1981 fn test_c14n_null_doc() {
1982 unsafe {
1983 let mut result: *mut xmlChar = ptr::null_mut();
1984 let len = c14n_doc_dump_memory(
1985 ptr::null_mut(),
1986 ptr::null_mut(),
1987 C14nMode::XML_C14N_1_0,
1988 ptr::null(),
1989 0,
1990 &mut result as *mut *mut xmlChar,
1991 );
1992 assert_eq!(len, -1, "Null doc should return -1");
1993 }
1994 }
1995
1996 #[test]
1997 fn test_c14n_text_node() {
1998 unsafe {
1999 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
2000 assert!(!doc.is_null());
2001
2002 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
2003 assert!(!root.is_null());
2004 tree::doc_set_root_element(doc, root);
2005
2006 let text = tree::new_text(b"Hello World\0" as *const u8 as *const xmlChar);
2008 assert!(!text.is_null());
2009 tree::add_child(root, text);
2010
2011 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
2012 assert!(
2013 result.contains("Hello World"),
2014 "Should contain text content, got: {}",
2015 result
2016 );
2017
2018 tree::free_doc(doc);
2019 }
2020 }
2021
2022 #[test]
2023 fn test_c14n_cdata_section() {
2024 unsafe {
2025 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
2026 assert!(!doc.is_null());
2027
2028 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
2029 assert!(!root.is_null());
2030 tree::doc_set_root_element(doc, root);
2031
2032 let cdata =
2035 tree::new_text(b"<greeting>Hello</greeting>\0" as *const u8 as *const xmlChar);
2036 assert!(!cdata.is_null());
2037 tree::add_child(root, cdata);
2038
2039 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
2040 assert!(
2041 result.contains("<greeting>"),
2042 "CDATA should be converted to escaped text, got: {}",
2043 result
2044 );
2045
2046 tree::free_doc(doc);
2047 }
2048 }
2049
2050 #[test]
2051 fn test_c14n_pi_node() {
2052 unsafe {
2053 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
2054 assert!(!doc.is_null());
2055
2056 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
2057 assert!(!root.is_null());
2058 tree::doc_set_root_element(doc, root);
2059
2060 let pi = tree::new_pi(
2061 b"xml-model\0" as *const u8 as *const xmlChar,
2062 b"href=\"schema.xsd\"\0" as *const u8 as *const xmlChar,
2063 );
2064 assert!(!pi.is_null());
2065 tree::add_child(root, pi);
2066
2067 let result = canonicalize_doc(doc, C14nMode::XML_C14N_1_0, 0);
2068 assert!(result.contains("<?"), "Should contain PI, got: {}", result);
2069
2070 tree::free_doc(doc);
2071 }
2072 }
2073
2074 #[test]
2075 fn test_c14n_with_comments_flag() {
2076 unsafe {
2077 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
2078 assert!(!doc.is_null());
2079
2080 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
2081 assert!(!root.is_null());
2082 tree::doc_set_root_element(doc, root);
2083
2084 let comment = tree::new_comment(b"test\0" as *const u8 as *const xmlChar);
2085 assert!(!comment.is_null());
2086 tree::add_child(root, comment);
2087
2088 let mut result: *mut xmlChar = ptr::null_mut();
2090 let len = c14n_doc_dump_memory(
2091 doc,
2092 ptr::null_mut(),
2093 C14nMode::XML_C14N_1_0,
2094 ptr::null(),
2095 1, &mut result as *mut *mut xmlChar,
2097 );
2098 assert!(len >= 0);
2099 let s = {
2100 let slice = core::slice::from_raw_parts(result, len as usize);
2101 String::from_utf8_lossy(slice).to_string()
2102 };
2103 xmlFreeImpl(result as *mut c_void);
2104
2105 assert!(
2106 s.contains("<!--"),
2107 "With comments flag should include comments, got: {}",
2108 s
2109 );
2110
2111 tree::free_doc(doc);
2112 }
2113 }
2114
2115 #[test]
2116 fn test_c14n_mode_enum_values() {
2117 assert_eq!(C14nMode::XML_C14N_1_0 as c_int, 0);
2119 assert_eq!(C14nMode::XML_C14N_EXCLUSIVE_1_0 as c_int, 1);
2120 assert_eq!(C14nMode::XML_C14N_1_1 as c_int, 2);
2121 assert_eq!(C14nMode::XML_C14N_1_0_WITH_COMMENTS as c_int, 3);
2122 assert_eq!(C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS as c_int, 4);
2123 assert_eq!(C14nMode::XML_C14N_1_1_WITH_COMMENTS as c_int, 5);
2124 }
2125
2126 #[test]
2127 fn test_c14n_with_comments_property() {
2128 assert!(C14nMode::XML_C14N_1_0_WITH_COMMENTS.with_comments());
2129 assert!(C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS.with_comments());
2130 assert!(C14nMode::XML_C14N_1_1_WITH_COMMENTS.with_comments());
2131 assert!(!C14nMode::XML_C14N_1_0.with_comments());
2132 assert!(!C14nMode::XML_C14N_EXCLUSIVE_1_0.with_comments());
2133 assert!(!C14nMode::XML_C14N_1_1.with_comments());
2134 }
2135
2136 #[test]
2137 fn test_c14n_is_exclusive_property() {
2138 assert!(C14nMode::XML_C14N_EXCLUSIVE_1_0.is_exclusive());
2139 assert!(C14nMode::XML_C14N_EXCLUSIVE_1_0_WITH_COMMENTS.is_exclusive());
2140 assert!(!C14nMode::XML_C14N_1_0.is_exclusive());
2141 assert!(!C14nMode::XML_C14N_1_0_WITH_COMMENTS.is_exclusive());
2142 assert!(!C14nMode::XML_C14N_1_1.is_exclusive());
2143 }
2144
2145 #[test]
2146 fn test_c14n_escape_text_cr() {
2147 unsafe {
2148 let buf = io::buf_create(-1);
2149 assert!(!buf.is_null());
2150
2151 let text = b"line1\r\nline2\r\0" as *const u8 as *const xmlChar;
2152 c14n_escape_text(buf, text, 13);
2153
2154 let content = io::buf_content(buf);
2155 let len = io::buf_length(buf);
2156 let s = {
2157 let slice = core::slice::from_raw_parts(content, len as usize);
2158 String::from_utf8_lossy(slice).to_string()
2159 };
2160 assert!(
2161 s.contains("
"),
2162 "CR should be escaped as 
, got: {}",
2163 s
2164 );
2165 assert!(s.contains("\n"), "LF should remain as-is, got: {}", s);
2166
2167 io::buf_free(buf);
2168 }
2169 }
2170
2171 #[test]
2172 fn test_c14n_escape_attr_tab_nl_cr() {
2173 unsafe {
2174 let buf = io::buf_create(-1);
2175 assert!(!buf.is_null());
2176
2177 let text = b"a\tb\nc\rd\0" as *const u8 as *const xmlChar;
2178 c14n_escape_attr(buf, text);
2179
2180 let content = io::buf_content(buf);
2181 let len = io::buf_length(buf);
2182 let s = {
2183 let slice = core::slice::from_raw_parts(content, len as usize);
2184 String::from_utf8_lossy(slice).to_string()
2185 };
2186 assert!(
2187 s.contains("	"),
2188 "Tab should be escaped as 	, got: {}",
2189 s
2190 );
2191 assert!(
2192 s.contains("
"),
2193 "NL should be escaped as 
, got: {}",
2194 s
2195 );
2196 assert!(
2197 s.contains("
"),
2198 "CR should be escaped as 
, got: {}",
2199 s
2200 );
2201
2202 io::buf_free(buf);
2203 }
2204 }
2205
2206 #[test]
2207 fn test_c14n_parse_inclusive_prefixes() {
2208 assert!(parse_inclusive_prefixes(ptr::null()).is_none());
2210
2211 let empty = b"\0" as *const u8 as *const xmlChar;
2213 assert!(parse_inclusive_prefixes(empty).is_none());
2214
2215 let single = b"foo\0" as *const u8 as *const xmlChar;
2217 let result = parse_inclusive_prefixes(single);
2218 assert!(result.is_some());
2219 assert!(result.unwrap().contains("foo"));
2220
2221 let multi = b"foo,bar,baz\0" as *const u8 as *const xmlChar;
2223 let result = parse_inclusive_prefixes(multi);
2224 assert!(result.is_some());
2225 let set = result.unwrap();
2226 assert!(set.contains("foo"));
2227 assert!(set.contains("bar"));
2228 assert!(set.contains("baz"));
2229 assert_eq!(set.len(), 3);
2230 }
2231
2232 #[test]
2233 fn test_c14n_document_order() {
2234 unsafe {
2235 let doc = tree::new_doc(b"1.0\0" as *const u8 as *const xmlChar);
2236 assert!(!doc.is_null());
2237
2238 let root = tree::new_node(ptr::null_mut(), b"root\0" as *const u8 as *const xmlChar);
2239 assert!(!root.is_null());
2240 tree::doc_set_root_element(doc, root);
2241
2242 let child1 =
2243 tree::new_node(ptr::null_mut(), b"child1\0" as *const u8 as *const xmlChar);
2244 assert!(!child1.is_null());
2245 tree::add_child(root, child1);
2246
2247 let child2 =
2248 tree::new_node(ptr::null_mut(), b"child2\0" as *const u8 as *const xmlChar);
2249 assert!(!child2.is_null());
2250 tree::add_child(root, child2);
2251
2252 assert_eq!(
2254 cmp_document_order(child1, child2),
2255 std::cmp::Ordering::Less,
2256 "child1 should be before child2"
2257 );
2258 assert_eq!(
2259 cmp_document_order(child2, child1),
2260 std::cmp::Ordering::Greater,
2261 "child2 should be after child1"
2262 );
2263 assert_eq!(
2264 cmp_document_order(child1, child1),
2265 std::cmp::Ordering::Equal,
2266 "Same node should be equal"
2267 );
2268
2269 tree::free_doc(doc);
2270 }
2271 }
2272
2273 #[test]
2274 fn test_c14n_escape_text_gt() {
2275 unsafe {
2276 let buf = io::buf_create(-1);
2277 assert!(!buf.is_null());
2278
2279 let text = b"a > b\0" as *const u8 as *const xmlChar;
2280 c14n_escape_text(buf, text, 5);
2281
2282 let content = io::buf_content(buf);
2283 let len = io::buf_length(buf);
2284 let s = {
2285 let slice = core::slice::from_raw_parts(content, len as usize);
2286 String::from_utf8_lossy(slice).to_string()
2287 };
2288 assert!(
2289 s.contains(">"),
2290 "> should be escaped as >, got: {}",
2291 s
2292 );
2293
2294 io::buf_free(buf);
2295 }
2296 }
2297
2298 #[test]
2299 fn test_c14n_escape_text_cdata_end() {
2300 unsafe {
2301 let buf = io::buf_create(-1);
2302 assert!(!buf.is_null());
2303
2304 let text = b"a]]>b\0" as *const u8 as *const xmlChar;
2305 c14n_escape_text(buf, text, 5);
2306
2307 let content = io::buf_content(buf);
2308 let len = io::buf_length(buf);
2309 let s = {
2310 let slice = core::slice::from_raw_parts(content, len as usize);
2311 String::from_utf8_lossy(slice).to_string()
2312 };
2313 assert!(
2315 s.contains("]]>"),
2316 "]]> should be escaped as ]]>, got: {}",
2317 s
2318 );
2319
2320 io::buf_free(buf);
2321 }
2322 }
2323
2324 #[test]
2325 fn test_c14n_execute_callback() {
2326 unsafe {
2327 let doc = create_simple_doc();
2328
2329 let output_vec = Box::into_raw(Box::new(Vec::<u8>::new()));
2331
2332 unsafe extern "C" fn test_callback(
2333 ctx: *mut c_void,
2334 data: *const c_char,
2335 len: c_int,
2336 ) -> c_int {
2337 let slice = unsafe { core::slice::from_raw_parts(data as *const u8, len as usize) };
2338 let output = unsafe { &mut *(ctx as *mut Vec<u8>) };
2339 output.extend_from_slice(slice);
2340 len
2341 }
2342
2343 let ret = c14n_execute(
2344 doc,
2345 C14nMode::XML_C14N_1_0,
2346 ptr::null(),
2347 0,
2348 Some(
2349 test_callback
2350 as unsafe extern "C" fn(*mut c_void, *const c_char, c_int) -> c_int,
2351 ),
2352 output_vec as *mut c_void,
2353 );
2354
2355 assert!(ret >= 0, "c14n_execute should succeed");
2356 let output = Box::from_raw(output_vec);
2357 let output_str = String::from_utf8_lossy(&output);
2358 assert!(
2359 output_str.contains("<root>"),
2360 "Callback output should contain <root>, got: {}",
2361 output_str
2362 );
2363
2364 tree::free_doc(doc);
2365 }
2366 }
2367
2368 #[test]
2369 fn test_c14n_save_to_output_buffer() {
2370 unsafe {
2371 let doc = create_simple_doc();
2372
2373 let buf = io::buf_create(-1);
2375 assert!(!buf.is_null());
2376
2377 let output = io::output_buffer_create_buffer(buf, ptr::null_mut());
2378 assert!(!output.is_null());
2379
2380 let ret = c14n_doc_save_to(
2381 doc,
2382 ptr::null_mut(),
2383 C14nMode::XML_C14N_1_0,
2384 ptr::null(),
2385 0,
2386 output,
2387 );
2388
2389 assert!(ret >= 0, "c14n_doc_save_to should succeed");
2390
2391 let content = io::buf_content(buf);
2392 let len = io::buf_length(buf);
2393 let s = {
2394 let slice = core::slice::from_raw_parts(content, len as usize);
2395 String::from_utf8_lossy(slice).to_string()
2396 };
2397 assert!(
2398 s.contains("<root>"),
2399 "Output buffer should contain <root>, got: {}",
2400 s
2401 );
2402
2403 io::output_buffer_close(output);
2404 io::buf_free(buf);
2405 tree::free_doc(doc);
2406 }
2407 }
2408
2409 #[test]
2410 fn test_c14n_c_abi_doc_dump_memory() {
2411 unsafe {
2412 let doc = create_simple_doc();
2413
2414 let mut result: *mut xmlChar = ptr::null_mut();
2415 let len = xmlC14NDocDumpMemory(
2416 doc,
2417 ptr::null_mut(),
2418 0, ptr::null_mut(),
2420 0, &mut result as *mut *mut xmlChar,
2422 );
2423
2424 assert!(len >= 0, "xmlC14NDocDumpMemory should succeed");
2425 assert!(!result.is_null());
2426
2427 let s = {
2428 let slice = core::slice::from_raw_parts(result, len as usize);
2429 String::from_utf8_lossy(slice).to_string()
2430 };
2431 assert!(
2432 s.contains("<root>"),
2433 "C ABI export should produce canonical output, got: {}",
2434 s
2435 );
2436
2437 xmlFreeImpl(result as *mut c_void);
2438 tree::free_doc(doc);
2439 }
2440 }
2441
2442 #[test]
2443 fn test_c14n_c_abi_execute() {
2444 unsafe {
2445 let doc = create_simple_doc();
2446
2447 let output_vec = Box::into_raw(Box::new(Vec::<u8>::new()));
2448
2449 unsafe extern "C" fn test_callback(
2450 ctx: *mut c_void,
2451 data: *const c_char,
2452 len: c_int,
2453 ) -> c_int {
2454 let slice = unsafe { core::slice::from_raw_parts(data as *const u8, len as usize) };
2455 let output = unsafe { &mut *(ctx as *mut Vec<u8>) };
2456 output.extend_from_slice(slice);
2457 len
2458 }
2459
2460 let ret = xmlC14NExecute(
2461 doc,
2462 0, ptr::null_mut(),
2464 0,
2465 Some(
2466 test_callback
2467 as unsafe extern "C" fn(*mut c_void, *const c_char, c_int) -> c_int,
2468 ),
2469 output_vec as *mut c_void,
2470 );
2471
2472 assert!(ret >= 0, "xmlC14NExecute should succeed");
2473 let output = Box::from_raw(output_vec);
2474 let output_str = String::from_utf8_lossy(&output);
2475 assert!(
2476 output_str.contains("<root>"),
2477 "C ABI execute should produce canonical output, got: {}",
2478 output_str
2479 );
2480
2481 tree::free_doc(doc);
2482 }
2483 }
2484
2485 #[test]
2486 fn test_c14n_c_abi_save_to() {
2487 unsafe {
2488 let doc = create_simple_doc();
2489
2490 let buf = io::buf_create(-1);
2491 assert!(!buf.is_null());
2492
2493 let output = io::output_buffer_create_buffer(buf, ptr::null_mut());
2494 assert!(!output.is_null());
2495
2496 let ret = xmlC14NDocSaveTo(
2497 doc,
2498 ptr::null_mut(),
2499 0, ptr::null_mut(),
2501 0,
2502 output,
2503 );
2504
2505 assert!(ret >= 0, "xmlC14NDocSaveTo should succeed");
2506
2507 let content = io::buf_content(buf);
2508 let len = io::buf_length(buf);
2509 let s = {
2510 let slice = core::slice::from_raw_parts(content, len as usize);
2511 String::from_utf8_lossy(slice).to_string()
2512 };
2513 assert!(
2514 s.contains("<root>"),
2515 "C ABI save_to should produce canonical output, got: {}",
2516 s
2517 );
2518
2519 io::output_buffer_close(output);
2520 io::buf_free(buf);
2521 tree::free_doc(doc);
2522 }
2523 }
2524}