Skip to main content

libxml_rs/xml/io/
mod.rs

1//! Custom I/O and resource loaders (§59, §85 Phase 4).
2//!
3//! Input/output callback infrastructure: buffers, file I/O, encoding
4//! integration, input/output buffer management, and helper utilities.
5//!
6//! This module implements the libxml2 I/O subsystem in native Rust.
7//! No C dependencies beyond `libc` for file operations.
8
9#![allow(
10    clippy::cast_possible_truncation,
11    clippy::cast_sign_loss,
12    clippy::cast_ptr_alignment,
13    clippy::missing_safety_doc
14)]
15
16use std::ffi::CStr;
17use std::os::raw::{c_char, c_int, c_uint, c_ulong, c_void};
18use std::ptr;
19
20use libc;
21
22use crate::abi::allocator::{xmlFree, xmlMalloc, xmlRealloc};
23use crate::abi::callbacks::{
24    xmlInputCloseCallback, xmlInputReadCallback, xmlOutputCloseCallback, xmlOutputWriteCallback,
25};
26use crate::abi::structs::{
27    _xmlBuf, _xmlBuffer, _xmlCharEncodingHandler, _xmlOutputBuffer, _xmlParserInputBuffer,
28};
29use crate::abi::types::{xmlChar, xmlCharEncoding, xmlCharPtr};
30use crate::xml::encoding;
31
32// ═══════════════════════════════════════════════════════════════════════════════
33// Constants
34// ═══════════════════════════════════════════════════════════════════════════════
35
36/// Default buffer size for new buffers.
37const DEFAULT_BUFFER_SIZE: c_uint = 4000;
38
39/// Minimum buffer size.
40const MIN_BUFFER_SIZE: c_uint = 256;
41
42/// Buffer allocation scheme: double the size on growth.
43const XML_BUFFER_ALLOC_DOUBLEIT: c_int = 0;
44
45/// Buffer allocation scheme: exact size on growth.
46const XML_BUFFER_ALLOC_EXACT: c_int = 1;
47
48/// Buffer allocation scheme: immutable (no growth, no free of content).
49const XML_BUFFER_ALLOC_IMMUTABLE: c_int = 2;
50
51// ═══════════════════════════════════════════════════════════════════════════════
52// 1. xmlBuffer operations (deprecated)
53// ═══════════════════════════════════════════════════════════════════════════════
54
55/// Create a new xmlBuffer with the given initial size.
56///
57/// If `size` <= 0, a default buffer size is used.
58/// The buffer content is initialized to an empty null-terminated string.
59///
60/// Returns a pointer to the new buffer, or NULL on allocation failure.
61pub(crate) fn buf_create(size: c_int) -> *mut _xmlBuffer {
62    let buf_size = if size <= 0 {
63        DEFAULT_BUFFER_SIZE
64    } else {
65        size as c_uint
66    };
67
68    // Ensure minimum size
69    let buf_size = buf_size.max(MIN_BUFFER_SIZE);
70
71    let buf = unsafe { xmlMalloc(size_of::<_xmlBuffer>()) as *mut _xmlBuffer };
72    if buf.is_null() {
73        return ptr::null_mut();
74    }
75
76    let content = unsafe { xmlMalloc(buf_size as usize) as *mut xmlChar };
77    if content.is_null() {
78        unsafe { xmlFree(buf as *mut c_void) };
79        return ptr::null_mut();
80    }
81
82    // Initialize: null-terminate the empty buffer
83    unsafe {
84        ptr::write(content, 0);
85    }
86
87    unsafe {
88        ptr::write(
89            buf,
90            _xmlBuffer {
91                content,
92                use_: 0,
93                size: buf_size,
94                alloc: XML_BUFFER_ALLOC_DOUBLEIT,
95                contentIO: content, // Track original allocation for I/O mode
96            },
97        );
98    }
99
100    buf
101}
102
103/// Create a new xmlBuffer from a static string.
104///
105/// The buffer's content points directly to `str` (no copy is made).
106/// The buffer's allocation scheme is set to IMMUTABLE, meaning the
107/// content will not be freed when the buffer is freed.
108///
109/// If `size` <= 0, the length is determined by `xmlStrlen` (scanning for null).
110pub(crate) fn buf_create_static(str: *const xmlChar, size: c_int) -> *mut _xmlBuffer {
111    if str.is_null() {
112        return ptr::null_mut();
113    }
114
115    let len = if size <= 0 {
116        // Calculate length by scanning for null terminator
117        let mut len: c_uint = 0;
118        unsafe {
119            while *str.add(len as usize) != 0 {
120                len += 1;
121            }
122        }
123        len
124    } else {
125        size as c_uint
126    };
127
128    let buf = unsafe { xmlMalloc(size_of::<_xmlBuffer>()) as *mut _xmlBuffer };
129    if buf.is_null() {
130        return ptr::null_mut();
131    }
132
133    unsafe {
134        ptr::write(
135            buf,
136            _xmlBuffer {
137                content: str as *mut xmlChar,
138                use_: len,
139                size: len + 1, // Include space for null terminator
140                alloc: XML_BUFFER_ALLOC_IMMUTABLE,
141                contentIO: ptr::null_mut(),
142            },
143        );
144    }
145
146    buf
147}
148
149/// Free an xmlBuffer.
150///
151/// If the buffer's allocation scheme is not IMMUTABLE, the content is freed.
152/// The contentIO pointer (if non-NULL and different from content) is also freed.
153pub(crate) fn buf_free(buf: *mut _xmlBuffer) {
154    if buf.is_null() {
155        return;
156    }
157
158    unsafe {
159        let alloc = (*buf).alloc;
160        let content = (*buf).content;
161        let content_io = (*buf).contentIO;
162
163        if alloc != XML_BUFFER_ALLOC_IMMUTABLE {
164            // contentIO is the original allocation base (set in I/O mode).
165            // content may have been advanced during reads.
166            // Free the base pointer, not the possibly-advanced content.
167            let base = if !content_io.is_null() {
168                content_io
169            } else {
170                content
171            };
172            if !base.is_null() {
173                xmlFree(base as *mut c_void);
174            }
175        }
176
177        xmlFree(buf as *mut c_void);
178    }
179}
180
181/// Empty an xmlBuffer (reset `use_` to 0).
182///
183/// The content is kept allocated but the first byte is set to null terminator.
184pub(crate) fn buf_empty(buf: *mut _xmlBuffer) {
185    if buf.is_null() {
186        return;
187    }
188
189    unsafe {
190        (*buf).use_ = 0;
191        if !(*buf).content.is_null() {
192            ptr::write((*buf).content, 0);
193        }
194    }
195}
196
197/// Get the content of an xmlBuffer.
198pub(crate) fn buf_content(buf: *mut _xmlBuffer) -> *mut xmlChar {
199    if buf.is_null() {
200        return ptr::null_mut();
201    }
202    unsafe { (*buf).content }
203}
204
205/// Get the used length of an xmlBuffer.
206pub(crate) fn buf_length(buf: *mut _xmlBuffer) -> c_int {
207    if buf.is_null() {
208        return -1;
209    }
210    unsafe { (*buf).use_ as c_int }
211}
212
213/// Write `len` bytes from `str` to an xmlBuffer.
214///
215/// Grows the buffer if needed. Always maintains null termination.
216/// Returns the number of bytes written, or -1 on error.
217pub(crate) fn buf_add(buf: *mut _xmlBuffer, str: *const xmlChar, len: c_int) -> c_int {
218    if buf.is_null() || str.is_null() || len <= 0 {
219        return 0;
220    }
221
222    let len = len as c_uint;
223    let b = unsafe { &mut *buf };
224
225    // IMMUTABLE buffers cannot be written to
226    if b.alloc == XML_BUFFER_ALLOC_IMMUTABLE {
227        return -1;
228    }
229
230    // Ensure capacity: need use_ + len + 1 (for null terminator)
231    let needed = b.use_.saturating_add(len).saturating_add(1);
232    if needed > b.size {
233        // Grow buffer
234        let new_size = if b.alloc == XML_BUFFER_ALLOC_EXACT {
235            needed
236        } else {
237            // DOUBLEIT or default: double until big enough
238            let mut doubled = b.size.saturating_mul(2).max(MIN_BUFFER_SIZE);
239            while doubled < needed {
240                doubled = doubled.saturating_mul(2);
241            }
242            doubled
243        };
244
245        let new_content =
246            unsafe { xmlRealloc(b.content as *mut c_void, new_size as usize) as *mut xmlChar };
247        if new_content.is_null() {
248            return -1;
249        }
250        b.content = new_content;
251        b.contentIO = new_content; // Track reallocated base
252        b.size = new_size;
253    }
254
255    // Copy data
256    unsafe {
257        ptr::copy_nonoverlapping(str, b.content.add(b.use_ as usize), len as usize);
258    }
259    b.use_ = b.use_.saturating_add(len);
260
261    // Null-terminate
262    unsafe {
263        ptr::write(b.content.add(b.use_ as usize), 0);
264    }
265
266    len as c_int
267}
268
269/// Cat a null-terminated string to an xmlBuffer.
270pub(crate) fn buf_cat(buf: *mut _xmlBuffer, str: *const xmlChar) -> c_int {
271    if buf.is_null() || str.is_null() {
272        return -1;
273    }
274
275    // Calculate length of the null-terminated string
276    let len = unsafe {
277        let mut i: c_uint = 0;
278        while *str.add(i as usize) != 0 {
279            i += 1;
280        }
281        i
282    };
283
284    buf_add(buf, str, len as c_int)
285}
286
287/// Write a single character to an xmlBuffer.
288pub(crate) fn buf_ccat(buf: *mut _xmlBuffer, c: xmlChar) -> c_int {
289    buf_add(buf, &c as *const xmlChar, 1)
290}
291
292/// Shrink an xmlBuffer by `len` bytes from the end.
293///
294/// If `len` exceeds the used length, the buffer is emptied.
295/// Returns the new used length, or -1 on error.
296pub(crate) fn buf_shrink(buf: *mut _xmlBuffer, len: c_uint) -> c_int {
297    if buf.is_null() {
298        return -1;
299    }
300
301    let b = unsafe { &mut *buf };
302    if b.use_ == 0 {
303        return 0;
304    }
305
306    b.use_ = if len >= b.use_ { 0 } else { b.use_ - len };
307
308    // Null-terminate
309    unsafe {
310        ptr::write(b.content.add(b.use_ as usize), 0);
311    }
312
313    b.use_ as c_int
314}
315
316/// Grow an xmlBuffer to at least `size` bytes of capacity.
317///
318/// Returns 0 on success, -1 on failure.
319/// Add data at the head of a buffer.
320///
321/// Returns 0 on success, -1 on error.
322pub(crate) fn buf_add_head(buf: *mut _xmlBuffer, str: *const xmlChar, len: c_int) -> c_int {
323    if buf.is_null() || str.is_null() || len <= 0 {
324        return -1;
325    }
326    let len = len as c_uint;
327    unsafe {
328        let b = &mut *buf;
329        let needed = b.use_.saturating_add(len).saturating_add(1);
330        if needed > b.size {
331            let new_size = needed.saturating_mul(2).max(MIN_BUFFER_SIZE);
332            let new_content =
333                xmlRealloc(b.content as *mut c_void, new_size as usize) as *mut xmlChar;
334            if new_content.is_null() {
335                return -1;
336            }
337            b.content = new_content;
338            b.contentIO = new_content;
339            b.size = new_size;
340        }
341        // Shift existing content right by len bytes
342        if b.use_ > 0 {
343            core::ptr::copy(b.content, b.content.add(len as usize), b.use_ as usize);
344        }
345        // Copy new content to the beginning
346        core::ptr::copy_nonoverlapping(str, b.content, len as usize);
347        b.use_ = b.use_.saturating_add(len);
348        *b.content.add(b.use_ as usize) = 0;
349    }
350    0
351}
352
353pub(crate) fn buf_grow(buf: *mut _xmlBuffer, size: c_uint) -> c_int {
354    if buf.is_null() {
355        return -1;
356    }
357
358    let b = unsafe { &mut *buf };
359
360    if size <= b.size {
361        return 0; // Already big enough
362    }
363
364    let new_content =
365        unsafe { xmlRealloc(b.content as *mut c_void, size as usize) as *mut xmlChar };
366    if new_content.is_null() {
367        return -1;
368    }
369
370    b.content = new_content;
371    b.contentIO = new_content;
372    b.size = size;
373
374    0
375}
376
377// ═══════════════════════════════════════════════════════════════════════════════
378// 2. xmlBuf operations (modern replacement)
379// ═══════════════════════════════════════════════════════════════════════════════
380
381/// Create a new xmlBuf with the given initial size.
382///
383/// If `size` <= 0, a default buffer size is used.
384/// Returns a pointer to the new buffer, or NULL on allocation failure.
385pub(crate) fn xml_buf_create(size: c_int) -> *mut _xmlBuf {
386    let buf_size = if size <= 0 {
387        DEFAULT_BUFFER_SIZE
388    } else {
389        size as c_uint
390    };
391    let buf_size = buf_size.max(MIN_BUFFER_SIZE);
392
393    let buf = unsafe { xmlMalloc(size_of::<_xmlBuf>()) as *mut _xmlBuf };
394    if buf.is_null() {
395        return ptr::null_mut();
396    }
397
398    let content = unsafe { xmlMalloc(buf_size as usize) as *mut xmlChar };
399    if content.is_null() {
400        unsafe { xmlFree(buf as *mut c_void) };
401        return ptr::null_mut();
402    }
403
404    unsafe {
405        ptr::write(content, 0);
406    }
407
408    unsafe {
409        ptr::write(
410            buf,
411            _xmlBuf {
412                content,
413                use_: 0,
414                size: buf_size,
415                alloc: XML_BUFFER_ALLOC_DOUBLEIT,
416                error: 0,
417                buffer: 0,
418                io: 0,
419            },
420        );
421    }
422
423    buf
424}
425
426/// Free an xmlBuf.
427///
428/// Frees the content and the buffer struct itself.
429pub(crate) fn xml_buf_free(buf: *mut _xmlBuf) {
430    if buf.is_null() {
431        return;
432    }
433
434    unsafe {
435        if !(*buf).content.is_null() {
436            xmlFree((*buf).content as *mut c_void);
437        }
438        xmlFree(buf as *mut c_void);
439    }
440}
441
442/// Get the content of an xmlBuf.
443pub(crate) fn xml_buf_content(buf: *mut _xmlBuf) -> *mut xmlChar {
444    if buf.is_null() {
445        return ptr::null_mut();
446    }
447    unsafe { (*buf).content }
448}
449
450/// Get the used length of an xmlBuf.
451pub(crate) fn xml_buf_length(buf: *mut _xmlBuf) -> c_int {
452    if buf.is_null() {
453        return -1;
454    }
455    unsafe { (*buf).use_ as c_int }
456}
457
458/// Add `len` bytes from `str` to an xmlBuf.
459///
460/// Returns the number of bytes added, or -1 on error.
461pub(crate) fn xml_buf_add(buf: *mut _xmlBuf, str: *const xmlChar, len: c_int) -> c_int {
462    if buf.is_null() || str.is_null() || len <= 0 {
463        return 0;
464    }
465
466    let len = len as c_uint;
467    let b = unsafe { &mut *buf };
468
469    let needed = b.use_.saturating_add(len).saturating_add(1);
470    if needed > b.size {
471        let new_size = needed.saturating_mul(2).max(MIN_BUFFER_SIZE);
472        let new_content =
473            unsafe { xmlRealloc(b.content as *mut c_void, new_size as usize) as *mut xmlChar };
474        if new_content.is_null() {
475            return -1;
476        }
477        b.content = new_content;
478        b.size = new_size;
479    }
480
481    unsafe {
482        ptr::copy_nonoverlapping(str, b.content.add(b.use_ as usize), len as usize);
483    }
484    b.use_ = b.use_.saturating_add(len);
485
486    unsafe {
487        ptr::write(b.content.add(b.use_ as usize), 0);
488    }
489
490    len as c_int
491}
492
493/// Cat a null-terminated string to an xmlBuf.
494pub(crate) fn xml_buf_cat(buf: *mut _xmlBuf, str: *const xmlChar) -> c_int {
495    if buf.is_null() || str.is_null() {
496        return -1;
497    }
498
499    let len = unsafe {
500        let mut i: c_uint = 0;
501        while *str.add(i as usize) != 0 {
502            i += 1;
503        }
504        i
505    };
506
507    xml_buf_add(buf, str, len as c_int)
508}
509
510/// Grow an xmlBuf to at least `size` bytes of capacity.
511///
512/// Returns 0 on success, -1 on failure.
513pub(crate) fn xml_buf_grow(buf: *mut _xmlBuf, size: c_uint) -> c_int {
514    if buf.is_null() {
515        return -1;
516    }
517
518    let b = unsafe { &mut *buf };
519    if size <= b.size {
520        return 0;
521    }
522
523    let new_content =
524        unsafe { xmlRealloc(b.content as *mut c_void, size as usize) as *mut xmlChar };
525    if new_content.is_null() {
526        return -1;
527    }
528
529    b.content = new_content;
530    b.size = size;
531    0
532}
533
534/// Shrink an xmlBuf by `len` bytes from the end.
535///
536/// Returns the new used length, or -1 on error.
537pub(crate) fn xml_buf_shrink(buf: *mut _xmlBuf, len: c_uint) -> c_int {
538    if buf.is_null() {
539        return -1;
540    }
541
542    let b = unsafe { &mut *buf };
543    if b.use_ == 0 {
544        return 0;
545    }
546
547    b.use_ = if len >= b.use_ { 0 } else { b.use_ - len };
548
549    unsafe {
550        ptr::write(b.content.add(b.use_ as usize), 0);
551    }
552
553    b.use_ as c_int
554}
555
556// ═══════════════════════════════════════════════════════════════════════════════
557// 3. Input buffer operations
558// ═══════════════════════════════════════════════════════════════════════════════
559
560/// Convert a `c_int` encoding value to an `xmlCharEncoding` enum.
561fn encoding_from_int(enc: c_int) -> xmlCharEncoding {
562    match enc {
563        -1 => xmlCharEncoding::XML_CHAR_ENCODING_ERROR,
564        0 => xmlCharEncoding::XML_CHAR_ENCODING_NONE,
565        1 => xmlCharEncoding::XML_CHAR_ENCODING_UTF8,
566        2 => xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE,
567        3 => xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE,
568        4 => xmlCharEncoding::XML_CHAR_ENCODING_UCS4LE,
569        5 => xmlCharEncoding::XML_CHAR_ENCODING_UCS4BE,
570        6 => xmlCharEncoding::XML_CHAR_ENCODING_EBCDIC,
571        7 => xmlCharEncoding::XML_CHAR_ENCODING_UCS4_2143,
572        8 => xmlCharEncoding::XML_CHAR_ENCODING_UCS4_3412,
573        9 => xmlCharEncoding::XML_CHAR_ENCODING_UCS2,
574        10 => xmlCharEncoding::XML_CHAR_ENCODING_8859_1,
575        11 => xmlCharEncoding::XML_CHAR_ENCODING_8859_2,
576        12 => xmlCharEncoding::XML_CHAR_ENCODING_8859_3,
577        13 => xmlCharEncoding::XML_CHAR_ENCODING_8859_4,
578        14 => xmlCharEncoding::XML_CHAR_ENCODING_8859_5,
579        15 => xmlCharEncoding::XML_CHAR_ENCODING_8859_6,
580        16 => xmlCharEncoding::XML_CHAR_ENCODING_8859_7,
581        17 => xmlCharEncoding::XML_CHAR_ENCODING_8859_8,
582        18 => xmlCharEncoding::XML_CHAR_ENCODING_8859_9,
583        19 => xmlCharEncoding::XML_CHAR_ENCODING_2022_JP,
584        20 => xmlCharEncoding::XML_CHAR_ENCODING_SHIFT_JIS,
585        21 => xmlCharEncoding::XML_CHAR_ENCODING_EUC_JP,
586        22 => xmlCharEncoding::XML_CHAR_ENCODING_ASCII,
587        _ => xmlCharEncoding::XML_CHAR_ENCODING_ERROR,
588    }
589}
590
591/// Find an encoding handler for the given encoding integer.
592///
593/// Returns a pointer to the handler, or NULL if not found or if the
594/// encoding is NONE or UTF-8 (which don't need conversion).
595fn find_handler_for_encoding(enc: c_int) -> *mut _xmlCharEncodingHandler {
596    let enc_enum = encoding_from_int(enc);
597    if enc_enum == xmlCharEncoding::XML_CHAR_ENCODING_NONE
598        || enc_enum == xmlCharEncoding::XML_CHAR_ENCODING_UTF8
599        || enc_enum == xmlCharEncoding::XML_CHAR_ENCODING_ERROR
600    {
601        return ptr::null_mut();
602    }
603
604    // Get the encoding name and create a null-terminated version for lookup
605    if let Some(name) = encoding::encoding_name(enc_enum) {
606        // Create a null-terminated copy on the stack if small, or heap
607        let mut name_nul = name.to_vec();
608        name_nul.push(0);
609        let handler = encoding::find_encoding_handler(name_nul.as_ptr() as *const xmlChar);
610        if !handler.is_null() {
611            return handler;
612        }
613    }
614
615    ptr::null_mut()
616}
617
618/// Internal helper: create an _xmlParserInputBuffer struct.
619///
620/// Allocates the struct and initializes all fields to zero/NULL.
621/// The caller is responsible for setting the specific fields.
622fn allocate_input_buffer() -> *mut _xmlParserInputBuffer {
623    let buf =
624        unsafe { xmlMalloc(size_of::<_xmlParserInputBuffer>()) as *mut _xmlParserInputBuffer };
625    if buf.is_null() {
626        return ptr::null_mut();
627    }
628
629    unsafe {
630        ptr::write(
631            buf,
632            _xmlParserInputBuffer {
633                context: ptr::null_mut(),
634                readcallback: None,
635                closecallback: None,
636                encoder: ptr::null_mut(),
637                buffer: ptr::null_mut(),
638                raw: ptr::null_mut(),
639                compressed: 0,
640                error: 0,
641                rawconsumed: 0,
642            },
643        );
644    }
645
646    buf
647}
648
649/// Create an input buffer from memory.
650///
651/// The data is copied into the input buffer's internal storage.
652/// If `enc` specifies a non-UTF-8 encoding, the data is converted to UTF-8.
653pub(crate) fn input_buffer_create_mem(
654    buffer: *const c_char,
655    size: c_int,
656    enc: c_int,
657) -> *mut _xmlParserInputBuffer {
658    if buffer.is_null() || size <= 0 {
659        return ptr::null_mut();
660    }
661
662    let buf = allocate_input_buffer();
663    if buf.is_null() {
664        return ptr::null_mut();
665    }
666
667    // Create the raw buffer containing the input data
668    let raw_buf = buf_create(size);
669    if raw_buf.is_null() {
670        unsafe { xmlFree(buf as *mut c_void) };
671        return ptr::null_mut();
672    }
673
674    // Copy data into the raw buffer
675    buf_add(raw_buf, buffer as *const xmlChar, size);
676
677    // Check if encoding conversion is needed
678    let handler = find_handler_for_encoding(enc);
679    if !handler.is_null() {
680        // Encoding conversion needed
681        // Create the output (UTF-8) buffer
682        let out_buf = buf_create((size as c_uint).saturating_mul(3).max(MIN_BUFFER_SIZE) as c_int);
683        if out_buf.is_null() {
684            buf_free(raw_buf);
685            unsafe { xmlFree(buf as *mut c_void) };
686            return ptr::null_mut();
687        }
688
689        // Convert raw data to UTF-8
690        let written = encoding::char_enc_in(handler, out_buf, raw_buf);
691        if written < 0 {
692            buf_free(raw_buf);
693            buf_free(out_buf);
694            unsafe { xmlFree(buf as *mut c_void) };
695            return ptr::null_mut();
696        }
697
698        unsafe {
699            (*buf).encoder = handler as *mut c_void;
700            (*buf).buffer = out_buf as *mut c_void;
701            (*buf).raw = raw_buf as *mut c_void;
702        }
703    } else {
704        // No encoding conversion needed — data is (or will be treated as) UTF-8
705        unsafe {
706            (*buf).buffer = raw_buf as *mut c_void;
707            (*buf).raw = raw_buf as *mut c_void;
708        }
709    }
710
711    buf
712}
713
714// ── File I/O callbacks ──────────────────────────────────────────────────────
715
716/// Read callback for file descriptor-based input.
717unsafe extern "C" fn file_read_callback(
718    context: *mut c_void,
719    buffer: *mut c_char,
720    len: c_int,
721) -> c_int {
722    if context.is_null() || buffer.is_null() || len <= 0 {
723        return -1;
724    }
725
726    let fd = context as c_int;
727    let ret = libc::read(fd, buffer as *mut c_void, len as usize);
728    if ret < 0 {
729        return -1;
730    }
731    ret as c_int
732}
733
734/// Close callback for file descriptor-based input.
735unsafe extern "C" fn file_close_callback(context: *mut c_void) -> c_int {
736    if context.is_null() {
737        return -1;
738    }
739
740    let fd = context as c_int;
741    libc::close(fd)
742}
743
744/// Create an input buffer from a file.
745///
746/// Opens the file, reads its contents into memory, and creates a memory-based
747/// input buffer. The file is closed after reading.
748pub(crate) fn input_buffer_create_file(
749    filename: *const c_char,
750    enc: c_int,
751) -> *mut _xmlParserInputBuffer {
752    if filename.is_null() {
753        return ptr::null_mut();
754    }
755
756    // Get filename as a Rust string
757    let filename_str = unsafe {
758        match CStr::from_ptr(filename).to_str() {
759            Ok(s) => s,
760            Err(_) => return ptr::null_mut(),
761        }
762    };
763
764    // Open the file
765    let fd = unsafe {
766        let path_c = std::ffi::CString::new(filename_str).unwrap_or_default();
767        libc::open(path_c.as_ptr(), libc::O_RDONLY)
768    };
769
770    if fd < 0 {
771        return ptr::null_mut();
772    }
773
774    // Stat the file to get its size
775    let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
776    let stat_ret = unsafe {
777        let path_c = std::ffi::CString::new(filename_str).unwrap_or_default();
778        libc::stat(path_c.as_ptr(), &mut stat_buf)
779    };
780
781    let file_size = if stat_ret == 0 {
782        stat_buf.st_size as usize
783    } else {
784        // Fall back to reading in chunks
785        0
786    };
787
788    // Read the file contents
789    let read_size = if file_size > 0 {
790        file_size
791    } else {
792        4096 // Default chunk
793    };
794
795    let mut data = vec![0u8; read_size];
796    let mut total_read: isize = 0;
797
798    loop {
799        let remaining = read_size.saturating_sub(total_read as usize);
800        if remaining == 0 {
801            // Grow buffer
802            let new_size = read_size.saturating_mul(2);
803            data.resize(new_size, 0u8);
804        }
805
806        let ret = unsafe {
807            libc::read(
808                fd,
809                data.as_mut_ptr().add(total_read as usize) as *mut c_void,
810                remaining,
811            )
812        };
813
814        if ret < 0 {
815            // Error
816            unsafe { libc::close(fd) };
817            return ptr::null_mut();
818        }
819
820        if ret == 0 {
821            // EOF
822            break;
823        }
824
825        total_read += ret as isize;
826    }
827
828    unsafe { libc::close(fd) };
829
830    data.truncate(total_read as usize);
831
832    if data.is_empty() {
833        return ptr::null_mut();
834    }
835
836    // Create a memory-based input buffer from the data
837    input_buffer_create_mem(data.as_ptr() as *const c_char, data.len() as c_int, enc)
838}
839
840/// Create an input buffer from I/O callbacks.
841///
842/// The `ioread` callback is called to fill the raw buffer.
843/// The `ioclose` callback is called when the buffer is freed.
844pub(crate) fn input_buffer_create_io(
845    ioread: Option<xmlInputReadCallback>,
846    ioclose: Option<xmlInputCloseCallback>,
847    ioctx: *mut c_void,
848    enc: c_int,
849) -> *mut _xmlParserInputBuffer {
850    let buf = allocate_input_buffer();
851    if buf.is_null() {
852        return ptr::null_mut();
853    }
854
855    // Create the raw buffer (used for reading from callback)
856    let raw_buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
857    if raw_buf.is_null() {
858        unsafe { xmlFree(buf as *mut c_void) };
859        return ptr::null_mut();
860    }
861
862    unsafe {
863        (*buf).context = ioctx;
864        (*buf).readcallback = ioread;
865        (*buf).closecallback = ioclose;
866        (*buf).raw = raw_buf as *mut c_void;
867    }
868
869    // Set up encoder if needed
870    let handler = find_handler_for_encoding(enc);
871    if !handler.is_null() {
872        let out_buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
873        if out_buf.is_null() {
874            buf_free(raw_buf);
875            unsafe { xmlFree(buf as *mut c_void) };
876            return ptr::null_mut();
877        }
878        unsafe {
879            (*buf).encoder = handler as *mut c_void;
880            (*buf).buffer = out_buf as *mut c_void;
881        }
882    } else {
883        unsafe {
884            (*buf).buffer = raw_buf as *mut c_void;
885        }
886    }
887
888    buf
889}
890
891/// Create an input buffer from a file descriptor.
892///
893/// The buffer uses read/close callbacks that wrap `libc::read` and `libc::close`.
894pub(crate) fn input_buffer_create_fd(fd: c_int, enc: c_int) -> *mut _xmlParserInputBuffer {
895    if fd < 0 {
896        return ptr::null_mut();
897    }
898
899    input_buffer_create_io(
900        Some(file_read_callback as xmlInputReadCallback),
901        Some(file_close_callback as xmlInputCloseCallback),
902        fd as *mut c_void,
903        enc,
904    )
905}
906
907/// Free an input buffer.
908///
909/// Calls the close callback if one is set, frees all internal buffers,
910/// then frees the input buffer struct itself.
911pub(crate) fn input_buffer_free(buf: *mut _xmlParserInputBuffer) {
912    if buf.is_null() {
913        return;
914    }
915
916    unsafe {
917        // Call the close callback if one is set
918        if let Some(close_cb) = (*buf).closecallback {
919            close_cb((*buf).context);
920        }
921
922        // Free the raw buffer
923        if !(*buf).raw.is_null() {
924            buf_free((*buf).raw as *mut _xmlBuffer);
925        }
926
927        // Free the (converted) buffer if different from raw
928        if !(*buf).buffer.is_null() && (*buf).buffer != (*buf).raw {
929            buf_free((*buf).buffer as *mut _xmlBuffer);
930        }
931
932        // Note: the encoder is owned by the encoding module, not by us.
933        // We do NOT free it here.
934
935        xmlFree(buf as *mut c_void);
936    }
937}
938
939/// Read from an input buffer.
940///
941/// If the buffer has a read callback, the callback is called to fill the raw
942/// buffer, then the data is converted (if an encoder is set) and copied to
943/// `buffer`. If no read callback is set (memory-based input), data is read
944/// directly from the internal buffer.
945///
946/// Returns the number of bytes read, or -1 on error.
947pub(crate) fn input_buffer_read(
948    buf: *mut _xmlParserInputBuffer,
949    buffer: *mut c_char,
950    len: c_int,
951) -> c_int {
952    if buf.is_null() || buffer.is_null() || len <= 0 {
953        return -1;
954    }
955
956    let b = unsafe { &mut *buf };
957
958    if b.error != 0 {
959        return -1;
960    }
961
962    if let Some(read_cb) = b.readcallback {
963        // Callback-based input: read into raw buffer, then convert
964        // Read a chunk
965        let raw_buf = b.raw as *mut _xmlBuffer;
966        if raw_buf.is_null() {
967            return -1;
968        }
969
970        // Read up to `len` bytes into a temporary buffer
971        let mut tmp = vec![0u8; len as usize];
972        let ret = unsafe { read_cb(b.context, tmp.as_mut_ptr() as *mut c_char, len) };
973
974        if ret < 0 {
975            b.error = 1;
976            return -1;
977        }
978
979        if ret == 0 {
980            // EOF
981            return 0;
982        }
983
984        // Add read data to raw buffer
985        buf_add(raw_buf, tmp.as_ptr() as *const xmlChar, ret);
986
987        // If encoder is set, convert raw -> buffer
988        if !b.encoder.is_null() {
989            let out_buf = b.buffer as *mut _xmlBuffer;
990            if out_buf.is_null() {
991                return -1;
992            }
993
994            let handler = b.encoder as *mut _xmlCharEncodingHandler;
995            let conv_ret = encoding::char_enc_in(handler, out_buf, raw_buf);
996            if conv_ret < 0 {
997                b.error = 1;
998                return -1;
999            }
1000
1001            // Read from the converted buffer
1002            let out_b = unsafe { &*out_buf };
1003            let to_copy = (out_b.use_ as c_int).min(len);
1004            if to_copy > 0 {
1005                unsafe {
1006                    ptr::copy_nonoverlapping(
1007                        out_b.content,
1008                        buffer as *mut xmlChar,
1009                        to_copy as usize,
1010                    );
1011                }
1012                // Remove the copied bytes from the output buffer
1013                buf_shrink(out_buf, to_copy as c_uint);
1014            }
1015            return to_copy;
1016        }
1017
1018        // No encoder: read from raw buffer directly
1019        let raw_b = unsafe { &*raw_buf };
1020        let to_copy = (raw_b.use_ as c_int).min(len);
1021        if to_copy > 0 {
1022            unsafe {
1023                ptr::copy_nonoverlapping(raw_b.content, buffer as *mut xmlChar, to_copy as usize);
1024            }
1025            buf_shrink(raw_buf, to_copy as c_uint);
1026        }
1027        return to_copy;
1028    }
1029
1030    // Memory-based input: read directly from the buffer
1031    let src_buf = b.buffer as *mut _xmlBuffer;
1032    if src_buf.is_null() {
1033        return -1;
1034    }
1035
1036    let src = unsafe { &mut *src_buf };
1037    if src.content.is_null() || src.use_ == 0 {
1038        return 0;
1039    }
1040
1041    let to_copy = (src.use_ as c_int).min(len);
1042    if to_copy > 0 {
1043        unsafe {
1044            ptr::copy_nonoverlapping(src.content, buffer as *mut xmlChar, to_copy as usize);
1045        }
1046        // Advance the content pointer and reduce use_
1047        unsafe {
1048            src.content = src.content.add(to_copy as usize);
1049        }
1050        src.use_ = src.use_.saturating_sub(to_copy as c_uint);
1051    }
1052
1053    to_copy
1054}
1055
1056/// Push data into an input buffer (for push parser).
1057///
1058/// The data is appended to the raw buffer and, if an encoder is set,
1059/// converted to UTF-8 in the buffer.
1060pub(crate) fn input_buffer_push(
1061    buf: *mut _xmlParserInputBuffer,
1062    buffer: *const c_char,
1063    len: c_int,
1064) -> c_int {
1065    if buf.is_null() || buffer.is_null() || len <= 0 {
1066        return -1;
1067    }
1068
1069    let b = unsafe { &mut *buf };
1070
1071    if b.error != 0 {
1072        return -1;
1073    }
1074
1075    // Append to raw buffer
1076    let raw_buf = b.raw as *mut _xmlBuffer;
1077    if raw_buf.is_null() {
1078        return -1;
1079    }
1080
1081    buf_add(raw_buf, buffer as *const xmlChar, len);
1082
1083    // If encoder is set, convert raw -> buffer
1084    if !b.encoder.is_null() {
1085        let out_buf = b.buffer as *mut _xmlBuffer;
1086        if out_buf.is_null() {
1087            return -1;
1088        }
1089
1090        let handler = b.encoder as *mut _xmlCharEncodingHandler;
1091        let ret = encoding::char_enc_in(handler, out_buf, raw_buf);
1092        if ret < 0 {
1093            b.error = 1;
1094            return -1;
1095        }
1096    }
1097
1098    len
1099}
1100
1101/// Update input buffer encoding.
1102///
1103/// Sets the encoder for an input buffer. The handler must already be
1104/// properly initialized.
1105pub(crate) fn input_buffer_set_encoder(
1106    buf: *mut _xmlParserInputBuffer,
1107    handler: *mut _xmlCharEncodingHandler,
1108) {
1109    if buf.is_null() {
1110        return;
1111    }
1112
1113    unsafe {
1114        (*buf).encoder = handler as *mut c_void;
1115    }
1116}
1117
1118// ═══════════════════════════════════════════════════════════════════════════════
1119// 4. Output buffer operations
1120// ═══════════════════════════════════════════════════════════════════════════════
1121
1122/// Write callback for file descriptor-based output.
1123unsafe extern "C" fn file_write_callback(
1124    context: *mut c_void,
1125    buffer: *const c_char,
1126    len: c_int,
1127) -> c_int {
1128    if context.is_null() || buffer.is_null() || len <= 0 {
1129        return -1;
1130    }
1131
1132    let fd = context as c_int;
1133    let ret = libc::write(fd, buffer as *const c_void, len as usize);
1134    if ret < 0 {
1135        return -1;
1136    }
1137    ret as c_int
1138}
1139
1140/// Close callback for file descriptor-based output.
1141unsafe extern "C" fn file_close_output_callback(context: *mut c_void) -> c_int {
1142    if context.is_null() {
1143        return -1;
1144    }
1145
1146    let fd = context as c_int;
1147    libc::close(fd)
1148}
1149
1150/// Write callback for buffer-based output (writes into an xmlBuffer).
1151unsafe extern "C" fn buffer_write_callback(
1152    context: *mut c_void,
1153    buffer: *const c_char,
1154    len: c_int,
1155) -> c_int {
1156    if context.is_null() || buffer.is_null() || len <= 0 {
1157        return -1;
1158    }
1159
1160    let target_buf = context as *mut _xmlBuffer;
1161    buf_add(target_buf, buffer as *const xmlChar, len)
1162}
1163
1164/// Internal helper: create an _xmlOutputBuffer struct.
1165///
1166/// Allocates the struct and initializes all fields to zero/NULL.
1167fn allocate_output_buffer() -> *mut _xmlOutputBuffer {
1168    let buf = unsafe { xmlMalloc(size_of::<_xmlOutputBuffer>()) as *mut _xmlOutputBuffer };
1169    if buf.is_null() {
1170        return ptr::null_mut();
1171    }
1172
1173    unsafe {
1174        ptr::write(
1175            buf,
1176            _xmlOutputBuffer {
1177                context: ptr::null_mut(),
1178                writecallback: None,
1179                closecallback: None,
1180                encoder: ptr::null_mut(),
1181                buffer: ptr::null_mut(),
1182                conv: ptr::null_mut(),
1183                written: 0,
1184                error: 0,
1185            },
1186        );
1187    }
1188
1189    buf
1190}
1191
1192/// Create an output buffer for a filename.
1193///
1194/// Opens the file for writing and sets up write/close callbacks.
1195/// If `compression` is nonzero, future versions may support compression.
1196pub(crate) fn output_buffer_create_filename(
1197    URI: *const c_char,
1198    encoder: *mut _xmlCharEncodingHandler,
1199    compression: c_int,
1200) -> *mut _xmlOutputBuffer {
1201    if URI.is_null() {
1202        return ptr::null_mut();
1203    }
1204
1205    let path_str = unsafe {
1206        match CStr::from_ptr(URI).to_str() {
1207            Ok(s) => s,
1208            Err(_) => return ptr::null_mut(),
1209        }
1210    };
1211
1212    let path_c = std::ffi::CString::new(path_str).unwrap_or_default();
1213
1214    // Open file for writing (create/truncate)
1215    let fd = unsafe {
1216        libc::open(
1217            path_c.as_ptr(),
1218            libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC,
1219            0o644,
1220        )
1221    };
1222
1223    if fd < 0 {
1224        return ptr::null_mut();
1225    }
1226
1227    let obuf = allocate_output_buffer();
1228    if obuf.is_null() {
1229        unsafe { libc::close(fd) };
1230        return ptr::null_mut();
1231    }
1232
1233    let buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
1234    if buf.is_null() {
1235        unsafe {
1236            libc::close(fd);
1237            xmlFree(obuf as *mut c_void);
1238        }
1239        return ptr::null_mut();
1240    }
1241
1242    let conv_buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
1243    if conv_buf.is_null() {
1244        unsafe {
1245            libc::close(fd);
1246            buf_free(buf);
1247            xmlFree(obuf as *mut c_void);
1248        }
1249        return ptr::null_mut();
1250    }
1251
1252    unsafe {
1253        (*obuf).context = fd as *mut c_void;
1254        (*obuf).writecallback = Some(file_write_callback as xmlOutputWriteCallback);
1255        (*obuf).closecallback = Some(file_close_output_callback as xmlOutputCloseCallback);
1256        (*obuf).encoder = encoder as *mut c_void;
1257        (*obuf).buffer = buf as *mut c_void;
1258        (*obuf).conv = conv_buf as *mut c_void;
1259        (*obuf).written = 0;
1260        (*obuf).error = 0;
1261    }
1262
1263    obuf
1264}
1265
1266/// Create an output buffer for a file descriptor.
1267pub(crate) fn output_buffer_create_fd(
1268    fd: c_int,
1269    encoder: *mut _xmlCharEncodingHandler,
1270) -> *mut _xmlOutputBuffer {
1271    if fd < 0 {
1272        return ptr::null_mut();
1273    }
1274
1275    let obuf = allocate_output_buffer();
1276    if obuf.is_null() {
1277        return ptr::null_mut();
1278    }
1279
1280    let buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
1281    if buf.is_null() {
1282        unsafe { xmlFree(obuf as *mut c_void) };
1283        return ptr::null_mut();
1284    }
1285
1286    let conv_buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
1287    if conv_buf.is_null() {
1288        unsafe {
1289            buf_free(buf);
1290            xmlFree(obuf as *mut c_void);
1291        }
1292        return ptr::null_mut();
1293    }
1294
1295    unsafe {
1296        (*obuf).context = fd as *mut c_void;
1297        (*obuf).writecallback = Some(file_write_callback as xmlOutputWriteCallback);
1298        (*obuf).closecallback = Some(file_close_output_callback as xmlOutputCloseCallback);
1299        (*obuf).encoder = encoder as *mut c_void;
1300        (*obuf).buffer = buf as *mut c_void;
1301        (*obuf).conv = conv_buf as *mut c_void;
1302        (*obuf).written = 0;
1303        (*obuf).error = 0;
1304    }
1305
1306    obuf
1307}
1308
1309/// Create an output buffer from I/O callbacks.
1310pub(crate) fn output_buffer_create_io(
1311    iowrite: Option<xmlOutputWriteCallback>,
1312    ioclose: Option<xmlOutputCloseCallback>,
1313    ioctx: *mut c_void,
1314    encoder: *mut _xmlCharEncodingHandler,
1315) -> *mut _xmlOutputBuffer {
1316    let obuf = allocate_output_buffer();
1317    if obuf.is_null() {
1318        return ptr::null_mut();
1319    }
1320
1321    let buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
1322    if buf.is_null() {
1323        unsafe { xmlFree(obuf as *mut c_void) };
1324        return ptr::null_mut();
1325    }
1326
1327    let conv_buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
1328    if conv_buf.is_null() {
1329        unsafe {
1330            buf_free(buf);
1331            xmlFree(obuf as *mut c_void);
1332        }
1333        return ptr::null_mut();
1334    }
1335
1336    unsafe {
1337        (*obuf).context = ioctx;
1338        (*obuf).writecallback = iowrite;
1339        (*obuf).closecallback = ioclose;
1340        (*obuf).encoder = encoder as *mut c_void;
1341        (*obuf).buffer = buf as *mut c_void;
1342        (*obuf).conv = conv_buf as *mut c_void;
1343        (*obuf).written = 0;
1344        (*obuf).error = 0;
1345    }
1346
1347    obuf
1348}
1349
1350/// Create an output buffer from a pre-existing xmlBuffer.
1351///
1352/// Writes to the output buffer will be appended to the given `_xmlBuffer`.
1353pub(crate) fn output_buffer_create_buffer(
1354    target_buf: *mut _xmlBuffer,
1355    encoder: *mut _xmlCharEncodingHandler,
1356) -> *mut _xmlOutputBuffer {
1357    if target_buf.is_null() {
1358        return ptr::null_mut();
1359    }
1360
1361    let obuf = allocate_output_buffer();
1362    if obuf.is_null() {
1363        return ptr::null_mut();
1364    }
1365
1366    // Internal buffer for buffering writes before flush
1367    let internal_buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
1368    if internal_buf.is_null() {
1369        unsafe { xmlFree(obuf as *mut c_void) };
1370        return ptr::null_mut();
1371    }
1372
1373    // Conversion buffer (used when encoder is present)
1374    let conv_buf = buf_create(DEFAULT_BUFFER_SIZE as c_int);
1375    if conv_buf.is_null() {
1376        unsafe {
1377            buf_free(internal_buf);
1378            xmlFree(obuf as *mut c_void);
1379        }
1380        return ptr::null_mut();
1381    }
1382
1383    unsafe {
1384        (*obuf).context = target_buf as *mut c_void;
1385        (*obuf).writecallback = Some(buffer_write_callback as xmlOutputWriteCallback);
1386        (*obuf).closecallback = None;
1387        (*obuf).encoder = encoder as *mut c_void;
1388        (*obuf).buffer = internal_buf as *mut c_void;
1389        (*obuf).conv = conv_buf as *mut c_void;
1390        (*obuf).written = 0;
1391        (*obuf).error = 0;
1392    }
1393
1394    obuf
1395}
1396
1397/// Flush an output buffer.
1398///
1399/// Encodes the buffered data (if an encoder is set) and writes it via the
1400/// write callback. Resets the internal buffer after writing.
1401///
1402/// Returns the number of bytes written, or -1 on error.
1403pub(crate) fn output_buffer_flush(out: *mut _xmlOutputBuffer) -> c_int {
1404    if out.is_null() {
1405        return -1;
1406    }
1407
1408    let ob = unsafe { &mut *out };
1409
1410    if ob.error != 0 {
1411        return -1;
1412    }
1413
1414    let buf = ob.buffer as *mut _xmlBuffer;
1415    if buf.is_null() {
1416        return 0;
1417    }
1418
1419    let b = unsafe { &*buf };
1420    if b.use_ == 0 {
1421        return 0;
1422    }
1423
1424    let write_cb = match ob.writecallback {
1425        Some(cb) => cb,
1426        None => {
1427            // No write callback — just clear the buffer
1428            buf_empty(buf);
1429            return 0;
1430        }
1431    };
1432
1433    let total_written = if !ob.encoder.is_null() {
1434        // Convert buffer content via encoder
1435        let handler = ob.encoder as *mut _xmlCharEncodingHandler;
1436        let conv = ob.conv as *mut _xmlBuffer;
1437
1438        // Ensure conv buffer is empty before converting
1439        buf_empty(conv);
1440
1441        let ret = encoding::char_enc_out(handler, conv, buf);
1442        if ret < 0 {
1443            ob.error = 1;
1444            return -1;
1445        }
1446
1447        // Write converted data via callback
1448        let conv_b = unsafe { &*conv };
1449        if conv_b.use_ > 0 {
1450            let written = unsafe {
1451                write_cb(
1452                    ob.context,
1453                    conv_b.content as *const c_char,
1454                    conv_b.use_ as c_int,
1455                )
1456            };
1457
1458            if written < 0 {
1459                ob.error = 1;
1460                return -1;
1461            }
1462
1463            ob.written = ob.written.saturating_add(written);
1464            buf_empty(conv);
1465            written
1466        } else {
1467            0
1468        }
1469    } else {
1470        // No encoder: write buffer content directly
1471        let written = unsafe { write_cb(ob.context, b.content as *const c_char, b.use_ as c_int) };
1472
1473        if written < 0 {
1474            ob.error = 1;
1475            return -1;
1476        }
1477
1478        ob.written = ob.written.saturating_add(written);
1479        written
1480    };
1481
1482    // Clear the buffer after writing
1483    buf_empty(buf);
1484
1485    total_written
1486}
1487
1488/// Free an output buffer.
1489///
1490/// Flushes any pending data, calls the close callback if set,
1491/// frees all internal buffers, then frees the output buffer struct.
1492pub(crate) fn output_buffer_close(out: *mut _xmlOutputBuffer) -> c_int {
1493    if out.is_null() {
1494        return -1;
1495    }
1496
1497    let ob = unsafe { &mut *out };
1498
1499    // Flush any pending data
1500    let flush_ret = output_buffer_flush(out);
1501
1502    // Call the close callback
1503    if let Some(close_cb) = ob.closecallback {
1504        unsafe {
1505            close_cb(ob.context);
1506        }
1507    }
1508
1509    // Free internal buffers
1510    if !ob.buffer.is_null() {
1511        buf_free(ob.buffer as *mut _xmlBuffer);
1512    }
1513    if !ob.conv.is_null() {
1514        buf_free(ob.conv as *mut _xmlBuffer);
1515    }
1516
1517    // Note: encoder is owned by the caller/encoding module, not by us
1518
1519    unsafe { xmlFree(out as *mut c_void) };
1520
1521    flush_ret
1522}
1523
1524/// Write to an output buffer.
1525///
1526/// The data is appended to the internal buffer. Use `output_buffer_flush`
1527/// to write the buffered data via the callback.
1528///
1529/// Returns the number of bytes written (always `len` on success), or -1 on error.
1530pub(crate) fn output_buffer_write(
1531    out: *mut _xmlOutputBuffer,
1532    len: c_int,
1533    data: *const c_char,
1534) -> c_int {
1535    if out.is_null() || data.is_null() || len <= 0 {
1536        return -1;
1537    }
1538
1539    let ob = unsafe { &mut *out };
1540
1541    if ob.error != 0 {
1542        return -1;
1543    }
1544
1545    let buf = ob.buffer as *mut _xmlBuffer;
1546    if buf.is_null() {
1547        return -1;
1548    }
1549
1550    let ret = buf_add(buf, data as *const xmlChar, len);
1551    if ret < 0 {
1552        ob.error = 1;
1553        return -1;
1554    }
1555
1556    len
1557}
1558
1559/// Write a null-terminated string to an output buffer.
1560pub(crate) fn output_buffer_write_string(out: *mut _xmlOutputBuffer, str: *const c_char) -> c_int {
1561    if out.is_null() || str.is_null() {
1562        return -1;
1563    }
1564
1565    let len = unsafe {
1566        let mut i: c_int = 0;
1567        while *str.add(i as usize) != 0 {
1568            i += 1;
1569        }
1570        i
1571    };
1572
1573    output_buffer_write(out, len, str)
1574}
1575
1576/// Write a single character to an output buffer.
1577pub(crate) fn output_buffer_write_char(out: *mut _xmlOutputBuffer, c: c_char) -> c_int {
1578    output_buffer_write(out, 1, &c as *const c_char)
1579}
1580
1581/// Get the content of an output buffer's internal buffer.
1582///
1583/// Returns a pointer to the internal buffer's content, or NULL on error.
1584pub(crate) fn output_buffer_get_content(out: *mut _xmlOutputBuffer) -> *const xmlChar {
1585    if out.is_null() {
1586        return ptr::null();
1587    }
1588
1589    let ob = unsafe { &*out };
1590    let buf = ob.buffer as *mut _xmlBuffer;
1591    if buf.is_null() {
1592        return ptr::null();
1593    }
1594
1595    buf_content(buf)
1596}
1597
1598// ═══════════════════════════════════════════════════════════════════════════════
1599// 5. I/O helper functions
1600// ═══════════════════════════════════════════════════════════════════════════════
1601
1602/// Check if a file exists.
1603///
1604/// Returns 1 if the file exists, 0 if not, -1 on error.
1605pub(crate) fn check_file_exists(filename: *const c_char) -> c_int {
1606    if filename.is_null() {
1607        return -1;
1608    }
1609
1610    let path_str = unsafe {
1611        match CStr::from_ptr(filename).to_str() {
1612            Ok(s) => s,
1613            Err(_) => return -1,
1614        }
1615    };
1616
1617    let path_c = match std::ffi::CString::new(path_str) {
1618        Ok(c) => c,
1619        Err(_) => return -1,
1620    };
1621
1622    let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
1623    let ret = unsafe { libc::stat(path_c.as_ptr(), &mut stat_buf) };
1624
1625    if ret == 0 {
1626        1
1627    } else {
1628        0
1629    }
1630}
1631
1632/// Read a file into memory.
1633///
1634/// Reads the entire file contents into a newly allocated buffer.
1635/// Returns a pointer to the buffer, or NULL on failure.
1636/// The size of the buffer is stored in `size` if it's non-NULL.
1637///
1638/// The returned buffer must be freed with `xmlFree`.
1639pub(crate) fn read_file_to_memory(filename: *const c_char, size: *mut c_int) -> *mut c_char {
1640    if filename.is_null() {
1641        return ptr::null_mut();
1642    }
1643
1644    let path_str = unsafe {
1645        match CStr::from_ptr(filename).to_str() {
1646            Ok(s) => s,
1647            Err(_) => return ptr::null_mut(),
1648        }
1649    };
1650
1651    let path_c = match std::ffi::CString::new(path_str) {
1652        Ok(c) => c,
1653        Err(_) => return ptr::null_mut(),
1654    };
1655
1656    let fd = unsafe { libc::open(path_c.as_ptr(), libc::O_RDONLY) };
1657    if fd < 0 {
1658        return ptr::null_mut();
1659    }
1660
1661    // Stat to get file size
1662    let mut stat_buf: libc::stat = unsafe { std::mem::zeroed() };
1663    let file_size = if unsafe { libc::stat(path_c.as_ptr(), &mut stat_buf) } == 0 {
1664        stat_buf.st_size as usize
1665    } else {
1666        0
1667    };
1668
1669    // Read in chunks
1670    let chunk_size = 4096usize;
1671    let initial_capacity = if file_size > 0 { file_size } else { chunk_size };
1672
1673    let mut data = Vec::with_capacity(initial_capacity);
1674    let mut buf = vec![0u8; chunk_size];
1675
1676    loop {
1677        let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut c_void, chunk_size) };
1678
1679        if ret < 0 {
1680            unsafe { libc::close(fd) };
1681            return ptr::null_mut();
1682        }
1683
1684        if ret == 0 {
1685            break; // EOF
1686        }
1687
1688        data.extend_from_slice(&buf[..ret as usize]);
1689    }
1690
1691    unsafe { libc::close(fd) };
1692
1693    if data.is_empty() {
1694        return ptr::null_mut();
1695    }
1696
1697    // Allocate via xmlMalloc and copy
1698    let result = unsafe { xmlMalloc(data.len()) as *mut c_char };
1699    if result.is_null() {
1700        return ptr::null_mut();
1701    }
1702
1703    unsafe {
1704        ptr::copy_nonoverlapping(data.as_ptr(), result as *mut u8, data.len());
1705    }
1706
1707    if !size.is_null() {
1708        unsafe {
1709            *size = data.len() as c_int;
1710        }
1711    }
1712
1713    result
1714}
1715
1716/// Write memory to a file.
1717///
1718/// Creates or truncates the file and writes `size` bytes from `data`.
1719/// Returns 0 on success, -1 on error.
1720pub(crate) fn write_memory_to_file(
1721    filename: *const c_char,
1722    data: *const c_char,
1723    size: c_int,
1724) -> c_int {
1725    if filename.is_null() || data.is_null() || size <= 0 {
1726        return -1;
1727    }
1728
1729    let path_str = unsafe {
1730        match CStr::from_ptr(filename).to_str() {
1731            Ok(s) => s,
1732            Err(_) => return -1,
1733        }
1734    };
1735
1736    let path_c = match std::ffi::CString::new(path_str) {
1737        Ok(c) => c,
1738        Err(_) => return -1,
1739    };
1740
1741    let fd = unsafe {
1742        libc::open(
1743            path_c.as_ptr(),
1744            libc::O_WRONLY | libc::O_CREAT | libc::O_TRUNC,
1745            0o644,
1746        )
1747    };
1748
1749    if fd < 0 {
1750        return -1;
1751    }
1752
1753    let mut remaining = size as usize;
1754    let mut offset: usize = 0;
1755
1756    while remaining > 0 {
1757        let ret = unsafe {
1758            libc::write(
1759                fd,
1760                (data as *const u8).add(offset) as *const c_void,
1761                remaining,
1762            )
1763        };
1764
1765        if ret < 0 {
1766            unsafe { libc::close(fd) };
1767            return -1;
1768        }
1769
1770        let written = ret as usize;
1771        remaining -= written;
1772        offset += written;
1773    }
1774
1775    unsafe { libc::close(fd) };
1776    0
1777}
1778
1779/// Get the current working directory.
1780///
1781/// Returns a newly allocated null-terminated string, or NULL on failure.
1782/// The returned pointer must be freed with `xmlFree`.
1783pub(crate) fn get_cwd() -> *mut c_char {
1784    // Use a reasonable initial buffer size
1785    let mut size: usize = 1024;
1786
1787    loop {
1788        let buf = unsafe { xmlMalloc(size) as *mut c_char };
1789        if buf.is_null() {
1790            return ptr::null_mut();
1791        }
1792
1793        let ret = unsafe { libc::getcwd(buf as *mut c_char, size) };
1794        if !ret.is_null() {
1795            return buf;
1796        }
1797
1798        unsafe { xmlFree(buf as *mut c_void) };
1799
1800        // Check if the error was ERANGE (buffer too small)
1801        let err = std::io::Error::last_os_error();
1802        if err.raw_os_error() == Some(libc::ERANGE) {
1803            size = size.saturating_mul(2);
1804            if size > 65536 {
1805                return ptr::null_mut(); // Sanity cap
1806            }
1807        } else {
1808            return ptr::null_mut();
1809        }
1810    }
1811}
1812
1813// ═══════════════════════════════════════════════════════════════════════════════
1814// Tests
1815// ═══════════════════════════════════════════════════════════════════════════════
1816
1817#[cfg(test)]
1818mod tests {
1819    use super::*;
1820    use std::ffi::CString;
1821    use std::os::raw::c_char;
1822
1823    // ── Helpers ────────────────────────────────────────────────────────────
1824
1825    fn c(s: &str) -> CString {
1826        CString::new(s).unwrap()
1827    }
1828
1829    /// Create a CString from raw bytes (may contain non-ASCII).
1830    unsafe fn c_bytes(bytes: &[u8]) -> CString {
1831        CString::from_vec_unchecked(bytes.to_vec())
1832    }
1833
1834    /// Interpret a &[u8] as &[i8] for comparison with c_char buffers.
1835    fn i8_slice(s: &[u8]) -> &[i8] {
1836        unsafe { std::slice::from_raw_parts(s.as_ptr() as *const i8, s.len()) }
1837    }
1838
1839    // ── xmlBuffer tests ────────────────────────────────────────────────────
1840
1841    #[test]
1842    fn test_buf_create_free() {
1843        let buf = buf_create(100);
1844        assert!(!buf.is_null());
1845
1846        let b = unsafe { &*buf };
1847        assert!(!b.content.is_null());
1848        assert_eq!(b.use_, 0);
1849        assert!(b.size >= 100);
1850        assert_eq!(b.alloc, XML_BUFFER_ALLOC_DOUBLEIT);
1851
1852        // Content should be null-terminated empty string
1853        unsafe {
1854            assert_eq!(*b.content, 0);
1855        }
1856
1857        buf_free(buf);
1858    }
1859
1860    #[test]
1861    fn test_buf_create_default_size() {
1862        let buf = buf_create(0);
1863        assert!(!buf.is_null());
1864
1865        let b = unsafe { &*buf };
1866        assert!(b.size >= MIN_BUFFER_SIZE);
1867
1868        buf_free(buf);
1869    }
1870
1871    #[test]
1872    fn test_buf_create_static() {
1873        let s: &[u8] = b"hello\0";
1874        let buf = buf_create_static(s.as_ptr() as *const xmlChar, 5);
1875        assert!(!buf.is_null());
1876
1877        let b = unsafe { &*buf };
1878        assert_eq!(b.use_, 5);
1879        assert_eq!(b.alloc, XML_BUFFER_ALLOC_IMMUTABLE);
1880
1881        // Content should point to the original string
1882        unsafe {
1883            assert_eq!(*b.content.offset(0), b'h');
1884            assert_eq!(*b.content.offset(4), b'o');
1885            assert_eq!(*b.content.offset(5), 0);
1886        }
1887
1888        buf_free(buf); // Should not free the static content
1889    }
1890
1891    #[test]
1892    fn test_buf_add() {
1893        let buf = buf_create(10);
1894        assert!(!buf.is_null());
1895
1896        let s1: &[u8] = b"Hello\0";
1897        let ret = buf_add(buf, s1.as_ptr() as *const xmlChar, 5);
1898        assert_eq!(ret, 5);
1899
1900        let b = unsafe { &*buf };
1901        assert_eq!(b.use_, 5);
1902        unsafe {
1903            assert_eq!(*b.content.offset(0), b'H');
1904            assert_eq!(*b.content.offset(4), b'o');
1905            assert_eq!(*b.content.offset(5), 0); // null-terminated
1906        }
1907
1908        // Add more to trigger growth
1909        let s2: &[u8] = b" World!\0";
1910        let ret = buf_add(buf, s2.as_ptr() as *const xmlChar, 7);
1911        assert_eq!(ret, 7);
1912
1913        let b = unsafe { &*buf };
1914        assert_eq!(b.use_, 12);
1915        unsafe {
1916            assert_eq!(*b.content.offset(6), b'W');
1917            assert_eq!(*b.content.offset(11), b'!');
1918            assert_eq!(*b.content.offset(12), 0);
1919        }
1920
1921        buf_free(buf);
1922    }
1923
1924    #[test]
1925    fn test_buf_add_null() {
1926        let buf = buf_create(10);
1927        let ret = buf_add(buf, ptr::null(), 5);
1928        assert_eq!(ret, 0);
1929        buf_free(buf);
1930    }
1931
1932    #[test]
1933    fn test_buf_cat() {
1934        let buf = buf_create(10);
1935        let s: &[u8] = b"Hello\0";
1936        let ret = buf_cat(buf, s.as_ptr() as *const xmlChar);
1937        assert_eq!(ret, 5);
1938
1939        let b = unsafe { &*buf };
1940        assert_eq!(b.use_, 5);
1941
1942        buf_free(buf);
1943    }
1944
1945    #[test]
1946    fn test_buf_ccat() {
1947        let buf = buf_create(10);
1948        let ret = buf_ccat(buf, b'A' as xmlChar);
1949        assert_eq!(ret, 1);
1950
1951        let b = unsafe { &*buf };
1952        assert_eq!(b.use_, 1);
1953        unsafe {
1954            assert_eq!(*b.content, b'A');
1955        }
1956
1957        buf_free(buf);
1958    }
1959
1960    #[test]
1961    fn test_buf_empty() {
1962        let buf = buf_create(10);
1963        let s: &[u8] = b"Hello\0";
1964        buf_add(buf, s.as_ptr() as *const xmlChar, 5);
1965        assert_eq!(unsafe { &*buf }.use_, 5);
1966
1967        buf_empty(buf);
1968        let b = unsafe { &*buf };
1969        assert_eq!(b.use_, 0);
1970        unsafe {
1971            assert_eq!(*b.content, 0);
1972        }
1973
1974        buf_free(buf);
1975    }
1976
1977    #[test]
1978    fn test_buf_content() {
1979        let buf = buf_create(10);
1980        let content = buf_content(buf);
1981        assert!(!content.is_null());
1982        buf_free(buf);
1983    }
1984
1985    #[test]
1986    fn test_buf_length() {
1987        let buf = buf_create(10);
1988        assert_eq!(buf_length(buf), 0);
1989
1990        let s: &[u8] = b"Hi\0";
1991        buf_add(buf, s.as_ptr() as *const xmlChar, 2);
1992        assert_eq!(buf_length(buf), 2);
1993
1994        buf_free(buf);
1995    }
1996
1997    #[test]
1998    fn test_buf_shrink() {
1999        let buf = buf_create(10);
2000        let s: &[u8] = b"Hello World\0";
2001        buf_add(buf, s.as_ptr() as *const xmlChar, 11);
2002        assert_eq!(buf_length(buf), 11);
2003
2004        buf_shrink(buf, 5);
2005        assert_eq!(buf_length(buf), 6);
2006
2007        let b = unsafe { &*buf };
2008        unsafe {
2009            assert_eq!(*b.content.offset(6), 0); // null-terminated
2010        }
2011
2012        // Shrink more than available
2013        buf_shrink(buf, 100);
2014        assert_eq!(buf_length(buf), 0);
2015
2016        buf_free(buf);
2017    }
2018
2019    #[test]
2020    fn test_buf_grow() {
2021        let buf = buf_create(10);
2022        assert!(unsafe { &*buf }.size >= 10);
2023
2024        let ret = buf_grow(buf, 1000);
2025        assert_eq!(ret, 0);
2026        assert!(unsafe { &*buf }.size >= 1000);
2027
2028        buf_free(buf);
2029    }
2030
2031    #[test]
2032    fn test_buf_free_null() {
2033        buf_free(ptr::null_mut()); // Should not crash
2034    }
2035
2036    // ── xmlBuf tests ───────────────────────────────────────────────────────
2037
2038    #[test]
2039    fn test_xml_buf_create_free() {
2040        let buf = xml_buf_create(100);
2041        assert!(!buf.is_null());
2042
2043        let b = unsafe { &*buf };
2044        assert!(!b.content.is_null());
2045        assert_eq!(b.use_, 0);
2046        assert!(b.size >= 100);
2047        assert_eq!(b.error, 0);
2048        assert_eq!(b.buffer, 0);
2049        assert_eq!(b.io, 0);
2050
2051        xml_buf_free(buf);
2052    }
2053
2054    #[test]
2055    fn test_xml_buf_add() {
2056        let buf = xml_buf_create(10);
2057        let s: &[u8] = b"Hello\0";
2058        let ret = xml_buf_add(buf, s.as_ptr() as *const xmlChar, 5);
2059        assert_eq!(ret, 5);
2060
2061        let b = unsafe { &*buf };
2062        assert_eq!(b.use_, 5);
2063
2064        xml_buf_free(buf);
2065    }
2066
2067    #[test]
2068    fn test_xml_buf_cat() {
2069        let buf = xml_buf_create(10);
2070        let s: &[u8] = b"Hello\0";
2071        let ret = xml_buf_cat(buf, s.as_ptr() as *const xmlChar);
2072        assert_eq!(ret, 5);
2073
2074        xml_buf_free(buf);
2075    }
2076
2077    #[test]
2078    fn test_xml_buf_content() {
2079        let buf = xml_buf_create(10);
2080        let content = xml_buf_content(buf);
2081        assert!(!content.is_null());
2082        xml_buf_free(buf);
2083    }
2084
2085    #[test]
2086    fn test_xml_buf_length() {
2087        let buf = xml_buf_create(10);
2088        assert_eq!(xml_buf_length(buf), 0);
2089
2090        let s: &[u8] = b"Hi\0";
2091        xml_buf_add(buf, s.as_ptr() as *const xmlChar, 2);
2092        assert_eq!(xml_buf_length(buf), 2);
2093
2094        xml_buf_free(buf);
2095    }
2096
2097    #[test]
2098    fn test_xml_buf_grow() {
2099        let buf = xml_buf_create(10);
2100        let ret = xml_buf_grow(buf, 500);
2101        assert_eq!(ret, 0);
2102        assert!(unsafe { &*buf }.size >= 500);
2103
2104        xml_buf_free(buf);
2105    }
2106
2107    #[test]
2108    fn test_xml_buf_shrink() {
2109        let buf = xml_buf_create(10);
2110        let s: &[u8] = b"Hello\0";
2111        xml_buf_add(buf, s.as_ptr() as *const xmlChar, 5);
2112        assert_eq!(xml_buf_length(buf), 5);
2113
2114        xml_buf_shrink(buf, 3);
2115        assert_eq!(xml_buf_length(buf), 2);
2116
2117        xml_buf_free(buf);
2118    }
2119
2120    // ── Input buffer tests ─────────────────────────────────────────────────
2121
2122    #[test]
2123    fn test_input_buffer_create_mem() {
2124        let data = c("Hello XML");
2125        let buf = input_buffer_create_mem(data.as_ptr(), 9, 0); // NONE encoding
2126        assert!(!buf.is_null());
2127
2128        let b = unsafe { &*buf };
2129        assert!(b.readcallback.is_none());
2130        assert!(!b.buffer.is_null());
2131        assert_eq!(b.error, 0);
2132
2133        // Read back
2134        let mut out = [0i8; 16];
2135        let ret = input_buffer_read(buf, out.as_mut_ptr(), 16);
2136        assert_eq!(ret, 9);
2137        assert_eq!(&out[..9], i8_slice(b"Hello XML"));
2138
2139        input_buffer_free(buf);
2140    }
2141
2142    #[test]
2143    fn test_input_buffer_create_mem_empty() {
2144        let buf = input_buffer_create_mem(ptr::null(), 0, 0);
2145        assert!(buf.is_null());
2146    }
2147
2148    #[test]
2149    fn test_input_buffer_read_partial() {
2150        let data = c("Hello XML World");
2151        let buf = input_buffer_create_mem(data.as_ptr(), 15, 0);
2152        assert!(!buf.is_null());
2153
2154        // Read in two parts
2155        let mut out1 = [0i8; 5];
2156        let ret = input_buffer_read(buf, out1.as_mut_ptr(), 5);
2157        assert_eq!(ret, 5);
2158        assert_eq!(&out1[..5], i8_slice(b"Hello"));
2159
2160        let mut out2 = [0i8; 10];
2161        let ret = input_buffer_read(buf, out2.as_mut_ptr(), 10);
2162        assert_eq!(ret, 10);
2163        assert_eq!(&out2[..10], i8_slice(b" XML World"));
2164
2165        input_buffer_free(buf);
2166    }
2167
2168    #[test]
2169    fn test_input_buffer_push() {
2170        let buf = input_buffer_create_io(None, None, ptr::null_mut(), 0);
2171        assert!(!buf.is_null());
2172
2173        let data1 = c("<root>");
2174        let ret = input_buffer_push(buf, data1.as_ptr(), 6);
2175        assert_eq!(ret, 6);
2176
2177        let data2 = c("</root>");
2178        let ret = input_buffer_push(buf, data2.as_ptr(), 7);
2179        assert_eq!(ret, 7);
2180
2181        // Read back the pushed data
2182        let mut out = [0i8; 32];
2183        let ret = input_buffer_read(buf, out.as_mut_ptr(), 32);
2184        assert_eq!(ret, 13);
2185        assert_eq!(&out[..13], i8_slice(b"<root></root>"));
2186
2187        input_buffer_free(buf);
2188    }
2189
2190    #[test]
2191    fn test_input_buffer_set_encoder() {
2192        let buf = input_buffer_create_mem(ptr::null(), 0, 0);
2193        // Create a fresh buffer
2194        let data = c("test");
2195        let buf = input_buffer_create_mem(data.as_ptr(), 4, 0);
2196        assert!(!buf.is_null());
2197
2198        // Set encoder to null (no encoding)
2199        input_buffer_set_encoder(buf, ptr::null_mut());
2200        let b = unsafe { &*buf };
2201        assert!(b.encoder.is_null());
2202
2203        input_buffer_free(buf);
2204    }
2205
2206    // ── Output buffer tests ────────────────────────────────────────────────
2207
2208    #[test]
2209    fn test_output_buffer_create_buffer() {
2210        let internal_buf = buf_create(100);
2211        assert!(!internal_buf.is_null());
2212
2213        let obuf = output_buffer_create_buffer(internal_buf, ptr::null_mut());
2214        assert!(!obuf.is_null());
2215
2216        // Write data
2217        let data = c("Hello Output");
2218        let ret = output_buffer_write(obuf, 12, data.as_ptr());
2219        assert_eq!(ret, 12);
2220
2221        // Flush - this writes buffered data via callback to the target buffer
2222        let flushed = output_buffer_flush(obuf);
2223        assert_eq!(flushed, 12);
2224
2225        // After flush, the internal buffer should be empty again
2226        let content = output_buffer_get_content(obuf);
2227        assert!(content.is_null() || unsafe { *content } == 0);
2228
2229        // The data was written via callback to the context buffer (internal_buf)
2230        let ctx = unsafe { (*obuf).context as *mut _xmlBuffer };
2231        let ctx_b = unsafe { &*ctx };
2232        assert_eq!(ctx_b.use_, 12);
2233        unsafe {
2234            assert_eq!(*ctx_b.content.offset(0), b'H' as xmlChar);
2235        }
2236
2237        // NOTE: output_buffer_close frees all internal buffers including
2238        // the internal_buf passed as target. Don't free it again here.
2239        output_buffer_close(obuf);
2240    }
2241
2242    #[test]
2243    fn test_output_buffer_write_string() {
2244        let internal_buf = buf_create(100);
2245        let obuf = output_buffer_create_buffer(internal_buf, ptr::null_mut());
2246        assert!(!obuf.is_null());
2247
2248        let s = c("Hello");
2249        let ret = output_buffer_write_string(obuf, s.as_ptr());
2250        assert_eq!(ret, 5);
2251
2252        // output_buffer_close frees internal_buf via obuf.buffer
2253        output_buffer_close(obuf);
2254    }
2255
2256    #[test]
2257    fn test_output_buffer_write_char() {
2258        let internal_buf = buf_create(100);
2259        let obuf = output_buffer_create_buffer(internal_buf, ptr::null_mut());
2260        assert!(!obuf.is_null());
2261
2262        let ret = output_buffer_write_char(obuf, b'X' as c_char);
2263        assert_eq!(ret, 1);
2264
2265        output_buffer_close(obuf);
2266    }
2267
2268    #[test]
2269    fn test_output_buffer_get_content() {
2270        let internal_buf = buf_create(100);
2271        let obuf = output_buffer_create_buffer(internal_buf, ptr::null_mut());
2272        assert!(!obuf.is_null());
2273
2274        let content = output_buffer_get_content(obuf);
2275        assert!(!content.is_null());
2276
2277        output_buffer_close(obuf);
2278    }
2279
2280    // ── File I/O tests ─────────────────────────────────────────────────────
2281
2282    #[test]
2283    fn test_check_file_exists() {
2284        // This file should exist
2285        let exists = check_file_exists(c("/dev/null").as_ptr());
2286        assert!(exists == 1);
2287
2288        // This file should not exist
2289        let not_exists = check_file_exists(c("/tmp/__nonexistent_file_xyz123__").as_ptr());
2290        assert!(not_exists == 0);
2291    }
2292
2293    #[test]
2294    fn test_read_write_file() {
2295        let tmpfile = c("/tmp/libxml_rs_test_io_file.txt");
2296
2297        // Write data to file
2298        let data = c("Hello File I/O!");
2299        let ret = write_memory_to_file(tmpfile.as_ptr(), data.as_ptr(), 15);
2300        assert_eq!(ret, 0);
2301
2302        // Check it exists
2303        assert!(check_file_exists(tmpfile.as_ptr()) == 1);
2304
2305        // Read it back
2306        let mut size: c_int = 0;
2307        let read_data = read_file_to_memory(tmpfile.as_ptr(), &mut size as *mut c_int);
2308        assert!(!read_data.is_null());
2309        assert_eq!(size, 15);
2310
2311        unsafe {
2312            let slice = std::slice::from_raw_parts(read_data as *const u8, size as usize);
2313            assert_eq!(slice, b"Hello File I/O!");
2314        }
2315
2316        unsafe { xmlFree(read_data as *mut c_void) };
2317
2318        // Clean up
2319        std::fs::remove_file("/tmp/libxml_rs_test_io_file.txt").ok();
2320    }
2321
2322    #[test]
2323    fn test_read_file_nonexistent() {
2324        let result = read_file_to_memory(
2325            c("/tmp/__nonexistent_file_xyz456__").as_ptr(),
2326            ptr::null_mut(),
2327        );
2328        assert!(result.is_null());
2329    }
2330
2331    #[test]
2332    fn test_write_file_null() {
2333        let ret = write_memory_to_file(ptr::null(), c("data").as_ptr(), 4);
2334        assert_eq!(ret, -1);
2335    }
2336
2337    #[test]
2338    fn test_get_cwd() {
2339        let cwd = get_cwd();
2340        assert!(!cwd.is_null());
2341        unsafe {
2342            let s = CStr::from_ptr(cwd);
2343            assert!(!s.to_bytes().is_empty());
2344            xmlFree(cwd as *mut c_void);
2345        }
2346    }
2347
2348    // ── Edge case tests ────────────────────────────────────────────────────
2349
2350    #[test]
2351    fn test_buf_add_large_data() {
2352        let buf = buf_create(10);
2353        let mut large_data = Vec::new();
2354        large_data.resize(5000, b'X');
2355        large_data.push(0);
2356
2357        let ret = buf_add(buf, large_data.as_ptr() as *const xmlChar, 5000);
2358        assert_eq!(ret, 5000);
2359
2360        let b = unsafe { &*buf };
2361        assert_eq!(b.use_, 5000);
2362        assert!(b.size >= 5001);
2363
2364        buf_free(buf);
2365    }
2366
2367    #[test]
2368    fn test_input_buffer_free_null() {
2369        input_buffer_free(ptr::null_mut()); // Should not crash
2370    }
2371
2372    #[test]
2373    fn test_output_buffer_close_null() {
2374        let ret = output_buffer_close(ptr::null_mut());
2375        assert_eq!(ret, -1);
2376    }
2377
2378    #[test]
2379    fn test_buf_add_to_immutable() {
2380        let s: &[u8] = b"static\0";
2381        let buf = buf_create_static(s.as_ptr() as *const xmlChar, 6);
2382        assert!(!buf.is_null());
2383
2384        // Try to add to immutable buffer
2385        let data: &[u8] = b"more\0";
2386        let ret = buf_add(buf, data.as_ptr() as *const xmlChar, 4);
2387        assert_eq!(ret, -1); // Should fail
2388
2389        buf_free(buf);
2390    }
2391
2392    // ── Encoding integration test ──────────────────────────────────────────
2393
2394    #[test]
2395    fn test_encoding_from_int() {
2396        assert_eq!(
2397            encoding_from_int(0),
2398            xmlCharEncoding::XML_CHAR_ENCODING_NONE
2399        );
2400        assert_eq!(
2401            encoding_from_int(1),
2402            xmlCharEncoding::XML_CHAR_ENCODING_UTF8
2403        );
2404        assert_eq!(
2405            encoding_from_int(10),
2406            xmlCharEncoding::XML_CHAR_ENCODING_8859_1
2407        );
2408        assert_eq!(
2409            encoding_from_int(22),
2410            xmlCharEncoding::XML_CHAR_ENCODING_ASCII
2411        );
2412        assert_eq!(
2413            encoding_from_int(999),
2414            xmlCharEncoding::XML_CHAR_ENCODING_ERROR
2415        );
2416    }
2417
2418    #[test]
2419    fn test_find_handler_for_encoding() {
2420        // UTF-8 should return null (no conversion needed)
2421        let handler = find_handler_for_encoding(1);
2422        assert!(handler.is_null());
2423
2424        // NONE should return null
2425        let handler = find_handler_for_encoding(0);
2426        assert!(handler.is_null());
2427
2428        // ERROR should return null
2429        let handler = find_handler_for_encoding(-1);
2430        assert!(handler.is_null());
2431    }
2432
2433    #[test]
2434    fn test_input_buffer_with_encoding_latin1() {
2435        // Initialize encodings
2436        encoding::init_encodings();
2437
2438        // Latin-1 byte 0xE9 = é in Latin-1, which is U+00E9 = 0xC3 0xA9 in UTF-8
2439        let latin1_data: &[u8] = &[0x48, 0x65, 0x6C, 0x6C, 0xF6, 0x00]; // "Hellö" in Latin-1
2440
2441        let buf = input_buffer_create_mem(
2442            latin1_data.as_ptr() as *const c_char,
2443            5,
2444            10, // ISO-8859-1
2445        );
2446        assert!(!buf.is_null());
2447
2448        // Read back as UTF-8
2449        let mut out = [0i8; 16];
2450        let ret = input_buffer_read(buf, out.as_mut_ptr(), 16);
2451        assert!(ret > 0);
2452
2453        // The output should be UTF-8 encoded "Hellö" = b"Hell\xC3\xB6"
2454        let expected = b"Hell\xC3\xB6";
2455        assert_eq!(&out[..ret as usize], i8_slice(expected));
2456
2457        input_buffer_free(buf);
2458    }
2459
2460    #[test]
2461    fn test_output_buffer_with_encoding() {
2462        // Initialize encodings
2463        encoding::init_encodings();
2464
2465        // Find Latin-1 encoder
2466        let enc_name: &[u8] = b"ISO-8859-1\0";
2467        let handler = encoding::find_encoding_handler(enc_name.as_ptr() as *const xmlChar);
2468        assert!(!handler.is_null(), "Latin-1 handler should be available");
2469
2470        let internal_buf = buf_create(100);
2471        let obuf = output_buffer_create_buffer(internal_buf, handler);
2472        assert!(!obuf.is_null());
2473
2474        // Write UTF-8 "Hellö" = [0x48, 0x65, 0x6C, 0x6C, 0xC3, 0xB6]
2475        let utf8_data = unsafe { c_bytes(&[0x48, 0x65, 0x6C, 0x6C, 0xC3, 0xB6]) };
2476        let ret = output_buffer_write(obuf, 6, utf8_data.as_ptr());
2477        assert_eq!(ret, 6);
2478
2479        // Flush - this should convert via Latin-1 encoder
2480        let flushed = output_buffer_flush(obuf);
2481        assert!(flushed > 0);
2482
2483        // The context buffer should have the Latin-1 encoded data
2484        let ctx = unsafe { (*obuf).context as *mut _xmlBuffer };
2485        let ctx_b = unsafe { &*ctx };
2486        // Latin-1 "Hellö" = [0x48, 0x65, 0x6C, 0x6C, 0xF6]
2487        assert_eq!(ctx_b.use_, 5);
2488        unsafe {
2489            assert_eq!(*ctx_b.content.offset(0), 0x48); // 'H'
2490            assert_eq!(*ctx_b.content.offset(4), 0xF6); // 'ö'
2491        }
2492
2493        // output_buffer_close frees the internal buffer, but NOT the
2494        // context buffer (internal_buf) since that's the user's buffer.
2495        // Actually it frees obuf.buffer (a separate internal buffer) and
2496        // obuf.conv. The context buffer is NOT freed by output_buffer_close.
2497        // However, obuf.buffer was set to internal_buf in the OLD code.
2498        // With the fix, obuf.buffer is a separate internal buffer, so
2499        // we still need to free internal_buf ourselves.
2500        //
2501        // Wait -- let me check what output_buffer_close frees:
2502        // - ob.buffer: this is the internal buffer (SEPARATE from internal_buf)
2503        // - ob.conv: the conversion buffer
2504        // - The context (internal_buf) is NOT freed by output_buffer_close
2505        //
2506        // Actually, let me re-read the function...
2507        // output_buffer_close frees ob.buffer and ob.conv.
2508        // The context is the user's buffer (internal_buf), which is NOT freed.
2509        // So we DO need to free internal_buf here.
2510        output_buffer_close(obuf);
2511        buf_free(internal_buf);
2512    }
2513
2514    // ── Input buffer from fd (requires /dev/null) ──────────────────────────
2515
2516    #[test]
2517    fn test_input_buffer_create_fd() {
2518        // Open /dev/null and create an fd-based input buffer
2519        let fd =
2520            unsafe { libc::open(b"/dev/null\0" as *const u8 as *const c_char, libc::O_RDONLY) };
2521        assert!(fd >= 0);
2522
2523        let buf = input_buffer_create_fd(fd, 0);
2524        assert!(!buf.is_null());
2525
2526        // Reading from /dev/null should return 0 (EOF)
2527        let mut out = [0i8; 16];
2528        let ret = input_buffer_read(buf, out.as_mut_ptr(), 16);
2529        assert_eq!(ret, 0);
2530
2531        input_buffer_free(buf); // This will also close the fd via closecallback
2532    }
2533
2534    // ── Input buffer create_io ─────────────────────────────────────────────
2535
2536    #[test]
2537    fn test_input_buffer_create_io() {
2538        // Create a simple callback that provides data
2539        static mut TEST_DATA: &[u8] = b"Hello from callback!";
2540        static mut CALLED: bool = false;
2541
2542        unsafe extern "C" fn test_read(
2543            _ctx: *mut c_void,
2544            buffer: *mut c_char,
2545            len: c_int,
2546        ) -> c_int {
2547            if CALLED {
2548                return 0; // EOF on second call
2549            }
2550            CALLED = true;
2551            let data = TEST_DATA;
2552            let to_copy = (data.len() as c_int).min(len);
2553            if to_copy > 0 {
2554                std::ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut u8, to_copy as usize);
2555            }
2556            to_copy
2557        }
2558
2559        unsafe extern "C" fn test_close(_ctx: *mut c_void) -> c_int {
2560            0
2561        }
2562
2563        let buf = input_buffer_create_io(
2564            Some(test_read as xmlInputReadCallback),
2565            Some(test_close as xmlInputCloseCallback),
2566            ptr::null_mut(),
2567            0,
2568        );
2569        assert!(!buf.is_null());
2570
2571        let mut out = [0i8; 32];
2572        let ret = input_buffer_read(buf, out.as_mut_ptr(), 32);
2573        assert!(ret > 0);
2574
2575        input_buffer_free(buf);
2576    }
2577}