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