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