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}