libxml_rs/abi/exports_xinclude.rs
1//! C ABI exports for the XInclude family — `xmlXInclude*` (upstream
2//! `xinclude.h`, 2.15.3).
3//!
4//! Implements the context-based XInclude entry points by wrapping the
5//! native-Rust XInclude engine (`src/xml/xinclude`), which also powers
6//! `xmllint --xinclude`.
7//!
8//! The context struct (`_xmlXIncludeCtxt`) is opaque at the C boundary — no
9//! field is ever dereferenced by the caller. The candidate engine keeps all
10//! per-include state (URL stack, circular-reference tracking, fallback
11//! handling) in Rust-owned structures that live for the duration of a single
12//! process call, so the context only needs to record the document, the
13//! processing flags, the error handler and the last error code.
14//!
15//! # Engine scope
16//!
17//! The engine public entry points (`xinclude_process`,
18//! `xinclude_process_flags`) always walk the whole document starting at its
19//! root element. The tree-based entry points below therefore process the
20//! document that owns the given node; for a node that is the document root
21//! (the common case) this is exactly the upstream subtree semantics.
22//!
23//! # Upstream contract
24//!
25//! Parity target is upstream `xinclude.c` (libxml2 2.15.3,
26//! SRC-LIBXML2-2.15.0-XINCLUDE-C) with the `xinclude.h` signatures; R-000165
27//! (11.1-O) closed the xinclude export gaps (e.g.
28//! `xmlXIncludeSetResourceLoader`).
29//!
30//! # Conceptual behavior
31//!
32//! This module implements the XInclude context ABI: context create/free, the
33//! process entry points (`xmlXIncludeProcessNode`, `xmlXIncludeProcessTree`
34//! with the flag variants), the recursive flag variants and the context
35//! accessors, wrapping the native-Rust engine in `src/xml/xinclude` that also
36//! powers `xmllint --xinclude`.
37//!
38//! # Ownership & safety invariants
39//!
40//! Contexts are caller-owned (freed with `xmlXIncludeFreeContext`); processed
41//! documents stay caller-owned — the engine mutates the tree in place and
42//! never adopts the doc; the context records only the document, flags, error
43//! handler and last error code (all internal per-include state is Rust-owned
44//! and process-lifetime). Return codes follow upstream (-1 error, 0
45//! not-processed, 1 processed).
46//!
47//! # Historical quirks & epochs
48//!
49//! XInclude support arrived in the 2.6 era and its ABI is stable through the
50//! 2.15.3 parity target; R-000165 (11.1-O) added the missing xinclude symbols
51//! so the oracle export set is complete.
52//!
53//! # Deliberate oddities
54//!
55//! The tree-based entry points process the whole document that owns the given
56//! node rather than a strict subtree (documented in the header above) — a
57//! deliberate scope choice that is exactly upstream semantics for the common
58//! root-node case.
59//!
60//! # Proving courts
61//!
62//! The XINCLUDE court family, the CLI-XMLLINT xinclude cases and the
63//! DSO-LOADER/HEADER-COMPILE courts cover this module; the xinclude unit
64//! tests run under cargo test.
65//!
66//! # Tempting simplifications that would break parity
67//!
68//! A tempting simplification is to make `xmlXIncludeProcessNode` process only
69//! the node subtree — for non-root nodes upstream walks the owning document,
70//! so the fingerprint the XINCLUDE courts compare would diverge. Another
71//! shortcut, freeing the document in `xmlXIncludeFreeContext`, would break the
72//! caller-owned-document contract.
73
74#![allow(
75 missing_docs,
76 non_snake_case,
77 non_camel_case_types,
78 non_upper_case_globals
79)]
80
81// SAFETY-SCOPE: EXPORT-XINCLUDE-MECHANICAL-001
82// (11.1-Z.3 proof scope, classified-generated) — this module is the
83// mechanical extern-"C" export surface: every `unsafe` block in it is
84// the documented indirection/registry-access pattern whose validity
85// rests on the upstream C contract, and the exported signatures are
86// machine-measured by the ABI-FUNCTION-SIGNATURE and DSO-LOADER
87// courts and the C-API differential probes. The safety contract of
88// each export is stated in its own doc comment; this scope covers the
89// mechanical wrappers' unsafe blocks.
90
91use core::ffi::c_void;
92use core::mem::size_of;
93use core::ptr;
94use std::os::raw::{c_char, c_int};
95
96use crate::abi::allocator::{xmlFreeImpl, xmlMallocZero};
97use crate::abi::structs::{_xmlDoc, _xmlNode};
98use crate::abi::types::{XML_ERR_ARGUMENT, XML_ERR_INTERNAL_ERROR, XML_ERR_OK};
99use crate::xml::xinclude;
100
101/// Generic failure return for the process entry points (upstream `-1`).
102const XINCLUDE_ERROR: c_int = -1;
103
104/// XInclude error callback (upstream `xmlXIncludeErrorFunc`, xinclude.h).
105///
106/// `ctx` is the application data pointer registered with the handler,
107/// `code` is the error code, `message` the human-readable message.
108///
109/// # UPSTREAM-PARITY
110///
111/// ```c
112/// typedef void (*xmlXIncludeErrorFunc) (void *context, int code,
113/// const char *message);
114/// ```
115pub type xmlXIncludeErrorFunc =
116 unsafe extern "C" fn(ctx: *mut c_void, code: c_int, message: *const c_char);
117
118/// XInclude processing context (upstream `struct _xmlXIncludeCtxt`).
119///
120/// Opaque to C callers; only the fields the candidate engine needs are kept.
121#[repr(C)]
122#[derive(Debug)]
123pub struct _xmlXIncludeCtxt {
124 /// The source document being processed.
125 pub doc: *mut _xmlDoc,
126 /// Error handling function (never invoked by the candidate engine).
127 pub error: Option<xmlXIncludeErrorFunc>,
128 /// Application data passed to the error handler.
129 pub data: *mut c_void,
130 /// Processing flags (e.g. `XML_PARSE_NOXINCNODE`, `XML_PARSE_NONET`).
131 pub flags: c_int,
132 /// Error code of the last failure during processing.
133 pub lastError: c_int,
134}
135
136/// XInclude context pointer (upstream `xmlXIncludeCtxtPtr`).
137pub type xmlXIncludeCtxtPtr = *mut _xmlXIncludeCtxt;
138
139// ═══════════════════════════════════════════════════════════════════════════════
140// Context lifecycle
141// ═══════════════════════════════════════════════════════════════════════════════
142
143/// Create a new XInclude processing context.
144///
145/// Returns the context, or NULL on allocation failure. `doc` may be NULL;
146/// processing calls then fail with an argument error.
147///
148/// # UPSTREAM-PARITY
149///
150/// ```c
151/// xmlXIncludeCtxt *xmlXIncludeNewContext(xmlDoc *doc);
152/// ```
153///
154/// # SAFETY
155///
156/// `doc` must be NULL or a valid pointer to a parsed `_xmlDoc`.
157#[no_mangle]
158pub unsafe extern "C" fn xmlXIncludeNewContext(doc: *mut _xmlDoc) -> xmlXIncludeCtxtPtr {
159 let ctxt = unsafe { xmlMallocZero(size_of::<_xmlXIncludeCtxt>()) } as *mut _xmlXIncludeCtxt;
160 if ctxt.is_null() {
161 return ptr::null_mut();
162 }
163 unsafe {
164 (*ctxt).doc = doc;
165 (*ctxt).error = None;
166 (*ctxt).data = ptr::null_mut();
167 (*ctxt).flags = 0;
168 (*ctxt).lastError = XML_ERR_OK;
169 }
170 ctxt
171}
172
173/// Free an XInclude processing context.
174///
175/// # UPSTREAM-PARITY
176///
177/// ```c
178/// void xmlXIncludeFreeContext(xmlXIncludeCtxt *ctxt);
179/// ```
180///
181/// # SAFETY
182///
183/// `ctxt` must be NULL or a pointer previously returned by
184/// `xmlXIncludeNewContext` that has not already been freed.
185#[no_mangle]
186pub unsafe extern "C" fn xmlXIncludeFreeContext(ctxt: xmlXIncludeCtxtPtr) {
187 if ctxt.is_null() {
188 return;
189 }
190 // The context owns no heap allocations of its own (the engine keeps its
191 // per-include state only for the duration of a process call), so the
192 // struct itself is the only thing to release.
193 unsafe { xmlFreeImpl(ctxt as *mut c_void) };
194}
195
196// ═══════════════════════════════════════════════════════════════════════════════
197// Processing
198// ═══════════════════════════════════════════════════════════════════════════════
199
200/// Run the internal engine over the context's document, recording the
201/// outcome in `lastError`.
202///
203/// Returns the number of XInclude nodes processed, or -1 on error.
204///
205/// # SAFETY
206///
207/// `ctxt` must be a valid, non-null pointer to a context created by
208/// `xmlXIncludeNewContext`.
209unsafe fn process_ctxt_doc(ctxt: xmlXIncludeCtxtPtr) -> c_int {
210 let doc = unsafe { (*ctxt).doc };
211 if doc.is_null() {
212 unsafe { (*ctxt).lastError = XML_ERR_ARGUMENT };
213 return XINCLUDE_ERROR;
214 }
215 let flags = unsafe { (*ctxt).flags };
216 let ret = unsafe { xinclude::xinclude_process_flags(doc, flags) };
217 if ret < 0 {
218 // The engine reports failure without a fine-grained error code, so
219 // record a generic parser error to make xmlXIncludeGetLastError
220 // non-zero after a failure.
221 unsafe { (*ctxt).lastError = XML_ERR_INTERNAL_ERROR };
222 }
223 ret
224}
225
226/// Process the XInclude nodes in the tree rooted at `tree`, using the
227/// context's document and flags.
228///
229/// Returns the number of XInclude nodes processed, or -1 on error.
230///
231/// # UPSTREAM-PARITY
232///
233/// ```c
234/// int xmlXIncludeProcessNode(xmlXIncludeCtxt *ctxt, xmlNode *tree);
235/// ```
236///
237/// # SAFETY
238///
239/// `ctxt` must be a valid context created by `xmlXIncludeNewContext`; `tree`
240/// must be NULL or a valid `_xmlNode`.
241#[no_mangle]
242pub unsafe extern "C" fn xmlXIncludeProcessNode(
243 ctxt: xmlXIncludeCtxtPtr,
244 tree: *mut _xmlNode,
245) -> c_int {
246 if ctxt.is_null() || tree.is_null() {
247 return XINCLUDE_ERROR;
248 }
249 unsafe { process_ctxt_doc(ctxt) }
250}
251
252/// Process all XInclude nodes in the document containing `tree`.
253///
254/// Returns the number of XInclude nodes processed, or -1 on error.
255///
256/// # UPSTREAM-PARITY
257///
258/// ```c
259/// int xmlXIncludeProcessTree(xmlNode *tree);
260/// ```
261///
262/// # SAFETY
263///
264/// `tree` must be NULL or a valid `_xmlNode` attached to a document.
265#[no_mangle]
266pub unsafe extern "C" fn xmlXIncludeProcessTree(tree: *mut _xmlNode) -> c_int {
267 if tree.is_null() {
268 return XINCLUDE_ERROR;
269 }
270 let doc = unsafe { (*tree).doc };
271 if doc.is_null() {
272 return XINCLUDE_ERROR;
273 }
274 let ctxt = unsafe { xmlXIncludeNewContext(doc) };
275 if ctxt.is_null() {
276 return XINCLUDE_ERROR;
277 }
278 let ret = unsafe { xmlXIncludeProcessNode(ctxt, tree) };
279 unsafe { xmlXIncludeFreeContext(ctxt) };
280 ret
281}
282
283/// Process all XInclude nodes in the document containing `tree`, with flags.
284///
285/// Returns the number of XInclude nodes processed, or -1 on error.
286///
287/// # UPSTREAM-PARITY
288///
289/// ```c
290/// int xmlXIncludeProcessTreeFlags(xmlNode *tree, int flags);
291/// ```
292///
293/// # SAFETY
294///
295/// `tree` must be NULL or a valid `_xmlNode` attached to a document.
296#[no_mangle]
297pub unsafe extern "C" fn xmlXIncludeProcessTreeFlags(tree: *mut _xmlNode, flags: c_int) -> c_int {
298 if tree.is_null() {
299 return XINCLUDE_ERROR;
300 }
301 let doc = unsafe { (*tree).doc };
302 if doc.is_null() {
303 return XINCLUDE_ERROR;
304 }
305 let ctxt = unsafe { xmlXIncludeNewContext(doc) };
306 if ctxt.is_null() {
307 return XINCLUDE_ERROR;
308 }
309 unsafe {
310 (*ctxt).flags = flags;
311 }
312 let ret = unsafe { xmlXIncludeProcessNode(ctxt, tree) };
313 unsafe { xmlXIncludeFreeContext(ctxt) };
314 ret
315}
316
317/// Process all XInclude nodes in the document containing `tree`, with flags
318/// and an error-handler data pointer.
319///
320/// Returns the number of XInclude nodes processed, or -1 on error.
321///
322/// # UPSTREAM-PARITY
323///
324/// ```c
325/// int xmlXIncludeProcessTreeFlagsData(xmlNode *tree, int flags, void *data);
326/// ```
327///
328/// # SAFETY
329///
330/// `tree` must be NULL or a valid `_xmlNode` attached to a document; `data`
331/// must be NULL or a valid pointer for the lifetime of the call.
332#[no_mangle]
333pub unsafe extern "C" fn xmlXIncludeProcessTreeFlagsData(
334 tree: *mut _xmlNode,
335 flags: c_int,
336 data: *mut c_void,
337) -> c_int {
338 if tree.is_null() {
339 return XINCLUDE_ERROR;
340 }
341 let doc = unsafe { (*tree).doc };
342 if doc.is_null() {
343 return XINCLUDE_ERROR;
344 }
345 let ctxt = unsafe { xmlXIncludeNewContext(doc) };
346 if ctxt.is_null() {
347 return XINCLUDE_ERROR;
348 }
349 unsafe {
350 (*ctxt).flags = flags;
351 (*ctxt).data = data;
352 // Upstream also clears the handler function when only data is
353 // supplied; the candidate engine never invokes the handler, so the
354 // data pointer is the only observable part.
355 (*ctxt).error = None;
356 }
357 let ret = unsafe { xmlXIncludeProcessNode(ctxt, tree) };
358 unsafe { xmlXIncludeFreeContext(ctxt) };
359 ret
360}
361
362/// Process all XInclude nodes in `doc`, with flags and an error-handler data
363/// pointer.
364///
365/// Returns the number of XInclude nodes processed, or -1 on error.
366///
367/// # UPSTREAM-PARITY
368///
369/// ```c
370/// int xmlXIncludeProcessFlagsData(xmlDoc *doc, int flags, void *data);
371/// ```
372///
373/// # SAFETY
374///
375/// `doc` must be NULL or a valid pointer to a parsed `_xmlDoc`; `data` must
376/// be NULL or a valid pointer for the lifetime of the call.
377#[no_mangle]
378pub unsafe extern "C" fn xmlXIncludeProcessFlagsData(
379 doc: *mut _xmlDoc,
380 flags: c_int,
381 data: *mut c_void,
382) -> c_int {
383 if doc.is_null() {
384 return XINCLUDE_ERROR;
385 }
386 let ctxt = unsafe { xmlXIncludeNewContext(doc) };
387 if ctxt.is_null() {
388 return XINCLUDE_ERROR;
389 }
390 unsafe {
391 (*ctxt).flags = flags;
392 (*ctxt).data = data;
393 }
394 let ret = unsafe { process_ctxt_doc(ctxt) };
395 unsafe { xmlXIncludeFreeContext(ctxt) };
396 ret
397}
398
399// ═══════════════════════════════════════════════════════════════════════════════
400// Context configuration & state
401// ═══════════════════════════════════════════════════════════════════════════════
402
403/// Set the processing flags on the context.
404///
405/// Returns the previous set of flags, or -1 if `ctxt` is NULL.
406///
407/// # UPSTREAM-PARITY
408///
409/// ```c
410/// int xmlXIncludeSetFlags(xmlXIncludeCtxt *ctxt, int flags);
411/// ```
412///
413/// # SAFETY
414///
415/// `ctxt` must be NULL or a valid context created by `xmlXIncludeNewContext`.
416#[no_mangle]
417pub unsafe extern "C" fn xmlXIncludeSetFlags(ctxt: xmlXIncludeCtxtPtr, flags: c_int) -> c_int {
418 if ctxt.is_null() {
419 return XINCLUDE_ERROR;
420 }
421 let old_flags = unsafe { (*ctxt).flags };
422 unsafe {
423 (*ctxt).flags = flags;
424 }
425 old_flags
426}
427
428/// Set the error handler and its data on the context.
429///
430/// # UPSTREAM-PARITY
431///
432/// ```c
433/// void xmlXIncludeSetErrorHandler(xmlXIncludeCtxt *ctxt,
434/// xmlXIncludeErrorFunc handler,
435/// void *data);
436/// ```
437///
438/// # SAFETY
439///
440/// `ctxt` must be NULL or a valid context created by `xmlXIncludeNewContext`;
441/// `data` must be NULL or a valid pointer for as long as the handler may be
442/// called.
443#[no_mangle]
444pub unsafe extern "C" fn xmlXIncludeSetErrorHandler(
445 ctxt: xmlXIncludeCtxtPtr,
446 handler: Option<xmlXIncludeErrorFunc>,
447 data: *mut c_void,
448) {
449 if ctxt.is_null() {
450 return;
451 }
452 unsafe {
453 (*ctxt).error = handler;
454 (*ctxt).data = data;
455 }
456}
457
458/// Get the error code of the last failure during XInclude processing on this
459/// context.
460///
461/// Returns the last error code, or -1 if `ctxt` is NULL.
462///
463/// # UPSTREAM-PARITY
464///
465/// ```c
466/// int xmlXIncludeGetLastError(xmlXIncludeCtxt *ctxt);
467/// ```
468///
469/// # SAFETY
470///
471/// `ctxt` must be NULL or a valid context created by `xmlXIncludeNewContext`.
472#[no_mangle]
473pub unsafe extern "C" fn xmlXIncludeGetLastError(ctxt: xmlXIncludeCtxtPtr) -> c_int {
474 if ctxt.is_null() {
475 return XINCLUDE_ERROR;
476 }
477 unsafe { (*ctxt).lastError }
478}