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//! [Flags][Data]), the recursive flag variants and the context accessors,
35//! wrapping the native-Rust engine in `src/xml/xinclude` that also powers
36//! `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
81use core::ffi::c_void;
82use core::mem::size_of;
83use core::ptr;
84use std::os::raw::{c_char, c_int};
85
86use crate::abi::allocator::{xmlFreeImpl, xmlMallocZero};
87use crate::abi::structs::{_xmlDoc, _xmlNode};
88use crate::abi::types::{XML_ERR_ARGUMENT, XML_ERR_INTERNAL_ERROR, XML_ERR_OK};
89use crate::xml::xinclude;
90
91/// Generic failure return for the process entry points (upstream `-1`).
92const XINCLUDE_ERROR: c_int = -1;
93
94/// XInclude error callback (upstream `xmlXIncludeErrorFunc`, xinclude.h).
95///
96/// `ctx` is the application data pointer registered with the handler,
97/// `code` is the error code, `message` the human-readable message.
98///
99/// # UPSTREAM-PARITY
100///
101/// ```c
102/// typedef void (*xmlXIncludeErrorFunc) (void *context, int code,
103///                                       const char *message);
104/// ```
105pub type xmlXIncludeErrorFunc =
106    unsafe extern "C" fn(ctx: *mut c_void, code: c_int, message: *const c_char);
107
108/// XInclude processing context (upstream `struct _xmlXIncludeCtxt`).
109///
110/// Opaque to C callers; only the fields the candidate engine needs are kept.
111#[repr(C)]
112#[derive(Debug)]
113pub struct _xmlXIncludeCtxt {
114    /// The source document being processed.
115    pub doc: *mut _xmlDoc,
116    /// Error handling function (never invoked by the candidate engine).
117    pub error: Option<xmlXIncludeErrorFunc>,
118    /// Application data passed to the error handler.
119    pub data: *mut c_void,
120    /// Processing flags (e.g. `XML_PARSE_NOXINCNODE`, `XML_PARSE_NONET`).
121    pub flags: c_int,
122    /// Error code of the last failure during processing.
123    pub lastError: c_int,
124}
125
126/// XInclude context pointer (upstream `xmlXIncludeCtxtPtr`).
127pub type xmlXIncludeCtxtPtr = *mut _xmlXIncludeCtxt;
128
129// ═══════════════════════════════════════════════════════════════════════════════
130// Context lifecycle
131// ═══════════════════════════════════════════════════════════════════════════════
132
133/// Create a new XInclude processing context.
134///
135/// Returns the context, or NULL on allocation failure. `doc` may be NULL;
136/// processing calls then fail with an argument error.
137///
138/// # UPSTREAM-PARITY
139///
140/// ```c
141/// xmlXIncludeCtxt *xmlXIncludeNewContext(xmlDoc *doc);
142/// ```
143///
144/// # SAFETY
145///
146/// `doc` must be NULL or a valid pointer to a parsed `_xmlDoc`.
147#[no_mangle]
148pub unsafe extern "C" fn xmlXIncludeNewContext(doc: *mut _xmlDoc) -> xmlXIncludeCtxtPtr {
149    let ctxt = unsafe { xmlMallocZero(size_of::<_xmlXIncludeCtxt>()) } as *mut _xmlXIncludeCtxt;
150    if ctxt.is_null() {
151        return ptr::null_mut();
152    }
153    unsafe {
154        (*ctxt).doc = doc;
155        (*ctxt).error = None;
156        (*ctxt).data = ptr::null_mut();
157        (*ctxt).flags = 0;
158        (*ctxt).lastError = XML_ERR_OK;
159    }
160    ctxt
161}
162
163/// Free an XInclude processing context.
164///
165/// # UPSTREAM-PARITY
166///
167/// ```c
168/// void xmlXIncludeFreeContext(xmlXIncludeCtxt *ctxt);
169/// ```
170///
171/// # SAFETY
172///
173/// `ctxt` must be NULL or a pointer previously returned by
174/// `xmlXIncludeNewContext` that has not already been freed.
175#[no_mangle]
176pub unsafe extern "C" fn xmlXIncludeFreeContext(ctxt: xmlXIncludeCtxtPtr) {
177    if ctxt.is_null() {
178        return;
179    }
180    // The context owns no heap allocations of its own (the engine keeps its
181    // per-include state only for the duration of a process call), so the
182    // struct itself is the only thing to release.
183    unsafe { xmlFreeImpl(ctxt as *mut c_void) };
184}
185
186// ═══════════════════════════════════════════════════════════════════════════════
187// Processing
188// ═══════════════════════════════════════════════════════════════════════════════
189
190/// Run the internal engine over the context's document, recording the
191/// outcome in `lastError`.
192///
193/// Returns the number of XInclude nodes processed, or -1 on error.
194///
195/// # SAFETY
196///
197/// `ctxt` must be a valid, non-null pointer to a context created by
198/// `xmlXIncludeNewContext`.
199unsafe fn process_ctxt_doc(ctxt: xmlXIncludeCtxtPtr) -> c_int {
200    let doc = unsafe { (*ctxt).doc };
201    if doc.is_null() {
202        unsafe { (*ctxt).lastError = XML_ERR_ARGUMENT };
203        return XINCLUDE_ERROR;
204    }
205    let flags = unsafe { (*ctxt).flags };
206    let ret = unsafe { xinclude::xinclude_process_flags(doc, flags) };
207    if ret < 0 {
208        // The engine reports failure without a fine-grained error code, so
209        // record a generic parser error to make xmlXIncludeGetLastError
210        // non-zero after a failure.
211        unsafe { (*ctxt).lastError = XML_ERR_INTERNAL_ERROR };
212    }
213    ret
214}
215
216/// Process the XInclude nodes in the tree rooted at `tree`, using the
217/// context's document and flags.
218///
219/// Returns the number of XInclude nodes processed, or -1 on error.
220///
221/// # UPSTREAM-PARITY
222///
223/// ```c
224/// int xmlXIncludeProcessNode(xmlXIncludeCtxt *ctxt, xmlNode *tree);
225/// ```
226///
227/// # SAFETY
228///
229/// `ctxt` must be a valid context created by `xmlXIncludeNewContext`; `tree`
230/// must be NULL or a valid `_xmlNode`.
231#[no_mangle]
232pub unsafe extern "C" fn xmlXIncludeProcessNode(
233    ctxt: xmlXIncludeCtxtPtr,
234    tree: *mut _xmlNode,
235) -> c_int {
236    if ctxt.is_null() || tree.is_null() {
237        return XINCLUDE_ERROR;
238    }
239    unsafe { process_ctxt_doc(ctxt) }
240}
241
242/// Process all XInclude nodes in the document containing `tree`.
243///
244/// Returns the number of XInclude nodes processed, or -1 on error.
245///
246/// # UPSTREAM-PARITY
247///
248/// ```c
249/// int xmlXIncludeProcessTree(xmlNode *tree);
250/// ```
251///
252/// # SAFETY
253///
254/// `tree` must be NULL or a valid `_xmlNode` attached to a document.
255#[no_mangle]
256pub unsafe extern "C" fn xmlXIncludeProcessTree(tree: *mut _xmlNode) -> c_int {
257    if tree.is_null() {
258        return XINCLUDE_ERROR;
259    }
260    let doc = unsafe { (*tree).doc };
261    if doc.is_null() {
262        return XINCLUDE_ERROR;
263    }
264    let ctxt = unsafe { xmlXIncludeNewContext(doc) };
265    if ctxt.is_null() {
266        return XINCLUDE_ERROR;
267    }
268    let ret = unsafe { xmlXIncludeProcessNode(ctxt, tree) };
269    unsafe { xmlXIncludeFreeContext(ctxt) };
270    ret
271}
272
273/// Process all XInclude nodes in the document containing `tree`, with flags.
274///
275/// Returns the number of XInclude nodes processed, or -1 on error.
276///
277/// # UPSTREAM-PARITY
278///
279/// ```c
280/// int xmlXIncludeProcessTreeFlags(xmlNode *tree, int flags);
281/// ```
282///
283/// # SAFETY
284///
285/// `tree` must be NULL or a valid `_xmlNode` attached to a document.
286#[no_mangle]
287pub unsafe extern "C" fn xmlXIncludeProcessTreeFlags(tree: *mut _xmlNode, flags: c_int) -> c_int {
288    if tree.is_null() {
289        return XINCLUDE_ERROR;
290    }
291    let doc = unsafe { (*tree).doc };
292    if doc.is_null() {
293        return XINCLUDE_ERROR;
294    }
295    let ctxt = unsafe { xmlXIncludeNewContext(doc) };
296    if ctxt.is_null() {
297        return XINCLUDE_ERROR;
298    }
299    unsafe {
300        (*ctxt).flags = flags;
301    }
302    let ret = unsafe { xmlXIncludeProcessNode(ctxt, tree) };
303    unsafe { xmlXIncludeFreeContext(ctxt) };
304    ret
305}
306
307/// Process all XInclude nodes in the document containing `tree`, with flags
308/// and an error-handler data pointer.
309///
310/// Returns the number of XInclude nodes processed, or -1 on error.
311///
312/// # UPSTREAM-PARITY
313///
314/// ```c
315/// int xmlXIncludeProcessTreeFlagsData(xmlNode *tree, int flags, void *data);
316/// ```
317///
318/// # SAFETY
319///
320/// `tree` must be NULL or a valid `_xmlNode` attached to a document; `data`
321/// must be NULL or a valid pointer for the lifetime of the call.
322#[no_mangle]
323pub unsafe extern "C" fn xmlXIncludeProcessTreeFlagsData(
324    tree: *mut _xmlNode,
325    flags: c_int,
326    data: *mut c_void,
327) -> c_int {
328    if tree.is_null() {
329        return XINCLUDE_ERROR;
330    }
331    let doc = unsafe { (*tree).doc };
332    if doc.is_null() {
333        return XINCLUDE_ERROR;
334    }
335    let ctxt = unsafe { xmlXIncludeNewContext(doc) };
336    if ctxt.is_null() {
337        return XINCLUDE_ERROR;
338    }
339    unsafe {
340        (*ctxt).flags = flags;
341        (*ctxt).data = data;
342        // Upstream also clears the handler function when only data is
343        // supplied; the candidate engine never invokes the handler, so the
344        // data pointer is the only observable part.
345        (*ctxt).error = None;
346    }
347    let ret = unsafe { xmlXIncludeProcessNode(ctxt, tree) };
348    unsafe { xmlXIncludeFreeContext(ctxt) };
349    ret
350}
351
352/// Process all XInclude nodes in `doc`, with flags and an error-handler data
353/// pointer.
354///
355/// Returns the number of XInclude nodes processed, or -1 on error.
356///
357/// # UPSTREAM-PARITY
358///
359/// ```c
360/// int xmlXIncludeProcessFlagsData(xmlDoc *doc, int flags, void *data);
361/// ```
362///
363/// # SAFETY
364///
365/// `doc` must be NULL or a valid pointer to a parsed `_xmlDoc`; `data` must
366/// be NULL or a valid pointer for the lifetime of the call.
367#[no_mangle]
368pub unsafe extern "C" fn xmlXIncludeProcessFlagsData(
369    doc: *mut _xmlDoc,
370    flags: c_int,
371    data: *mut c_void,
372) -> c_int {
373    if doc.is_null() {
374        return XINCLUDE_ERROR;
375    }
376    let ctxt = unsafe { xmlXIncludeNewContext(doc) };
377    if ctxt.is_null() {
378        return XINCLUDE_ERROR;
379    }
380    unsafe {
381        (*ctxt).flags = flags;
382        (*ctxt).data = data;
383    }
384    let ret = unsafe { process_ctxt_doc(ctxt) };
385    unsafe { xmlXIncludeFreeContext(ctxt) };
386    ret
387}
388
389// ═══════════════════════════════════════════════════════════════════════════════
390// Context configuration & state
391// ═══════════════════════════════════════════════════════════════════════════════
392
393/// Set the processing flags on the context.
394///
395/// Returns the previous set of flags, or -1 if `ctxt` is NULL.
396///
397/// # UPSTREAM-PARITY
398///
399/// ```c
400/// int xmlXIncludeSetFlags(xmlXIncludeCtxt *ctxt, int flags);
401/// ```
402///
403/// # SAFETY
404///
405/// `ctxt` must be NULL or a valid context created by `xmlXIncludeNewContext`.
406#[no_mangle]
407pub unsafe extern "C" fn xmlXIncludeSetFlags(ctxt: xmlXIncludeCtxtPtr, flags: c_int) -> c_int {
408    if ctxt.is_null() {
409        return XINCLUDE_ERROR;
410    }
411    let old_flags = unsafe { (*ctxt).flags };
412    unsafe {
413        (*ctxt).flags = flags;
414    }
415    old_flags
416}
417
418/// Set the error handler and its data on the context.
419///
420/// # UPSTREAM-PARITY
421///
422/// ```c
423/// void xmlXIncludeSetErrorHandler(xmlXIncludeCtxt *ctxt,
424///                                 xmlXIncludeErrorFunc handler,
425///                                 void *data);
426/// ```
427///
428/// # SAFETY
429///
430/// `ctxt` must be NULL or a valid context created by `xmlXIncludeNewContext`;
431/// `data` must be NULL or a valid pointer for as long as the handler may be
432/// called.
433#[no_mangle]
434pub unsafe extern "C" fn xmlXIncludeSetErrorHandler(
435    ctxt: xmlXIncludeCtxtPtr,
436    handler: Option<xmlXIncludeErrorFunc>,
437    data: *mut c_void,
438) {
439    if ctxt.is_null() {
440        return;
441    }
442    unsafe {
443        (*ctxt).error = handler;
444        (*ctxt).data = data;
445    }
446}
447
448/// Get the error code of the last failure during XInclude processing on this
449/// context.
450///
451/// Returns the last error code, or -1 if `ctxt` is NULL.
452///
453/// # UPSTREAM-PARITY
454///
455/// ```c
456/// int xmlXIncludeGetLastError(xmlXIncludeCtxt *ctxt);
457/// ```
458///
459/// # SAFETY
460///
461/// `ctxt` must be NULL or a valid context created by `xmlXIncludeNewContext`.
462#[no_mangle]
463pub unsafe extern "C" fn xmlXIncludeGetLastError(ctxt: xmlXIncludeCtxtPtr) -> c_int {
464    if ctxt.is_null() {
465        return XINCLUDE_ERROR;
466    }
467    unsafe { (*ctxt).lastError }
468}