libxml-rs 0.1.0-alpha.13

Phase 11: Historical matrix — semantic epochs. Native-Rust forensic reimplementation of libxml2+libxslt with C ABI drop-in replacement. Cross-version oracle matrix (libxml2 2.7.8-2.15.3, libxslt 1.1.26-1.1.45) with semantic epochs E-001..E-008 correlated to upstream commits (xpath output 2.9.10, parser diagnostics 2.9.11/2.12, exit-code + entity-text epochs 2.13.0, html/valid 2.15.0), historical residual fingerprints, full xmllint/xmlcatalog/xsltproc CLIs, 1110 tests passing.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
//! XSLT security preferences (§33, §85 Phase 8).
//!
//! Implements the xsltSecurityPrefs API for controlling what operations
//! are permitted during XSLT transformations.
//!
//! # Upstream mapping
//!
//! | Function | Header |
//! |---|---|
//! | `xsltNewSecurityPrefs` | xslt.h |
//! | `xsltFreeSecurityPrefs` | xslt.h |
//! | `xsltSetSecurityPrefs` | xslt.h |
//! | `xsltGetSecurityPrefs` | xslt.h |
//! | `xsltSetDefaultSecurityPrefs` | xslt.h |
//! | `xsltGetDefaultSecurityPrefs` | xslt.h |

#![allow(
    clippy::missing_inline_in_public_items,
    clippy::must_use_candidate,
    clippy::missing_safety_doc
)]

use crate::abi::structs::*;
use crate::abi::types::*;
use std::ffi::c_void;
use std::os::raw::c_int;
use std::ptr;
use std::sync::Mutex;

/// Security option: read a file from the filesystem.
pub const XSLT_SECPREF_READ_FILE: c_int = 1;

/// Security option: write a file to the filesystem.
pub const XSLT_SECPREF_WRITE_FILE: c_int = 2;

/// Security option: create a directory on the filesystem.
pub const XSLT_SECPREF_CREATE_DIRECTORY: c_int = 3;

/// Security option: read a network resource.
pub const XSLT_SECPREF_READ_NETWORK: c_int = 4;

/// Security option: write to a network resource.
pub const XSLT_SECPREF_WRITE_NETWORK: c_int = 5;

/// Default security preference value (alias for [`XSLT_SECPREF_DENY`]).
pub const XSLT_SECPREF_DEFAULT: c_int = 0;

/// Security preference value: deny the operation.
pub const XSLT_SECPREF_DENY: c_int = 0;

/// Security preference value: allow the operation.
pub const XSLT_SECPREF_ALLOW: c_int = 1;

/// Internal security preferences structure.
///
/// Stores the current allow/deny setting for each of the five
/// controllable security options.
#[repr(C)]
pub struct XsltSecurityPrefs {
    /// Allow reading files from the filesystem.
    pub readFile: c_int,
    /// Allow writing files to the filesystem.
    pub writeFile: c_int,
    /// Allow creating directories on the filesystem.
    pub createDirectory: c_int,
    /// Allow reading network resources.
    pub readNetwork: c_int,
    /// Allow writing to network resources.
    pub writeNetwork: c_int,
}

/// Wrapper around `*mut c_void` that implements `Send` so it can be stored
/// in a `Mutex`.
///
/// # Safety
///
/// The caller is responsible for ensuring that the pointed-to value is
/// accessed in a thread-safe manner. The global default is only ever written
/// or read through the `Mutex` guard, so concurrent access is serialized.
#[repr(transparent)]
struct SecurityPrefsPtr(*mut c_void);

// SAFETY: Access to the wrapped pointer is serialized via Mutex, making
// it safe to send between threads.
unsafe impl Send for SecurityPrefsPtr {}

/// Global default security preferences, stored as a raw pointer behind a
/// [`Mutex`] for thread-safe access.
static DEFAULT_SECURITY_PREFS: Mutex<Option<SecurityPrefsPtr>> = Mutex::new(None);

/// Create new security preferences with default (allow) settings.
///
/// Returns a raw pointer to a heap-allocated [`XsltSecurityPrefs`] with all
/// options set to [`XSLT_SECPREF_ALLOW`]. The caller is responsible for
/// freeing the returned pointer via [`xsltFreeSecurityPrefs`].
///
/// # Returns
///
/// A non-null pointer to the newly allocated security preferences on success.
///
/// # Safety
///
/// The caller must ensure the returned pointer is eventually freed with
/// [`xsltFreeSecurityPrefs`] to avoid memory leaks.
#[no_mangle]
pub unsafe extern "C" fn xsltNewSecurityPrefs() -> *mut c_void {
    let prefs = Box::new(XsltSecurityPrefs {
        readFile: XSLT_SECPREF_ALLOW,
        writeFile: XSLT_SECPREF_ALLOW,
        createDirectory: XSLT_SECPREF_ALLOW,
        readNetwork: XSLT_SECPREF_ALLOW,
        writeNetwork: XSLT_SECPREF_ALLOW,
    });
    Box::into_raw(prefs) as *mut c_void
}

