1#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
21
22use core::ffi::c_void;
23use std::ffi::CStr;
24use std::fs;
25use std::os::raw::{c_char, c_int};
26use std::path::Path;
27use std::ptr;
28
29use once_cell::sync::Lazy;
30use parking_lot::RwLock;
31
32use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl};
33use crate::abi::structs::{_xmlDoc, _xmlNode};
34use crate::abi::types::xmlChar;
35use crate::xml::string::{
36 bytes_to_xmlstr, c_strdup, xml_str_starts_with, xml_strcat, xml_strcmp, xml_strdup, xml_strlen,
37 xmlstr_to_bytes,
38};
39
40pub(crate) const XML_CATA_ALLOW_NONE: i32 = 0;
46
47pub(crate) const XML_CATA_ALLOW_GLOBAL: i32 = 1;
49
50pub(crate) const XML_CATA_ALLOW_DOCUMENT: i32 = 2;
52
53pub(crate) const XML_CATA_ALLOW_ALL: i32 = 3;
55
56const DEFAULT_CATALOG: &str = "/etc/xml/catalog";
58
59const XML_CATALOG_FILES_ENV: &str = "XML_CATALOG_FILES";
61
62const SGML_CATALOG_FILES_ENV: &str = "SGML_CATALOG_FILES";
64
65const MAX_CATALOG_FILE_SIZE: usize = 10_485_760;
67
68#[derive(Clone, Debug)]
74enum CatalogEntry {
75 Public { public_id: Vec<u8>, uri: Vec<u8> },
77 System { system_id: Vec<u8>, uri: Vec<u8> },
79 RewriteSystem { prefix: Vec<u8>, rewrite: Vec<u8> },
81 RewriteURI { prefix: Vec<u8>, rewrite: Vec<u8> },
83 DelegatePublic { prefix: Vec<u8>, catalog: Vec<u8> },
85 DelegateSystem { prefix: Vec<u8>, catalog: Vec<u8> },
87 DelegateURI { prefix: Vec<u8>, catalog: Vec<u8> },
89 NextCatalog { catalog: Vec<u8> },
91}
92
93#[derive(Clone, Copy, Debug, PartialEq)]
95enum CatalogFormat {
96 Xml,
97 Sgml,
98}
99
100#[derive(Clone, Debug)]
102struct CatalogInfo {
103 path: Vec<u8>,
104 format: CatalogFormat,
105}
106
107struct CatalogState {
113 entries: Vec<CatalogEntry>,
115 catalogs: Vec<CatalogInfo>,
117 initialized: bool,
119 allow: i32,
121}
122
123impl CatalogState {
124 fn new() -> Self {
125 Self {
126 entries: Vec::new(),
127 catalogs: Vec::new(),
128 initialized: false,
129 allow: XML_CATA_ALLOW_ALL,
130 }
131 }
132
133 fn clear(&mut self) {
135 self.entries.clear();
136 self.catalogs.clear();
137 self.allow = XML_CATA_ALLOW_ALL;
138 }
139}
140
141static CATALOG_STATE: Lazy<RwLock<CatalogState>> = Lazy::new(|| RwLock::new(CatalogState::new()));
143
144fn trim_whitespace(bytes: &[u8]) -> &[u8] {
150 let start = bytes
151 .iter()
152 .position(|b| !b.is_ascii_whitespace())
153 .unwrap_or(bytes.len());
154 let end = bytes
155 .iter()
156 .rposition(|b| !b.is_ascii_whitespace())
157 .map_or(0, |p| p + 1);
158 &bytes[start..end]
159}
160
161fn starts_with(data: &[u8], prefix: &[u8]) -> bool {
163 if data.len() < prefix.len() {
164 return false;
165 }
166 data[..prefix.len()] == prefix[..]
167}
168
169fn starts_with_ignore_ascii_case(data: &[u8], prefix: &[u8]) -> bool {
171 if data.len() < prefix.len() {
172 return false;
173 }
174 data[..prefix.len()]
175 .iter()
176 .zip(prefix.iter())
177 .all(|(a, b)| a.eq_ignore_ascii_case(b))
178}
179
180fn extract_attr_value<'a>(data: &'a [u8], name: &[u8], pos: usize) -> Option<(&'a [u8], usize)> {
185 let remaining = &data[pos..];
186 let name_pos = find_subsequence(remaining, name)?;
188 let after_name = name_pos + name.len();
189 let after_name_slice = &remaining[after_name..];
190
191 let eq_pos = after_name_slice.iter().position(|b| *b == b'=')?;
193
194 let rel_quote_start = after_name_slice[eq_pos + 1..]
196 .iter()
197 .position(|b| *b == b'"' || *b == b'\'')
198 .map(|p| after_name + eq_pos + 1 + p)?;
199 let abs_quote_start = pos + rel_quote_start;
200 let quote_char = data[abs_quote_start];
201 let value_start = abs_quote_start + 1;
203 let value_end = data[value_start..]
204 .iter()
205 .position(|b| *b == quote_char)
206 .map(|p| value_start + p)?;
207
208 Some((&data[value_start..value_end], value_end + 1))
209}
210
211fn find_subsequence(data: &[u8], seq: &[u8]) -> Option<usize> {
213 if seq.is_empty() {
214 return Some(0);
215 }
216 data.windows(seq.len()).position(|w| w == seq)
217}
218
219fn extract_token(line: &[u8], pos: usize) -> Option<(&[u8], usize)> {
221 let line = &line[pos..];
222 let start = line.iter().position(|b| !b.is_ascii_whitespace())?;
223 let end = line[start..]
224 .iter()
225 .position(|b| b.is_ascii_whitespace())
226 .map(|p| start + p)
227 .unwrap_or(line.len());
228 Some((&line[start..end], pos + end))
229}
230
231fn extract_quoted_token(line: &[u8], pos: usize) -> Option<(&[u8], usize)> {
233 let line = &line[pos..];
234 let start = line.iter().position(|b| !b.is_ascii_whitespace())?;
235 if start >= line.len() {
236 return None;
237 }
238 let quote_char = line[start];
239 if quote_char != b'"' && quote_char != b'\'' {
240 return extract_token(line, 0);
242 }
243 let value_start = start + 1;
244 let end = line[value_start..]
245 .iter()
246 .position(|b| *b == quote_char)
247 .map(|p| value_start + p)?;
248 Some((&line[value_start..end], pos + end + 1))
249}
250
251fn parse_sgml_line(line: &[u8], entries: &mut Vec<CatalogEntry>) {
270 let trimmed = trim_whitespace(line);
271 if trimmed.is_empty() || trimmed.starts_with(b"--") {
272 return;
273 }
274
275 let Some((directive, after_directive)) = extract_token(trimmed, 0) else {
277 return;
278 };
279
280 match directive {
281 b"PUBLIC" | b"public" => {
282 let Some((pub_id, after_pub)) = extract_quoted_token(trimmed, after_directive) else {
283 return;
284 };
285 let Some((uri, _)) = extract_quoted_token(trimmed, after_pub) else {
286 return;
287 };
288 entries.push(CatalogEntry::Public {
289 public_id: pub_id.to_vec(),
290 uri: uri.to_vec(),
291 });
292 }
293 b"SYSTEM" | b"system" => {
294 let Some((sys_id, after_sys)) = extract_quoted_token(trimmed, after_directive) else {
295 return;
296 };
297 let Some((uri, _)) = extract_quoted_token(trimmed, after_sys) else {
298 return;
299 };
300 entries.push(CatalogEntry::System {
301 system_id: sys_id.to_vec(),
302 uri: uri.to_vec(),
303 });
304 }
305 b"URI" | b"uri" => {
306 let Some((uri_id, after_uri)) = extract_quoted_token(trimmed, after_directive) else {
308 return;
309 };
310 let Some((replacement, _)) = extract_quoted_token(trimmed, after_uri) else {
311 return;
312 };
313 entries.push(CatalogEntry::System {
314 system_id: uri_id.to_vec(),
315 uri: replacement.to_vec(),
316 });
317 }
318 b"CATALOG" | b"catalog" => {
319 let Some((path, _)) = extract_quoted_token(trimmed, after_directive) else {
320 return;
321 };
322 entries.push(CatalogEntry::NextCatalog {
323 catalog: path.to_vec(),
324 });
325 }
326 _ => {
327 }
329 }
330}
331
332fn parse_sgml_catalog(data: &[u8], entries: &mut Vec<CatalogEntry>) {
334 for line in data.split(|b| *b == b'\n') {
335 parse_sgml_line(line, entries);
336 }
337}
338
339fn parse_xml_catalog(data: &[u8], entries: &mut Vec<CatalogEntry>) {
348 let mut pos = 0;
349 let len = data.len();
350
351 while pos < len {
352 let Some(lt_pos) = data[pos..].iter().position(|b| *b == b'<') else {
354 break;
355 };
356 let tag_start = pos + lt_pos;
357
358 if tag_start + 1 >= len {
360 break;
361 }
362
363 let is_closing = data[tag_start + 1] == b'/';
364 if is_closing {
365 let Some(gt_pos) = data[tag_start..].iter().position(|b| *b == b'>') else {
367 break;
368 };
369 pos = tag_start + gt_pos + 1;
370 continue;
371 }
372
373 if data[tag_start + 1] == b'!' || data[tag_start + 1] == b'?' {
375 let Some(gt_pos) = data[tag_start..].iter().position(|b| *b == b'>') else {
376 break;
377 };
378 pos = tag_start + gt_pos + 1;
379 continue;
380 }
381
382 let tag_name_start = tag_start + 1;
384 let tag_name_end = data[tag_name_start..]
385 .iter()
386 .position(|b| b.is_ascii_whitespace() || *b == b'>' || *b == b'/')
387 .map(|p| tag_name_start + p)
388 .unwrap_or(len);
389
390 let tag_name = &data[tag_name_start..tag_name_end];
391
392 let Some(gt_or_slash_pos) = data[tag_start..]
394 .iter()
395 .position(|b| *b == b'>')
396 .map(|p| tag_start + p)
397 else {
398 break;
399 };
400
401 let is_self_closing = gt_or_slash_pos > 0 && data[gt_or_slash_pos - 1] == b'/';
402 let tag_content_end = if is_self_closing {
403 gt_or_slash_pos + 1
404 } else {
405 let close_tag = {
407 let mut close = Vec::with_capacity(tag_name.len() + 3);
408 close.push(b'<');
409 close.push(b'/');
410 close.extend_from_slice(tag_name);
411 close.push(b'>');
412 close
413 };
414 let close_pos = data[gt_or_slash_pos + 1..]
415 .windows(close_tag.len())
416 .position(|w| w == close_tag.as_slice())
417 .map(|p| gt_or_slash_pos + 1 + p + close_tag.len());
418
419 match close_pos {
420 Some(p) => p,
421 None => {
422 pos = gt_or_slash_pos + 1;
423 continue;
424 }
425 }
426 };
427
428 let tag_body_start = gt_or_slash_pos + 1;
429 let tag_body = &data[tag_body_start
430 ..tag_content_end
431 - if is_self_closing {
432 0
433 } else {
434 tag_name.len() + 3
435 }];
436 let tag_body = trim_whitespace(tag_body);
437
438 match tag_name {
439 b"public" => {
440 let Some((pub_id, _)) = extract_attr_value(data, b"publicId", tag_start) else {
441 pos = tag_content_end;
442 continue;
443 };
444 let Some((uri, _)) = extract_attr_value(data, b"uri", tag_start) else {
445 pos = tag_content_end;
446 continue;
447 };
448 entries.push(CatalogEntry::Public {
449 public_id: pub_id.to_vec(),
450 uri: uri.to_vec(),
451 });
452 }
453 b"system" => {
454 let Some((sys_id, _)) = extract_attr_value(data, b"systemId", tag_start) else {
455 pos = tag_content_end;
456 continue;
457 };
458 let Some((uri, _)) = extract_attr_value(data, b"uri", tag_start) else {
459 pos = tag_content_end;
460 continue;
461 };
462 entries.push(CatalogEntry::System {
463 system_id: sys_id.to_vec(),
464 uri: uri.to_vec(),
465 });
466 }
467 b"rewriteSystem" => {
468 let Some((prefix, _)) = extract_attr_value(data, b"systemIdStartString", tag_start)
469 else {
470 pos = tag_content_end;
471 continue;
472 };
473 let Some((rewrite, _)) = extract_attr_value(data, b"rewritePrefix", tag_start)
474 else {
475 pos = tag_content_end;
476 continue;
477 };
478 entries.push(CatalogEntry::RewriteSystem {
479 prefix: prefix.to_vec(),
480 rewrite: rewrite.to_vec(),
481 });
482 }
483 b"rewriteURI" => {
484 let Some((prefix, _)) = extract_attr_value(data, b"uriStartString", tag_start)
485 else {
486 pos = tag_content_end;
487 continue;
488 };
489 let Some((rewrite, _)) = extract_attr_value(data, b"rewritePrefix", tag_start)
490 else {
491 pos = tag_content_end;
492 continue;
493 };
494 entries.push(CatalogEntry::RewriteURI {
495 prefix: prefix.to_vec(),
496 rewrite: rewrite.to_vec(),
497 });
498 }
499 b"delegatePublic" => {
500 let Some((prefix, _)) = extract_attr_value(data, b"publicIdStartString", tag_start)
501 else {
502 pos = tag_content_end;
503 continue;
504 };
505 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
506 pos = tag_content_end;
507 continue;
508 };
509 entries.push(CatalogEntry::DelegatePublic {
510 prefix: prefix.to_vec(),
511 catalog: catalog.to_vec(),
512 });
513 }
514 b"delegateSystem" => {
515 let Some((prefix, _)) = extract_attr_value(data, b"systemIdStartString", tag_start)
516 else {
517 pos = tag_content_end;
518 continue;
519 };
520 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
521 pos = tag_content_end;
522 continue;
523 };
524 entries.push(CatalogEntry::DelegateSystem {
525 prefix: prefix.to_vec(),
526 catalog: catalog.to_vec(),
527 });
528 }
529 b"delegateURI" => {
530 let Some((prefix, _)) = extract_attr_value(data, b"uriStartString", tag_start)
531 else {
532 pos = tag_content_end;
533 continue;
534 };
535 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
536 pos = tag_content_end;
537 continue;
538 };
539 entries.push(CatalogEntry::DelegateURI {
540 prefix: prefix.to_vec(),
541 catalog: catalog.to_vec(),
542 });
543 }
544 b"nextCatalog" => {
545 let Some((catalog, _)) = extract_attr_value(data, b"catalog", tag_start) else {
546 pos = tag_content_end;
547 continue;
548 };
549 entries.push(CatalogEntry::NextCatalog {
550 catalog: catalog.to_vec(),
551 });
552 }
553 b"group" | b"catalog" => {
554 parse_xml_catalog(tag_body, entries);
556 }
557 _ => {
558 }
560 }
561
562 pos = tag_content_end;
563 }
564}
565
566fn read_file_bytes(path: &str) -> Option<Vec<u8>> {
572 let p = Path::new(path);
573 let metadata = fs::metadata(p).ok()?;
575 if metadata.len() > MAX_CATALOG_FILE_SIZE as u64 {
576 return None;
577 }
578 fs::read(p).ok()
579}
580
581fn detect_catalog_format(data: &[u8]) -> CatalogFormat {
583 let trimmed = trim_whitespace(data);
584 if trimmed.starts_with(b"<?xml") || trimmed.starts_with(b"<catalog") {
585 CatalogFormat::Xml
586 } else {
587 CatalogFormat::Sgml
588 }
589}
590
591fn load_catalog_data(path: &str, data: &[u8], entries: &mut Vec<CatalogEntry>) {
593 let format = detect_catalog_format(data);
594 match format {
595 CatalogFormat::Xml => {
596 parse_xml_catalog(data, entries);
597 }
598 CatalogFormat::Sgml => {
599 parse_sgml_catalog(data, entries);
600 }
601 }
602}
603
604fn load_single_catalog(path: &str, state: &mut CatalogState) {
606 let data = match read_file_bytes(path) {
607 Some(d) => d,
608 None => return,
609 };
610
611 let format = detect_catalog_format(&data);
612 state.catalogs.push(CatalogInfo {
613 path: path.as_bytes().to_vec(),
614 format,
615 });
616
617 load_catalog_data(path, &data, &mut state.entries);
618}
619
620fn load_catalog_list(catalogs: &str, state: &mut CatalogState) {
622 for catalog_path in catalogs.split(':') {
623 let trimmed = catalog_path.trim();
624 if !trimmed.is_empty() {
625 load_single_catalog(trimmed, state);
626 }
627 }
628}
629
630pub(crate) fn init() {
639 let mut state = CATALOG_STATE.write();
640 if state.initialized {
641 return;
642 }
643
644 state.allow = XML_CATA_ALLOW_ALL;
646 crate::xml::globals::set_catalog_defaults(XML_CATA_ALLOW_ALL);
647
648 if let Ok(catalogs) = std::env::var(XML_CATALOG_FILES_ENV) {
650 load_catalog_list(&catalogs, &mut state);
651 }
652
653 if let Ok(catalogs) = std::env::var(SGML_CATALOG_FILES_ENV) {
655 load_catalog_list(&catalogs, &mut state);
656 }
657
658 if Path::new(DEFAULT_CATALOG).exists() {
660 load_single_catalog(DEFAULT_CATALOG, &mut state);
661 }
662
663 state.initialized = true;
664}
665
666pub(crate) fn cleanup() {
670 let mut state = CATALOG_STATE.write();
671 state.clear();
672 state.initialized = false;
673}
674
675pub(crate) fn load_catalog(catalogs: *const c_char) -> *mut c_void {
689 if catalogs.is_null() {
690 return ptr::null_mut();
691 }
692
693 let catalogs_str = unsafe { CStr::from_ptr(catalogs) };
694 let catalogs_str = catalogs_str.to_str().unwrap_or("");
695
696 let mut state = CATALOG_STATE.write();
697
698 if !state.initialized {
700 drop(state);
701 init();
702 state = CATALOG_STATE.write();
703 }
704
705 let count_before = state.catalogs.len();
706 load_catalog_list(catalogs_str, &mut state);
707
708 if state.catalogs.len() > count_before {
709 (state.catalogs.len() as isize) as *mut c_void
711 } else {
712 ptr::null_mut()
713 }
714}
715
716fn catalog_allowed(state: &CatalogState) -> bool {
722 let allow = state.allow;
723 match allow {
724 XML_CATA_ALLOW_NONE => false,
725 XML_CATA_ALLOW_GLOBAL | XML_CATA_ALLOW_DOCUMENT | XML_CATA_ALLOW_ALL => true,
726 _ => false,
727 }
728}
729
730unsafe fn resolve_public_entries(entries: &[CatalogEntry], pub_id_bytes: &[u8]) -> Option<Vec<u8>> {
733 for entry in entries {
735 if let CatalogEntry::Public { public_id, uri } = entry {
736 if public_id.as_slice() == pub_id_bytes {
737 return Some(uri.clone());
738 }
739 }
740 }
741
742 let mut best_match: Option<Vec<u8>> = None;
744 let mut best_prefix_len: usize = 0;
745
746 for entry in entries {
747 if let CatalogEntry::DelegatePublic { prefix, catalog } = entry {
748 if pub_id_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
749 best_prefix_len = prefix.len();
750 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
752 let mut temp_entries = Vec::new();
753 parse_xml_catalog(&delegated_data, &mut temp_entries);
754 for temp_entry in &temp_entries {
756 if let CatalogEntry::Public { public_id: dp, uri } = temp_entry {
757 if dp.as_slice() == pub_id_bytes {
758 best_match = Some(uri.clone());
759 }
760 }
761 }
762 }
763 }
764 }
765 }
766
767 best_match
768}
769
770pub(crate) unsafe fn resolve_public(pub_id: *const xmlChar) -> *mut xmlChar {
781 if pub_id.is_null() {
782 return ptr::null_mut();
783 }
784
785 let state = CATALOG_STATE.read();
786 if !catalog_allowed(&state) {
787 return ptr::null_mut();
788 }
789
790 let pub_id_bytes = xmlstr_to_bytes(pub_id);
791 unsafe { resolve_public_entries(&state.entries, &pub_id_bytes) }
792 .as_ref()
793 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
794}
795
796unsafe fn resolve_system_entries(entries: &[CatalogEntry], sys_id_bytes: &[u8]) -> Option<Vec<u8>> {
798 for entry in entries {
800 if let CatalogEntry::System { system_id, uri } = entry {
801 if system_id.as_slice() == sys_id_bytes {
802 return Some(uri.clone());
803 }
804 }
805 }
806
807 let mut best_rewrite: Option<Vec<u8>> = None;
809 let mut best_prefix_len: usize = 0;
810
811 for entry in entries {
812 if let CatalogEntry::RewriteSystem { prefix, rewrite } = entry {
813 if sys_id_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
814 best_prefix_len = prefix.len();
815 let suffix = &sys_id_bytes[prefix.len()..];
817 let mut result = rewrite.clone();
818 result.extend_from_slice(suffix);
819 best_rewrite = Some(result);
820 }
821 }
822 }
823
824 if let Some(rewritten) = best_rewrite {
825 return Some(rewritten);
826 }
827
828 for entry in entries {
830 if let CatalogEntry::DelegateSystem { prefix, catalog } = entry {
831 if sys_id_bytes.starts_with(prefix) {
832 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
833 let mut temp_entries = Vec::new();
834 parse_xml_catalog(&delegated_data, &mut temp_entries);
835 for temp_entry in &temp_entries {
836 if let CatalogEntry::System { system_id, uri } = temp_entry {
837 if system_id.as_slice() == sys_id_bytes {
838 return Some(uri.clone());
839 }
840 }
841 }
842 }
843 }
844 }
845 }
846
847 None
848}
849
850pub(crate) unsafe fn resolve_system(sys_id: *const xmlChar) -> *mut xmlChar {
863 if sys_id.is_null() {
864 return ptr::null_mut();
865 }
866
867 let state = CATALOG_STATE.read();
868 if !catalog_allowed(&state) {
869 return ptr::null_mut();
870 }
871
872 let sys_id_bytes = xmlstr_to_bytes(sys_id);
873 unsafe { resolve_system_entries(&state.entries, &sys_id_bytes) }
874 .as_ref()
875 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
876}
877
878unsafe fn resolve_uri_entries(entries: &[CatalogEntry], uri_bytes: &[u8]) -> Option<Vec<u8>> {
880 for entry in entries {
882 if let CatalogEntry::System {
883 system_id,
884 uri: sys_uri,
885 } = entry
886 {
887 if system_id.as_slice() == uri_bytes {
888 return Some(sys_uri.clone());
889 }
890 }
891 }
892
893 let mut best_rewrite: Option<Vec<u8>> = None;
895 let mut best_prefix_len: usize = 0;
896
897 for entry in entries {
898 if let CatalogEntry::RewriteURI { prefix, rewrite } = entry {
899 if uri_bytes.starts_with(prefix) && prefix.len() > best_prefix_len {
900 best_prefix_len = prefix.len();
901 let suffix = &uri_bytes[prefix.len()..];
902 let mut result = rewrite.clone();
903 result.extend_from_slice(suffix);
904 best_rewrite = Some(result);
905 }
906 }
907 }
908
909 if let Some(rewritten) = best_rewrite {
910 return Some(rewritten);
911 }
912
913 for entry in entries {
915 if let CatalogEntry::DelegateURI { prefix, catalog } = entry {
916 if uri_bytes.starts_with(prefix) {
917 if let Some(delegated_data) = read_file_bytes(&String::from_utf8_lossy(catalog)) {
918 let mut temp_entries = Vec::new();
919 parse_xml_catalog(&delegated_data, &mut temp_entries);
920 for temp_entry in &temp_entries {
921 if let CatalogEntry::System {
922 system_id,
923 uri: sys_uri,
924 } = temp_entry
925 {
926 if system_id.as_slice() == uri_bytes {
927 return Some(sys_uri.clone());
928 }
929 }
930 }
931 }
932 }
933 }
934 }
935
936 None
937}
938
939pub(crate) unsafe fn resolve_uri(uri: *const xmlChar) -> *mut xmlChar {
952 if uri.is_null() {
953 return ptr::null_mut();
954 }
955
956 let state = CATALOG_STATE.read();
957 if !catalog_allowed(&state) {
958 return ptr::null_mut();
959 }
960
961 let uri_bytes = xmlstr_to_bytes(uri);
962 unsafe { resolve_uri_entries(&state.entries, &uri_bytes) }
963 .as_ref()
964 .map_or(ptr::null_mut(), |uri| bytes_to_xmlstr(uri))
965}
966
967pub(crate) fn set_defaults(allow: c_int) {
982 let mut state = CATALOG_STATE.write();
983 state.allow = allow;
984 crate::xml::globals::set_catalog_defaults(allow);
985}
986
987pub(crate) fn get_defaults() -> c_int {
995 let state = CATALOG_STATE.read();
996 state.allow
997}
998
999pub(crate) unsafe fn add(
1016 type_: *const xmlChar,
1017 orig: *const xmlChar,
1018 replace: *const xmlChar,
1019) -> c_int {
1020 if type_.is_null() || orig.is_null() || replace.is_null() {
1021 return -1;
1022 }
1023
1024 let type_bytes = xmlstr_to_bytes(type_);
1025 let orig_bytes = xmlstr_to_bytes(orig);
1026 let replace_bytes = xmlstr_to_bytes(replace);
1027
1028 let mut state = CATALOG_STATE.write();
1029
1030 match type_bytes {
1031 b"public" => {
1032 state.entries.push(CatalogEntry::Public {
1033 public_id: orig_bytes.to_vec(),
1034 uri: replace_bytes.to_vec(),
1035 });
1036 0
1037 }
1038 b"system" => {
1039 state.entries.push(CatalogEntry::System {
1040 system_id: orig_bytes.to_vec(),
1041 uri: replace_bytes.to_vec(),
1042 });
1043 0
1044 }
1045 b"rewriteSystem" => {
1046 state.entries.push(CatalogEntry::RewriteSystem {
1047 prefix: orig_bytes.to_vec(),
1048 rewrite: replace_bytes.to_vec(),
1049 });
1050 0
1051 }
1052 b"rewriteURI" => {
1053 state.entries.push(CatalogEntry::RewriteURI {
1054 prefix: orig_bytes.to_vec(),
1055 rewrite: replace_bytes.to_vec(),
1056 });
1057 0
1058 }
1059 b"delegatePublic" => {
1060 state.entries.push(CatalogEntry::DelegatePublic {
1061 prefix: orig_bytes.to_vec(),
1062 catalog: replace_bytes.to_vec(),
1063 });
1064 0
1065 }
1066 b"delegateSystem" => {
1067 state.entries.push(CatalogEntry::DelegateSystem {
1068 prefix: orig_bytes.to_vec(),
1069 catalog: replace_bytes.to_vec(),
1070 });
1071 0
1072 }
1073 b"delegateURI" => {
1074 state.entries.push(CatalogEntry::DelegateURI {
1075 prefix: orig_bytes.to_vec(),
1076 catalog: replace_bytes.to_vec(),
1077 });
1078 0
1079 }
1080 b"nextCatalog" => {
1081 state.entries.push(CatalogEntry::NextCatalog {
1082 catalog: orig_bytes.to_vec(),
1083 });
1084 0
1085 }
1086 _ => -1,
1087 }
1088}
1089
1090pub(crate) unsafe fn remove(value: *const xmlChar) -> c_int {
1101 if value.is_null() {
1102 return -1;
1103 }
1104
1105 let value_bytes = xmlstr_to_bytes(value);
1106 let mut state = CATALOG_STATE.write();
1107
1108 let before = state.entries.len();
1109 state.entries.retain(|entry| match entry {
1110 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != value_bytes,
1111 CatalogEntry::System { system_id, .. } => system_id.as_slice() != value_bytes,
1112 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != value_bytes,
1113 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != value_bytes,
1114 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != value_bytes,
1115 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != value_bytes,
1116 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != value_bytes,
1117 CatalogEntry::NextCatalog { catalog } => catalog.as_slice() != value_bytes,
1118 });
1119
1120 (before - state.entries.len()) as c_int
1121}
1122
1123pub(crate) unsafe fn convert() -> *mut _xmlDoc {
1138 let state = CATALOG_STATE.read();
1139
1140 if state.entries.is_empty() {
1141 return ptr::null_mut();
1142 }
1143
1144 let doc = crate::xml::tree::new_doc(ptr::null_mut());
1146 if doc.is_null() {
1147 return ptr::null_mut();
1148 }
1149
1150 let catalog_name = b"catalog\0" as *const u8 as *const xmlChar;
1152 let root = crate::xml::tree::new_node(ptr::null_mut(), catalog_name);
1153 if root.is_null() {
1154 crate::xml::tree::free_doc(doc);
1155 return ptr::null_mut();
1156 }
1157
1158 let xmlns_name = b"xmlns\0" as *const u8 as *const xmlChar;
1160 let ns_value = b"urn:oasis:names:tc:entity:xmlns:xml:catalog\0" as *const u8 as *const xmlChar;
1161 crate::xml::tree::set_prop(root, xmlns_name, ns_value);
1162
1163 crate::xml::tree::doc_set_root_element(doc, root);
1164
1165 for entry in &state.entries {
1167 let (elem_name, attr1_name, attr1_value, attr2_name, attr2_value) = match entry {
1168 CatalogEntry::Public { public_id, uri } => {
1169 let elem = b"public\0" as *const u8 as *mut xmlChar;
1170 let attr1 = b"publicId\0" as *const u8 as *mut xmlChar;
1171 let val1 = bytes_to_xmlstr(public_id);
1172 let attr2 = b"uri\0" as *const u8 as *mut xmlChar;
1173 let val2 = bytes_to_xmlstr(uri);
1174 (elem, attr1, val1, attr2, val2)
1175 }
1176 CatalogEntry::System { system_id, uri } => {
1177 let elem = b"system\0" as *const u8 as *mut xmlChar;
1178 let attr1 = b"systemId\0" as *const u8 as *mut xmlChar;
1179 let val1 = bytes_to_xmlstr(system_id);
1180 let attr2 = b"uri\0" as *const u8 as *mut xmlChar;
1181 let val2 = bytes_to_xmlstr(uri);
1182 (elem, attr1, val1, attr2, val2)
1183 }
1184 CatalogEntry::RewriteSystem { prefix, rewrite } => {
1185 let elem = b"rewriteSystem\0" as *const u8 as *mut xmlChar;
1186 let attr1 = b"systemIdStartString\0" as *const u8 as *mut xmlChar;
1187 let val1 = bytes_to_xmlstr(prefix);
1188 let attr2 = b"rewritePrefix\0" as *const u8 as *mut xmlChar;
1189 let val2 = bytes_to_xmlstr(rewrite);
1190 (elem, attr1, val1, attr2, val2)
1191 }
1192 CatalogEntry::RewriteURI { prefix, rewrite } => {
1193 let elem = b"rewriteURI\0" as *const u8 as *mut xmlChar;
1194 let attr1 = b"uriStartString\0" as *const u8 as *mut xmlChar;
1195 let val1 = bytes_to_xmlstr(prefix);
1196 let attr2 = b"rewritePrefix\0" as *const u8 as *mut xmlChar;
1197 let val2 = bytes_to_xmlstr(rewrite);
1198 (elem, attr1, val1, attr2, val2)
1199 }
1200 CatalogEntry::DelegatePublic { prefix, catalog } => {
1201 let elem = b"delegatePublic\0" as *const u8 as *mut xmlChar;
1202 let attr1 = b"publicIdStartString\0" as *const u8 as *mut xmlChar;
1203 let val1 = bytes_to_xmlstr(prefix);
1204 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1205 let val2 = bytes_to_xmlstr(catalog);
1206 (elem, attr1, val1, attr2, val2)
1207 }
1208 CatalogEntry::DelegateSystem { prefix, catalog } => {
1209 let elem = b"delegateSystem\0" as *const u8 as *mut xmlChar;
1210 let attr1 = b"systemIdStartString\0" as *const u8 as *mut xmlChar;
1211 let val1 = bytes_to_xmlstr(prefix);
1212 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1213 let val2 = bytes_to_xmlstr(catalog);
1214 (elem, attr1, val1, attr2, val2)
1215 }
1216 CatalogEntry::DelegateURI { prefix, catalog } => {
1217 let elem = b"delegateURI\0" as *const u8 as *mut xmlChar;
1218 let attr1 = b"uriStartString\0" as *const u8 as *mut xmlChar;
1219 let val1 = bytes_to_xmlstr(prefix);
1220 let attr2 = b"catalog\0" as *const u8 as *mut xmlChar;
1221 let val2 = bytes_to_xmlstr(catalog);
1222 (elem, attr1, val1, attr2, val2)
1223 }
1224 CatalogEntry::NextCatalog { catalog } => {
1225 let elem = b"nextCatalog\0" as *const u8 as *mut xmlChar;
1226 let attr1 = b"catalog\0" as *const u8 as *mut xmlChar;
1227 let val1 = bytes_to_xmlstr(catalog);
1228 let attr2 = ptr::null_mut();
1229 let val2 = ptr::null_mut();
1230 (elem, attr1, val1, attr2, val2)
1231 }
1232 };
1233
1234 let child = crate::xml::tree::new_child(root, ptr::null_mut(), elem_name);
1235 if child.is_null() {
1236 if !attr1_value.is_null() {
1238 xmlFreeImpl(attr1_value as *mut c_void);
1239 }
1240 if !attr2_value.is_null() {
1241 xmlFreeImpl(attr2_value as *mut c_void);
1242 }
1243 continue;
1244 }
1245
1246 crate::xml::tree::set_prop(child, attr1_name, attr1_value);
1247 if !attr2_name.is_null() {
1248 crate::xml::tree::set_prop(child, attr2_name, attr2_value);
1249 }
1250
1251 if !attr1_value.is_null() {
1253 xmlFreeImpl(attr1_value as *mut c_void);
1254 }
1255 if !attr2_value.is_null() {
1256 xmlFreeImpl(attr2_value as *mut c_void);
1257 }
1258 }
1259
1260 doc
1261}
1262
1263pub unsafe fn dump_doc() -> *mut _xmlDoc {
1274 let mut doc = convert();
1275 if doc.is_null() {
1276 doc = crate::xml::tree::new_doc(ptr::null_mut());
1278 if doc.is_null() {
1279 return ptr::null_mut();
1280 }
1281 let root =
1282 crate::xml::tree::new_node(ptr::null_mut(), b"catalog\0".as_ptr() as *const xmlChar);
1283 if root.is_null() {
1284 crate::xml::tree::free_doc(doc);
1285 return ptr::null_mut();
1286 }
1287 crate::xml::tree::set_prop(
1288 root,
1289 b"xmlns\0".as_ptr() as *const xmlChar,
1290 b"urn:oasis:names:tc:entity:xmlns:xml:catalog\0".as_ptr() as *const xmlChar,
1291 );
1292 crate::xml::tree::doc_set_root_element(doc, root);
1293 }
1294
1295 let dtd = crate::xml::tree::new_dtd(
1296 doc,
1297 b"catalog\0".as_ptr() as *const xmlChar,
1298 b"-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN\0".as_ptr() as *const xmlChar,
1299 b"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd\0".as_ptr()
1300 as *const xmlChar,
1301 );
1302 if !dtd.is_null() {
1303 (*doc).intSubset = ptr::null_mut();
1304 let dtd_node = dtd as *mut _xmlNode;
1305 let first = (*doc).children;
1306 (*dtd_node).next = first;
1307 (*dtd_node).parent = doc as *mut _xmlNode;
1308 (*dtd_node).doc = doc;
1309 if !first.is_null() {
1310 (*first).prev = dtd_node;
1311 }
1312 (*doc).children = dtd_node;
1313 }
1314 doc
1315}
1316
1317#[repr(C)]
1334pub struct XmlCatalogHandle {
1335 pub entries: Vec<CatalogEntry>,
1336 pub children: Vec<CatalogEntry>,
1337 pub sgml: c_int,
1338}
1339
1340static CATALOG_DEBUG: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
1342
1343static CATALOG_PREFER: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1);
1346
1347#[no_mangle]
1355pub unsafe extern "C" fn xmlNewCatalog(sgml: c_int) -> *mut XmlCatalogHandle {
1356 let h = Box::new(XmlCatalogHandle {
1357 entries: Vec::new(),
1358 children: Vec::new(),
1359 sgml,
1360 });
1361 Box::into_raw(h)
1362}
1363
1364#[no_mangle]
1370pub unsafe extern "C" fn xmlFreeCatalog(catal: *mut XmlCatalogHandle) {
1371 if !catal.is_null() {
1372 unsafe { drop(Box::from_raw(catal)) };
1373 }
1374}
1375
1376#[no_mangle]
1382pub unsafe extern "C" fn xmlLoadACatalog(filename: *const c_char) -> *mut XmlCatalogHandle {
1383 if filename.is_null() {
1384 return ptr::null_mut();
1385 }
1386 let name = unsafe { CStr::from_ptr(filename) };
1387 let name = name.to_str().unwrap_or("");
1388 let mut entries = Vec::new();
1389 if let Some(data) = read_file_bytes(name) {
1390 load_catalog_data(name, &data, &mut entries);
1391 }
1392 if entries.is_empty() {
1393 return ptr::null_mut();
1394 }
1395 Box::into_raw(Box::new(XmlCatalogHandle {
1396 entries,
1397 children: Vec::new(),
1398 sgml: 0,
1399 }))
1400}
1401
1402#[no_mangle]
1409pub unsafe extern "C" fn xmlLoadSGMLSuperCatalog(filename: *const c_char) -> *mut XmlCatalogHandle {
1410 unsafe { xmlLoadACatalog(filename) }
1411}
1412
1413#[no_mangle]
1421pub unsafe extern "C" fn xmlConvertSGMLCatalog(catal: *mut XmlCatalogHandle) -> c_int {
1422 if catal.is_null() {
1423 return -1;
1424 }
1425 unsafe { (*catal).sgml = 0 };
1426 0
1427}
1428
1429#[no_mangle]
1438pub unsafe extern "C" fn xmlACatalogAdd(
1439 catal: *mut XmlCatalogHandle,
1440 type_: *const xmlChar,
1441 orig: *const xmlChar,
1442 replace: *const xmlChar,
1443) -> c_int {
1444 if catal.is_null() || type_.is_null() || orig.is_null() || replace.is_null() {
1445 return -1;
1446 }
1447 if unsafe { (*catal).entries.is_empty() } {
1453 return -1;
1454 }
1455 let t = xmlstr_to_bytes(type_);
1456 let o = xmlstr_to_bytes(orig).to_vec();
1457 let r = xmlstr_to_bytes(replace).to_vec();
1458 let entry = if t == b"public" {
1459 CatalogEntry::Public {
1460 public_id: o,
1461 uri: r,
1462 }
1463 } else if t == b"system" {
1464 CatalogEntry::System {
1465 system_id: o,
1466 uri: r,
1467 }
1468 } else if t == b"rewriteSystem" {
1469 CatalogEntry::RewriteSystem {
1470 prefix: o,
1471 rewrite: r,
1472 }
1473 } else if t == b"rewriteURI" {
1474 CatalogEntry::RewriteURI {
1475 prefix: o,
1476 rewrite: r,
1477 }
1478 } else if t == b"delegatePublic" {
1479 CatalogEntry::DelegatePublic {
1480 prefix: o,
1481 catalog: r,
1482 }
1483 } else if t == b"delegateSystem" {
1484 CatalogEntry::DelegateSystem {
1485 prefix: o,
1486 catalog: r,
1487 }
1488 } else if t == b"delegateURI" {
1489 CatalogEntry::DelegateURI {
1490 prefix: o,
1491 catalog: r,
1492 }
1493 } else if t == b"nextCatalog" {
1494 CatalogEntry::NextCatalog { catalog: r }
1495 } else {
1496 return -1;
1497 };
1498 unsafe {
1499 (*catal).entries.push(entry.clone());
1500 (*catal).children.push(entry);
1501 };
1502 0
1503}
1504
1505#[no_mangle]
1511pub unsafe extern "C" fn xmlACatalogRemove(
1512 catal: *mut XmlCatalogHandle,
1513 value: *const xmlChar,
1514) -> c_int {
1515 if catal.is_null() || value.is_null() {
1516 return -1;
1517 }
1518 let v = xmlstr_to_bytes(value);
1519 let entries = unsafe { &mut (*catal).entries };
1520 let before = entries.len();
1521 entries.retain(|entry| match entry {
1522 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != v,
1523 CatalogEntry::System { system_id, .. } => system_id.as_slice() != v,
1524 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != v,
1525 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != v,
1526 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != v,
1527 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != v,
1528 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != v,
1529 CatalogEntry::NextCatalog { .. } => true,
1530 });
1531 let children = unsafe { &mut (*catal).children };
1532 children.retain(|entry| match entry {
1533 CatalogEntry::Public { public_id, .. } => public_id.as_slice() != v,
1534 CatalogEntry::System { system_id, .. } => system_id.as_slice() != v,
1535 CatalogEntry::RewriteSystem { prefix, .. } => prefix.as_slice() != v,
1536 CatalogEntry::RewriteURI { prefix, .. } => prefix.as_slice() != v,
1537 CatalogEntry::DelegatePublic { prefix, .. } => prefix.as_slice() != v,
1538 CatalogEntry::DelegateSystem { prefix, .. } => prefix.as_slice() != v,
1539 CatalogEntry::DelegateURI { prefix, .. } => prefix.as_slice() != v,
1540 CatalogEntry::NextCatalog { .. } => true,
1541 });
1542 if entries.len() == before {
1543 0
1544 } else {
1545 0
1546 }
1547}
1548
1549#[no_mangle]
1561pub unsafe extern "C" fn xmlACatalogResolve(
1562 catal: *mut XmlCatalogHandle,
1563 pubID: *const xmlChar,
1564 sysID: *const xmlChar,
1565) -> *mut xmlChar {
1566 if catal.is_null() {
1567 return ptr::null_mut();
1568 }
1569 let entries = unsafe { &(*catal).entries };
1570 if !sysID.is_null() {
1571 let b = xmlstr_to_bytes(sysID);
1572 if let Some(r) = unsafe { resolve_system_entries(entries, &b) } {
1573 return bytes_to_xmlstr(&r);
1574 }
1575 }
1576 if !pubID.is_null() {
1577 let b = xmlstr_to_bytes(pubID);
1578 if let Some(r) = unsafe { resolve_public_entries(entries, &b) } {
1579 return bytes_to_xmlstr(&r);
1580 }
1581 }
1582 ptr::null_mut()
1583}
1584
1585#[no_mangle]
1587pub unsafe extern "C" fn xmlACatalogResolveSystem(
1588 catal: *mut XmlCatalogHandle,
1589 sysID: *const xmlChar,
1590) -> *mut xmlChar {
1591 if catal.is_null() || sysID.is_null() {
1592 return ptr::null_mut();
1593 }
1594 let entries = unsafe { &(*catal).entries };
1595 let b = xmlstr_to_bytes(sysID);
1596 unsafe { resolve_system_entries(entries, &b) }
1597 .as_ref()
1598 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1599}
1600
1601#[no_mangle]
1603pub unsafe extern "C" fn xmlACatalogResolvePublic(
1604 catal: *mut XmlCatalogHandle,
1605 pubID: *const xmlChar,
1606) -> *mut xmlChar {
1607 if catal.is_null() || pubID.is_null() {
1608 return ptr::null_mut();
1609 }
1610 let entries = unsafe { &(*catal).entries };
1611 let b = xmlstr_to_bytes(pubID);
1612 unsafe { resolve_public_entries(entries, &b) }
1613 .as_ref()
1614 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1615}
1616
1617#[no_mangle]
1619pub unsafe extern "C" fn xmlACatalogResolveURI(
1620 catal: *mut XmlCatalogHandle,
1621 URI: *const xmlChar,
1622) -> *mut xmlChar {
1623 if catal.is_null() || URI.is_null() {
1624 return ptr::null_mut();
1625 }
1626 let entries = unsafe { &(*catal).entries };
1627 let b = xmlstr_to_bytes(URI);
1628 unsafe { resolve_uri_entries(entries, &b) }
1629 .as_ref()
1630 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1631}
1632
1633#[no_mangle]
1635pub unsafe extern "C" fn xmlCatalogIsEmpty(catal: *mut XmlCatalogHandle) -> c_int {
1636 if catal.is_null() {
1637 return 1;
1638 }
1639 unsafe { (*catal).children.is_empty() as c_int }
1643}
1644
1645#[no_mangle]
1651pub unsafe extern "C" fn xmlACatalogDump(catal: *mut XmlCatalogHandle, out: *mut libc::FILE) {
1652 if catal.is_null() || out.is_null() {
1653 return;
1654 }
1655 let entries = unsafe { &(*catal).entries };
1656 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");
1657 for e in entries {
1658 match e {
1659 CatalogEntry::Public { public_id, uri } => {
1660 text.push_str(&format!(
1661 " <public publicId=\"{}\" uri=\"{}\"/>\n",
1662 String::from_utf8_lossy(public_id),
1663 String::from_utf8_lossy(uri)
1664 ));
1665 }
1666 CatalogEntry::System { system_id, uri } => {
1667 text.push_str(&format!(
1668 " <system systemId=\"{}\" uri=\"{}\"/>\n",
1669 String::from_utf8_lossy(system_id),
1670 String::from_utf8_lossy(uri)
1671 ));
1672 }
1673 CatalogEntry::RewriteSystem { prefix, rewrite } => {
1674 text.push_str(&format!(
1675 " <rewriteSystem systemIdStartString=\"{}\" rewritePrefix=\"{}\"/>\n",
1676 String::from_utf8_lossy(prefix),
1677 String::from_utf8_lossy(rewrite)
1678 ));
1679 }
1680 CatalogEntry::RewriteURI { prefix, rewrite } => {
1681 text.push_str(&format!(
1682 " <rewriteURI uriStartString=\"{}\" rewritePrefix=\"{}\"/>\n",
1683 String::from_utf8_lossy(prefix),
1684 String::from_utf8_lossy(rewrite)
1685 ));
1686 }
1687 _ => {}
1688 }
1689 }
1690 text.push_str("</catalog>\n");
1691 let bytes = text.into_bytes();
1692 unsafe {
1693 libc::fwrite(bytes.as_ptr() as *const libc::c_void, 1, bytes.len(), out);
1694 }
1695}
1696
1697#[no_mangle]
1699pub unsafe extern "C" fn xmlInitializeCatalog() {
1700 crate::xml::catalog::init();
1701}
1702
1703#[no_mangle]
1705pub unsafe extern "C" fn xmlCatalogDumpDoc() -> *mut _xmlDoc {
1706 unsafe { dump_doc() }
1707}
1708
1709#[no_mangle]
1712pub unsafe extern "C" fn xmlCatalogSetDebug(level: c_int) -> c_int {
1713 let old = CATALOG_DEBUG.load(std::sync::atomic::Ordering::Relaxed);
1714 if level <= 0 {
1715 CATALOG_DEBUG.store(0, std::sync::atomic::Ordering::Relaxed);
1716 } else {
1717 CATALOG_DEBUG.store(level, std::sync::atomic::Ordering::Relaxed);
1718 }
1719 old
1720}
1721
1722#[no_mangle]
1725pub unsafe extern "C" fn xmlCatalogSetDefaultPrefer(prefer: c_int) -> c_int {
1726 let old = CATALOG_PREFER.load(std::sync::atomic::Ordering::Relaxed);
1727 if prefer == 0 {
1728 return old;
1729 }
1730 CATALOG_PREFER.store(prefer, std::sync::atomic::Ordering::Relaxed);
1731 old
1732}
1733
1734#[no_mangle]
1741pub unsafe extern "C" fn xmlCatalogResolve(
1742 pubID: *const xmlChar,
1743 sysID: *const xmlChar,
1744) -> *mut xmlChar {
1745 if !sysID.is_null() {
1746 let r = unsafe { resolve_system(sysID) };
1747 if !r.is_null() {
1748 return r;
1749 }
1750 }
1751 if !pubID.is_null() {
1752 return unsafe { resolve_public(pubID) };
1753 }
1754 ptr::null_mut()
1755}
1756
1757#[no_mangle]
1760pub unsafe extern "C" fn xmlCatalogGetSystem(sysID: *const xmlChar) -> *const xmlChar {
1761 unsafe { resolve_system(sysID) }
1762}
1763
1764#[no_mangle]
1765pub unsafe extern "C" fn xmlCatalogGetPublic(pubID: *const xmlChar) -> *const xmlChar {
1766 unsafe { resolve_public(pubID) }
1767}
1768
1769#[no_mangle]
1775pub unsafe extern "C" fn xmlParseCatalogFile(filename: *const c_char) -> *mut _xmlDoc {
1776 if filename.is_null() {
1777 return ptr::null_mut();
1778 }
1779 unsafe { dump_doc() }
1780}
1781
1782#[no_mangle]
1786pub unsafe extern "C" fn xmlCatalogAddLocal(
1787 catalogs: *mut c_void,
1788 URL: *const xmlChar,
1789) -> *mut c_void {
1790 if URL.is_null() {
1791 return catalogs;
1792 }
1793 let list: *mut Vec<CatalogEntry> = if catalogs.is_null() {
1794 Box::into_raw(Box::new(Vec::<CatalogEntry>::new()))
1795 } else {
1796 catalogs as *mut Vec<CatalogEntry>
1797 };
1798 let url = xmlstr_to_bytes(URL);
1799 let url_str = String::from_utf8_lossy(&url).into_owned();
1800 let entries = unsafe { &mut *(list as *mut Vec<CatalogEntry>) };
1801 if let Some(data) = read_file_bytes(&url_str) {
1802 let mut temp = Vec::new();
1803 load_catalog_data(&url_str, &data, &mut temp);
1804 entries.extend(temp);
1805 }
1806 list as *mut c_void
1807}
1808
1809#[no_mangle]
1815pub unsafe extern "C" fn xmlCatalogFreeLocal(catalogs: *mut c_void) {
1816 if !catalogs.is_null() {
1817 unsafe { drop(Box::from_raw(catalogs as *mut Vec<CatalogEntry>)) };
1818 }
1819}
1820
1821#[no_mangle]
1823pub unsafe extern "C" fn xmlCatalogLocalResolve(
1824 catalogs: *mut c_void,
1825 pubID: *const xmlChar,
1826 sysID: *const xmlChar,
1827) -> *mut xmlChar {
1828 if catalogs.is_null() {
1829 return ptr::null_mut();
1830 }
1831 let entries = unsafe { &*(catalogs as *const Vec<CatalogEntry>) };
1832 if !sysID.is_null() {
1834 let b = xmlstr_to_bytes(sysID);
1835 if let Some(r) = unsafe { resolve_system_entries(entries, &b) } {
1836 return bytes_to_xmlstr(&r);
1837 }
1838 }
1839 if !pubID.is_null() {
1840 let b = xmlstr_to_bytes(pubID);
1841 if let Some(r) = unsafe { resolve_public_entries(entries, &b) } {
1842 return bytes_to_xmlstr(&r);
1843 }
1844 }
1845 ptr::null_mut()
1846}
1847
1848#[no_mangle]
1850pub unsafe extern "C" fn xmlCatalogLocalResolveURI(
1851 catalogs: *mut c_void,
1852 URI: *const xmlChar,
1853) -> *mut xmlChar {
1854 if catalogs.is_null() || URI.is_null() {
1855 return ptr::null_mut();
1856 }
1857 let entries = unsafe { &*(catalogs as *const Vec<CatalogEntry>) };
1858 let b = xmlstr_to_bytes(URI);
1859 unsafe { resolve_uri_entries(entries, &b) }
1860 .as_ref()
1861 .map_or(ptr::null_mut(), |r| bytes_to_xmlstr(r))
1862}
1863
1864#[cfg(test)]
1869mod tests {
1870 use super::*;
1871 use crate::abi::allocator::xmlFreeImpl;
1872 use crate::xml::string::xmlstr_to_bytes;
1873 use std::ffi::CString;
1874 use std::sync::Mutex;
1875
1876 static CATALOG_TEST_MUTEX: Mutex<()> = Mutex::new(());
1885
1886 unsafe fn to_xmlstr(s: &[u8]) -> *const xmlChar {
1888 let ptr = bytes_to_xmlstr(s);
1889 ptr as *const xmlChar
1890 }
1891
1892 unsafe fn to_xmlstr_str(s: &str) -> *const xmlChar {
1894 to_xmlstr(s.as_bytes())
1895 }
1896
1897 unsafe fn free_xmlstr(ptr: *const xmlChar) {
1898 if !ptr.is_null() {
1899 xmlFreeImpl(ptr as *mut c_void);
1900 }
1901 }
1902
1903 fn setup() -> std::sync::MutexGuard<'static, ()> {
1910 let guard = CATALOG_TEST_MUTEX.lock().unwrap();
1911 cleanup();
1912 init();
1913 set_defaults(XML_CATA_ALLOW_ALL);
1915 guard
1916 }
1917
1918 fn teardown(_guard: std::sync::MutexGuard<'static, ()>) {
1919 cleanup();
1920 }
1922
1923 #[test]
1926 fn test_resolve_public_basic() {
1927 let _guard = setup();
1928 unsafe {
1929 let type_ = to_xmlstr_str("public");
1931 let pub_id = to_xmlstr_str("-//OASIS//DTD DocBook XML V4.2//EN");
1932 let uri = to_xmlstr_str("http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd");
1933 assert_eq!(add(type_, pub_id, uri), 0);
1934
1935 let result = resolve_public(pub_id);
1937 assert!(!result.is_null());
1938 assert_eq!(
1939 xmlstr_to_bytes(result),
1940 b"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
1941 );
1942 xmlFreeImpl(result as *mut c_void);
1943
1944 let unknown = to_xmlstr_str("-//Unknown//DTD Unknown//EN");
1946 assert!(resolve_public(unknown).is_null());
1947 free_xmlstr(unknown);
1948
1949 free_xmlstr(type_);
1950 free_xmlstr(pub_id);
1951 free_xmlstr(uri);
1952 teardown(_guard);
1953 }
1954 }
1955
1956 #[test]
1959 fn test_resolve_system_basic() {
1960 let _guard = setup();
1961 unsafe {
1962 let type_ = to_xmlstr_str("system");
1963 let sys_id = to_xmlstr_str("http://example.com/foo.dtd");
1964 let uri = to_xmlstr_str("/local/foo.dtd");
1965 assert_eq!(add(type_, sys_id, uri), 0);
1966
1967 let result = resolve_system(sys_id);
1968 assert!(!result.is_null());
1969 assert_eq!(xmlstr_to_bytes(result), b"/local/foo.dtd");
1970 xmlFreeImpl(result as *mut c_void);
1971
1972 free_xmlstr(type_);
1973 free_xmlstr(sys_id);
1974 free_xmlstr(uri);
1975 teardown(_guard);
1976 }
1977 }
1978
1979 #[test]
1982 fn test_resolve_uri_basic() {
1983 let _guard = setup();
1984 unsafe {
1985 let type_ = to_xmlstr_str("system");
1987 let sys_id = to_xmlstr_str("http://example.com/resource.xml");
1988 let uri = to_xmlstr_str("/local/resource.xml");
1989 assert_eq!(add(type_, sys_id, uri), 0);
1990
1991 let result = resolve_uri(sys_id);
1992 assert!(!result.is_null());
1993 assert_eq!(xmlstr_to_bytes(result), b"/local/resource.xml");
1994 xmlFreeImpl(result as *mut c_void);
1995
1996 free_xmlstr(type_);
1997 free_xmlstr(sys_id);
1998 free_xmlstr(uri);
1999 teardown(_guard);
2000 }
2001 }
2002
2003 #[test]
2006 fn test_rewrite_system() {
2007 let _guard = setup();
2008 unsafe {
2009 let type_ = to_xmlstr_str("rewriteSystem");
2010 let prefix = to_xmlstr_str("http://example.com/old/");
2011 let rewrite = to_xmlstr_str("http://mirror.example.com/new/");
2012 assert_eq!(add(type_, prefix, rewrite), 0);
2013
2014 let sys_id = to_xmlstr_str("http://example.com/old/path/file.xml");
2015 let result = resolve_system(sys_id);
2016 assert!(!result.is_null());
2017 assert_eq!(
2018 xmlstr_to_bytes(result),
2019 b"http://mirror.example.com/new/path/file.xml"
2020 );
2021 xmlFreeImpl(result as *mut c_void);
2022
2023 free_xmlstr(type_);
2024 free_xmlstr(prefix);
2025 free_xmlstr(rewrite);
2026 free_xmlstr(sys_id);
2027 teardown(_guard);
2028 }
2029 }
2030
2031 #[test]
2034 fn test_rewrite_uri() {
2035 let _guard = setup();
2036 unsafe {
2037 let type_ = to_xmlstr_str("rewriteURI");
2038 let prefix = to_xmlstr_str("http://example.com/old/");
2039 let rewrite = to_xmlstr_str("http://mirror.example.com/new/");
2040 assert_eq!(add(type_, prefix, rewrite), 0);
2041
2042 let uri = to_xmlstr_str("http://example.com/old/path/file.xml");
2043 let result = resolve_uri(uri);
2044 assert!(!result.is_null());
2045 assert_eq!(
2046 xmlstr_to_bytes(result),
2047 b"http://mirror.example.com/new/path/file.xml"
2048 );
2049 xmlFreeImpl(result as *mut c_void);
2050
2051 free_xmlstr(type_);
2052 free_xmlstr(prefix);
2053 free_xmlstr(rewrite);
2054 free_xmlstr(uri);
2055 teardown(_guard);
2056 }
2057 }
2058
2059 #[test]
2062 fn test_remove_entries() {
2063 let _guard = setup();
2064 unsafe {
2065 let type_ = to_xmlstr_str("public");
2066 let pub_id = to_xmlstr_str("-//TEST//PUBLIC//EN");
2067 let uri = to_xmlstr_str("test.dtd");
2068 assert_eq!(add(type_, pub_id, uri), 0);
2069
2070 assert!(!resolve_public(pub_id).is_null());
2072
2073 assert_eq!(remove(pub_id), 1);
2075
2076 assert!(resolve_public(pub_id).is_null());
2078
2079 free_xmlstr(type_);
2080 free_xmlstr(pub_id);
2081 free_xmlstr(uri);
2082 teardown(_guard);
2083 }
2084 }
2085
2086 #[test]
2089 fn test_catalog_defaults() {
2090 let _guard = setup();
2091
2092 assert_eq!(get_defaults(), XML_CATA_ALLOW_ALL);
2093
2094 set_defaults(XML_CATA_ALLOW_NONE);
2095 assert_eq!(get_defaults(), XML_CATA_ALLOW_NONE);
2096
2097 set_defaults(XML_CATA_ALLOW_GLOBAL);
2098 assert_eq!(get_defaults(), XML_CATA_ALLOW_GLOBAL);
2099
2100 set_defaults(XML_CATA_ALLOW_ALL);
2101 assert_eq!(get_defaults(), XML_CATA_ALLOW_ALL);
2102
2103 teardown(_guard);
2104 }
2105
2106 #[test]
2109 fn test_parse_xml_catalog_in_memory() {
2110 let _guard = setup();
2111 unsafe {
2112 let catalog_xml = br#"<?xml version="1.0"?>
2113<!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN" "http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd">
2114<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
2115 <public publicId="-//OASIS//DTD DocBook XML V4.2//EN" uri="http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"/>
2116 <system systemId="http://example.com/foo.dtd" uri="/local/foo.dtd"/>
2117 <rewriteSystem systemIdStartString="http://example.com/old/" rewritePrefix="http://mirror.example.com/new/"/>
2118 <rewriteURI uriStartString="http://example.com/old/" rewritePrefix="http://mirror.example.com/new/"/>
2119</catalog>"#;
2120
2121 let mut entries = Vec::new();
2123 parse_xml_catalog(catalog_xml, &mut entries);
2124 assert_eq!(entries.len(), 4);
2125
2126 match &entries[0] {
2128 CatalogEntry::Public { public_id, uri } => {
2129 assert_eq!(public_id.as_slice(), b"-//OASIS//DTD DocBook XML V4.2//EN");
2130 assert_eq!(
2131 uri.as_slice(),
2132 b"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
2133 );
2134 }
2135 _ => panic!("Expected Public entry"),
2136 }
2137
2138 match &entries[1] {
2140 CatalogEntry::System { system_id, uri } => {
2141 assert_eq!(system_id.as_slice(), b"http://example.com/foo.dtd");
2142 assert_eq!(uri.as_slice(), b"/local/foo.dtd");
2143 }
2144 _ => panic!("Expected System entry"),
2145 }
2146
2147 match &entries[2] {
2149 CatalogEntry::RewriteSystem { prefix, rewrite } => {
2150 assert_eq!(prefix.as_slice(), b"http://example.com/old/");
2151 assert_eq!(rewrite.as_slice(), b"http://mirror.example.com/new/");
2152 }
2153 _ => panic!("Expected RewriteSystem entry"),
2154 }
2155
2156 match &entries[3] {
2158 CatalogEntry::RewriteURI { prefix, rewrite } => {
2159 assert_eq!(prefix.as_slice(), b"http://example.com/old/");
2160 assert_eq!(rewrite.as_slice(), b"http://mirror.example.com/new/");
2161 }
2162 _ => panic!("Expected RewriteURI entry"),
2163 }
2164
2165 teardown(_guard);
2166 }
2167 }
2168
2169 #[test]
2172 fn test_parse_sgml_catalog() {
2173 let _guard = setup();
2174 unsafe {
2175 let sgml_data = br#"-- SGML catalog
2176PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "docbookx.dtd"
2177SYSTEM "http://example.com/foo.dtd" "/local/foo.dtd"
2178URI "http://example.com/resource" "/local/resource"
2179"#;
2180
2181 let mut entries = Vec::new();
2182 parse_sgml_catalog(sgml_data, &mut entries);
2183 assert_eq!(entries.len(), 3);
2184
2185 match &entries[0] {
2187 CatalogEntry::Public { public_id, uri } => {
2188 assert_eq!(public_id.as_slice(), b"-//OASIS//DTD DocBook XML V4.2//EN");
2189 assert_eq!(uri.as_slice(), b"docbookx.dtd");
2190 }
2191 _ => panic!("Expected Public entry"),
2192 }
2193
2194 match &entries[1] {
2196 CatalogEntry::System { system_id, uri } => {
2197 assert_eq!(system_id.as_slice(), b"http://example.com/foo.dtd");
2198 assert_eq!(uri.as_slice(), b"/local/foo.dtd");
2199 }
2200 _ => panic!("Expected System entry"),
2201 }
2202
2203 match &entries[2] {
2205 CatalogEntry::System { system_id, uri } => {
2206 assert_eq!(system_id.as_slice(), b"http://example.com/resource");
2207 assert_eq!(uri.as_slice(), b"/local/resource");
2208 }
2209 _ => panic!("Expected System entry for URI"),
2210 }
2211
2212 teardown(_guard);
2213 }
2214 }
2215
2216 #[test]
2219 fn test_resolution_precedence() {
2220 let _guard = setup();
2221 unsafe {
2222 let type_sys = to_xmlstr_str("system");
2224 let sys_id = to_xmlstr_str("http://example.com/target.xml");
2225 let uri_direct = to_xmlstr_str("/direct/uri.xml");
2226 assert_eq!(add(type_sys, sys_id, uri_direct), 0);
2227
2228 let type_rw = to_xmlstr_str("rewriteSystem");
2230 let prefix = to_xmlstr_str("http://example.com/");
2231 let rewrite = to_xmlstr_str("/rewrite/");
2232 assert_eq!(add(type_rw, prefix, rewrite), 0);
2233
2234 let result = resolve_system(sys_id);
2236 assert!(!result.is_null());
2237 assert_eq!(xmlstr_to_bytes(result), b"/direct/uri.xml");
2238 xmlFreeImpl(result as *mut c_void);
2239
2240 free_xmlstr(type_sys);
2241 free_xmlstr(sys_id);
2242 free_xmlstr(uri_direct);
2243 free_xmlstr(type_rw);
2244 free_xmlstr(prefix);
2245 free_xmlstr(rewrite);
2246 teardown(_guard);
2247 }
2248 }
2249
2250 #[test]
2253 fn test_convert_sgml_to_xml() {
2254 let _guard = setup();
2255 unsafe {
2256 let type_ = to_xmlstr_str("public");
2257 let pub_id = to_xmlstr_str("-//TEST//PUBLIC//EN");
2258 let uri = to_xmlstr_str("test.dtd");
2259 assert_eq!(add(type_, pub_id, uri), 0);
2260
2261 let doc = convert();
2262 assert!(!doc.is_null());
2263
2264 let root = crate::xml::tree::doc_get_root_element(doc);
2266 assert!(!root.is_null());
2267 let root_name = crate::xml::string::xmlstr_to_bytes((*root).name);
2268 assert_eq!(root_name, b"catalog");
2269
2270 let child = (*root).children;
2272 assert!(!child.is_null());
2273 let child_name = crate::xml::string::xmlstr_to_bytes((*child).name);
2274 assert_eq!(child_name, b"public");
2275
2276 crate::xml::tree::free_doc(doc);
2277 free_xmlstr(type_);
2278 free_xmlstr(pub_id);
2279 free_xmlstr(uri);
2280 teardown(_guard);
2281 }
2282 }
2283
2284 #[test]
2287 fn test_catalog_disallowed() {
2288 let _guard = setup();
2289 unsafe {
2290 let type_ = to_xmlstr_str("system");
2292 let sys_id = to_xmlstr_str("http://example.com/test.dtd");
2293 let uri = to_xmlstr_str("/local/test.dtd");
2294 add(type_, sys_id, uri);
2295
2296 set_defaults(XML_CATA_ALLOW_NONE);
2298
2299 assert!(resolve_system(sys_id).is_null());
2301 assert!(resolve_public(sys_id).is_null());
2302 assert!(resolve_uri(sys_id).is_null());
2303
2304 set_defaults(XML_CATA_ALLOW_ALL);
2305 free_xmlstr(type_);
2306 free_xmlstr(sys_id);
2307 free_xmlstr(uri);
2308 teardown(_guard);
2309 }
2310 }
2311
2312 #[test]
2315 fn test_init_cleanup() {
2316 let _guard = CATALOG_TEST_MUTEX.lock().unwrap();
2317 cleanup();
2318 assert_eq!(CATALOG_STATE.read().initialized, false);
2319
2320 init();
2321 assert_eq!(CATALOG_STATE.read().initialized, true);
2322
2323 cleanup();
2324 assert_eq!(CATALOG_STATE.read().initialized, false);
2325 }
2326
2327 #[test]
2330 fn test_parse_xml_catalog_group() {
2331 let catalog_xml = br#"<?xml version="1.0"?>
2332<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
2333 <group>
2334 <public publicId="-//GROUP//PUBLIC//EN" uri="group.dtd"/>
2335 <system systemId="http://group.example.com/" uri="/group/"/>
2336 </group>
2337</catalog>"#;
2338
2339 let mut entries = Vec::new();
2340 parse_xml_catalog(catalog_xml, &mut entries);
2341 assert_eq!(entries.len(), 2);
2342
2343 match &entries[0] {
2344 CatalogEntry::Public { public_id, .. } => {
2345 assert_eq!(public_id.as_slice(), b"-//GROUP//PUBLIC//EN");
2346 }
2347 _ => panic!("Expected Public entry"),
2348 }
2349
2350 match &entries[1] {
2351 CatalogEntry::System { system_id, .. } => {
2352 assert_eq!(system_id.as_slice(), b"http://group.example.com/");
2353 }
2354 _ => panic!("Expected System entry"),
2355 }
2356 }
2357
2358 #[test]
2361 fn test_multiple_entries() {
2362 let _guard = setup();
2363 unsafe {
2364 let t = to_xmlstr_str("public");
2366 let id1 = to_xmlstr_str("-//A//PUBLIC//EN");
2367 let uri1 = to_xmlstr_str("a.dtd");
2368 let id2 = to_xmlstr_str("-//B//PUBLIC//EN");
2369 let uri2 = to_xmlstr_str("b.dtd");
2370
2371 assert_eq!(add(t, id1, uri1), 0);
2372 assert_eq!(add(t, id2, uri2), 0);
2373
2374 let r1 = resolve_public(id1);
2375 assert!(!r1.is_null());
2376 assert_eq!(xmlstr_to_bytes(r1), b"a.dtd");
2377 xmlFreeImpl(r1 as *mut c_void);
2378
2379 let r2 = resolve_public(id2);
2380 assert!(!r2.is_null());
2381 assert_eq!(xmlstr_to_bytes(r2), b"b.dtd");
2382 xmlFreeImpl(r2 as *mut c_void);
2383
2384 free_xmlstr(t);
2385 free_xmlstr(id1);
2386 free_xmlstr(uri1);
2387 free_xmlstr(id2);
2388 free_xmlstr(uri2);
2389 teardown(_guard);
2390 }
2391 }
2392
2393 #[test]
2396 fn test_longest_prefix_wins() {
2397 let _guard = setup();
2398 unsafe {
2399 let t = to_xmlstr_str("rewriteSystem");
2400 let p1 = to_xmlstr_str("http://example.com/");
2401 let r1 = to_xmlstr_str("/general/");
2402 let p2 = to_xmlstr_str("http://example.com/specific/");
2403 let r2 = to_xmlstr_str("/specific/");
2404
2405 add(t, p1, r1);
2406 add(t, p2, r2);
2407
2408 let sys_id = to_xmlstr_str("http://example.com/specific/file.xml");
2409 let result = resolve_system(sys_id);
2410 assert!(!result.is_null());
2411 assert_eq!(xmlstr_to_bytes(result), b"/specific/file.xml");
2412 xmlFreeImpl(result as *mut c_void);
2413
2414 free_xmlstr(t);
2415 free_xmlstr(p1);
2416 free_xmlstr(r1);
2417 free_xmlstr(p2);
2418 free_xmlstr(r2);
2419 free_xmlstr(sys_id);
2420 teardown(_guard);
2421 }
2422 }
2423}
2424
2425#[cfg(test)]
2430mod c_abi_tests {
2431 use super::*;
2432 use crate::abi::allocator::xmlFreeImpl;
2433
2434 fn cstr(s: &[u8]) -> *const xmlChar {
2435 s.as_ptr() as *const xmlChar
2436 }
2437
2438 #[test]
2439 fn test_new_free_catalog() {
2440 unsafe {
2441 let h = xmlNewCatalog(0);
2442 assert!(!h.is_null());
2443 assert_eq!(xmlCatalogIsEmpty(h), 1);
2444 xmlFreeCatalog(h);
2445 xmlFreeCatalog(ptr::null_mut());
2446 }
2447 }
2448
2449 #[test]
2450 fn test_acatalog_add_resolve_remove() {
2451 unsafe {
2452 let h = xmlNewCatalog(0);
2455 assert!(!h.is_null());
2456 assert_eq!(
2457 xmlACatalogAdd(
2458 h,
2459 cstr(b"system\0"),
2460 cstr(b"http://x\0"),
2461 cstr(b"file:///x\0")
2462 ),
2463 -1
2464 );
2465 xmlFreeCatalog(h);
2466
2467 let h = xmlNewCatalog(0);
2470 assert!(!h.is_null());
2471 (*h).entries.push(CatalogEntry::System {
2472 system_id: b"http://example.com/foo\0".to_vec(),
2473 uri: b"file:///tmp/foo.xml\0".to_vec(),
2474 });
2475 assert_eq!(
2476 xmlACatalogAdd(
2477 h,
2478 cstr(b"system\0"),
2479 cstr(b"http://example.com/foo\0"),
2480 cstr(b"file:///tmp/foo.xml\0")
2481 ),
2482 0
2483 );
2484 assert_eq!(xmlCatalogIsEmpty(h), 0);
2485 let r = xmlACatalogResolveSystem(h, cstr(b"http://example.com/foo\0"));
2487 assert!(!r.is_null());
2488 let bytes = xmlstr_to_bytes(r);
2489 assert_eq!(bytes, b"file:///tmp/foo.xml");
2490 xmlFreeImpl(r as *mut libc::c_void);
2491 let r2 = xmlACatalogResolveURI(h, cstr(b"http://example.com/foo\0"));
2493 assert!(!r2.is_null());
2494 xmlFreeImpl(r2 as *mut libc::c_void);
2495 assert_eq!(
2497 xmlACatalogAdd(h, cstr(b"bogus\0"), cstr(b"a\0"), cstr(b"b\0")),
2498 -1
2499 );
2500 assert_eq!(xmlACatalogRemove(h, cstr(b"http://example.com/foo\0")), 0);
2502 assert_eq!(xmlCatalogIsEmpty(h), 1);
2503 xmlFreeCatalog(h);
2504 }
2505 }
2506
2507 #[test]
2508 fn test_acatalog_public_and_rewrite() {
2509 unsafe {
2510 let h = xmlNewCatalog(0);
2511 assert!(!h.is_null());
2512 (*h).entries.push(CatalogEntry::Public {
2514 public_id: b"-//OASIS//DTD X//EN\0".to_vec(),
2515 uri: b"file:///dtd/x.dtd\0".to_vec(),
2516 });
2517 assert_eq!(
2518 xmlACatalogAdd(
2519 h,
2520 cstr(b"public\0"),
2521 cstr(b"-//OASIS//DTD X//EN\0"),
2522 cstr(b"file:///dtd/x.dtd\0")
2523 ),
2524 0
2525 );
2526 assert_eq!(
2527 xmlACatalogAdd(
2528 h,
2529 cstr(b"rewriteSystem\0"),
2530 cstr(b"http://old/\0"),
2531 cstr(b"http://new/\0")
2532 ),
2533 0
2534 );
2535 let r = xmlACatalogResolvePublic(h, cstr(b"-//OASIS//DTD X//EN\0"));
2536 assert!(!r.is_null());
2537 assert_eq!(xmlstr_to_bytes(r), b"file:///dtd/x.dtd");
2538 xmlFreeImpl(r as *mut libc::c_void);
2539 let r2 = xmlACatalogResolveSystem(h, cstr(b"http://old/foo.xml\0"));
2540 assert!(!r2.is_null());
2541 assert_eq!(xmlstr_to_bytes(r2), b"http://new/foo.xml");
2542 xmlFreeImpl(r2 as *mut libc::c_void);
2543 xmlFreeCatalog(h);
2544 }
2545 }
2546
2547 #[test]
2548 fn test_catalog_set_debug_and_prefer() {
2549 unsafe {
2550 assert_eq!(xmlCatalogSetDefaultPrefer(1), 1);
2553 assert_eq!(xmlCatalogSetDefaultPrefer(2), 1);
2554 assert_eq!(xmlCatalogSetDefaultPrefer(0), 2);
2555 assert_eq!(xmlCatalogSetDefaultPrefer(1), 2);
2556 assert_eq!(xmlCatalogSetDebug(0), 0);
2557 assert_eq!(xmlCatalogSetDebug(7), 0);
2558 assert_eq!(xmlCatalogSetDebug(0), 7);
2559 }
2560 }
2561
2562 #[test]
2563 fn test_catalog_local_resolve() {
2564 unsafe {
2565 assert!(xmlCatalogLocalResolve(ptr::null_mut(), cstr(b"x\0"), cstr(b"y\0")).is_null());
2567 assert!(xmlCatalogLocalResolveURI(ptr::null_mut(), cstr(b"x\0")).is_null());
2568 xmlCatalogFreeLocal(ptr::null_mut());
2569 }
2570 }
2571
2572 #[test]
2573 fn test_catalog_resolve_global_null() {
2574 unsafe {
2575 assert!(xmlCatalogResolve(ptr::null(), ptr::null()).is_null());
2576 }
2577 }
2578}