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 is_initialized() -> bool {
771 CATALOG_STATE.read().initialized
772}
773
774pub(crate) fn cleanup() {
775 let mut state = CATALOG_STATE.write();
776 state.clear();
777 state.initialized = false;
778}
779
780pub(crate) fn load_catalog(catalogs: *const c_char) -> *mut c_void {
794 if catalogs.is_null() {
795 return ptr::null_mut();
796 }
797
798 let catalogs_str = unsafe { CStr::from_ptr(catalogs) };
799 let catalogs_str = catalogs_str.to_str().unwrap_or("");
800
801 let mut state = CATALOG_STATE.write();
802
803 if !state.initialized {
805 drop(state);
806 init();
807 state = CATALOG_STATE.write();
808 }
809
810 let count_before = state.catalogs.len();
811 load_catalog_list(catalogs_str, &mut state);
812
813 if state.catalogs.len() > count_before {
814 (state.catalogs.len() as isize) as *mut c_void
816 } else {
817 ptr::null_mut()
818 }
819}
820
821const fn catalog_allowed(state: &CatalogState) -> bool {
827 let allow = state.allow;
828 match allow {
829 XML_CATA_ALLOW_NONE => false,
830 XML_CATA_ALLOW_GLOBAL | XML_CATA_ALLOW_DOCUMENT | XML_CATA_ALLOW_ALL => true,
831 _ => false,
832 }
833}
834
835unsafe fn resolve_public_entries(entries: &[CatalogEntry], pub_id_bytes: &[u8]) -> Option<Vec<u8>> {
838 for entry in entries {
840 if let CatalogEntry::Public { public_id, uri } = entry {
841 if public_id.as_slice() == pub_id_bytes {
842 return Some(uri.clone());
843 }
844 }
845 }
846
847 let mut best_match: Option<Vec<u8>> = None;
849 let mut best_prefix_len: usize = 0;
850
851 for entry in entries {
852 if let CatalogEntry::DelegatePublic { prefix, catalog } = entry {
853 if pub_id_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
854 best_prefix_len = prefix.len();
855 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
857 let mut temp_entries = Vec::new();
858 parse_xml_catalog(&delegated_data, &mut temp_entries);
859 for temp_entry in &temp_entries {
861 if let CatalogEntry::Public { public_id: dp, uri } = temp_entry {
862 if dp.as_slice() == pub_id_bytes {
863 best_match = Some(uri.clone());
864 }
865 }
866 }
867 }
868 }
869 }
870 }
871
872 best_match
873}
874
875pub(crate) unsafe fn resolve_public(pub_id: *const xmlChar) -> *mut xmlChar {
886 if pub_id.is_null() {
887 return ptr::null_mut();
888 }
889
890 let state = CATALOG_STATE.read();
891 if !catalog_allowed(&state) {
892 return ptr::null_mut();
893 }
894
895 let pub_id_bytes = xmlstr_to_bytes(pub_id);
896 unsafe { resolve_public_entries(&state.entries, pub_id_bytes) }
897 .as_ref()
898 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
899}
900
901unsafe fn resolve_system_entries(entries: &[CatalogEntry], sys_id_bytes: &[u8]) -> Option<Vec<u8>> {
903 for entry in entries {
905 if let CatalogEntry::System { system_id, uri } = entry {
906 if system_id.as_slice() == sys_id_bytes {
907 return Some(uri.clone());
908 }
909 }
910 }
911
912 let mut best_rewrite: Option<Vec<u8>> = None;
914 let mut best_prefix_len: usize = 0;
915
916 for entry in entries {
917 if let CatalogEntry::RewriteSystem { prefix, rewrite } = entry {
918 if sys_id_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
919 best_prefix_len = prefix.len();
920 let suffix = &sys_id_bytes[prefix.len()..];
922 let mut result = rewrite.clone();
923 result.extend_from_slice(suffix);
924 best_rewrite = Some(result);
925 }
926 }
927 }
928
929 if let Some(rewritten) = best_rewrite {
930 return Some(rewritten);
931 }
932
933 for entry in entries {
935 if let CatalogEntry::DelegateSystem { prefix, catalog } = entry {
936 if sys_id_bytes.starts_with(prefix) {
937 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
938 let mut temp_entries = Vec::new();
939 parse_xml_catalog(&delegated_data, &mut temp_entries);
940 for temp_entry in &temp_entries {
941 if let CatalogEntry::System { system_id, uri } = temp_entry {
942 if system_id.as_slice() == sys_id_bytes {
943 return Some(uri.clone());
944 }
945 }
946 }
947 }
948 }
949 }
950 }
951
952 None
953}
954
955pub(crate) unsafe fn resolve_system(sys_id: *const xmlChar) -> *mut xmlChar {
968 if sys_id.is_null() {
969 return ptr::null_mut();
970 }
971
972 let state = CATALOG_STATE.read();
973 if !catalog_allowed(&state) {
974 return ptr::null_mut();
975 }
976
977 let sys_id_bytes = xmlstr_to_bytes(sys_id);
978 unsafe { resolve_system_entries(&state.entries, sys_id_bytes) }
979 .as_ref()
980 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
981}
982
983unsafe fn resolve_uri_entries(entries: &[CatalogEntry], uri_bytes: &[u8]) -> Option<Vec<u8>> {
985 for entry in entries {
987 if let CatalogEntry::System {
988 system_id,
989 uri: sys_uri,
990 } = entry
991 {
992 if system_id.as_slice() == uri_bytes {
993 return Some(sys_uri.clone());
994 }
995 }
996 }
997
998 let mut best_rewrite: Option<Vec<u8>> = None;
1000 let mut best_prefix_len: usize = 0;
1001
1002 for entry in entries {
1003 if let CatalogEntry::RewriteURI { prefix, rewrite } = entry {
1004 if uri_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
1005 best_prefix_len = prefix.len();
1006 let suffix = &uri_bytes[prefix.len()..];
1007 let mut result = rewrite.clone();
1008 result.extend_from_slice(suffix);
1009 best_rewrite = Some(result);
1010 }
1011 }
1012 }
1013
1014 if let Some(rewritten) = best_rewrite {
1015 return Some(rewritten);
1016 }
1017
1018 for entry in entries {
1020 if let CatalogEntry::DelegateURI { prefix, catalog } = entry {
1021 if uri_bytes.starts_with(prefix) {
1022 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
1023 let mut temp_entries = Vec::new();
1024 parse_xml_catalog(&delegated_data, &mut temp_entries);
1025 for temp_entry in &temp_entries {
1026 if let CatalogEntry::System {
1027 system_id,
1028 uri: sys_uri,
1029 } = temp_entry
1030 {
1031 if system_id.as_slice() == uri_bytes {
1032 return Some(sys_uri.clone());
1033 }
1034 }
1035 }
1036 }
1037 }
1038 }
1039 }
1040
1041 None
1042}
1043
1044pub(crate) unsafe fn resolve_uri(uri: *const xmlChar) -> *mut xmlChar {
1057 if uri.is_null() {
1058 return ptr::null_mut();
1059 }
1060
1061 let state = CATALOG_STATE.read();
1062 if !catalog_allowed(&state) {
1063 return ptr::null_mut();
1064 }
1065
1066 let uri_bytes = xmlstr_to_bytes(uri);
1067 unsafe { resolve_uri_entries(&state.entries, uri_bytes) }
1068 .as_ref()
1069 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
1070}
1071
1072pub(crate) fn set_defaults(allow: c_int) {
1087 let mut state = CATALOG_STATE.write();
1088 state.allow = allow;
1089 crate::xml::globals::set_catalog_defaults(allow);
1090}
1091
1092pub(crate) fn get_defaults() -> c_int {
1100 let state = CATALOG_STATE.read();
1101 state.allow
1102}
1103
1104pub(crate) unsafe fn add(
1121 type_: *const xmlChar,
1122 orig: *const xmlChar,
1123 replace: *const xmlChar,
1124) -> c_int {
1125 if type_.is_null() || orig.is_null() || replace.is_null() {
1126 return -1;
1127 }
1128
1129 let type_bytes = xmlstr_to_bytes(type_);
1130 let orig_bytes = xmlstr_to_bytes(orig);
1131 let replace_bytes = xmlstr_to_bytes(replace);
1132
1133 let mut state = CATALOG_STATE.write();
1134
1135 match type_bytes {
1136 b"public" => {
1137 state.entries.push(CatalogEntry::Public {
1138 public_id: orig_bytes.to_vec(),
1139 uri: replace_bytes.to_vec(),
1140 });
1141 0
1142 }
1143 b"system" => {
1144 state.entries.push(CatalogEntry::System {
1145 system_id: orig_bytes.to_vec(),
1146 uri: replace_bytes.to_vec(),
1147 });
1148 0
1149 }
1150 b"rewriteSystem" => {
1151 state.entries.push(CatalogEntry::RewriteSystem {
1152 prefix: orig_bytes.to_vec(),
1153 rewrite: replace_bytes.to_vec(),
1154 });
1155 0
1156 }
1157 b"rewriteURI" => {
1158 state.entries.push(CatalogEntry::RewriteURI {
1159 prefix: orig_bytes.to_vec(),
1160 rewrite: replace_bytes.to_vec(),
1161 });
1162 0
1163 }
1164 b"delegatePublic" => {
1165 state.entries.push(CatalogEntry::DelegatePublic {
1166 prefix: orig_bytes.to_vec(),
1167 catalog: replace_bytes.to_vec(),
1168 });
1169 0
1170 }
1171 b"delegateSystem" => {
1172 state.entries.push(CatalogEntry::DelegateSystem {
1173 prefix: orig_bytes.to_vec(),
1174 catalog: replace_bytes.to_vec(),
1175 });
1176 0
1177 }
1178 b"delegateURI" => {
1179 state.entries.push(CatalogEntry::DelegateURI {
1180 prefix: orig_bytes.to_vec(),
1181 catalog: replace_bytes.to_vec(),
1182 });
1183 0
1184 }
1185 b"nextCatalog" => {
1186 state.entries.push(CatalogEntry::NextCatalog {
1187 catalog: orig_bytes.to_vec(),
1188 });
1189 0
1190 }
1191 _ => -1,
1192 }
1193}
1194
1195pub(crate) unsafe fn remove(value: *const xmlChar) -> c_int {
1206 if value.is_null() {
1207 return -1;
1208 }
1209
1210 let value_bytes = xmlstr_to_bytes(value);
1211 let mut state = CATALOG_STATE.write();
1212
1213 let before = state.entries.len();
1214 state.entries.retain(|entry| match entry {
1215 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != value_bytes,
1216 CatalogEntry::System { system_id, .. } => system_id.as_slice() != value_bytes,
1217 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != value_bytes,
1218 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != value_bytes,
1219 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != value_bytes,
1220 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != value_bytes,
1221 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != value_bytes,
1222 CatalogEntry::NextCatalog { catalog } => catalog.as_slice() != value_bytes,
1223 });
1224
1225 (before - state.entries.len()) as c_int
1226}
1227
1228pub(crate) unsafe fn convert() -> *mut _xmlDoc {
1243 let state = CATALOG_STATE.read();
1244
1245 if state.entries.is_empty() {
1246 return ptr::null_mut();
1247 }
1248
1249 let doc = crate::xml::tree::new_doc(ptr::null_mut());
1251 if doc.is_null() {
1252 return ptr::null_mut();
1253 }
1254
1255 let catalog_name = b"catalog\0" as *const u8 as *const xmlChar;
1257 let root = crate::xml::tree::new_node(ptr::null_mut(), catalog_name);
1258 if root.is_null() {
1259 crate::xml::tree::free_doc(doc);
1260 return ptr::null_mut();
1261 }
1262
1263 let xmlns_name = b"xmlns\0" as *const u8 as *const xmlChar;
1265 let ns_value = b"urn:oasis:names:tc:entity:xmlns:xml:catalog\0" as *const u8 as *const xmlChar;
1266 crate::xml::tree::set_prop(root, xmlns_name, ns_value);
1267
1268 crate::xml::tree::doc_set_root_element(doc, root);
1269
1270 for entry in &state.entries {
1272 let (elem_name, attr1_name, attr1_value, attr2_name, attr2_value) = match entry {
1273 CatalogEntry::Public { public_id, uri } => {
1274 let elem = b"public\0" as *const u8 as *mut xmlChar;
1275 let attr1 = b"publicId\0" as *const u8 as *mut xmlChar;
1276 let val1 = bytes_to_xmlstr(public_id);
1277 let attr2 = b"uri\0" as *const u8 as *mut xmlChar;
1278 let val2 = bytes_to_xmlstr(uri);
1279 (elem, attr1, val1, attr2, val2)
1280 }
1281 CatalogEntry::System { system_id, uri } => {
1282 let elem = b"system\0" as *const u8 as *mut xmlChar;
1283 let attr1 = b"systemId\0" as *const u8 as *mut xmlChar;
1284 let val1 = bytes_to_xmlstr(system_id);
1285 let attr2 = b"uri\0" as *const u8 as *mut xmlChar;
1286 let val2 = bytes_to_xmlstr(uri);
1287 (elem, attr1, val1, attr2, val2)
1288 }
1289 CatalogEntry::RewriteSystem { prefix, rewrite } => {
1290 let elem = b"rewriteSystem\0" as *const u8 as *mut xmlChar;
1291 let attr1 = b"systemIdStartString\0" as *const u8 as *mut xmlChar;
1292 let val1 = bytes_to_xmlstr(prefix);
1293 let attr2 = b"rewritePrefix\0" as *const u8 as *mut xmlChar;
1294 let val2 = bytes_to_xmlstr(rewrite);
1295 (elem, attr1, val1, attr2, val2)
1296 }
1297 CatalogEntry::RewriteURI { prefix, rewrite } => {
1298 let elem = b"rewriteURI\0" as *const u8 as *mut xmlChar;
1299 let attr1 = b"uriStartString\0" as *const u8 as *mut xmlChar;
1300 let val1 = bytes_to_xmlstr(prefix);
1301 let attr2 = b"rewritePrefix\0" as *const u8 as *mut xmlChar;
1302 let val2 = bytes_to_xmlstr(rewrite);
1303 (elem, attr1, val1, attr2, val2)
1304 }
1305 CatalogEntry::DelegatePublic { prefix, catalog } => {
1306 let elem = b"delegatePublic\0" as *const u8 as *mut xmlChar;
1307 let attr1 = b"publicIdStartString\0" as *const u8 as *mut xmlChar;
1308 let val1 = bytes_to_xmlstr(prefix);
1309 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1310 let val2 = bytes_to_xmlstr(catalog);
1311 (elem, attr1, val1, attr2, val2)
1312 }
1313 CatalogEntry::DelegateSystem { prefix, catalog } => {
1314 let elem = b"delegateSystem\0" as *const u8 as *mut xmlChar;
1315 let attr1 = b"systemIdStartString\0" as *const u8 as *mut xmlChar;
1316 let val1 = bytes_to_xmlstr(prefix);
1317 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1318 let val2 = bytes_to_xmlstr(catalog);
1319 (elem, attr1, val1, attr2, val2)
1320 }
1321 CatalogEntry::DelegateURI { prefix, catalog } => {
1322 let elem = b"delegateURI\0" as *const u8 as *mut xmlChar;
1323 let attr1 = b"uriStartString\0" as *const u8 as *mut xmlChar;
1324 let val1 = bytes_to_xmlstr(prefix);
1325 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1326 let val2 = bytes_to_xmlstr(catalog);
1327 (elem, attr1, val1, attr2, val2)
1328 }
1329 CatalogEntry::NextCatalog { catalog } => {
1330 let elem = b"nextCatalog\0" as *const u8 as *mut xmlChar;
1331 let attr1 = b"catalog\0" as *const u8 as *mut xmlChar;
1332 let val1 = bytes_to_xmlstr(catalog);
1333 let attr2 = ptr::null_mut();
1334 let val2 = ptr::null_mut();
1335 (elem, attr1, val1, attr2, val2)
1336 }
1337 };
1338
1339 let child = crate::xml::tree::new_child(root, ptr::null_mut(), elem_name);
1340 if child.is_null() {
1341 if !attr1_value.is_null() {
1343 xmlFreeImpl(attr1_value as *mut c_void);
1344 }
1345 if !attr2_value.is_null() {
1346 xmlFreeImpl(attr2_value as *mut c_void);
1347 }
1348 continue;
1349 }
1350
1351 crate::xml::tree::set_prop(child, attr1_name, attr1_value);
1352 if !attr2_name.is_null() {
1353 crate::xml::tree::set_prop(child, attr2_name, attr2_value);
1354 }
1355
1356 if !attr1_value.is_null() {
1358 xmlFreeImpl(attr1_value as *mut c_void);
1359 }
1360 if !attr2_value.is_null() {
1361 xmlFreeImpl(attr2_value as *mut c_void);
1362 }
1363 }
1364
1365 doc
1366}
1367
1368pub unsafe fn dump_doc() -> *mut _xmlDoc {
1389 let mut doc = convert();
1390 if doc.is_null() {
1391 doc = crate::xml::tree::new_doc(ptr::null_mut());
1393 if doc.is_null() {
1394 return ptr::null_mut();
1395 }
1396 let root =
1397 crate::xml::tree::new_node(ptr::null_mut(), c"catalog".as_ptr() as *const xmlChar);
1398 if root.is_null() {
1399 crate::xml::tree::free_doc(doc);
1400 return ptr::null_mut();
1401 }
1402 crate::xml::tree::set_prop(
1403 root,
1404 c"xmlns".as_ptr() as *const xmlChar,
1405 c"urn:oasis:names:tc:entity:xmlns:xml:catalog".as_ptr() as *const xmlChar,
1406 );
1407 crate::xml::tree::doc_set_root_element(doc, root);
1408 }
1409
1410 let dtd = crate::xml::tree::new_dtd(
1411 doc,
1412 c"catalog".as_ptr() as *const xmlChar,
1413 c"-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN".as_ptr() as *const xmlChar,
1414 c"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd".as_ptr()
1415 as *const xmlChar,
1416 );
1417 if !dtd.is_null() {
1418 (*doc).intSubset = ptr::null_mut();
1419 let dtd_node = dtd as *mut _xmlNode;
1420 let first = (*doc).children;
1421 (*dtd_node).next = first;
1422 (*dtd_node).parent = doc as *mut _xmlNode;
1423 (*dtd_node).doc = doc;
1424 if !first.is_null() {
1425 (*first).prev = dtd_node;
1426 }
1427 (*doc).children = dtd_node;
1428 }
1429 doc
1430}
1431
1432#[derive(Debug)]
1449#[repr(C)]
1450pub struct XmlCatalogHandle {
1451 pub entries: Vec<CatalogEntry>,
1453 pub children: Vec<CatalogEntry>,
1456 pub sgml: c_int,
1459}
1460
1461static CATALOG_DEBUG: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
1463
1464static CATALOG_PREFER: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1);
1467
1468#[no_mangle]
1486pub unsafe extern "C" fn xmlNewCatalog(sgml: c_int) -> *mut XmlCatalogHandle {
1487 let h = Box::new(XmlCatalogHandle {
1488 entries: Vec::new(),
1489 children: Vec::new(),
1490 sgml,
1491 });
1492 Box::into_raw(h)
1493}
1494
1495#[no_mangle]
1501pub unsafe extern "C" fn xmlFreeCatalog(catal: *mut XmlCatalogHandle) {
1502 if !catal.is_null() {
1503 unsafe { drop(Box::from_raw(catal)) };
1504 }
1505}
1506
1507#[no_mangle]
1513pub unsafe extern "C" fn xmlLoadACatalog(filename: *const c_char) -> *mut XmlCatalogHandle {
1514 if filename.is_null() {
1515 return ptr::null_mut();
1516 }
1517 let name = unsafe { CStr::from_ptr(filename) };
1518 let name = name.to_str().unwrap_or("");
1519 let mut entries = Vec::new();
1520 if let Some(data) = read_file_bytes(name) {
1521 load_catalog_data(name, &data, &mut entries);
1522 }
1523 if entries.is_empty() {
1524 return ptr::null_mut();
1525 }
1526 Box::into_raw(Box::new(XmlCatalogHandle {
1527 entries,
1528 children: Vec::new(),
1529 sgml: 0,
1530 }))
1531}
1532
1533#[no_mangle]
1540pub unsafe extern "C" fn xmlLoadSGMLSuperCatalog(filename: *const c_char) -> *mut XmlCatalogHandle {
1541 unsafe { xmlLoadACatalog(filename) }
1542}
1543
1544#[no_mangle]
1552pub unsafe extern "C" fn xmlConvertSGMLCatalog(catal: *mut XmlCatalogHandle) -> c_int {
1553 if catal.is_null() {
1554 return -1;
1555 }
1556 unsafe { (*catal).sgml = 0 };
1557 0
1558}
1559
1560#[no_mangle]
1569pub unsafe extern "C" fn xmlACatalogAdd(
1570 catal: *mut XmlCatalogHandle,
1571 type_: *const xmlChar,
1572 orig: *const xmlChar,
1573 replace: *const xmlChar,
1574) -> c_int {
1575 if catal.is_null() || type_.is_null() || orig.is_null() || replace.is_null() {
1576 return -1;
1577 }
1578 if unsafe { (*catal).entries.is_empty() } {
1584 return -1;
1585 }
1586 let t = xmlstr_to_bytes(type_);
1587 let o = xmlstr_to_bytes(orig).to_vec();
1588 let r = xmlstr_to_bytes(replace).to_vec();
1589 let entry = if t == b"public" {
1590 CatalogEntry::Public {
1591 public_id: o,
1592 uri: r,
1593 }
1594 } else if t == b"system" {
1595 CatalogEntry::System {
1596 system_id: o,
1597 uri: r,
1598 }
1599 } else if t == b"rewriteSystem" {
1600 CatalogEntry::RewriteSystem {
1601 prefix: o,
1602 rewrite: r,
1603 }
1604 } else if t == b"rewriteURI" {
1605 CatalogEntry::RewriteURI {
1606 prefix: o,
1607 rewrite: r,
1608 }
1609 } else if t == b"delegatePublic" {
1610 CatalogEntry::DelegatePublic {
1611 prefix: o,
1612 catalog: r,
1613 }
1614 } else if t == b"delegateSystem" {
1615 CatalogEntry::DelegateSystem {
1616 prefix: o,
1617 catalog: r,
1618 }
1619 } else if t == b"delegateURI" {
1620 CatalogEntry::DelegateURI {
1621 prefix: o,
1622 catalog: r,
1623 }
1624 } else if t == b"nextCatalog" {
1625 CatalogEntry::NextCatalog { catalog: r }
1626 } else {
1627 return -1;
1628 };
1629 unsafe {
1630 (*catal).entries.push(entry.clone());
1631 (*catal).children.push(entry);
1632 };
1633 0
1634}
1635
1636#[no_mangle]
1642pub unsafe extern "C" fn xmlACatalogRemove(
1643 catal: *mut XmlCatalogHandle,
1644 value: *const xmlChar,
1645) -> c_int {
1646 if catal.is_null() || value.is_null() {
1647 return -1;
1648 }
1649 let v = xmlstr_to_bytes(value);
1650 let entries = unsafe { &mut (*catal).entries };
1651 entries.retain(|entry| match entry {
1652 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != v,
1653 CatalogEntry::System { system_id, .. } => system_id.as_slice() != v,
1654 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != v,
1655 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != v,
1656 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != v,
1657 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != v,
1658 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != v,
1659 CatalogEntry::NextCatalog { .. } => true,
1660 });
1661 let children = unsafe { &mut (*catal).children };
1662 children.retain(|entry| match entry {
1663 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != v,
1664 CatalogEntry::System { system_id, .. } => system_id.as_slice() != v,
1665 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != v,
1666 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != v,
1667 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != v,
1668 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != v,
1669 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != v,
1670 CatalogEntry::NextCatalog { .. } => true,
1671 });
1672 0
1678}
1679
1680#[no_mangle]
1692pub unsafe extern "C" fn xmlACatalogResolve(
1693 catal: *mut XmlCatalogHandle,
1694 pubID: *const xmlChar,
1695 sysID: *const xmlChar,
1696) -> *mut xmlChar {
1697 if catal.is_null() {
1698 return ptr::null_mut();
1699 }
1700 let entries = unsafe { &(*catal).entries };
1701 if !sysID.is_null() {
1702 let b = xmlstr_to_bytes(sysID);
1703 if let Some(r) = unsafe { resolve_system_entries(entries, b) } {
1704 return bytes_to_xmlstr(&r);
1705 }
1706 }
1707 if !pubID.is_null() {
1708 let b = xmlstr_to_bytes(pubID);
1709 if let Some(r) = unsafe { resolve_public_entries(entries, b) } {
1710 return bytes_to_xmlstr(&r);
1711 }
1712 }
1713 ptr::null_mut()
1714}
1715
1716#[no_mangle]
1737pub unsafe extern "C" fn xmlACatalogResolveSystem(
1738 catal: *mut XmlCatalogHandle,
1739 sysID: *const xmlChar,
1740) -> *mut xmlChar {
1741 if catal.is_null() || sysID.is_null() {
1742 return ptr::null_mut();
1743 }
1744 let entries = unsafe { &(*catal).entries };
1745 let b = xmlstr_to_bytes(sysID);
1746 unsafe { resolve_system_entries(entries, b) }
1747 .as_ref()
1748 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1749}
1750
1751#[no_mangle]
1772pub unsafe extern "C" fn xmlACatalogResolvePublic(
1773 catal: *mut XmlCatalogHandle,
1774 pubID: *const xmlChar,
1775) -> *mut xmlChar {
1776 if catal.is_null() || pubID.is_null() {
1777 return ptr::null_mut();
1778 }
1779 let entries = unsafe { &(*catal).entries };
1780 let b = xmlstr_to_bytes(pubID);
1781 unsafe { resolve_public_entries(entries, b) }
1782 .as_ref()
1783 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1784}
1785
1786#[no_mangle]
1807pub unsafe extern "C" fn xmlACatalogResolveURI(
1808 catal: *mut XmlCatalogHandle,
1809 URI: *const xmlChar,
1810) -> *mut xmlChar {
1811 if catal.is_null() || URI.is_null() {
1812 return ptr::null_mut();
1813 }
1814 let entries = unsafe { &(*catal).entries };
1815 let b = xmlstr_to_bytes(URI);
1816 unsafe { resolve_uri_entries(entries, b) }
1817 .as_ref()
1818 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1819}
1820
1821#[no_mangle]
1838pub unsafe extern "C" fn xmlCatalogIsEmpty(catal: *mut XmlCatalogHandle) -> c_int {
1839 if catal.is_null() {
1840 return 1;
1841 }
1842 unsafe { (*catal).children.is_empty() as c_int }
1846}
1847
1848#[no_mangle]
1854pub unsafe extern "C" fn xmlACatalogDump(catal: *mut XmlCatalogHandle, out: *mut libc::FILE) {
1855 if catal.is_null() || out.is_null() {
1856 return;
1857 }
1858 let entries = unsafe { &(*catal).entries };
1859 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");
1860 for e in entries {
1861 match e {
1862 CatalogEntry::Public { public_id, uri } => {
1863 text.push_str(&format!(
1864 " <public publicId=\"{}\" uri=\"{}\"/>\n",
1865 String::from_utf8_lossy(public_id),
1866 String::from_utf8_lossy(uri)
1867 ));
1868 }
1869 CatalogEntry::System { system_id, uri } => {
1870 text.push_str(&format!(
1871 " <system systemId=\"{}\" uri=\"{}\"/>\n",
1872 String::from_utf8_lossy(system_id),
1873 String::from_utf8_lossy(uri)
1874 ));
1875 }
1876 CatalogEntry::RewriteSystem { prefix, rewrite } => {
1877 text.push_str(&format!(
1878 " <rewriteSystem systemIdStartString=\"{}\" rewritePrefix=\"{}\"/>\n",
1879 String::from_utf8_lossy(prefix),
1880 String::from_utf8_lossy(rewrite)
1881 ));
1882 }
1883 CatalogEntry::RewriteURI { prefix, rewrite } => {
1884 text.push_str(&format!(
1885 " <rewriteURI uriStartString=\"{}\" rewritePrefix=\"{}\"/>\n",
1886 String::from_utf8_lossy(prefix),
1887 String::from_utf8_lossy(rewrite)
1888 ));
1889 }
1890 _ => {}
1891 }
1892 }
1893 text.push_str("</catalog>\n");
1894 let bytes = text.into_bytes();
1895 unsafe {
1896 libc::fwrite(bytes.as_ptr() as *const libc::c_void, 1, bytes.len(), out);
1897 }
1898}
1899
1900#[no_mangle]
1912pub unsafe extern "C" fn xmlInitializeCatalog() {
1913 crate::xml::catalog::init();
1914}
1915
1916#[no_mangle]
1928pub unsafe extern "C" fn xmlCatalogDumpDoc() -> *mut _xmlDoc {
1929 unsafe { dump_doc() }
1930}
1931
1932#[no_mangle]
1945pub unsafe extern "C" fn xmlCatalogSetDebug(level: c_int) -> c_int {
1946 let old = CATALOG_DEBUG.load(std::sync::atomic::Ordering::Relaxed);
1947 if level <= 0 {
1948 CATALOG_DEBUG.store(0, std::sync::atomic::Ordering::Relaxed);
1949 } else {
1950 CATALOG_DEBUG.store(level, std::sync::atomic::Ordering::Relaxed);
1951 }
1952 old
1953}
1954
1955#[no_mangle]
1968pub unsafe extern "C" fn xmlCatalogSetDefaultPrefer(prefer: c_int) -> c_int {
1969 let old = CATALOG_PREFER.load(std::sync::atomic::Ordering::Relaxed);
1970 if prefer == 0 {
1971 return old;
1972 }
1973 CATALOG_PREFER.store(prefer, std::sync::atomic::Ordering::Relaxed);
1974 old
1975}
1976
1977#[no_mangle]
1999pub unsafe extern "C" fn xmlCatalogResolve(
2000 pubID: *const xmlChar,
2001 sysID: *const xmlChar,
2002) -> *mut xmlChar {
2003 if !sysID.is_null() {
2004 let r = unsafe { resolve_system(sysID) };
2005 if !r.is_null() {
2006 return r;
2007 }
2008 }
2009 if !pubID.is_null() {
2010 return unsafe { resolve_public(pubID) };
2011 }
2012 ptr::null_mut()
2013}
2014
2015#[no_mangle]
2033pub unsafe extern "C" fn xmlCatalogGetSystem(sysID: *const xmlChar) -> *const xmlChar {
2034 unsafe { resolve_system(sysID) }
2035}
2036#[no_mangle]
2053pub unsafe extern "C" fn xmlCatalogGetPublic(pubID: *const xmlChar) -> *const xmlChar {
2054 unsafe { resolve_public(pubID) }
2055}
2056
2057#[no_mangle]
2063pub unsafe extern "C" fn xmlParseCatalogFile(filename: *const c_char) -> *mut _xmlDoc {
2064 if filename.is_null() {
2065 return ptr::null_mut();
2066 }
2067 unsafe { dump_doc() }
2068}
2069
2070#[no_mangle]
2093pub unsafe extern "C" fn xmlCatalogAddLocal(
2094 catalogs: *mut c_void,
2095 URL: *const xmlChar,
2096) -> *mut c_void {
2097 if URL.is_null() {
2098 return catalogs;
2099 }
2100 let list: *mut Vec<CatalogEntry> = if catalogs.is_null() {
2101 Box::into_raw(Box::new(Vec::<CatalogEntry>::new()))
2102 } else {
2103 catalogs as *mut Vec<CatalogEntry>
2104 };
2105 let url = xmlstr_to_bytes(URL);
2106 let url_str = String::from_utf8_lossy(url).into_owned();
2107 let entries = unsafe { &mut *list };
2108 if let Some(data) = read_file_bytes(&url_str) {
2109 let mut temp = Vec::new();
2110 load_catalog_data(&url_str, &data, &mut temp);
2111 entries.extend(temp);
2112 }
2113 list as *mut c_void
2114}
2115
2116#[no_mangle]
2122pub unsafe extern "C" fn xmlCatalogFreeLocal(catalogs: *mut c_void) {
2123 if !catalogs.is_null() {
2124 unsafe { drop(Box::from_raw(catalogs as *mut Vec<CatalogEntry>)) };
2125 }
2126}
2127
2128#[no_mangle]
2149pub unsafe extern "C" fn xmlCatalogLocalResolve(
2150 catalogs: *mut c_void,
2151 pubID: *const xmlChar,
2152 sysID: *const xmlChar,
2153) -> *mut xmlChar {
2154 if catalogs.is_null() {
2155 return ptr::null_mut();
2156 }
2157 let entries = unsafe { &*(catalogs as *const Vec<CatalogEntry>) };
2158 if !sysID.is_null() {
2160 let b = xmlstr_to_bytes(sysID);
2161 if let Some(r) = unsafe { resolve_system_entries(entries, b) } {
2162 return bytes_to_xmlstr(&r);
2163 }
2164 }
2165 if !pubID.is_null() {
2166 let b = xmlstr_to_bytes(pubID);
2167 if let Some(r) = unsafe { resolve_public_entries(entries, b) } {
2168 return bytes_to_xmlstr(&r);
2169 }
2170 }
2171 ptr::null_mut()
2172}
2173
2174#[no_mangle]
2195pub unsafe extern "C" fn xmlCatalogLocalResolveURI(
2196 catalogs: *mut c_void,
2197 URI: *const xmlChar,
2198) -> *mut xmlChar {
2199 if catalogs.is_null() || URI.is_null() {
2200 return ptr::null_mut();
2201 }
2202 let entries = unsafe { &*(catalogs as *const Vec<CatalogEntry>) };
2203 let b = xmlstr_to_bytes(URI);
2204 unsafe { resolve_uri_entries(entries, b) }
2205 .as_ref()
2206 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
2207}
2208
2209#[cfg(test)]
2214mod tests {
2215 use super::*;
2216 use crate::abi::allocator::xmlFreeImpl;
2217 use crate::xml::string::xmlstr_to_bytes;
2218
2219 use std::sync::Mutex;
2220
2221 static CATALOG_TEST_MUTEX: Mutex<()> = Mutex::new(());
2230
2231 unsafe fn to_xmlstr(s: &[u8]) -> *const xmlChar {
2233 let ptr = bytes_to_xmlstr(s);
2234 ptr as *const xmlChar
2235 }
2236
2237 unsafe fn to_xmlstr_str(s: &str) -> *const xmlChar {
2239 to_xmlstr(s.as_bytes())
2240 }
2241
2242 unsafe fn free_xmlstr(ptr: *const xmlChar) {
2243 if !ptr.is_null() {
2244 xmlFreeImpl(ptr as *mut c_void);
2245 }
2246 }
2247
2248 fn setup() -> std::sync::MutexGuard<'static, ()> {
2255 let guard = CATALOG_TEST_MUTEX.lock().unwrap();
2256 cleanup();
2257 init();
2258 set_defaults(XML_CATA_ALLOW_ALL);
2260 guard
2261 }
2262
2263 fn teardown(_guard: std::sync::MutexGuard<'static, ()>) {
2264 cleanup();
2265 }
2267
2268 #[test]
2271 fn test_resolve_public_basic() {
2272 let _guard = setup();
2273 unsafe {
2274 let type_ = to_xmlstr_str("public");
2276 let pub_id = to_xmlstr_str("-//OASIS//DTD DocBook XML V4.2//EN");
2277 let uri = to_xmlstr_str("http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd");
2278 assert_eq!(add(type_, pub_id, uri), 0);
2279
2280 let result = resolve_public(pub_id);
2282 assert!(!result.is_null());
2283 assert_eq!(
2284 xmlstr_to_bytes(result),
2285 b"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
2286 );
2287 xmlFreeImpl(result as *mut c_void);
2288
2289 let unknown = to_xmlstr_str("-//Unknown//DTD Unknown//EN");
2291 assert!(resolve_public(unknown).is_null());
2292 free_xmlstr(unknown);
2293
2294 free_xmlstr(type_);
2295 free_xmlstr(pub_id);
2296 free_xmlstr(uri);
2297 teardown(_guard);
2298 }
2299 }
2300
2301 #[test]
2304 fn test_resolve_system_basic() {
2305 let _guard = setup();
2306 unsafe {
2307 let type_ = to_xmlstr_str("system");
2308 let sys_id = to_xmlstr_str("http://example.com/foo.dtd");
2309 let uri = to_xmlstr_str("/local/foo.dtd");
2310 assert_eq!(add(type_, sys_id, uri), 0);
2311
2312 let result = resolve_system(sys_id);
2313 assert!(!result.is_null());
2314 assert_eq!(xmlstr_to_bytes(result), b"/local/foo.dtd");
2315 xmlFreeImpl(result as *mut c_void);
2316
2317 free_xmlstr(type_);
2318 free_xmlstr(sys_id);
2319 free_xmlstr(uri);
2320 teardown(_guard);
2321 }
2322 }
2323
2324 #[test]
2327 fn test_resolve_uri_basic() {
2328 let _guard = setup();
2329 unsafe {
2330 let type_ = to_xmlstr_str("system");
2332 let sys_id = to_xmlstr_str("http://example.com/resource.xml");
2333 let uri = to_xmlstr_str("/local/resource.xml");
2334 assert_eq!(add(type_, sys_id, uri), 0);
2335
2336 let result = resolve_uri(sys_id);
2337 assert!(!result.is_null());
2338 assert_eq!(xmlstr_to_bytes(result), b"/local/resource.xml");
2339 xmlFreeImpl(result as *mut c_void);
2340
2341 free_xmlstr(type_);
2342 free_xmlstr(sys_id);
2343 free_xmlstr(uri);
2344 teardown(_guard);
2345 }
2346 }
2347
2348 #[test]
2351 fn test_rewrite_system() {
2352 let _guard = setup();
2353 unsafe {
2354 let type_ = to_xmlstr_str("rewriteSystem");
2355 let prefix = to_xmlstr_str("http://example.com/old/");
2356 let rewrite = to_xmlstr_str("http://mirror.example.com/new/");
2357 assert_eq!(add(type_, prefix, rewrite), 0);
2358
2359 let sys_id = to_xmlstr_str("http://example.com/old/path/file.xml");
2360 let result = resolve_system(sys_id);
2361 assert!(!result.is_null());
2362 assert_eq!(
2363 xmlstr_to_bytes(result),
2364 b"http://mirror.example.com/new/path/file.xml"
2365 );
2366 xmlFreeImpl(result as *mut c_void);
2367
2368 free_xmlstr(type_);
2369 free_xmlstr(prefix);
2370 free_xmlstr(rewrite);
2371 free_xmlstr(sys_id);
2372 teardown(_guard);
2373 }
2374 }
2375
2376 #[test]
2379 fn test_rewrite_uri() {
2380 let _guard = setup();
2381 unsafe {
2382 let type_ = to_xmlstr_str("rewriteURI");
2383 let prefix = to_xmlstr_str("http://example.com/old/");
2384 let rewrite = to_xmlstr_str("http://mirror.example.com/new/");
2385 assert_eq!(add(type_, prefix, rewrite), 0);
2386
2387 let uri = to_xmlstr_str("http://example.com/old/path/file.xml");
2388 let result = resolve_uri(uri);
2389 assert!(!result.is_null());
2390 assert_eq!(
2391 xmlstr_to_bytes(result),
2392 b"http://mirror.example.com/new/path/file.xml"
2393 );
2394 xmlFreeImpl(result as *mut c_void);
2395
2396 free_xmlstr(type_);
2397 free_xmlstr(prefix);
2398 free_xmlstr(rewrite);
2399 free_xmlstr(uri);
2400 teardown(_guard);
2401 }
2402 }
2403
2404 #[test]
2407 fn test_remove_entries() {
2408 let _guard = setup();
2409 unsafe {
2410 let type_ = to_xmlstr_str("public");
2411 let pub_id = to_xmlstr_str("-//TEST//PUBLIC//EN");
2412 let uri = to_xmlstr_str("test.dtd");
2413 assert_eq!(add(type_, pub_id, uri), 0);
2414
2415 assert!(!resolve_public(pub_id).is_null());
2417
2418 assert_eq!(remove(pub_id), 1);
2420
2421 assert!(resolve_public(pub_id).is_null());
2423
2424 free_xmlstr(type_);
2425 free_xmlstr(pub_id);
2426 free_xmlstr(uri);
2427 teardown(_guard);
2428 }
2429 }
2430
2431 #[test]
2434 fn test_catalog_defaults() {
2435 let _guard = setup();
2436
2437 assert_eq!(get_defaults(), XML_CATA_ALLOW_ALL);
2438
2439 set_defaults(XML_CATA_ALLOW_NONE);
2440 assert_eq!(get_defaults(), XML_CATA_ALLOW_NONE);
2441
2442 set_defaults(XML_CATA_ALLOW_GLOBAL);
2443 assert_eq!(get_defaults(), XML_CATA_ALLOW_GLOBAL);
2444
2445 set_defaults(XML_CATA_ALLOW_ALL);
2446 assert_eq!(get_defaults(), XML_CATA_ALLOW_ALL);
2447
2448 teardown(_guard);
2449 }
2450
2451 #[test]
2454 fn test_parse_xml_catalog_in_memory() {
2455 let _guard = setup();
2456 {
2457 let catalog_xml = br#"<?xml version="1.0"?>
2458<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
2459<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
2460 <public publicId="-//OASIS//DTD DocBook XML V4.2//EN" uri="http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"/>
2461 <system systemId="http://example.com/foo.dtd" uri="/local/foo.dtd"/>
2462 <rewriteSystem systemIdStartString="http://example.com/old/" rewritePrefix="http://mirror.example.com/new/"/>
2463 <rewriteURI uriStartString="http://example.com/old/" rewritePrefix="http://mirror.example.com/new/"/>
2464</catalog>"#;
2465
2466 let mut entries = Vec::new();
2468 parse_xml_catalog(catalog_xml, &mut entries);
2469 assert_eq!(entries.len(), 4);
2470
2471 match &entries[0] {
2473 CatalogEntry::Public { public_id, uri } => {
2474 assert_eq!(public_id.as_slice(), b"-//OASIS//DTD DocBook XML V4.2//EN");
2475 assert_eq!(
2476 uri.as_slice(),
2477 b"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
2478 );
2479 }
2480 _ => panic!("Expected Public entry"),
2481 }
2482
2483 match &entries[1] {
2485 CatalogEntry::System { system_id, uri } => {
2486 assert_eq!(system_id.as_slice(), b"http://example.com/foo.dtd");
2487 assert_eq!(uri.as_slice(), b"/local/foo.dtd");
2488 }
2489 _ => panic!("Expected System entry"),
2490 }
2491
2492 match &entries[2] {
2494 CatalogEntry::RewriteSystem { prefix, rewrite } => {
2495 assert_eq!(prefix.as_slice(), b"http://example.com/old/");
2496 assert_eq!(rewrite.as_slice(), b"http://mirror.example.com/new/");
2497 }
2498 _ => panic!("Expected RewriteSystem entry"),
2499 }
2500
2501 match &entries[3] {
2503 CatalogEntry::RewriteURI { prefix, rewrite } => {
2504 assert_eq!(prefix.as_slice(), b"http://example.com/old/");
2505 assert_eq!(rewrite.as_slice(), b"http://mirror.example.com/new/");
2506 }
2507 _ => panic!("Expected RewriteURI entry"),
2508 }
2509
2510 teardown(_guard);
2511 }
2512 }
2513
2514 #[test]
2517 fn test_parse_sgml_catalog() {
2518 let _guard = setup();
2519 {
2520 let sgml_data = br#"-- SGML catalog
2521PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "docbookx.dtd"
2522SYSTEM "http://example.com/foo.dtd" "/local/foo.dtd"
2523URI "http://example.com/resource" "/local/resource"
2524"#;
2525
2526 let mut entries = Vec::new();
2527 parse_sgml_catalog(sgml_data, &mut entries);
2528 assert_eq!(entries.len(), 3);
2529
2530 match &entries[0] {
2532 CatalogEntry::Public { public_id, uri } => {
2533 assert_eq!(public_id.as_slice(), b"-//OASIS//DTD DocBook XML V4.2//EN");
2534 assert_eq!(uri.as_slice(), b"docbookx.dtd");
2535 }
2536 _ => panic!("Expected Public entry"),
2537 }
2538
2539 match &entries[1] {
2541 CatalogEntry::System { system_id, uri } => {
2542 assert_eq!(system_id.as_slice(), b"http://example.com/foo.dtd");
2543 assert_eq!(uri.as_slice(), b"/local/foo.dtd");
2544 }
2545 _ => panic!("Expected System entry"),
2546 }
2547
2548 match &entries[2] {
2550 CatalogEntry::System { system_id, uri } => {
2551 assert_eq!(system_id.as_slice(), b"http://example.com/resource");
2552 assert_eq!(uri.as_slice(), b"/local/resource");
2553 }
2554 _ => panic!("Expected System entry for URI"),
2555 }
2556
2557 teardown(_guard);
2558 }
2559 }
2560
2561 #[test]
2564 fn test_resolution_precedence() {
2565 let _guard = setup();
2566 unsafe {
2567 let type_sys = to_xmlstr_str("system");
2569 let sys_id = to_xmlstr_str("http://example.com/target.xml");
2570 let uri_direct = to_xmlstr_str("/direct/uri.xml");
2571 assert_eq!(add(type_sys, sys_id, uri_direct), 0);
2572
2573 let type_rw = to_xmlstr_str("rewriteSystem");
2575 let prefix = to_xmlstr_str("http://example.com/");
2576 let rewrite = to_xmlstr_str("/rewrite/");
2577 assert_eq!(add(type_rw, prefix, rewrite), 0);
2578
2579 let result = resolve_system(sys_id);
2581 assert!(!result.is_null());
2582 assert_eq!(xmlstr_to_bytes(result), b"/direct/uri.xml");
2583 xmlFreeImpl(result as *mut c_void);
2584
2585 free_xmlstr(type_sys);
2586 free_xmlstr(sys_id);
2587 free_xmlstr(uri_direct);
2588 free_xmlstr(type_rw);
2589 free_xmlstr(prefix);
2590 free_xmlstr(rewrite);
2591 teardown(_guard);
2592 }
2593 }
2594
2595 #[test]
2598 fn test_convert_sgml_to_xml() {
2599 let _guard = setup();
2600 unsafe {
2601 let type_ = to_xmlstr_str("public");
2602 let pub_id = to_xmlstr_str("-//TEST//PUBLIC//EN");
2603 let uri = to_xmlstr_str("test.dtd");
2604 assert_eq!(add(type_, pub_id, uri), 0);
2605
2606 let doc = convert();
2607 assert!(!doc.is_null());
2608
2609 let root = crate::xml::tree::doc_get_root_element(doc);
2611 assert!(!root.is_null());
2612 let root_name = crate::xml::string::xmlstr_to_bytes((*root).name);
2613 assert_eq!(root_name, b"catalog");
2614
2615 let child = (*root).children;
2617 assert!(!child.is_null());
2618 let child_name = crate::xml::string::xmlstr_to_bytes((*child).name);
2619 assert_eq!(child_name, b"public");
2620
2621 crate::xml::tree::free_doc(doc);
2622 free_xmlstr(type_);
2623 free_xmlstr(pub_id);
2624 free_xmlstr(uri);
2625 teardown(_guard);
2626 }
2627 }
2628
2629 #[test]
2632 fn test_catalog_disallowed() {
2633 let _guard = setup();
2634 unsafe {
2635 let type_ = to_xmlstr_str("system");
2637 let sys_id = to_xmlstr_str("http://example.com/test.dtd");
2638 let uri = to_xmlstr_str("/local/test.dtd");
2639 add(type_, sys_id, uri);
2640
2641 set_defaults(XML_CATA_ALLOW_NONE);
2643
2644 assert!(resolve_system(sys_id).is_null());
2646 assert!(resolve_public(sys_id).is_null());
2647 assert!(resolve_uri(sys_id).is_null());
2648
2649 set_defaults(XML_CATA_ALLOW_ALL);
2650 free_xmlstr(type_);
2651 free_xmlstr(sys_id);
2652 free_xmlstr(uri);
2653 teardown(_guard);
2654 }
2655 }
2656
2657 #[test]
2660 fn test_init_cleanup() {
2661 let _guard = CATALOG_TEST_MUTEX.lock().unwrap();
2662 cleanup();
2663 assert!(!CATALOG_STATE.read().initialized);
2664
2665 init();
2666 assert!(CATALOG_STATE.read().initialized);
2667
2668 cleanup();
2669 assert!(!CATALOG_STATE.read().initialized);
2670 }
2671
2672 #[test]
2675 fn test_parse_xml_catalog_group() {
2676 let catalog_xml = br#"<?xml version="1.0"?>
2677<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
2678 <group>
2679 <public publicId="-//GROUP//PUBLIC//EN" uri="group.dtd"/>
2680 <system systemId="http://group.example.com/" uri="/group/"/>
2681 </group>
2682</catalog>"#;
2683
2684 let mut entries = Vec::new();
2685 parse_xml_catalog(catalog_xml, &mut entries);
2686 assert_eq!(entries.len(), 2);
2687
2688 match &entries[0] {
2689 CatalogEntry::Public { public_id, .. } => {
2690 assert_eq!(public_id.as_slice(), b"-//GROUP//PUBLIC//EN");
2691 }
2692 _ => panic!("Expected Public entry"),
2693 }
2694
2695 match &entries[1] {
2696 CatalogEntry::System { system_id, .. } => {
2697 assert_eq!(system_id.as_slice(), b"http://group.example.com/");
2698 }
2699 _ => panic!("Expected System entry"),
2700 }
2701 }
2702
2703 #[test]
2706 fn test_multiple_entries() {
2707 let _guard = setup();
2708 unsafe {
2709 let t = to_xmlstr_str("public");
2711 let id1 = to_xmlstr_str("-//A//PUBLIC//EN");
2712 let uri1 = to_xmlstr_str("a.dtd");
2713 let id2 = to_xmlstr_str("-//B//PUBLIC//EN");
2714 let uri2 = to_xmlstr_str("b.dtd");
2715
2716 assert_eq!(add(t, id1, uri1), 0);
2717 assert_eq!(add(t, id2, uri2), 0);
2718
2719 let r1 = resolve_public(id1);
2720 assert!(!r1.is_null());
2721 assert_eq!(xmlstr_to_bytes(r1), b"a.dtd");
2722 xmlFreeImpl(r1 as *mut c_void);
2723
2724 let r2 = resolve_public(id2);
2725 assert!(!r2.is_null());
2726 assert_eq!(xmlstr_to_bytes(r2), b"b.dtd");
2727 xmlFreeImpl(r2 as *mut c_void);
2728
2729 free_xmlstr(t);
2730 free_xmlstr(id1);
2731 free_xmlstr(uri1);
2732 free_xmlstr(id2);
2733 free_xmlstr(uri2);
2734 teardown(_guard);
2735 }
2736 }
2737
2738 #[test]
2741 fn test_longest_prefix_wins() {
2742 let _guard = setup();
2743 unsafe {
2744 let t = to_xmlstr_str("rewriteSystem");
2745 let p1 = to_xmlstr_str("http://example.com/");
2746 let r1 = to_xmlstr_str("/general/");
2747 let p2 = to_xmlstr_str("http://example.com/specific/");
2748 let r2 = to_xmlstr_str("/specific/");
2749
2750 add(t, p1, r1);
2751 add(t, p2, r2);
2752
2753 let sys_id = to_xmlstr_str("http://example.com/specific/file.xml");
2754 let result = resolve_system(sys_id);
2755 assert!(!result.is_null());
2756 assert_eq!(xmlstr_to_bytes(result), b"/specific/file.xml");
2757 xmlFreeImpl(result as *mut c_void);
2758
2759 free_xmlstr(t);
2760 free_xmlstr(p1);
2761 free_xmlstr(r1);
2762 free_xmlstr(p2);
2763 free_xmlstr(r2);
2764 free_xmlstr(sys_id);
2765 teardown(_guard);
2766 }
2767 }
2768}
2769
2770#[cfg(test)]
2775mod c_abi_tests {
2776 use super::*;
2777 use crate::abi::allocator::xmlFreeImpl;
2778
2779 fn cstr(s: &[u8]) -> *const xmlChar {
2780 s.as_ptr() as *const xmlChar
2781 }
2782
2783 #[test]
2784 fn test_new_free_catalog() {
2785 unsafe {
2786 let h = xmlNewCatalog(0);
2787 assert!(!h.is_null());
2788 assert_eq!(xmlCatalogIsEmpty(h), 1);
2789 xmlFreeCatalog(h);
2790 xmlFreeCatalog(ptr::null_mut());
2791 }
2792 }
2793
2794 #[test]
2795 fn test_acatalog_add_resolve_remove() {
2796 unsafe {
2797 let h = xmlNewCatalog(0);
2800 assert!(!h.is_null());
2801 assert_eq!(
2802 xmlACatalogAdd(
2803 h,
2804 cstr(b"system\0"),
2805 cstr(b"http://x\0"),
2806 cstr(b"file:///x\0")
2807 ),
2808 -1
2809 );
2810 xmlFreeCatalog(h);
2811
2812 let h = xmlNewCatalog(0);
2815 assert!(!h.is_null());
2816 (*h).entries.push(CatalogEntry::System {
2817 system_id: b"http://example.com/foo\0".to_vec(),
2818 uri: b"file:///tmp/foo.xml\0".to_vec(),
2819 });
2820 assert_eq!(
2821 xmlACatalogAdd(
2822 h,
2823 cstr(b"system\0"),
2824 cstr(b"http://example.com/foo\0"),
2825 cstr(b"file:///tmp/foo.xml\0")
2826 ),
2827 0
2828 );
2829 assert_eq!(xmlCatalogIsEmpty(h), 0);
2830 let r = xmlACatalogResolveSystem(h, cstr(b"http://example.com/foo\0"));
2832 assert!(!r.is_null());
2833 let bytes = xmlstr_to_bytes(r);
2834 assert_eq!(bytes, b"file:///tmp/foo.xml");
2835 xmlFreeImpl(r as *mut libc::c_void);
2836 let r2 = xmlACatalogResolveURI(h, cstr(b"http://example.com/foo\0"));
2838 assert!(!r2.is_null());
2839 xmlFreeImpl(r2 as *mut libc::c_void);
2840 assert_eq!(
2842 xmlACatalogAdd(h, cstr(b"bogus\0"), cstr(b"a\0"), cstr(b"b\0")),
2843 -1
2844 );
2845 assert_eq!(xmlACatalogRemove(h, cstr(b"http://example.com/foo\0")), 0);
2849 assert_eq!(xmlCatalogIsEmpty(h), 1);
2850 xmlFreeCatalog(h);
2851 }
2852 }
2853
2854 #[test]
2855 fn test_acatalog_public_and_rewrite() {
2856 unsafe {
2857 let h = xmlNewCatalog(0);
2858 assert!(!h.is_null());
2859 (*h).entries.push(CatalogEntry::Public {
2861 public_id: b"-//OASIS//DTD X//EN\0".to_vec(),
2862 uri: b"file:///dtd/x.dtd\0".to_vec(),
2863 });
2864 assert_eq!(
2865 xmlACatalogAdd(
2866 h,
2867 cstr(b"public\0"),
2868 cstr(b"-//OASIS//DTD X//EN\0"),
2869 cstr(b"file:///dtd/x.dtd\0")
2870 ),
2871 0
2872 );
2873 assert_eq!(
2874 xmlACatalogAdd(
2875 h,
2876 cstr(b"rewriteSystem\0"),
2877 cstr(b"http://old/\0"),
2878 cstr(b"http://new/\0")
2879 ),
2880 0
2881 );
2882 let r = xmlACatalogResolvePublic(h, cstr(b"-//OASIS//DTD X//EN\0"));
2883 assert!(!r.is_null());
2884 assert_eq!(xmlstr_to_bytes(r), b"file:///dtd/x.dtd");
2885 xmlFreeImpl(r as *mut libc::c_void);
2886 let r2 = xmlACatalogResolveSystem(h, cstr(b"http://old/foo.xml\0"));
2887 assert!(!r2.is_null());
2888 assert_eq!(xmlstr_to_bytes(r2), b"http://new/foo.xml");
2889 xmlFreeImpl(r2 as *mut libc::c_void);
2890 xmlFreeCatalog(h);
2891 }
2892 }
2893
2894 #[test]
2895 fn test_catalog_set_debug_and_prefer() {
2896 unsafe {
2897 assert_eq!(xmlCatalogSetDefaultPrefer(1), 1);
2900 assert_eq!(xmlCatalogSetDefaultPrefer(2), 1);
2901 assert_eq!(xmlCatalogSetDefaultPrefer(0), 2);
2902 assert_eq!(xmlCatalogSetDefaultPrefer(1), 2);
2903 assert_eq!(xmlCatalogSetDebug(0), 0);
2904 assert_eq!(xmlCatalogSetDebug(7), 0);
2905 assert_eq!(xmlCatalogSetDebug(0), 7);
2906 }
2907 }
2908
2909 #[test]
2910 fn test_catalog_local_resolve() {
2911 unsafe {
2912 assert!(xmlCatalogLocalResolve(ptr::null_mut(), cstr(b"x\0"), cstr(b"y\0")).is_null());
2914 assert!(xmlCatalogLocalResolveURI(ptr::null_mut(), cstr(b"x\0")).is_null());
2915 xmlCatalogFreeLocal(ptr::null_mut());
2916 }
2917 }
2918
2919 #[test]
2920 fn test_catalog_resolve_global_null() {
2921 unsafe {
2922 assert!(xmlCatalogResolve(ptr::null(), ptr::null()).is_null());
2923 }
2924 }
2925}