/// Free security preferences previously allocated by [`xsltNewSecurityPrefs`].
///
/// # Safety
///
/// - `sec` must be a pointer returned by [`xsltNewSecurityPrefs`] that has
///   not yet been freed.
/// - After this call, `sec` is dangling and must not be dereferenced.
#[no_mangle]
pub unsafe extern "C" fn xsltFreeSecurityPrefs(sec: *mut c_void) {
    if !sec.is_null() {
        let _ = Box::from_raw(sec as *mut XsltSecurityPrefs);
    }
}

/// Set a security preference for the given options structure.
///
/// # Arguments
///
/// * `sec` - Pointer to security preferences (must be non-null).
/// * `option` - One of `XSLT_SECPREF_READ_FILE`, `XSLT_SECPREF_WRITE_FILE`,
///   `XSLT_SECPREF_CREATE_DIRECTORY`, `XSLT_SECPREF_READ_NETWORK`, or
///   `XSLT_SECPREF_WRITE_NETWORK`.
/// * `value` - The value to set (typically [`XSLT_SECPREF_ALLOW`] or
///   [`XSLT_SECPREF_DENY`]).
///
/// # Returns
///
/// `0` on success, or `-1` if `sec` is null or `option` is invalid.
///
/// # Safety
///
/// `sec` must point to a valid [`XsltSecurityPrefs`] structure obtained from
/// [`xsltNewSecurityPrefs`] that has not yet been freed.
#[no_mangle]
pub unsafe extern "C" fn xsltSetSecurityPrefs(
    sec: *mut c_void,
    option: c_int,
    value: c_int,
) -> c_int {
    if sec.is_null() {
        return -1;
    }
    let prefs = &mut *(sec as *mut XsltSecurityPrefs);
    match option {
        XSLT_SECPREF_READ_FILE => prefs.readFile = value,
        XSLT_SECPREF_WRITE_FILE => prefs.writeFile = value,
        XSLT_SECPREF_CREATE_DIRECTORY => prefs.createDirectory = value,
        XSLT_SECPREF_READ_NETWORK => prefs.readNetwork = value,
        XSLT_SECPREF_WRITE_NETWORK => prefs.writeNetwork = value,
        _ => return -1,
    }
    0
}

/// Get the current value of a security preference.
///
/// # Arguments
///
/// * `sec` - Pointer to security preferences. If null, returns
///   [`XSLT_SECPREF_DENY`] for all options.
/// * `option` - One of `XSLT_SECPREF_READ_FILE`, `XSLT_SECPREF_WRITE_FILE`,
///   `XSLT_SECPREF_CREATE_DIRECTORY`, `XSLT_SECPREF_READ_NETWORK`, or
///   `XSLT_SECPREF_WRITE_NETWORK`.
///
/// # Returns
///
/// The current preference value, or [`XSLT_SECPREF_DENY`] if `sec` is null
/// or `option` is invalid.
///
/// # Safety
///
/// If `sec` is non-null, it must point to a valid [`XsltSecurityPrefs`]
/// structure obtained from [`xsltNewSecurityPrefs`] that has not yet been
/// freed.
#[no_mangle]
pub unsafe extern "C" fn xsltGetSecurityPrefs(sec: *mut c_void, option: c_int) -> c_int {
    if sec.is_null() {
        return XSLT_SECPREF_DENY;
    }
    let prefs = &*(sec as *mut XsltSecurityPrefs);
    match option {
        XSLT_SECPREF_READ_FILE => prefs.readFile,
        XSLT_SECPREF_WRITE_FILE => prefs.writeFile,
        XSLT_SECPREF_CREATE_DIRECTORY => prefs.createDirectory,
        XSLT_SECPREF_READ_NETWORK => prefs.readNetwork,
        XSLT_SECPREF_WRITE_NETWORK => prefs.writeNetwork,
        _ => XSLT_SECPREF_DENY,
    }
}

/// Set the default security preferences used by new transformations.
///
/// The provided pointer is stored as the global default. It is the caller's
/// responsibility to manage the lifetime of the pointed-to preferences.
///
/// # Safety
///
/// `sec` must point to a valid [`XsltSecurityPrefs`] structure that remains
/// valid for the duration it is set as the default (i.e., until replaced by
/// another call to this function).
#[no_mangle]
pub unsafe extern "C" fn xsltSetDefaultSecurityPrefs(sec: *mut c_void) {
    let mut guard = DEFAULT_SECURITY_PREFS.lock().unwrap();
    *guard = Some(SecurityPrefsPtr(sec));
}

