Skip to main content

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}