1#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
77
78use core::ffi::c_void;
79use std::ffi::CStr;
80use std::fs;
81use std::os::raw::{c_char, c_int};
82use std::path::Path;
83use std::ptr;
84
85use once_cell::sync::Lazy;
86use parking_lot::RwLock;
87
88use crate::abi::allocator::xmlFreeImpl;
89use crate::abi::structs::{_xmlDoc, _xmlNode};
90use crate::abi::types::xmlChar;
91use crate::xml::string::{bytes_to_xmlstr, xmlstr_to_bytes};
92
93pub(crate) const XML_CATA_ALLOW_NONE: i32 = 0;
99
100pub(crate) const XML_CATA_ALLOW_GLOBAL: i32 = 1;
102
103pub(crate) const XML_CATA_ALLOW_DOCUMENT: i32 = 2;
105
106pub(crate) const XML_CATA_ALLOW_ALL: i32 = 3;
108
109const DEFAULT_CATALOG: &str = "/etc/xml/catalog";
111
112const XML_CATALOG_FILES_ENV: &str = "XML_CATALOG_FILES";
114
115const SGML_CATALOG_FILES_ENV: &str = "SGML_CATALOG_FILES";
117
118const MAX_CATALOG_FILE_SIZE: usize = 10_485_760;
120
121#[derive(Clone, Debug)]
127pub enum CatalogEntry {
128 Public {
130 public_id: Vec<u8>,
132 uri: Vec<u8>,
134 },
135 System {
137 system_id: Vec<u8>,
139 uri: Vec<u8>,
141 },
142 RewriteSystem {
144 prefix: Vec<u8>,
146 rewrite: Vec<u8>,
148 },
149 RewriteURI {
151 prefix: Vec<u8>,
153 rewrite: Vec<u8>,
155 },
156 DelegatePublic {
158 prefix: Vec<u8>,
160 catalog: Vec<u8>,
162 },
163 DelegateSystem {
165 prefix: Vec<u8>,
167 catalog: Vec<u8>,
169 },
170 DelegateURI {
172 prefix: Vec<u8>,
174 catalog: Vec<u8>,
176 },
177 NextCatalog {
179 catalog: Vec<u8>,
181 },
182}
183
184#[derive(Clone, Copy, Debug, PartialEq)]
186enum CatalogFormat {
187 Xml,
188 Sgml,
189}
190
191#[allow(dead_code)]
193#[derive(Clone, Debug)]
194struct CatalogInfo {
195 path: Vec<u8>,
196 format: CatalogFormat,
197}
198
199struct CatalogState {
205 entries: Vec<CatalogEntry>,
207 catalogs: Vec<CatalogInfo>,
209 initialized: bool,
211 allow: i32,
213}
214
215impl CatalogState {
216 const fn new() -> Self {
217 Self {
218 entries: Vec::new(),
219 catalogs: Vec::new(),
220 initialized: false,
221 allow: XML_CATA_ALLOW_ALL,
222 }
223 }
224
225 fn clear(&mut self) {
227 self.entries.clear();
228 self.catalogs.clear();
229 self.allow = XML_CATA_ALLOW_ALL;
230 }
231}
232
233static CATALOG_STATE: Lazy<RwLock<CatalogState>> = Lazy::new(|| RwLock::new(CatalogState::new()));
235
236fn trim_whitespace(bytes: &[u8]) -> &[u8] {
242 let start = bytes
243 .iter()
244 .position(|b| !b.is_ascii_whitespace())
245 .unwrap_or(bytes.len());
246 let end = bytes
247 .iter()
248 .rposition(|b| !b.is_ascii_whitespace())
249 .map_or(0, |p| p + 1);
250 &bytes[start..end]
251}
252
253#[allow(dead_code)]
255fn starts_with(data: &[u8], prefix: &[u8]) -> bool {
256 if data.len() < prefix.len() {
257 return false;
258 }
259 data[..prefix.len()] == prefix[..]
260}
261
262#[allow(dead_code)]
264fn starts_with_ignore_ascii_case(data: &[u8], prefix: &[u8]) -> bool {
265 if data.len() < prefix.len() {
266 return false;
267 }
268 data[..prefix.len()]
269 .iter()
270 .zip(prefix.iter())
271 .all(|(a, b)| a.eq_ignore_ascii_case(b))
272}
273
274fn extract_attr_value<'a>(data: &'a [u8], name: &[u8], pos: usize) -> Option<(&'a [u8], usize)> {
279 let remaining = &data[pos..];
280 let name_pos = find_subsequence(remaining, name)?;
282 let after_name = name_pos + name.len();
283 let after_name_slice = &remaining[after_name..];
284
285 let eq_pos = after_name_slice.iter().position(|b| *b == b'=')?;
287
288 let rel_quote_start = after_name_slice[eq_pos + 1..]
290 .iter()
291 .position(|b| *b == b'"' || *b == b'\'')
292 .map(|p| after_name + eq_pos + 1 + p)?;
293 let abs_quote_start = pos + rel_quote_start;
294 let quote_char = data[abs_quote_start];
295 let value_start = abs_quote_start + 1;
297 let value_end = data[value_start..]
298 .iter()
299 .position(|b| *b == quote_char)
300 .map(|p| value_start + p)?;
301
302 Some((&data[value_start..value_end], value_end + 1))
303}
304
305fn find_subsequence(data: &[u8], seq: &[u8]) -> Option<usize> {
307 if seq.is_empty() {
308 return Some(0);
309 }
310 data.windows(seq.len()).position(|w| w == seq)
311}
312
313fn extract_token(line: &[u8], pos: usize) -> Option<(&[u8], usize)> {
315 let line = &line[pos..];
316 let start = line.iter().position(|b| !b.is_ascii_whitespace())?;
317 let end = line[start..]
318 .iter()
319 .position(|b| b.is_ascii_whitespace())
320 .map(|p| start + p)
321 .unwrap_or(line.len());
322 Some((&line[start..end], pos + end))
323}
324
325fn extract_quoted_token(line: &[u8], pos: usize) -> Option<(&[u8], usize)> {
327 let line = &line[pos..];
328 let start = line.iter().position(|b| !b.is_ascii_whitespace())?;
329 if start >= line.len() {
330 return None;
331 }
332 let quote_char = line[start];
333 if quote_char != b'"' && quote_char != b'\'' {
334 return extract_token(line, 0);
336 }
337 let value_start = start + 1;
338 let end = line[value_start..]
339 .iter()
340 .position(|b| *b == quote_char)
341 .map(|p| value_start + p)?;
342 Some((&line[value_start..end], pos + end + 1))
343}
344
345fn parse_sgml_line(line: &[u8], entries: &mut Vec<CatalogEntry>) {
364 let trimmed = trim_whitespace(line);
365 if trimmed.is_empty() || trimmed.starts_with(b"--") {
366 return;
367 }
368
369 let Some((directive, after_directive)) = extract_token(trimmed, 0) else {
371 return;
372 };
373
374 match directive {
375 b"PUBLIC" | b"public" => {
376 let Some((pub_id, after_pub)) = extract_quoted_token(trimmed, after_directive) else {
377 return;
378 };
379 let Some((uri, _)) = extract_quoted_token(trimmed, after_pub) else {
380 return;
381 };
382 entries.push(CatalogEntry::Public {
383 public_id: pub_id.to_vec(),
384 uri: uri.to_vec(),
385 });
386 }
387 b"SYSTEM" | b"system" => {
388 let Some((sys_id, after_sys)) = extract_quoted_token(trimmed, after_directive) else {
389 return;
390 };
391 let Some((uri, _)) = extract_quoted_token(trimmed, after_sys) else {
392 return;
393 };
394 entries.push(CatalogEntry::System {
395 system_id: sys_id.to_vec(),
396 uri: uri.to_vec(),
397 });
398 }
399 b"URI" | b"uri" => {
400 let Some((uri_id, after_uri)) = extract_quoted_token(trimmed, after_directive) else {
402 return;
403 };
404 let Some((replacement, _)) = extract_quoted_token(trimmed, after_uri) else {
405 return;
406 };
407 entries.push(CatalogEntry::System {
408 system_id: uri_id.to_vec(),
409 uri: replacement.to_vec(),
410 });
411 }
412 b"CATALOG" | b"catalog" => {
413 let Some((path, _)) = extract_quoted_token(trimmed, after_directive) else {
414 return;
415 };
416 entries.push(CatalogEntry::NextCatalog {
417 catalog: path.to_vec(),
418 });
419 }
420 _ => {
421 }
423 }
424}
425
426fn parse_sgml_catalog(data: &[u8], entries: &mut Vec<CatalogEntry>) {
428 for line in data.split(|b| *b == b'\n') {
429 parse_sgml_line(line, entries);
430 }
431}
432
433fn parse_xml_catalog(data: &[u8], entries: &mut Vec<CatalogEntry>) {
442 let mut pos = 0;
443 let len = data.len();
444
445 while pos < len {
446 let Some(lt_pos) = data[pos..].iter().position(|b| *b == b'<') else {
448 break;
449 };
450 let tag_start = pos + lt_pos;
451
452 if tag_start + 1 >= len {
454 break;
455 }
456
457 let is_closing = data[tag_start + 1] == b'/';
458 if is_closing {
459 let Some(gt_pos) = data[tag_start..].iter().position(|b| *b == b'>') else {
461 break;
462 };
463 pos = tag_start + gt_pos + 1;
464 continue;
465 }
466
467 if data[tag_start + 1] == b'!' || data[tag_start + 1] == b'?' {
469 let Some(gt_pos) = data[tag_start..].iter().position(|b| *b == b'>') else {
470 break;
471 };
472 pos = tag_start + gt_pos + 1;
473 continue;
474 }
475
476 let tag_name_start = tag_start + 1;
478 let tag_name_end = data[tag_name_start..]
479 .iter()
480 .position(|b| b.is_ascii_whitespace() || *b == b'>' || *b == b'/')
481 .map(|p| tag_name_start + p)
482 .unwrap_or(len);
483
484 let tag_name = &data[tag_name_start..tag_name_end];
485
486 let Some(gt_or_slash_pos) = data[tag_start..]
488 .iter()
489 .position(|b| *b == b'>')
490 .map(|p| tag_start + p)
491 else {
492 break;
493 };
494
495 let is_self_closing = gt_or_slash_pos > 0 && data[gt_or_slash_pos - 1] == b'/';
496 let tag_content_end = if is_self_closing {
497 gt_or_slash_pos + 1
498 } else {
499 let close_tag = {
501 let mut close = Vec::with_capacity(tag_name.len() + 3);
502 close.push(b'<');
503 close.push(b'/');
504 close.extend_from_slice(tag_name);
505 close.push(b'>');
506 close
507 };
508 let close_pos = data[gt_or_slash_pos + 1..]
509 .windows(close_tag.len())
510 .position(|w| w == close_tag.as_slice())
511 .map(|p| gt_or_slash_pos + 1 + p + close_tag.len());
512
513 match close_pos {
514 Some(p) => p,
515 None => {
516 pos = gt_or_slash_pos + 1;
517 continue;
518 }
519 }
520 };
521
522 let tag_body_start = gt_or_slash_pos + 1;
523 let tag_body = &data[tag_body_start
524 ..tag_content_end
525 - if is_self_closing {
526 0
527 } else {
528 tag_name.len() + 3
529 }];
530 let tag_body = trim_whitespace(tag_body);
531
532 match tag_name {
533 b"public" => {
534 let Some((pub_id, _)) = extract_attr_value(data, b"publicId", tag_start) else {
535 pos = tag_content_end;
536 continue;
537 };
538 let Some((uri, _)) = extract_attr_value(data, b"uri", tag_start) else {
539 pos = tag_content_end;
540 continue;
541 };
542 entries.push(CatalogEntry::Public {
543 public_id: pub_id.to_vec(),
544 uri: uri.to_vec(),
545 });
546 }
547 b"system" => {
548 let Some((sys_id, _)) = extract_attr_value(data, b"systemId", tag_start) else {
549 pos = tag_content_end;
550 continue;
551 };
552 let Some((uri, _)) = extract_attr_value(data, b"uri", tag_start) else {
553 pos = tag_content_end;
554 continue;
555 };
556 entries.push(CatalogEntry::System {
557 system_id: sys_id.to_vec(),
558 uri: uri.to_vec(),
559 });
560 }
561 b"rewriteSystem" => {
562 let Some((prefix, _)) = extract_attr_value(data, b"systemIdStartString", tag_start)
563 else {
564 pos = tag_content_end;
565 continue;
566 };
567 let Some((rewrite, _)) = extract_attr_value(data, b"rewritePrefix", tag_start)
568 else {
569 pos = tag_content_end;
570 continue;
571 };
572 entries.push(CatalogEntry::RewriteSystem {
573 prefix: prefix.to_vec(),
574 rewrite: rewrite.to_vec(),
575 });
576 }
577 b"rewriteURI" => {
578 let Some((prefix, _)) = extract_attr_value(data, b"uriStartString", tag_start)
579 else {
580 pos = tag_content_end;
581 continue;
582 };
583 let Some((rewrite, _)) = extract_attr_value(data, b"rewritePrefix", tag_start)
584 else {
585 pos = tag_content_end;
586 continue;
587 };
588 entries.push(CatalogEntry::RewriteURI {
589 prefix: prefix.to_vec(),
590 rewrite: rewrite.to_vec(),
591 });
592 }
593 b"delegatePublic" => {
594 let Some((prefix, _)) = extract_attr_value(data, b"publicIdStartString", tag_start)
595 else {
596 pos = tag_content_end;
597 continue;
598 };
599 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
600 pos = tag_content_end;
601 continue;
602 };
603 entries.push(CatalogEntry::DelegatePublic {
604 prefix: prefix.to_vec(),
605 catalog: catalog.to_vec(),
606 });
607 }
608 b"delegateSystem" => {
609 let Some((prefix, _)) = extract_attr_value(data, b"systemIdStartString", tag_start)
610 else {
611 pos = tag_content_end;
612 continue;
613 };
614 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
615 pos = tag_content_end;
616 continue;
617 };
618 entries.push(CatalogEntry::DelegateSystem {
619 prefix: prefix.to_vec(),
620 catalog: catalog.to_vec(),
621 });
622 }
623 b"delegateURI" => {
624 let Some((prefix, _)) = extract_attr_value(data, b"uriStartString", tag_start)
625 else {
626 pos = tag_content_end;
627 continue;
628 };
629 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
630 pos = tag_content_end;
631 continue;
632 };
633 entries.push(CatalogEntry::DelegateURI {
634 prefix: prefix.to_vec(),
635 catalog: catalog.to_vec(),
636 });
637 }
638 b"nextCatalog" => {
639 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
640 pos = tag_content_end;
641 continue;
642 };
643 entries.push(CatalogEntry::NextCatalog {
644 catalog: catalog.to_vec(),
645 });
646 }
647 b"group" | b"catalog" => {
648 parse_xml_catalog(tag_body, entries);
650 }
651 _ => {
652 }
654 }
655
656 pos = tag_content_end;
657 }
658}
659
660fn read_file_bytes(path: &str) -> Option<Vec<u8>> {
666 let p = Path::new(path);
667 let metadata = fs::metadata(p).ok()?;
669 if metadata.len() > MAX_CATALOG_FILE_SIZE as u64 {
670 return None;
671 }
672 fs::read(p).ok()
673}
674
675fn detect_catalog_format(data: &[u8]) -> CatalogFormat {
682 let trimmed = trim_whitespace(data);
683 if trimmed.starts_with(b"<?xml") || trimmed.starts_with(b"<catalog") {
684 CatalogFormat::Xml
685 } else {
686 CatalogFormat::Sgml
687 }
688}
689
690fn load_catalog_data(_path: &str, data: &[u8], entries: &mut Vec<CatalogEntry>) {
692 let format = detect_catalog_format(data);
693 match format {
694 CatalogFormat::Xml => {
695 parse_xml_catalog(data, entries);
696 }
697 CatalogFormat::Sgml => {
698 parse_sgml_catalog(data, entries);
699 }
700 }
701}
702
703fn load_single_catalog(path: &str, state: &mut CatalogState) {
705 let data = match read_file_bytes(path) {
706 Some(d) => d,
707 None => return,
708 };
709
710 let format = detect_catalog_format(&data);
711 state.catalogs.push(CatalogInfo {
712 path: path.as_bytes().to_vec(),
713 format,
714 });
715
716 load_catalog_data(path, &data, &mut state.entries);
717}
718
719fn load_catalog_list(catalogs: &str, state: &mut CatalogState) {
721 for catalog_path in catalogs.split(':') {
722 let trimmed = catalog_path.trim();
723 if !trimmed.is_empty() {
724 load_single_catalog(trimmed, state);
725 }
726 }
727}
728
729pub(crate) fn init() {
738 let mut state = CATALOG_STATE.write();
739 if state.initialized {
740 return;
741 }
742
743 state.allow = XML_CATA_ALLOW_ALL;
745 crate::xml::globals::set_catalog_defaults(XML_CATA_ALLOW_ALL);
746
747 if let Ok(catalogs) = std::env::var(XML_CATALOG_FILES_ENV) {
749 load_catalog_list(&catalogs, &mut state);
750 }
751
752 if let Ok(catalogs) = std::env::var(SGML_CATALOG_FILES_ENV) {
754 load_catalog_list(&catalogs, &mut state);
755 }
756
757 if Path::new(DEFAULT_CATALOG).exists() {
759 load_single_catalog(DEFAULT_CATALOG, &mut state);
760 }
761
762 state.initialized = true;
763}
764
765pub(crate) fn cleanup() {
769 let mut state = CATALOG_STATE.write();
770 state.clear();
771 state.initialized = false;
772}
773
774pub(crate) fn load_catalog(catalogs: *const c_char) -> *mut c_void {
788 if catalogs.is_null() {
789 return ptr::null_mut();
790 }
791
792 let catalogs_str = unsafe { CStr::from_ptr(catalogs) };
793 let catalogs_str = catalogs_str.to_str().unwrap_or("");
794
795 let mut state = CATALOG_STATE.write();
796
797 if !state.initialized {
799 drop(state);
800 init();
801 state = CATALOG_STATE.write();
802 }
803
804 let count_before = state.catalogs.len();
805 load_catalog_list(catalogs_str, &mut state);
806
807 if state.catalogs.len() > count_before {
808 (state.catalogs.len() as isize) as *mut c_void
810 } else {
811 ptr::null_mut()
812 }
813}
814
815const fn catalog_allowed(state: &CatalogState) -> bool {
821 let allow = state.allow;
822 match allow {
823 XML_CATA_ALLOW_NONE => false,
824 XML_CATA_ALLOW_GLOBAL | XML_CATA_ALLOW_DOCUMENT | XML_CATA_ALLOW_ALL => true,
825 _ => false,
826 }
827}
828
829unsafe fn resolve_public_entries(entries: &[CatalogEntry], pub_id_bytes: &[u8]) -> Option<Vec<u8>> {
832 for entry in entries {
834 if let CatalogEntry::Public { public_id, uri } = entry {
835 if public_id.as_slice() == pub_id_bytes {
836 return Some(uri.clone());
837 }
838 }
839 }
840
841 let mut best_match: Option<Vec<u8>> = None;
843 let mut best_prefix_len: usize = 0;
844
845 for entry in entries {
846 if let CatalogEntry::DelegatePublic { prefix, catalog } = entry {
847 if pub_id_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
848 best_prefix_len = prefix.len();
849 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
851 let mut temp_entries = Vec::new();
852 parse_xml_catalog(&delegated_data, &mut temp_entries);
853 for temp_entry in &temp_entries {
855 if let CatalogEntry::Public { public_id: dp, uri } = temp_entry {
856 if dp.as_slice() == pub_id_bytes {
857 best_match = Some(uri.clone());
858 }
859 }
860 }
861 }
862 }
863 }
864 }
865
866 best_match
867}
868
869pub(crate) unsafe fn resolve_public(pub_id: *const xmlChar) -> *mut xmlChar {
880 if pub_id.is_null() {
881 return ptr::null_mut();
882 }
883
884 let state = CATALOG_STATE.read();
885 if !catalog_allowed(&state) {
886 return ptr::null_mut();
887 }
888
889 let pub_id_bytes = xmlstr_to_bytes(pub_id);
890 unsafe { resolve_public_entries(&state.entries, pub_id_bytes) }
891 .as_ref()
892 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
893}
894
895unsafe fn resolve_system_entries(entries: &[CatalogEntry], sys_id_bytes: &[u8]) -> Option<Vec<u8>> {
897 for entry in entries {
899 if let CatalogEntry::System { system_id, uri } = entry {
900 if system_id.as_slice() == sys_id_bytes {
901 return Some(uri.clone());
902 }
903 }
904 }
905
906 let mut best_rewrite: Option<Vec<u8>> = None;
908 let mut best_prefix_len: usize = 0;
909
910 for entry in entries {
911 if let CatalogEntry::RewriteSystem { prefix, rewrite } = entry {
912 if sys_id_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
913 best_prefix_len = prefix.len();
914 let suffix = &sys_id_bytes[prefix.len()..];
916 let mut result = rewrite.clone();
917 result.extend_from_slice(suffix);
918 best_rewrite = Some(result);
919 }
920 }
921 }
922
923 if let Some(rewritten) = best_rewrite {
924 return Some(rewritten);
925 }
926
927 for entry in entries {
929 if let CatalogEntry::DelegateSystem { prefix, catalog } = entry {
930 if sys_id_bytes.starts_with(prefix) {
931 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
932 let mut temp_entries = Vec::new();
933 parse_xml_catalog(&delegated_data, &mut temp_entries);
934 for temp_entry in &temp_entries {
935 if let CatalogEntry::System { system_id, uri } = temp_entry {
936 if system_id.as_slice() == sys_id_bytes {
937 return Some(uri.clone());
938 }
939 }
940 }
941 }
942 }
943 }
944 }
945
946 None
947}
948
949pub(crate) unsafe fn resolve_system(sys_id: *const xmlChar) -> *mut xmlChar {
962 if sys_id.is_null() {
963 return ptr::null_mut();
964 }
965
966 let state = CATALOG_STATE.read();
967 if !catalog_allowed(&state) {
968 return ptr::null_mut();
969 }
970
971 let sys_id_bytes = xmlstr_to_bytes(sys_id);
972 unsafe { resolve_system_entries(&state.entries, sys_id_bytes) }
973 .as_ref()
974 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
975}
976
977unsafe fn resolve_uri_entries(entries: &[CatalogEntry], uri_bytes: &[u8]) -> Option<Vec<u8>> {
979 for entry in entries {
981 if let CatalogEntry::System {
982 system_id,
983 uri: sys_uri,
984 } = entry
985 {
986 if system_id.as_slice() == uri_bytes {
987 return Some(sys_uri.clone());
988 }
989 }
990 }
991
992 let mut best_rewrite: Option<Vec<u8>> = None;
994 let mut best_prefix_len: usize = 0;
995
996 for entry in entries {
997 if let CatalogEntry::RewriteURI { prefix, rewrite } = entry {
998 if uri_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
999 best_prefix_len = prefix.len();
1000 let suffix = &uri_bytes[prefix.len()..];
1001 let mut result = rewrite.clone();
1002 result.extend_from_slice(suffix);
1003 best_rewrite = Some(result);
1004 }
1005 }
1006 }
1007
1008 if let Some(rewritten) = best_rewrite {
1009 return Some(rewritten);
1010 }
1011
1012 for entry in entries {
1014 if let CatalogEntry::DelegateURI { prefix, catalog } = entry {
1015 if uri_bytes.starts_with(prefix) {
1016 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
1017 let mut temp_entries = Vec::new();
1018 parse_xml_catalog(&delegated_data, &mut temp_entries);
1019 for temp_entry in &temp_entries {
1020 if let CatalogEntry::System {
1021 system_id,
1022 uri: sys_uri,
1023 } = temp_entry
1024 {
1025 if system_id.as_slice() == uri_bytes {
1026 return Some(sys_uri.clone());
1027 }
1028 }
1029 }
1030 }
1031 }
1032 }
1033 }
1034
1035 None
1036}
1037
1038pub(crate) unsafe fn resolve_uri(uri: *const xmlChar) -> *mut xmlChar {
1051 if uri.is_null() {
1052 return ptr::null_mut();
1053 }
1054
1055 let state = CATALOG_STATE.read();
1056 if !catalog_allowed(&state) {
1057 return ptr::null_mut();
1058 }
1059
1060 let uri_bytes = xmlstr_to_bytes(uri);
1061 unsafe { resolve_uri_entries(&state.entries, uri_bytes) }
1062 .as_ref()
1063 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
1064}
1065
1066pub(crate) fn set_defaults(allow: c_int) {
1081 let mut state = CATALOG_STATE.write();
1082 state.allow = allow;
1083 crate::xml::globals::set_catalog_defaults(allow);
1084}
1085
1086pub(crate) fn get_defaults() -> c_int {
1094 let state = CATALOG_STATE.read();
1095 state.allow
1096}
1097
1098pub(crate) unsafe fn add(
1115 type_: *const xmlChar,
1116 orig: *const xmlChar,
1117 replace: *const xmlChar,
1118) -> c_int {
1119 if type_.is_null() || orig.is_null() || replace.is_null() {
1120 return -1;
1121 }
1122
1123 let type_bytes = xmlstr_to_bytes(type_);
1124 let orig_bytes = xmlstr_to_bytes(orig);
1125 let replace_bytes = xmlstr_to_bytes(replace);
1126
1127 let mut state = CATALOG_STATE.write();
1128
1129 match type_bytes {
1130 b"public" => {
1131 state.entries.push(CatalogEntry::Public {
1132 public_id: orig_bytes.to_vec(),
1133 uri: replace_bytes.to_vec(),
1134 });
1135 0
1136 }
1137 b"system" => {
1138 state.entries.push(CatalogEntry::System {
1139 system_id: orig_bytes.to_vec(),
1140 uri: replace_bytes.to_vec(),
1141 });
1142 0
1143 }
1144 b"rewriteSystem" => {
1145 state.entries.push(CatalogEntry::RewriteSystem {
1146 prefix: orig_bytes.to_vec(),
1147 rewrite: replace_bytes.to_vec(),
1148 });
1149 0
1150 }
1151 b"rewriteURI" => {
1152 state.entries.push(CatalogEntry::RewriteURI {
1153 prefix: orig_bytes.to_vec(),
1154 rewrite: replace_bytes.to_vec(),
1155 });
1156 0
1157 }
1158 b"delegatePublic" => {
1159 state.entries.push(CatalogEntry::DelegatePublic {
1160 prefix: orig_bytes.to_vec(),
1161 catalog: replace_bytes.to_vec(),
1162 });
1163 0
1164 }
1165 b"delegateSystem" => {
1166 state.entries.push(CatalogEntry::DelegateSystem {
1167 prefix: orig_bytes.to_vec(),
1168 catalog: replace_bytes.to_vec(),
1169 });
1170 0
1171 }
1172 b"delegateURI" => {
1173 state.entries.push(CatalogEntry::DelegateURI {
1174 prefix: orig_bytes.to_vec(),
1175 catalog: replace_bytes.to_vec(),
1176 });
1177 0
1178 }
1179 b"nextCatalog" => {
1180 state.entries.push(CatalogEntry::NextCatalog {
1181 catalog: orig_bytes.to_vec(),
1182 });
1183 0
1184 }
1185 _ => -1,
1186 }
1187}
1188
1189pub(crate) unsafe fn remove(value: *const xmlChar) -> c_int {
1200 if value.is_null() {
1201 return -1;
1202 }
1203
1204 let value_bytes = xmlstr_to_bytes(value);
1205 let mut state = CATALOG_STATE.write();
1206
1207 let before = state.entries.len();
1208 state.entries.retain(|entry| match entry {
1209 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != value_bytes,
1210 CatalogEntry::System { system_id, .. } => system_id.as_slice() != value_bytes,
1211 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != value_bytes,
1212 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != value_bytes,
1213 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != value_bytes,
1214 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != value_bytes,
1215 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != value_bytes,
1216 CatalogEntry::NextCatalog { catalog } => catalog.as_slice() != value_bytes,
1217 });
1218
1219 (before - state.entries.len()) as c_int
1220}
1221
1222pub(crate) unsafe fn convert() -> *mut _xmlDoc {
1237 let state = CATALOG_STATE.read();
1238
1239 if state.entries.is_empty() {
1240 return ptr::null_mut();
1241 }
1242
1243 let doc = crate::xml::tree::new_doc(ptr::null_mut());
1245 if doc.is_null() {
1246 return ptr::null_mut();
1247 }
1248
1249 let catalog_name = b"catalog\0" as *const u8 as *const xmlChar;
1251 let root = crate::xml::tree::new_node(ptr::null_mut(), catalog_name);
1252 if root.is_null() {
1253 crate::xml::tree::free_doc(doc);
1254 return ptr::null_mut();
1255 }
1256
1257 let xmlns_name = b"xmlns\0" as *const u8 as *const xmlChar;
1259 let ns_value = b"urn:oasis:names:tc:entity:xmlns:xml:catalog\0" as *const u8 as *const xmlChar;
1260 crate::xml::tree::set_prop(root, xmlns_name, ns_value);
1261
1262 crate::xml::tree::doc_set_root_element(doc, root);
1263
1264 for entry in &state.entries {
1266 let (elem_name, attr1_name, attr1_value, attr2_name, attr2_value) = match entry {
1267 CatalogEntry::Public { public_id, uri } => {
1268 let elem = b"public\0" as *const u8 as *mut xmlChar;
1269 let attr1 = b"publicId\0" as *const u8 as *mut xmlChar;
1270 let val1 = bytes_to_xmlstr(public_id);
1271 let attr2 = b"uri\0" as *const u8 as *mut xmlChar;
1272 let val2 = bytes_to_xmlstr(uri);
1273 (elem, attr1, val1, attr2, val2)
1274 }
1275 CatalogEntry::System { system_id, uri } => {
1276 let elem = b"system\0" as *const u8 as *mut xmlChar;
1277 let attr1 = b"systemId\0" as *const u8 as *mut xmlChar;
1278 let val1 = bytes_to_xmlstr(system_id);
1279 let attr2 = b"uri\0" as *const u8 as *mut xmlChar;
1280 let val2 = bytes_to_xmlstr(uri);
1281 (elem, attr1, val1, attr2, val2)
1282 }
1283 CatalogEntry::RewriteSystem { prefix, rewrite } => {
1284 let elem = b"rewriteSystem\0" as *const u8 as *mut xmlChar;
1285 let attr1 = b"systemIdStartString\0" as *const u8 as *mut xmlChar;
1286 let val1 = bytes_to_xmlstr(prefix);
1287 let attr2 = b"rewritePrefix\0" as *const u8 as *mut xmlChar;
1288 let val2 = bytes_to_xmlstr(rewrite);
1289 (elem, attr1, val1, attr2, val2)
1290 }
1291 CatalogEntry::RewriteURI { prefix, rewrite } => {
1292 let elem = b"rewriteURI\0" as *const u8 as *mut xmlChar;
1293 let attr1 = b"uriStartString\0" as *const u8 as *mut xmlChar;
1294 let val1 = bytes_to_xmlstr(prefix);
1295 let attr2 = b"rewritePrefix\0" as *const u8 as *mut xmlChar;
1296 let val2 = bytes_to_xmlstr(rewrite);
1297 (elem, attr1, val1, attr2, val2)
1298 }
1299 CatalogEntry::DelegatePublic { prefix, catalog } => {
1300 let elem = b"delegatePublic\0" as *const u8 as *mut xmlChar;
1301 let attr1 = b"publicIdStartString\0" as *const u8 as *mut xmlChar;
1302 let val1 = bytes_to_xmlstr(prefix);
1303 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1304 let val2 = bytes_to_xmlstr(catalog);
1305 (elem, attr1, val1, attr2, val2)
1306 }
1307 CatalogEntry::DelegateSystem { prefix, catalog } => {
1308 let elem = b"delegateSystem\0" as *const u8 as *mut xmlChar;
1309 let attr1 = b"systemIdStartString\0" as *const u8 as *mut xmlChar;
1310 let val1 = bytes_to_xmlstr(prefix);
1311 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1312 let val2 = bytes_to_xmlstr(catalog);
1313 (elem, attr1, val1, attr2, val2)
1314 }
1315 CatalogEntry::DelegateURI { prefix, catalog } => {
1316 let elem = b"delegateURI\0" as *const u8 as *mut xmlChar;
1317 let attr1 = b"uriStartString\0" as *const u8 as *mut xmlChar;
1318 let val1 = bytes_to_xmlstr(prefix);
1319 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1320 let val2 = bytes_to_xmlstr(catalog);
1321 (elem, attr1, val1, attr2, val2)
1322 }
1323 CatalogEntry::NextCatalog { catalog } => {
1324 let elem = b"nextCatalog\0" as *const u8 as *mut xmlChar;
1325 let attr1 = b"catalog\0" as *const u8 as *mut xmlChar;
1326 let val1 = bytes_to_xmlstr(catalog);
1327 let attr2 = ptr::null_mut();
1328 let val2 = ptr::null_mut();
1329 (elem, attr1, val1, attr2, val2)
1330 }
1331 };
1332
1333 let child = crate::xml::tree::new_child(root, ptr::null_mut(), elem_name);
1334 if child.is_null() {
1335 if !attr1_value.is_null() {
1337 xmlFreeImpl(attr1_value as *mut c_void);
1338 }
1339 if !attr2_value.is_null() {
1340 xmlFreeImpl(attr2_value as *mut c_void);
1341 }
1342 continue;
1343 }
1344
1345 crate::xml::tree::set_prop(child, attr1_name, attr1_value);
1346 if !attr2_name.is_null() {
1347 crate::xml::tree::set_prop(child, attr2_name, attr2_value);
1348 }
1349
1350 if !attr1_value.is_null() {
1352 xmlFreeImpl(attr1_value as *mut c_void);
1353 }
1354 if !attr2_value.is_null() {
1355 xmlFreeImpl(attr2_value as *mut c_void);
1356 }
1357 }
1358
1359 doc
1360}
1361
1362pub unsafe fn dump_doc() -> *mut _xmlDoc {
1383 let mut doc = convert();
1384 if doc.is_null() {
1385 doc = crate::xml::tree::new_doc(ptr::null_mut());
1387 if doc.is_null() {
1388 return ptr::null_mut();
1389 }
1390 let root =
1391 crate::xml::tree::new_node(ptr::null_mut(), c"catalog".as_ptr() as *const xmlChar);
1392 if root.is_null() {
1393 crate::xml::tree::free_doc(doc);
1394 return ptr::null_mut();
1395 }
1396 crate::xml::tree::set_prop(
1397 root,
1398 c"xmlns".as_ptr() as *const xmlChar,
1399 c"urn:oasis:names:tc:entity:xmlns:xml:catalog".as_ptr() as *const xmlChar,
1400 );
1401 crate::xml::tree::doc_set_root_element(doc, root);
1402 }
1403
1404 let dtd = crate::xml::tree::new_dtd(
1405 doc,
1406 c"catalog".as_ptr() as *const xmlChar,
1407 c"-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN".as_ptr() as *const xmlChar,
1408 c"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd".as_ptr()
1409 as *const xmlChar,
1410 );
1411 if !dtd.is_null() {
1412 (*doc).intSubset = ptr::null_mut();
1413 let dtd_node = dtd as *mut _xmlNode;
1414 let first = (*doc).children;
1415 (*dtd_node).next = first;
1416 (*dtd_node).parent = doc as *mut _xmlNode;
1417 (*dtd_node).doc = doc;
1418 if !first.is_null() {
1419 (*first).prev = dtd_node;
1420 }
1421 (*doc).children = dtd_node;
1422 }
1423 doc
1424}
1425
1426#[derive(Debug)]
1443#[repr(C)]
1444pub struct XmlCatalogHandle {
1445 pub entries: Vec<CatalogEntry>,
1447 pub children: Vec<CatalogEntry>,
1450 pub sgml: c_int,
1453}
1454
1455static CATALOG_DEBUG: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
1457
1458static CATALOG_PREFER: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1);
1461
1462#[no_mangle]
1480pub unsafe extern "C" fn xmlNewCatalog(sgml: c_int) -> *mut XmlCatalogHandle {
1481 let h = Box::new(XmlCatalogHandle {
1482 entries: Vec::new(),
1483 children: Vec::new(),
1484 sgml,
1485 });
1486 Box::into_raw(h)
1487}
1488
1489#[no_mangle]
1495pub unsafe extern "C" fn xmlFreeCatalog(catal: *mut XmlCatalogHandle) {
1496 if !catal.is_null() {
1497 unsafe { drop(Box::from_raw(catal)) };
1498 }
1499}
1500
1501#[no_mangle]
1507pub unsafe extern "C" fn xmlLoadACatalog(filename: *const c_char) -> *mut XmlCatalogHandle {
1508 if filename.is_null() {
1509 return ptr::null_mut();
1510 }
1511 let name = unsafe { CStr::from_ptr(filename) };
1512 let name = name.to_str().unwrap_or("");
1513 let mut entries = Vec::new();
1514 if let Some(data) = read_file_bytes(name) {
1515 load_catalog_data(name, &data, &mut entries);
1516 }
1517 if entries.is_empty() {
1518 return ptr::null_mut();
1519 }
1520 Box::into_raw(Box::new(XmlCatalogHandle {
1521 entries,
1522 children: Vec::new(),
1523 sgml: 0,
1524 }))
1525}
1526
1527#[no_mangle]
1534pub unsafe extern "C" fn xmlLoadSGMLSuperCatalog(filename: *const c_char) -> *mut XmlCatalogHandle {
1535 unsafe { xmlLoadACatalog(filename) }
1536}
1537
1538#[no_mangle]
1546pub unsafe extern "C" fn xmlConvertSGMLCatalog(catal: *mut XmlCatalogHandle) -> c_int {
1547 if catal.is_null() {
1548 return -1;
1549 }
1550 unsafe { (*catal).sgml = 0 };
1551 0
1552}
1553
1554#[no_mangle]
1563pub unsafe extern "C" fn xmlACatalogAdd(
1564 catal: *mut XmlCatalogHandle,
1565 type_: *const xmlChar,
1566 orig: *const xmlChar,
1567 replace: *const xmlChar,
1568) -> c_int {
1569 if catal.is_null() || type_.is_null() || orig.is_null() || replace.is_null() {
1570 return -1;
1571 }
1572 if unsafe { (*catal).entries.is_empty() } {
1578 return -1;
1579 }
1580 let t = xmlstr_to_bytes(type_);
1581 let o = xmlstr_to_bytes(orig).to_vec();
1582 let r = xmlstr_to_bytes(replace).to_vec();
1583 let entry = if t == b"public" {
1584 CatalogEntry::Public {
1585 public_id: o,
1586 uri: r,
1587 }
1588 } else if t == b"system" {
1589 CatalogEntry::System {
1590 system_id: o,
1591 uri: r,
1592 }
1593 } else if t == b"rewriteSystem" {
1594 CatalogEntry::RewriteSystem {
1595 prefix: o,
1596 rewrite: r,
1597 }
1598 } else if t == b"rewriteURI" {
1599 CatalogEntry::RewriteURI {
1600 prefix: o,
1601 rewrite: r,
1602 }
1603 } else if t == b"delegatePublic" {
1604 CatalogEntry::DelegatePublic {
1605 prefix: o,
1606 catalog: r,
1607 }
1608 } else if t == b"delegateSystem" {
1609 CatalogEntry::DelegateSystem {
1610 prefix: o,
1611 catalog: r,
1612 }
1613 } else if t == b"delegateURI" {
1614 CatalogEntry::DelegateURI {
1615 prefix: o,
1616 catalog: r,
1617 }
1618 } else if t == b"nextCatalog" {
1619 CatalogEntry::NextCatalog { catalog: r }
1620 } else {
1621 return -1;
1622 };
1623 unsafe {
1624 (*catal).entries.push(entry.clone());
1625 (*catal).children.push(entry);
1626 };
1627 0
1628}
1629
1630#[no_mangle]
1636pub unsafe extern "C" fn xmlACatalogRemove(
1637 catal: *mut XmlCatalogHandle,
1638 value: *const xmlChar,
1639) -> c_int {
1640 if catal.is_null() || value.is_null() {
1641 return -1;
1642 }
1643 let v = xmlstr_to_bytes(value);
1644 let entries = unsafe { &mut (*catal).entries };
1645 entries.retain(|entry| match entry {
1646 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != v,
1647 CatalogEntry::System { system_id, .. } => system_id.as_slice() != v,
1648 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != v,
1649 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != v,
1650 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != v,
1651 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != v,
1652 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != v,
1653 CatalogEntry::NextCatalog { .. } => true,
1654 });
1655 let children = unsafe { &mut (*catal).children };
1656 children.retain(|entry| match entry {
1657 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != v,
1658 CatalogEntry::System { system_id, .. } => system_id.as_slice() != v,
1659 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != v,
1660 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != v,
1661 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != v,
1662 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != v,
1663 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != v,
1664 CatalogEntry::NextCatalog { .. } => true,
1665 });
1666 0
1672}
1673
1674#[no_mangle]
1686pub unsafe extern "C" fn xmlACatalogResolve(
1687 catal: *mut XmlCatalogHandle,
1688 pubID: *const xmlChar,
1689 sysID: *const xmlChar,
1690) -> *mut xmlChar {
1691 if catal.is_null() {
1692 return ptr::null_mut();
1693 }
1694 let entries = unsafe { &(*catal).entries };
1695 if !sysID.is_null() {
1696 let b = xmlstr_to_bytes(sysID);
1697 if let Some(r) = unsafe { resolve_system_entries(entries, b) } {
1698 return bytes_to_xmlstr(&r);
1699 }
1700 }
1701 if !pubID.is_null() {
1702 let b = xmlstr_to_bytes(pubID);
1703 if let Some(r) = unsafe { resolve_public_entries(entries, b) } {
1704 return bytes_to_xmlstr(&r);
1705 }
1706 }
1707 ptr::null_mut()
1708}
1709
1710#[no_mangle]
1731pub unsafe extern "C" fn xmlACatalogResolveSystem(
1732 catal: *mut XmlCatalogHandle,
1733 sysID: *const xmlChar,
1734) -> *mut xmlChar {
1735 if catal.is_null() || sysID.is_null() {
1736 return ptr::null_mut();
1737 }
1738 let entries = unsafe { &(*catal).entries };
1739 let b = xmlstr_to_bytes(sysID);
1740 unsafe { resolve_system_entries(entries, b) }
1741 .as_ref()
1742 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1743}
1744
1745#[no_mangle]
1766pub unsafe extern "C" fn xmlACatalogResolvePublic(
1767 catal: *mut XmlCatalogHandle,
1768 pubID: *const xmlChar,
1769) -> *mut xmlChar {
1770 if catal.is_null() || pubID.is_null() {
1771 return ptr::null_mut();
1772 }
1773 let entries = unsafe { &(*catal).entries };
1774 let b = xmlstr_to_bytes(pubID);
1775 unsafe { resolve_public_entries(entries, b) }
1776 .as_ref()
1777 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1778}
1779
1780#[no_mangle]
1801pub unsafe extern "C" fn xmlACatalogResolveURI(
1802 catal: *mut XmlCatalogHandle,
1803 URI: *const xmlChar,
1804) -> *mut xmlChar {
1805 if catal.is_null() || URI.is_null() {
1806 return ptr::null_mut();
1807 }
1808 let entries = unsafe { &(*catal).entries };
1809 let b = xmlstr_to_bytes(URI);
1810 unsafe { resolve_uri_entries(entries, b) }
1811 .as_ref()
1812 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1813}
1814
1815#[no_mangle]
1832pub unsafe extern "C" fn xmlCatalogIsEmpty(catal: *mut XmlCatalogHandle) -> c_int {
1833 if catal.is_null() {
1834 return 1;
1835 }
1836 unsafe { (*catal).children.is_empty() as c_int }
1840}
1841
1842#[no_mangle]
1848pub unsafe extern "C" fn xmlACatalogDump(catal: *mut XmlCatalogHandle, out: *mut libc::FILE) {
1849 if catal.is_null() || out.is_null() {
1850 return;
1851 }
1852 let entries = unsafe { &(*catal).entries };
1853 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");
1854 for e in entries {
1855 match e {
1856 CatalogEntry::Public { public_id, uri } => {
1857 text.push_str(&format!(
1858 " <public publicId=\"{}\" uri=\"{}\"/>\n",
1859 String::from_utf8_lossy(public_id),
1860 String::from_utf8_lossy(uri)
1861 ));
1862 }
1863 CatalogEntry::System { system_id, uri } => {
1864 text.push_str(&format!(
1865 " <system systemId=\"{}\" uri=\"{}\"/>\n",
1866 String::from_utf8_lossy(system_id),
1867 String::from_utf8_lossy(uri)
1868 ));
1869 }
1870 CatalogEntry::RewriteSystem { prefix, rewrite } => {
1871 text.push_str(&format!(
1872 " <rewriteSystem systemIdStartString=\"{}\" rewritePrefix=\"{}\"/>\n",
1873 String::from_utf8_lossy(prefix),
1874 String::from_utf8_lossy(rewrite)
1875 ));
1876 }
1877 CatalogEntry::RewriteURI { prefix, rewrite } => {
1878 text.push_str(&format!(
1879 " <rewriteURI uriStartString=\"{}\" rewritePrefix=\"{}\"/>\n",
1880 String::from_utf8_lossy(prefix),
1881 String::from_utf8_lossy(rewrite)
1882 ));
1883 }
1884 _ => {}
1885 }
1886 }
1887 text.push_str("</catalog>\n");
1888 let bytes = text.into_bytes();
1889 unsafe {
1890 libc::fwrite(bytes.as_ptr() as *const libc::c_void, 1, bytes.len(), out);
1891 }
1892}
1893
1894#[no_mangle]
1906pub unsafe extern "C" fn xmlInitializeCatalog() {
1907 crate::xml::catalog::init();
1908}
1909
1910#[no_mangle]
1922pub unsafe extern "C" fn xmlCatalogDumpDoc() -> *mut _xmlDoc {
1923 unsafe { dump_doc() }
1924}
1925
1926#[no_mangle]
1939pub unsafe extern "C" fn xmlCatalogSetDebug(level: c_int) -> c_int {
1940 let old = CATALOG_DEBUG.load(std::sync::atomic::Ordering::Relaxed);
1941 if level <= 0 {
1942 CATALOG_DEBUG.store(0, std::sync::atomic::Ordering::Relaxed);
1943 } else {
1944 CATALOG_DEBUG.store(level, std::sync::atomic::Ordering::Relaxed);
1945 }
1946 old
1947}
1948
1949#[no_mangle]
1962pub unsafe extern "C" fn xmlCatalogSetDefaultPrefer(prefer: c_int) -> c_int {
1963 let old = CATALOG_PREFER.load(std::sync::atomic::Ordering::Relaxed);
1964 if prefer == 0 {
1965 return old;
1966 }
1967 CATALOG_PREFER.store(prefer, std::sync::atomic::Ordering::Relaxed);
1968 old
1969}
1970
1971#[no_mangle]
1993pub unsafe extern "C" fn xmlCatalogResolve(
1994 pubID: *const xmlChar,
1995 sysID: *const xmlChar,
1996) -> *mut xmlChar {
1997 if !sysID.is_null() {
1998 let r = unsafe { resolve_system(sysID) };
1999 if !r.is_null() {
2000 return r;
2001 }
2002 }
2003 if !pubID.is_null() {
2004 return unsafe { resolve_public(pubID) };
2005 }
2006 ptr::null_mut()
2007}
2008
2009#[no_mangle]
2027pub unsafe extern "C" fn xmlCatalogGetSystem(sysID: *const xmlChar) -> *const xmlChar {
2028 unsafe { resolve_system(sysID) }
2029}
2030#[no_mangle]
2047pub unsafe extern "C" fn xmlCatalogGetPublic(pubID: *const xmlChar) -> *const xmlChar {
2048 unsafe { resolve_public(pubID) }
2049}
2050
2051#[no_mangle]
2057pub unsafe extern "C" fn xmlParseCatalogFile(filename: *const c_char) -> *mut _xmlDoc {
2058 if filename.is_null() {
2059 return ptr::null_mut();
2060 }
2061 unsafe { dump_doc() }
2062}
2063
2064#[no_mangle]
2087pub unsafe extern "C" fn xmlCatalogAddLocal(
2088 catalogs: *mut c_void,
2089 URL: *const xmlChar,
2090) -> *mut c_void {
2091 if URL.is_null() {
2092 return catalogs;
2093 }
2094 let list: *mut Vec<CatalogEntry> = if catalogs.is_null() {
2095 Box::into_raw(Box::new(Vec::<CatalogEntry>::new()))
2096 } else {
2097 catalogs as *mut Vec<CatalogEntry>
2098 };
2099 let url = xmlstr_to_bytes(URL);
2100 let url_str = String::from_utf8_lossy(url).into_owned();
2101 let entries = unsafe { &mut *list };
2102 if let Some(data) = read_file_bytes(&url_str) {
2103 let mut temp = Vec::new();
2104 load_catalog_data(&url_str, &data, &mut temp);
2105 entries.extend(temp);
2106 }
2107 list as *mut c_void
2108}
2109
2110#[no_mangle]
2116pub unsafe extern "C" fn xmlCatalogFreeLocal(catalogs: *mut c_void) {
2117 if !catalogs.is_null() {
2118 unsafe { drop(Box::from_raw(catalogs as *mut Vec<CatalogEntry>)) };
2119 }
2120}
2121
2122#[no_mangle]
2143pub unsafe extern "C" fn xmlCatalogLocalResolve(
2144 catalogs: *mut c_void,
2145 pubID: *const xmlChar,
2146 sysID: *const xmlChar,
2147) -> *mut xmlChar {
2148 if catalogs.is_null() {
2149 return ptr::null_mut();
2150 }
2151 let entries = unsafe { &*(catalogs as *const Vec<CatalogEntry>) };
2152 if !sysID.is_null() {
2154 let b = xmlstr_to_bytes(sysID);
2155 if let Some(r) = unsafe { resolve_system_entries(entries, b) } {
2156 return bytes_to_xmlstr(&r);
2157 }
2158 }
2159 if !pubID.is_null() {
2160 let b = xmlstr_to_bytes(pubID);
2161 if let Some(r) = unsafe { resolve_public_entries(entries, b) } {
2162 return bytes_to_xmlstr(&r);
2163 }
2164 }
2165 ptr::null_mut()
2166}
2167
2168#[no_mangle]
2189pub unsafe extern "C" fn xmlCatalogLocalResolveURI(
2190 catalogs: *mut c_void,
2191 URI: *const xmlChar,
2192) -> *mut xmlChar {
2193 if catalogs.is_null() || URI.is_null() {
2194 return ptr::null_mut();
2195 }
2196 let entries = unsafe { &*(catalogs as *const Vec<CatalogEntry>) };
2197 let b = xmlstr_to_bytes(URI);
2198 unsafe { resolve_uri_entries(entries, b) }
2199 .as_ref()
2200 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
2201}
2202
2203#[cfg(test)]
2208mod tests {
2209 use super::*;
2210 use crate::abi::allocator::xmlFreeImpl;
2211 use crate::xml::string::xmlstr_to_bytes;
2212
2213 use std::sync::Mutex;
2214
2215 static CATALOG_TEST_MUTEX: Mutex<()> = Mutex::new(());
2224
2225 unsafe fn to_xmlstr(s: &[u8]) -> *const xmlChar {
2227 let ptr = bytes_to_xmlstr(s);
2228 ptr as *const xmlChar
2229 }
2230
2231 unsafe fn to_xmlstr_str(s: &str) -> *const xmlChar {
2233 to_xmlstr(s.as_bytes())
2234 }
2235
2236 unsafe fn free_xmlstr(ptr: *const xmlChar) {
2237 if !ptr.is_null() {
2238 xmlFreeImpl(ptr as *mut c_void);
2239 }
2240 }
2241
2242 fn setup() -> std::sync::MutexGuard<'static, ()> {
2249 let guard = CATALOG_TEST_MUTEX.lock().unwrap();
2250 cleanup();
2251 init();
2252 set_defaults(XML_CATA_ALLOW_ALL);
2254 guard
2255 }
2256
2257 fn teardown(_guard: std::sync::MutexGuard<'static, ()>) {
2258 cleanup();
2259 }
2261
2262 #[test]
2265 fn test_resolve_public_basic() {
2266 let _guard = setup();
2267 unsafe {
2268 let type_ = to_xmlstr_str("public");
2270 let pub_id = to_xmlstr_str("-//OASIS//DTD DocBook XML V4.2//EN");
2271 let uri = to_xmlstr_str("http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd");
2272 assert_eq!(add(type_, pub_id, uri), 0);
2273
2274 let result = resolve_public(pub_id);
2276 assert!(!result.is_null());
2277 assert_eq!(
2278 xmlstr_to_bytes(result),
2279 b"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
2280 );
2281 xmlFreeImpl(result as *mut c_void);
2282
2283 let unknown = to_xmlstr_str("-//Unknown//DTD Unknown//EN");
2285 assert!(resolve_public(unknown).is_null());
2286 free_xmlstr(unknown);
2287
2288 free_xmlstr(type_);
2289 free_xmlstr(pub_id);
2290 free_xmlstr(uri);
2291 teardown(_guard);
2292 }
2293 }
2294
2295 #[test]
2298 fn test_resolve_system_basic() {
2299 let _guard = setup();
2300 unsafe {
2301 let type_ = to_xmlstr_str("system");
2302 let sys_id = to_xmlstr_str("http://example.com/foo.dtd");
2303 let uri = to_xmlstr_str("/local/foo.dtd");
2304 assert_eq!(add(type_, sys_id, uri), 0);
2305
2306 let result = resolve_system(sys_id);
2307 assert!(!result.is_null());
2308 assert_eq!(xmlstr_to_bytes(result), b"/local/foo.dtd");
2309 xmlFreeImpl(result as *mut c_void);
2310
2311 free_xmlstr(type_);
2312 free_xmlstr(sys_id);
2313 free_xmlstr(uri);
2314 teardown(_guard);
2315 }
2316 }
2317
2318 #[test]
2321 fn test_resolve_uri_basic() {
2322 let _guard = setup();
2323 unsafe {
2324 let type_ = to_xmlstr_str("system");
2326 let sys_id = to_xmlstr_str("http://example.com/resource.xml");
2327 let uri = to_xmlstr_str("/local/resource.xml");
2328 assert_eq!(add(type_, sys_id, uri), 0);
2329
2330 let result = resolve_uri(sys_id);
2331 assert!(!result.is_null());
2332 assert_eq!(xmlstr_to_bytes(result), b"/local/resource.xml");
2333 xmlFreeImpl(result as *mut c_void);
2334
2335 free_xmlstr(type_);
2336 free_xmlstr(sys_id);
2337 free_xmlstr(uri);
2338 teardown(_guard);
2339 }
2340 }
2341
2342 #[test]
2345 fn test_rewrite_system() {
2346 let _guard = setup();
2347 unsafe {
2348 let type_ = to_xmlstr_str("rewriteSystem");
2349 let prefix = to_xmlstr_str("http://example.com/old/");
2350 let rewrite = to_xmlstr_str("http://mirror.example.com/new/");
2351 assert_eq!(add(type_, prefix, rewrite), 0);
2352
2353 let sys_id = to_xmlstr_str("http://example.com/old/path/file.xml");
2354 let result = resolve_system(sys_id);
2355 assert!(!result.is_null());
2356 assert_eq!(
2357 xmlstr_to_bytes(result),
2358 b"http://mirror.example.com/new/path/file.xml"
2359 );
2360 xmlFreeImpl(result as *mut c_void);
2361
2362 free_xmlstr(type_);
2363 free_xmlstr(prefix);
2364 free_xmlstr(rewrite);
2365 free_xmlstr(sys_id);
2366 teardown(_guard);
2367 }
2368 }
2369
2370 #[test]
2373 fn test_rewrite_uri() {
2374 let _guard = setup();
2375 unsafe {
2376 let type_ = to_xmlstr_str("rewriteURI");
2377 let prefix = to_xmlstr_str("http://example.com/old/");
2378 let rewrite = to_xmlstr_str("http://mirror.example.com/new/");
2379 assert_eq!(add(type_, prefix, rewrite), 0);
2380
2381 let uri = to_xmlstr_str("http://example.com/old/path/file.xml");
2382 let result = resolve_uri(uri);
2383 assert!(!result.is_null());
2384 assert_eq!(
2385 xmlstr_to_bytes(result),
2386 b"http://mirror.example.com/new/path/file.xml"
2387 );
2388 xmlFreeImpl(result as *mut c_void);
2389
2390 free_xmlstr(type_);
2391 free_xmlstr(prefix);
2392 free_xmlstr(rewrite);
2393 free_xmlstr(uri);
2394 teardown(_guard);
2395 }
2396 }
2397
2398 #[test]
2401 fn test_remove_entries() {
2402 let _guard = setup();
2403 unsafe {
2404 let type_ = to_xmlstr_str("public");
2405 let pub_id = to_xmlstr_str("-//TEST//PUBLIC//EN");
2406 let uri = to_xmlstr_str("test.dtd");
2407 assert_eq!(add(type_, pub_id, uri), 0);
2408
2409 assert!(!resolve_public(pub_id).is_null());
2411
2412 assert_eq!(remove(pub_id), 1);
2414
2415 assert!(resolve_public(pub_id).is_null());
2417
2418 free_xmlstr(type_);
2419 free_xmlstr(pub_id);
2420 free_xmlstr(uri);
2421 teardown(_guard);
2422 }
2423 }
2424
2425 #[test]
2428 fn test_catalog_defaults() {
2429 let _guard = setup();
2430
2431 assert_eq!(get_defaults(), XML_CATA_ALLOW_ALL);
2432
2433 set_defaults(XML_CATA_ALLOW_NONE);
2434 assert_eq!(get_defaults(), XML_CATA_ALLOW_NONE);
2435
2436 set_defaults(XML_CATA_ALLOW_GLOBAL);
2437 assert_eq!(get_defaults(), XML_CATA_ALLOW_GLOBAL);
2438
2439 set_defaults(XML_CATA_ALLOW_ALL);
2440 assert_eq!(get_defaults(), XML_CATA_ALLOW_ALL);
2441
2442 teardown(_guard);
2443 }
2444
2445 #[test]
2448 fn test_parse_xml_catalog_in_memory() {
2449 let _guard = setup();
2450 {
2451 let catalog_xml = br#"<?xml version="1.0"?>
2452<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
2453<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
2454 <public publicId="-//OASIS//DTD DocBook XML V4.2//EN" uri="http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"/>
2455 <system systemId="http://example.com/foo.dtd" uri="/local/foo.dtd"/>
2456 <rewriteSystem systemIdStartString="http://example.com/old/" rewritePrefix="http://mirror.example.com/new/"/>
2457 <rewriteURI uriStartString="http://example.com/old/" rewritePrefix="http://mirror.example.com/new/"/>
2458</catalog>"#;
2459
2460 let mut entries = Vec::new();
2462 parse_xml_catalog(catalog_xml, &mut entries);
2463 assert_eq!(entries.len(), 4);
2464
2465 match &entries[0] {
2467 CatalogEntry::Public { public_id, uri } => {
2468 assert_eq!(public_id.as_slice(), b"-//OASIS//DTD DocBook XML V4.2//EN");
2469 assert_eq!(
2470 uri.as_slice(),
2471 b"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
2472 );
2473 }
2474 _ => panic!("Expected Public entry"),
2475 }
2476
2477 match &entries[1] {
2479 CatalogEntry::System { system_id, uri } => {
2480 assert_eq!(system_id.as_slice(), b"http://example.com/foo.dtd");
2481 assert_eq!(uri.as_slice(), b"/local/foo.dtd");
2482 }
2483 _ => panic!("Expected System entry"),
2484 }
2485
2486 match &entries[2] {
2488 CatalogEntry::RewriteSystem { prefix, rewrite } => {
2489 assert_eq!(prefix.as_slice(), b"http://example.com/old/");
2490 assert_eq!(rewrite.as_slice(), b"http://mirror.example.com/new/");
2491 }
2492 _ => panic!("Expected RewriteSystem entry"),
2493 }
2494
2495 match &entries[3] {
2497 CatalogEntry::RewriteURI { prefix, rewrite } => {
2498 assert_eq!(prefix.as_slice(), b"http://example.com/old/");
2499 assert_eq!(rewrite.as_slice(), b"http://mirror.example.com/new/");
2500 }
2501 _ => panic!("Expected RewriteURI entry"),
2502 }
2503
2504 teardown(_guard);
2505 }
2506 }
2507
2508 #[test]
2511 fn test_parse_sgml_catalog() {
2512 let _guard = setup();
2513 {
2514 let sgml_data = br#"-- SGML catalog
2515PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "docbookx.dtd"
2516SYSTEM "http://example.com/foo.dtd" "/local/foo.dtd"
2517URI "http://example.com/resource" "/local/resource"
2518"#;
2519
2520 let mut entries = Vec::new();
2521 parse_sgml_catalog(sgml_data, &mut entries);
2522 assert_eq!(entries.len(), 3);
2523
2524 match &entries[0] {
2526 CatalogEntry::Public { public_id, uri } => {
2527 assert_eq!(public_id.as_slice(), b"-//OASIS//DTD DocBook XML V4.2//EN");
2528 assert_eq!(uri.as_slice(), b"docbookx.dtd");
2529 }
2530 _ => panic!("Expected Public entry"),
2531 }
2532
2533 match &entries[1] {
2535 CatalogEntry::System { system_id, uri } => {
2536 assert_eq!(system_id.as_slice(), b"http://example.com/foo.dtd");
2537 assert_eq!(uri.as_slice(), b"/local/foo.dtd");
2538 }
2539 _ => panic!("Expected System entry"),
2540 }
2541
2542 match &entries[2] {
2544 CatalogEntry::System { system_id, uri } => {
2545 assert_eq!(system_id.as_slice(), b"http://example.com/resource");
2546 assert_eq!(uri.as_slice(), b"/local/resource");
2547 }
2548 _ => panic!("Expected System entry for URI"),
2549 }
2550
2551 teardown(_guard);
2552 }
2553 }
2554
2555 #[test]
2558 fn test_resolution_precedence() {
2559 let _guard = setup();
2560 unsafe {
2561 let type_sys = to_xmlstr_str("system");
2563 let sys_id = to_xmlstr_str("http://example.com/target.xml");
2564 let uri_direct = to_xmlstr_str("/direct/uri.xml");
2565 assert_eq!(add(type_sys, sys_id, uri_direct), 0);
2566
2567 let type_rw = to_xmlstr_str("rewriteSystem");
2569 let prefix = to_xmlstr_str("http://example.com/");
2570 let rewrite = to_xmlstr_str("/rewrite/");
2571 assert_eq!(add(type_rw, prefix, rewrite), 0);
2572
2573 let result = resolve_system(sys_id);
2575 assert!(!result.is_null());
2576 assert_eq!(xmlstr_to_bytes(result), b"/direct/uri.xml");
2577 xmlFreeImpl(result as *mut c_void);
2578
2579 free_xmlstr(type_sys);
2580 free_xmlstr(sys_id);
2581 free_xmlstr(uri_direct);
2582 free_xmlstr(type_rw);
2583 free_xmlstr(prefix);
2584 free_xmlstr(rewrite);
2585 teardown(_guard);
2586 }
2587 }
2588
2589 #[test]
2592 fn test_convert_sgml_to_xml() {
2593 let _guard = setup();
2594 unsafe {
2595 let type_ = to_xmlstr_str("public");
2596 let pub_id = to_xmlstr_str("-//TEST//PUBLIC//EN");
2597 let uri = to_xmlstr_str("test.dtd");
2598 assert_eq!(add(type_, pub_id, uri), 0);
2599
2600 let doc = convert();
2601 assert!(!doc.is_null());
2602
2603 let root = crate::xml::tree::doc_get_root_element(doc);
2605 assert!(!root.is_null());
2606 let root_name = crate::xml::string::xmlstr_to_bytes((*root).name);
2607 assert_eq!(root_name, b"catalog");
2608
2609 let child = (*root).children;
2611 assert!(!child.is_null());
2612 let child_name = crate::xml::string::xmlstr_to_bytes((*child).name);
2613 assert_eq!(child_name, b"public");
2614
2615 crate::xml::tree::free_doc(doc);
2616 free_xmlstr(type_);
2617 free_xmlstr(pub_id);
2618 free_xmlstr(uri);
2619 teardown(_guard);
2620 }
2621 }
2622
2623 #[test]
2626 fn test_catalog_disallowed() {
2627 let _guard = setup();
2628 unsafe {
2629 let type_ = to_xmlstr_str("system");
2631 let sys_id = to_xmlstr_str("http://example.com/test.dtd");
2632 let uri = to_xmlstr_str("/local/test.dtd");
2633 add(type_, sys_id, uri);
2634
2635 set_defaults(XML_CATA_ALLOW_NONE);
2637
2638 assert!(resolve_system(sys_id).is_null());
2640 assert!(resolve_public(sys_id).is_null());
2641 assert!(resolve_uri(sys_id).is_null());
2642
2643 set_defaults(XML_CATA_ALLOW_ALL);
2644 free_xmlstr(type_);
2645 free_xmlstr(sys_id);
2646 free_xmlstr(uri);
2647 teardown(_guard);
2648 }
2649 }
2650
2651 #[test]
2654 fn test_init_cleanup() {
2655 let _guard = CATALOG_TEST_MUTEX.lock().unwrap();
2656 cleanup();
2657 assert!(!CATALOG_STATE.read().initialized);
2658
2659 init();
2660 assert!(CATALOG_STATE.read().initialized);
2661
2662 cleanup();
2663 assert!(!CATALOG_STATE.read().initialized);
2664 }
2665
2666 #[test]
2669 fn test_parse_xml_catalog_group() {
2670 let catalog_xml = br#"<?xml version="1.0"?>
2671<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
2672 <group>
2673 <public publicId="-//GROUP//PUBLIC//EN" uri="group.dtd"/>
2674 <system systemId="http://group.example.com/" uri="/group/"/>
2675 </group>
2676</catalog>"#;
2677
2678 let mut entries = Vec::new();
2679 parse_xml_catalog(catalog_xml, &mut entries);
2680 assert_eq!(entries.len(), 2);
2681
2682 match &entries[0] {
2683 CatalogEntry::Public { public_id, .. } => {
2684 assert_eq!(public_id.as_slice(), b"-//GROUP//PUBLIC//EN");
2685 }
2686 _ => panic!("Expected Public entry"),
2687 }
2688
2689 match &entries[1] {
2690 CatalogEntry::System { system_id, .. } => {
2691 assert_eq!(system_id.as_slice(), b"http://group.example.com/");
2692 }
2693 _ => panic!("Expected System entry"),
2694 }
2695 }
2696
2697 #[test]
2700 fn test_multiple_entries() {
2701 let _guard = setup();
2702 unsafe {
2703 let t = to_xmlstr_str("public");
2705 let id1 = to_xmlstr_str("-//A//PUBLIC//EN");
2706 let uri1 = to_xmlstr_str("a.dtd");
2707 let id2 = to_xmlstr_str("-//B//PUBLIC//EN");
2708 let uri2 = to_xmlstr_str("b.dtd");
2709
2710 assert_eq!(add(t, id1, uri1), 0);
2711 assert_eq!(add(t, id2, uri2), 0);
2712
2713 let r1 = resolve_public(id1);
2714 assert!(!r1.is_null());
2715 assert_eq!(xmlstr_to_bytes(r1), b"a.dtd");
2716 xmlFreeImpl(r1 as *mut c_void);
2717
2718 let r2 = resolve_public(id2);
2719 assert!(!r2.is_null());
2720 assert_eq!(xmlstr_to_bytes(r2), b"b.dtd");
2721 xmlFreeImpl(r2 as *mut c_void);
2722
2723 free_xmlstr(t);
2724 free_xmlstr(id1);
2725 free_xmlstr(uri1);
2726 free_xmlstr(id2);
2727 free_xmlstr(uri2);
2728 teardown(_guard);
2729 }
2730 }
2731
2732 #[test]
2735 fn test_longest_prefix_wins() {
2736 let _guard = setup();
2737 unsafe {
2738 let t = to_xmlstr_str("rewriteSystem");
2739 let p1 = to_xmlstr_str("http://example.com/");
2740 let r1 = to_xmlstr_str("/general/");
2741 let p2 = to_xmlstr_str("http://example.com/specific/");
2742 let r2 = to_xmlstr_str("/specific/");
2743
2744 add(t, p1, r1);
2745 add(t, p2, r2);
2746
2747 let sys_id = to_xmlstr_str("http://example.com/specific/file.xml");
2748 let result = resolve_system(sys_id);
2749 assert!(!result.is_null());
2750 assert_eq!(xmlstr_to_bytes(result), b"/specific/file.xml");
2751 xmlFreeImpl(result as *mut c_void);
2752
2753 free_xmlstr(t);
2754 free_xmlstr(p1);
2755 free_xmlstr(r1);
2756 free_xmlstr(p2);
2757 free_xmlstr(r2);
2758 free_xmlstr(sys_id);
2759 teardown(_guard);
2760 }
2761 }
2762}
2763
2764#[cfg(test)]
2769mod c_abi_tests {
2770 use super::*;
2771 use crate::abi::allocator::xmlFreeImpl;
2772
2773 fn cstr(s: &[u8]) -> *const xmlChar {
2774 s.as_ptr() as *const xmlChar
2775 }
2776
2777 #[test]
2778 fn test_new_free_catalog() {
2779 unsafe {
2780 let h = xmlNewCatalog(0);
2781 assert!(!h.is_null());
2782 assert_eq!(xmlCatalogIsEmpty(h), 1);
2783 xmlFreeCatalog(h);
2784 xmlFreeCatalog(ptr::null_mut());
2785 }
2786 }
2787
2788 #[test]
2789 fn test_acatalog_add_resolve_remove() {
2790 unsafe {
2791 let h = xmlNewCatalog(0);
2794 assert!(!h.is_null());
2795 assert_eq!(
2796 xmlACatalogAdd(
2797 h,
2798 cstr(b"system\0"),
2799 cstr(b"http://x\0"),
2800 cstr(b"file:///x\0")
2801 ),
2802 -1
2803 );
2804 xmlFreeCatalog(h);
2805
2806 let h = xmlNewCatalog(0);
2809 assert!(!h.is_null());
2810 (*h).entries.push(CatalogEntry::System {
2811 system_id: b"http://example.com/foo\0".to_vec(),
2812 uri: b"file:///tmp/foo.xml\0".to_vec(),
2813 });
2814 assert_eq!(
2815 xmlACatalogAdd(
2816 h,
2817 cstr(b"system\0"),
2818 cstr(b"http://example.com/foo\0"),
2819 cstr(b"file:///tmp/foo.xml\0")
2820 ),
2821 0
2822 );
2823 assert_eq!(xmlCatalogIsEmpty(h), 0);
2824 let r = xmlACatalogResolveSystem(h, cstr(b"http://example.com/foo\0"));
2826 assert!(!r.is_null());
2827 let bytes = xmlstr_to_bytes(r);
2828 assert_eq!(bytes, b"file:///tmp/foo.xml");
2829 xmlFreeImpl(r as *mut libc::c_void);
2830 let r2 = xmlACatalogResolveURI(h, cstr(b"http://example.com/foo\0"));
2832 assert!(!r2.is_null());
2833 xmlFreeImpl(r2 as *mut libc::c_void);
2834 assert_eq!(
2836 xmlACatalogAdd(h, cstr(b"bogus\0"), cstr(b"a\0"), cstr(b"b\0")),
2837 -1
2838 );
2839 assert_eq!(xmlACatalogRemove(h, cstr(b"http://example.com/foo\0")), 0);
2843 assert_eq!(xmlCatalogIsEmpty(h), 1);
2844 xmlFreeCatalog(h);
2845 }
2846 }
2847
2848 #[test]
2849 fn test_acatalog_public_and_rewrite() {
2850 unsafe {
2851 let h = xmlNewCatalog(0);
2852 assert!(!h.is_null());
2853 (*h).entries.push(CatalogEntry::Public {
2855 public_id: b"-//OASIS//DTD X//EN\0".to_vec(),
2856 uri: b"file:///dtd/x.dtd\0".to_vec(),
2857 });
2858 assert_eq!(
2859 xmlACatalogAdd(
2860 h,
2861 cstr(b"public\0"),
2862 cstr(b"-//OASIS//DTD X//EN\0"),
2863 cstr(b"file:///dtd/x.dtd\0")
2864 ),
2865 0
2866 );
2867 assert_eq!(
2868 xmlACatalogAdd(
2869 h,
2870 cstr(b"rewriteSystem\0"),
2871 cstr(b"http://old/\0"),
2872 cstr(b"http://new/\0")
2873 ),
2874 0
2875 );
2876 let r = xmlACatalogResolvePublic(h, cstr(b"-//OASIS//DTD X//EN\0"));
2877 assert!(!r.is_null());
2878 assert_eq!(xmlstr_to_bytes(r), b"file:///dtd/x.dtd");
2879 xmlFreeImpl(r as *mut libc::c_void);
2880 let r2 = xmlACatalogResolveSystem(h, cstr(b"http://old/foo.xml\0"));
2881 assert!(!r2.is_null());
2882 assert_eq!(xmlstr_to_bytes(r2), b"http://new/foo.xml");
2883 xmlFreeImpl(r2 as *mut libc::c_void);
2884 xmlFreeCatalog(h);
2885 }
2886 }
2887
2888 #[test]
2889 fn test_catalog_set_debug_and_prefer() {
2890 unsafe {
2891 assert_eq!(xmlCatalogSetDefaultPrefer(1), 1);
2894 assert_eq!(xmlCatalogSetDefaultPrefer(2), 1);
2895 assert_eq!(xmlCatalogSetDefaultPrefer(0), 2);
2896 assert_eq!(xmlCatalogSetDefaultPrefer(1), 2);
2897 assert_eq!(xmlCatalogSetDebug(0), 0);
2898 assert_eq!(xmlCatalogSetDebug(7), 0);
2899 assert_eq!(xmlCatalogSetDebug(0), 7);
2900 }
2901 }
2902
2903 #[test]
2904 fn test_catalog_local_resolve() {
2905 unsafe {
2906 assert!(xmlCatalogLocalResolve(ptr::null_mut(), cstr(b"x\0"), cstr(b"y\0")).is_null());
2908 assert!(xmlCatalogLocalResolveURI(ptr::null_mut(), cstr(b"x\0")).is_null());
2909 xmlCatalogFreeLocal(ptr::null_mut());
2910 }
2911 }
2912
2913 #[test]
2914 fn test_catalog_resolve_global_null() {
2915 unsafe {
2916 assert!(xmlCatalogResolve(ptr::null(), ptr::null()).is_null());
2917 }
2918 }
2919}