/// Get the current default security preferences.
///
/// # Returns
///
/// A pointer to the default security preferences previously set with
/// [`xsltSetDefaultSecurityPrefs`], or a null pointer if none have been set.
///
/// # Safety
///
/// The returned pointer is only valid as long as no other call to
/// [`xsltSetDefaultSecurityPrefs`] has replaced it and the original
/// [`XsltSecurityPrefs`] has not been freed.
#[no_mangle]
pub unsafe extern "C" fn xsltGetDefaultSecurityPrefs() -> *mut c_void {
    let guard = DEFAULT_SECURITY_PREFS.lock().unwrap();
    guard.as_ref().map_or(ptr::null_mut(), |p| p.0)
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Verify that creating and freeing security prefs works.
    #[test]
    fn test_new_free_security_prefs() {
        unsafe {
            let prefs = xsltNewSecurityPrefs();
            assert!(!prefs.is_null());
            xsltFreeSecurityPrefs(prefs);
        }
    }

    /// Verify that freeing a null pointer is a no-op.
    #[test]
    fn test_free_null() {
        unsafe {
            // Should not panic or crash.
            xsltFreeSecurityPrefs(ptr::null_mut());
        }
    }

    /// Verify that newly created prefs have all options set to ALLOW.
    #[test]
    fn test_new_prefs_defaults_allow() {
        unsafe {
            let prefs = xsltNewSecurityPrefs();
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE),
                XSLT_SECPREF_ALLOW
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE),
                XSLT_SECPREF_ALLOW
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY),
                XSLT_SECPREF_ALLOW
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK),
                XSLT_SECPREF_ALLOW
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK),
                XSLT_SECPREF_ALLOW
            );
            xsltFreeSecurityPrefs(prefs);
        }
    }

    /// Verify that setting and getting each option round-trips correctly.
    #[test]
    fn test_set_and_get() {
        unsafe {
            let prefs = xsltNewSecurityPrefs();

            // Set all to DENY
            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE, XSLT_SECPREF_DENY),
                0
            );
            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE, XSLT_SECPREF_DENY),
                0
            );
            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY, XSLT_SECPREF_DENY),
                0
            );
            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK, XSLT_SECPREF_DENY),
                0
            );
            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK, XSLT_SECPREF_DENY),
                0
            );

            // Verify all are DENY
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE),
                XSLT_SECPREF_DENY
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE),
                XSLT_SECPREF_DENY
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY),
                XSLT_SECPREF_DENY
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK),
                XSLT_SECPREF_DENY
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK),
                XSLT_SECPREF_DENY
            );

            // Set each individually back to ALLOW
            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE, XSLT_SECPREF_ALLOW),
                0
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_FILE),
                XSLT_SECPREF_ALLOW
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE),
                XSLT_SECPREF_DENY
            );

            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE, XSLT_SECPREF_ALLOW),
                0
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_FILE),
                XSLT_SECPREF_ALLOW
            );

            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY, XSLT_SECPREF_ALLOW),
                0
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_CREATE_DIRECTORY),
                XSLT_SECPREF_ALLOW
            );

            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK, XSLT_SECPREF_ALLOW),
                0
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_READ_NETWORK),
                XSLT_SECPREF_ALLOW
            );

            assert_eq!(
                xsltSetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK, XSLT_SECPREF_ALLOW),
                0
            );
            assert_eq!(
                xsltGetSecurityPrefs(prefs, XSLT_SECPREF_WRITE_NETWORK),
                XSLT_SECPREF_ALLOW
            );

            xsltFreeSecurityPrefs(prefs);
        }
    }

    /// Verify that setting/getting with null pointer returns error/default.
    #[test]
    fn test_null_pointer() {
        unsafe {
            assert_eq!(
                xsltSetSecurityPrefs(ptr::null_mut(), XSLT_SECPREF_READ_FILE, XSLT_SECPREF_ALLOW),
                -1
            );
            assert_eq!(
                xsltGetSecurityPrefs(ptr::null_mut(), XSLT_SECPREF_READ_FILE),
                XSLT_SECPREF_DENY
            );
        }
    }

    /// Verify that an invalid option returns error/default.
    #[test]
    fn test_invalid_option() {
        unsafe {
            let prefs = xsltNewSecurityPrefs();
            assert_eq!(xsltSetSecurityPrefs(prefs, 99, XSLT_SECPREF_ALLOW), -1);
            assert_eq!(xsltGetSecurityPrefs(prefs, 99), XSLT_SECPREF_DENY);
            xsltFreeSecurityPrefs(prefs);
        }
    }

    /// Verify that default security prefs set/get round-trip correctly.
    #[test]
    fn test_default_security_prefs() {
        unsafe {
            // Initially null
            assert!(xsltGetDefaultSecurityPrefs().is_null());

            // Create prefs and set as default
            let prefs = xsltNewSecurityPrefs();
            xsltSetDefaultSecurityPrefs(prefs);

            // Retrieve and verify
            let retrieved = xsltGetDefaultSecurityPrefs();
            assert_eq!(retrieved, prefs);

            // Verify we can read from the default
            assert_eq!(
                xsltGetSecurityPrefs(retrieved, XSLT_SECPREF_READ_FILE),
                XSLT_SECPREF_ALLOW
            );

            // Clear the default by setting null
            xsltSetDefaultSecurityPrefs(ptr::null_mut());
            assert!(xsltGetDefaultSecurityPrefs().is_null());

            // Free the original prefs
            xsltFreeSecurityPrefs(prefs);
        }
    }
}