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 (`extFunctions`), 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 (extFunctions
76    // is a void* chain in the struct; we use a linked list here).
77    (*entry).next = (*ctxt).extFunctions as *mut _xsltExtFunction;
78    (*ctxt).extFunctions = entry as *mut c_void;
79    0
80}
81
82/// Register an XSLT extension element.
83///
84/// # SAFETY
85///
86/// - `ctxt` must be a valid `_xsltTransformContext`.
87/// - `name` and `NS_uri` must be valid NUL-terminated strings.
88/// - `f` must be a valid function pointer.
89#[no_mangle]
90pub unsafe extern "C" fn xsltRegisterExtElement(
91    ctxt: *mut _xsltTransformContext,
92    name: *const xmlChar,
93    NS_uri: *const xmlChar,
94    f: Option<
95        unsafe extern "C" fn(*mut c_void, *mut c_void, *mut _xmlNode, *mut c_void, *mut _xmlNode),
96    >,
97) -> c_int {
98    if ctxt.is_null() || name.is_null() || NS_uri.is_null() {
99        return -1;
100    }
101    let entry = libc::calloc(1, core::mem::size_of::<_xsltExtElement>()) as *mut _xsltExtElement;
102    if entry.is_null() {
103        return -1;
104    }
105    (*entry).name = dup_str(name);
106    (*entry).ns = dup_str(NS_uri);
107    if (*entry).name.is_null() || (*entry).ns.is_null() {
108        if !(*entry).name.is_null() {
109            libc::free((*entry).name as *mut libc::c_void);
110        }
111        if !(*entry).ns.is_null() {
112            libc::free((*entry).ns as *mut libc::c_void);
113        }
114        libc::free(entry as *mut libc::c_void);
115        return -1;
116    }
117    (*entry).func = f.map(|fp| fp as *mut c_void).unwrap_or(ptr::null_mut());
118    (*entry).next = (*ctxt).extElements as *mut _xsltExtElement;
119    (*ctxt).extElements = entry as *mut c_void;
120    0
121}
122
123/// Look up a registered extension function.
124///
125/// # SAFETY
126///
127/// - `ctxt` must be a valid `_xsltTransformContext`.
128/// - `name` and `ns` must be valid NUL-terminated strings.
129pub unsafe fn xsltFindExtFunction(
130    ctxt: *mut _xsltTransformContext,
131    name: *const xmlChar,
132    ns: *const xmlChar,
133) -> *mut c_void {
134    if ctxt.is_null() || name.is_null() || ns.is_null() {
135        return ptr::null_mut();
136    }
137    let mut cur = (*ctxt).extFunctions as *mut _xsltExtFunction;
138    while !cur.is_null() {
139        if !(*cur).name.is_null()
140            && !(*cur).ns.is_null()
141            && libc::strcmp(
142                (*cur).name as *const libc::c_char,
143                name as *const libc::c_char,
144            ) == 0
145            && libc::strcmp((*cur).ns as *const libc::c_char, ns as *const libc::c_char) == 0
146        {
147            return (*cur).func;
148        }
149        cur = (*cur).next;
150    }
151    ptr::null_mut()
152}
153
154/// Look up a registered extension element.
155///
156/// # SAFETY
157///
158/// - `ctxt` must be a valid `_xsltTransformContext`.
159/// - `name` and `ns` must be valid NUL-terminated strings.
160pub unsafe fn xsltFindExtElement(
161    ctxt: *mut _xsltTransformContext,
162    name: *const xmlChar,
163    ns: *const xmlChar,
164) -> *mut c_void {
165    if ctxt.is_null() || name.is_null() || ns.is_null() {
166        return ptr::null_mut();
167    }
168    let mut cur = (*ctxt).extElements as *mut _xsltExtElement;
169    while !cur.is_null() {
170        if !(*cur).name.is_null()
171            && !(*cur).ns.is_null()
172            && libc::strcmp(
173                (*cur).name as *const libc::c_char,
174                name as *const libc::c_char,
175            ) == 0
176            && libc::strcmp((*cur).ns as *const libc::c_char, ns as *const libc::c_char) == 0
177        {
178            return (*cur).func;
179        }
180        cur = (*cur).next;
181    }
182    ptr::null_mut()
183}
184
185/// Free all extension registrations in a transform context.
186///
187/// # SAFETY
188///
189/// - `ctxt` must be a valid `_xsltTransformContext`.
190pub unsafe fn xsltFreeExts(ctxt: *mut _xsltTransformContext) {
191    if ctxt.is_null() {
192        return;
193    }
194    // Free extension functions.
195    let mut cur = (*ctxt).extFunctions as *mut _xsltExtFunction;
196    (*ctxt).extFunctions = ptr::null_mut();
197    while !cur.is_null() {
198        let next = (*cur).next;
199        if !(*cur).name.is_null() {
200            libc::free((*cur).name as *mut libc::c_void);
201        }
202        if !(*cur).ns.is_null() {
203            libc::free((*cur).ns as *mut libc::c_void);
204        }
205        libc::free(cur as *mut libc::c_void);
206        cur = next;
207    }
208    // Free extension elements.
209    let mut cur = (*ctxt).extElements as *mut _xsltExtElement;
210    (*ctxt).extElements = ptr::null_mut();
211    while !cur.is_null() {
212        let next = (*cur).next;
213        if !(*cur).name.is_null() {
214            libc::free((*cur).name as *mut libc::c_void);
215        }
216        if !(*cur).ns.is_null() {
217            libc::free((*cur).ns as *mut libc::c_void);
218        }
219        libc::free(cur as *mut libc::c_void);
220        cur = next;
221    }
222}
223
224/// Duplicate a NUL-terminated string.
225unsafe fn dup_str(s: *const xmlChar) -> *mut xmlChar {
226    let len = libc::strlen(s as *const libc::c_char);
227    let copy = libc::malloc(len + 1) as *mut xmlChar;
228    if copy.is_null() {
229        return ptr::null_mut();
230    }
231    libc::memcpy(copy as *mut libc::c_void, s as *const libc::c_void, len);
232    *copy.add(len) = 0;
233    copy
234}
235
236use std::ffi::c_void;
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use core::ptr;
242
243    fn make_ctxt() -> *mut _xsltTransformContext {
244        unsafe {
245            libc::calloc(1, core::mem::size_of::<_xsltTransformContext>())
246                as *mut _xsltTransformContext
247        }
248    }
249
250    #[test]
251    fn test_register_and_find_function() {
252        unsafe {
253            let ctxt = make_ctxt();
254            extern "C" fn dummy(_ctx: *mut c_void, _n: c_int) {}
255            assert_eq!(
256                xsltRegisterExtFunction(
257                    ctxt,
258                    b"myfunc\0".as_ptr() as *const xmlChar,
259                    b"http://example.com/ext\0".as_ptr() as *const xmlChar,
260                    Some(dummy),
261                ),
262                0
263            );
264            let found = xsltFindExtFunction(
265                ctxt,
266                b"myfunc\0".as_ptr() as *const xmlChar,
267                b"http://example.com/ext\0".as_ptr() as *const xmlChar,
268            );
269            assert_eq!(found, dummy as *mut c_void);
270            let not_found = xsltFindExtFunction(
271                ctxt,
272                b"other\0".as_ptr() as *const xmlChar,
273                b"http://example.com/ext\0".as_ptr() as *const xmlChar,
274            );
275            assert!(not_found.is_null());
276            xsltFreeExts(ctxt);
277            libc::free(ctxt as *mut libc::c_void);
278        }
279    }
280
281    #[test]
282    fn test_register_null() {
283        unsafe {
284            assert_eq!(
285                xsltRegisterExtFunction(ptr::null_mut(), ptr::null(), ptr::null(), None),
286                -1
287            );
288            assert_eq!(
289                xsltRegisterExtElement(ptr::null_mut(), ptr::null(), ptr::null(), None),
290                -1
291            );
292        }
293    }
294}