libxml_rs/abi/exports_parser.rs
1//! exports_parser — C ABI exports for the XML parser family (§11.1-I).
2//!
3//! Implements the parser/parserInternals/xmlIO/encoding/tree export surface:
4//! parser-context creation and lifecycle, the `xmlCtxtRead*` family, parser
5//! input buffers and streams, encoding switches, the deprecated node-info
6//! sequence, global I/O callback registration, external-entity loaders, the
7//! `xmlFile*` I/O callbacks, low-level character scanning helpers and the
8//! SAX/DTD parse front-ends.
9//!
10//! Where an internal engine entry point exists (e.g. `crate::xml::parser::helpers`),
11//! the export wraps it; otherwise the function is ported from upstream
12//! `parser.c` / `parserInternals.c` / `xmlIO.c` / `error.c` / `encoding.c`
13//! (see `archaeology/libxml2-git`).
14
15#![allow(missing_docs)]
16#![allow(non_snake_case)]
17#![allow(non_camel_case_types)]
18#![allow(non_upper_case_globals)]
19
20use core::ffi::CStr;
21use core::ptr;
22use std::os::raw::{c_char, c_int, c_long, c_uchar, c_uint, c_ulong, c_void};
23
24use parking_lot::Mutex;
25
26use crate::abi::allocator::{
27 xmlFreeImpl, xmlMallocImpl, xmlMallocZero, xmlMemStrdupImpl, xmlReallocImpl,
28};
29use crate::abi::callbacks::{
30 xmlGenericErrorFunc, xmlInputCloseCallback, xmlInputReadCallback, xmlOutputCloseCallback,
31 xmlOutputWriteCallback, xmlStructuredErrorFunc,
32};
33use crate::abi::structs::*;
34use crate::abi::types::*;
35use crate::xml::parser::helpers;
36use crate::xml::parser::input::InputBuffer;
37use crate::xml::{dtd, encoding, entities, errors, globals, io, string, tree};
38
39// ═══════════════════════════════════════════════════════════════════════════════
40// Local ABI types (upstream xmlIO.h / parser.h, not present in callbacks.rs)
41// ═══════════════════════════════════════════════════════════════════════════════
42
43/// `xmlInputMatchCallback` — decide whether a filename is handled by the
44/// registered input callback pair.
45type xmlInputMatchCallback = unsafe extern "C" fn(filename: *const c_char) -> c_int;
46
47/// `xmlInputOpenCallback` — open a resource and return an I/O context.
48type xmlInputOpenCallback = unsafe extern "C" fn(filename: *const c_char) -> *mut c_void;
49
50/// `xmlOutputMatchCallback` — decide whether a filename is handled by the
51/// registered output callback pair.
52type xmlOutputMatchCallback = unsafe extern "C" fn(filename: *const c_char) -> c_int;
53
54/// `xmlOutputOpenCallback` — open a resource for writing and return a context.
55type xmlOutputOpenCallback = unsafe extern "C" fn(filename: *const c_char) -> *mut c_void;
56
57/// `xmlExternalEntityLoader` — resolve an external entity to a parser input.
58type xmlExternalEntityLoader = unsafe extern "C" fn(
59 URL: *const c_char,
60 ID: *const c_char,
61 ctxt: *mut _xmlParserCtxt,
62) -> *mut _xmlParserInput;
63
64#[derive(Clone, Copy)]
65struct InputCallbackEntry {
66 matchcb: Option<xmlInputMatchCallback>,
67 opencb: Option<xmlInputOpenCallback>,
68 readcb: Option<xmlInputReadCallback>,
69 closecb: Option<xmlInputCloseCallback>,
70}
71
72#[allow(dead_code)]
73#[derive(Clone, Copy)]
74struct OutputCallbackEntry {
75 matchcb: Option<xmlOutputMatchCallback>,
76 opencb: Option<xmlOutputOpenCallback>,
77 writecb: Option<xmlOutputWriteCallback>,
78 closecb: Option<xmlOutputCloseCallback>,
79}
80
81static INPUT_CALLBACKS: Mutex<Vec<InputCallbackEntry>> = Mutex::new(Vec::new());
82static OUTPUT_CALLBACKS: Mutex<Vec<OutputCallbackEntry>> = Mutex::new(Vec::new());
83
84static EXTERNAL_ENTITY_LOADER: Mutex<Option<xmlExternalEntityLoader>> =
85 Mutex::new(Some(default_external_entity_loader));
86
87// Deprecated legacy function codes not present in types.rs (upstream xmlerror.h).
88const XML_ERR_USER_STOP: c_int = 111;
89#[allow(dead_code)]
90const XML_ERR_RESOURCE_LIMIT: c_int = 114;
91
92// XML_SCAN_* flags (upstream include/private/parser.h).
93const XML_SCAN_NC: c_int = 1;
94const XML_SCAN_NMTOKEN: c_int = 2;
95const XML_SCAN_OLD10: c_int = 4;
96
97// xmlParserLoadSubset bits (upstream parser.h).
98#[allow(dead_code)]
99const XML_DETECT_IDS: c_int = 1 << 0;
100const XML_COMPLETE_ATTRS: c_int = 1 << 1;
101
102/// Keep enough input around to show errors in context (parserInternals.c).
103const LINE_LEN: usize = 80;
104
105/// Minimal amount of data the parser expects in the buffer (parserInternals.c).
106#[allow(dead_code)]
107const INPUT_CHUNK: usize = 100;
108
109const XML_INVALID_CHAR: c_int = -1;
110
111// ═══════════════════════════════════════════════════════════════════════════════
112// Internal helpers
113// ═══════════════════════════════════════════════════════════════════════════════
114
115/// Shared context initialisation: zeroes `ctxt`, installs the SAX handler and
116/// sets the initial parser state (upstream `xmlInitSAXParserCtxt`).
117///
118/// # Safety
119///
120/// `ctxt` must be a valid, writable, freshly allocated parser context.
121unsafe fn init_sax_parser_ctxt(
122 ctxt: *mut _xmlParserCtxt,
123 sax: *const _xmlSAXHandler,
124 userData: *mut c_void,
125) -> c_int {
126 unsafe {
127 ptr::write_bytes(ctxt as *mut u8, 0, core::mem::size_of::<_xmlParserCtxt>());
128
129 let c = &mut *ctxt;
130
131 // SAX handler.
132 if c.sax.is_null() {
133 let new_sax =
134 xmlMallocZero(core::mem::size_of::<_xmlSAXHandler>()) as *mut _xmlSAXHandler;
135 if new_sax.is_null() {
136 return -1;
137 }
138 c.sax = new_sax;
139 }
140 if sax.is_null() {
141 crate::xml::sax::xmlSAX2InitDefaultSAXHandler(c.sax);
142 c.userData = ctxt as *mut c_void;
143 } else if (*sax).initialized == XML_SAX2_MAGIC as c_uint {
144 // Full SAX2 handler copy.
145 ptr::copy_nonoverlapping(sax, c.sax, 1);
146 c.userData = if userData.is_null() {
147 ctxt as *mut c_void
148 } else {
149 userData
150 };
151 } else {
152 // SAX1 handler: only the V1 prefix is meaningful.
153 ptr::write_bytes(c.sax as *mut u8, 0, core::mem::size_of::<_xmlSAXHandler>());
154 ptr::copy_nonoverlapping(
155 sax as *const u8,
156 c.sax as *mut u8,
157 core::mem::size_of::<_xmlSAXHandlerV1>(),
158 );
159 c.userData = if userData.is_null() {
160 ctxt as *mut c_void
161 } else {
162 userData
163 };
164 }
165
166 c.wellFormed = 1;
167 c.standalone = -1;
168 c.errNo = XML_ERR_OK;
169 c.valid = 1;
170 c.nsWellFormed = 1;
171 c.instate = xmlParserInputState::XML_PARSER_START as c_int;
172 c.keepBlanks = globals::get_keep_blanks_default();
173 c.replaceEntities = globals::get_substitute_entities_default();
174 c.linenumbers = 1;
175 c.charset = xmlCharEncoding::XML_CHAR_ENCODING_UTF8 as c_int;
176 c.pedantic = globals::get_pedantic_parser_default();
177 c.loadsubset = globals::get_load_ext_dtd_default();
178 c.docdict = 1;
179 c.options = 0;
180
181 c.vctxt.userData = ctxt as *mut c_void;
182 c.vctxt.valid = 1;
183 }
184 0
185}
186
187/// Mirror `options` into the parser context's historical struct members
188/// (upstream `xmlCtxtSetOptionsInternal`).
189///
190/// # Safety
191///
192/// `ctxt` must be a valid, writable parser context.
193pub(crate) unsafe fn apply_options(ctxt: *mut _xmlParserCtxt, options: c_int) {
194 unsafe {
195 let c = &mut *ctxt;
196 c.options = options;
197 c.recovery = (options & XML_PARSE_RECOVER != 0) as c_int;
198 c.replaceEntities = (options & XML_PARSE_NOENT != 0) as c_int;
199 c.loadsubset = ((options & XML_PARSE_DTDLOAD != 0) as c_int)
200 | if options & XML_PARSE_DTDATTR != 0 {
201 XML_COMPLETE_ATTRS
202 } else {
203 0
204 };
205 c.validate = (options & XML_PARSE_DTDVALID != 0) as c_int;
206 c.pedantic = (options & XML_PARSE_PEDANTIC != 0) as c_int;
207 c.keepBlanks = if options & XML_PARSE_NOBLANKS != 0 {
208 0
209 } else {
210 1
211 };
212 c.dictNames = if options & XML_PARSE_NODICT != 0 {
213 0
214 } else {
215 1
216 };
217 }
218}
219
220/// Find the registered encoding handler for an `xmlCharEncoding` value, or NULL.
221unsafe fn encoding_handler_for(enc: c_int) -> *mut _xmlCharEncodingHandler {
222 let e: xmlCharEncoding = unsafe { core::mem::transmute(enc) };
223 match encoding::encoding_name(e) {
224 Some(name) => {
225 let mut nul = name.to_vec();
226 nul.push(0);
227 encoding::find_encoding_handler(nul.as_ptr() as *const xmlChar)
228 }
229 None => ptr::null_mut(),
230 }
231}
232
233/// Build a `_xmlParserInput` that references the data owned by `buf` (an input
234/// buffer previously created by the xmlIO layer). The buffer keeps the data
235/// alive; the returned input must be freed with `helpers::free_parser_input`.
236///
237/// # Safety
238///
239/// `buf` must be a valid input buffer or NULL, and must outlive the returned
240/// input.
241unsafe fn parser_input_from_buf(buf: *mut _xmlParserInputBuffer) -> *mut _xmlParserInput {
242 let input =
243 unsafe { xmlMallocZero(core::mem::size_of::<_xmlParserInput>()) } as *mut _xmlParserInput;
244 if input.is_null() {
245 return ptr::null_mut();
246 }
247 unsafe {
248 (*input).buf = buf;
249 (*input).line = 1;
250 (*input).col = 1;
251 if !buf.is_null() {
252 let b = &*buf;
253 if !b.buffer.is_null() {
254 let xbuf = &*(b.buffer as *mut _xmlBuffer);
255 if !xbuf.content.is_null() {
256 (*input).base = xbuf.content;
257 (*input).cur = xbuf.content;
258 (*input).end = xbuf.content.add(xbuf.use_ as usize);
259 (*input).length = xbuf.use_ as c_int;
260 }
261 }
262 }
263 }
264 input
265}
266
267/// `pub(crate)` wrapper of [`parser_input_from_buf`] for the sibling
268/// xmlNewInputFrom* family (11.1-X R-000165 closure).
269pub(crate) unsafe fn parser_input_from_buf_pub(
270 buf: *mut _xmlParserInputBuffer,
271) -> *mut _xmlParserInput {
272 unsafe { parser_input_from_buf(buf) }
273}
274
275/// Materialise an `InputBuffer` (owned copy) from a raw `_xmlParserInput`,
276/// so the data survives the caller's input lifetime.
277///
278/// # Safety
279///
280/// `input` must be a valid pointer to a `_xmlParserInput`.
281unsafe fn input_buffer_from_parser_input(input: *mut _xmlParserInput) -> InputBuffer {
282 unsafe {
283 let pi = &*input;
284 if !pi.buf.is_null() {
285 let b = &*pi.buf;
286 if let Some(read) = b.readcallback {
287 return helpers::input_from_io(Some(read), b.closecallback, b.context);
288 }
289 }
290 if !pi.base.is_null() && !pi.end.is_null() && pi.end >= pi.base {
291 let len = (pi.end as usize).saturating_sub(pi.base as usize);
292 let slice = core::slice::from_raw_parts(pi.base, len);
293 return InputBuffer::from_memory(slice, None);
294 }
295 InputBuffer::from_memory(&[], None)
296 }
297}
298
299/// Core of `xmlCtxtRead*`: reset the context, wire an input buffer, parse,
300/// and return the resulting document (freed on hard error unless recovery).
301///
302/// # Safety
303///
304/// `ctxt` must be a valid parser context; `input` is consumed.
305unsafe fn ctxt_read_doc(
306 ctxt: *mut _xmlParserCtxt,
307 input: InputBuffer,
308 url: *const c_char,
309 options: c_int,
310) -> *mut _xmlDoc {
311 unsafe {
312 xmlCtxtReset(ctxt);
313 apply_options(ctxt, options);
314 helpers::setup_parser_input(ctxt, input);
315 if helpers::parse_document(ctxt) != 0 {
316 let doc = (*ctxt).myDoc;
317 (*ctxt).myDoc = ptr::null_mut();
318 if options & XML_PARSE_RECOVER != 0 {
319 return doc;
320 }
321 if !doc.is_null() {
322 tree::free_doc(doc);
323 }
324 return ptr::null_mut();
325 }
326 let doc = (*ctxt).myDoc;
327 if !doc.is_null() && !url.is_null() {
328 (*doc).URL = string::xml_strdup(url as *const xmlChar);
329 }
330 doc
331 }
332}
333
334/// Parse DTD declaration text using the internal engine by wrapping it in a
335/// synthetic document (`<!DOCTYPE none [ ... ]><none/>`) when the text is a
336/// bare DTD subset, or parsing it directly when it is already a document.
337///
338/// Returns a detached DTD (never owned by a document), or NULL.
339///
340/// # Safety
341///
342/// `ctxt` must be a valid parser context; `data` must be readable for `len`
343/// bytes.
344unsafe fn parse_dtd_text(
345 ctxt: *mut _xmlParserCtxt,
346 data: &[u8],
347 public_id: *const xmlChar,
348 system_id: *const xmlChar,
349) -> *mut _xmlDtd {
350 unsafe {
351 // If the content is already a full document (contains a DOCTYPE),
352 // parse it directly; otherwise wrap the declarations.
353 let has_doctype = data
354 .windows(9)
355 .any(|w| w.eq_ignore_ascii_case(b"<!DOCTYPE"));
356 let mut wrapped: Vec<u8>;
357 let parse_data: &[u8] = if has_doctype {
358 data
359 } else {
360 wrapped = Vec::with_capacity(data.len() + 32);
361 wrapped.extend_from_slice(b"<!DOCTYPE none [");
362 wrapped.extend_from_slice(data);
363 wrapped.extend_from_slice(b"]><none/>");
364 &wrapped
365 };
366
367 let input = InputBuffer::from_memory(parse_data, None);
368 helpers::setup_parser_input(ctxt, input);
369 let rc = helpers::parse_document(ctxt);
370 let doc = (*ctxt).myDoc;
371 (*ctxt).myDoc = ptr::null_mut();
372
373 if rc == 0 && !doc.is_null() && !(*doc).intSubset.is_null() {
374 let dtd = (*doc).intSubset;
375 (*doc).intSubset = ptr::null_mut();
376 (*dtd).parent = ptr::null_mut();
377 (*dtd).doc = ptr::null_mut();
378 if !public_id.is_null() {
379 (*dtd).ExternalID = string::xml_strdup(public_id);
380 }
381 if !system_id.is_null() {
382 (*dtd).SystemID = string::xml_strdup(system_id);
383 }
384 tree::free_doc(doc);
385 return dtd;
386 }
387
388 if !doc.is_null() {
389 tree::free_doc(doc);
390 }
391 // Fallback: an empty DTD carrying the identifiers.
392
393 dtd::new_dtd(
394 ptr::null_mut(),
395 c"none".as_ptr() as *const xmlChar,
396 public_id,
397 system_id,
398 )
399 }
400}
401
402// ═══════════════════════════════════════════════════════════════════════════════
403// Context creation / lifecycle
404// ═══════════════════════════════════════════════════════════════════════════════
405
406/// Create a new parser context with a default SAX2 handler.
407///
408/// # UPSTREAM-PARITY
409///
410/// ```c
411/// xmlParserCtxtPtr xmlNewParserCtxt(void);
412/// ```
413///
414/// # SAFETY
415///
416/// The function touches crate-global state only; it is safe
417/// as long as the caller respects the library's global
418/// initialization/cleanup ordering (xmlInitParser before use,
419/// xmlCleanupParser only after all users are done).
420///
421/// Violating the global lifecycle ordering, or calling this after
422/// teardown or from a signal handler, is undefined behavior.
423#[no_mangle]
424pub unsafe extern "C" fn xmlNewParserCtxt() -> *mut _xmlParserCtxt {
425 unsafe {
426 globals::init_parser();
427 let ctxt = xmlMallocZero(core::mem::size_of::<_xmlParserCtxt>()) as *mut _xmlParserCtxt;
428 if ctxt.is_null() {
429 return ptr::null_mut();
430 }
431 if init_sax_parser_ctxt(ctxt, ptr::null(), ptr::null_mut()) < 0 {
432 helpers::free_parser_ctxt(ctxt);
433 return ptr::null_mut();
434 }
435 ctxt
436 }
437}
438
439/// Create a new parser context using the given SAX handler (or the default
440/// SAX2 handler when `sax` is NULL).
441///
442/// # UPSTREAM-PARITY
443///
444/// ```c
445/// xmlParserCtxtPtr xmlNewSAXParserCtxt(const xmlSAXHandler *sax, void *userData);
446/// ```
447///
448/// # SAFETY
449///
450/// - `sax`, `userData` must be valid pointers (or NULL
451/// where the upstream C contract allows), obtained from the
452/// matching constructor/owner and not yet freed; the callee may
453/// take or keep ownership exactly as the C API specifies.
454///
455/// The caller must not race this call with concurrent mutation of the
456/// same objects from other threads (per-object state is not internally
457/// synchronized). Violating any of the above is undefined behavior.
458///
459/// Exercised by the C-API differential courts
460/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
461/// courts; those pass byte-for-byte against the upstream oracle.
462#[no_mangle]
463pub unsafe extern "C" fn xmlNewSAXParserCtxt(
464 sax: *const _xmlSAXHandler,
465 userData: *mut c_void,
466) -> *mut _xmlParserCtxt {
467 unsafe {
468 globals::init_parser();
469 let ctxt = xmlMallocZero(core::mem::size_of::<_xmlParserCtxt>()) as *mut _xmlParserCtxt;
470 if ctxt.is_null() {
471 return ptr::null_mut();
472 }
473 if init_sax_parser_ctxt(ctxt, sax, userData) < 0 {
474 helpers::free_parser_ctxt(ctxt);
475 return ptr::null_mut();
476 }
477 ctxt
478 }
479}
480
481/// Initialise a parser context (legacy API): zeroes the context, installs a
482/// default SAX2 handler and sets the initial parser state.
483///
484/// # UPSTREAM-PARITY
485///
486/// ```c
487/// int xmlInitParserCtxt(xmlParserCtxtPtr ctxt);
488/// ```
489///
490/// # SAFETY
491///
492/// - `ctxt` must be valid pointers (or NULL
493/// where the upstream C contract allows), obtained from the
494/// matching constructor/owner and not yet freed; the callee may
495/// take or keep ownership exactly as the C API specifies.
496///
497/// The caller must not race this call with concurrent mutation of the
498/// same objects from other threads (per-object state is not internally
499/// synchronized). Violating any of the above is undefined behavior.
500///
501/// Exercised by the C-API differential courts
502/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
503/// courts; those pass byte-for-byte against the upstream oracle.
504#[no_mangle]
505pub unsafe extern "C" fn xmlInitParserCtxt(ctxt: *mut _xmlParserCtxt) -> c_int {
506 unsafe { init_sax_parser_ctxt(ctxt, ptr::null(), ptr::null_mut()) }
507}
508
509/// Clear (reset) a parser context.
510///
511/// # UPSTREAM-PARITY
512///
513/// ```c
514/// void xmlClearParserCtxt(xmlParserCtxtPtr ctxt);
515/// ```
516///
517/// # SAFETY
518///
519/// - `ctxt` must be valid pointers (or NULL
520/// where the upstream C contract allows), obtained from the
521/// matching constructor/owner and not yet freed; the callee may
522/// take or keep ownership exactly as the C API specifies.
523///
524/// The caller must not race this call with concurrent mutation of the
525/// same objects from other threads (per-object state is not internally
526/// synchronized). Violating any of the above is undefined behavior.
527///
528/// Exercised by the C-API differential courts
529/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
530/// courts; those pass byte-for-byte against the upstream oracle.
531#[no_mangle]
532pub unsafe extern "C" fn xmlClearParserCtxt(ctxt: *mut _xmlParserCtxt) {
533 unsafe { xmlCtxtReset(ctxt) }
534}
535
536/// Reset a parser context: drop the input stack, node/name stacks, strings,
537/// document and error state so the context can be reused.
538///
539/// # UPSTREAM-PARITY
540///
541/// ```c
542/// void xmlCtxtReset(xmlParserCtxtPtr ctxt);
543/// ```
544///
545/// # SAFETY
546///
547/// - `ctxt` must be valid pointers (or NULL
548/// where the upstream C contract allows), obtained from the
549/// matching constructor/owner and not yet freed; the callee may
550/// take or keep ownership exactly as the C API specifies.
551///
552/// The caller must not race this call with concurrent mutation of the
553/// same objects from other threads (per-object state is not internally
554/// synchronized). Violating any of the above is undefined behavior.
555///
556/// Exercised by the C-API differential courts
557/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
558/// courts; those pass byte-for-byte against the upstream oracle.
559#[no_mangle]
560pub unsafe extern "C" fn xmlCtxtReset(ctxt: *mut _xmlParserCtxt) {
561 if ctxt.is_null() {
562 return;
563 }
564 unsafe {
565 let c = &mut *ctxt;
566
567 // Free all inputs on the stack.
568 let input_nr = c.inputNr;
569 let input_tab = c.inputTab;
570 if !input_tab.is_null() {
571 for i in 0..input_nr {
572 let input = *input_tab.add(i as usize);
573 if !input.is_null() {
574 helpers::free_parser_input(input);
575 }
576 }
577 xmlFreeImpl(input_tab as *mut c_void);
578 }
579 c.inputTab = ptr::null_mut();
580 c.inputMax = 0;
581 c.inputNr = 0;
582 c.input = ptr::null_mut();
583
584 // Free the stored InputBuffer (stashed by setup_parser_input; the
585 // side table keeps ctxt._private application data — 11.1-X).
586 helpers::free_stashed_input_buffer(ctxt);
587
588 // Node stack (array only; nodes are owned by the doc).
589 if !c.nodeTab.is_null() {
590 xmlFreeImpl(c.nodeTab as *mut c_void);
591 }
592 c.nodeTab = ptr::null_mut();
593 c.nodeMax = 0;
594 c.nodeNr = 0;
595 c.node = ptr::null_mut();
596
597 // Name stack.
598 if !c.nameTab.is_null() {
599 xmlFreeImpl(c.nameTab as *mut c_void);
600 }
601 c.nameTab = ptr::null_mut();
602 c.nameMax = 0;
603 c.nameNr = 0;
604 c.name = ptr::null();
605
606 // Space stack: keep the allocation, reset the counter.
607 c.spaceNr = 0;
608 c.space = ptr::null_mut();
609
610 // Namespaces.
611 c.nsNr = 0;
612
613 // Strings owned by the context.
614 if !c.version.is_null() {
615 xmlFreeImpl(c.version as *mut c_void);
616 c.version = ptr::null_mut();
617 }
618 if !c.encoding.is_null() {
619 xmlFreeImpl(c.encoding as *mut c_void);
620 c.encoding = ptr::null_mut();
621 }
622 if !c.extSubURI.is_null() {
623 xmlFreeImpl(c.extSubURI as *mut c_void);
624 c.extSubURI = ptr::null_mut();
625 }
626 if !c.extSubSystem.is_null() {
627 xmlFreeImpl(c.extSubSystem as *mut c_void);
628 c.extSubSystem = ptr::null_mut();
629 }
630 if !c.directory.is_null() {
631 xmlFreeImpl(c.directory as *mut c_void);
632 c.directory = ptr::null_mut();
633 }
634
635 // Document: the context owns it until reset/free.
636 if !c.myDoc.is_null() {
637 tree::free_doc(c.myDoc);
638 }
639 c.myDoc = ptr::null_mut();
640
641 // Parser state.
642 c.standalone = -1;
643 c.hasExternalSubset = 0;
644 c.hasPErefs = 0;
645 c.instate = xmlParserInputState::XML_PARSER_START as c_int;
646 c.wellFormed = 1;
647 c.nsWellFormed = 1;
648 c.disableSAX = 0;
649 c.valid = 1;
650 c.record_info = 0;
651 c.checkIndex = 0;
652 c.inSubset = 0;
653 c.errNo = XML_ERR_OK;
654 c.depth = 0;
655 c.nbentities = 0;
656 c.sizeentities = 0;
657 c.nbErrors = 0;
658 c.nbWarnings = 0;
659
660 xmlInitNodeInfoSeq(&mut c.node_seq);
661
662 if c.lastError.code != XML_ERR_OK {
663 errors::reset_error(&mut c.lastError);
664 }
665 }
666}
667
668/// Reset a push-parser context and set up a fresh input chunk.
669///
670/// # UPSTREAM-PARITY
671///
672/// ```c
673/// int xmlCtxtResetPush(xmlParserCtxtPtr ctxt, const char *chunk, int size,
674/// const char *filename, const char *encoding);
675/// ```
676///
677/// # SAFETY
678///
679/// - `ctxt` must be valid pointers (or NULL
680/// where the upstream C contract allows), obtained from the
681/// matching constructor/owner and not yet freed; the callee may
682/// take or keep ownership exactly as the C API specifies.
683///
684/// - `chunk`, `filename`, `encoding` must point to valid NUL-terminated
685/// strings (or NULL where the C contract allows) for the lifetime
686/// of the call.
687///
688/// The caller must not race this call with concurrent mutation of the
689/// same objects from other threads (per-object state is not internally
690/// synchronized). Violating any of the above is undefined behavior.
691///
692/// Exercised by the C-API differential courts
693/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
694/// courts; those pass byte-for-byte against the upstream oracle.
695#[no_mangle]
696pub unsafe extern "C" fn xmlCtxtResetPush(
697 ctxt: *mut _xmlParserCtxt,
698 chunk: *const c_char,
699 size: c_int,
700 filename: *const c_char,
701 encoding: *const c_char,
702) -> c_int {
703 if ctxt.is_null() {
704 return 1;
705 }
706 unsafe {
707 xmlCtxtReset(ctxt);
708
709 let slice = if size > 0 && !chunk.is_null() {
710 core::slice::from_raw_parts(chunk as *const u8, size as usize)
711 } else {
712 &[]
713 };
714 let uri = if filename.is_null() {
715 None
716 } else {
717 CStr::from_ptr(filename).to_str().ok()
718 };
719 let input = InputBuffer::from_memory(slice, uri);
720 helpers::setup_parser_input(ctxt, input);
721
722 if !encoding.is_null() {
723 let handler = encoding::find_encoding_handler(encoding as *const xmlChar);
724 if !handler.is_null() {
725 xmlSwitchToEncoding(ctxt, handler);
726 }
727 }
728 }
729 0
730}
731
732/// Apply a full set of parser options, clearing options not present.
733///
734/// # UPSTREAM-PARITY
735///
736/// ```c
737/// int xmlCtxtSetOptions(xmlParserCtxtPtr ctxt, int options);
738/// ```
739///
740/// # SAFETY
741///
742/// - `ctxt` must be valid pointers (or NULL
743/// where the upstream C contract allows), obtained from the
744/// matching constructor/owner and not yet freed; the callee may
745/// take or keep ownership exactly as the C API specifies.
746///
747/// The caller must not race this call with concurrent mutation of the
748/// same objects from other threads (per-object state is not internally
749/// synchronized). Violating any of the above is undefined behavior.
750///
751/// Exercised by the C-API differential courts
752/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
753/// courts; those pass byte-for-byte against the upstream oracle.
754#[no_mangle]
755pub unsafe extern "C" fn xmlCtxtSetOptions(ctxt: *mut _xmlParserCtxt, options: c_int) -> c_int {
756 if ctxt.is_null() {
757 return -1;
758 }
759 const ALL_MASK: c_int = XML_PARSE_RECOVER
760 | XML_PARSE_NOENT
761 | XML_PARSE_DTDLOAD
762 | XML_PARSE_DTDATTR
763 | XML_PARSE_DTDVALID
764 | XML_PARSE_NOERROR
765 | XML_PARSE_NOWARNING
766 | XML_PARSE_PEDANTIC
767 | XML_PARSE_NOBLANKS
768 | XML_PARSE_SAX1
769 | XML_PARSE_NONET
770 | XML_PARSE_NODICT
771 | XML_PARSE_NSCLEAN
772 | XML_PARSE_NOCDATA
773 | XML_PARSE_COMPACT
774 | XML_PARSE_OLD10
775 | XML_PARSE_HUGE
776 | XML_PARSE_OLDSAX
777 | XML_PARSE_IGNORE_ENC
778 | XML_PARSE_BIG_LINES;
779
780 unsafe {
781 apply_options(ctxt, options & ALL_MASK);
782 }
783 options & !ALL_MASK
784}
785
786/// Install a per-context structured error handler.
787///
788/// # UPSTREAM-PARITY
789///
790/// ```c
791/// void xmlCtxtSetErrorHandler(xmlParserCtxtPtr ctxt,
792/// xmlStructuredErrorFunc handler, void *data);
793/// ```
794///
795/// # SAFETY
796///
797/// - `ctxt`, `data` must be valid pointers (or NULL
798/// where the upstream C contract allows), obtained from the
799/// matching constructor/owner and not yet freed; the callee may
800/// take or keep ownership exactly as the C API specifies.
801///
802/// - `handler` must be a valid callback (or None);
803/// the callback is invoked with the documented context pointer and
804/// must itself uphold the same pointer invariants.
805///
806/// The caller must not race this call with concurrent mutation of the
807/// same objects from other threads (per-object state is not internally
808/// synchronized). Violating any of the above is undefined behavior.
809///
810/// Exercised by the C-API differential courts
811/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
812/// courts; those pass byte-for-byte against the upstream oracle.
813#[no_mangle]
814pub unsafe extern "C" fn xmlCtxtSetErrorHandler(
815 ctxt: *mut _xmlParserCtxt,
816 handler: Option<xmlStructuredErrorFunc>,
817 data: *mut c_void,
818) {
819 if ctxt.is_null() {
820 return;
821 }
822 unsafe {
823 (*ctxt).errorHandler = handler;
824 (*ctxt).errorCtxt = data;
825 }
826}
827
828/// Set the maximum entity expansion amplification factor.
829///
830/// # UPSTREAM-PARITY
831///
832/// ```c
833/// void xmlCtxtSetMaxAmplification(xmlParserCtxtPtr ctxt, unsigned maxAmpl);
834/// ```
835///
836/// # SAFETY
837///
838/// - `ctxt` must be valid pointers (or NULL
839/// where the upstream C contract allows), obtained from the
840/// matching constructor/owner and not yet freed; the callee may
841/// take or keep ownership exactly as the C API specifies.
842///
843/// The caller must not race this call with concurrent mutation of the
844/// same objects from other threads (per-object state is not internally
845/// synchronized). Violating any of the above is undefined behavior.
846///
847/// Exercised by the C-API differential courts
848/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
849/// courts; those pass byte-for-byte against the upstream oracle.
850#[no_mangle]
851pub unsafe extern "C" fn xmlCtxtSetMaxAmplification(ctxt: *mut _xmlParserCtxt, maxAmpl: c_uint) {
852 if ctxt.is_null() || maxAmpl == 0 {
853 return;
854 }
855 unsafe {
856 (*ctxt).maxAmpl = maxAmpl;
857 }
858}
859
860/// Get the last error raised on the context, or NULL.
861///
862/// # UPSTREAM-PARITY
863///
864/// ```c
865/// const xmlError *xmlCtxtGetLastError(void *ctx);
866/// ```
867///
868/// # SAFETY
869///
870/// - `ctx` must be valid pointers (or NULL
871/// where the upstream C contract allows), obtained from the
872/// matching constructor/owner and not yet freed; the callee may
873/// take or keep ownership exactly as the C API specifies.
874///
875/// The caller must not race this call with concurrent mutation of the
876/// same objects from other threads (per-object state is not internally
877/// synchronized). Violating any of the above is undefined behavior.
878///
879/// Exercised by the C-API differential courts
880/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
881/// courts; those pass byte-for-byte against the upstream oracle.
882#[no_mangle]
883pub unsafe extern "C" fn xmlCtxtGetLastError(ctx: *mut c_void) -> *const _xmlError {
884 if ctx.is_null() {
885 return ptr::null();
886 }
887 let ctxt = ctx as *mut _xmlParserCtxt;
888 unsafe {
889 if (*ctxt).lastError.code == XML_ERR_OK {
890 return ptr::null();
891 }
892 &(*ctxt).lastError
893 }
894}
895
896/// Reset the context's last-error state.
897///
898/// # UPSTREAM-PARITY
899///
900/// ```c
901/// void xmlCtxtResetLastError(void *ctx);
902/// ```
903///
904/// # SAFETY
905///
906/// - `ctx` must be valid pointers (or NULL
907/// where the upstream C contract allows), obtained from the
908/// matching constructor/owner and not yet freed; the callee may
909/// take or keep ownership exactly as the C API specifies.
910///
911/// The caller must not race this call with concurrent mutation of the
912/// same objects from other threads (per-object state is not internally
913/// synchronized). Violating any of the above is undefined behavior.
914///
915/// Exercised by the C-API differential courts
916/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
917/// courts; those pass byte-for-byte against the upstream oracle.
918#[no_mangle]
919pub unsafe extern "C" fn xmlCtxtResetLastError(ctx: *mut c_void) {
920 if ctx.is_null() {
921 return;
922 }
923 let ctxt = ctx as *mut _xmlParserCtxt;
924 unsafe {
925 (*ctxt).errNo = XML_ERR_OK;
926 if (*ctxt).lastError.code != XML_ERR_OK {
927 errors::reset_error(&mut (*ctxt).lastError);
928 }
929 }
930}
931
932/// Handle an out-of-memory error on a parser context.
933///
934/// # UPSTREAM-PARITY
935///
936/// ```c
937/// void xmlCtxtErrMemory(xmlParserCtxtPtr ctxt);
938/// ```
939///
940/// # SAFETY
941///
942/// - `ctxt` must be valid pointers (or NULL
943/// where the upstream C contract allows), obtained from the
944/// matching constructor/owner and not yet freed; the callee may
945/// take or keep ownership exactly as the C API specifies.
946///
947/// The caller must not race this call with concurrent mutation of the
948/// same objects from other threads (per-object state is not internally
949/// synchronized). Violating any of the above is undefined behavior.
950///
951/// Exercised by the C-API differential courts
952/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
953/// courts; those pass byte-for-byte against the upstream oracle.
954#[no_mangle]
955pub unsafe extern "C" fn xmlCtxtErrMemory(ctxt: *mut _xmlParserCtxt) {
956 if ctxt.is_null() {
957 return;
958 }
959 unsafe {
960 let c = &mut *ctxt;
961 c.errNo = XML_ERR_NO_MEMORY;
962 c.instate = xmlParserInputState::XML_PARSER_EOF as c_int;
963 c.wellFormed = 0;
964 c.disableSAX = 2;
965
966 c.lastError.domain = XML_FROM_PARSER;
967 c.lastError.code = XML_ERR_NO_MEMORY;
968 c.lastError.level = xmlErrorLevel::XML_ERR_FATAL as c_int;
969 c.lastError.message = c"out of memory\n".as_ptr() as *mut c_char;
970
971 if let Some(handler) = c.errorHandler {
972 handler(c.errorCtxt, &c.lastError);
973 } else if !c.sax.is_null() {
974 if let Some(serror) = (*c.sax).serror {
975 serror(c.userData, &c.lastError);
976 }
977 }
978 }
979}
980
981/// Stop the parser: no further processing will happen.
982///
983/// # UPSTREAM-PARITY
984///
985/// ```c
986/// void xmlStopParser(xmlParserCtxtPtr ctxt);
987/// ```
988///
989/// # SAFETY
990///
991/// - `ctxt` must be valid pointers (or NULL
992/// where the upstream C contract allows), obtained from the
993/// matching constructor/owner and not yet freed; the callee may
994/// take or keep ownership exactly as the C API specifies.
995///
996/// The caller must not race this call with concurrent mutation of the
997/// same objects from other threads (per-object state is not internally
998/// synchronized). Violating any of the above is undefined behavior.
999///
1000/// Exercised by the C-API differential courts
1001/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1002/// courts; those pass byte-for-byte against the upstream oracle.
1003#[no_mangle]
1004pub unsafe extern "C" fn xmlStopParser(ctxt: *mut _xmlParserCtxt) {
1005 if ctxt.is_null() {
1006 return;
1007 }
1008 unsafe {
1009 (*ctxt).disableSAX = 2;
1010 if (*ctxt).errNo == XML_ERR_OK {
1011 (*ctxt).errNo = XML_ERR_USER_STOP;
1012 (*ctxt).lastError.code = XML_ERR_USER_STOP;
1013 (*ctxt).wellFormed = 0;
1014 }
1015 }
1016}
1017
1018/// Return the byte offset of the current parse position within the current
1019/// entity, or -1 when it cannot be computed.
1020///
1021/// # UPSTREAM-PARITY
1022///
1023/// ```c
1024/// long xmlByteConsumed(xmlParserCtxtPtr ctxt);
1025/// ```
1026///
1027/// # SAFETY
1028///
1029/// - `ctxt` must be valid pointers (or NULL
1030/// where the upstream C contract allows), obtained from the
1031/// matching constructor/owner and not yet freed; the callee may
1032/// take or keep ownership exactly as the C API specifies.
1033///
1034/// The caller must not race this call with concurrent mutation of the
1035/// same objects from other threads (per-object state is not internally
1036/// synchronized). Violating any of the above is undefined behavior.
1037///
1038/// Exercised by the C-API differential courts
1039/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1040/// courts; those pass byte-for-byte against the upstream oracle.
1041#[no_mangle]
1042pub unsafe extern "C" fn xmlByteConsumed(ctxt: *mut _xmlParserCtxt) -> c_long {
1043 if ctxt.is_null() {
1044 return -1;
1045 }
1046 unsafe {
1047 let input = (*ctxt).input;
1048 if input.is_null() {
1049 return -1;
1050 }
1051 if !(*input).buf.is_null() && !(*(*input).buf).encoder.is_null() {
1052 // With an encoder we cannot cheaply compute the original byte
1053 // position; report the raw consumed count.
1054 return (*(*input).buf).rawconsumed as c_long;
1055 }
1056 let consumed = (*input).consumed;
1057 if (*input).base.is_null() {
1058 return consumed as c_long;
1059 }
1060 (consumed + ((*input).cur as usize).saturating_sub((*input).base as usize) as c_ulong)
1061 as c_long
1062 }
1063}
1064
1065/// Extract the directory part of a filename (newly allocated).
1066///
1067/// # UPSTREAM-PARITY
1068///
1069/// ```c
1070/// char *xmlParserGetDirectory(const char *filename);
1071/// ```
1072///
1073/// # SAFETY
1074///
1075///
1076/// - `filename` must point to valid NUL-terminated
1077/// strings (or NULL where the C contract allows) for the lifetime
1078/// of the call.
1079///
1080/// The caller must not race this call with concurrent mutation of the
1081/// same objects from other threads (per-object state is not internally
1082/// synchronized). Violating any of the above is undefined behavior.
1083///
1084/// Exercised by the C-API differential courts
1085/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1086/// courts; those pass byte-for-byte against the upstream oracle.
1087#[no_mangle]
1088pub unsafe extern "C" fn xmlParserGetDirectory(filename: *const c_char) -> *mut c_char {
1089 if filename.is_null() {
1090 return ptr::null_mut();
1091 }
1092 unsafe {
1093 let len = libc::strlen(filename);
1094 let mut last_sep: Option<usize> = None;
1095 for i in 0..len {
1096 if *filename.add(i) == b'/' as c_char {
1097 last_sep = Some(i);
1098 }
1099 }
1100 match last_sep {
1101 Some(0) => xmlMemStrdupImpl(c"/".as_ptr() as *const c_char) as *mut c_char,
1102 Some(pos) => {
1103 let slice = core::slice::from_raw_parts(filename as *const u8, pos);
1104 let mut v = slice.to_vec();
1105 v.push(0);
1106 xmlMemStrdupImpl(v.as_ptr() as *const c_char) as *mut c_char
1107 }
1108 None => xmlMemStrdupImpl(c".".as_ptr() as *const c_char) as *mut c_char,
1109 }
1110 }
1111}
1112
1113/// Check whether a file exists: 0 if stat fails, 2 if it is a directory,
1114/// 1 otherwise.
1115///
1116/// # UPSTREAM-PARITY
1117///
1118/// ```c
1119/// int xmlCheckFilename(const char *path);
1120/// ```
1121///
1122/// # SAFETY
1123///
1124///
1125/// - `path` must point to valid NUL-terminated
1126/// strings (or NULL where the C contract allows) for the lifetime
1127/// of the call.
1128///
1129/// The caller must not race this call with concurrent mutation of the
1130/// same objects from other threads (per-object state is not internally
1131/// synchronized). Violating any of the above is undefined behavior.
1132///
1133/// Exercised by the C-API differential courts
1134/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1135/// courts; those pass byte-for-byte against the upstream oracle.
1136#[no_mangle]
1137pub unsafe extern "C" fn xmlCheckFilename(path: *const c_char) -> c_int {
1138 if path.is_null() {
1139 return 0;
1140 }
1141 unsafe {
1142 let mut st: libc::stat = core::mem::zeroed();
1143 if libc::stat(path, &mut st) != 0 {
1144 return 0;
1145 }
1146 if st.st_mode & libc::S_IFMT == libc::S_IFDIR {
1147 2
1148 } else {
1149 1
1150 }
1151 }
1152}
1153
1154/// Test whether a public/system ID pair is one of the XHTML DTDs.
1155///
1156/// # UPSTREAM-PARITY
1157///
1158/// ```c
1159/// int xmlIsXHTML(const xmlChar *systemID, const xmlChar *publicID);
1160/// ```
1161///
1162/// # SAFETY
1163///
1164///
1165/// - `systemID`, `publicID` must point to valid NUL-terminated
1166/// strings (or NULL where the C contract allows) for the lifetime
1167/// of the call.
1168///
1169/// The caller must not race this call with concurrent mutation of the
1170/// same objects from other threads (per-object state is not internally
1171/// synchronized). Violating any of the above is undefined behavior.
1172///
1173/// Exercised by the C-API differential courts
1174/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1175/// courts; those pass byte-for-byte against the upstream oracle.
1176#[no_mangle]
1177pub unsafe extern "C" fn xmlIsXHTML(systemID: *const xmlChar, publicID: *const xmlChar) -> c_int {
1178 const XHTML_STRICT_PUBLIC_ID: &[u8] = b"-//W3C//DTD XHTML 1.0 Strict//EN\0";
1179 const XHTML_STRICT_SYSTEM_ID: &[u8] = b"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\0";
1180 const XHTML_FRAME_PUBLIC_ID: &[u8] = b"-//W3C//DTD XHTML 1.0 Frameset//EN\0";
1181 const XHTML_FRAME_SYSTEM_ID: &[u8] = b"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\0";
1182 const XHTML_TRANS_PUBLIC_ID: &[u8] = b"-//W3C//DTD XHTML 1.0 Transitional//EN\0";
1183 const XHTML_TRANS_SYSTEM_ID: &[u8] =
1184 b"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\0";
1185
1186 if systemID.is_null() && publicID.is_null() {
1187 return -1;
1188 }
1189 unsafe {
1190 if !publicID.is_null()
1191 && (string::xml_strcmp(publicID, XHTML_STRICT_PUBLIC_ID.as_ptr() as *const xmlChar)
1192 == 0
1193 || string::xml_strcmp(publicID, XHTML_FRAME_PUBLIC_ID.as_ptr() as *const xmlChar)
1194 == 0
1195 || string::xml_strcmp(publicID, XHTML_TRANS_PUBLIC_ID.as_ptr() as *const xmlChar)
1196 == 0)
1197 {
1198 return 1;
1199 }
1200 if !systemID.is_null()
1201 && (string::xml_strcmp(systemID, XHTML_STRICT_SYSTEM_ID.as_ptr() as *const xmlChar)
1202 == 0
1203 || string::xml_strcmp(systemID, XHTML_FRAME_SYSTEM_ID.as_ptr() as *const xmlChar)
1204 == 0
1205 || string::xml_strcmp(systemID, XHTML_TRANS_SYSTEM_ID.as_ptr() as *const xmlChar)
1206 == 0)
1207 {
1208 return 1;
1209 }
1210 }
1211 0
1212}
1213
1214// ═══════════════════════════════════════════════════════════════════════════════
1215// Context creation from sources
1216// ═══════════════════════════════════════════════════════════════════════════════
1217
1218/// Create a parser context for an in-memory document.
1219///
1220/// # UPSTREAM-PARITY
1221///
1222/// ```c
1223/// xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char *buffer, int size);
1224/// ```
1225///
1226/// # SAFETY
1227///
1228///
1229/// - `buffer` must point to valid NUL-terminated
1230/// strings (or NULL where the C contract allows) for the lifetime
1231/// of the call.
1232///
1233/// The caller must not race this call with concurrent mutation of the
1234/// same objects from other threads (per-object state is not internally
1235/// synchronized). Violating any of the above is undefined behavior.
1236///
1237/// Exercised by the C-API differential courts
1238/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1239/// courts; those pass byte-for-byte against the upstream oracle.
1240#[no_mangle]
1241pub unsafe extern "C" fn xmlCreateMemoryParserCtxt(
1242 buffer: *const c_char,
1243 size: c_int,
1244) -> *mut _xmlParserCtxt {
1245 if buffer.is_null() || size < 0 {
1246 return ptr::null_mut();
1247 }
1248 unsafe {
1249 let ctxt = xmlNewParserCtxt();
1250 if ctxt.is_null() {
1251 return ptr::null_mut();
1252 }
1253 let input = helpers::input_from_memory(buffer, size);
1254 helpers::setup_parser_input(ctxt, input);
1255 ctxt
1256 }
1257}
1258
1259/// Create a parser context for push parsing.
1260///
1261/// # UPSTREAM-PARITY
1262///
1263/// ```c
1264/// xmlParserCtxtPtr xmlCreatePushParserCtxt(xmlSAXHandler *sax, void *user_data,
1265/// const char *chunk, int size,
1266/// const char *filename);
1267/// ```
1268///
1269/// # SAFETY
1270///
1271/// - `sax`, `user_data` must be valid pointers (or NULL
1272/// where the upstream C contract allows), obtained from the
1273/// matching constructor/owner and not yet freed; the callee may
1274/// take or keep ownership exactly as the C API specifies.
1275///
1276/// - `chunk`, `filename` must point to valid NUL-terminated
1277/// strings (or NULL where the C contract allows) for the lifetime
1278/// of the call.
1279///
1280/// The caller must not race this call with concurrent mutation of the
1281/// same objects from other threads (per-object state is not internally
1282/// synchronized). Violating any of the above is undefined behavior.
1283///
1284/// Exercised by the C-API differential courts
1285/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1286/// courts; those pass byte-for-byte against the upstream oracle.
1287#[no_mangle]
1288pub unsafe extern "C" fn xmlCreatePushParserCtxt(
1289 sax: *mut _xmlSAXHandler,
1290 user_data: *mut c_void,
1291 chunk: *const c_char,
1292 size: c_int,
1293 filename: *const c_char,
1294) -> *mut _xmlParserCtxt {
1295 unsafe {
1296 let ctxt = xmlNewSAXParserCtxt(sax, user_data);
1297 if ctxt.is_null() {
1298 return ptr::null_mut();
1299 }
1300 let slice = if size > 0 && !chunk.is_null() {
1301 core::slice::from_raw_parts(chunk as *const u8, size as usize)
1302 } else {
1303 &[]
1304 };
1305 let uri = if filename.is_null() {
1306 None
1307 } else {
1308 CStr::from_ptr(filename).to_str().ok()
1309 };
1310 let input = InputBuffer::from_memory(slice, uri);
1311 helpers::setup_parser_input(ctxt, input);
1312 ctxt
1313 }
1314}
1315
1316/// Create a parser context for an I/O stream.
1317///
1318/// # UPSTREAM-PARITY
1319///
1320/// ```c
1321/// xmlParserCtxtPtr xmlCreateIOParserCtxt(xmlSAXHandler *sax, void *user_data,
1322/// xmlInputReadCallback ioread,
1323/// xmlInputCloseCallback ioclose,
1324/// void *ioctx, xmlCharEncoding enc);
1325/// ```
1326///
1327/// # SAFETY
1328///
1329/// - `sax`, `user_data`, `ioctx` must be valid pointers (or NULL
1330/// where the upstream C contract allows), obtained from the
1331/// matching constructor/owner and not yet freed; the callee may
1332/// take or keep ownership exactly as the C API specifies.
1333///
1334/// - `ioread`, `ioclose` must be a valid callback (or None);
1335/// the callback is invoked with the documented context pointer and
1336/// must itself uphold the same pointer invariants.
1337///
1338/// The caller must not race this call with concurrent mutation of the
1339/// same objects from other threads (per-object state is not internally
1340/// synchronized). Violating any of the above is undefined behavior.
1341///
1342/// Exercised by the C-API differential courts
1343/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1344/// courts; those pass byte-for-byte against the upstream oracle.
1345#[no_mangle]
1346pub unsafe extern "C" fn xmlCreateIOParserCtxt(
1347 sax: *mut _xmlSAXHandler,
1348 user_data: *mut c_void,
1349 ioread: Option<xmlInputReadCallback>,
1350 ioclose: Option<xmlInputCloseCallback>,
1351 ioctx: *mut c_void,
1352 enc: c_int,
1353) -> *mut _xmlParserCtxt {
1354 unsafe {
1355 let ctxt = xmlNewSAXParserCtxt(sax, user_data);
1356 if ctxt.is_null() {
1357 return ptr::null_mut();
1358 }
1359 let input = helpers::input_from_io(ioread, ioclose, ioctx);
1360 helpers::setup_parser_input(ctxt, input);
1361 if enc != xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int {
1362 xmlSwitchEncoding(ctxt, enc);
1363 }
1364 ctxt
1365 }
1366}
1367
1368/// Create a parser context for a file or URL.
1369///
1370/// # UPSTREAM-PARITY
1371///
1372/// ```c
1373/// xmlParserCtxtPtr xmlCreateURLParserCtxt(const char *filename, int options);
1374/// ```
1375///
1376/// # SAFETY
1377///
1378///
1379/// - `filename` must point to valid NUL-terminated
1380/// strings (or NULL where the C contract allows) for the lifetime
1381/// of the call.
1382///
1383/// The caller must not race this call with concurrent mutation of the
1384/// same objects from other threads (per-object state is not internally
1385/// synchronized). Violating any of the above is undefined behavior.
1386///
1387/// Exercised by the C-API differential courts
1388/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1389/// courts; those pass byte-for-byte against the upstream oracle.
1390#[no_mangle]
1391pub unsafe extern "C" fn xmlCreateURLParserCtxt(
1392 filename: *const c_char,
1393 options: c_int,
1394) -> *mut _xmlParserCtxt {
1395 if filename.is_null() {
1396 return ptr::null_mut();
1397 }
1398 unsafe {
1399 let ctxt = xmlNewParserCtxt();
1400 if ctxt.is_null() {
1401 return ptr::null_mut();
1402 }
1403 apply_options(ctxt, options);
1404 let input = match helpers::input_from_file(filename) {
1405 Ok(i) => i,
1406 Err(_) => {
1407 helpers::free_parser_ctxt(ctxt);
1408 return ptr::null_mut();
1409 }
1410 };
1411 helpers::setup_parser_input(ctxt, input);
1412 ctxt
1413 }
1414}
1415
1416/// Create a parser context for an external entity.
1417///
1418/// # UPSTREAM-PARITY
1419///
1420/// ```c
1421/// xmlParserCtxtPtr xmlCreateEntityParserCtxt(const xmlChar *URL,
1422/// const xmlChar *ID,
1423/// const xmlChar *base);
1424/// ```
1425///
1426/// # SAFETY
1427///
1428///
1429/// - `URL`, `ID`, `base` must point to valid NUL-terminated
1430/// strings (or NULL where the C contract allows) for the lifetime
1431/// of the call.
1432///
1433/// The caller must not race this call with concurrent mutation of the
1434/// same objects from other threads (per-object state is not internally
1435/// synchronized). Violating any of the above is undefined behavior.
1436///
1437/// Exercised by the C-API differential courts
1438/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1439/// courts; those pass byte-for-byte against the upstream oracle.
1440#[no_mangle]
1441pub unsafe extern "C" fn xmlCreateEntityParserCtxt(
1442 URL: *const xmlChar,
1443 ID: *const xmlChar,
1444 base: *const xmlChar,
1445) -> *mut _xmlParserCtxt {
1446 let _ = base; // base URI resolution is a no-op here
1447 unsafe {
1448 let ctxt = xmlNewParserCtxt();
1449 if ctxt.is_null() {
1450 return ptr::null_mut();
1451 }
1452 let input = xmlLoadExternalEntity(URL as *const c_char, ID as *const c_char, ctxt);
1453 if input.is_null() {
1454 helpers::free_parser_ctxt(ctxt);
1455 return ptr::null_mut();
1456 }
1457 if xmlPushInput(ctxt, input) < 0 {
1458 helpers::free_parser_input(input);
1459 helpers::free_parser_ctxt(ctxt);
1460 return ptr::null_mut();
1461 }
1462 ctxt
1463 }
1464}
1465
1466// ═══════════════════════════════════════════════════════════════════════════════
1467// CtxtRead family
1468// ═══════════════════════════════════════════════════════════════════════════════
1469
1470/// Parse an XML in-memory document with a given context.
1471///
1472/// # UPSTREAM-PARITY
1473///
1474/// ```c
1475/// xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar *cur,
1476/// const char *URL, const char *encoding, int options);
1477/// ```
1478///
1479/// # SAFETY
1480///
1481/// - `ctxt` must be valid pointers (or NULL
1482/// where the upstream C contract allows), obtained from the
1483/// matching constructor/owner and not yet freed; the callee may
1484/// take or keep ownership exactly as the C API specifies.
1485///
1486/// - `cur`, `URL`, `_encoding` must point to valid NUL-terminated
1487/// strings (or NULL where the C contract allows) for the lifetime
1488/// of the call.
1489///
1490/// The caller must not race this call with concurrent mutation of the
1491/// same objects from other threads (per-object state is not internally
1492/// synchronized). Violating any of the above is undefined behavior.
1493///
1494/// Exercised by the C-API differential courts
1495/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1496/// courts; those pass byte-for-byte against the upstream oracle.
1497#[no_mangle]
1498pub unsafe extern "C" fn xmlCtxtReadDoc(
1499 ctxt: *mut _xmlParserCtxt,
1500 cur: *const xmlChar,
1501 URL: *const c_char,
1502 _encoding: *const c_char,
1503 options: c_int,
1504) -> *mut _xmlDoc {
1505 if ctxt.is_null() || cur.is_null() {
1506 return ptr::null_mut();
1507 }
1508 unsafe {
1509 let len = string::xml_strlen(cur);
1510 let input = helpers::input_from_memory(cur as *const c_char, len as c_int);
1511 ctxt_read_doc(ctxt, input, URL, options)
1512 }
1513}
1514
1515/// Parse an XML file with a given context.
1516///
1517/// # UPSTREAM-PARITY
1518///
1519/// ```c
1520/// xmlDocPtr xmlCtxtReadFile(xmlParserCtxtPtr ctxt, const char *filename,
1521/// const char *encoding, int options);
1522/// ```
1523///
1524/// # SAFETY
1525///
1526/// - `ctxt` must be valid pointers (or NULL
1527/// where the upstream C contract allows), obtained from the
1528/// matching constructor/owner and not yet freed; the callee may
1529/// take or keep ownership exactly as the C API specifies.
1530///
1531/// - `filename`, `_encoding` must point to valid NUL-terminated
1532/// strings (or NULL where the C contract allows) for the lifetime
1533/// of the call.
1534///
1535/// The caller must not race this call with concurrent mutation of the
1536/// same objects from other threads (per-object state is not internally
1537/// synchronized). Violating any of the above is undefined behavior.
1538///
1539/// Exercised by the C-API differential courts
1540/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1541/// courts; those pass byte-for-byte against the upstream oracle.
1542#[no_mangle]
1543pub unsafe extern "C" fn xmlCtxtReadFile(
1544 ctxt: *mut _xmlParserCtxt,
1545 filename: *const c_char,
1546 _encoding: *const c_char,
1547 options: c_int,
1548) -> *mut _xmlDoc {
1549 if ctxt.is_null() || filename.is_null() {
1550 return ptr::null_mut();
1551 }
1552 unsafe {
1553 match helpers::input_from_file(filename) {
1554 Ok(input) => ctxt_read_doc(ctxt, input, filename, options),
1555 Err(_) => ptr::null_mut(),
1556 }
1557 }
1558}
1559
1560/// Parse an XML in-memory block with a given context.
1561///
1562/// # UPSTREAM-PARITY
1563///
1564/// ```c
1565/// xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt, const char *buffer,
1566/// int size, const char *URL, const char *encoding,
1567/// int options);
1568/// ```
1569///
1570/// # SAFETY
1571///
1572/// - `ctxt` must be valid pointers (or NULL
1573/// where the upstream C contract allows), obtained from the
1574/// matching constructor/owner and not yet freed; the callee may
1575/// take or keep ownership exactly as the C API specifies.
1576///
1577/// - `buffer`, `URL`, `_encoding` must point to valid NUL-terminated
1578/// strings (or NULL where the C contract allows) for the lifetime
1579/// of the call.
1580///
1581/// The caller must not race this call with concurrent mutation of the
1582/// same objects from other threads (per-object state is not internally
1583/// synchronized). Violating any of the above is undefined behavior.
1584///
1585/// Exercised by the C-API differential courts
1586/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1587/// courts; those pass byte-for-byte against the upstream oracle.
1588#[no_mangle]
1589pub unsafe extern "C" fn xmlCtxtReadMemory(
1590 ctxt: *mut _xmlParserCtxt,
1591 buffer: *const c_char,
1592 size: c_int,
1593 URL: *const c_char,
1594 _encoding: *const c_char,
1595 options: c_int,
1596) -> *mut _xmlDoc {
1597 if ctxt.is_null() || buffer.is_null() || size < 0 {
1598 return ptr::null_mut();
1599 }
1600 unsafe {
1601 let input = helpers::input_from_memory(buffer, size);
1602 ctxt_read_doc(ctxt, input, URL, options)
1603 }
1604}
1605
1606/// Parse an XML document from a file descriptor with a given context.
1607///
1608/// # UPSTREAM-PARITY
1609///
1610/// ```c
1611/// xmlDocPtr xmlCtxtReadFd(xmlParserCtxtPtr ctxt, int fd, const char *URL,
1612/// const char *encoding, int options);
1613/// ```
1614///
1615/// # SAFETY
1616///
1617/// - `ctxt` must be valid pointers (or NULL
1618/// where the upstream C contract allows), obtained from the
1619/// matching constructor/owner and not yet freed; the callee may
1620/// take or keep ownership exactly as the C API specifies.
1621///
1622/// - `URL`, `_encoding` must point to valid NUL-terminated
1623/// strings (or NULL where the C contract allows) for the lifetime
1624/// of the call.
1625///
1626/// The caller must not race this call with concurrent mutation of the
1627/// same objects from other threads (per-object state is not internally
1628/// synchronized). Violating any of the above is undefined behavior.
1629///
1630/// Exercised by the C-API differential courts
1631/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1632/// courts; those pass byte-for-byte against the upstream oracle.
1633#[no_mangle]
1634pub unsafe extern "C" fn xmlCtxtReadFd(
1635 ctxt: *mut _xmlParserCtxt,
1636 fd: c_int,
1637 URL: *const c_char,
1638 _encoding: *const c_char,
1639 options: c_int,
1640) -> *mut _xmlDoc {
1641 if ctxt.is_null() || fd < 0 {
1642 return ptr::null_mut();
1643 }
1644 unsafe {
1645 let mut buf = Vec::new();
1646 let mut tmp = [0u8; 4096];
1647 loop {
1648 let n = libc::read(fd, tmp.as_mut_ptr() as *mut c_void, tmp.len());
1649 if n <= 0 {
1650 break;
1651 }
1652 buf.extend_from_slice(&tmp[..n as usize]);
1653 }
1654 let input = helpers::input_from_memory(buf.as_ptr() as *const c_char, buf.len() as c_int);
1655 ctxt_read_doc(ctxt, input, URL, options)
1656 }
1657}
1658
1659/// Parse an XML document from I/O callbacks with a given context.
1660///
1661/// # UPSTREAM-PARITY
1662///
1663/// ```c
1664/// xmlDocPtr xmlCtxtReadIO(xmlParserCtxtPtr ctxt, xmlInputReadCallback ioread,
1665/// xmlInputCloseCallback ioclose, void *ioctx,
1666/// const char *URL, const char *encoding, int options);
1667/// ```
1668///
1669/// # SAFETY
1670///
1671/// - `ctxt`, `ioctx` must be valid pointers (or NULL
1672/// where the upstream C contract allows), obtained from the
1673/// matching constructor/owner and not yet freed; the callee may
1674/// take or keep ownership exactly as the C API specifies.
1675///
1676/// - `URL`, `_encoding` must point to valid NUL-terminated
1677/// strings (or NULL where the C contract allows) for the lifetime
1678/// of the call.
1679///
1680/// - `ioread`, `ioclose` must be a valid callback (or None);
1681/// the callback is invoked with the documented context pointer and
1682/// must itself uphold the same pointer invariants.
1683///
1684/// The caller must not race this call with concurrent mutation of the
1685/// same objects from other threads (per-object state is not internally
1686/// synchronized). Violating any of the above is undefined behavior.
1687///
1688/// Exercised by the C-API differential courts
1689/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1690/// courts; those pass byte-for-byte against the upstream oracle.
1691#[no_mangle]
1692pub unsafe extern "C" fn xmlCtxtReadIO(
1693 ctxt: *mut _xmlParserCtxt,
1694 ioread: Option<xmlInputReadCallback>,
1695 ioclose: Option<xmlInputCloseCallback>,
1696 ioctx: *mut c_void,
1697 URL: *const c_char,
1698 _encoding: *const c_char,
1699 options: c_int,
1700) -> *mut _xmlDoc {
1701 if ctxt.is_null() {
1702 return ptr::null_mut();
1703 }
1704 unsafe {
1705 let input = helpers::input_from_io(ioread, ioclose, ioctx);
1706 ctxt_read_doc(ctxt, input, URL, options)
1707 }
1708}
1709
1710/// Parse a document from a raw parser input, taking ownership of `input`.
1711///
1712/// # UPSTREAM-PARITY
1713///
1714/// ```c
1715/// xmlDocPtr xmlCtxtParseDocument(xmlParserCtxtPtr ctxt, xmlParserInputPtr input);
1716/// ```
1717///
1718/// # SAFETY
1719///
1720/// - `ctxt`, `input` must be valid pointers (or NULL
1721/// where the upstream C contract allows), obtained from the
1722/// matching constructor/owner and not yet freed; the callee may
1723/// take or keep ownership exactly as the C API specifies.
1724///
1725/// The caller must not race this call with concurrent mutation of the
1726/// same objects from other threads (per-object state is not internally
1727/// synchronized). Violating any of the above is undefined behavior.
1728///
1729/// Exercised by the C-API differential courts
1730/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1731/// courts; those pass byte-for-byte against the upstream oracle.
1732#[no_mangle]
1733pub unsafe extern "C" fn xmlCtxtParseDocument(
1734 ctxt: *mut _xmlParserCtxt,
1735 input: *mut _xmlParserInput,
1736) -> *mut _xmlDoc {
1737 if ctxt.is_null() || input.is_null() {
1738 return ptr::null_mut();
1739 }
1740 unsafe {
1741 // Determine whether the caller's input is already owned by the
1742 // context's input stack (pushed via xmlPushInput).
1743 let mut owned = false;
1744 let nr = (*ctxt).inputNr;
1745 let tab = (*ctxt).inputTab;
1746 if !tab.is_null() {
1747 for i in 0..nr {
1748 if *tab.add(i as usize) == input {
1749 owned = true;
1750 break;
1751 }
1752 }
1753 }
1754 if (*ctxt).input == input {
1755 owned = true;
1756 }
1757
1758 // Copy the data first so the context reset cannot invalidate it.
1759 let ib = input_buffer_from_parser_input(input);
1760
1761 xmlCtxtReset(ctxt);
1762 helpers::setup_parser_input(ctxt, ib);
1763 helpers::parse_document(ctxt);
1764
1765 if !owned {
1766 helpers::free_parser_input(input);
1767 }
1768
1769 (*ctxt).myDoc
1770 }
1771}
1772
1773// ═══════════════════════════════════════════════════════════════════════════════
1774// Parser input buffers / streams
1775// ═══════════════════════════════════════════════════════════════════════════════
1776
1777/// Allocate a parser input buffer for the given encoding.
1778///
1779/// # UPSTREAM-PARITY
1780///
1781/// ```c
1782/// xmlParserInputBufferPtr xmlAllocParserInputBuffer(xmlCharEncoding enc);
1783/// ```
1784///
1785/// # SAFETY
1786///
1787/// The function touches crate-global state only; it is safe
1788/// as long as the caller respects the library's global
1789/// initialization/cleanup ordering (xmlInitParser before use,
1790/// xmlCleanupParser only after all users are done).
1791///
1792/// Violating the global lifecycle ordering, or calling this after
1793/// teardown or from a signal handler, is undefined behavior.
1794#[no_mangle]
1795pub unsafe extern "C" fn xmlAllocParserInputBuffer(enc: c_int) -> *mut _xmlParserInputBuffer {
1796 unsafe {
1797 let buf = xmlMallocZero(core::mem::size_of::<_xmlParserInputBuffer>())
1798 as *mut _xmlParserInputBuffer;
1799 if buf.is_null() {
1800 return ptr::null_mut();
1801 }
1802 let b = &mut *buf;
1803 b.buffer = io::buf_create(crate::abi::data_globals::xmlDefaultBufferSize) as *mut c_void;
1804 b.raw = io::buf_create(crate::abi::data_globals::xmlDefaultBufferSize) as *mut c_void;
1805 if b.buffer.is_null() || b.raw.is_null() {
1806 io::buf_free(b.buffer as *mut _xmlBuffer);
1807 io::buf_free(b.raw as *mut _xmlBuffer);
1808 xmlFreeImpl(buf as *mut c_void);
1809 return ptr::null_mut();
1810 }
1811 b.compressed = -1;
1812
1813 if enc != xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int
1814 && enc != xmlCharEncoding::XML_CHAR_ENCODING_ERROR as c_int
1815 {
1816 let handler = encoding_handler_for(enc);
1817 if !handler.is_null() {
1818 b.encoder = handler as *mut c_void;
1819 }
1820 }
1821 buf
1822 }
1823}
1824
1825/// Grow an input buffer by reading up to `len` bytes from its source.
1826///
1827/// # UPSTREAM-PARITY
1828///
1829/// ```c
1830/// int xmlParserInputBufferGrow(xmlParserInputBufferPtr in, int len);
1831/// ```
1832///
1833/// # SAFETY
1834///
1835/// - `in_` must be valid pointers (or NULL
1836/// where the upstream C contract allows), obtained from the
1837/// matching constructor/owner and not yet freed; the callee may
1838/// take or keep ownership exactly as the C API specifies.
1839///
1840/// The caller must not race this call with concurrent mutation of the
1841/// same objects from other threads (per-object state is not internally
1842/// synchronized). Violating any of the above is undefined behavior.
1843///
1844/// Exercised by the C-API differential courts
1845/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1846/// courts; those pass byte-for-byte against the upstream oracle.
1847#[no_mangle]
1848pub unsafe extern "C" fn xmlParserInputBufferGrow(
1849 in_: *mut _xmlParserInputBuffer,
1850 len: c_int,
1851) -> c_int {
1852 if in_.is_null() || len <= 0 {
1853 return 0;
1854 }
1855 unsafe {
1856 let b = &mut *in_;
1857 if b.error != 0 {
1858 return -1;
1859 }
1860 let Some(read_cb) = b.readcallback else {
1861 // Memory-based buffer: nothing to grow.
1862 return 0;
1863 };
1864 let mut tmp = vec![0u8; len as usize];
1865 let n = read_cb(b.context, tmp.as_mut_ptr() as *mut c_char, len);
1866 if n < 0 {
1867 b.error = 1;
1868 return -1;
1869 }
1870 if n == 0 {
1871 return 0;
1872 }
1873 io::input_buffer_push(in_, tmp.as_ptr() as *const c_char, n);
1874 n
1875 }
1876}
1877
1878/// Push `len` bytes into an input buffer (push parser).
1879///
1880/// # UPSTREAM-PARITY
1881///
1882/// ```c
1883/// int xmlParserInputBufferPush(xmlParserInputBufferPtr in, int len, const char *buf);
1884/// ```
1885///
1886/// # SAFETY
1887///
1888/// - `in_` must be valid pointers (or NULL
1889/// where the upstream C contract allows), obtained from the
1890/// matching constructor/owner and not yet freed; the callee may
1891/// take or keep ownership exactly as the C API specifies.
1892///
1893/// - `buf` must point to valid NUL-terminated
1894/// strings (or NULL where the C contract allows) for the lifetime
1895/// of the call.
1896///
1897/// The caller must not race this call with concurrent mutation of the
1898/// same objects from other threads (per-object state is not internally
1899/// synchronized). Violating any of the above is undefined behavior.
1900///
1901/// Exercised by the C-API differential courts
1902/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1903/// courts; those pass byte-for-byte against the upstream oracle.
1904#[no_mangle]
1905pub unsafe extern "C" fn xmlParserInputBufferPush(
1906 in_: *mut _xmlParserInputBuffer,
1907 len: c_int,
1908 buf: *const c_char,
1909) -> c_int {
1910 if in_.is_null() {
1911 return -1;
1912 }
1913 if len < 0 || (len > 0 && buf.is_null()) {
1914 return -1;
1915 }
1916 if len == 0 {
1917 return 0;
1918 }
1919 io::input_buffer_push(in_, buf, len)
1920}
1921
1922/// Read up to `len` bytes from an input buffer's source.
1923///
1924/// # UPSTREAM-PARITY
1925///
1926/// ```c
1927/// int xmlParserInputBufferRead(xmlParserInputBufferPtr in, int len);
1928/// ```
1929///
1930/// # SAFETY
1931///
1932/// - `in_` must be valid pointers (or NULL
1933/// where the upstream C contract allows), obtained from the
1934/// matching constructor/owner and not yet freed; the callee may
1935/// take or keep ownership exactly as the C API specifies.
1936///
1937/// The caller must not race this call with concurrent mutation of the
1938/// same objects from other threads (per-object state is not internally
1939/// synchronized). Violating any of the above is undefined behavior.
1940///
1941/// Exercised by the C-API differential courts
1942/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1943/// courts; those pass byte-for-byte against the upstream oracle.
1944#[no_mangle]
1945pub unsafe extern "C" fn xmlParserInputBufferRead(
1946 in_: *mut _xmlParserInputBuffer,
1947 len: c_int,
1948) -> c_int {
1949 xmlParserInputBufferGrow(in_, len)
1950}
1951
1952/// Deprecated: reading directly from an input stream is an error.
1953///
1954/// # UPSTREAM-PARITY
1955///
1956/// ```c
1957/// int xmlParserInputRead(xmlParserInputPtr in, int len);
1958/// ```
1959///
1960/// # SAFETY
1961///
1962/// - `_in_` must be valid pointers (or NULL
1963/// where the upstream C contract allows), obtained from the
1964/// matching constructor/owner and not yet freed; the callee may
1965/// take or keep ownership exactly as the C API specifies.
1966///
1967/// The caller must not race this call with concurrent mutation of the
1968/// same objects from other threads (per-object state is not internally
1969/// synchronized). Violating any of the above is undefined behavior.
1970///
1971/// Exercised by the C-API differential courts
1972/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
1973/// courts; those pass byte-for-byte against the upstream oracle.
1974#[no_mangle]
1975pub const unsafe extern "C" fn xmlParserInputRead(
1976 _in_: *mut _xmlParserInput,
1977 _len: c_int,
1978) -> c_int {
1979 -1
1980}
1981
1982/// Grow a parser input's buffer by reading more data from its source.
1983///
1984/// # UPSTREAM-PARITY
1985///
1986/// ```c
1987/// int xmlParserInputGrow(xmlParserInputPtr in, int len);
1988/// ```
1989///
1990/// # SAFETY
1991///
1992/// - `in_` must be valid pointers (or NULL
1993/// where the upstream C contract allows), obtained from the
1994/// matching constructor/owner and not yet freed; the callee may
1995/// take or keep ownership exactly as the C API specifies.
1996///
1997/// The caller must not race this call with concurrent mutation of the
1998/// same objects from other threads (per-object state is not internally
1999/// synchronized). Violating any of the above is undefined behavior.
2000///
2001/// Exercised by the C-API differential courts
2002/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2003/// courts; those pass byte-for-byte against the upstream oracle.
2004#[no_mangle]
2005pub unsafe extern "C" fn xmlParserInputGrow(in_: *mut _xmlParserInput, len: c_int) -> c_int {
2006 if in_.is_null() || len < 0 {
2007 return -1;
2008 }
2009 unsafe {
2010 let pi = &*in_;
2011 if pi.base.is_null() || pi.cur.is_null() {
2012 return -1;
2013 }
2014 if pi.buf.is_null() {
2015 // Pure memory input: nothing to grow.
2016 return 0;
2017 }
2018 let b = &*pi.buf;
2019 // Memory buffers are not growable.
2020 if b.readcallback.is_none() && b.encoder.is_null() {
2021 return 0;
2022 }
2023 xmlParserInputBufferGrow(pi.buf, len)
2024 }
2025}
2026
2027/// Shrink a parser input, releasing already-consumed data from the buffer.
2028///
2029/// # UPSTREAM-PARITY
2030///
2031/// ```c
2032/// void xmlParserInputShrink(xmlParserInputPtr in);
2033/// ```
2034///
2035/// # SAFETY
2036///
2037/// - `in_` must be valid pointers (or NULL
2038/// where the upstream C contract allows), obtained from the
2039/// matching constructor/owner and not yet freed; the callee may
2040/// take or keep ownership exactly as the C API specifies.
2041///
2042/// The caller must not race this call with concurrent mutation of the
2043/// same objects from other threads (per-object state is not internally
2044/// synchronized). Violating any of the above is undefined behavior.
2045///
2046/// Exercised by the C-API differential courts
2047/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2048/// courts; those pass byte-for-byte against the upstream oracle.
2049#[no_mangle]
2050pub unsafe extern "C" fn xmlParserInputShrink(in_: *mut _xmlParserInput) {
2051 if in_.is_null() {
2052 return;
2053 }
2054 unsafe {
2055 let pi = &mut *in_;
2056 if pi.buf.is_null() || pi.base.is_null() || pi.cur.is_null() {
2057 return;
2058 }
2059 let used = (pi.cur as usize).saturating_sub(pi.base as usize);
2060 if used > LINE_LEN {
2061 // The candidate's inputs are backed by stable memory buffers, so
2062 // the base pointer cannot move; account for the consumed bytes.
2063 pi.consumed = pi.consumed.saturating_add((used - LINE_LEN) as c_ulong);
2064 }
2065 }
2066}
2067
2068/// Create a new (empty) parser input stream.
2069///
2070/// # UPSTREAM-PARITY
2071///
2072/// ```c
2073/// xmlParserInputPtr xmlNewInputStream(xmlParserCtxtPtr ctxt);
2074/// ```
2075///
2076/// # SAFETY
2077///
2078/// - `ctxt` must be valid pointers (or NULL
2079/// where the upstream C contract allows), obtained from the
2080/// matching constructor/owner and not yet freed; the callee may
2081/// take or keep ownership exactly as the C API specifies.
2082///
2083/// The caller must not race this call with concurrent mutation of the
2084/// same objects from other threads (per-object state is not internally
2085/// synchronized). Violating any of the above is undefined behavior.
2086///
2087/// Exercised by the C-API differential courts
2088/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2089/// courts; those pass byte-for-byte against the upstream oracle.
2090#[no_mangle]
2091pub unsafe extern "C" fn xmlNewInputStream(ctxt: *mut _xmlParserCtxt) -> *mut _xmlParserInput {
2092 unsafe {
2093 let input = xmlMallocZero(core::mem::size_of::<_xmlParserInput>()) as *mut _xmlParserInput;
2094 if input.is_null() {
2095 if !ctxt.is_null() {
2096 xmlCtxtErrMemory(ctxt);
2097 }
2098 return ptr::null_mut();
2099 }
2100 (*input).line = 1;
2101 (*input).col = 1;
2102 input
2103 }
2104}
2105
2106/// Wrap an input buffer in a parser input stream.
2107///
2108/// # UPSTREAM-PARITY
2109///
2110/// ```c
2111/// xmlParserInputPtr xmlNewIOInputStream(xmlParserCtxtPtr ctxt,
2112/// xmlParserInputBufferPtr input,
2113/// xmlCharEncoding enc);
2114/// ```
2115///
2116/// # SAFETY
2117///
2118/// - `ctxt`, `input` must be valid pointers (or NULL
2119/// where the upstream C contract allows), obtained from the
2120/// matching constructor/owner and not yet freed; the callee may
2121/// take or keep ownership exactly as the C API specifies.
2122///
2123/// The caller must not race this call with concurrent mutation of the
2124/// same objects from other threads (per-object state is not internally
2125/// synchronized). Violating any of the above is undefined behavior.
2126///
2127/// Exercised by the C-API differential courts
2128/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2129/// courts; those pass byte-for-byte against the upstream oracle.
2130#[no_mangle]
2131pub unsafe extern "C" fn xmlNewIOInputStream(
2132 ctxt: *mut _xmlParserCtxt,
2133 input: *mut _xmlParserInputBuffer,
2134 enc: c_int,
2135) -> *mut _xmlParserInput {
2136 if ctxt.is_null() || input.is_null() {
2137 return ptr::null_mut();
2138 }
2139 unsafe {
2140 let pi = xmlNewInputStream(ctxt);
2141 if pi.is_null() {
2142 return ptr::null_mut();
2143 }
2144 (*pi).buf = input;
2145 if enc != xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int
2146 && enc != xmlCharEncoding::XML_CHAR_ENCODING_ERROR as c_int
2147 {
2148 let handler = encoding_handler_for(enc);
2149 if !handler.is_null() {
2150 io::input_buffer_set_encoder(input, handler);
2151 }
2152 }
2153 pi
2154 }
2155}
2156
2157/// Create a parser input stream from a zero-terminated string. The string
2158/// must remain valid for the lifetime of the input (static mode).
2159///
2160/// # UPSTREAM-PARITY
2161///
2162/// ```c
2163/// xmlParserInputPtr xmlNewStringInputStream(xmlParserCtxtPtr ctxt,
2164/// const xmlChar *buffer);
2165/// ```
2166///
2167/// # SAFETY
2168///
2169/// - `ctxt` must be valid pointers (or NULL
2170/// where the upstream C contract allows), obtained from the
2171/// matching constructor/owner and not yet freed; the callee may
2172/// take or keep ownership exactly as the C API specifies.
2173///
2174/// - `buffer` must point to valid NUL-terminated
2175/// strings (or NULL where the C contract allows) for the lifetime
2176/// of the call.
2177///
2178/// The caller must not race this call with concurrent mutation of the
2179/// same objects from other threads (per-object state is not internally
2180/// synchronized). Violating any of the above is undefined behavior.
2181///
2182/// Exercised by the C-API differential courts
2183/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2184/// courts; those pass byte-for-byte against the upstream oracle.
2185#[no_mangle]
2186pub unsafe extern "C" fn xmlNewStringInputStream(
2187 ctxt: *mut _xmlParserCtxt,
2188 buffer: *const xmlChar,
2189) -> *mut _xmlParserInput {
2190 if ctxt.is_null() || buffer.is_null() {
2191 return ptr::null_mut();
2192 }
2193 unsafe {
2194 let input = xmlNewInputStream(ctxt);
2195 if input.is_null() {
2196 return ptr::null_mut();
2197 }
2198 let len = string::xml_strlen(buffer);
2199 (*input).base = buffer;
2200 (*input).cur = buffer;
2201 (*input).end = buffer.add(len);
2202 (*input).length = len as c_int;
2203 input
2204 }
2205}
2206
2207/// Setup the parser context to parse a new buffer (legacy API).
2208///
2209/// # UPSTREAM-PARITY
2210///
2211/// ```c
2212/// void xmlSetupParserForBuffer(xmlParserCtxtPtr ctxt, const xmlChar* buffer,
2213/// const char *filename);
2214/// ```
2215///
2216/// # SAFETY
2217///
2218/// - `ctxt` must be valid pointers (or NULL
2219/// where the upstream C contract allows), obtained from the
2220/// matching constructor/owner and not yet freed; the callee may
2221/// take or keep ownership exactly as the C API specifies.
2222///
2223/// - `buffer`, `filename` must point to valid NUL-terminated
2224/// strings (or NULL where the C contract allows) for the lifetime
2225/// of the call.
2226///
2227/// The caller must not race this call with concurrent mutation of the
2228/// same objects from other threads (per-object state is not internally
2229/// synchronized). Violating any of the above is undefined behavior.
2230///
2231/// Exercised by the C-API differential courts
2232/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2233/// courts; those pass byte-for-byte against the upstream oracle.
2234#[no_mangle]
2235pub unsafe extern "C" fn xmlSetupParserForBuffer(
2236 ctxt: *mut _xmlParserCtxt,
2237 buffer: *const xmlChar,
2238 filename: *const c_char,
2239) {
2240 if ctxt.is_null() || buffer.is_null() {
2241 return;
2242 }
2243 unsafe {
2244 xmlCtxtReset(ctxt);
2245 let len = string::xml_strlen(buffer);
2246 let uri = if filename.is_null() {
2247 None
2248 } else {
2249 CStr::from_ptr(filename).to_str().ok()
2250 };
2251 let input = InputBuffer::from_memory(core::slice::from_raw_parts(buffer, len), uri);
2252 helpers::setup_parser_input(ctxt, input);
2253 }
2254}
2255
2256/// Push an input stream onto the context's input stack.
2257///
2258/// # UPSTREAM-PARITY
2259///
2260/// ```c
2261/// int xmlPushInput(xmlParserCtxtPtr ctxt, xmlParserInputPtr input);
2262/// ```
2263///
2264/// # SAFETY
2265///
2266/// - `ctxt`, `input` must be valid pointers (or NULL
2267/// where the upstream C contract allows), obtained from the
2268/// matching constructor/owner and not yet freed; the callee may
2269/// take or keep ownership exactly as the C API specifies.
2270///
2271/// The caller must not race this call with concurrent mutation of the
2272/// same objects from other threads (per-object state is not internally
2273/// synchronized). Violating any of the above is undefined behavior.
2274///
2275/// Exercised by the C-API differential courts
2276/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2277/// courts; those pass byte-for-byte against the upstream oracle.
2278#[no_mangle]
2279pub unsafe extern "C" fn xmlPushInput(
2280 ctxt: *mut _xmlParserCtxt,
2281 input: *mut _xmlParserInput,
2282) -> c_int {
2283 if ctxt.is_null() || input.is_null() {
2284 return -1;
2285 }
2286 unsafe {
2287 let c = &mut *ctxt;
2288 if c.inputNr >= c.inputMax {
2289 let new_max = if c.inputMax == 0 { 5 } else { c.inputMax * 2 };
2290 let new_tab = xmlReallocImpl(
2291 c.inputTab as *mut c_void,
2292 (new_max as usize) * core::mem::size_of::<*mut _xmlParserInput>(),
2293 ) as *mut *mut _xmlParserInput;
2294 if new_tab.is_null() {
2295 return -1;
2296 }
2297 c.inputTab = new_tab;
2298 c.inputMax = new_max;
2299 }
2300 *c.inputTab.add(c.inputNr as usize) = input;
2301 c.input = input;
2302 (*input).id = c.input_id;
2303 c.input_id += 1;
2304 let idx = c.inputNr;
2305 c.inputNr += 1;
2306 idx
2307 }
2308}
2309
2310/// Pop the top input from the context's input stack and free it; returns the
2311/// current character after the pop (0 at end of input).
2312///
2313/// # UPSTREAM-PARITY
2314///
2315/// ```c
2316/// xmlChar xmlPopInput(xmlParserCtxtPtr ctxt);
2317/// ```
2318///
2319/// # SAFETY
2320///
2321/// - `ctxt` must be valid pointers (or NULL
2322/// where the upstream C contract allows), obtained from the
2323/// matching constructor/owner and not yet freed; the callee may
2324/// take or keep ownership exactly as the C API specifies.
2325///
2326/// The caller must not race this call with concurrent mutation of the
2327/// same objects from other threads (per-object state is not internally
2328/// synchronized). Violating any of the above is undefined behavior.
2329///
2330/// Exercised by the C-API differential courts
2331/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2332/// courts; those pass byte-for-byte against the upstream oracle.
2333#[no_mangle]
2334pub unsafe extern "C" fn xmlPopInput(ctxt: *mut _xmlParserCtxt) -> xmlChar {
2335 if ctxt.is_null() || (*ctxt).inputNr <= 1 {
2336 return 0;
2337 }
2338 unsafe {
2339 let c = &mut *ctxt;
2340 c.inputNr -= 1;
2341 let popped = *c.inputTab.add(c.inputNr as usize);
2342 *c.inputTab.add(c.inputNr as usize) = ptr::null_mut();
2343 if c.inputNr > 0 {
2344 c.input = *c.inputTab.add((c.inputNr - 1) as usize);
2345 } else {
2346 c.input = ptr::null_mut();
2347 }
2348 if !popped.is_null() {
2349 helpers::free_parser_input(popped);
2350 }
2351 if c.input.is_null() {
2352 return 0;
2353 }
2354 let cur = (*c.input).cur;
2355 let end = (*c.input).end;
2356 if cur.is_null() || cur >= end {
2357 0
2358 } else {
2359 *cur
2360 }
2361 }
2362}
2363
2364// ═══════════════════════════════════════════════════════════════════════════════
2365// Encoding switching
2366// ═══════════════════════════════════════════════════════════════════════════════
2367
2368/// Switch the input encoding of the current input.
2369///
2370/// # UPSTREAM-PARITY
2371///
2372/// ```c
2373/// int xmlSwitchEncoding(xmlParserCtxtPtr ctxt, xmlCharEncoding enc);
2374/// ```
2375///
2376/// # SAFETY
2377///
2378/// - `ctxt` must be valid pointers (or NULL
2379/// where the upstream C contract allows), obtained from the
2380/// matching constructor/owner and not yet freed; the callee may
2381/// take or keep ownership exactly as the C API specifies.
2382///
2383/// The caller must not race this call with concurrent mutation of the
2384/// same objects from other threads (per-object state is not internally
2385/// synchronized). Violating any of the above is undefined behavior.
2386///
2387/// Exercised by the C-API differential courts
2388/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2389/// courts; those pass byte-for-byte against the upstream oracle.
2390#[no_mangle]
2391pub unsafe extern "C" fn xmlSwitchEncoding(ctxt: *mut _xmlParserCtxt, enc: c_int) -> c_int {
2392 if ctxt.is_null() || (*ctxt).input.is_null() {
2393 return -1;
2394 }
2395 if enc == xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int {
2396 return 0;
2397 }
2398 unsafe {
2399 let handler = encoding_handler_for(enc);
2400 if handler.is_null() {
2401 return -1;
2402 }
2403 xmlSwitchToEncoding(ctxt, handler)
2404 }
2405}
2406
2407/// Switch the input encoding by name.
2408///
2409/// # UPSTREAM-PARITY
2410///
2411/// ```c
2412/// int xmlSwitchEncodingName(xmlParserCtxtPtr ctxt, const char *encoding);
2413/// ```
2414///
2415/// # SAFETY
2416///
2417/// - `ctxt` must be valid pointers (or NULL
2418/// where the upstream C contract allows), obtained from the
2419/// matching constructor/owner and not yet freed; the callee may
2420/// take or keep ownership exactly as the C API specifies.
2421///
2422/// - `encoding` must point to valid NUL-terminated
2423/// strings (or NULL where the C contract allows) for the lifetime
2424/// of the call.
2425///
2426/// The caller must not race this call with concurrent mutation of the
2427/// same objects from other threads (per-object state is not internally
2428/// synchronized). Violating any of the above is undefined behavior.
2429///
2430/// Exercised by the C-API differential courts
2431/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2432/// courts; those pass byte-for-byte against the upstream oracle.
2433#[no_mangle]
2434pub unsafe extern "C" fn xmlSwitchEncodingName(
2435 ctxt: *mut _xmlParserCtxt,
2436 encoding: *const c_char,
2437) -> c_int {
2438 if ctxt.is_null() || encoding.is_null() {
2439 return -1;
2440 }
2441 unsafe {
2442 let handler = encoding::find_encoding_handler(encoding as *const xmlChar);
2443 if handler.is_null() {
2444 return -1;
2445 }
2446 xmlSwitchToEncoding(ctxt, handler)
2447 }
2448}
2449
2450/// Switch the encoding of a specific parser input using an encoding handler.
2451///
2452/// # UPSTREAM-PARITY
2453///
2454/// ```c
2455/// int xmlSwitchInputEncoding(xmlParserCtxtPtr ctxt, xmlParserInputPtr input,
2456/// xmlCharEncodingHandlerPtr handler);
2457/// ```
2458///
2459/// # SAFETY
2460///
2461/// - `ctxt`, `input`, `handler` must be valid pointers (or NULL
2462/// where the upstream C contract allows), obtained from the
2463/// matching constructor/owner and not yet freed; the callee may
2464/// take or keep ownership exactly as the C API specifies.
2465///
2466/// The caller must not race this call with concurrent mutation of the
2467/// same objects from other threads (per-object state is not internally
2468/// synchronized). Violating any of the above is undefined behavior.
2469///
2470/// Exercised by the C-API differential courts
2471/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2472/// courts; those pass byte-for-byte against the upstream oracle.
2473#[no_mangle]
2474pub unsafe extern "C" fn xmlSwitchInputEncoding(
2475 ctxt: *mut _xmlParserCtxt,
2476 input: *mut _xmlParserInput,
2477 handler: *mut _xmlCharEncodingHandler,
2478) -> c_int {
2479 let _ = ctxt;
2480 if input.is_null() {
2481 return -1;
2482 }
2483 unsafe {
2484 if (*input).buf.is_null() {
2485 return -1;
2486 }
2487 io::input_buffer_set_encoder((*input).buf, handler);
2488 }
2489 0
2490}
2491
2492/// Switch the encoding of the current input using an encoding handler.
2493///
2494/// # UPSTREAM-PARITY
2495///
2496/// ```c
2497/// int xmlSwitchToEncoding(xmlParserCtxtPtr ctxt,
2498/// xmlCharEncodingHandlerPtr handler);
2499/// ```
2500///
2501/// # SAFETY
2502///
2503/// - `ctxt`, `handler` must be valid pointers (or NULL
2504/// where the upstream C contract allows), obtained from the
2505/// matching constructor/owner and not yet freed; the callee may
2506/// take or keep ownership exactly as the C API specifies.
2507///
2508/// The caller must not race this call with concurrent mutation of the
2509/// same objects from other threads (per-object state is not internally
2510/// synchronized). Violating any of the above is undefined behavior.
2511///
2512/// Exercised by the C-API differential courts
2513/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2514/// courts; those pass byte-for-byte against the upstream oracle.
2515#[no_mangle]
2516pub unsafe extern "C" fn xmlSwitchToEncoding(
2517 ctxt: *mut _xmlParserCtxt,
2518 handler: *mut _xmlCharEncodingHandler,
2519) -> c_int {
2520 if ctxt.is_null() {
2521 return -1;
2522 }
2523 unsafe {
2524 let input = (*ctxt).input;
2525 if input.is_null() || (*input).buf.is_null() {
2526 return -1;
2527 }
2528 io::input_buffer_set_encoder((*input).buf, handler);
2529 }
2530 0
2531}
2532
2533// ═══════════════════════════════════════════════════════════════════════════════
2534// Node info sequence (deprecated, parser.h)
2535// ═══════════════════════════════════════════════════════════════════════════════
2536
2537/// Initialise a node info sequence.
2538///
2539/// # UPSTREAM-PARITY
2540///
2541/// ```c
2542/// void xmlInitNodeInfoSeq(xmlParserNodeInfoSeqPtr seq);
2543/// ```
2544///
2545/// # SAFETY
2546///
2547/// - `seq` must be valid pointers (or NULL
2548/// where the upstream C contract allows), obtained from the
2549/// matching constructor/owner and not yet freed; the callee may
2550/// take or keep ownership exactly as the C API specifies.
2551///
2552/// The caller must not race this call with concurrent mutation of the
2553/// same objects from other threads (per-object state is not internally
2554/// synchronized). Violating any of the above is undefined behavior.
2555///
2556/// Exercised by the C-API differential courts
2557/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2558/// courts; those pass byte-for-byte against the upstream oracle.
2559#[no_mangle]
2560pub unsafe extern "C" fn xmlInitNodeInfoSeq(seq: *mut _xmlParserNodeInfoSeq) {
2561 if seq.is_null() {
2562 return;
2563 }
2564 unsafe {
2565 (*seq).block = ptr::null_mut();
2566 (*seq).index = ptr::null_mut();
2567 (*seq).block_max = 0;
2568 (*seq).size = 0;
2569 }
2570}
2571
2572/// Clear (release and reinitialise) a node info sequence.
2573///
2574/// # UPSTREAM-PARITY
2575///
2576/// ```c
2577/// void xmlClearNodeInfoSeq(xmlParserNodeInfoSeqPtr seq);
2578/// ```
2579///
2580/// # SAFETY
2581///
2582/// - `seq` must be valid pointers (or NULL
2583/// where the upstream C contract allows), obtained from the
2584/// matching constructor/owner and not yet freed; the callee may
2585/// take or keep ownership exactly as the C API specifies.
2586///
2587/// The caller must not race this call with concurrent mutation of the
2588/// same objects from other threads (per-object state is not internally
2589/// synchronized). Violating any of the above is undefined behavior.
2590///
2591/// Exercised by the C-API differential courts
2592/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2593/// courts; those pass byte-for-byte against the upstream oracle.
2594#[no_mangle]
2595pub unsafe extern "C" fn xmlClearNodeInfoSeq(seq: *mut _xmlParserNodeInfoSeq) {
2596 if seq.is_null() {
2597 return;
2598 }
2599 unsafe {
2600 if !(*seq).block.is_null() {
2601 xmlFreeImpl((*seq).block as *mut c_void);
2602 }
2603 if !(*seq).index.is_null() {
2604 xmlFreeImpl((*seq).index as *mut c_void);
2605 }
2606 xmlInitNodeInfoSeq(seq);
2607 }
2608}
2609
2610/// Find the index where the info record for `node` is (or should be) in the
2611/// sorted sequence; binary search by node pointer.
2612///
2613/// # UPSTREAM-PARITY
2614///
2615/// ```c
2616/// unsigned long xmlParserFindNodeInfoIndex(xmlParserNodeInfoSeqPtr seq,
2617/// xmlNodePtr node);
2618/// ```
2619///
2620/// # SAFETY
2621///
2622/// - `seq`, `node` must be valid pointers (or NULL
2623/// where the upstream C contract allows), obtained from the
2624/// matching constructor/owner and not yet freed; the callee may
2625/// take or keep ownership exactly as the C API specifies.
2626///
2627/// The caller must not race this call with concurrent mutation of the
2628/// same objects from other threads (per-object state is not internally
2629/// synchronized). Violating any of the above is undefined behavior.
2630///
2631/// Exercised by the C-API differential courts
2632/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2633/// courts; those pass byte-for-byte against the upstream oracle.
2634#[no_mangle]
2635pub unsafe extern "C" fn xmlParserFindNodeInfoIndex(
2636 seq: *mut _xmlParserNodeInfoSeq,
2637 node: *mut _xmlNode,
2638) -> c_ulong {
2639 if seq.is_null() || node.is_null() {
2640 return c_ulong::MAX;
2641 }
2642 unsafe {
2643 let s = &*seq;
2644 if s.block.is_null() || s.size == 0 {
2645 return 0;
2646 }
2647 let mut lower: usize = 0;
2648 let mut upper: usize = s.size as usize;
2649 while lower < upper {
2650 let middle = lower + (upper - lower) / 2;
2651 let cur_node = (*s.block.add(middle)).node;
2652 if cur_node == node {
2653 return middle as c_ulong;
2654 }
2655 if (cur_node as usize) < (node as usize) {
2656 lower = middle + 1;
2657 } else {
2658 upper = middle;
2659 }
2660 }
2661 lower as c_ulong
2662 }
2663}
2664
2665/// Find the node info record for a given node, or NULL.
2666///
2667/// # UPSTREAM-PARITY
2668///
2669/// ```c
2670/// const xmlParserNodeInfo *xmlParserFindNodeInfo(xmlParserCtxtPtr ctxt,
2671/// xmlNodePtr node);
2672/// ```
2673///
2674/// # SAFETY
2675///
2676/// - `ctxt`, `node` must be valid pointers (or NULL
2677/// where the upstream C contract allows), obtained from the
2678/// matching constructor/owner and not yet freed; the callee may
2679/// take or keep ownership exactly as the C API specifies.
2680///
2681/// The caller must not race this call with concurrent mutation of the
2682/// same objects from other threads (per-object state is not internally
2683/// synchronized). Violating any of the above is undefined behavior.
2684///
2685/// Exercised by the C-API differential courts
2686/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2687/// courts; those pass byte-for-byte against the upstream oracle.
2688#[no_mangle]
2689pub unsafe extern "C" fn xmlParserFindNodeInfo(
2690 ctxt: *mut _xmlParserCtxt,
2691 node: *mut _xmlNode,
2692) -> *const _xmlParserNodeInfo {
2693 if ctxt.is_null() || node.is_null() {
2694 return ptr::null();
2695 }
2696 unsafe {
2697 let seq = &(*ctxt).node_seq;
2698 let seq_mut = seq as *const _ as *mut _xmlParserNodeInfoSeq;
2699 let pos = xmlParserFindNodeInfoIndex(seq_mut, node);
2700 if !seq.block.is_null() && (pos as usize) < (seq.size as usize) {
2701 let info = &*seq.block.add(pos as usize);
2702 if info.node == node {
2703 return info;
2704 }
2705 }
2706 ptr::null()
2707 }
2708}
2709
2710/// Insert a node info record into the context's sorted sequence.
2711///
2712/// # UPSTREAM-PARITY
2713///
2714/// ```c
2715/// void xmlParserAddNodeInfo(xmlParserCtxtPtr ctxt, xmlParserNodeInfoPtr info);
2716/// ```
2717///
2718/// # SAFETY
2719///
2720/// - `ctxt`, `info` must be valid pointers (or NULL
2721/// where the upstream C contract allows), obtained from the
2722/// matching constructor/owner and not yet freed; the callee may
2723/// take or keep ownership exactly as the C API specifies.
2724///
2725/// The caller must not race this call with concurrent mutation of the
2726/// same objects from other threads (per-object state is not internally
2727/// synchronized). Violating any of the above is undefined behavior.
2728///
2729/// Exercised by the C-API differential courts
2730/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2731/// courts; those pass byte-for-byte against the upstream oracle.
2732#[no_mangle]
2733pub unsafe extern "C" fn xmlParserAddNodeInfo(
2734 ctxt: *mut _xmlParserCtxt,
2735 info: *mut _xmlParserNodeInfo,
2736) {
2737 if ctxt.is_null() || info.is_null() {
2738 return;
2739 }
2740 unsafe {
2741 let seq = &mut (*ctxt).node_seq;
2742 let node = (*info).node;
2743 let pos = xmlParserFindNodeInfoIndex(seq, node as *mut _xmlNode) as usize;
2744
2745 if !seq.block.is_null() && pos < seq.size as usize && (*seq.block.add(pos)).node == node {
2746 ptr::copy_nonoverlapping(info, seq.block.add(pos), 1);
2747 return;
2748 }
2749
2750 // Grow the block.
2751 if seq.size + 1 > seq.block_max {
2752 let new_max = if seq.block_max == 0 {
2753 4
2754 } else {
2755 seq.block_max * 2
2756 };
2757 let new_block = xmlReallocImpl(
2758 seq.block as *mut c_void,
2759 (new_max as usize) * core::mem::size_of::<_xmlParserNodeInfo>(),
2760 ) as *mut _xmlParserNodeInfo;
2761 if new_block.is_null() {
2762 xmlCtxtErrMemory(ctxt);
2763 return;
2764 }
2765 seq.block = new_block;
2766 seq.block_max = new_max;
2767 }
2768
2769 // Shift elements right to make room at `pos`.
2770 let size = seq.size as usize;
2771 for i in (pos + 1..=size).rev() {
2772 ptr::copy_nonoverlapping(seq.block.add(i - 1), seq.block.add(i), 1);
2773 }
2774 ptr::copy_nonoverlapping(info, seq.block.add(pos), 1);
2775 seq.size += 1;
2776 }
2777}
2778
2779// ═══════════════════════════════════════════════════════════════════════════════
2780// I/O callback registration (xmlIO.h)
2781// ═══════════════════════════════════════════════════════════════════════════════
2782
2783/// Register a new set of input I/O callbacks.
2784///
2785/// # UPSTREAM-PARITY
2786///
2787/// ```c
2788/// int xmlRegisterInputCallbacks(xmlInputMatchCallback matchFunc,
2789/// xmlInputOpenCallback openFunc,
2790/// xmlInputReadCallback readFunc,
2791/// xmlInputCloseCallback closeFunc);
2792/// ```
2793///
2794/// # SAFETY
2795///
2796///
2797/// - `matchFunc`, `openFunc`, `readFunc`, `closeFunc` must be a valid callback (or None);
2798/// the callback is invoked with the documented context pointer and
2799/// must itself uphold the same pointer invariants.
2800///
2801/// The caller must not race this call with concurrent mutation of the
2802/// same objects from other threads (per-object state is not internally
2803/// synchronized). Violating any of the above is undefined behavior.
2804///
2805/// Exercised by the C-API differential courts
2806/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2807/// courts; those pass byte-for-byte against the upstream oracle.
2808#[no_mangle]
2809pub unsafe extern "C" fn xmlRegisterInputCallbacks(
2810 matchFunc: Option<xmlInputMatchCallback>,
2811 openFunc: Option<xmlInputOpenCallback>,
2812 readFunc: Option<xmlInputReadCallback>,
2813 closeFunc: Option<xmlInputCloseCallback>,
2814) -> c_int {
2815 unsafe {
2816 globals::init_parser();
2817 }
2818 let mut table = INPUT_CALLBACKS.lock();
2819 if table.len() >= 10 {
2820 return -1;
2821 }
2822 table.push(InputCallbackEntry {
2823 matchcb: matchFunc,
2824 opencb: openFunc,
2825 readcb: readFunc,
2826 closecb: closeFunc,
2827 });
2828 (table.len() - 1) as c_int
2829}
2830
2831/// Register the default compiled-in input callbacks (the `xmlFile*` pair).
2832///
2833/// # UPSTREAM-PARITY
2834///
2835/// ```c
2836/// void xmlRegisterDefaultInputCallbacks(void);
2837/// ```
2838///
2839/// # SAFETY
2840///
2841/// The function touches crate-global state only; it is safe
2842/// as long as the caller respects the library's global
2843/// initialization/cleanup ordering (xmlInitParser before use,
2844/// xmlCleanupParser only after all users are done).
2845///
2846/// Violating the global lifecycle ordering, or calling this after
2847/// teardown or from a signal handler, is undefined behavior.
2848#[no_mangle]
2849pub unsafe extern "C" fn xmlRegisterDefaultInputCallbacks() {
2850 unsafe {
2851 xmlRegisterInputCallbacks(
2852 Some(xmlFileMatch),
2853 Some(xmlFileOpen),
2854 Some(xmlFileRead),
2855 Some(xmlFileClose),
2856 );
2857 }
2858}
2859
2860/// Remove the top input callback from the stack.
2861///
2862/// # UPSTREAM-PARITY
2863///
2864/// ```c
2865/// int xmlPopInputCallbacks(void);
2866/// ```
2867///
2868/// # SAFETY
2869///
2870/// The function touches crate-global state only; it is safe
2871/// as long as the caller respects the library's global
2872/// initialization/cleanup ordering (xmlInitParser before use,
2873/// xmlCleanupParser only after all users are done).
2874///
2875/// Violating the global lifecycle ordering, or calling this after
2876/// teardown or from a signal handler, is undefined behavior.
2877#[no_mangle]
2878pub unsafe extern "C" fn xmlPopInputCallbacks() -> c_int {
2879 unsafe {
2880 globals::init_parser();
2881 }
2882 let mut table = INPUT_CALLBACKS.lock();
2883 if table.is_empty() {
2884 return -1;
2885 }
2886 table.pop();
2887 table.len() as c_int
2888}
2889
2890/// Clear the entire input callback table.
2891///
2892/// # UPSTREAM-PARITY
2893///
2894/// ```c
2895/// void xmlCleanupInputCallbacks(void);
2896/// ```
2897///
2898/// # SAFETY
2899///
2900/// The function touches crate-global state only; it is safe
2901/// as long as the caller respects the library's global
2902/// initialization/cleanup ordering (xmlInitParser before use,
2903/// xmlCleanupParser only after all users are done).
2904///
2905/// Violating the global lifecycle ordering, or calling this after
2906/// teardown or from a signal handler, is undefined behavior.
2907#[no_mangle]
2908pub unsafe extern "C" fn xmlCleanupInputCallbacks() {
2909 unsafe {
2910 globals::init_parser();
2911 }
2912 INPUT_CALLBACKS.lock().clear();
2913}
2914
2915/// Register a new set of output I/O callbacks.
2916///
2917/// # UPSTREAM-PARITY
2918///
2919/// ```c
2920/// int xmlRegisterOutputCallbacks(xmlOutputMatchCallback matchFunc,
2921/// xmlOutputOpenCallback openFunc,
2922/// xmlOutputWriteCallback writeFunc,
2923/// xmlOutputCloseCallback closeFunc);
2924/// ```
2925///
2926/// # SAFETY
2927///
2928///
2929/// - `matchFunc`, `openFunc`, `writeFunc`, `closeFunc` must be a valid callback (or None);
2930/// the callback is invoked with the documented context pointer and
2931/// must itself uphold the same pointer invariants.
2932///
2933/// The caller must not race this call with concurrent mutation of the
2934/// same objects from other threads (per-object state is not internally
2935/// synchronized). Violating any of the above is undefined behavior.
2936///
2937/// Exercised by the C-API differential courts
2938/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
2939/// courts; those pass byte-for-byte against the upstream oracle.
2940#[no_mangle]
2941pub unsafe extern "C" fn xmlRegisterOutputCallbacks(
2942 matchFunc: Option<xmlOutputMatchCallback>,
2943 openFunc: Option<xmlOutputOpenCallback>,
2944 writeFunc: Option<xmlOutputWriteCallback>,
2945 closeFunc: Option<xmlOutputCloseCallback>,
2946) -> c_int {
2947 unsafe {
2948 globals::init_parser();
2949 }
2950 let mut table = OUTPUT_CALLBACKS.lock();
2951 if table.len() >= 10 {
2952 return -1;
2953 }
2954 table.push(OutputCallbackEntry {
2955 matchcb: matchFunc,
2956 opencb: openFunc,
2957 writecb: writeFunc,
2958 closecb: closeFunc,
2959 });
2960 (table.len() - 1) as c_int
2961}
2962
2963/// Register the default compiled-in output callbacks.
2964///
2965/// # UPSTREAM-PARITY
2966///
2967/// ```c
2968/// void xmlRegisterDefaultOutputCallbacks(void);
2969/// ```
2970///
2971/// # SAFETY
2972///
2973/// The function touches crate-global state only; it is safe
2974/// as long as the caller respects the library's global
2975/// initialization/cleanup ordering (xmlInitParser before use,
2976/// xmlCleanupParser only after all users are done).
2977///
2978/// Violating the global lifecycle ordering, or calling this after
2979/// teardown or from a signal handler, is undefined behavior.
2980#[no_mangle]
2981pub unsafe extern "C" fn xmlRegisterDefaultOutputCallbacks() {
2982 unsafe {
2983 xmlRegisterOutputCallbacks(Some(xmlFileMatch), None, None, None);
2984 }
2985}
2986
2987/// Register the HTTP POST output callbacks (upstream: default output callbacks).
2988///
2989/// # UPSTREAM-PARITY
2990///
2991/// ```c
2992/// void xmlRegisterHTTPPostCallbacks(void);
2993/// ```
2994///
2995/// # SAFETY
2996///
2997/// The function touches crate-global state only; it is safe
2998/// as long as the caller respects the library's global
2999/// initialization/cleanup ordering (xmlInitParser before use,
3000/// xmlCleanupParser only after all users are done).
3001///
3002/// Violating the global lifecycle ordering, or calling this after
3003/// teardown or from a signal handler, is undefined behavior.
3004#[no_mangle]
3005pub unsafe extern "C" fn xmlRegisterHTTPPostCallbacks() {
3006 unsafe { xmlRegisterDefaultOutputCallbacks() }
3007}
3008
3009/// Remove the top output callback from the stack.
3010///
3011/// # UPSTREAM-PARITY
3012///
3013/// ```c
3014/// int xmlPopOutputCallbacks(void);
3015/// ```
3016///
3017/// # SAFETY
3018///
3019/// The function touches crate-global state only; it is safe
3020/// as long as the caller respects the library's global
3021/// initialization/cleanup ordering (xmlInitParser before use,
3022/// xmlCleanupParser only after all users are done).
3023///
3024/// Violating the global lifecycle ordering, or calling this after
3025/// teardown or from a signal handler, is undefined behavior.
3026#[no_mangle]
3027pub unsafe extern "C" fn xmlPopOutputCallbacks() -> c_int {
3028 unsafe {
3029 globals::init_parser();
3030 }
3031 let mut table = OUTPUT_CALLBACKS.lock();
3032 if table.is_empty() {
3033 return -1;
3034 }
3035 table.pop();
3036 table.len() as c_int
3037}
3038
3039/// Clear the entire output callback table.
3040///
3041/// # UPSTREAM-PARITY
3042///
3043/// ```c
3044/// void xmlCleanupOutputCallbacks(void);
3045/// ```
3046///
3047/// # SAFETY
3048///
3049/// The function touches crate-global state only; it is safe
3050/// as long as the caller respects the library's global
3051/// initialization/cleanup ordering (xmlInitParser before use,
3052/// xmlCleanupParser only after all users are done).
3053///
3054/// Violating the global lifecycle ordering, or calling this after
3055/// teardown or from a signal handler, is undefined behavior.
3056#[no_mangle]
3057pub unsafe extern "C" fn xmlCleanupOutputCallbacks() {
3058 unsafe {
3059 globals::init_parser();
3060 }
3061 OUTPUT_CALLBACKS.lock().clear();
3062}
3063
3064// ═══════════════════════════════════════════════════════════════════════════════
3065// External entity loaders (parser.h)
3066// ═══════════════════════════════════════════════════════════════════════════════
3067
3068/// Default external entity loader: resolve `url` against the filesystem,
3069/// honouring XML_PARSE_NONET.
3070///
3071/// # Safety
3072///
3073/// `url`/`publicId` must be valid C strings or NULL; `ctxt` may be NULL.
3074unsafe extern "C" fn default_external_entity_loader(
3075 url: *const c_char,
3076 public_id: *const c_char,
3077 ctxt: *mut _xmlParserCtxt,
3078) -> *mut _xmlParserInput {
3079 let _ = public_id;
3080 if url.is_null() {
3081 return ptr::null_mut();
3082 }
3083 unsafe {
3084 // Refuse network access when NONET is set.
3085 if !ctxt.is_null() && (*ctxt).options & XML_PARSE_NONET != 0 {
3086 let len = libc::strlen(url);
3087 if len >= 7 && libc::strncasecmp(url, c"http://".as_ptr() as *const c_char, 7) == 0 {
3088 return ptr::null_mut();
3089 }
3090 }
3091 // Try the registered input callbacks first.
3092 let table = INPUT_CALLBACKS.lock();
3093 for entry in table.iter() {
3094 if let (Some(match_cb), Some(open_cb)) = (entry.matchcb, entry.opencb) {
3095 if match_cb(url) != 0 {
3096 let ctx = open_cb(url);
3097 if !ctx.is_null() {
3098 let buf = helpers::alloc_parser_input_buffer();
3099 if buf.is_null() {
3100 if let Some(close_cb) = entry.closecb {
3101 close_cb(ctx);
3102 }
3103 return ptr::null_mut();
3104 }
3105 (*buf).context = ctx;
3106 (*buf).readcallback = entry.readcb;
3107 (*buf).closecallback = entry.closecb;
3108 return parser_input_from_buf(buf);
3109 }
3110 }
3111 }
3112 }
3113
3114 // Fall back to a plain file open.
3115 let buf =
3116 io::input_buffer_create_file(url, xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int);
3117 if buf.is_null() {
3118 return ptr::null_mut();
3119 }
3120 parser_input_from_buf(buf)
3121 }
3122}
3123
3124/// Set the application-wide external entity loader.
3125///
3126/// # UPSTREAM-PARITY
3127///
3128/// ```c
3129/// void xmlSetExternalEntityLoader(xmlExternalEntityLoader f);
3130/// ```
3131///
3132/// # SAFETY
3133///
3134///
3135/// - `f` must be a valid callback (or None);
3136/// the callback is invoked with the documented context pointer and
3137/// must itself uphold the same pointer invariants.
3138///
3139/// The caller must not race this call with concurrent mutation of the
3140/// same objects from other threads (per-object state is not internally
3141/// synchronized). Violating any of the above is undefined behavior.
3142///
3143/// Exercised by the C-API differential courts
3144/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3145/// courts; those pass byte-for-byte against the upstream oracle.
3146#[no_mangle]
3147pub unsafe extern "C" fn xmlSetExternalEntityLoader(f: Option<xmlExternalEntityLoader>) {
3148 *EXTERNAL_ENTITY_LOADER.lock() = f;
3149}
3150
3151/// Get the current external entity loader.
3152///
3153/// # UPSTREAM-PARITY
3154///
3155/// ```c
3156/// xmlExternalEntityLoader xmlGetExternalEntityLoader(void);
3157/// ```
3158///
3159/// # SAFETY
3160///
3161/// The function touches crate-global state only; it is safe
3162/// as long as the caller respects the library's global
3163/// initialization/cleanup ordering (xmlInitParser before use,
3164/// xmlCleanupParser only after all users are done).
3165///
3166/// Violating the global lifecycle ordering, or calling this after
3167/// teardown or from a signal handler, is undefined behavior.
3168#[no_mangle]
3169pub unsafe extern "C" fn xmlGetExternalEntityLoader() -> Option<xmlExternalEntityLoader> {
3170 *EXTERNAL_ENTITY_LOADER.lock()
3171}
3172
3173/// External entity loader that disables network access.
3174///
3175/// # UPSTREAM-PARITY
3176///
3177/// ```c
3178/// xmlParserInputPtr xmlNoNetExternalEntityLoader(const char *URL,
3179/// const char *ID,
3180/// xmlParserCtxtPtr ctxt);
3181/// ```
3182///
3183/// # SAFETY
3184///
3185/// - `ctxt` must be valid pointers (or NULL
3186/// where the upstream C contract allows), obtained from the
3187/// matching constructor/owner and not yet freed; the callee may
3188/// take or keep ownership exactly as the C API specifies.
3189///
3190/// - `URL`, `ID` must point to valid NUL-terminated
3191/// strings (or NULL where the C contract allows) for the lifetime
3192/// of the call.
3193///
3194/// The caller must not race this call with concurrent mutation of the
3195/// same objects from other threads (per-object state is not internally
3196/// synchronized). Violating any of the above is undefined behavior.
3197///
3198/// Exercised by the C-API differential courts
3199/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3200/// courts; those pass byte-for-byte against the upstream oracle.
3201#[no_mangle]
3202pub unsafe extern "C" fn xmlNoNetExternalEntityLoader(
3203 URL: *const c_char,
3204 ID: *const c_char,
3205 ctxt: *mut _xmlParserCtxt,
3206) -> *mut _xmlParserInput {
3207 unsafe {
3208 let old_options = if ctxt.is_null() { 0 } else { (*ctxt).options };
3209 if !ctxt.is_null() {
3210 (*ctxt).options |= XML_PARSE_NONET;
3211 }
3212 let input = default_external_entity_loader(URL, ID, ctxt);
3213 if !ctxt.is_null() {
3214 (*ctxt).options = old_options;
3215 }
3216 input
3217 }
3218}
3219
3220/// Load an external entity using the registered loader.
3221///
3222/// # UPSTREAM-PARITY
3223///
3224/// ```c
3225/// xmlParserInputPtr xmlLoadExternalEntity(const char *URL, const char *ID,
3226/// xmlParserCtxtPtr ctxt);
3227/// ```
3228///
3229/// # SAFETY
3230///
3231/// - `ctxt` must be valid pointers (or NULL
3232/// where the upstream C contract allows), obtained from the
3233/// matching constructor/owner and not yet freed; the callee may
3234/// take or keep ownership exactly as the C API specifies.
3235///
3236/// - `URL`, `ID` must point to valid NUL-terminated
3237/// strings (or NULL where the C contract allows) for the lifetime
3238/// of the call.
3239///
3240/// The caller must not race this call with concurrent mutation of the
3241/// same objects from other threads (per-object state is not internally
3242/// synchronized). Violating any of the above is undefined behavior.
3243///
3244/// Exercised by the C-API differential courts
3245/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3246/// courts; those pass byte-for-byte against the upstream oracle.
3247#[no_mangle]
3248pub unsafe extern "C" fn xmlLoadExternalEntity(
3249 URL: *const c_char,
3250 ID: *const c_char,
3251 ctxt: *mut _xmlParserCtxt,
3252) -> *mut _xmlParserInput {
3253 let loader = *EXTERNAL_ENTITY_LOADER.lock();
3254 match loader {
3255 Some(f) => unsafe { f(URL, ID, ctxt) },
3256 None => unsafe { default_external_entity_loader(URL, ID, ctxt) },
3257 }
3258}
3259
3260/// Check an input for HTTP access; with XML_PARSE_NONET set, HTTP inputs are
3261/// refused and freed.
3262///
3263/// # UPSTREAM-PARITY
3264///
3265/// ```c
3266/// xmlParserInputPtr xmlCheckHTTPInput(xmlParserCtxtPtr ctxt,
3267/// xmlParserInputPtr ret);
3268/// ```
3269///
3270/// # SAFETY
3271///
3272/// - `ctxt`, `ret` must be valid pointers (or NULL
3273/// where the upstream C contract allows), obtained from the
3274/// matching constructor/owner and not yet freed; the callee may
3275/// take or keep ownership exactly as the C API specifies.
3276///
3277/// The caller must not race this call with concurrent mutation of the
3278/// same objects from other threads (per-object state is not internally
3279/// synchronized). Violating any of the above is undefined behavior.
3280///
3281/// Exercised by the C-API differential courts
3282/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3283/// courts; those pass byte-for-byte against the upstream oracle.
3284#[no_mangle]
3285pub unsafe extern "C" fn xmlCheckHTTPInput(
3286 ctxt: *mut _xmlParserCtxt,
3287 ret: *mut _xmlParserInput,
3288) -> *mut _xmlParserInput {
3289 if ret.is_null() {
3290 return ptr::null_mut();
3291 }
3292 unsafe {
3293 if !ctxt.is_null() && (*ctxt).options & XML_PARSE_NONET != 0 {
3294 let filename = (*ret).filename;
3295 if !filename.is_null() {
3296 let len = libc::strlen(filename);
3297 if len >= 7
3298 && libc::strncasecmp(filename, c"http://".as_ptr() as *const c_char, 7) == 0
3299 {
3300 // free_parser_input now frees the owned buffer (upstream
3301 // xmlFreeInputStream semantics); no separate buf free.
3302 helpers::free_parser_input(ret);
3303 return ptr::null_mut();
3304 }
3305 }
3306 }
3307 ret
3308 }
3309}
3310
3311// ═══════════════════════════════════════════════════════════════════════════════
3312// xmlFile* I/O callbacks (xmlIO.c)
3313// ═══════════════════════════════════════════════════════════════════════════════
3314
3315/// Match callback: the file I/O handlers accept every filename.
3316///
3317/// # UPSTREAM-PARITY
3318///
3319/// ```c
3320/// int xmlFileMatch(const char *filename);
3321/// ```
3322///
3323/// # SAFETY
3324///
3325///
3326/// - `_filename` must point to valid NUL-terminated
3327/// strings (or NULL where the C contract allows) for the lifetime
3328/// of the call.
3329///
3330/// The caller must not race this call with concurrent mutation of the
3331/// same objects from other threads (per-object state is not internally
3332/// synchronized). Violating any of the above is undefined behavior.
3333///
3334/// Exercised by the C-API differential courts
3335/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3336/// courts; those pass byte-for-byte against the upstream oracle.
3337#[no_mangle]
3338pub const unsafe extern "C" fn xmlFileMatch(_filename: *const c_char) -> c_int {
3339 1
3340}
3341
3342/// Open a file and return a `FILE *` I/O context (cast to `void *`).
3343///
3344/// # UPSTREAM-PARITY
3345///
3346/// ```c
3347/// void *xmlFileOpen(const char *filename);
3348/// ```
3349///
3350/// # SAFETY
3351///
3352///
3353/// - `filename` must point to valid NUL-terminated
3354/// strings (or NULL where the C contract allows) for the lifetime
3355/// of the call.
3356///
3357/// The caller must not race this call with concurrent mutation of the
3358/// same objects from other threads (per-object state is not internally
3359/// synchronized). Violating any of the above is undefined behavior.
3360///
3361/// Exercised by the C-API differential courts
3362/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3363/// courts; those pass byte-for-byte against the upstream oracle.
3364#[no_mangle]
3365pub unsafe extern "C" fn xmlFileOpen(filename: *const c_char) -> *mut c_void {
3366 if filename.is_null() {
3367 return ptr::null_mut();
3368 }
3369 unsafe { libc::fopen(filename, c"rb".as_ptr() as *const c_char) as *mut c_void }
3370}
3371
3372/// Read up to `len` bytes from a `FILE *` I/O context.
3373///
3374/// # UPSTREAM-PARITY
3375///
3376/// ```c
3377/// int xmlFileRead(void *context, char *buffer, int len);
3378/// ```
3379///
3380/// # SAFETY
3381///
3382/// - `context`, `buffer` must be valid pointers (or NULL
3383/// where the upstream C contract allows), obtained from the
3384/// matching constructor/owner and not yet freed; the callee may
3385/// take or keep ownership exactly as the C API specifies.
3386///
3387/// The caller must not race this call with concurrent mutation of the
3388/// same objects from other threads (per-object state is not internally
3389/// synchronized). Violating any of the above is undefined behavior.
3390///
3391/// Exercised by the C-API differential courts
3392/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3393/// courts; those pass byte-for-byte against the upstream oracle.
3394#[no_mangle]
3395pub unsafe extern "C" fn xmlFileRead(
3396 context: *mut c_void,
3397 buffer: *mut c_char,
3398 len: c_int,
3399) -> c_int {
3400 if context.is_null() || buffer.is_null() || len <= 0 {
3401 return -1;
3402 }
3403 unsafe {
3404 let n = libc::fread(
3405 buffer as *mut c_void,
3406 1,
3407 len as usize,
3408 context as *mut libc::FILE,
3409 );
3410 if n < len as usize && libc::ferror(context as *mut libc::FILE) != 0 {
3411 return -1;
3412 }
3413 n as c_int
3414 }
3415}
3416
3417/// Close a `FILE *` I/O context.
3418///
3419/// # UPSTREAM-PARITY
3420///
3421/// ```c
3422/// int xmlFileClose(void *context);
3423/// ```
3424///
3425/// # SAFETY
3426///
3427/// - `context` must be valid pointers (or NULL
3428/// where the upstream C contract allows), obtained from the
3429/// matching constructor/owner and not yet freed; the callee may
3430/// take or keep ownership exactly as the C API specifies.
3431///
3432/// The caller must not race this call with concurrent mutation of the
3433/// same objects from other threads (per-object state is not internally
3434/// synchronized). Violating any of the above is undefined behavior.
3435///
3436/// Exercised by the C-API differential courts
3437/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3438/// courts; those pass byte-for-byte against the upstream oracle.
3439#[no_mangle]
3440pub unsafe extern "C" fn xmlFileClose(context: *mut c_void) -> c_int {
3441 if context.is_null() {
3442 return -1;
3443 }
3444 unsafe {
3445 let file = context as *mut libc::FILE;
3446 let fd = libc::fileno(file);
3447 if fd == 0 {
3448 // stdin must not be closed.
3449 return 0;
3450 }
3451 if fd == 1 || fd == 2 {
3452 // stdout/stderr are only flushed.
3453 return if libc::fflush(file) == 0 { 0 } else { -1 };
3454 }
3455 libc::fclose(file)
3456 }
3457}
3458
3459// ═══════════════════════════════════════════════════════════════════════════════
3460// Low-level character scanning (parserInternals.c)
3461// ═══════════════════════════════════════════════════════════════════════════════
3462
3463/// Return the current character (UTF-8 decoded, EOL normalised) and its byte
3464/// length in `*len`. Does not advance the input pointer.
3465///
3466/// # UPSTREAM-PARITY
3467///
3468/// ```c
3469/// int xmlCurrentChar(xmlParserCtxtPtr ctxt, int *len);
3470/// ```
3471///
3472/// # SAFETY
3473///
3474/// - `ctxt`, `len` must be valid pointers (or NULL
3475/// where the upstream C contract allows), obtained from the
3476/// matching constructor/owner and not yet freed; the callee may
3477/// take or keep ownership exactly as the C API specifies.
3478///
3479/// The caller must not race this call with concurrent mutation of the
3480/// same objects from other threads (per-object state is not internally
3481/// synchronized). Violating any of the above is undefined behavior.
3482///
3483/// Exercised by the C-API differential courts
3484/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3485/// courts; those pass byte-for-byte against the upstream oracle.
3486#[no_mangle]
3487pub unsafe extern "C" fn xmlCurrentChar(ctxt: *mut _xmlParserCtxt, len: *mut c_int) -> c_int {
3488 if ctxt.is_null() || len.is_null() || (*ctxt).input.is_null() {
3489 return 0;
3490 }
3491 unsafe {
3492 let pi = &*((*ctxt).input);
3493 let cur = pi.cur;
3494 if cur.is_null() {
3495 *len = 0;
3496 return 0;
3497 }
3498 let avail = (pi.end as usize).saturating_sub(cur as usize);
3499 let c = *cur;
3500
3501 if c < 0x80 {
3502 if c == b'\r' {
3503 // EOL normalisation: CR (optionally CRLF) becomes LF.
3504 if avail >= 2 && *cur.add(1) == b'\n' {
3505 (*(*ctxt).input).cur = cur.add(1);
3506 }
3507 *len = 1;
3508 return b'\n' as c_int;
3509 }
3510 if c == 0 {
3511 if avail == 0 {
3512 *len = 0;
3513 } else {
3514 *len = 1;
3515 }
3516 return 0;
3517 }
3518 *len = 1;
3519 return c as c_int;
3520 }
3521
3522 // Multi-byte UTF-8.
3523 if avail < 2 || (*cur.add(1) & 0xc0) != 0x80 {
3524 *len = 1;
3525 return XML_INVALID_CHAR;
3526 }
3527 if c < 0xe0 {
3528 if c < 0xc2 {
3529 *len = 1;
3530 return XML_INVALID_CHAR;
3531 }
3532 let val = (((c & 0x1f) as c_int) << 6) | ((*cur.add(1) & 0x3f) as c_int);
3533 *len = 2;
3534 return val;
3535 }
3536 if avail < 3 || (*cur.add(2) & 0xc0) != 0x80 {
3537 *len = 1;
3538 return XML_INVALID_CHAR;
3539 }
3540 if c < 0xf0 {
3541 let val = (((c & 0x0f) as c_int) << 12)
3542 | (((*cur.add(1) & 0x3f) as c_int) << 6)
3543 | ((*cur.add(2) & 0x3f) as c_int);
3544 if val < 0x800 || (0xd800..0xe000).contains(&val) {
3545 *len = 1;
3546 return XML_INVALID_CHAR;
3547 }
3548 *len = 3;
3549 return val;
3550 }
3551 if avail < 4 || (*cur.add(3) & 0xc0) != 0x80 {
3552 *len = 1;
3553 return XML_INVALID_CHAR;
3554 }
3555 let val = (((c & 0x07) as c_int) << 18)
3556 | (((*cur.add(1) & 0x3f) as c_int) << 12)
3557 | (((*cur.add(2) & 0x3f) as c_int) << 6)
3558 | ((*cur.add(3) & 0x3f) as c_int);
3559 if !(0x10000..0x110000).contains(&val) {
3560 *len = 1;
3561 return XML_INVALID_CHAR;
3562 }
3563 *len = 4;
3564 val
3565 }
3566}
3567
3568/// Advance to the next character, updating line/column accounting.
3569///
3570/// # UPSTREAM-PARITY
3571///
3572/// ```c
3573/// void xmlNextChar(xmlParserCtxtPtr ctxt);
3574/// ```
3575///
3576/// # SAFETY
3577///
3578/// - `ctxt` must be valid pointers (or NULL
3579/// where the upstream C contract allows), obtained from the
3580/// matching constructor/owner and not yet freed; the callee may
3581/// take or keep ownership exactly as the C API specifies.
3582///
3583/// The caller must not race this call with concurrent mutation of the
3584/// same objects from other threads (per-object state is not internally
3585/// synchronized). Violating any of the above is undefined behavior.
3586///
3587/// Exercised by the C-API differential courts
3588/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3589/// courts; those pass byte-for-byte against the upstream oracle.
3590#[no_mangle]
3591pub unsafe extern "C" fn xmlNextChar(ctxt: *mut _xmlParserCtxt) {
3592 if ctxt.is_null() || (*ctxt).input.is_null() {
3593 return;
3594 }
3595 unsafe {
3596 let pi = &mut *((*ctxt).input);
3597 let cur = pi.cur;
3598 if cur.is_null() {
3599 return;
3600 }
3601 let avail = (pi.end as usize).saturating_sub(cur as usize);
3602 if avail == 0 {
3603 return;
3604 }
3605 let c = *cur;
3606
3607 if c < 0x80 {
3608 if c == b'\n' {
3609 pi.cur = cur.add(1);
3610 pi.line += 1;
3611 pi.col = 1;
3612 } else if c == b'\r' {
3613 // CRLF is a single line break.
3614 pi.cur = cur.add(if avail >= 2 && *cur.add(1) == b'\n' {
3615 2
3616 } else {
3617 1
3618 });
3619 pi.line += 1;
3620 pi.col = 1;
3621 } else {
3622 pi.cur = cur.add(1);
3623 pi.col += 1;
3624 }
3625 return;
3626 }
3627
3628 pi.col += 1;
3629
3630 if avail < 2 || (*cur.add(1) & 0xc0) != 0x80 {
3631 pi.cur = cur.add(1);
3632 return;
3633 }
3634 if c < 0xe0 {
3635 if c < 0xc2 {
3636 pi.cur = cur.add(1);
3637 return;
3638 }
3639 pi.cur = cur.add(2);
3640 return;
3641 }
3642 if avail < 3 || (*cur.add(2) & 0xc0) != 0x80 {
3643 pi.cur = cur.add(1);
3644 return;
3645 }
3646 if c < 0xf0 {
3647 let val = (((c as c_int) << 8) as u32) | (*cur.add(1) as u32);
3648 if (val < 0xe0a0) || (0xeda0..0xee00).contains(&val) {
3649 pi.cur = cur.add(1);
3650 return;
3651 }
3652 pi.cur = cur.add(3);
3653 return;
3654 }
3655 if avail < 4 || (*cur.add(3) & 0xc0) != 0x80 {
3656 pi.cur = cur.add(1);
3657 return;
3658 }
3659 let val = (((c as c_int) << 8) as u32) | (*cur.add(1) as u32);
3660 if !(0xf090..0xf490).contains(&val) {
3661 pi.cur = cur.add(1);
3662 return;
3663 }
3664 pi.cur = cur.add(4);
3665 }
3666}
3667
3668/// Skip blank characters (space, tab, LF, CR), updating line/column.
3669/// Returns the number of blanks skipped.
3670///
3671/// # UPSTREAM-PARITY
3672///
3673/// ```c
3674/// int xmlSkipBlankChars(xmlParserCtxtPtr ctxt);
3675/// ```
3676///
3677/// # SAFETY
3678///
3679/// - `ctxt` must be valid pointers (or NULL
3680/// where the upstream C contract allows), obtained from the
3681/// matching constructor/owner and not yet freed; the callee may
3682/// take or keep ownership exactly as the C API specifies.
3683///
3684/// The caller must not race this call with concurrent mutation of the
3685/// same objects from other threads (per-object state is not internally
3686/// synchronized). Violating any of the above is undefined behavior.
3687///
3688/// Exercised by the C-API differential courts
3689/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3690/// courts; those pass byte-for-byte against the upstream oracle.
3691#[no_mangle]
3692pub unsafe extern "C" fn xmlSkipBlankChars(ctxt: *mut _xmlParserCtxt) -> c_int {
3693 if ctxt.is_null() || (*ctxt).input.is_null() {
3694 return 0;
3695 }
3696 unsafe {
3697 let pi = &mut *((*ctxt).input);
3698 let mut cur = pi.cur;
3699 if cur.is_null() {
3700 return 0;
3701 }
3702 let end = pi.end;
3703 let mut res = 0;
3704 while cur < end && (*cur == 0x20 || *cur == 0x09 || *cur == 0x0a || *cur == 0x0d) {
3705 if *cur == b'\n' {
3706 pi.line += 1;
3707 pi.col = 1;
3708 } else {
3709 pi.col += 1;
3710 }
3711 cur = cur.add(1);
3712 res += 1;
3713 }
3714 pi.cur = cur;
3715 res
3716 }
3717}
3718
3719/// XML 1.0 5th-edition NameStartChar predicate (upstream `xmlIsNameStartCharNew`).
3720const fn is_name_start_char_new(c: c_int) -> bool {
3721 if c == b' ' as c_int || c == b'>' as c_int || c == b'/' as c_int {
3722 return false;
3723 }
3724 (c >= b'a' as c_int && c <= b'z' as c_int)
3725 || (c >= b'A' as c_int && c <= b'Z' as c_int)
3726 || c == b'_' as c_int
3727 || c == b':' as c_int
3728 || (c >= 0xC0 && c <= 0xD6)
3729 || (c >= 0xD8 && c <= 0xF6)
3730 || (c >= 0xF8 && c <= 0x2FF)
3731 || (c >= 0x370 && c <= 0x37D)
3732 || (c >= 0x37F && c <= 0x1FFF)
3733 || (c >= 0x200C && c <= 0x200D)
3734 || (c >= 0x2070 && c <= 0x218F)
3735 || (c >= 0x2C00 && c <= 0x2FEF)
3736 || (c >= 0x3001 && c <= 0xD7FF)
3737 || (c >= 0xF900 && c <= 0xFDCF)
3738 || (c >= 0xFDF0 && c <= 0xFFFD)
3739 || (c >= 0x10000 && c <= 0xEFFFF)
3740}
3741
3742/// XML 1.0 5th-edition NameChar predicate (upstream `xmlIsNameCharNew`).
3743const fn is_name_char_new(c: c_int) -> bool {
3744 if c == b' ' as c_int || c == b'>' as c_int || c == b'/' as c_int {
3745 return false;
3746 }
3747 (c >= b'a' as c_int && c <= b'z' as c_int)
3748 || (c >= b'A' as c_int && c <= b'Z' as c_int)
3749 || (c >= b'0' as c_int && c <= b'9' as c_int)
3750 || c == b'_' as c_int
3751 || c == b':' as c_int
3752 || c == b'-' as c_int
3753 || c == b'.' as c_int
3754 || c == 0xB7
3755 || (c >= 0xC0 && c <= 0xD6)
3756 || (c >= 0xD8 && c <= 0xF6)
3757 || (c >= 0xF8 && c <= 0x2FF)
3758 || (c >= 0x300 && c <= 0x36F)
3759 || (c >= 0x370 && c <= 0x37D)
3760 || (c >= 0x37F && c <= 0x1FFF)
3761 || (c >= 0x200C && c <= 0x200D)
3762 || (c >= 0x203F && c <= 0x2040)
3763 || (c >= 0x2070 && c <= 0x218F)
3764 || (c >= 0x2C00 && c <= 0x2FEF)
3765 || (c >= 0x3001 && c <= 0xD7FF)
3766 || (c >= 0xF900 && c <= 0xFDCF)
3767 || (c >= 0xFDF0 && c <= 0xFFFD)
3768 || (c >= 0x10000 && c <= 0xEFFFF)
3769}
3770
3771/// Scan an XML Name (or NCName/Nmtoken) at `ctxt->input->cur`, advancing the
3772/// input pointer. Returns a pointer to the end of the name, or NULL when the
3773/// name exceeds `max` bytes.
3774///
3775/// # UPSTREAM-PARITY
3776///
3777/// ```c
3778/// const xmlChar *xmlScanName(xmlParserCtxtPtr ctxt, int max, int flags);
3779/// ```
3780///
3781/// # SAFETY
3782///
3783/// - `ctxt` must be valid pointers (or NULL
3784/// where the upstream C contract allows), obtained from the
3785/// matching constructor/owner and not yet freed; the callee may
3786/// take or keep ownership exactly as the C API specifies.
3787///
3788/// The caller must not race this call with concurrent mutation of the
3789/// same objects from other threads (per-object state is not internally
3790/// synchronized). Violating any of the above is undefined behavior.
3791///
3792/// Exercised by the C-API differential courts
3793/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3794/// courts; those pass byte-for-byte against the upstream oracle.
3795#[no_mangle]
3796pub unsafe extern "C" fn xmlScanName(
3797 ctxt: *mut _xmlParserCtxt,
3798 max: c_int,
3799 flags: c_int,
3800) -> *const xmlChar {
3801 if ctxt.is_null() || (*ctxt).input.is_null() || max <= 0 {
3802 return ptr::null();
3803 }
3804 unsafe {
3805 let pi = &mut *((*ctxt).input);
3806 let mut ptr = pi.cur;
3807 if ptr.is_null() {
3808 return ptr::null();
3809 }
3810 let end = pi.end;
3811 let mut remaining = max as usize;
3812 let stop: u8 = if flags & XML_SCAN_NC != 0 { b':' } else { 0 };
3813 let old10 = flags & XML_SCAN_OLD10 != 0;
3814 let mut f = flags;
3815
3816 loop {
3817 if ptr >= end {
3818 break;
3819 }
3820 let c = *ptr;
3821 let (cp, len) = if c < 0x80 {
3822 if stop != 0 && c == stop {
3823 break;
3824 }
3825 (c as c_int, 1usize)
3826 } else {
3827 // Decode a multi-byte UTF-8 character.
3828 let avail = (end as usize).saturating_sub(ptr as usize);
3829 let mut l = 4usize;
3830 let cp = decode_utf8_char(ptr, avail, &mut l);
3831 if cp < 0 {
3832 break;
3833 }
3834 (cp, l)
3835 };
3836
3837 let ok = if f & XML_SCAN_NMTOKEN != 0 {
3838 if old10 {
3839 is_name_char_old10(cp)
3840 } else {
3841 is_name_char_new(cp)
3842 }
3843 } else if old10 {
3844 is_name_start_char_old10(cp)
3845 } else {
3846 is_name_start_char_new(cp)
3847 };
3848 if !ok {
3849 break;
3850 }
3851 if len > remaining {
3852 return ptr::null();
3853 }
3854 ptr = ptr.add(len);
3855 remaining -= len;
3856 f |= XML_SCAN_NMTOKEN;
3857 }
3858
3859 pi.cur = ptr;
3860 ptr
3861 }
3862}
3863
3864/// Decode a UTF-8 character at `ptr` with `avail` bytes available; returns the
3865/// codepoint (or -1 on invalid/truncated input) and sets `*len` to its length.
3866const unsafe fn decode_utf8_char(ptr: *const u8, avail: usize, len: &mut usize) -> c_int {
3867 unsafe {
3868 let c = *ptr;
3869 if avail < 2 || (*ptr.add(1) & 0xc0) != 0x80 {
3870 return -1;
3871 }
3872 if c < 0xe0 {
3873 if c < 0xc2 {
3874 return -1;
3875 }
3876 *len = 2;
3877 return (((c & 0x1f) as c_int) << 6) | ((*ptr.add(1) & 0x3f) as c_int);
3878 }
3879 if avail < 3 || (*ptr.add(2) & 0xc0) != 0x80 {
3880 return -1;
3881 }
3882 if c < 0xf0 {
3883 let val = (((c & 0x0f) as c_int) << 12)
3884 | (((*ptr.add(1) & 0x3f) as c_int) << 6)
3885 | ((*ptr.add(2) & 0x3f) as c_int);
3886 if val < 0x800 || (val >= 0xd800 && val < 0xe000) {
3887 return -1;
3888 }
3889 *len = 3;
3890 return val;
3891 }
3892 if avail < 4 || (*ptr.add(3) & 0xc0) != 0x80 {
3893 return -1;
3894 }
3895 let val = (((c & 0x07) as c_int) << 18)
3896 | (((*ptr.add(1) & 0x3f) as c_int) << 12)
3897 | (((*ptr.add(2) & 0x3f) as c_int) << 6)
3898 | ((*ptr.add(3) & 0x3f) as c_int);
3899 if val < 0x10000 || val >= 0x110000 {
3900 return -1;
3901 }
3902 *len = 4;
3903 val
3904 }
3905}
3906
3907/// XML 1.0 (pre-revision-5) NameStartChar predicate: Letter, '_' or ':'.
3908const fn is_name_start_char_old10(c: c_int) -> bool {
3909 (c >= b'a' as c_int && c <= b'z' as c_int)
3910 || (c >= b'A' as c_int && c <= b'Z' as c_int)
3911 || c == b'_' as c_int
3912 || c == b':' as c_int
3913 || (c >= 0xC0 && c <= 0xD6)
3914 || (c >= 0xD8 && c <= 0xF6)
3915 || (c >= 0xF8 && c <= 0x2FF)
3916 || (c >= 0x370 && c <= 0x37D)
3917 || (c >= 0x37F && c <= 0x1FFF)
3918 || (c >= 0x200C && c <= 0x200D)
3919 || (c >= 0x2070 && c <= 0x218F)
3920 || (c >= 0x2C00 && c <= 0x2FEF)
3921 || (c >= 0x3001 && c <= 0xD7FF)
3922 || (c >= 0xF900 && c <= 0xFDCF)
3923 || (c >= 0xFDF0 && c <= 0xFFFD)
3924 || (c >= 0x10000 && c <= 0xEFFFF)
3925}
3926
3927/// XML 1.0 (pre-revision-5) NameChar predicate: NameStartChar, digits, '.',
3928/// '-', combining chars and extenders.
3929const fn is_name_char_old10(c: c_int) -> bool {
3930 is_name_start_char_old10(c)
3931 || (c >= b'0' as c_int && c <= b'9' as c_int)
3932 || c == b'.' as c_int
3933 || c == b'-' as c_int
3934 || c == 0xB7
3935 || (c >= 0x300 && c <= 0x36F)
3936 || c == 0x02D0
3937 || c == 0x02D1
3938 || c == 0x0387
3939 || c == 0x0640
3940 || c == 0x0E46
3941 || c == 0x0EC6
3942 || c == 0x3005
3943 || (c >= 0x3031 && c <= 0x3035)
3944 || (c >= 0x309D && c <= 0x309E)
3945 || (c >= 0x30FC && c <= 0x30FE)
3946}
3947
3948/// Decode entities from the current input position: char references and
3949/// (predefined and DTD-declared) entity references are substituted. Stops at
3950/// the first of `end`/`end2`/`end3`, or after `len` bytes (`len < 0` = rest).
3951///
3952/// # UPSTREAM-PARITY
3953///
3954/// ```c
3955/// xmlChar *xmlDecodeEntities(xmlParserCtxtPtr ctxt, int len, xmlChar end,
3956/// xmlChar end2, xmlChar end3);
3957/// ```
3958///
3959/// # SAFETY
3960///
3961/// - `ctxt` must be valid pointers (or NULL
3962/// where the upstream C contract allows), obtained from the
3963/// matching constructor/owner and not yet freed; the callee may
3964/// take or keep ownership exactly as the C API specifies.
3965///
3966/// The caller must not race this call with concurrent mutation of the
3967/// same objects from other threads (per-object state is not internally
3968/// synchronized). Violating any of the above is undefined behavior.
3969///
3970/// Exercised by the C-API differential courts
3971/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
3972/// courts; those pass byte-for-byte against the upstream oracle.
3973#[no_mangle]
3974pub unsafe extern "C" fn xmlDecodeEntities(
3975 ctxt: *mut _xmlParserCtxt,
3976 len: c_int,
3977 end: xmlChar,
3978 end2: xmlChar,
3979 end3: xmlChar,
3980) -> *mut xmlChar {
3981 if ctxt.is_null() || (*ctxt).input.is_null() {
3982 return ptr::null_mut();
3983 }
3984 unsafe {
3985 let pi = &*((*ctxt).input);
3986 let cur = pi.cur;
3987 if cur.is_null() {
3988 return ptr::null_mut();
3989 }
3990 let avail = (pi.end as usize).saturating_sub(cur as usize);
3991 let n = if len < 0 {
3992 avail
3993 } else {
3994 (len as usize).min(avail)
3995 };
3996
3997 let mut out: Vec<u8> = Vec::new();
3998 let mut i = 0usize;
3999
4000 while i < n {
4001 let c = *cur.add(i);
4002 if c == end || c == end2 || c == end3 {
4003 break;
4004 }
4005 if c != b'&' {
4006 out.push(c);
4007 i += 1;
4008 continue;
4009 }
4010
4011 // Character reference: &#...; or &#x...;
4012 if i + 1 < n && *cur.add(i + 1) == b'#' {
4013 let (value, consumed) = parse_char_ref(cur.add(i), n - i);
4014 if consumed == 0 {
4015 out.push(b'&');
4016 i += 1;
4017 continue;
4018 }
4019 let mut buf = [0u8; 4];
4020 let blen = copy_char_utf8(&mut buf, value);
4021 out.extend_from_slice(&buf[..blen]);
4022 i += consumed;
4023 continue;
4024 }
4025
4026 // Entity reference: &name;
4027 let mut j = i + 1;
4028 while j < n
4029 && ((*cur.add(j)).is_ascii_alphanumeric()
4030 || *cur.add(j) == b'_'
4031 || *cur.add(j) == b'-'
4032 || *cur.add(j) == b'.'
4033 || *cur.add(j) == b':')
4034 {
4035 j += 1;
4036 }
4037 if j < n && *cur.add(j) == b';' {
4038 let name = core::slice::from_raw_parts(cur.add(i + 1), j - i - 1);
4039 let mut replaced = false;
4040 // Predefined entities.
4041 let content: Option<&[u8]> = match name {
4042 b"amp" => Some(b"&"),
4043 b"lt" => Some(b"<"),
4044 b"gt" => Some(b">"),
4045 b"quot" => Some(b"\""),
4046 b"apos" => Some(b"'"),
4047 _ => None,
4048 };
4049 if let Some(c) = content {
4050 out.extend_from_slice(c);
4051 replaced = true;
4052 } else {
4053 // DTD-declared entity.
4054 let mut name_nul = name.to_vec();
4055 name_nul.push(0);
4056 let ent = entities::get_entity((*ctxt).myDoc, name_nul.as_ptr());
4057 if !ent.is_null() && !(*ent).content.is_null() {
4058 let clen = string::xml_strlen((*ent).content);
4059 out.extend_from_slice(core::slice::from_raw_parts((*ent).content, clen));
4060 replaced = true;
4061 }
4062 }
4063 if replaced {
4064 i = j + 1;
4065 continue;
4066 }
4067 }
4068 out.push(b'&');
4069 i += 1;
4070 }
4071
4072 out.push(0);
4073 let result = xmlMallocImpl(out.len()) as *mut xmlChar;
4074 if result.is_null() {
4075 return ptr::null_mut();
4076 }
4077 ptr::copy_nonoverlapping(out.as_ptr(), result, out.len());
4078 result
4079 }
4080}
4081
4082/// Parse a numeric character reference at `ptr` (starting at '&#'); returns
4083/// the value and total bytes consumed, or (0, 0) when malformed.
4084const unsafe fn parse_char_ref(ptr: *const u8, avail: usize) -> (c_int, usize) {
4085 unsafe {
4086 if avail < 3 || *ptr != b'&' || *ptr.add(1) != b'#' {
4087 return (0, 0);
4088 }
4089 let mut i = 2usize;
4090 let hex = i < avail && (*ptr.add(i) == b'x' || *ptr.add(i) == b'X');
4091 if hex {
4092 i += 1;
4093 }
4094 let start = i;
4095 let mut value: u32 = 0;
4096 while i < avail && *ptr.add(i) != b';' {
4097 let d = (*ptr.add(i) as char).to_digit(if hex { 16 } else { 10 });
4098 match d {
4099 Some(d) => {
4100 value = value
4101 .saturating_mul(if hex { 16 } else { 10 })
4102 .saturating_add(d);
4103 i += 1;
4104 }
4105 None => return (0, 0),
4106 }
4107 }
4108 if i == start || i >= avail || *ptr.add(i) != b';' {
4109 return (0, 0);
4110 }
4111 (value as c_int, i + 1)
4112 }
4113}
4114
4115/// Encode a codepoint into a UTF-8 byte sequence; returns the byte count.
4116const fn copy_char_utf8(out: &mut [u8; 4], val: c_int) -> usize {
4117 if val < 0x80 {
4118 out[0] = val as u8;
4119 1
4120 } else if val < 0x800 {
4121 out[0] = 0xC0 | ((val >> 6) as u8);
4122 out[1] = 0x80 | ((val & 0x3F) as u8);
4123 2
4124 } else if val < 0x10000 {
4125 out[0] = 0xE0 | ((val >> 12) as u8);
4126 out[1] = 0x80 | (((val >> 6) & 0x3F) as u8);
4127 out[2] = 0x80 | ((val & 0x3F) as u8);
4128 3
4129 } else if val < 0x110000 {
4130 out[0] = 0xF0 | ((val >> 18) as u8);
4131 out[1] = 0x80 | (((val >> 12) & 0x3F) as u8);
4132 out[2] = 0x80 | (((val >> 6) & 0x3F) as u8);
4133 out[3] = 0x80 | ((val & 0x3F) as u8);
4134 4
4135 } else {
4136 out[0] = 0;
4137 1
4138 }
4139}
4140
4141/// Detect the character encoding of a buffer from its initial bytes.
4142///
4143/// # UPSTREAM-PARITY
4144///
4145/// ```c
4146/// xmlCharEncoding xmlDetectCharEncoding(const unsigned char *in, int len);
4147/// ```
4148///
4149/// # SAFETY
4150///
4151/// - `in_` must be valid pointers (or NULL
4152/// where the upstream C contract allows), obtained from the
4153/// matching constructor/owner and not yet freed; the callee may
4154/// take or keep ownership exactly as the C API specifies.
4155///
4156/// The caller must not race this call with concurrent mutation of the
4157/// same objects from other threads (per-object state is not internally
4158/// synchronized). Violating any of the above is undefined behavior.
4159///
4160/// Exercised by the C-API differential courts
4161/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4162/// courts; those pass byte-for-byte against the upstream oracle.
4163#[no_mangle]
4164pub const unsafe extern "C" fn xmlDetectCharEncoding(in_: *const c_uchar, len: c_int) -> c_int {
4165 if in_.is_null() {
4166 return xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int;
4167 }
4168 unsafe {
4169 if len >= 4 {
4170 if *in_ == 0x00 && *in_.add(1) == 0x00 && *in_.add(2) == 0x00 && *in_.add(3) == 0x3C {
4171 return xmlCharEncoding::XML_CHAR_ENCODING_UCS4BE as c_int;
4172 }
4173 if *in_ == 0x3C && *in_.add(1) == 0x00 && *in_.add(2) == 0x00 && *in_.add(3) == 0x00 {
4174 return xmlCharEncoding::XML_CHAR_ENCODING_UCS4LE as c_int;
4175 }
4176 if *in_ == 0x4C && *in_.add(1) == 0x6F && *in_.add(2) == 0xA7 && *in_.add(3) == 0x94 {
4177 return xmlCharEncoding::XML_CHAR_ENCODING_EBCDIC as c_int;
4178 }
4179 if *in_ == 0x3C && *in_.add(1) == 0x3F && *in_.add(2) == 0x78 && *in_.add(3) == 0x6D {
4180 return xmlCharEncoding::XML_CHAR_ENCODING_UTF8 as c_int;
4181 }
4182 if *in_ == 0x3C && *in_.add(1) == 0x00 && *in_.add(2) == 0x3F && *in_.add(3) == 0x00 {
4183 return xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE as c_int;
4184 }
4185 if *in_ == 0x00 && *in_.add(1) == 0x3C && *in_.add(2) == 0x00 && *in_.add(3) == 0x3F {
4186 return xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE as c_int;
4187 }
4188 }
4189 if len >= 3 && *in_ == 0xEF && *in_.add(1) == 0xBB && *in_.add(2) == 0xBF {
4190 return xmlCharEncoding::XML_CHAR_ENCODING_UTF8 as c_int;
4191 }
4192 if len >= 2 {
4193 if *in_ == 0xFE && *in_.add(1) == 0xFF {
4194 return xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE as c_int;
4195 }
4196 if *in_ == 0xFF && *in_.add(1) == 0xFE {
4197 return xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE as c_int;
4198 }
4199 }
4200 }
4201 xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int
4202}
4203
4204/// Convert the first line of `in` using the encoding handler, appending the
4205/// result to `out`.
4206///
4207/// # UPSTREAM-PARITY
4208///
4209/// ```c
4210/// int xmlCharEncFirstLine(xmlCharEncodingHandlerPtr handler,
4211/// struct _xmlBuffer *out, struct _xmlBuffer *in);
4212/// ```
4213///
4214/// # SAFETY
4215///
4216/// - `handler`, `out`, `in_` must be valid pointers (or NULL
4217/// where the upstream C contract allows), obtained from the
4218/// matching constructor/owner and not yet freed; the callee may
4219/// take or keep ownership exactly as the C API specifies.
4220///
4221/// The caller must not race this call with concurrent mutation of the
4222/// same objects from other threads (per-object state is not internally
4223/// synchronized). Violating any of the above is undefined behavior.
4224///
4225/// Exercised by the C-API differential courts
4226/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4227/// courts; those pass byte-for-byte against the upstream oracle.
4228#[no_mangle]
4229pub unsafe extern "C" fn xmlCharEncFirstLine(
4230 handler: *mut _xmlCharEncodingHandler,
4231 out: *mut _xmlBuffer,
4232 in_: *mut _xmlBuffer,
4233) -> c_int {
4234 encoding::xmlCharEncInFunc(handler, out, in_)
4235}
4236
4237/// Check whether the current thread is the main thread.
4238///
4239/// # UPSTREAM-PARITY
4240///
4241/// ```c
4242/// int xmlIsMainThread(void);
4243/// ```
4244///
4245/// # SAFETY
4246///
4247/// The function touches crate-global state only; it is safe
4248/// as long as the caller respects the library's global
4249/// initialization/cleanup ordering (xmlInitParser before use,
4250/// xmlCleanupParser only after all users are done).
4251///
4252/// Violating the global lifecycle ordering, or calling this after
4253/// teardown or from a signal handler, is undefined behavior.
4254#[no_mangle]
4255pub const unsafe extern "C" fn xmlIsMainThread() -> c_int {
4256 1
4257}
4258
4259// ═══════════════════════════════════════════════════════════════════════════════
4260// Error reporting helpers (xmlerror.h)
4261// ═══════════════════════════════════════════════════════════════════════════════
4262
4263/// Print file and line information for a parser input to the generic error
4264/// channel.
4265///
4266/// # UPSTREAM-PARITY
4267///
4268/// ```c
4269/// void xmlParserPrintFileInfo(struct _xmlParserInput *input);
4270/// ```
4271///
4272/// # SAFETY
4273///
4274/// - `input` must be valid pointers (or NULL
4275/// where the upstream C contract allows), obtained from the
4276/// matching constructor/owner and not yet freed; the callee may
4277/// take or keep ownership exactly as the C API specifies.
4278///
4279/// The caller must not race this call with concurrent mutation of the
4280/// same objects from other threads (per-object state is not internally
4281/// synchronized). Violating any of the above is undefined behavior.
4282///
4283/// Exercised by the C-API differential courts
4284/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4285/// courts; those pass byte-for-byte against the upstream oracle.
4286#[no_mangle]
4287pub unsafe extern "C" fn xmlParserPrintFileInfo(input: *mut _xmlParserInput) {
4288 if input.is_null() {
4289 return;
4290 }
4291 unsafe {
4292 let channel: Option<xmlGenericErrorFunc> = globals::get_generic_error_func();
4293 let data = globals::get_generic_error_ctx();
4294 let Some(ch) = channel else { return };
4295
4296 let msg = if !(*input).filename.is_null() {
4297 let file = CStr::from_ptr((*input).filename);
4298 let s = format!("{}:{}: ", file.to_string_lossy(), (*input).line);
4299 std::ffi::CString::new(s).unwrap_or_default()
4300 } else {
4301 let s = format!("Entity: line {}: ", (*input).line);
4302 std::ffi::CString::new(s).unwrap_or_default()
4303 };
4304 ch(data, msg.as_ptr());
4305 }
4306}
4307
4308/// Print the input context around the current error position to the generic
4309/// error channel.
4310///
4311/// # UPSTREAM-PARITY
4312///
4313/// ```c
4314/// void xmlParserPrintFileContext(struct _xmlParserInput *input);
4315/// ```
4316///
4317/// # SAFETY
4318///
4319/// - `input` must be valid pointers (or NULL
4320/// where the upstream C contract allows), obtained from the
4321/// matching constructor/owner and not yet freed; the callee may
4322/// take or keep ownership exactly as the C API specifies.
4323///
4324/// The caller must not race this call with concurrent mutation of the
4325/// same objects from other threads (per-object state is not internally
4326/// synchronized). Violating any of the above is undefined behavior.
4327///
4328/// Exercised by the C-API differential courts
4329/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4330/// courts; those pass byte-for-byte against the upstream oracle.
4331#[no_mangle]
4332pub unsafe extern "C" fn xmlParserPrintFileContext(input: *mut _xmlParserInput) {
4333 if input.is_null() || (*input).cur.is_null() {
4334 return;
4335 }
4336 unsafe {
4337 let channel: Option<xmlGenericErrorFunc> = globals::get_generic_error_func();
4338 let data = globals::get_generic_error_ctx();
4339 let Some(ch) = channel else { return };
4340
4341 let pi = &*input;
4342 let cur = pi.cur;
4343 let base = pi.base;
4344 let end = pi.end;
4345
4346 // Build a window of up to 80 bytes ending at `cur`.
4347 let before = if base.is_null() {
4348 0
4349 } else {
4350 (cur as usize).saturating_sub(base as usize)
4351 };
4352 let take = before.min(LINE_LEN);
4353 let start = cur.sub(take);
4354 let n = (end as usize).saturating_sub(start as usize).min(LINE_LEN);
4355
4356 let mut content = vec![0u8; n];
4357 if n > 0 {
4358 ptr::copy_nonoverlapping(start, content.as_mut_ptr(), n);
4359 }
4360 let line = std::ffi::CString::new(content.clone()).unwrap_or_default();
4361 ch(data, line.as_ptr());
4362
4363 // Caret line pointing at the current character.
4364 let mut caret = vec![b' '; take];
4365 if take < LINE_LEN + 1 {
4366 caret.push(b'^');
4367 }
4368 let caret_c = std::ffi::CString::new(caret).unwrap_or_default();
4369 ch(data, caret_c.as_ptr());
4370 }
4371}
4372
4373// ═══════════════════════════════════════════════════════════════════════════════
4374// SAX/DTD parse front-ends
4375// ═══════════════════════════════════════════════════════════════════════════════
4376
4377/// Handle an entity reference by pushing the entity's content as a new input
4378/// stream (deprecated internal API).
4379///
4380/// # UPSTREAM-PARITY
4381///
4382/// ```c
4383/// void xmlHandleEntity(xmlParserCtxtPtr ctxt, xmlEntityPtr entity);
4384/// ```
4385///
4386/// # SAFETY
4387///
4388/// - `ctxt`, `entity` must be valid pointers (or NULL
4389/// where the upstream C contract allows), obtained from the
4390/// matching constructor/owner and not yet freed; the callee may
4391/// take or keep ownership exactly as the C API specifies.
4392///
4393/// The caller must not race this call with concurrent mutation of the
4394/// same objects from other threads (per-object state is not internally
4395/// synchronized). Violating any of the above is undefined behavior.
4396///
4397/// Exercised by the C-API differential courts
4398/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4399/// courts; those pass byte-for-byte against the upstream oracle.
4400#[no_mangle]
4401pub unsafe extern "C" fn xmlHandleEntity(ctxt: *mut _xmlParserCtxt, entity: *mut c_void) {
4402 if ctxt.is_null() {
4403 return;
4404 }
4405 unsafe {
4406 let ent = entity as *mut _xmlEntity;
4407 if ent.is_null() {
4408 return;
4409 }
4410 // Unparsed entities cannot be included by reference.
4411 if (*ent).etype == xmlEntityType::XML_EXTERNAL_GENERAL_UNPARSED_ENTITY as c_int {
4412 return;
4413 }
4414
4415 let mut input = ptr::null_mut();
4416 if !(*ent).content.is_null() {
4417 // Internal entity: push its replacement text as a new stream.
4418 let content = (*ent).content;
4419 let pi = xmlNewInputStream(ctxt);
4420 if pi.is_null() {
4421 return;
4422 }
4423 let len = string::xml_strlen(content);
4424 (*pi).base = content;
4425 (*pi).cur = content;
4426 (*pi).end = content.add(len);
4427 (*pi).length = len as c_int;
4428 (*pi).entity = ent;
4429 input = pi;
4430 } else if !(*ent).URI.is_null() {
4431 // External parsed entity: load it through the entity loader.
4432 input = xmlLoadExternalEntity(
4433 (*ent).URI as *const c_char,
4434 (*ent).ExternalID as *const c_char,
4435 ctxt,
4436 );
4437 if !input.is_null() {
4438 (*input).entity = ent;
4439 }
4440 }
4441
4442 if input.is_null() {
4443 return;
4444 }
4445 xmlPushInput(ctxt, input);
4446 }
4447}
4448
4449/// Load and parse a DTD, returning the resulting `xmlDtd` (detached from any
4450/// document).
4451///
4452/// # UPSTREAM-PARITY
4453///
4454/// ```c
4455/// xmlDtdPtr xmlSAXParseDTD(xmlSAXHandlerPtr sax, const xmlChar *publicId,
4456/// const xmlChar *systemId);
4457/// ```
4458///
4459/// # SAFETY
4460///
4461/// - `sax` must be valid pointers (or NULL
4462/// where the upstream C contract allows), obtained from the
4463/// matching constructor/owner and not yet freed; the callee may
4464/// take or keep ownership exactly as the C API specifies.
4465///
4466/// - `publicId`, `systemId` must point to valid NUL-terminated
4467/// strings (or NULL where the C contract allows) for the lifetime
4468/// of the call.
4469///
4470/// The caller must not race this call with concurrent mutation of the
4471/// same objects from other threads (per-object state is not internally
4472/// synchronized). Violating any of the above is undefined behavior.
4473///
4474/// Exercised by the C-API differential courts
4475/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4476/// courts; those pass byte-for-byte against the upstream oracle.
4477#[no_mangle]
4478pub unsafe extern "C" fn xmlSAXParseDTD(
4479 sax: *mut _xmlSAXHandler,
4480 publicId: *const xmlChar,
4481 systemId: *const xmlChar,
4482) -> *mut _xmlDtd {
4483 if publicId.is_null() && systemId.is_null() {
4484 return ptr::null_mut();
4485 }
4486 unsafe {
4487 let ctxt = xmlNewSAXParserCtxt(sax, ptr::null_mut());
4488 if ctxt.is_null() {
4489 return ptr::null_mut();
4490 }
4491 apply_options(ctxt, XML_PARSE_DTDLOAD);
4492
4493 // Resolve via the SAX resolveEntity callback when available, else
4494 // load the system ID directly.
4495 let mut input = ptr::null_mut();
4496 if !sax.is_null() {
4497 if let Some(resolve) = (*sax).resolveEntity {
4498 input = resolve((*ctxt).userData, publicId, systemId);
4499 }
4500 }
4501 if input.is_null() {
4502 if systemId.is_null() {
4503 helpers::free_parser_ctxt(ctxt);
4504 return ptr::null_mut();
4505 }
4506 input = xmlLoadExternalEntity(systemId as *const c_char, ptr::null(), ctxt);
4507 }
4508 if input.is_null() {
4509 helpers::free_parser_ctxt(ctxt);
4510 return ptr::null_mut();
4511 }
4512
4513 // Materialise the DTD text before freeing the input struct.
4514 let data: Vec<u8> = {
4515 let pi = &*input;
4516 if !pi.base.is_null() && !pi.end.is_null() && pi.end >= pi.base {
4517 let len = (pi.end as usize).saturating_sub(pi.base as usize);
4518 core::slice::from_raw_parts(pi.base, len).to_vec()
4519 } else if !pi.buf.is_null() {
4520 input_buffer_data(pi.buf)
4521 } else {
4522 Vec::new()
4523 }
4524 };
4525 helpers::free_parser_input(input);
4526
4527 let dtd = parse_dtd_text(ctxt, &data, publicId, systemId);
4528 helpers::free_parser_ctxt(ctxt);
4529 dtd
4530 }
4531}
4532
4533/// Load and parse a DTD from an input buffer.
4534///
4535/// # UPSTREAM-PARITY
4536///
4537/// ```c
4538/// xmlDtdPtr xmlIOParseDTD(xmlSAXHandlerPtr sax, xmlParserInputBufferPtr input,
4539/// xmlCharEncoding enc);
4540/// ```
4541///
4542/// # SAFETY
4543///
4544/// - `sax`, `input` must be valid pointers (or NULL
4545/// where the upstream C contract allows), obtained from the
4546/// matching constructor/owner and not yet freed; the callee may
4547/// take or keep ownership exactly as the C API specifies.
4548///
4549/// The caller must not race this call with concurrent mutation of the
4550/// same objects from other threads (per-object state is not internally
4551/// synchronized). Violating any of the above is undefined behavior.
4552///
4553/// Exercised by the C-API differential courts
4554/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4555/// courts; those pass byte-for-byte against the upstream oracle.
4556#[no_mangle]
4557pub unsafe extern "C" fn xmlIOParseDTD(
4558 sax: *mut _xmlSAXHandler,
4559 input: *mut _xmlParserInputBuffer,
4560 enc: c_int,
4561) -> *mut _xmlDtd {
4562 if input.is_null() {
4563 return ptr::null_mut();
4564 }
4565 unsafe {
4566 let ctxt = xmlNewSAXParserCtxt(sax, ptr::null_mut());
4567 if ctxt.is_null() {
4568 io::input_buffer_free(input);
4569 return ptr::null_mut();
4570 }
4571 apply_options(ctxt, XML_PARSE_DTDLOAD);
4572 if enc != xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int {
4573 (*ctxt).charset = enc;
4574 }
4575
4576 // Materialise the data from the input buffer.
4577 let data: Vec<u8> = input_buffer_data(input);
4578 io::input_buffer_free(input);
4579
4580 let dtd = parse_dtd_text(ctxt, &data, ptr::null(), ptr::null());
4581 helpers::free_parser_ctxt(ctxt);
4582 dtd
4583 }
4584}
4585
4586/// Extract the buffered data of an input buffer as an owned byte vector.
4587unsafe fn input_buffer_data(buf: *mut _xmlParserInputBuffer) -> Vec<u8> {
4588 unsafe {
4589 if buf.is_null() {
4590 return Vec::new();
4591 }
4592 let b = &*buf;
4593 if let Some(read) = b.readcallback {
4594 let mut out = Vec::new();
4595 let mut tmp = [0u8; 4096];
4596 loop {
4597 let n = read(
4598 b.context,
4599 tmp.as_mut_ptr() as *mut c_char,
4600 tmp.len() as c_int,
4601 );
4602 if n <= 0 {
4603 break;
4604 }
4605 out.extend_from_slice(&tmp[..n as usize]);
4606 }
4607 return out;
4608 }
4609 if !b.buffer.is_null() {
4610 let xbuf = &*(b.buffer as *mut _xmlBuffer);
4611 if !xbuf.content.is_null() && xbuf.use_ > 0 {
4612 return core::slice::from_raw_parts(xbuf.content, xbuf.use_ as usize).to_vec();
4613 }
4614 }
4615 Vec::new()
4616 }
4617}
4618
4619/// Parse an external general entity and build a tree.
4620///
4621/// # UPSTREAM-PARITY
4622///
4623/// ```c
4624/// xmlDocPtr xmlSAXParseEntity(xmlSAXHandlerPtr sax, const char *filename);
4625/// ```
4626///
4627/// # SAFETY
4628///
4629/// - `sax` must be valid pointers (or NULL
4630/// where the upstream C contract allows), obtained from the
4631/// matching constructor/owner and not yet freed; the callee may
4632/// take or keep ownership exactly as the C API specifies.
4633///
4634/// - `filename` must point to valid NUL-terminated
4635/// strings (or NULL where the C contract allows) for the lifetime
4636/// of the call.
4637///
4638/// The caller must not race this call with concurrent mutation of the
4639/// same objects from other threads (per-object state is not internally
4640/// synchronized). Violating any of the above is undefined behavior.
4641///
4642/// Exercised by the C-API differential courts
4643/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4644/// courts; those pass byte-for-byte against the upstream oracle.
4645#[no_mangle]
4646pub unsafe extern "C" fn xmlSAXParseEntity(
4647 sax: *mut _xmlSAXHandler,
4648 filename: *const c_char,
4649) -> *mut _xmlDoc {
4650 if filename.is_null() {
4651 return ptr::null_mut();
4652 }
4653 unsafe {
4654 let ctxt = xmlNewSAXParserCtxt(sax, ptr::null_mut());
4655 if ctxt.is_null() {
4656 return ptr::null_mut();
4657 }
4658 let input = match helpers::input_from_file(filename) {
4659 Ok(i) => i,
4660 Err(_) => {
4661 helpers::free_parser_ctxt(ctxt);
4662 return ptr::null_mut();
4663 }
4664 };
4665 helpers::setup_parser_input(ctxt, input);
4666 let rc = helpers::parse_document(ctxt);
4667 let doc = (*ctxt).myDoc;
4668 (*ctxt).myDoc = ptr::null_mut();
4669 if rc != 0 || (*ctxt).wellFormed == 0 {
4670 if !doc.is_null() {
4671 tree::free_doc(doc);
4672 }
4673 helpers::free_parser_ctxt(ctxt);
4674 return ptr::null_mut();
4675 }
4676 helpers::free_parser_ctxt(ctxt);
4677 doc
4678 }
4679}
4680
4681// ═══════════════════════════════════════════════════════════════════════════════
4682// C14N: xmlC14NDocSave
4683// ═══════════════════════════════════════════════════════════════════════════════
4684
4685/// Canonicalise a document (or node set) and save it to a file.
4686///
4687/// # UPSTREAM-PARITY
4688///
4689/// ```c
4690/// int xmlC14NDocSave(xmlDocPtr doc, xmlNodeSetPtr nodes, int mode,
4691/// xmlChar **inclusive_ns_prefixes, int with_comments,
4692/// const char *filename, int compression);
4693/// ```
4694///
4695/// # SAFETY
4696///
4697/// - `doc`, `nodes`, `inclusive_ns_prefixes` must be valid pointers (or NULL
4698/// where the upstream C contract allows), obtained from the
4699/// matching constructor/owner and not yet freed; the callee may
4700/// take or keep ownership exactly as the C API specifies.
4701///
4702/// - `filename` must point to valid NUL-terminated
4703/// strings (or NULL where the C contract allows) for the lifetime
4704/// of the call.
4705///
4706/// The caller must not race this call with concurrent mutation of the
4707/// same objects from other threads (per-object state is not internally
4708/// synchronized). Violating any of the above is undefined behavior.
4709///
4710/// Exercised by the C-API differential courts
4711/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
4712/// courts; those pass byte-for-byte against the upstream oracle.
4713#[no_mangle]
4714pub unsafe extern "C" fn xmlC14NDocSave(
4715 doc: *mut _xmlDoc,
4716 nodes: *mut _xmlNodeSet,
4717 mode: c_int,
4718 inclusive_ns_prefixes: *mut *mut xmlChar,
4719 with_comments: c_int,
4720 filename: *const c_char,
4721 compression: c_int,
4722) -> c_int {
4723 if filename.is_null() {
4724 return -1;
4725 }
4726 unsafe {
4727 let output = io::output_buffer_create_filename(filename, ptr::null_mut(), compression);
4728 if output.is_null() {
4729 return -1;
4730 }
4731 let ret = crate::xml::c14n::xmlC14NDocSaveTo(
4732 doc,
4733 nodes,
4734 mode,
4735 inclusive_ns_prefixes,
4736 with_comments,
4737 output,
4738 );
4739 if ret < 0 {
4740 io::output_buffer_close(output);
4741 return -1;
4742 }
4743 let close_ret = io::output_buffer_close(output);
4744 if close_ret < 0 {
4745 -1
4746 } else {
4747 ret
4748 }
4749 }
4750}