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