Skip to main content

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}