1#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
21
22use core::ffi::c_void;
23use std::ffi::CStr;
24use std::fs;
25use std::os::raw::{c_char, c_int};
26use std::path::Path;
27use std::ptr;
28
29use once_cell::sync::Lazy;
30use parking_lot::RwLock;
31
32use crate::abi::allocator::xmlFreeImpl;
33use crate::abi::structs::{_xmlDoc, _xmlNode};
34use crate::abi::types::xmlChar;
35use crate::xml::string::{bytes_to_xmlstr, xmlstr_to_bytes};
36
37pub(crate) const XML_CATA_ALLOW_NONE: i32 = 0;
43
44pub(crate) const XML_CATA_ALLOW_GLOBAL: i32 = 1;
46
47pub(crate) const XML_CATA_ALLOW_DOCUMENT: i32 = 2;
49
50pub(crate) const XML_CATA_ALLOW_ALL: i32 = 3;
52
53const DEFAULT_CATALOG: &str = "/etc/xml/catalog";
55
56const XML_CATALOG_FILES_ENV: &str = "XML_CATALOG_FILES";
58
59const SGML_CATALOG_FILES_ENV: &str = "SGML_CATALOG_FILES";
61
62const MAX_CATALOG_FILE_SIZE: usize = 10_485_760;
64
65#[derive(Clone, Debug)]
71pub enum CatalogEntry {
72 Public {
74 public_id: Vec<u8>,
76 uri: Vec<u8>,
78 },
79 System {
81 system_id: Vec<u8>,
83 uri: Vec<u8>,
85 },
86 RewriteSystem {
88 prefix: Vec<u8>,
90 rewrite: Vec<u8>,
92 },
93 RewriteURI {
95 prefix: Vec<u8>,
97 rewrite: Vec<u8>,
99 },
100 DelegatePublic {
102 prefix: Vec<u8>,
104 catalog: Vec<u8>,
106 },
107 DelegateSystem {
109 prefix: Vec<u8>,
111 catalog: Vec<u8>,
113 },
114 DelegateURI {
116 prefix: Vec<u8>,
118 catalog: Vec<u8>,
120 },
121 NextCatalog {
123 catalog: Vec<u8>,
125 },
126}
127
128#[derive(Clone, Copy, Debug, PartialEq)]
130enum CatalogFormat {
131 Xml,
132 Sgml,
133}
134
135#[allow(dead_code)]
137#[derive(Clone, Debug)]
138struct CatalogInfo {
139 path: Vec<u8>,
140 format: CatalogFormat,
141}
142
143struct CatalogState {
149 entries: Vec<CatalogEntry>,
151 catalogs: Vec<CatalogInfo>,
153 initialized: bool,
155 allow: i32,
157}
158
159impl CatalogState {
160 const fn new() -> Self {
161 Self {
162 entries: Vec::new(),
163 catalogs: Vec::new(),
164 initialized: false,
165 allow: XML_CATA_ALLOW_ALL,
166 }
167 }
168
169 fn clear(&mut self) {
171 self.entries.clear();
172 self.catalogs.clear();
173 self.allow = XML_CATA_ALLOW_ALL;
174 }
175}
176
177static CATALOG_STATE: Lazy<RwLock<CatalogState>> = Lazy::new(|| RwLock::new(CatalogState::new()));
179
180fn trim_whitespace(bytes: &[u8]) -> &[u8] {
186 let start = bytes
187 .iter()
188 .position(|b| !b.is_ascii_whitespace())
189 .unwrap_or(bytes.len());
190 let end = bytes
191 .iter()
192 .rposition(|b| !b.is_ascii_whitespace())
193 .map_or(0, |p| p + 1);
194 &bytes[start..end]
195}
196
197#[allow(dead_code)]
199fn starts_with(data: &[u8], prefix: &[u8]) -> bool {
200 if data.len() < prefix.len() {
201 return false;
202 }
203 data[..prefix.len()] == prefix[..]
204}
205
206#[allow(dead_code)]
208fn starts_with_ignore_ascii_case(data: &[u8], prefix: &[u8]) -> bool {
209 if data.len() < prefix.len() {
210 return false;
211 }
212 data[..prefix.len()]
213 .iter()
214 .zip(prefix.iter())
215 .all(|(a, b)| a.eq_ignore_ascii_case(b))
216}
217
218fn extract_attr_value<'a>(data: &'a [u8], name: &[u8], pos: usize) -> Option<(&'a [u8], usize)> {
223 let remaining = &data[pos..];
224 let name_pos = find_subsequence(remaining, name)?;
226 let after_name = name_pos + name.len();
227 let after_name_slice = &remaining[after_name..];
228
229 let eq_pos = after_name_slice.iter().position(|b| *b == b'=')?;
231
232 let rel_quote_start = after_name_slice[eq_pos + 1..]
234 .iter()
235 .position(|b| *b == b'"' || *b == b'\'')
236 .map(|p| after_name + eq_pos + 1 + p)?;
237 let abs_quote_start = pos + rel_quote_start;
238 let quote_char = data[abs_quote_start];
239 let value_start = abs_quote_start + 1;
241 let value_end = data[value_start..]
242 .iter()
243 .position(|b| *b == quote_char)
244 .map(|p| value_start + p)?;
245
246 Some((&data[value_start..value_end], value_end + 1))
247}
248
249fn find_subsequence(data: &[u8], seq: &[u8]) -> Option<usize> {
251 if seq.is_empty() {
252 return Some(0);
253 }
254 data.windows(seq.len()).position(|w| w == seq)
255}
256
257fn extract_token(line: &[u8], pos: usize) -> Option<(&[u8], usize)> {
259 let line = &line[pos..];
260 let start = line.iter().position(|b| !b.is_ascii_whitespace())?;
261 let end = line[start..]
262 .iter()
263 .position(|b| b.is_ascii_whitespace())
264 .map(|p| start + p)
265 .unwrap_or(line.len());
266 Some((&line[start..end], pos + end))
267}
268
269fn extract_quoted_token(line: &[u8], pos: usize) -> Option<(&[u8], usize)> {
271 let line = &line[pos..];
272 let start = line.iter().position(|b| !b.is_ascii_whitespace())?;
273 if start >= line.len() {
274 return None;
275 }
276 let quote_char = line[start];
277 if quote_char != b'"' && quote_char != b'\'' {
278 return extract_token(line, 0);
280 }
281 let value_start = start + 1;
282 let end = line[value_start..]
283 .iter()
284 .position(|b| *b == quote_char)
285 .map(|p| value_start + p)?;
286 Some((&line[value_start..end], pos + end + 1))
287}
288
289fn parse_sgml_line(line: &[u8], entries: &mut Vec<CatalogEntry>) {
308 let trimmed = trim_whitespace(line);
309 if trimmed.is_empty() || trimmed.starts_with(b"--") {
310 return;
311 }
312
313 let Some((directive, after_directive)) = extract_token(trimmed, 0) else {
315 return;
316 };
317
318 match directive {
319 b"PUBLIC" | b"public" => {
320 let Some((pub_id, after_pub)) = extract_quoted_token(trimmed, after_directive) else {
321 return;
322 };
323 let Some((uri, _)) = extract_quoted_token(trimmed, after_pub) else {
324 return;
325 };
326 entries.push(CatalogEntry::Public {
327 public_id: pub_id.to_vec(),
328 uri: uri.to_vec(),
329 });
330 }
331 b"SYSTEM" | b"system" => {
332 let Some((sys_id, after_sys)) = extract_quoted_token(trimmed, after_directive) else {
333 return;
334 };
335 let Some((uri, _)) = extract_quoted_token(trimmed, after_sys) else {
336 return;
337 };
338 entries.push(CatalogEntry::System {
339 system_id: sys_id.to_vec(),
340 uri: uri.to_vec(),
341 });
342 }
343 b"URI" | b"uri" => {
344 let Some((uri_id, after_uri)) = extract_quoted_token(trimmed, after_directive) else {
346 return;
347 };
348 let Some((replacement, _)) = extract_quoted_token(trimmed, after_uri) else {
349 return;
350 };
351 entries.push(CatalogEntry::System {
352 system_id: uri_id.to_vec(),
353 uri: replacement.to_vec(),
354 });
355 }
356 b"CATALOG" | b"catalog" => {
357 let Some((path, _)) = extract_quoted_token(trimmed, after_directive) else {
358 return;
359 };
360 entries.push(CatalogEntry::NextCatalog {
361 catalog: path.to_vec(),
362 });
363 }
364 _ => {
365 }
367 }
368}
369
370fn parse_sgml_catalog(data: &[u8], entries: &mut Vec<CatalogEntry>) {
372 for line in data.split(|b| *b == b'\n') {
373 parse_sgml_line(line, entries);
374 }
375}
376
377fn parse_xml_catalog(data: &[u8], entries: &mut Vec<CatalogEntry>) {
386 let mut pos = 0;
387 let len = data.len();
388
389 while pos < len {
390 let Some(lt_pos) = data[pos..].iter().position(|b| *b == b'<') else {
392 break;
393 };
394 let tag_start = pos + lt_pos;
395
396 if tag_start + 1 >= len {
398 break;
399 }
400
401 let is_closing = data[tag_start + 1] == b'/';
402 if is_closing {
403 let Some(gt_pos) = data[tag_start..].iter().position(|b| *b == b'>') else {
405 break;
406 };
407 pos = tag_start + gt_pos + 1;
408 continue;
409 }
410
411 if data[tag_start + 1] == b'!' || data[tag_start + 1] == b'?' {
413 let Some(gt_pos) = data[tag_start..].iter().position(|b| *b == b'>') else {
414 break;
415 };
416 pos = tag_start + gt_pos + 1;
417 continue;
418 }
419
420 let tag_name_start = tag_start + 1;
422 let tag_name_end = data[tag_name_start..]
423 .iter()
424 .position(|b| b.is_ascii_whitespace() || *b == b'>' || *b == b'/')
425 .map(|p| tag_name_start + p)
426 .unwrap_or(len);
427
428 let tag_name = &data[tag_name_start..tag_name_end];
429
430 let Some(gt_or_slash_pos) = data[tag_start..]
432 .iter()
433 .position(|b| *b == b'>')
434 .map(|p| tag_start + p)
435 else {
436 break;
437 };
438
439 let is_self_closing = gt_or_slash_pos > 0 && data[gt_or_slash_pos - 1] == b'/';
440 let tag_content_end = if is_self_closing {
441 gt_or_slash_pos + 1
442 } else {
443 let close_tag = {
445 let mut close = Vec::with_capacity(tag_name.len() + 3);
446 close.push(b'<');
447 close.push(b'/');
448 close.extend_from_slice(tag_name);
449 close.push(b'>');
450 close
451 };
452 let close_pos = data[gt_or_slash_pos + 1..]
453 .windows(close_tag.len())
454 .position(|w| w == close_tag.as_slice())
455 .map(|p| gt_or_slash_pos + 1 + p + close_tag.len());
456
457 match close_pos {
458 Some(p) => p,
459 None => {
460 pos = gt_or_slash_pos + 1;
461 continue;
462 }
463 }
464 };
465
466 let tag_body_start = gt_or_slash_pos + 1;
467 let tag_body = &data[tag_body_start
468 ..tag_content_end
469 - if is_self_closing {
470 0
471 } else {
472 tag_name.len() + 3
473 }];
474 let tag_body = trim_whitespace(tag_body);
475
476 match tag_name {
477 b"public" => {
478 let Some((pub_id, _)) = extract_attr_value(data, b"publicId", tag_start) else {
479 pos = tag_content_end;
480 continue;
481 };
482 let Some((uri, _)) = extract_attr_value(data, b"uri", tag_start) else {
483 pos = tag_content_end;
484 continue;
485 };
486 entries.push(CatalogEntry::Public {
487 public_id: pub_id.to_vec(),
488 uri: uri.to_vec(),
489 });
490 }
491 b"system" => {
492 let Some((sys_id, _)) = extract_attr_value(data, b"systemId", tag_start) else {
493 pos = tag_content_end;
494 continue;
495 };
496 let Some((uri, _)) = extract_attr_value(data, b"uri", tag_start) else {
497 pos = tag_content_end;
498 continue;
499 };
500 entries.push(CatalogEntry::System {
501 system_id: sys_id.to_vec(),
502 uri: uri.to_vec(),
503 });
504 }
505 b"rewriteSystem" => {
506 let Some((prefix, _)) = extract_attr_value(data, b"systemIdStartString", tag_start)
507 else {
508 pos = tag_content_end;
509 continue;
510 };
511 let Some((rewrite, _)) = extract_attr_value(data, b"rewritePrefix", tag_start)
512 else {
513 pos = tag_content_end;
514 continue;
515 };
516 entries.push(CatalogEntry::RewriteSystem {
517 prefix: prefix.to_vec(),
518 rewrite: rewrite.to_vec(),
519 });
520 }
521 b"rewriteURI" => {
522 let Some((prefix, _)) = extract_attr_value(data, b"uriStartString", tag_start)
523 else {
524 pos = tag_content_end;
525 continue;
526 };
527 let Some((rewrite, _)) = extract_attr_value(data, b"rewritePrefix", tag_start)
528 else {
529 pos = tag_content_end;
530 continue;
531 };
532 entries.push(CatalogEntry::RewriteURI {
533 prefix: prefix.to_vec(),
534 rewrite: rewrite.to_vec(),
535 });
536 }
537 b"delegatePublic" => {
538 let Some((prefix, _)) = extract_attr_value(data, b"publicIdStartString", tag_start)
539 else {
540 pos = tag_content_end;
541 continue;
542 };
543 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
544 pos = tag_content_end;
545 continue;
546 };
547 entries.push(CatalogEntry::DelegatePublic {
548 prefix: prefix.to_vec(),
549 catalog: catalog.to_vec(),
550 });
551 }
552 b"delegateSystem" => {
553 let Some((prefix, _)) = extract_attr_value(data, b"systemIdStartString", tag_start)
554 else {
555 pos = tag_content_end;
556 continue;
557 };
558 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
559 pos = tag_content_end;
560 continue;
561 };
562 entries.push(CatalogEntry::DelegateSystem {
563 prefix: prefix.to_vec(),
564 catalog: catalog.to_vec(),
565 });
566 }
567 b"delegateURI" => {
568 let Some((prefix, _)) = extract_attr_value(data, b"uriStartString", tag_start)
569 else {
570 pos = tag_content_end;
571 continue;
572 };
573 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
574 pos = tag_content_end;
575 continue;
576 };
577 entries.push(CatalogEntry::DelegateURI {
578 prefix: prefix.to_vec(),
579 catalog: catalog.to_vec(),
580 });
581 }
582 b"nextCatalog" => {
583 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
584 pos = tag_content_end;
585 continue;
586 };
587 entries.push(CatalogEntry::NextCatalog {
588 catalog: catalog.to_vec(),
589 });
590 }
591 b"group" | b"catalog" => {
592 parse_xml_catalog(tag_body, entries);
594 }
595 _ => {
596 }
598 }
599
600 pos = tag_content_end;
601 }
602}
603
604fn read_file_bytes(path: &str) -> Option<Vec<u8>> {
610 let p = Path::new(path);
611 let metadata = fs::metadata(p).ok()?;
613 if metadata.len() > MAX_CATALOG_FILE_SIZE as u64 {
614 return None;
615 }
616 fs::read(p).ok()
617}
618
619fn detect_catalog_format(data: &[u8]) -> CatalogFormat {
621 let trimmed = trim_whitespace(data);
622 if trimmed.starts_with(b"<?xml") || trimmed.starts_with(b"<catalog") {
623 CatalogFormat::Xml
624 } else {
625 CatalogFormat::Sgml
626 }
627}
628
629fn load_catalog_data(_path: &str, data: &[u8], entries: &mut Vec<CatalogEntry>) {
631 let format = detect_catalog_format(data);
632 match format {
633 CatalogFormat::Xml => {
634 parse_xml_catalog(data, entries);
635 }
636 CatalogFormat::Sgml => {
637 parse_sgml_catalog(data, entries);
638 }
639 }
640}
641
642fn load_single_catalog(path: &str, state: &mut CatalogState) {
644 let data = match read_file_bytes(path) {
645 Some(d) => d,
646 None => return,
647 };
648
649 let format = detect_catalog_format(&data);
650 state.catalogs.push(CatalogInfo {
651 path: path.as_bytes().to_vec(),
652 format,
653 });
654
655 load_catalog_data(path, &data, &mut state.entries);
656}
657
658fn load_catalog_list(catalogs: &str, state: &mut CatalogState) {
660 for catalog_path in catalogs.split(':') {
661 let trimmed = catalog_path.trim();
662 if !trimmed.is_empty() {
663 load_single_catalog(trimmed, state);
664 }
665 }
666}
667
668pub(crate) fn init() {
677 let mut state = CATALOG_STATE.write();
678 if state.initialized {
679 return;
680 }
681
682 state.allow = XML_CATA_ALLOW_ALL;
684 crate::xml::globals::set_catalog_defaults(XML_CATA_ALLOW_ALL);
685
686 if let Ok(catalogs) = std::env::var(XML_CATALOG_FILES_ENV) {
688 load_catalog_list(&catalogs, &mut state);
689 }
690
691 if let Ok(catalogs) = std::env::var(SGML_CATALOG_FILES_ENV) {
693 load_catalog_list(&catalogs, &mut state);
694 }
695
696 if Path::new(DEFAULT_CATALOG).exists() {
698 load_single_catalog(DEFAULT_CATALOG, &mut state);
699 }
700
701 state.initialized = true;
702}
703
704pub(crate) fn cleanup() {
708 let mut state = CATALOG_STATE.write();
709 state.clear();
710 state.initialized = false;
711}
712
713pub(crate) fn load_catalog(catalogs: *const c_char) -> *mut c_void {
727 if catalogs.is_null() {
728 return ptr::null_mut();
729 }
730
731 let catalogs_str = unsafe { CStr::from_ptr(catalogs) };
732 let catalogs_str = catalogs_str.to_str().unwrap_or("");
733
734 let mut state = CATALOG_STATE.write();
735
736 if !state.initialized {
738 drop(state);
739 init();
740 state = CATALOG_STATE.write();
741 }
742
743 let count_before = state.catalogs.len();
744 load_catalog_list(catalogs_str, &mut state);
745
746 if state.catalogs.len() > count_before {
747 (state.catalogs.len() as isize) as *mut c_void
749 } else {
750 ptr::null_mut()
751 }
752}
753
754const fn catalog_allowed(state: &CatalogState) -> bool {
760 let allow = state.allow;
761 match allow {
762 XML_CATA_ALLOW_NONE => false,
763 XML_CATA_ALLOW_GLOBAL | XML_CATA_ALLOW_DOCUMENT | XML_CATA_ALLOW_ALL => true,
764 _ => false,
765 }
766}
767
768unsafe fn resolve_public_entries(entries: &[CatalogEntry], pub_id_bytes: &[u8]) -> Option<Vec<u8>> {
771 for entry in entries {
773 if let CatalogEntry::Public { public_id, uri } = entry {
774 if public_id.as_slice() == pub_id_bytes {
775 return Some(uri.clone());
776 }
777 }
778 }
779
780 let mut best_match: Option<Vec<u8>> = None;
782 let mut best_prefix_len: usize = 0;
783
784 for entry in entries {
785 if let CatalogEntry::DelegatePublic { prefix, catalog } = entry {
786 if pub_id_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
787 best_prefix_len = prefix.len();
788 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
790 let mut temp_entries = Vec::new();
791 parse_xml_catalog(&delegated_data, &mut temp_entries);
792 for temp_entry in &temp_entries {
794 if let CatalogEntry::Public { public_id: dp, uri } = temp_entry {
795 if dp.as_slice() == pub_id_bytes {
796 best_match = Some(uri.clone());
797 }
798 }
799 }
800 }
801 }
802 }
803 }
804
805 best_match
806}
807
808pub(crate) unsafe fn resolve_public(pub_id: *const xmlChar) -> *mut xmlChar {
819 if pub_id.is_null() {
820 return ptr::null_mut();
821 }
822
823 let state = CATALOG_STATE.read();
824 if !catalog_allowed(&state) {
825 return ptr::null_mut();
826 }
827
828 let pub_id_bytes = xmlstr_to_bytes(pub_id);
829 unsafe { resolve_public_entries(&state.entries, pub_id_bytes) }
830 .as_ref()
831 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
832}
833
834unsafe fn resolve_system_entries(entries: &[CatalogEntry], sys_id_bytes: &[u8]) -> Option<Vec<u8>> {
836 for entry in entries {
838 if let CatalogEntry::System { system_id, uri } = entry {
839 if system_id.as_slice() == sys_id_bytes {
840 return Some(uri.clone());
841 }
842 }
843 }
844
845 let mut best_rewrite: Option<Vec<u8>> = None;
847 let mut best_prefix_len: usize = 0;
848
849 for entry in entries {
850 if let CatalogEntry::RewriteSystem { prefix, rewrite } = entry {
851 if sys_id_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
852 best_prefix_len = prefix.len();
853 let suffix = &sys_id_bytes[prefix.len()..];
855 let mut result = rewrite.clone();
856 result.extend_from_slice(suffix);
857 best_rewrite = Some(result);
858 }
859 }
860 }
861
862 if let Some(rewritten) = best_rewrite {
863 return Some(rewritten);
864 }
865
866 for entry in entries {
868 if let CatalogEntry::DelegateSystem { prefix, catalog } = entry {
869 if sys_id_bytes.starts_with(prefix) {
870 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
871 let mut temp_entries = Vec::new();
872 parse_xml_catalog(&delegated_data, &mut temp_entries);
873 for temp_entry in &temp_entries {
874 if let CatalogEntry::System { system_id, uri } = temp_entry {
875 if system_id.as_slice() == sys_id_bytes {
876 return Some(uri.clone());
877 }
878 }
879 }
880 }
881 }
882 }
883 }
884
885 None
886}
887
888pub(crate) unsafe fn resolve_system(sys_id: *const xmlChar) -> *mut xmlChar {
901 if sys_id.is_null() {
902 return ptr::null_mut();
903 }
904
905 let state = CATALOG_STATE.read();
906 if !catalog_allowed(&state) {
907 return ptr::null_mut();
908 }
909
910 let sys_id_bytes = xmlstr_to_bytes(sys_id);
911 unsafe { resolve_system_entries(&state.entries, sys_id_bytes) }
912 .as_ref()
913 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
914}
915
916unsafe fn resolve_uri_entries(entries: &[CatalogEntry], uri_bytes: &[u8]) -> Option<Vec<u8>> {
918 for entry in entries {
920 if let CatalogEntry::System {
921 system_id,
922 uri: sys_uri,
923 } = entry
924 {
925 if system_id.as_slice() == uri_bytes {
926 return Some(sys_uri.clone());
927 }
928 }
929 }
930
931 let mut best_rewrite: Option<Vec<u8>> = None;
933 let mut best_prefix_len: usize = 0;
934
935 for entry in entries {
936 if let CatalogEntry::RewriteURI { prefix, rewrite } = entry {
937 if uri_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
938 best_prefix_len = prefix.len();
939 let suffix = &uri_bytes[prefix.len()..];
940 let mut result = rewrite.clone();
941 result.extend_from_slice(suffix);
942 best_rewrite = Some(result);
943 }
944 }
945 }
946
947 if let Some(rewritten) = best_rewrite {
948 return Some(rewritten);
949 }
950
951 for entry in entries {
953 if let CatalogEntry::DelegateURI { prefix, catalog } = entry {
954 if uri_bytes.starts_with(prefix) {
955 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
956 let mut temp_entries = Vec::new();
957 parse_xml_catalog(&delegated_data, &mut temp_entries);
958 for temp_entry in &temp_entries {
959 if let CatalogEntry::System {
960 system_id,
961 uri: sys_uri,
962 } = temp_entry
963 {
964 if system_id.as_slice() == uri_bytes {
965 return Some(sys_uri.clone());
966 }
967 }
968 }
969 }
970 }
971 }
972 }
973
974 None
975}
976
977pub(crate) unsafe fn resolve_uri(uri: *const xmlChar) -> *mut xmlChar {
990 if uri.is_null() {
991 return ptr::null_mut();
992 }
993
994 let state = CATALOG_STATE.read();
995 if !catalog_allowed(&state) {
996 return ptr::null_mut();
997 }
998
999 let uri_bytes = xmlstr_to_bytes(uri);
1000 unsafe { resolve_uri_entries(&state.entries, uri_bytes) }
1001 .as_ref()
1002 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
1003}
1004
1005pub(crate) fn set_defaults(allow: c_int) {
1020 let mut state = CATALOG_STATE.write();
1021 state.allow = allow;
1022 crate::xml::globals::set_catalog_defaults(allow);
1023}
1024
1025pub(crate) fn get_defaults() -> c_int {
1033 let state = CATALOG_STATE.read();
1034 state.allow
1035}
1036
1037pub(crate) unsafe fn add(
1054 type_: *const xmlChar,
1055 orig: *const xmlChar,
1056 replace: *const xmlChar,
1057) -> c_int {
1058 if type_.is_null() || orig.is_null() || replace.is_null() {
1059 return -1;
1060 }
1061
1062 let type_bytes = xmlstr_to_bytes(type_);
1063 let orig_bytes = xmlstr_to_bytes(orig);
1064 let replace_bytes = xmlstr_to_bytes(replace);
1065
1066 let mut state = CATALOG_STATE.write();
1067
1068 match type_bytes {
1069 b"public" => {
1070 state.entries.push(CatalogEntry::Public {
1071 public_id: orig_bytes.to_vec(),
1072 uri: replace_bytes.to_vec(),
1073 });
1074 0
1075 }
1076 b"system" => {
1077 state.entries.push(CatalogEntry::System {
1078 system_id: orig_bytes.to_vec(),
1079 uri: replace_bytes.to_vec(),
1080 });
1081 0
1082 }
1083 b"rewriteSystem" => {
1084 state.entries.push(CatalogEntry::RewriteSystem {
1085 prefix: orig_bytes.to_vec(),
1086 rewrite: replace_bytes.to_vec(),
1087 });
1088 0
1089 }
1090 b"rewriteURI" => {
1091 state.entries.push(CatalogEntry::RewriteURI {
1092 prefix: orig_bytes.to_vec(),
1093 rewrite: replace_bytes.to_vec(),
1094 });
1095 0
1096 }
1097 b"delegatePublic" => {
1098 state.entries.push(CatalogEntry::DelegatePublic {
1099 prefix: orig_bytes.to_vec(),
1100 catalog: replace_bytes.to_vec(),
1101 });
1102 0
1103 }
1104 b"delegateSystem" => {
1105 state.entries.push(CatalogEntry::DelegateSystem {
1106 prefix: orig_bytes.to_vec(),
1107 catalog: replace_bytes.to_vec(),
1108 });
1109 0
1110 }
1111 b"delegateURI" => {
1112 state.entries.push(CatalogEntry::DelegateURI {
1113 prefix: orig_bytes.to_vec(),
1114 catalog: replace_bytes.to_vec(),
1115 });
1116 0
1117 }
1118 b"nextCatalog" => {
1119 state.entries.push(CatalogEntry::NextCatalog {
1120 catalog: orig_bytes.to_vec(),
1121 });
1122 0
1123 }
1124 _ => -1,
1125 }
1126}
1127
1128pub(crate) unsafe fn remove(value: *const xmlChar) -> c_int {
1139 if value.is_null() {
1140 return -1;
1141 }
1142
1143 let value_bytes = xmlstr_to_bytes(value);
1144 let mut state = CATALOG_STATE.write();
1145
1146 let before = state.entries.len();
1147 state.entries.retain(|entry| match entry {
1148 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != value_bytes,
1149 CatalogEntry::System { system_id, .. } => system_id.as_slice() != value_bytes,
1150 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != value_bytes,
1151 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != value_bytes,
1152 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != value_bytes,
1153 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != value_bytes,
1154 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != value_bytes,
1155 CatalogEntry::NextCatalog { catalog } => catalog.as_slice() != value_bytes,
1156 });
1157
1158 (before - state.entries.len()) as c_int
1159}
1160
1161pub(crate) unsafe fn convert() -> *mut _xmlDoc {
1176 let state = CATALOG_STATE.read();
1177
1178 if state.entries.is_empty() {
1179 return ptr::null_mut();
1180 }
1181
1182 let doc = crate::xml::tree::new_doc(ptr::null_mut());
1184 if doc.is_null() {
1185 return ptr::null_mut();
1186 }
1187
1188 let catalog_name = b"catalog\0" as *const u8 as *const xmlChar;
1190 let root = crate::xml::tree::new_node(ptr::null_mut(), catalog_name);
1191 if root.is_null() {
1192 crate::xml::tree::free_doc(doc);
1193 return ptr::null_mut();
1194 }
1195
1196 let xmlns_name = b"xmlns\0" as *const u8 as *const xmlChar;
1198 let ns_value = b"urn:oasis:names:tc:entity:xmlns:xml:catalog\0" as *const u8 as *const xmlChar;
1199 crate::xml::tree::set_prop(root, xmlns_name, ns_value);
1200
1201 crate::xml::tree::doc_set_root_element(doc, root);
1202
1203 for entry in &state.entries {
1205 let (elem_name, attr1_name, attr1_value, attr2_name, attr2_value) = match entry {
1206 CatalogEntry::Public { public_id, uri } => {
1207 let elem = b"public\0" as *const u8 as *mut xmlChar;
1208 let attr1 = b"publicId\0" as *const u8 as *mut xmlChar;
1209 let val1 = bytes_to_xmlstr(public_id);
1210 let attr2 = b"uri\0" as *const u8 as *mut xmlChar;
1211 let val2 = bytes_to_xmlstr(uri);
1212 (elem, attr1, val1, attr2, val2)
1213 }
1214 CatalogEntry::System { system_id, uri } => {
1215 let elem = b"system\0" as *const u8 as *mut xmlChar;
1216 let attr1 = b"systemId\0" as *const u8 as *mut xmlChar;
1217 let val1 = bytes_to_xmlstr(system_id);
1218 let attr2 = b"uri\0" as *const u8 as *mut xmlChar;
1219 let val2 = bytes_to_xmlstr(uri);
1220 (elem, attr1, val1, attr2, val2)
1221 }
1222 CatalogEntry::RewriteSystem { prefix, rewrite } => {
1223 let elem = b"rewriteSystem\0" as *const u8 as *mut xmlChar;
1224 let attr1 = b"systemIdStartString\0" as *const u8 as *mut xmlChar;
1225 let val1 = bytes_to_xmlstr(prefix);
1226 let attr2 = b"rewritePrefix\0" as *const u8 as *mut xmlChar;
1227 let val2 = bytes_to_xmlstr(rewrite);
1228 (elem, attr1, val1, attr2, val2)
1229 }
1230 CatalogEntry::RewriteURI { prefix, rewrite } => {
1231 let elem = b"rewriteURI\0" as *const u8 as *mut xmlChar;
1232 let attr1 = b"uriStartString\0" as *const u8 as *mut xmlChar;
1233 let val1 = bytes_to_xmlstr(prefix);
1234 let attr2 = b"rewritePrefix\0" as *const u8 as *mut xmlChar;
1235 let val2 = bytes_to_xmlstr(rewrite);
1236 (elem, attr1, val1, attr2, val2)
1237 }
1238 CatalogEntry::DelegatePublic { prefix, catalog } => {
1239 let elem = b"delegatePublic\0" as *const u8 as *mut xmlChar;
1240 let attr1 = b"publicIdStartString\0" as *const u8 as *mut xmlChar;
1241 let val1 = bytes_to_xmlstr(prefix);
1242 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1243 let val2 = bytes_to_xmlstr(catalog);
1244 (elem, attr1, val1, attr2, val2)
1245 }
1246 CatalogEntry::DelegateSystem { prefix, catalog } => {
1247 let elem = b"delegateSystem\0" as *const u8 as *mut xmlChar;
1248 let attr1 = b"systemIdStartString\0" as *const u8 as *mut xmlChar;
1249 let val1 = bytes_to_xmlstr(prefix);
1250 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1251 let val2 = bytes_to_xmlstr(catalog);
1252 (elem, attr1, val1, attr2, val2)
1253 }
1254 CatalogEntry::DelegateURI { prefix, catalog } => {
1255 let elem = b"delegateURI\0" as *const u8 as *mut xmlChar;
1256 let attr1 = b"uriStartString\0" as *const u8 as *mut xmlChar;
1257 let val1 = bytes_to_xmlstr(prefix);
1258 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1259 let val2 = bytes_to_xmlstr(catalog);
1260 (elem, attr1, val1, attr2, val2)
1261 }
1262 CatalogEntry::NextCatalog { catalog } => {
1263 let elem = b"nextCatalog\0" as *const u8 as *mut xmlChar;
1264 let attr1 = b"catalog\0" as *const u8 as *mut xmlChar;
1265 let val1 = bytes_to_xmlstr(catalog);
1266 let attr2 = ptr::null_mut();
1267 let val2 = ptr::null_mut();
1268 (elem, attr1, val1, attr2, val2)
1269 }
1270 };
1271
1272 let child = crate::xml::tree::new_child(root, ptr::null_mut(), elem_name);
1273 if child.is_null() {
1274 if !attr1_value.is_null() {
1276 xmlFreeImpl(attr1_value as *mut c_void);
1277 }
1278 if !attr2_value.is_null() {
1279 xmlFreeImpl(attr2_value as *mut c_void);
1280 }
1281 continue;
1282 }
1283
1284 crate::xml::tree::set_prop(child, attr1_name, attr1_value);
1285 if !attr2_name.is_null() {
1286 crate::xml::tree::set_prop(child, attr2_name, attr2_value);
1287 }
1288
1289 if !attr1_value.is_null() {
1291 xmlFreeImpl(attr1_value as *mut c_void);
1292 }
1293 if !attr2_value.is_null() {
1294 xmlFreeImpl(attr2_value as *mut c_void);
1295 }
1296 }
1297
1298 doc
1299}
1300
1301pub unsafe fn dump_doc() -> *mut _xmlDoc {
1322 let mut doc = convert();
1323 if doc.is_null() {
1324 doc = crate::xml::tree::new_doc(ptr::null_mut());
1326 if doc.is_null() {
1327 return ptr::null_mut();
1328 }
1329 let root =
1330 crate::xml::tree::new_node(ptr::null_mut(), c"catalog".as_ptr() as *const xmlChar);
1331 if root.is_null() {
1332 crate::xml::tree::free_doc(doc);
1333 return ptr::null_mut();
1334 }
1335 crate::xml::tree::set_prop(
1336 root,
1337 c"xmlns".as_ptr() as *const xmlChar,
1338 c"urn:oasis:names:tc:entity:xmlns:xml:catalog".as_ptr() as *const xmlChar,
1339 );
1340 crate::xml::tree::doc_set_root_element(doc, root);
1341 }
1342
1343 let dtd = crate::xml::tree::new_dtd(
1344 doc,
1345 c"catalog".as_ptr() as *const xmlChar,
1346 c"-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN".as_ptr() as *const xmlChar,
1347 c"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd".as_ptr()
1348 as *const xmlChar,
1349 );
1350 if !dtd.is_null() {
1351 (*doc).intSubset = ptr::null_mut();
1352 let dtd_node = dtd as *mut _xmlNode;
1353 let first = (*doc).children;
1354 (*dtd_node).next = first;
1355 (*dtd_node).parent = doc as *mut _xmlNode;
1356 (*dtd_node).doc = doc;
1357 if !first.is_null() {
1358 (*first).prev = dtd_node;
1359 }
1360 (*doc).children = dtd_node;
1361 }
1362 doc
1363}
1364
1365#[derive(Debug)]
1382#[repr(C)]
1383pub struct XmlCatalogHandle {
1384 pub entries: Vec<CatalogEntry>,
1386 pub children: Vec<CatalogEntry>,
1389 pub sgml: c_int,
1392}
1393
1394static CATALOG_DEBUG: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
1396
1397static CATALOG_PREFER: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1);
1400
1401#[no_mangle]
1419pub unsafe extern "C" fn xmlNewCatalog(sgml: c_int) -> *mut XmlCatalogHandle {
1420 let h = Box::new(XmlCatalogHandle {
1421 entries: Vec::new(),
1422 children: Vec::new(),
1423 sgml,
1424 });
1425 Box::into_raw(h)
1426}
1427
1428#[no_mangle]
1434pub unsafe extern "C" fn xmlFreeCatalog(catal: *mut XmlCatalogHandle) {
1435 if !catal.is_null() {
1436 unsafe { drop(Box::from_raw(catal)) };
1437 }
1438}
1439
1440#[no_mangle]
1446pub unsafe extern "C" fn xmlLoadACatalog(filename: *const c_char) -> *mut XmlCatalogHandle {
1447 if filename.is_null() {
1448 return ptr::null_mut();
1449 }
1450 let name = unsafe { CStr::from_ptr(filename) };
1451 let name = name.to_str().unwrap_or("");
1452 let mut entries = Vec::new();
1453 if let Some(data) = read_file_bytes(name) {
1454 load_catalog_data(name, &data, &mut entries);
1455 }
1456 if entries.is_empty() {
1457 return ptr::null_mut();
1458 }
1459 Box::into_raw(Box::new(XmlCatalogHandle {
1460 entries,
1461 children: Vec::new(),
1462 sgml: 0,
1463 }))
1464}
1465
1466#[no_mangle]
1473pub unsafe extern "C" fn xmlLoadSGMLSuperCatalog(filename: *const c_char) -> *mut XmlCatalogHandle {
1474 unsafe { xmlLoadACatalog(filename) }
1475}
1476
1477#[no_mangle]
1485pub unsafe extern "C" fn xmlConvertSGMLCatalog(catal: *mut XmlCatalogHandle) -> c_int {
1486 if catal.is_null() {
1487 return -1;
1488 }
1489 unsafe { (*catal).sgml = 0 };
1490 0
1491}
1492
1493#[no_mangle]
1502pub unsafe extern "C" fn xmlACatalogAdd(
1503 catal: *mut XmlCatalogHandle,
1504 type_: *const xmlChar,
1505 orig: *const xmlChar,
1506 replace: *const xmlChar,
1507) -> c_int {
1508 if catal.is_null() || type_.is_null() || orig.is_null() || replace.is_null() {
1509 return -1;
1510 }
1511 if unsafe { (*catal).entries.is_empty() } {
1517 return -1;
1518 }
1519 let t = xmlstr_to_bytes(type_);
1520 let o = xmlstr_to_bytes(orig).to_vec();
1521 let r = xmlstr_to_bytes(replace).to_vec();
1522 let entry = if t == b"public" {
1523 CatalogEntry::Public {
1524 public_id: o,
1525 uri: r,
1526 }
1527 } else if t == b"system" {
1528 CatalogEntry::System {
1529 system_id: o,
1530 uri: r,
1531 }
1532 } else if t == b"rewriteSystem" {
1533 CatalogEntry::RewriteSystem {
1534 prefix: o,
1535 rewrite: r,
1536 }
1537 } else if t == b"rewriteURI" {
1538 CatalogEntry::RewriteURI {
1539 prefix: o,
1540 rewrite: r,
1541 }
1542 } else if t == b"delegatePublic" {
1543 CatalogEntry::DelegatePublic {
1544 prefix: o,
1545 catalog: r,
1546 }
1547 } else if t == b"delegateSystem" {
1548 CatalogEntry::DelegateSystem {
1549 prefix: o,
1550 catalog: r,
1551 }
1552 } else if t == b"delegateURI" {
1553 CatalogEntry::DelegateURI {
1554 prefix: o,
1555 catalog: r,
1556 }
1557 } else if t == b"nextCatalog" {
1558 CatalogEntry::NextCatalog { catalog: r }
1559 } else {
1560 return -1;
1561 };
1562 unsafe {
1563 (*catal).entries.push(entry.clone());
1564 (*catal).children.push(entry);
1565 };
1566 0
1567}
1568
1569#[no_mangle]
1575pub unsafe extern "C" fn xmlACatalogRemove(
1576 catal: *mut XmlCatalogHandle,
1577 value: *const xmlChar,
1578) -> c_int {
1579 if catal.is_null() || value.is_null() {
1580 return -1;
1581 }
1582 let v = xmlstr_to_bytes(value);
1583 let entries = unsafe { &mut (*catal).entries };
1584 entries.retain(|entry| match entry {
1585 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != v,
1586 CatalogEntry::System { system_id, .. } => system_id.as_slice() != v,
1587 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != v,
1588 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != v,
1589 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != v,
1590 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != v,
1591 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != v,
1592 CatalogEntry::NextCatalog { .. } => true,
1593 });
1594 let children = unsafe { &mut (*catal).children };
1595 children.retain(|entry| match entry {
1596 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != v,
1597 CatalogEntry::System { system_id, .. } => system_id.as_slice() != v,
1598 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != v,
1599 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != v,
1600 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != v,
1601 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != v,
1602 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != v,
1603 CatalogEntry::NextCatalog { .. } => true,
1604 });
1605 0
1611}
1612
1613#[no_mangle]
1625pub unsafe extern "C" fn xmlACatalogResolve(
1626 catal: *mut XmlCatalogHandle,
1627 pubID: *const xmlChar,
1628 sysID: *const xmlChar,
1629) -> *mut xmlChar {
1630 if catal.is_null() {
1631 return ptr::null_mut();
1632 }
1633 let entries = unsafe { &(*catal).entries };
1634 if !sysID.is_null() {
1635 let b = xmlstr_to_bytes(sysID);
1636 if let Some(r) = unsafe { resolve_system_entries(entries, b) } {
1637 return bytes_to_xmlstr(&r);
1638 }
1639 }
1640 if !pubID.is_null() {
1641 let b = xmlstr_to_bytes(pubID);
1642 if let Some(r) = unsafe { resolve_public_entries(entries, b) } {
1643 return bytes_to_xmlstr(&r);
1644 }
1645 }
1646 ptr::null_mut()
1647}
1648
1649#[no_mangle]
1670pub unsafe extern "C" fn xmlACatalogResolveSystem(
1671 catal: *mut XmlCatalogHandle,
1672 sysID: *const xmlChar,
1673) -> *mut xmlChar {
1674 if catal.is_null() || sysID.is_null() {
1675 return ptr::null_mut();
1676 }
1677 let entries = unsafe { &(*catal).entries };
1678 let b = xmlstr_to_bytes(sysID);
1679 unsafe { resolve_system_entries(entries, b) }
1680 .as_ref()
1681 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1682}
1683
1684#[no_mangle]
1705pub unsafe extern "C" fn xmlACatalogResolvePublic(
1706 catal: *mut XmlCatalogHandle,
1707 pubID: *const xmlChar,
1708) -> *mut xmlChar {
1709 if catal.is_null() || pubID.is_null() {
1710 return ptr::null_mut();
1711 }
1712 let entries = unsafe { &(*catal).entries };
1713 let b = xmlstr_to_bytes(pubID);
1714 unsafe { resolve_public_entries(entries, b) }
1715 .as_ref()
1716 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1717}
1718
1719#[no_mangle]
1740pub unsafe extern "C" fn xmlACatalogResolveURI(
1741 catal: *mut XmlCatalogHandle,
1742 URI: *const xmlChar,
1743) -> *mut xmlChar {
1744 if catal.is_null() || URI.is_null() {
1745 return ptr::null_mut();
1746 }
1747 let entries = unsafe { &(*catal).entries };
1748 let b = xmlstr_to_bytes(URI);
1749 unsafe { resolve_uri_entries(entries, b) }
1750 .as_ref()
1751 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1752}
1753
1754#[no_mangle]
1771pub unsafe extern "C" fn xmlCatalogIsEmpty(catal: *mut XmlCatalogHandle) -> c_int {
1772 if catal.is_null() {
1773 return 1;
1774 }
1775 unsafe { (*catal).children.is_empty() as c_int }
1779}
1780
1781#[no_mangle]
1787pub unsafe extern "C" fn xmlACatalogDump(catal: *mut XmlCatalogHandle, out: *mut libc::FILE) {
1788 if catal.is_null() || out.is_null() {
1789 return;
1790 }
1791 let entries = unsafe { &(*catal).entries };
1792 let mut text = String::from("<?xml version=\"1.0\"?>\n<!DOCTYPE catalog PUBLIC \"-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN\" \"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd\">\n<catalog xmlns=\"urn:oasis:names:tc:entity:xmlns:xml:catalog\">\n");
1793 for e in entries {
1794 match e {
1795 CatalogEntry::Public { public_id, uri } => {
1796 text.push_str(&format!(
1797 " <public publicId=\"{}\" uri=\"{}\"/>\n",
1798 String::from_utf8_lossy(public_id),
1799 String::from_utf8_lossy(uri)
1800 ));
1801 }
1802 CatalogEntry::System { system_id, uri } => {
1803 text.push_str(&format!(
1804 " <system systemId=\"{}\" uri=\"{}\"/>\n",
1805 String::from_utf8_lossy(system_id),
1806 String::from_utf8_lossy(uri)
1807 ));
1808 }
1809 CatalogEntry::RewriteSystem { prefix, rewrite } => {
1810 text.push_str(&format!(
1811 " <rewriteSystem systemIdStartString=\"{}\" rewritePrefix=\"{}\"/>\n",
1812 String::from_utf8_lossy(prefix),
1813 String::from_utf8_lossy(rewrite)
1814 ));
1815 }
1816 CatalogEntry::RewriteURI { prefix, rewrite } => {
1817 text.push_str(&format!(
1818 " <rewriteURI uriStartString=\"{}\" rewritePrefix=\"{}\"/>\n",
1819 String::from_utf8_lossy(prefix),
1820 String::from_utf8_lossy(rewrite)
1821 ));
1822 }
1823 _ => {}
1824 }
1825 }
1826 text.push_str("</catalog>\n");
1827 let bytes = text.into_bytes();
1828 unsafe {
1829 libc::fwrite(bytes.as_ptr() as *const libc::c_void, 1, bytes.len(), out);
1830 }
1831}
1832
1833#[no_mangle]
1845pub unsafe extern "C" fn xmlInitializeCatalog() {
1846 crate::xml::catalog::init();
1847}
1848
1849#[no_mangle]
1861pub unsafe extern "C" fn xmlCatalogDumpDoc() -> *mut _xmlDoc {
1862 unsafe { dump_doc() }
1863}
1864
1865#[no_mangle]
1878pub unsafe extern "C" fn xmlCatalogSetDebug(level: c_int) -> c_int {
1879 let old = CATALOG_DEBUG.load(std::sync::atomic::Ordering::Relaxed);
1880 if level <= 0 {
1881 CATALOG_DEBUG.store(0, std::sync::atomic::Ordering::Relaxed);
1882 } else {
1883 CATALOG_DEBUG.store(level, std::sync::atomic::Ordering::Relaxed);
1884 }
1885 old
1886}
1887
1888#[no_mangle]
1901pub unsafe extern "C" fn xmlCatalogSetDefaultPrefer(prefer: c_int) -> c_int {
1902 let old = CATALOG_PREFER.load(std::sync::atomic::Ordering::Relaxed);
1903 if prefer == 0 {
1904 return old;
1905 }
1906 CATALOG_PREFER.store(prefer, std::sync::atomic::Ordering::Relaxed);
1907 old
1908}
1909
1910#[no_mangle]
1932pub unsafe extern "C" fn xmlCatalogResolve(
1933 pubID: *const xmlChar,
1934 sysID: *const xmlChar,
1935) -> *mut xmlChar {
1936 if !sysID.is_null() {
1937 let r = unsafe { resolve_system(sysID) };
1938 if !r.is_null() {
1939 return r;
1940 }
1941 }
1942 if !pubID.is_null() {
1943 return unsafe { resolve_public(pubID) };
1944 }
1945 ptr::null_mut()
1946}
1947
1948#[no_mangle]
1966pub unsafe extern "C" fn xmlCatalogGetSystem(sysID: *const xmlChar) -> *const xmlChar {
1967 unsafe { resolve_system(sysID) }
1968}
1969#[no_mangle]
1986pub unsafe extern "C" fn xmlCatalogGetPublic(pubID: *const xmlChar) -> *const xmlChar {
1987 unsafe { resolve_public(pubID) }
1988}
1989
1990#[no_mangle]
1996pub unsafe extern "C" fn xmlParseCatalogFile(filename: *const c_char) -> *mut _xmlDoc {
1997 if filename.is_null() {
1998 return ptr::null_mut();
1999 }
2000 unsafe { dump_doc() }
2001}
2002
2003#[no_mangle]
2026pub unsafe extern "C" fn xmlCatalogAddLocal(
2027 catalogs: *mut c_void,
2028 URL: *const xmlChar,
2029) -> *mut c_void {
2030 if URL.is_null() {
2031 return catalogs;
2032 }
2033 let list: *mut Vec<CatalogEntry> = if catalogs.is_null() {
2034 Box::into_raw(Box::new(Vec::<CatalogEntry>::new()))
2035 } else {
2036 catalogs as *mut Vec<CatalogEntry>
2037 };
2038 let url = xmlstr_to_bytes(URL);
2039 let url_str = String::from_utf8_lossy(url).into_owned();
2040 let entries = unsafe { &mut *list };
2041 if let Some(data) = read_file_bytes(&url_str) {
2042 let mut temp = Vec::new();
2043 load_catalog_data(&url_str, &data, &mut temp);
2044 entries.extend(temp);
2045 }
2046 list as *mut c_void
2047}
2048
2049#[no_mangle]
2055pub unsafe extern "C" fn xmlCatalogFreeLocal(catalogs: *mut c_void) {
2056 if !catalogs.is_null() {
2057 unsafe { drop(Box::from_raw(catalogs as *mut Vec<CatalogEntry>)) };
2058 }
2059}
2060
2061#[no_mangle]
2082pub unsafe extern "C" fn xmlCatalogLocalResolve(
2083 catalogs: *mut c_void,
2084 pubID: *const xmlChar,
2085 sysID: *const xmlChar,
2086) -> *mut xmlChar {
2087 if catalogs.is_null() {
2088 return ptr::null_mut();
2089 }
2090 let entries = unsafe { &*(catalogs as *const Vec<CatalogEntry>) };
2091 if !sysID.is_null() {
2093 let b = xmlstr_to_bytes(sysID);
2094 if let Some(r) = unsafe { resolve_system_entries(entries, b) } {
2095 return bytes_to_xmlstr(&r);
2096 }
2097 }
2098 if !pubID.is_null() {
2099 let b = xmlstr_to_bytes(pubID);
2100 if let Some(r) = unsafe { resolve_public_entries(entries, b) } {
2101 return bytes_to_xmlstr(&r);
2102 }
2103 }
2104 ptr::null_mut()
2105}
2106
2107#[no_mangle]
2128pub unsafe extern "C" fn xmlCatalogLocalResolveURI(
2129 catalogs: *mut c_void,
2130 URI: *const xmlChar,
2131) -> *mut xmlChar {
2132 if catalogs.is_null() || URI.is_null() {
2133 return ptr::null_mut();
2134 }
2135 let entries = unsafe { &*(catalogs as *const Vec<CatalogEntry>) };
2136 let b = xmlstr_to_bytes(URI);
2137 unsafe { resolve_uri_entries(entries, b) }
2138 .as_ref()
2139 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
2140}
2141
2142#[cfg(test)]
2147mod tests {
2148 use super::*;
2149 use crate::abi::allocator::xmlFreeImpl;
2150 use crate::xml::string::xmlstr_to_bytes;
2151
2152 use std::sync::Mutex;
2153
2154 static CATALOG_TEST_MUTEX: Mutex<()> = Mutex::new(());
2163
2164 unsafe fn to_xmlstr(s: &[u8]) -> *const xmlChar {
2166 let ptr = bytes_to_xmlstr(s);
2167 ptr as *const xmlChar
2168 }
2169
2170 unsafe fn to_xmlstr_str(s: &str) -> *const xmlChar {
2172 to_xmlstr(s.as_bytes())
2173 }
2174
2175 unsafe fn free_xmlstr(ptr: *const xmlChar) {
2176 if !ptr.is_null() {
2177 xmlFreeImpl(ptr as *mut c_void);
2178 }
2179 }
2180
2181 fn setup() -> std::sync::MutexGuard<'static, ()> {
2188 let guard = CATALOG_TEST_MUTEX.lock().unwrap();
2189 cleanup();
2190 init();
2191 set_defaults(XML_CATA_ALLOW_ALL);
2193 guard
2194 }
2195
2196 fn teardown(_guard: std::sync::MutexGuard<'static, ()>) {
2197 cleanup();
2198 }
2200
2201 #[test]
2204 fn test_resolve_public_basic() {
2205 let _guard = setup();
2206 unsafe {
2207 let type_ = to_xmlstr_str("public");
2209 let pub_id = to_xmlstr_str("-//OASIS//DTD DocBook XML V4.2//EN");
2210 let uri = to_xmlstr_str("http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd");
2211 assert_eq!(add(type_, pub_id, uri), 0);
2212
2213 let result = resolve_public(pub_id);
2215 assert!(!result.is_null());
2216 assert_eq!(
2217 xmlstr_to_bytes(result),
2218 b"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
2219 );
2220 xmlFreeImpl(result as *mut c_void);
2221
2222 let unknown = to_xmlstr_str("-//Unknown//DTD Unknown//EN");
2224 assert!(resolve_public(unknown).is_null());
2225 free_xmlstr(unknown);
2226
2227 free_xmlstr(type_);
2228 free_xmlstr(pub_id);
2229 free_xmlstr(uri);
2230 teardown(_guard);
2231 }
2232 }
2233
2234 #[test]
2237 fn test_resolve_system_basic() {
2238 let _guard = setup();
2239 unsafe {
2240 let type_ = to_xmlstr_str("system");
2241 let sys_id = to_xmlstr_str("http://example.com/foo.dtd");
2242 let uri = to_xmlstr_str("/local/foo.dtd");
2243 assert_eq!(add(type_, sys_id, uri), 0);
2244
2245 let result = resolve_system(sys_id);
2246 assert!(!result.is_null());
2247 assert_eq!(xmlstr_to_bytes(result), b"/local/foo.dtd");
2248 xmlFreeImpl(result as *mut c_void);
2249
2250 free_xmlstr(type_);
2251 free_xmlstr(sys_id);
2252 free_xmlstr(uri);
2253 teardown(_guard);
2254 }
2255 }
2256
2257 #[test]
2260 fn test_resolve_uri_basic() {
2261 let _guard = setup();
2262 unsafe {
2263 let type_ = to_xmlstr_str("system");
2265 let sys_id = to_xmlstr_str("http://example.com/resource.xml");
2266 let uri = to_xmlstr_str("/local/resource.xml");
2267 assert_eq!(add(type_, sys_id, uri), 0);
2268
2269 let result = resolve_uri(sys_id);
2270 assert!(!result.is_null());
2271 assert_eq!(xmlstr_to_bytes(result), b"/local/resource.xml");
2272 xmlFreeImpl(result as *mut c_void);
2273
2274 free_xmlstr(type_);
2275 free_xmlstr(sys_id);
2276 free_xmlstr(uri);
2277 teardown(_guard);
2278 }
2279 }
2280
2281 #[test]
2284 fn test_rewrite_system() {
2285 let _guard = setup();
2286 unsafe {
2287 let type_ = to_xmlstr_str("rewriteSystem");
2288 let prefix = to_xmlstr_str("http://example.com/old/");
2289 let rewrite = to_xmlstr_str("http://mirror.example.com/new/");
2290 assert_eq!(add(type_, prefix, rewrite), 0);
2291
2292 let sys_id = to_xmlstr_str("http://example.com/old/path/file.xml");
2293 let result = resolve_system(sys_id);
2294 assert!(!result.is_null());
2295 assert_eq!(
2296 xmlstr_to_bytes(result),
2297 b"http://mirror.example.com/new/path/file.xml"
2298 );
2299 xmlFreeImpl(result as *mut c_void);
2300
2301 free_xmlstr(type_);
2302 free_xmlstr(prefix);
2303 free_xmlstr(rewrite);
2304 free_xmlstr(sys_id);
2305 teardown(_guard);
2306 }
2307 }
2308
2309 #[test]
2312 fn test_rewrite_uri() {
2313 let _guard = setup();
2314 unsafe {
2315 let type_ = to_xmlstr_str("rewriteURI");
2316 let prefix = to_xmlstr_str("http://example.com/old/");
2317 let rewrite = to_xmlstr_str("http://mirror.example.com/new/");
2318 assert_eq!(add(type_, prefix, rewrite), 0);
2319
2320 let uri = to_xmlstr_str("http://example.com/old/path/file.xml");
2321 let result = resolve_uri(uri);
2322 assert!(!result.is_null());
2323 assert_eq!(
2324 xmlstr_to_bytes(result),
2325 b"http://mirror.example.com/new/path/file.xml"
2326 );
2327 xmlFreeImpl(result as *mut c_void);
2328
2329 free_xmlstr(type_);
2330 free_xmlstr(prefix);
2331 free_xmlstr(rewrite);
2332 free_xmlstr(uri);
2333 teardown(_guard);
2334 }
2335 }
2336
2337 #[test]
2340 fn test_remove_entries() {
2341 let _guard = setup();
2342 unsafe {
2343 let type_ = to_xmlstr_str("public");
2344 let pub_id = to_xmlstr_str("-//TEST//PUBLIC//EN");
2345 let uri = to_xmlstr_str("test.dtd");
2346 assert_eq!(add(type_, pub_id, uri), 0);
2347
2348 assert!(!resolve_public(pub_id).is_null());
2350
2351 assert_eq!(remove(pub_id), 1);
2353
2354 assert!(resolve_public(pub_id).is_null());
2356
2357 free_xmlstr(type_);
2358 free_xmlstr(pub_id);
2359 free_xmlstr(uri);
2360 teardown(_guard);
2361 }
2362 }
2363
2364 #[test]
2367 fn test_catalog_defaults() {
2368 let _guard = setup();
2369
2370 assert_eq!(get_defaults(), XML_CATA_ALLOW_ALL);
2371
2372 set_defaults(XML_CATA_ALLOW_NONE);
2373 assert_eq!(get_defaults(), XML_CATA_ALLOW_NONE);
2374
2375 set_defaults(XML_CATA_ALLOW_GLOBAL);
2376 assert_eq!(get_defaults(), XML_CATA_ALLOW_GLOBAL);
2377
2378 set_defaults(XML_CATA_ALLOW_ALL);
2379 assert_eq!(get_defaults(), XML_CATA_ALLOW_ALL);
2380
2381 teardown(_guard);
2382 }
2383
2384 #[test]
2387 fn test_parse_xml_catalog_in_memory() {
2388 let _guard = setup();
2389 {
2390 let catalog_xml = br#"<?xml version="1.0"?>
2391<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
2392<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
2393 <public publicId="-//OASIS//DTD DocBook XML V4.2//EN" uri="http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"/>
2394 <system systemId="http://example.com/foo.dtd" uri="/local/foo.dtd"/>
2395 <rewriteSystem systemIdStartString="http://example.com/old/" rewritePrefix="http://mirror.example.com/new/"/>
2396 <rewriteURI uriStartString="http://example.com/old/" rewritePrefix="http://mirror.example.com/new/"/>
2397</catalog>"#;
2398
2399 let mut entries = Vec::new();
2401 parse_xml_catalog(catalog_xml, &mut entries);
2402 assert_eq!(entries.len(), 4);
2403
2404 match &entries[0] {
2406 CatalogEntry::Public { public_id, uri } => {
2407 assert_eq!(public_id.as_slice(), b"-//OASIS//DTD DocBook XML V4.2//EN");
2408 assert_eq!(
2409 uri.as_slice(),
2410 b"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
2411 );
2412 }
2413 _ => panic!("Expected Public entry"),
2414 }
2415
2416 match &entries[1] {
2418 CatalogEntry::System { system_id, uri } => {
2419 assert_eq!(system_id.as_slice(), b"http://example.com/foo.dtd");
2420 assert_eq!(uri.as_slice(), b"/local/foo.dtd");
2421 }
2422 _ => panic!("Expected System entry"),
2423 }
2424
2425 match &entries[2] {
2427 CatalogEntry::RewriteSystem { prefix, rewrite } => {
2428 assert_eq!(prefix.as_slice(), b"http://example.com/old/");
2429 assert_eq!(rewrite.as_slice(), b"http://mirror.example.com/new/");
2430 }
2431 _ => panic!("Expected RewriteSystem entry"),
2432 }
2433
2434 match &entries[3] {
2436 CatalogEntry::RewriteURI { prefix, rewrite } => {
2437 assert_eq!(prefix.as_slice(), b"http://example.com/old/");
2438 assert_eq!(rewrite.as_slice(), b"http://mirror.example.com/new/");
2439 }
2440 _ => panic!("Expected RewriteURI entry"),
2441 }
2442
2443 teardown(_guard);
2444 }
2445 }
2446
2447 #[test]
2450 fn test_parse_sgml_catalog() {
2451 let _guard = setup();
2452 {
2453 let sgml_data = br#"-- SGML catalog
2454PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "docbookx.dtd"
2455SYSTEM "http://example.com/foo.dtd" "/local/foo.dtd"
2456URI "http://example.com/resource" "/local/resource"
2457"#;
2458
2459 let mut entries = Vec::new();
2460 parse_sgml_catalog(sgml_data, &mut entries);
2461 assert_eq!(entries.len(), 3);
2462
2463 match &entries[0] {
2465 CatalogEntry::Public { public_id, uri } => {
2466 assert_eq!(public_id.as_slice(), b"-//OASIS//DTD DocBook XML V4.2//EN");
2467 assert_eq!(uri.as_slice(), b"docbookx.dtd");
2468 }
2469 _ => panic!("Expected Public entry"),
2470 }
2471
2472 match &entries[1] {
2474 CatalogEntry::System { system_id, uri } => {
2475 assert_eq!(system_id.as_slice(), b"http://example.com/foo.dtd");
2476 assert_eq!(uri.as_slice(), b"/local/foo.dtd");
2477 }
2478 _ => panic!("Expected System entry"),
2479 }
2480
2481 match &entries[2] {
2483 CatalogEntry::System { system_id, uri } => {
2484 assert_eq!(system_id.as_slice(), b"http://example.com/resource");
2485 assert_eq!(uri.as_slice(), b"/local/resource");
2486 }
2487 _ => panic!("Expected System entry for URI"),
2488 }
2489
2490 teardown(_guard);
2491 }
2492 }
2493
2494 #[test]
2497 fn test_resolution_precedence() {
2498 let _guard = setup();
2499 unsafe {
2500 let type_sys = to_xmlstr_str("system");
2502 let sys_id = to_xmlstr_str("http://example.com/target.xml");
2503 let uri_direct = to_xmlstr_str("/direct/uri.xml");
2504 assert_eq!(add(type_sys, sys_id, uri_direct), 0);
2505
2506 let type_rw = to_xmlstr_str("rewriteSystem");
2508 let prefix = to_xmlstr_str("http://example.com/");
2509 let rewrite = to_xmlstr_str("/rewrite/");
2510 assert_eq!(add(type_rw, prefix, rewrite), 0);
2511
2512 let result = resolve_system(sys_id);
2514 assert!(!result.is_null());
2515 assert_eq!(xmlstr_to_bytes(result), b"/direct/uri.xml");
2516 xmlFreeImpl(result as *mut c_void);
2517
2518 free_xmlstr(type_sys);
2519 free_xmlstr(sys_id);
2520 free_xmlstr(uri_direct);
2521 free_xmlstr(type_rw);
2522 free_xmlstr(prefix);
2523 free_xmlstr(rewrite);
2524 teardown(_guard);
2525 }
2526 }
2527
2528 #[test]
2531 fn test_convert_sgml_to_xml() {
2532 let _guard = setup();
2533 unsafe {
2534 let type_ = to_xmlstr_str("public");
2535 let pub_id = to_xmlstr_str("-//TEST//PUBLIC//EN");
2536 let uri = to_xmlstr_str("test.dtd");
2537 assert_eq!(add(type_, pub_id, uri), 0);
2538
2539 let doc = convert();
2540 assert!(!doc.is_null());
2541
2542 let root = crate::xml::tree::doc_get_root_element(doc);
2544 assert!(!root.is_null());
2545 let root_name = crate::xml::string::xmlstr_to_bytes((*root).name);
2546 assert_eq!(root_name, b"catalog");
2547
2548 let child = (*root).children;
2550 assert!(!child.is_null());
2551 let child_name = crate::xml::string::xmlstr_to_bytes((*child).name);
2552 assert_eq!(child_name, b"public");
2553
2554 crate::xml::tree::free_doc(doc);
2555 free_xmlstr(type_);
2556 free_xmlstr(pub_id);
2557 free_xmlstr(uri);
2558 teardown(_guard);
2559 }
2560 }
2561
2562 #[test]
2565 fn test_catalog_disallowed() {
2566 let _guard = setup();
2567 unsafe {
2568 let type_ = to_xmlstr_str("system");
2570 let sys_id = to_xmlstr_str("http://example.com/test.dtd");
2571 let uri = to_xmlstr_str("/local/test.dtd");
2572 add(type_, sys_id, uri);
2573
2574 set_defaults(XML_CATA_ALLOW_NONE);
2576
2577 assert!(resolve_system(sys_id).is_null());
2579 assert!(resolve_public(sys_id).is_null());
2580 assert!(resolve_uri(sys_id).is_null());
2581
2582 set_defaults(XML_CATA_ALLOW_ALL);
2583 free_xmlstr(type_);
2584 free_xmlstr(sys_id);
2585 free_xmlstr(uri);
2586 teardown(_guard);
2587 }
2588 }
2589
2590 #[test]
2593 fn test_init_cleanup() {
2594 let _guard = CATALOG_TEST_MUTEX.lock().unwrap();
2595 cleanup();
2596 assert!(!CATALOG_STATE.read().initialized);
2597
2598 init();
2599 assert!(CATALOG_STATE.read().initialized);
2600
2601 cleanup();
2602 assert!(!CATALOG_STATE.read().initialized);
2603 }
2604
2605 #[test]
2608 fn test_parse_xml_catalog_group() {
2609 let catalog_xml = br#"<?xml version="1.0"?>
2610<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
2611 <group>
2612 <public publicId="-//GROUP//PUBLIC//EN" uri="group.dtd"/>
2613 <system systemId="http://group.example.com/" uri="/group/"/>
2614 </group>
2615</catalog>"#;
2616
2617 let mut entries = Vec::new();
2618 parse_xml_catalog(catalog_xml, &mut entries);
2619 assert_eq!(entries.len(), 2);
2620
2621 match &entries[0] {
2622 CatalogEntry::Public { public_id, .. } => {
2623 assert_eq!(public_id.as_slice(), b"-//GROUP//PUBLIC//EN");
2624 }
2625 _ => panic!("Expected Public entry"),
2626 }
2627
2628 match &entries[1] {
2629 CatalogEntry::System { system_id, .. } => {
2630 assert_eq!(system_id.as_slice(), b"http://group.example.com/");
2631 }
2632 _ => panic!("Expected System entry"),
2633 }
2634 }
2635
2636 #[test]
2639 fn test_multiple_entries() {
2640 let _guard = setup();
2641 unsafe {
2642 let t = to_xmlstr_str("public");
2644 let id1 = to_xmlstr_str("-//A//PUBLIC//EN");
2645 let uri1 = to_xmlstr_str("a.dtd");
2646 let id2 = to_xmlstr_str("-//B//PUBLIC//EN");
2647 let uri2 = to_xmlstr_str("b.dtd");
2648
2649 assert_eq!(add(t, id1, uri1), 0);
2650 assert_eq!(add(t, id2, uri2), 0);
2651
2652 let r1 = resolve_public(id1);
2653 assert!(!r1.is_null());
2654 assert_eq!(xmlstr_to_bytes(r1), b"a.dtd");
2655 xmlFreeImpl(r1 as *mut c_void);
2656
2657 let r2 = resolve_public(id2);
2658 assert!(!r2.is_null());
2659 assert_eq!(xmlstr_to_bytes(r2), b"b.dtd");
2660 xmlFreeImpl(r2 as *mut c_void);
2661
2662 free_xmlstr(t);
2663 free_xmlstr(id1);
2664 free_xmlstr(uri1);
2665 free_xmlstr(id2);
2666 free_xmlstr(uri2);
2667 teardown(_guard);
2668 }
2669 }
2670
2671 #[test]
2674 fn test_longest_prefix_wins() {
2675 let _guard = setup();
2676 unsafe {
2677 let t = to_xmlstr_str("rewriteSystem");
2678 let p1 = to_xmlstr_str("http://example.com/");
2679 let r1 = to_xmlstr_str("/general/");
2680 let p2 = to_xmlstr_str("http://example.com/specific/");
2681 let r2 = to_xmlstr_str("/specific/");
2682
2683 add(t, p1, r1);
2684 add(t, p2, r2);
2685
2686 let sys_id = to_xmlstr_str("http://example.com/specific/file.xml");
2687 let result = resolve_system(sys_id);
2688 assert!(!result.is_null());
2689 assert_eq!(xmlstr_to_bytes(result), b"/specific/file.xml");
2690 xmlFreeImpl(result as *mut c_void);
2691
2692 free_xmlstr(t);
2693 free_xmlstr(p1);
2694 free_xmlstr(r1);
2695 free_xmlstr(p2);
2696 free_xmlstr(r2);
2697 free_xmlstr(sys_id);
2698 teardown(_guard);
2699 }
2700 }
2701}
2702
2703#[cfg(test)]
2708mod c_abi_tests {
2709 use super::*;
2710 use crate::abi::allocator::xmlFreeImpl;
2711
2712 fn cstr(s: &[u8]) -> *const xmlChar {
2713 s.as_ptr() as *const xmlChar
2714 }
2715
2716 #[test]
2717 fn test_new_free_catalog() {
2718 unsafe {
2719 let h = xmlNewCatalog(0);
2720 assert!(!h.is_null());
2721 assert_eq!(xmlCatalogIsEmpty(h), 1);
2722 xmlFreeCatalog(h);
2723 xmlFreeCatalog(ptr::null_mut());
2724 }
2725 }
2726
2727 #[test]
2728 fn test_acatalog_add_resolve_remove() {
2729 unsafe {
2730 let h = xmlNewCatalog(0);
2733 assert!(!h.is_null());
2734 assert_eq!(
2735 xmlACatalogAdd(
2736 h,
2737 cstr(b"system\0"),
2738 cstr(b"http://x\0"),
2739 cstr(b"file:///x\0")
2740 ),
2741 -1
2742 );
2743 xmlFreeCatalog(h);
2744
2745 let h = xmlNewCatalog(0);
2748 assert!(!h.is_null());
2749 (*h).entries.push(CatalogEntry::System {
2750 system_id: b"http://example.com/foo\0".to_vec(),
2751 uri: b"file:///tmp/foo.xml\0".to_vec(),
2752 });
2753 assert_eq!(
2754 xmlACatalogAdd(
2755 h,
2756 cstr(b"system\0"),
2757 cstr(b"http://example.com/foo\0"),
2758 cstr(b"file:///tmp/foo.xml\0")
2759 ),
2760 0
2761 );
2762 assert_eq!(xmlCatalogIsEmpty(h), 0);
2763 let r = xmlACatalogResolveSystem(h, cstr(b"http://example.com/foo\0"));
2765 assert!(!r.is_null());
2766 let bytes = xmlstr_to_bytes(r);
2767 assert_eq!(bytes, b"file:///tmp/foo.xml");
2768 xmlFreeImpl(r as *mut libc::c_void);
2769 let r2 = xmlACatalogResolveURI(h, cstr(b"http://example.com/foo\0"));
2771 assert!(!r2.is_null());
2772 xmlFreeImpl(r2 as *mut libc::c_void);
2773 assert_eq!(
2775 xmlACatalogAdd(h, cstr(b"bogus\0"), cstr(b"a\0"), cstr(b"b\0")),
2776 -1
2777 );
2778 assert_eq!(xmlACatalogRemove(h, cstr(b"http://example.com/foo\0")), 0);
2782 assert_eq!(xmlCatalogIsEmpty(h), 1);
2783 xmlFreeCatalog(h);
2784 }
2785 }
2786
2787 #[test]
2788 fn test_acatalog_public_and_rewrite() {
2789 unsafe {
2790 let h = xmlNewCatalog(0);
2791 assert!(!h.is_null());
2792 (*h).entries.push(CatalogEntry::Public {
2794 public_id: b"-//OASIS//DTD X//EN\0".to_vec(),
2795 uri: b"file:///dtd/x.dtd\0".to_vec(),
2796 });
2797 assert_eq!(
2798 xmlACatalogAdd(
2799 h,
2800 cstr(b"public\0"),
2801 cstr(b"-//OASIS//DTD X//EN\0"),
2802 cstr(b"file:///dtd/x.dtd\0")
2803 ),
2804 0
2805 );
2806 assert_eq!(
2807 xmlACatalogAdd(
2808 h,
2809 cstr(b"rewriteSystem\0"),
2810 cstr(b"http://old/\0"),
2811 cstr(b"http://new/\0")
2812 ),
2813 0
2814 );
2815 let r = xmlACatalogResolvePublic(h, cstr(b"-//OASIS//DTD X//EN\0"));
2816 assert!(!r.is_null());
2817 assert_eq!(xmlstr_to_bytes(r), b"file:///dtd/x.dtd");
2818 xmlFreeImpl(r as *mut libc::c_void);
2819 let r2 = xmlACatalogResolveSystem(h, cstr(b"http://old/foo.xml\0"));
2820 assert!(!r2.is_null());
2821 assert_eq!(xmlstr_to_bytes(r2), b"http://new/foo.xml");
2822 xmlFreeImpl(r2 as *mut libc::c_void);
2823 xmlFreeCatalog(h);
2824 }
2825 }
2826
2827 #[test]
2828 fn test_catalog_set_debug_and_prefer() {
2829 unsafe {
2830 assert_eq!(xmlCatalogSetDefaultPrefer(1), 1);
2833 assert_eq!(xmlCatalogSetDefaultPrefer(2), 1);
2834 assert_eq!(xmlCatalogSetDefaultPrefer(0), 2);
2835 assert_eq!(xmlCatalogSetDefaultPrefer(1), 2);
2836 assert_eq!(xmlCatalogSetDebug(0), 0);
2837 assert_eq!(xmlCatalogSetDebug(7), 0);
2838 assert_eq!(xmlCatalogSetDebug(0), 7);
2839 }
2840 }
2841
2842 #[test]
2843 fn test_catalog_local_resolve() {
2844 unsafe {
2845 assert!(xmlCatalogLocalResolve(ptr::null_mut(), cstr(b"x\0"), cstr(b"y\0")).is_null());
2847 assert!(xmlCatalogLocalResolveURI(ptr::null_mut(), cstr(b"x\0")).is_null());
2848 xmlCatalogFreeLocal(ptr::null_mut());
2849 }
2850 }
2851
2852 #[test]
2853 fn test_catalog_resolve_global_null() {
2854 unsafe {
2855 assert!(xmlCatalogResolve(ptr::null(), ptr::null()).is_null());
2856 }
2857 }
2858}