Skip to main content

libxml_rs/abi/
exports_buffer.rs

1//! C ABI exports for the buffer family — xmlBuf* / xmlBuffer* / xmlCharStr*
2//! (upstream buf.c, tree.c, xmlstring.c, 2.15.3).
3//!
4//! In 2.15 `xmlBuf` and `xmlBuffer` are the same C struct (typedef xmlBuffer
5//! xmlBuf), but the candidate mirrors them as two distinct structs
6//! (`_xmlBuffer` with `{content, use_, size, alloc, contentIO}` and `_xmlBuf`
7//! with `{content, use_, size, alloc, error, buffer, io}`). The `xmlBuf*`
8//! exports therefore operate on the `_xmlBuf` fields directly, allocating
9//! through the candidate allocator (`xmlMalloc`/`xmlFree`), exactly as
10//! upstream manipulates `buf->content`/`buf->use`/`buf->size`.
11
12#![allow(
13    missing_docs,
14    non_snake_case,
15    non_camel_case_types,
16    non_upper_case_globals
17)]
18
19use core::ptr;
20use std::os::raw::{c_char, c_int, c_uint, c_void};
21
22use crate::abi::allocator::{xmlFreeImpl, xmlMallocImpl};
23use crate::abi::structs::{_xmlBuf, _xmlBuffer, _xmlDoc, _xmlNode, _xmlNs};
24use crate::abi::types::{xmlChar, xmlElementType, XML_ERR_ARGUMENT, XML_ERR_NO_MEMORY, XML_ERR_OK};
25use crate::xml::io;
26use crate::xml::tree;
27
28// ── libc FILE* plumbing for xmlBufferDump ───────────────────────────────
29//
30// The FILE* is opaque at the ABI boundary and is passed as *mut c_void.
31// fwrite(3) is declared here rather than pulled from the libc crate so the
32// dependency stays explicit; `stdout` is the libc data symbol used by
33// upstream's `if (file == NULL) file = stdout;` fallback.
34
35extern "C" {
36    /// libc `size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)`.
37    fn fwrite(ptr: *const c_void, size: usize, nmemb: usize, stream: *mut c_void) -> usize;
38    /// The libc `FILE *stdout` variable.
39    static mut stdout: *mut c_void;
40}
41
42// ═══════════════════════════════════════════════════════════════════════════════
43// 1. xmlBuf operations (modern replacement, tree.h)
44// ═══════════════════════════════════════════════════════════════════════════════
45
46/// Get pointer into buffer content.
47///
48/// # UPSTREAM-PARITY
49///
50/// ```c
51/// xmlChar *xmlBufContent(const xmlBuf *buf);
52/// ```
53///
54/// buf.c 2.15: returns `buf->content`, or NULL when `buf` is NULL or the
55/// buffer is in error state (`BUF_ERROR`).
56///
57/// # SAFETY
58///
59/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
60/// - The returned pointer is owned by `buf` and must not be freed by the
61///   caller.
62#[no_mangle]
63pub unsafe extern "C" fn xmlBufContent(buf: *const _xmlBuf) -> *mut xmlChar {
64    if buf.is_null() || unsafe { (*buf).error != 0 } {
65        return ptr::null_mut();
66    }
67    unsafe { (*buf).content }
68}
69
70/// Return a pointer to the end of the buffer content.
71///
72/// # UPSTREAM-PARITY
73///
74/// ```c
75/// xmlChar *xmlBufEnd(xmlBuf *buf);
76/// ```
77///
78/// buf.c 2.15: returns `&buf->content[buf->use]`, or NULL when `buf` is NULL
79/// or the buffer is in error state.
80///
81/// # SAFETY
82///
83/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
84/// - The returned pointer is owned by `buf` and must not be freed by the
85///   caller.
86#[no_mangle]
87pub unsafe extern "C" fn xmlBufEnd(buf: *mut _xmlBuf) -> *mut xmlChar {
88    if buf.is_null() || unsafe { (*buf).error != 0 } {
89        return ptr::null_mut();
90    }
91    unsafe { (*buf).content.add((*buf).use_ as usize) }
92}
93
94/// Append the string value of a node to `buf`.
95///
96/// For text/CDATA/comment/PI nodes the string value is the node content;
97/// otherwise it is the concatenation of the string values of the node's
98/// descendants, with entity references substituted. Namespace declaration
99/// nodes contribute their href.
100///
101/// # UPSTREAM-PARITY
102///
103/// ```c
104/// int xmlBufGetNodeContent(xmlBuf *buf, const xmlNode *cur);
105/// ```
106///
107/// tree.c 2.15: returns -1 only when `cur` or `buf` is NULL; otherwise 0
108/// (append failures are ignored upstream and here, matching the void
109/// contract).
110///
111/// # SAFETY
112///
113/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
114/// - `cur` must be a valid pointer to a `_xmlNode`/`_xmlNs` (or NULL).
115#[no_mangle]
116pub unsafe extern "C" fn xmlBufGetNodeContent(buf: *mut _xmlBuf, cur: *const _xmlNode) -> c_int {
117    if cur.is_null() || buf.is_null() {
118        return -1;
119    }
120
121    // Upstream xmlBufGetNodeContent appends the namespace href for
122    // XML_NAMESPACE_DECL nodes; node_get_content has no arm for them.
123    if unsafe { (*cur).type_ } == xmlElementType::XML_NAMESPACE_DECL as c_int {
124        let ns = cur as *const _xmlNs;
125        if !unsafe { (*ns).href }.is_null() {
126            io::xml_buf_cat(buf, unsafe { (*ns).href });
127        }
128        return 0;
129    }
130
131    // node_get_content mirrors tree.c xmlNodeGetContent: recursive string
132    // value of the node (text descendants, entity expansion, attribute
133    // value, comment/PI content).
134    let content = unsafe { tree::node_get_content(cur as *mut _xmlNode) };
135    if content.is_null() {
136        // Allocation failure: nothing was appended; upstream still returns 0.
137        return 0;
138    }
139    io::xml_buf_cat(buf, content);
140    unsafe { xmlFreeImpl(content as *mut c_void) };
141    0
142}
143
144/// Serialize an XML node to an xmlBuf.
145///
146/// # UPSTREAM-PARITY
147///
148/// ```c
149/// size_t xmlBufNodeDump(xmlBuf *buf, xmlDoc *doc, xmlNode *cur, int level, int format);
150/// ```
151///
152/// xmlsave.c/tree.c 2.15: returns the number of bytes written to `buf`, or
153/// `(size_t)-1` when `buf`/`cur` is NULL or serialization fails. The level
154/// is clamped to [0, 100] (xmlNodeDumpOutput). `doc` is ignored, matching
155/// upstream's `(void) doc`.
156///
157/// # SAFETY
158///
159/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
160/// - `cur` must be a valid pointer to a `_xmlNode` (or NULL).
161/// - `doc` is unused and may be NULL.
162#[no_mangle]
163pub unsafe extern "C" fn xmlBufNodeDump(
164    buf: *mut _xmlBuf,
165    doc: *mut _xmlDoc,
166    cur: *mut _xmlNode,
167    level: c_int,
168    format: c_int,
169) -> usize {
170    let _ = doc;
171    if cur.is_null() || buf.is_null() {
172        return usize::MAX; // (size_t)-1
173    }
174    let level = level.clamp(0, 100);
175
176    // Serialize through a temporary xmlBuffer (the candidate serializer
177    // targets _xmlBuffer), then append the serialized bytes to the xmlBuf.
178    let tmp = io::buf_create(-1);
179    if tmp.is_null() {
180        return usize::MAX;
181    }
182    tree::serialize_node_opts(cur, tmp, format, level, ptr::null(), 0);
183
184    let len = io::buf_length(tmp);
185    let content = io::buf_content(tmp);
186    let written = if len > 0 && !content.is_null() {
187        io::xml_buf_add(buf, content, len)
188    } else {
189        0
190    };
191    io::buf_free(tmp);
192
193    if written < 0 {
194        usize::MAX
195    } else {
196        written as usize
197    }
198}
199
200/// Discard bytes at the start of a buffer.
201///
202/// # UPSTREAM-PARITY
203///
204/// ```c
205/// size_t xmlBufShrink(xmlBuf *buf, size_t len);
206/// ```
207///
208/// buf.c 2.15: removes `len` bytes from the front by advancing
209/// `buf->content` and decreasing `buf->use`/`buf->size`; returns the number
210/// of bytes removed, or 0 when `buf` is NULL, the buffer is in error state,
211/// `len` is 0, or `len` exceeds `buf->use`. Unlike `xmlBufferShrink`, errors
212/// return 0 rather than -1 (size_t return type).
213///
214/// # SAFETY
215///
216/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
217#[no_mangle]
218pub unsafe extern "C" fn xmlBufShrink(buf: *mut _xmlBuf, len: usize) -> usize {
219    if buf.is_null() || unsafe { (*buf).error != 0 } {
220        return 0;
221    }
222    if len == 0 {
223        return 0;
224    }
225    let b = unsafe { &mut *buf };
226    if len > b.use_ as usize {
227        return 0;
228    }
229    b.use_ -= len as c_uint;
230    b.content = unsafe { b.content.add(len) };
231    b.size -= len as c_uint;
232    len
233}
234
235/// Return the size of the buffer content.
236///
237/// # UPSTREAM-PARITY
238///
239/// ```c
240/// size_t xmlBufUse(xmlBuf *buf);
241/// ```
242///
243/// buf.c 2.15: returns `buf->use`, or 0 when `buf` is NULL or the buffer is
244/// in error state.
245///
246/// # SAFETY
247///
248/// - `buf` must be a valid pointer to a `_xmlBuf` (or NULL).
249#[no_mangle]
250pub unsafe extern "C" fn xmlBufUse(buf: *mut _xmlBuf) -> usize {
251    if buf.is_null() || unsafe { (*buf).error != 0 } {
252        return 0;
253    }
254    unsafe { (*buf).use_ as usize }
255}
256
257// ═══════════════════════════════════════════════════════════════════════════════
258// 2. xmlBuffer operations (deprecated, tree.h)
259// ═══════════════════════════════════════════════════════════════════════════════
260
261/// Append a zero-terminated C string to a buffer.
262///
263/// # UPSTREAM-PARITY
264///
265/// ```c
266/// int xmlBufferCCat(xmlBuffer *buf, const char *str);
267/// ```
268///
269/// buf.c 2.15: forwards to `xmlBufferAdd(buf, (const xmlChar *) str, -1)`,
270/// returning XML_ERR_ARGUMENT when `buf` or `str` is NULL, XML_ERR_OK on
271/// success (including the empty string), and XML_ERR_NO_MEMORY when growth
272/// fails.
273///
274/// # SAFETY
275///
276/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
277/// - `str` must be a valid NUL-terminated C string (or NULL).
278#[no_mangle]
279pub unsafe extern "C" fn xmlBufferCCat(buf: *mut _xmlBuffer, str: *const c_char) -> c_int {
280    if buf.is_null() || str.is_null() {
281        return XML_ERR_ARGUMENT;
282    }
283    let ret = io::buf_cat(buf, str as *const xmlChar);
284    if ret < 0 {
285        XML_ERR_NO_MEMORY
286    } else {
287        XML_ERR_OK
288    }
289}
290
291/// Dump a buffer to a `FILE`.
292///
293/// # UPSTREAM-PARITY
294///
295/// ```c
296/// int xmlBufferDump(FILE *file, xmlBuffer *buf);
297/// ```
298///
299/// buf.c 2.15: returns 0 when `buf` is NULL or has no content, defaults a
300/// NULL `file` to stdout, and otherwise returns the `fwrite` byte count
301/// clamped to INT_MAX. Upstream never returns -1 from this function; write
302/// errors surface as a short count.
303///
304/// # SAFETY
305///
306/// - `file` must be a valid `FILE*` or NULL (then stdout is used).
307/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
308#[no_mangle]
309pub unsafe extern "C" fn xmlBufferDump(file: *mut c_void, buf: *mut _xmlBuffer) -> c_int {
310    if buf.is_null() {
311        return 0;
312    }
313    let b = unsafe { &*buf };
314    if b.content.is_null() {
315        return 0;
316    }
317    let stream = if file.is_null() {
318        // Upstream: `if (file == NULL) file = stdout;`
319        unsafe { stdout }
320    } else {
321        file
322    };
323    let n = unsafe { fwrite(b.content as *const c_void, 1, b.use_ as usize, stream) };
324    if n > c_int::MAX as usize {
325        c_int::MAX
326    } else {
327        n as c_int
328    }
329}
330
331/// Resize a buffer to a minimum size.
332///
333/// # UPSTREAM-PARITY
334///
335/// ```c
336/// int xmlBufferResize(xmlBuffer *buf, unsigned int size);
337/// ```
338///
339/// buf.c 2.15: returns 0 when `buf` is NULL, 1 when `size` is below the
340/// current capacity, otherwise grows the buffer so its total capacity is at
341/// least `size` and returns 1 on success / 0 on allocation failure.
342///
343/// # SAFETY
344///
345/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
346#[no_mangle]
347pub unsafe extern "C" fn xmlBufferResize(buf: *mut _xmlBuffer, size: c_uint) -> c_int {
348    if buf.is_null() {
349        return 0;
350    }
351    if size < unsafe { (*buf).size } {
352        return 1;
353    }
354    let res = io::buf_grow(buf, size);
355    if res < 0 {
356        0
357    } else {
358        1
359    }
360}
361
362/// Append a zero-terminated `xmlChar` string to a buffer.
363///
364/// # UPSTREAM-PARITY
365///
366/// ```c
367/// void xmlBufferWriteCHAR(xmlBuffer *buf, const xmlChar *string);
368/// ```
369///
370/// buf.c 2.15: `xmlBufferAdd(buf, string, -1)`, i.e. append the
371/// NUL-terminated string; failures are ignored (void return).
372///
373/// # SAFETY
374///
375/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
376/// - `string` must be a valid NUL-terminated xmlChar string (or NULL).
377#[no_mangle]
378pub unsafe extern "C" fn xmlBufferWriteCHAR(buf: *mut _xmlBuffer, string: *const xmlChar) {
379    let _ = io::buf_cat(buf, string);
380}
381
382/// Append a zero-terminated C string to a buffer.
383///
384/// Same as `xmlBufferCCat`.
385///
386/// # UPSTREAM-PARITY
387///
388/// ```c
389/// void xmlBufferWriteChar(xmlBuffer *buf, const char *string);
390/// ```
391///
392/// buf.c 2.15: `xmlBufferAdd(buf, (const xmlChar *) string, -1)`; failures
393/// are ignored (void return).
394///
395/// # SAFETY
396///
397/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
398/// - `string` must be a valid NUL-terminated C string (or NULL).
399#[no_mangle]
400pub unsafe extern "C" fn xmlBufferWriteChar(buf: *mut _xmlBuffer, string: *const c_char) {
401    let _ = io::buf_cat(buf, string as *const xmlChar);
402}
403
404/// Append a quoted string to a buffer.
405///
406/// Appends the string wrapped in quotes. If the string contains both single
407/// and double quotes, double quotes are escaped with `&quot;` (upstream
408/// buf.c 2.15; no backslash or CR/LF escaping exists in this version).
409///
410/// # UPSTREAM-PARITY
411///
412/// ```c
413/// void xmlBufferWriteQuotedString(xmlBuffer *buf, const xmlChar *string);
414/// ```
415///
416/// buf.c 2.15: with a double-quote-containing string that also contains a
417/// single quote, emits `"` + string with `"` → `&quot;` + `"`; with only
418/// double quotes emits `'` + string + `'`; otherwise emits `"` + string +
419/// `"`. A NULL string yields the bare quote pair, exactly like upstream
420/// (xmlStrchr(NULL) misses, xmlBufferCat(NULL) fails silently).
421///
422/// # SAFETY
423///
424/// - `buf` must be a valid pointer to a `_xmlBuffer` (or NULL).
425/// - `string` must be a valid NUL-terminated xmlChar string (or NULL).
426#[no_mangle]
427pub unsafe extern "C" fn xmlBufferWriteQuotedString(buf: *mut _xmlBuffer, string: *const xmlChar) {
428    if buf.is_null() {
429        return;
430    }
431    let has_dquote = !crate::abi::exports_xml2::xmlStrchr(string, b'"').is_null();
432    let has_squote = !crate::abi::exports_xml2::xmlStrchr(string, b'\'').is_null();
433    if has_dquote {
434        if has_squote {
435            // Escape every double quote with &quot; inside double quotes.
436            io::buf_cat(buf, b"\"".as_ptr());
437            let mut base = string;
438            let mut cur = string;
439            while !cur.is_null() && unsafe { *cur } != 0 {
440                if unsafe { *cur } == b'"' {
441                    if base != cur {
442                        let n = cur.offset_from(base) as c_int;
443                        io::buf_add(buf, base, n);
444                    }
445                    io::buf_cat(buf, b"&quot;".as_ptr());
446                    cur = cur.add(1);
447                    base = cur;
448                } else {
449                    cur = cur.add(1);
450                }
451            }
452            if base != cur {
453                let n = cur.offset_from(base) as c_int;
454                io::buf_add(buf, base, n);
455            }
456            io::buf_cat(buf, b"\"".as_ptr());
457        } else {
458            // Only double quotes: single-quote the string.
459            io::buf_cat(buf, b"\'".as_ptr());
460            io::buf_cat(buf, string);
461            io::buf_cat(buf, b"\'".as_ptr());
462        }
463    } else {
464        io::buf_cat(buf, b"\"".as_ptr());
465        io::buf_cat(buf, string);
466        io::buf_cat(buf, b"\"".as_ptr());
467    }
468}
469
470// ═══════════════════════════════════════════════════════════════════════════════
471// 3. xmlChar string duplication (xmlstring.h)
472// ═══════════════════════════════════════════════════════════════════════════════
473
474/// Duplicate a `char *` string to a new `xmlChar *` string.
475///
476/// # UPSTREAM-PARITY
477///
478/// ```c
479/// xmlChar *xmlCharStrdup(const char *cur);
480/// ```
481///
482/// xmlstring.c 2.15: returns NULL for a NULL input, otherwise
483/// `xmlCharStrndup(cur, strlen(cur))`.
484///
485/// # SAFETY
486///
487/// - `cur` must be a valid NUL-terminated C string or NULL.
488/// - The returned pointer is allocated with `xmlMalloc` and must be freed
489///   with `xmlFree`.
490#[no_mangle]
491pub unsafe extern "C" fn xmlCharStrdup(cur: *const c_char) -> *mut xmlChar {
492    if cur.is_null() {
493        return ptr::null_mut();
494    }
495    let len = unsafe { libc::strlen(cur) } as c_int;
496    unsafe { xmlCharStrndup(cur, len) }
497}
498
499/// Duplicate `len` bytes of a `char *` string to a new `xmlChar *` string.
500///
501/// # UPSTREAM-PARITY
502///
503/// ```c
504/// xmlChar *xmlCharStrndup(const char *cur, int len);
505/// ```
506///
507/// xmlstring.c 2.15: returns NULL when `cur` is NULL or `len` is negative;
508/// otherwise allocates `len + 1` bytes, copies at most `len` bytes, stops
509/// early (and returns immediately) if an embedded NUL is copied, and always
510/// NUL-terminates.
511///
512/// # SAFETY
513///
514/// - `cur` must be a valid pointer to at least `len` readable bytes or NULL.
515/// - The returned pointer is allocated with `xmlMalloc` and must be freed
516///   with `xmlFree`.
517#[no_mangle]
518pub unsafe extern "C" fn xmlCharStrndup(cur: *const c_char, len: c_int) -> *mut xmlChar {
519    if cur.is_null() || len < 0 {
520        return ptr::null_mut();
521    }
522    let ret = unsafe { xmlMallocImpl(len as usize + 1) } as *mut xmlChar;
523    if ret.is_null() {
524        return ptr::null_mut();
525    }
526    unsafe {
527        let mut i: usize = 0;
528        while i < len as usize {
529            let byte = *cur.add(i) as xmlChar;
530            *ret.add(i) = byte;
531            if byte == 0 {
532                return ret; // Embedded NUL: already terminated.
533            }
534            i += 1;
535        }
536        *ret.add(len as usize) = 0;
537    }
538    ret
539}