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