1use core::ffi::c_void;
32use core::fmt::Write;
33use core::ptr;
34use std::os::raw::{c_char, c_int};
35
36use crate::abi::callbacks::{xmlGenericErrorFunc, xmlStructuredErrorFunc};
37use crate::abi::structs::_xmlError;
38use crate::abi::types::xmlErrorLevel::*;
39use crate::abi::types::*;
40use crate::xml::globals;
41
42pub unsafe fn set_generic_error_func(ctx: *mut c_void, handler: Option<xmlGenericErrorFunc>) {
59 unsafe { globals::set_generic_error_func(ctx, handler) };
61}
62
63pub unsafe fn set_structured_error_func(ctx: *mut c_void, handler: Option<xmlStructuredErrorFunc>) {
75 unsafe { globals::set_structured_error_func(ctx, handler) };
77}
78
79pub fn get_last_error() -> *mut _xmlError {
90 globals::get_last_error()
91}
92
93pub unsafe fn copy_error(from: *const _xmlError, to: *mut _xmlError) -> c_int {
107 if from.is_null() || to.is_null() {
108 return -1;
109 }
110 unsafe {
112 ptr::copy_nonoverlapping(from, to, 1);
113 }
114 0
115}
116
117pub unsafe fn reset_error(err: *mut _xmlError) {
129 if err.is_null() {
130 return;
131 }
132 unsafe {
134 ptr::write(
135 err,
136 _xmlError {
137 domain: XML_FROM_NONE,
138 code: XML_ERR_OK as c_int,
139 message: ptr::null_mut(),
140 level: XML_ERR_NONE as c_int,
141 file: ptr::null_mut(),
142 line: 0,
143 str1: ptr::null_mut(),
144 str2: ptr::null_mut(),
145 str3: ptr::null_mut(),
146 int1: 0,
147 int2: 0,
148 ctxt: ptr::null_mut(),
149 node: ptr::null_mut(),
150 },
151 );
152 }
153}
154
155pub fn reset_last_error() {
163 globals::reset_last_error();
164}
165
166pub fn format_error_message(
181 _domain: c_int,
182 _code: c_int,
183 msg: *const c_char,
184 str1: *const c_char,
185 str2: *const c_char,
186 str3: *const c_char,
187) -> *mut c_char {
188 if !msg.is_null() {
191 let msg_str = unsafe { crate::abi::allocator::xmlMemStrdupImpl(msg) };
193 return msg_str as *mut c_char;
194 }
195
196 let mut buf: [u8; 1024] = [0; 1024];
200 let mut pos = 0;
201
202 let domain_str = match _domain {
204 XML_FROM_PARSER => "parser",
205 XML_FROM_TREE => "tree",
206 XML_FROM_NAMESPACE => "namespace",
207 XML_FROM_DTD => "dtd",
208 XML_FROM_HTML => "html",
209 XML_FROM_MEMORY => "memory",
210 XML_FROM_OUTPUT => "output",
211 XML_FROM_IO => "io",
212 XML_FROM_XPATH => "xpath",
213 XML_FROM_XPOINTER => "xpointer",
214 XML_FROM_XINCLUDE => "xinclude",
215 XML_FROM_CATALOG => "catalog",
216 XML_FROM_C14N => "c14n",
217 XML_FROM_XSLT => "xslt",
218 XML_FROM_VALID => "valid",
219 XML_FROM_CHECK => "check",
220 XML_FROM_WRITER => "writer",
221 XML_FROM_MODULE => "module",
222 XML_FROM_I18N => "i18n",
223 XML_FROM_SCHEMATRONV => "schematron",
224 XML_FROM_BUFFER => "buffer",
225 XML_FROM_URI => "uri",
226 XML_FROM_NONE => "",
227 XML_FROM_FTP => "ftp",
228 XML_FROM_HTTP => "http",
229 XML_FROM_REGEXP => "regexp",
230 XML_FROM_DATATYPE => "datatype",
231 XML_FROM_SCHEMASP => "schema parser",
232 XML_FROM_SCHEMASV => "schema validator",
233 XML_FROM_RELAXNGP => "relaxng parser",
234 XML_FROM_RELAXNGV => "relaxng validator",
235 _ => "unknown",
236 };
237
238 if !domain_str.is_empty() {
239 let bytes = domain_str.as_bytes();
240 let len = bytes.len().min(buf.len().saturating_sub(pos + 2));
241 buf[pos..pos + len].copy_from_slice(&bytes[..len]);
242 pos += len;
243 buf[pos] = b' ';
244 pos += 1;
245 }
246
247 if !str1.is_null() {
249 let s = unsafe { crate::abi::versioning::c_str_to_bytes(str1).unwrap_or_default() };
251 if pos + s.len() + 3 <= buf.len() {
252 buf[pos] = b'\'';
253 pos += 1;
254 buf[pos..pos + s.len()].copy_from_slice(s);
255 pos += s.len();
256 buf[pos] = b'\'';
257 pos += 1;
258 buf[pos] = b' ';
259 pos += 1;
260 }
261 }
262
263 if !str2.is_null() {
265 let s = unsafe { crate::abi::versioning::c_str_to_bytes(str2).unwrap_or_default() };
266 if pos + s.len() + 3 <= buf.len() {
267 buf[pos] = b'\'';
268 pos += 1;
269 buf[pos..pos + s.len()].copy_from_slice(s);
270 pos += s.len();
271 buf[pos] = b'\'';
272 pos += 1;
273 buf[pos] = b' ';
274 pos += 1;
275 }
276 }
277
278 if !str3.is_null() {
280 let s = unsafe { crate::abi::versioning::c_str_to_bytes(str3).unwrap_or_default() };
281 if pos + s.len() + 3 <= buf.len() {
282 buf[pos] = b'\'';
283 pos += 1;
284 buf[pos..pos + s.len()].copy_from_slice(s);
285 pos += s.len();
286 buf[pos] = b'\'';
287 pos += 1;
288 buf[pos] = b' ';
289 pos += 1;
290 }
291 }
292
293 if pos < buf.len() {
295 buf[pos] = 0;
296 } else {
297 buf[buf.len() - 1] = 0;
298 }
299
300 let result = unsafe { crate::abi::allocator::xmlMallocImpl(pos + 1) };
302 if result.is_null() {
303 return ptr::null_mut();
304 }
305 unsafe {
306 ptr::copy_nonoverlapping(buf.as_ptr(), result as *mut u8, pos + 1);
307 }
308 result as *mut c_char
309}
310
311pub unsafe fn raise_error(
348 ctxt: *mut c_void,
349 _ctxt2: *mut c_void,
350 _ctxt3: *mut c_void,
351 _ctxt4: *mut c_void,
352 _ctxt5: *mut c_void,
353 domain: c_int,
354 code: c_int,
355 level: c_int,
356 file: *const c_char,
357 line: c_int,
358 str1: *const c_char,
359 str2: *const c_char,
360 str3: *const c_char,
361 int1: c_int,
362 int2: c_int,
363 msg: *const c_char,
364) {
365 let formatted_msg = format_error_message(domain, code, msg, str1, str2, str3);
367
368 let err = _xmlError {
370 domain,
371 code,
372 message: formatted_msg,
373 level,
374 file: file as *mut c_char,
375 line,
376 str1: str1 as *mut c_char,
377 str2: str2 as *mut c_char,
378 str3: str3 as *mut c_char,
379 int1,
380 int2,
381 ctxt,
382 node: ptr::null_mut(),
383 };
384
385 globals::set_last_error(err);
386
387 if let Some(handler) = globals::get_structured_error_func() {
389 let ctx = globals::get_structured_error_ctx();
390 let err_ref = globals::get_last_error();
391 if !err_ref.is_null() {
392 handler(ctx, err_ref as *const _xmlError);
393 }
394 }
395
396 if let Some(handler) = globals::get_generic_error_func() {
398 if level != 0 {
399 let ctx = globals::get_generic_error_ctx();
400 if !formatted_msg.is_null() {
401 handler(ctx, formatted_msg as *const core::ffi::c_char);
402 } else if !msg.is_null() {
403 handler(ctx, msg);
404 }
405 }
406 }
407
408 }
414
415unsafe fn emit_legacy_message(level: &str, msg: *const c_char) {
421 if msg.is_null() {
422 return;
423 }
424 let len = libc::strlen(msg) as usize;
425 let text = core::slice::from_raw_parts(msg as *const u8, len);
426 let mut full = Vec::with_capacity(level.len() + 2 + len);
427 full.extend_from_slice(level.as_bytes());
428 full.push(b':');
429 full.push(b' ');
430 full.extend_from_slice(text);
431 if let Some(handler) = globals::get_generic_error_func() {
432 let ctx = globals::get_generic_error_ctx();
433 let mut cmsg = full.clone();
434 cmsg.push(0);
435 handler(ctx, cmsg.as_ptr() as *const c_char);
436 } else {
437 let _ = libc::write(2, full.as_ptr() as *const libc::c_void, full.len());
439 }
440}
441
442#[cfg(target_arch = "x86_64")]
456#[inline]
457unsafe fn ch_call0(handler: xmlGenericErrorFunc, data: *mut c_void, fmt: *const c_char) {
458 unsafe {
464 core::arch::asm!(
465 "xor edx, edx",
466 "xor ecx, ecx",
467 "call {h}",
468 h = in(reg) handler as usize,
469 in("rdi") data,
470 in("rsi") fmt,
471 out("rdx") _, out("rcx") _,
472 lateout("rax") _, lateout("r8") _, lateout("r9") _, lateout("r10") _, lateout("r11") _,
473 );
474 }
475}
476
477#[cfg(target_arch = "x86_64")]
479#[inline]
480unsafe fn ch_call1(handler: xmlGenericErrorFunc, data: *mut c_void, fmt: *const c_char, a1: usize) {
481 unsafe {
484 core::arch::asm!(
485 "xor ecx, ecx",
486 "call {h}",
487 h = in(reg) handler as usize,
488 in("rdi") data,
489 in("rsi") fmt,
490 in("rdx") a1,
491 out("rcx") _,
492 lateout("rax") _, lateout("r8") _, lateout("r9") _, lateout("r10") _, lateout("r11") _,
493 );
494 }
495}
496
497#[cfg(target_arch = "x86_64")]
499#[inline]
500unsafe fn ch_call2(
501 handler: xmlGenericErrorFunc,
502 data: *mut c_void,
503 fmt: *const c_char,
504 a1: usize,
505 a2: usize,
506) {
507 unsafe {
509 core::arch::asm!(
510 "call {h}",
511 h = in(reg) handler as usize,
512 in("rdi") data,
513 in("rsi") fmt,
514 in("rdx") a1,
515 in("rcx") a2,
516 lateout("rax") _, lateout("r8") _, lateout("r9") _, lateout("r10") _, lateout("r11") _,
517 );
518 }
519}
520
521#[cfg(target_arch = "x86_64")]
534unsafe fn format_error_streamed(
535 domain: c_int,
536 code: c_int,
537 level: c_int,
538 message: *const c_char,
539 file: *const c_char,
540 line: c_int,
541 source_window: Option<(&[u8], usize)>,
542) {
543 let Some(handler) = globals::get_generic_error_func() else {
545 return;
546 };
547 let data = globals::get_generic_error_ctx();
548
549 if !file.is_null() {
551 ch_call2(
552 handler,
553 data,
554 b"%s:%d: \0".as_ptr() as *const c_char,
555 file as usize,
556 line as usize,
557 );
558 } else if line != 0
559 && (domain == XML_FROM_PARSER
560 || domain == XML_FROM_SCHEMASV
561 || domain == XML_FROM_SCHEMASP
562 || domain == XML_FROM_DTD
563 || domain == XML_FROM_RELAXNGP
564 || domain == XML_FROM_RELAXNGV)
565 {
566 ch_call1(
567 handler,
568 data,
569 b"Entity: line %d: \0".as_ptr() as *const c_char,
570 line as usize,
571 );
572 }
573
574 let dom: &[u8] = match domain {
576 XML_FROM_PARSER => b"parser \0",
577 XML_FROM_NAMESPACE => b"namespace \0",
578 XML_FROM_DTD | XML_FROM_VALID => b"validity \0",
579 XML_FROM_HTML => b"HTML parser \0",
580 XML_FROM_MEMORY => b"memory \0",
581 XML_FROM_OUTPUT => b"output \0",
582 XML_FROM_IO => b"I/O \0",
583 XML_FROM_XINCLUDE => b"XInclude \0",
584 XML_FROM_XPATH => b"XPath \0",
585 XML_FROM_XPOINTER => b"parser \0",
586 XML_FROM_REGEXP => b"regexp \0",
587 XML_FROM_MODULE => b"module \0",
588 XML_FROM_SCHEMASV => b"Schemas validity \0",
589 XML_FROM_SCHEMASP => b"Schemas parser \0",
590 XML_FROM_RELAXNGP => b"Relax-NG parser \0",
591 XML_FROM_RELAXNGV => b"Relax-NG validity \0",
592 XML_FROM_CATALOG => b"Catalog \0",
593 XML_FROM_C14N => b"C14N \0",
594 XML_FROM_XSLT => b"XSLT \0",
595 XML_FROM_I18N => b"encoding \0",
596 XML_FROM_SCHEMATRONV => b"schematron \0",
597 XML_FROM_BUFFER => b"internal buffer \0",
598 XML_FROM_URI => b"URI \0",
599 _ => b"\0",
600 };
601 if !dom.is_empty() && dom[0] != 0 {
602 ch_call0(handler, data, dom.as_ptr() as *const c_char);
603 }
604
605 let lvl: &[u8] = if level == XML_ERR_NONE as c_int {
607 b": \0"
608 } else if level == XML_ERR_WARNING as c_int {
609 b"warning : \0"
610 } else if level == XML_ERR_ERROR as c_int || level == XML_ERR_FATAL as c_int {
611 b"error : \0"
612 } else {
613 b"\0"
614 };
615 if !lvl.is_empty() && lvl[0] != 0 {
616 ch_call0(handler, data, lvl.as_ptr() as *const c_char);
617 }
618
619 if !message.is_null() {
621 let msg = message as *const u8;
622 let mut len = 0usize;
623 while unsafe { *msg.add(len) } != 0 {
624 len += 1;
625 }
626 let ends_nl = len > 0 && unsafe { *msg.add(len - 1) } == b'\n';
627 let fmt: &[u8] = if ends_nl { b"%s\0" } else { b"%s\n\0" };
628 ch_call1(handler, data, fmt.as_ptr() as *const c_char, msg as usize);
629 }
630
631 if let Some((window, caret)) = source_window {
633 let mut win = window.to_vec();
634 win.push(0);
635 ch_call1(
636 handler,
637 data,
638 b"%s\n\0".as_ptr() as *const c_char,
639 win.as_ptr() as usize,
640 );
641 let mut caret_line = Vec::with_capacity(caret + 2);
642 for &b in window.iter().take(caret) {
643 caret_line.push(if b == b'\t' { b'\t' } else { b' ' });
644 }
645 caret_line.push(b'^');
646 caret_line.push(0);
647 ch_call1(
648 handler,
649 data,
650 b"%s\n\0".as_ptr() as *const c_char,
651 caret_line.as_ptr() as usize,
652 );
653 }
654}
655
656#[derive(Clone, Copy)]
659pub enum GenericDelivery {
660 Custom(xmlGenericErrorFunc, *mut c_void),
662 Stream,
665 None,
667}
668
669#[cfg(target_arch = "x86_64")]
682pub unsafe fn raise_error_streamed(
683 ctxt: *mut c_void,
684 domain: c_int,
685 code: c_int,
686 level: c_int,
687 file: *const c_char,
688 line: c_int,
689 msg: *const c_char,
690 source_window: Option<(&[u8], usize)>,
691 delivery: GenericDelivery,
692) {
693 let formatted_msg =
695 format_error_message(domain, code, msg, ptr::null(), ptr::null(), ptr::null());
696 let err = _xmlError {
697 domain,
698 code,
699 message: formatted_msg,
700 level,
701 file: file as *mut c_char,
702 line,
703 str1: ptr::null_mut(),
704 str2: ptr::null_mut(),
705 str3: ptr::null_mut(),
706 int1: 0,
707 int2: 0,
708 ctxt,
709 node: ptr::null_mut(),
710 };
711
712 globals::set_last_error(err);
713
714 if let Some(handler) = globals::get_structured_error_func() {
716 let ctx = globals::get_structured_error_ctx();
717 let err_ref = globals::get_last_error();
718 if !err_ref.is_null() {
719 handler(ctx, err_ref as *const _xmlError);
720 }
721 return;
722 }
723
724 match delivery {
725 GenericDelivery::Custom(channel, ctx) => {
726 if !msg.is_null() {
727 unsafe { channel(ctx, msg) };
729 }
730 }
731 GenericDelivery::Stream => {
732 if globals::get_generic_error_func().is_some() {
733 unsafe {
734 format_error_streamed(
735 domain,
736 code,
737 level,
738 formatted_msg,
739 file,
740 line,
741 source_window,
742 )
743 };
744 }
745 }
746 GenericDelivery::None => {}
747 }
748}
749
750#[no_mangle]
757pub unsafe extern "C" fn xmlParserError(ctx: *mut c_void, msg: *const c_char) {
758 let _ = ctx;
759 unsafe { emit_legacy_message("error", msg) };
760}
761
762#[no_mangle]
764pub unsafe extern "C" fn xmlParserWarning(ctx: *mut c_void, msg: *const c_char) {
765 let _ = ctx;
766 unsafe { emit_legacy_message("warning", msg) };
767}
768
769#[no_mangle]
771pub unsafe extern "C" fn xmlParserValidityError(ctx: *mut c_void, msg: *const c_char) {
772 let _ = ctx;
773 unsafe { emit_legacy_message("validity error", msg) };
774}
775
776#[no_mangle]
778pub unsafe extern "C" fn xmlParserValidityWarning(ctx: *mut c_void, msg: *const c_char) {
779 let _ = ctx;
780 unsafe { emit_legacy_message("validity warning", msg) };
781}
782
783#[cfg(test)]
788mod tests {
789 use super::*;
790 use crate::abi::allocator;
791 use core::ffi::c_void;
792
793 #[test]
794 fn test_error_default_reset() {
795 unsafe {
796 let mut err = _xmlError {
797 domain: XML_FROM_PARSER,
798 code: XML_ERR_NO_MEMORY,
799 message: ptr::null_mut(),
800 level: XML_ERR_ERROR as c_int,
801 file: ptr::null_mut(),
802 line: 42,
803 str1: ptr::null_mut(),
804 str2: ptr::null_mut(),
805 str3: ptr::null_mut(),
806 int1: 0,
807 int2: 0,
808 ctxt: ptr::null_mut(),
809 node: ptr::null_mut(),
810 };
811
812 reset_error(&mut err);
813 assert_eq!(err.domain, XML_FROM_NONE);
814 assert_eq!(err.code, XML_ERR_OK as c_int);
815 assert_eq!(err.level, XML_ERR_NONE as c_int);
816 assert_eq!(err.line, 0);
817 }
818 }
819
820 #[test]
821 fn test_copy_error() {
822 unsafe {
823 let from = _xmlError {
824 domain: XML_FROM_PARSER,
825 code: XML_ERR_NO_MEMORY,
826 message: ptr::null_mut(),
827 level: XML_ERR_FATAL as c_int,
828 file: ptr::null_mut(),
829 line: 100,
830 str1: ptr::null_mut(),
831 str2: ptr::null_mut(),
832 str3: ptr::null_mut(),
833 int1: 1,
834 int2: 2,
835 ctxt: ptr::null_mut(),
836 node: ptr::null_mut(),
837 };
838 let mut to = _xmlError {
839 domain: XML_FROM_NONE,
840 code: XML_ERR_OK as c_int,
841 message: ptr::null_mut(),
842 level: XML_ERR_NONE as c_int,
843 file: ptr::null_mut(),
844 line: 0,
845 str1: ptr::null_mut(),
846 str2: ptr::null_mut(),
847 str3: ptr::null_mut(),
848 int1: 0,
849 int2: 0,
850 ctxt: ptr::null_mut(),
851 node: ptr::null_mut(),
852 };
853
854 let result = copy_error(&from, &mut to);
855 assert_eq!(result, 0);
856 assert_eq!(to.domain, XML_FROM_PARSER);
857 assert_eq!(to.code, XML_ERR_NO_MEMORY);
858 assert_eq!(to.level, XML_ERR_FATAL as c_int);
859 assert_eq!(to.line, 100);
860 assert_eq!(to.int1, 1);
861 assert_eq!(to.int2, 2);
862 }
863 }
864
865 #[test]
866 fn test_raise_and_get_last_error() {
867 unsafe {
868 reset_last_error();
869 assert!(get_last_error().is_null());
870
871 let file = b"test.xml\0" as *const u8 as *const c_char;
872 let str1 = b"element\0" as *const u8 as *const c_char;
873
874 raise_error(
875 ptr::null_mut(),
876 ptr::null_mut(),
877 ptr::null_mut(),
878 ptr::null_mut(),
879 ptr::null_mut(),
880 XML_FROM_PARSER,
881 XML_ERR_TAG_NAME_MISMATCH,
882 XML_ERR_ERROR as c_int,
883 file,
884 10,
885 str1,
886 ptr::null(),
887 ptr::null(),
888 0,
889 0,
890 ptr::null(),
891 );
892
893 let last = get_last_error();
894 assert!(!last.is_null());
895 assert_eq!((*last).domain, XML_FROM_PARSER);
896 assert_eq!((*last).code, XML_ERR_TAG_NAME_MISMATCH);
897 assert_eq!((*last).level, XML_ERR_ERROR as c_int);
898 assert_eq!((*last).line, 10);
899
900 let last_file = (*last).file;
902 assert!(!last_file.is_null());
903
904 reset_last_error();
905 assert!(get_last_error().is_null());
906 }
907 }
908
909 #[test]
910 fn test_structured_error_callback() {
911 unsafe {
912 reset_last_error();
913
914 let mut captured_domain: c_int = 0;
916 let captured_ptr = &mut captured_domain as *mut c_int as *mut c_void;
917
918 extern "C" fn test_handler(ctx: *mut c_void, _err: *const _xmlError) {
921 unsafe {
923 let captured = &mut *(ctx as *mut c_int);
924 *captured = 42;
925 }
926 }
927
928 set_structured_error_func(captured_ptr, Some(test_handler as xmlStructuredErrorFunc));
929
930 raise_error(
931 ptr::null_mut(),
932 ptr::null_mut(),
933 ptr::null_mut(),
934 ptr::null_mut(),
935 ptr::null_mut(),
936 XML_FROM_PARSER,
937 XML_ERR_OK as c_int,
938 XML_ERR_WARNING as c_int,
939 ptr::null(),
940 0,
941 ptr::null(),
942 ptr::null(),
943 ptr::null(),
944 0,
945 0,
946 ptr::null(),
947 );
948
949 assert_eq!(captured_domain, 42);
950
951 set_structured_error_func(ptr::null_mut(), None);
953 reset_last_error();
954 }
955 }
956
957 #[test]
958 fn test_format_error_message() {
959 unsafe {
960 let msg = b"test error\0" as *const u8 as *const c_char;
962 let formatted = format_error_message(
963 XML_FROM_NONE,
964 XML_ERR_OK as c_int,
965 msg,
966 ptr::null(),
967 ptr::null(),
968 ptr::null(),
969 );
970 assert!(!formatted.is_null());
971 let formatted_str = std::ffi::CStr::from_ptr(formatted);
972 assert_eq!(formatted_str.to_bytes(), b"test error");
973
974 allocator::xmlFreeImpl(formatted as *mut c_void);
976
977 let str1 = b"foo\0" as *const u8 as *const c_char;
979 let formatted2 = format_error_message(
980 XML_FROM_PARSER,
981 XML_ERR_OK as c_int,
982 ptr::null(),
983 str1,
984 ptr::null(),
985 ptr::null(),
986 );
987 assert!(!formatted2.is_null());
988 allocator::xmlFreeImpl(formatted2 as *mut c_void);
989 }
990 }
991}