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