Skip to main content

libxml_rs/xslt/extensions/
mod.rs

1//! XSLT extension mechanisms (§33, §35, §85 Phase 8).
2//!
3//! Extensions allow stylesheets to call external functions and elements.
4//! Registered via `xsltRegisterExtFunction` (functions) and
5//! `xsltRegisterExtElement` (elements).
6//!
7//! # UPSTREAM-PARITY
8//!
9//! Upstream libxslt (extensions.c) stores extension function registrations
10//! in the transform context (`extFunctionsTab`), each entry holding the
11//! namespace URI, name, and function pointer. Extension elements are stored
12//! similarly with their transform function.
13//!
14//! Registration is per-context; functions are looked up at call time via
15//! the context's XPath function lookup mechanism.
16
17use crate::abi::allocator::xmlFree;
18use crate::abi::structs::*;
19use crate::abi::types::*;
20use std::os::raw::c_int;
21use std::ptr;
22
23/// A registered extension function.
24#[repr(C)]
25pub struct _xsltExtFunction {
26    pub next: *mut _xsltExtFunction,
27    pub name: *mut xmlChar,
28    pub ns: *mut xmlChar,
29    pub func: *mut c_void,
30}
31
32/// A registered extension element.
33#[repr(C)]
34pub struct _xsltExtElement {
35    pub next: *mut _xsltExtElement,
36    pub name: *mut xmlChar,
37    pub ns: *mut xmlChar,
38    pub func: *mut c_void,
39}
40
41/// Register an XSLT extension function.
42///
43/// # SAFETY
44///
45/// - `ctxt` must be a valid `_xsltTransformContext`.
46/// - `name` and `NS_uri` must be valid NUL-terminated strings.
47/// - `f` must be a valid function pointer.
48#[no_mangle]
49pub unsafe extern "C" fn xsltRegisterExtFunction(
50    ctxt: *mut _xsltTransformContext,
51    name: *const xmlChar,
52    NS_uri: *const xmlChar,
53    f: Option<unsafe extern "C" fn(*mut c_void, c_int)>,
54) -> c_int {
55    if ctxt.is_null() || name.is_null() || NS_uri.is_null() {
56        return -1;
57    }
58    let entry = libc::calloc(1, core::mem::size_of::<_xsltExtFunction>()) as *mut _xsltExtFunction;
59    if entry.is_null() {
60        return -1;
61    }
62    (*entry).name = dup_str(name);
63    (*entry).ns = dup_str(NS_uri);
64    if (*entry).name.is_null() || (*entry).ns.is_null() {
65        if !(*entry).name.is_null() {
66            libc::free((*entry).name as *mut libc::c_void);
67        }
68        if !(*entry).ns.is_null() {
69            libc::free((*entry).ns as *mut libc::c_void);
70        }
71        libc::free(entry as *mut libc::c_void);
72        return -1;
73    }
74    (*entry).func = f.map(|fp| fp as *mut c_void).unwrap_or(ptr::null_mut());
75    // Prepend to the context's extension function list (extFunctionsTab
76    // is a void* chain in the struct; we use a linked list here).
77    (*entry).next = (*ctxt).extFunctionsTab as *mut _xsltExtFunction;
78    (*ctxt).extFunctionsTab = entry as *mut c_void;
79    (*ctxt).extFunctionsNr += 1;
80    0
81}
82
83/// Register an XSLT extension element.
84///
85/// # SAFETY
86///
87/// - `ctxt` must be a valid `_xsltTransformContext`.
88/// - `name` and `NS_uri` must be valid NUL-terminated strings.
89/// - `f` must be a valid function pointer.
90#[no_mangle]
91pub unsafe extern "C" fn xsltRegisterExtElement(
92    ctxt: *mut _xsltTransformContext,
93    name: *const xmlChar,
94    NS_uri: *const xmlChar,
95    f: Option<
96        unsafe extern "C" fn(*mut c_void, *mut c_void, *mut _xmlNode, *mut c_void, *mut _xmlNode),
97    >,
98) -> c_int {
99    if ctxt.is_null() || name.is_null() || NS_uri.is_null() {
100        return -1;
101    }
102    let entry = libc::calloc(1, core::mem::size_of::<_xsltExtElement>()) as *mut _xsltExtElement;
103    if entry.is_null() {
104        return -1;
105    }
106    (*entry).name = dup_str(name);
107    (*entry).ns = dup_str(NS_uri);
108    if (*entry).name.is_null() || (*entry).ns.is_null() {
109        if !(*entry).name.is_null() {
110            libc::free((*entry).name as *mut libc::c_void);
111        }
112        if !(*entry).ns.is_null() {
113            libc::free((*entry).ns as *mut libc::c_void);
114        }
115        libc::free(entry as *mut libc::c_void);
116        return -1;
117    }
118    (*entry).func = f.map(|fp| fp as *mut c_void).unwrap_or(ptr::null_mut());
119    (*entry).next = (*ctxt).extInfos as *mut _xsltExtElement;
120    (*ctxt).extInfos = entry as *mut c_void;
121    0
122}
123
124/// Look up a registered extension function.
125///
126/// # SAFETY
127///
128/// - `ctxt` must be a valid `_xsltTransformContext`.
129/// - `name` and `ns` must be valid NUL-terminated strings.
130pub unsafe fn xsltFindExtFunction(
131    ctxt: *mut _xsltTransformContext,
132    name: *const xmlChar,
133    ns: *const xmlChar,
134) -> *mut c_void {
135    if ctxt.is_null() || name.is_null() || ns.is_null() {
136        return ptr::null_mut();
137    }
138    let mut cur = (*ctxt).extFunctionsTab as *mut _xsltExtFunction;
139    while !cur.is_null() {
140        if !(*cur).name.is_null()
141            && !(*cur).ns.is_null()
142            && libc::strcmp(
143                (*cur).name as *const libc::c_char,
144                name as *const libc::c_char,
145            ) == 0
146            && libc::strcmp((*cur).ns as *const libc::c_char, ns as *const libc::c_char) == 0
147        {
148            return (*cur).func;
149        }
150        cur = (*cur).next;
151    }
152    ptr::null_mut()
153}
154
155/// Look up a registered extension element.
156///
157/// # SAFETY
158///
159/// - `ctxt` must be a valid `_xsltTransformContext`.
160/// - `name` and `ns` must be valid NUL-terminated strings.
161pub unsafe fn xsltFindExtElement(
162    ctxt: *mut _xsltTransformContext,
163    name: *const xmlChar,
164    ns: *const xmlChar,
165) -> *mut c_void {
166    if ctxt.is_null() || name.is_null() || ns.is_null() {
167        return ptr::null_mut();
168    }
169    let mut cur = (*ctxt).extInfos as *mut _xsltExtElement;
170    while !cur.is_null() {
171        if !(*cur).name.is_null()
172            && !(*cur).ns.is_null()
173            && libc::strcmp(
174                (*cur).name as *const libc::c_char,
175                name as *const libc::c_char,
176            ) == 0
177            && libc::strcmp((*cur).ns as *const libc::c_char, ns as *const libc::c_char) == 0
178        {
179            return (*cur).func;
180        }
181        cur = (*cur).next;
182    }
183    ptr::null_mut()
184}
185
186/// Free all extension registrations in a transform context.
187///
188/// # SAFETY
189///
190/// - `ctxt` must be a valid `_xsltTransformContext`.
191pub unsafe fn xsltFreeExts(ctxt: *mut _xsltTransformContext) {
192    if ctxt.is_null() {
193        return;
194    }
195    // Free extension functions.
196    let mut cur = (*ctxt).extFunctionsTab as *mut _xsltExtFunction;
197    (*ctxt).extFunctionsTab = ptr::null_mut();
198    (*ctxt).extFunctionsNr = 0;
199    while !cur.is_null() {
200        let next = (*cur).next;
201        if !(*cur).name.is_null() {
202            libc::free((*cur).name as *mut libc::c_void);
203        }
204        if !(*cur).ns.is_null() {
205            libc::free((*cur).ns as *mut libc::c_void);
206        }
207        libc::free(cur as *mut libc::c_void);
208        cur = next;
209    }
210    // Free extension elements.
211    let mut cur = (*ctxt).extInfos as *mut _xsltExtElement;
212    (*ctxt).extInfos = ptr::null_mut();
213    while !cur.is_null() {
214        let next = (*cur).next;
215        if !(*cur).name.is_null() {
216            libc::free((*cur).name as *mut libc::c_void);
217        }
218        if !(*cur).ns.is_null() {
219            libc::free((*cur).ns as *mut libc::c_void);
220        }
221        libc::free(cur as *mut libc::c_void);
222        cur = next;
223    }
224}
225
226/// Duplicate a NUL-terminated string.
227unsafe fn dup_str(s: *const xmlChar) -> *mut xmlChar {
228    let len = libc::strlen(s as *const libc::c_char);
229    let copy = libc::malloc(len + 1) as *mut xmlChar;
230    if copy.is_null() {
231        return ptr::null_mut();
232    }
233    libc::memcpy(copy as *mut libc::c_void, s as *const libc::c_void, len);
234    *copy.add(len) = 0;
235    copy
236}
237
238use std::ffi::c_void;
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use core::ptr;
244
245    fn make_ctxt() -> *mut _xsltTransformContext {
246        unsafe {
247            libc::calloc(1, core::mem::size_of::<_xsltTransformContext>())
248                as *mut _xsltTransformContext
249        }
250    }
251
252    #[test]
253    fn test_register_and_find_function() {
254        unsafe {
255            let ctxt = make_ctxt();
256            extern "C" fn dummy(_ctx: *mut c_void, _n: c_int) {}
257            assert_eq!(
258                xsltRegisterExtFunction(
259                    ctxt,
260                    b"myfunc\0".as_ptr() as *const xmlChar,
261                    b"http://example.com/ext\0".as_ptr() as *const xmlChar,
262                    Some(dummy),
263                ),
264                0
265            );
266            let found = xsltFindExtFunction(
267                ctxt,
268                b"myfunc\0".as_ptr() as *const xmlChar,
269                b"http://example.com/ext\0".as_ptr() as *const xmlChar,
270            );
271            assert_eq!(found, dummy as *mut c_void);
272            let not_found = xsltFindExtFunction(
273                ctxt,
274                b"other\0".as_ptr() as *const xmlChar,
275                b"http://example.com/ext\0".as_ptr() as *const xmlChar,
276            );
277            assert!(not_found.is_null());
278            xsltFreeExts(ctxt);
279            libc::free(ctxt as *mut libc::c_void);
280        }
281    }
282
283    #[test]
284    fn test_register_null() {
285        unsafe {
286            assert_eq!(
287                xsltRegisterExtFunction(ptr::null_mut(), ptr::null(), ptr::null(), None),
288                -1
289            );
290            assert_eq!(
291                xsltRegisterExtElement(ptr::null_mut(), ptr::null(), ptr::null(), None),
292                -1
293            );
294        }
295    }
296}