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