Skip to main content

libxml_rs/xml/encoding/
mod.rs

1//! Character encoding handling (§22, §85 Phase 4).
2//!
3//! Encoding detection, XML declaration encoding, BOM behavior, UTF-8/UTF-16
4//! validity, legacy encodings, conversion errors, output conversion,
5//! serializer fallback, custom encoding handlers.
6//!
7//! # Architecture
8//!
9//! ```text
10//! ABI exports (exports_xml2.rs)  ←  pub(crate) functions in this module
11//!                                           ↕
12//!                           Encoding handler registry (global RwLock)
13//!                                           ↕
14//!              Built-in handlers: UTF-8, UTF-16LE, UTF-16BE, Latin-1, ASCII
15//! ```
16//!
17//! The internal encoding is always UTF-8. All conversions go to/from UTF-8.
18//! The handler registry stores `_xmlCharEncodingHandler` structs that contain
19//! function pointers for input (→UTF-8) and output (UTF-8→) conversion.
20
21#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
22
23use std::ffi::CStr;
24use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_void};
25use std::ptr;
26use std::sync::atomic::{AtomicBool, Ordering};
27
28use once_cell::sync::Lazy;
29use parking_lot::RwLock;
30
31use crate::abi::allocator::{xmlFree, xmlMalloc, xmlRealloc};
32use crate::abi::callbacks::{xmlCharEncodingInputFunc, xmlCharEncodingOutputFunc};
33use crate::abi::constants::XML_MAX_ENCODING_NAME_LEN;
34use crate::abi::structs::{
35    _xmlBuffer, _xmlCharEncodingHandler, EncodingInputUnion, EncodingOutputUnion,
36};
37use crate::abi::types::{xmlChar, xmlCharEncoding, xmlCharPtr};
38
39// ── Constants ──────────────────────────────────────────────────────────────
40
41/// Maximum bytes needed per character for any supported encoding.
42const MAX_CHAR_BYTES: usize = 6;
43
44/// UTF-8 BOM bytes.
45const UTF8_BOM: [u8; 3] = [0xEF, 0xBB, 0xBF];
46
47/// UTF-16LE BOM bytes.
48const UTF16LE_BOM: [u8; 2] = [0xFF, 0xFE];
49
50/// UTF-16BE BOM bytes.
51const UTF16BE_BOM: [u8; 2] = [0xFE, 0xFF];
52
53// ── Global handler registry ────────────────────────────────────────────────
54
55/// A raw pointer wrapper that implements `Send` and `Sync`.
56///
57/// This is safe because all access to the global handler registry is
58/// serialized through the `RwLock`, and handlers are only accessed from
59/// trusted internal code.
60#[derive(Clone, Copy)]
61struct HandlerPtr(*mut _xmlCharEncodingHandler);
62
63unsafe impl Send for HandlerPtr {}
64unsafe impl Sync for HandlerPtr {}
65
66/// Global list of registered encoding handlers, protected by a read-write lock.
67static ENCODING_HANDLERS: Lazy<RwLock<Vec<HandlerPtr>>> = Lazy::new(|| RwLock::new(Vec::new()));
68
69/// Whether the built-in encoding handlers have been initialized.
70static ENCODING_INITIALIZED: AtomicBool = AtomicBool::new(false);
71
72// ═══════════════════════════════════════════════════════════════════════════════
73// 1. Encoding detection
74// ═══════════════════════════════════════════════════════════════════════════════
75
76/// Determine encoding from BOM bytes.
77///
78/// Returns `XML_CHAR_ENCODING_NONE` if no BOM is present, or if `data` is empty.
79/// Otherwise returns the matching encoding enum value.
80pub(crate) fn detect_encoding_from_bom(data: &[u8]) -> xmlCharEncoding {
81    if data.len() >= 3 && data[0..3] == UTF8_BOM {
82        xmlCharEncoding::XML_CHAR_ENCODING_UTF8
83    } else if data.len() >= 2 && data[0..2] == UTF16LE_BOM {
84        xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE
85    } else if data.len() >= 2 && data[0..2] == UTF16BE_BOM {
86        xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE
87    } else {
88        xmlCharEncoding::XML_CHAR_ENCODING_NONE
89    }
90}
91
92/// Determine encoding from an XML declaration's `encoding` attribute.
93///
94/// Scans for `<?xml ... encoding="..." ?>` and returns the encoding name
95/// as a byte vector (lowercased), or `None` if not found.
96pub(crate) fn detect_encoding_from_declaration(data: &[u8]) -> Option<Vec<u8>> {
97    // Look for "<?xml" at the start (possibly after BOM)
98    let start = if data.len() >= 3 && data[0..3] == UTF8_BOM {
99        3
100    } else if data.len() >= 2 && (data[0..2] == UTF16LE_BOM || data[0..2] == UTF16BE_BOM) {
101        // For UTF-16, we can't easily scan the bytes; skip
102        return None;
103    } else {
104        0
105    };
106
107    let remaining = &data[start..];
108
109    // Must start with "<?xml"
110    if remaining.len() < 5 || !remaining[0..5].eq_ignore_ascii_case(b"<?xml") {
111        return None;
112    }
113
114    // Find the end of the PI: "?>"
115    let pi_end = remaining.windows(2).position(|w| w == b"?>")?;
116    let decl_content = &remaining[5..pi_end];
117
118    // Look for "encoding" attribute
119    let decl_str = core::str::from_utf8(decl_content).ok()?;
120    let lower = decl_str.to_ascii_lowercase();
121
122    // Find "encoding" keyword
123    let enc_pos = lower.find("encoding")?;
124
125    // After "encoding", expect optional whitespace and '='
126    let after_enc = &decl_content[enc_pos + 8..];
127    let after_enc_str = core::str::from_utf8(after_enc).ok()?;
128    let after_enc_trimmed = after_enc_str.trim_start();
129
130    if !after_enc_trimmed.starts_with('=') {
131        return None;
132    }
133
134    let after_eq = after_enc_trimmed[1..].trim_start();
135
136    // Expect quote character
137    let quote = after_eq.chars().next()?;
138    if quote != '"' && quote != '\'' {
139        return None;
140    }
141
142    // Find matching closing quote
143    let value_end = after_eq[1..].find(quote)?;
144    let encoding_value = &after_eq[1..=value_end];
145
146    Some(encoding_value.to_ascii_lowercase().as_bytes().to_vec())
147}
148
149/// Parse an encoding name string to an `xmlCharEncoding` enum value.
150///
151/// Matching is case-insensitive. Common aliases are recognized.
152/// Returns `XML_CHAR_ENCODING_ERROR` if the name is not recognized.
153pub(crate) fn encoding_from_name(name: &[u8]) -> xmlCharEncoding {
154    let s = core::str::from_utf8(name).unwrap_or("");
155    let s = s.trim().to_ascii_lowercase();
156
157    match s.as_str() {
158        // UTF-8
159        "utf-8" | "utf8" => xmlCharEncoding::XML_CHAR_ENCODING_UTF8,
160
161        // UTF-16
162        "utf-16" | "utf-16le" | "utf16le" => xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE,
163        "utf-16be" | "utf16be" => xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE,
164
165        // ISO-8859 variants
166        "iso-8859-1" | "iso_8859-1" | "latin1" | "latin-1" | "l1" | "cp819" | "ibm819"
167        | "iso-ir-100" | "iso_8859-1:1987" => xmlCharEncoding::XML_CHAR_ENCODING_8859_1,
168        "iso-8859-2" | "iso_8859-2" | "latin2" | "latin-2" | "l2" => {
169            xmlCharEncoding::XML_CHAR_ENCODING_8859_2
170        }
171        "iso-8859-3" | "iso_8859-3" | "latin3" | "latin-3" | "l3" => {
172            xmlCharEncoding::XML_CHAR_ENCODING_8859_3
173        }
174        "iso-8859-4" | "iso_8859-4" | "latin4" | "latin-4" | "l4" => {
175            xmlCharEncoding::XML_CHAR_ENCODING_8859_4
176        }
177        "iso-8859-5" | "iso_8859-5" | "cyrillic" => xmlCharEncoding::XML_CHAR_ENCODING_8859_5,
178        "iso-8859-6" | "iso_8859-6" | "arabic" => xmlCharEncoding::XML_CHAR_ENCODING_8859_6,
179        "iso-8859-7" | "iso_8859-7" | "greek" => xmlCharEncoding::XML_CHAR_ENCODING_8859_7,
180        "iso-8859-8" | "iso_8859-8" | "hebrew" => xmlCharEncoding::XML_CHAR_ENCODING_8859_8,
181        "iso-8859-9" | "iso_8859-9" | "latin5" | "latin-5" | "l5" | "turkish" => {
182            xmlCharEncoding::XML_CHAR_ENCODING_8859_9
183        }
184
185        // ASCII
186        "ascii" | "us-ascii" | "us" | "ansi_x3.4-1968" | "ansi_x3.4-1986" | "iso-ir-6"
187        | "iso_646.irv:1991" | "cp367" | "ibm367" => xmlCharEncoding::XML_CHAR_ENCODING_ASCII,
188
189        // East Asian
190        "iso-2022-jp" | "iso2022-jp" => xmlCharEncoding::XML_CHAR_ENCODING_2022_JP,
191        "shift_jis" | "shift-jis" | "sjis" | "cp932" => {
192            xmlCharEncoding::XML_CHAR_ENCODING_SHIFT_JIS
193        }
194        "euc-jp" | "eucjp" => xmlCharEncoding::XML_CHAR_ENCODING_EUC_JP,
195
196        // UCS/Unicode variants
197        "ucs-4" | "ucs4" => xmlCharEncoding::XML_CHAR_ENCODING_UCS4LE,
198        "ucs-4le" | "ucs4le" => xmlCharEncoding::XML_CHAR_ENCODING_UCS4LE,
199        "ucs-4be" | "ucs4be" => xmlCharEncoding::XML_CHAR_ENCODING_UCS4BE,
200        "ucs-2" | "ucs2" => xmlCharEncoding::XML_CHAR_ENCODING_UCS2,
201
202        // EBCDIC
203        "ebcdic" | "cp037" | "ibm037" => xmlCharEncoding::XML_CHAR_ENCODING_EBCDIC,
204
205        _ => xmlCharEncoding::XML_CHAR_ENCODING_ERROR,
206    }
207}
208
209/// Get the canonical name for an encoding as a byte slice.
210///
211/// Returns `None` for `XML_CHAR_ENCODING_ERROR` and `XML_CHAR_ENCODING_NONE`.
212pub(crate) fn encoding_name(enc: xmlCharEncoding) -> Option<&'static [u8]> {
213    match enc {
214        xmlCharEncoding::XML_CHAR_ENCODING_UTF8 => Some(b"UTF-8" as &[u8]),
215        xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE => Some(b"UTF-16LE" as &[u8]),
216        xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE => Some(b"UTF-16BE" as &[u8]),
217        xmlCharEncoding::XML_CHAR_ENCODING_UCS4LE => Some(b"UCS-4LE" as &[u8]),
218        xmlCharEncoding::XML_CHAR_ENCODING_UCS4BE => Some(b"UCS-4BE" as &[u8]),
219        xmlCharEncoding::XML_CHAR_ENCODING_EBCDIC => Some(b"EBCDIC" as &[u8]),
220        xmlCharEncoding::XML_CHAR_ENCODING_UCS4_2143 => Some(b"UCS-4-2143" as &[u8]),
221        xmlCharEncoding::XML_CHAR_ENCODING_UCS4_3412 => Some(b"UCS-4-3412" as &[u8]),
222        xmlCharEncoding::XML_CHAR_ENCODING_UCS2 => Some(b"UCS-2" as &[u8]),
223        xmlCharEncoding::XML_CHAR_ENCODING_8859_1 => Some(b"ISO-8859-1" as &[u8]),
224        xmlCharEncoding::XML_CHAR_ENCODING_8859_2 => Some(b"ISO-8859-2" as &[u8]),
225        xmlCharEncoding::XML_CHAR_ENCODING_8859_3 => Some(b"ISO-8859-3" as &[u8]),
226        xmlCharEncoding::XML_CHAR_ENCODING_8859_4 => Some(b"ISO-8859-4" as &[u8]),
227        xmlCharEncoding::XML_CHAR_ENCODING_8859_5 => Some(b"ISO-8859-5" as &[u8]),
228        xmlCharEncoding::XML_CHAR_ENCODING_8859_6 => Some(b"ISO-8859-6" as &[u8]),
229        xmlCharEncoding::XML_CHAR_ENCODING_8859_7 => Some(b"ISO-8859-7" as &[u8]),
230        xmlCharEncoding::XML_CHAR_ENCODING_8859_8 => Some(b"ISO-8859-8" as &[u8]),
231        xmlCharEncoding::XML_CHAR_ENCODING_8859_9 => Some(b"ISO-8859-9" as &[u8]),
232        xmlCharEncoding::XML_CHAR_ENCODING_2022_JP => Some(b"ISO-2022-JP" as &[u8]),
233        xmlCharEncoding::XML_CHAR_ENCODING_SHIFT_JIS => Some(b"SHIFT_JIS" as &[u8]),
234        xmlCharEncoding::XML_CHAR_ENCODING_EUC_JP => Some(b"EUC-JP" as &[u8]),
235        xmlCharEncoding::XML_CHAR_ENCODING_ASCII => Some(b"ASCII" as &[u8]),
236        _ => None,
237    }
238}
239
240// ═══════════════════════════════════════════════════════════════════════════════
241// 2. UTF-8 validation
242// ═══════════════════════════════════════════════════════════════════════════════
243
244/// Check if a byte sequence is valid UTF-8.
245///
246/// Returns `true` if the entire slice is valid UTF-8, `false` otherwise.
247pub(crate) fn utf8_valid(data: &[u8]) -> bool {
248    core::str::from_utf8(data).is_ok()
249}
250
251/// Check if a Unicode codepoint is a valid XML character.
252///
253/// Per XML 1.0 (Fifth Edition) §2.2, the valid character ranges are:
254/// - `#x9` (tab)
255/// - `#xA` (LF)
256/// - `#xD` (CR)
257/// - `#x20` – `#xD7FF`
258/// - `#xE000` – `#xFFFD`
259/// - `#x10000` – `#x10FFFF`
260///
261/// Excludes surrogate halves (`#xD800` – `#xDFFF`) and `#xFFFE`/`#xFFFF`.
262pub(crate) fn is_valid_xml_char(cp: u32) -> bool {
263    match cp {
264        0x9 | 0xA | 0xD => true,
265        0x20..=0xD7FF => true,
266        0xE000..=0xFFFD => true,
267        0x10000..=0x10FFFF => true,
268        _ => false,
269    }
270}
271
272// ═══════════════════════════════════════════════════════════════════════════════
273// 3. UTF-16 handling
274// ═══════════════════════════════════════════════════════════════════════════════
275
276/// Decode a single UTF-16LE code unit from two bytes.
277#[inline]
278fn read_utf16le_unit(data: &[u8]) -> Option<u16> {
279    if data.len() < 2 {
280        return None;
281    }
282    Some(u16::from_le_bytes([data[0], data[1]]))
283}
284
285/// Decode a single UTF-16BE code unit from two bytes.
286#[inline]
287fn read_utf16be_unit(data: &[u8]) -> Option<u16> {
288    if data.len() < 2 {
289        return None;
290    }
291    Some(u16::from_be_bytes([data[0], data[1]]))
292}
293
294/// Encode a Unicode codepoint as UTF-8 bytes.
295///
296/// Returns the number of bytes written (1–4), or 0 if the codepoint is invalid.
297fn encode_codepoint_to_utf8(cp: u32, out: &mut [u8]) -> usize {
298    if cp < 0x80 {
299        if !out.is_empty() {
300            out[0] = cp as u8;
301        }
302        1
303    } else if cp < 0x800 {
304        if out.len() < 2 {
305            return 0;
306        }
307        out[0] = 0xC0 | ((cp >> 6) as u8);
308        out[1] = 0x80 | (cp as u8 & 0x3F);
309        2
310    } else if cp < 0x10000 {
311        if out.len() < 3 {
312            return 0;
313        }
314        out[0] = 0xE0 | ((cp >> 12) as u8);
315        out[1] = 0x80 | ((cp >> 6) as u8 & 0x3F);
316        out[2] = 0x80 | (cp as u8 & 0x3F);
317        3
318    } else if cp < 0x110000 {
319        if out.len() < 4 {
320            return 0;
321        }
322        out[0] = 0xF0 | ((cp >> 18) as u8);
323        out[1] = 0x80 | ((cp >> 12) as u8 & 0x3F);
324        out[2] = 0x80 | ((cp >> 6) as u8 & 0x3F);
325        out[3] = 0x80 | (cp as u8 & 0x3F);
326        4
327    } else {
328        0
329    }
330}
331
332/// Convert UTF-16LE bytes to UTF-8.
333///
334/// Returns `Ok(converted_bytes)` on success, or `Err(())` on invalid input
335/// (e.g., unpaired surrogates, truncated data).
336pub(crate) fn utf16le_to_utf8(data: &[u8]) -> Result<Vec<u8>, ()> {
337    if data.is_empty() {
338        return Ok(Vec::new());
339    }
340
341    // Skip BOM if present
342    let offset = if data.len() >= 2 && data[0..2] == UTF16LE_BOM {
343        2
344    } else {
345        0
346    };
347
348    let mut result = Vec::with_capacity(data.len() / 2 + data.len() / 4);
349    let mut i = offset;
350
351    while i < data.len() {
352        let unit = read_utf16le_unit(&data[i..]).ok_or(())?;
353        i += 2;
354
355        if unit >= 0xD800 && unit <= 0xDBFF {
356            // High surrogate: expect a low surrogate
357            let low = read_utf16le_unit(&data[i..]).ok_or(())?;
358            i += 2;
359
360            if !(0xDC00..=0xDFFF).contains(&low) {
361                return Err(());
362            }
363
364            let cp = 0x10000 + ((unit as u32 - 0xD800) << 10) + (low as u32 - 0xDC00);
365            let mut buf = [0u8; 4];
366            let n = encode_codepoint_to_utf8(cp, &mut buf);
367            if n == 0 {
368                return Err(());
369            }
370            result.extend_from_slice(&buf[..n]);
371        } else if unit >= 0xDC00 && unit <= 0xDFFF {
372            // Unexpected low surrogate
373            return Err(());
374        } else {
375            let cp = unit as u32;
376            let mut buf = [0u8; 4];
377            let n = encode_codepoint_to_utf8(cp, &mut buf);
378            result.extend_from_slice(&buf[..n]);
379        }
380    }
381
382    Ok(result)
383}
384
385/// Convert UTF-16BE bytes to UTF-8.
386///
387/// Returns `Ok(converted_bytes)` on success, or `Err(())` on invalid input.
388pub(crate) fn utf16be_to_utf8(data: &[u8]) -> Result<Vec<u8>, ()> {
389    if data.is_empty() {
390        return Ok(Vec::new());
391    }
392
393    // Skip BOM if present
394    let offset = if data.len() >= 2 && data[0..2] == UTF16BE_BOM {
395        2
396    } else {
397        0
398    };
399
400    let mut result = Vec::with_capacity(data.len() / 2 + data.len() / 4);
401    let mut i = offset;
402
403    while i < data.len() {
404        let unit = read_utf16be_unit(&data[i..]).ok_or(())?;
405        i += 2;
406
407        if unit >= 0xD800 && unit <= 0xDBFF {
408            // High surrogate: expect a low surrogate
409            let low = read_utf16be_unit(&data[i..]).ok_or(())?;
410            i += 2;
411
412            if !(0xDC00..=0xDFFF).contains(&low) {
413                return Err(());
414            }
415
416            let cp = 0x10000 + ((unit as u32 - 0xD800) << 10) + (low as u32 - 0xDC00);
417            let mut buf = [0u8; 4];
418            let n = encode_codepoint_to_utf8(cp, &mut buf);
419            if n == 0 {
420                return Err(());
421            }
422            result.extend_from_slice(&buf[..n]);
423        } else if unit >= 0xDC00 && unit <= 0xDFFF {
424            // Unexpected low surrogate
425            return Err(());
426        } else {
427            let cp = unit as u32;
428            let mut buf = [0u8; 4];
429            let n = encode_codepoint_to_utf8(cp, &mut buf);
430            result.extend_from_slice(&buf[..n]);
431        }
432    }
433
434    Ok(result)
435}
436
437/// Encode a Unicode codepoint as UTF-16LE bytes.
438///
439/// Returns the number of bytes written (2 or 4), or 0 if the codepoint is invalid.
440fn encode_codepoint_to_utf16le(cp: u32, out: &mut [u8]) -> usize {
441    if cp < 0x10000 {
442        if out.len() < 2 {
443            return 0;
444        }
445        let u = cp as u16;
446        out[..2].copy_from_slice(&u.to_le_bytes());
447        2
448    } else if cp < 0x110000 {
449        if out.len() < 4 {
450            return 0;
451        }
452        let cp = cp - 0x10000;
453        let high = 0xD800 | ((cp >> 10) as u16);
454        let low = 0xDC00 | (cp as u16 & 0x3FF);
455        out[..2].copy_from_slice(&high.to_le_bytes());
456        out[2..4].copy_from_slice(&low.to_le_bytes());
457        4
458    } else {
459        0
460    }
461}
462
463/// Convert UTF-8 bytes to UTF-16LE.
464///
465/// Returns `Ok(converted_bytes)` on success, or `Err(())` on invalid UTF-8 input.
466pub(crate) fn utf8_to_utf16le(data: &[u8]) -> Result<Vec<u8>, ()> {
467    let s = core::str::from_utf8(data).map_err(|_| ())?;
468    let mut result = Vec::with_capacity(data.len() * 2);
469
470    for ch in s.chars() {
471        let cp = ch as u32;
472        let mut buf = [0u8; 4];
473        let n = encode_codepoint_to_utf16le(cp, &mut buf);
474        if n == 0 {
475            return Err(());
476        }
477        result.extend_from_slice(&buf[..n]);
478    }
479
480    Ok(result)
481}
482
483// ═══════════════════════════════════════════════════════════════════════════════
484// 4. ISO-8859-1 (Latin-1) handling
485// ═══════════════════════════════════════════════════════════════════════════════
486
487/// Convert Latin-1 (ISO-8859-1) bytes to UTF-8.
488///
489/// Latin-1 maps codepoints 0x00–0xFF directly to Unicode codepoints U+0000–U+00FF.
490/// Each input byte produces either 1 or 2 UTF-8 bytes.
491pub(crate) fn latin1_to_utf8(data: &[u8]) -> Vec<u8> {
492    let mut result = Vec::with_capacity(data.len() * 2);
493
494    for &byte in data {
495        let cp = byte as u32;
496        let mut buf = [0u8; 2];
497        let n = encode_codepoint_to_utf8(cp, &mut buf);
498        result.extend_from_slice(&buf[..n]);
499    }
500
501    result
502}
503
504/// Convert UTF-8 bytes to Latin-1 (ISO-8859-1).
505///
506/// Returns `Err(())` if the input is not valid UTF-8 or contains codepoints
507/// outside the Latin-1 range (U+0000–U+00FF).
508pub(crate) fn utf8_to_latin1(data: &[u8]) -> Result<Vec<u8>, ()> {
509    let s = core::str::from_utf8(data).map_err(|_| ())?;
510    let mut result = Vec::with_capacity(data.len());
511
512    for ch in s.chars() {
513        let cp = ch as u32;
514        if cp > 0xFF {
515            return Err(());
516        }
517        result.push(cp as u8);
518    }
519
520    Ok(result)
521}
522
523// ═══════════════════════════════════════════════════════════════════════════════
524// 5. Encoding handler registry
525// ═══════════════════════════════════════════════════════════════════════════════
526
527/// Initialize the built-in encoding handlers.
528///
529/// This function registers handlers for:
530/// - UTF-8 (identity/no conversion)
531/// - UTF-16LE
532/// - UTF-16BE
533/// - ISO-8859-1 (Latin-1)
534/// - ASCII
535///
536/// Safe to call multiple times — only the first call has an effect.
537pub(crate) fn init_encodings() {
538    if ENCODING_INITIALIZED.swap(true, Ordering::SeqCst) {
539        return;
540    }
541
542    register_builtin_handlers();
543}
544
545/// Register all built-in encoding handlers.
546fn register_builtin_handlers() {
547    // UTF-8 (identity handler — no conversion needed)
548    register_handler(
549        b"UTF-8\0",
550        xmlCharEncoding::XML_CHAR_ENCODING_UTF8,
551        xmlCharEncoding::XML_CHAR_ENCODING_UTF8,
552        Some(utf8_input_func as xmlCharEncodingInputFunc),
553        Some(utf8_output_func as xmlCharEncodingOutputFunc),
554    );
555
556    // UTF-16LE
557    register_handler(
558        b"UTF-16LE\0",
559        xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE,
560        xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE,
561        Some(utf16le_input_func as xmlCharEncodingInputFunc),
562        Some(utf16le_output_func as xmlCharEncodingOutputFunc),
563    );
564
565    // UTF-16BE
566    register_handler(
567        b"UTF-16BE\0",
568        xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE,
569        xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE,
570        Some(utf16be_input_func as xmlCharEncodingInputFunc),
571        Some(utf16be_output_func as xmlCharEncodingOutputFunc),
572    );
573
574    // ISO-8859-1 (Latin-1)
575    register_handler(
576        b"ISO-8859-1\0",
577        xmlCharEncoding::XML_CHAR_ENCODING_8859_1,
578        xmlCharEncoding::XML_CHAR_ENCODING_8859_1,
579        Some(latin1_input_func as xmlCharEncodingInputFunc),
580        Some(latin1_output_func as xmlCharEncodingOutputFunc),
581    );
582
583    // ASCII
584    register_handler(
585        b"ASCII\0",
586        xmlCharEncoding::XML_CHAR_ENCODING_ASCII,
587        xmlCharEncoding::XML_CHAR_ENCODING_ASCII,
588        Some(ascii_input_func as xmlCharEncodingInputFunc),
589        Some(ascii_output_func as xmlCharEncodingOutputFunc),
590    );
591}
592
593/// Helper to create and register an encoding handler.
594fn register_handler(
595    name_bytes: &[u8],
596    input_enc: xmlCharEncoding,
597    output_enc: xmlCharEncoding,
598    input_func: Option<xmlCharEncodingInputFunc>,
599    output_func: Option<xmlCharEncodingOutputFunc>,
600) {
601    let name_raw =
602        unsafe { crate::abi::allocator::xmlMemStrdup(name_bytes.as_ptr() as *const c_char) };
603    if name_raw.is_null() {
604        return;
605    }
606
607    let handler =
608        unsafe { xmlMalloc(size_of::<_xmlCharEncodingHandler>()) } as *mut _xmlCharEncodingHandler;
609
610    if handler.is_null() {
611        unsafe { xmlFree(name_raw) };
612        return;
613    }
614
615    unsafe {
616        ptr::write(
617            handler,
618            _xmlCharEncodingHandler {
619                name: name_raw as *mut c_char,
620                input: EncodingInputUnion {
621                    legacyFunc: input_func,
622                },
623                output: EncodingOutputUnion {
624                    legacyFunc: output_func,
625                },
626                inputCtxt: ptr::null_mut(),
627                outputCtxt: ptr::null_mut(),
628                ctxtDtor: None,
629                flags: 0,
630            },
631        );
632    }
633
634    add_encoding_handler(handler);
635}
636
637/// Clean up encoding handlers.
638///
639/// Frees all registered handlers and resets the registry.
640pub(crate) fn cleanup_encodings() {
641    let mut handlers = ENCODING_HANDLERS.write();
642    for &handler in handlers.iter() {
643        let ptr = handler.0;
644        if !ptr.is_null() {
645            unsafe {
646                if !(*ptr).name.is_null() {
647                    xmlFree((*ptr).name as *mut c_void);
648                }
649                xmlFree(ptr as *mut c_void);
650            }
651        }
652    }
653    handlers.clear();
654    ENCODING_INITIALIZED.store(false, Ordering::SeqCst);
655}
656
657/// Find an encoding handler by name.
658///
659/// Searches the global handler registry for a handler whose name matches
660/// (case-insensitive). Returns a pointer to the handler, or `ptr::null_mut()`
661/// if not found.
662pub(crate) fn find_encoding_handler(name: *const xmlChar) -> *mut _xmlCharEncodingHandler {
663    if name.is_null() {
664        return ptr::null_mut();
665    }
666
667    let name_str = unsafe {
668        match CStr::from_ptr(name as *const c_char).to_bytes() {
669            b"" => return ptr::null_mut(),
670            s => s,
671        }
672    };
673
674    let handlers = ENCODING_HANDLERS.read();
675    for &handler in handlers.iter() {
676        let ptr = handler.0;
677        if ptr.is_null() {
678            continue;
679        }
680        let h_name = unsafe {
681            if (*ptr).name.is_null() {
682                continue;
683            }
684            CStr::from_ptr((*ptr).name).to_bytes()
685        };
686
687        if name_str.eq_ignore_ascii_case(h_name) {
688            return ptr;
689        }
690    }
691
692    ptr::null_mut()
693}
694
695/// Add an encoding handler to the registry.
696///
697/// Returns 0 on success, -1 on failure (e.g., null pointer).
698pub(crate) fn add_encoding_handler(handler: *mut _xmlCharEncodingHandler) -> c_int {
699    if handler.is_null() {
700        return -1;
701    }
702
703    let mut handlers = ENCODING_HANDLERS.write();
704    handlers.push(HandlerPtr(handler));
705    0
706}
707
708// ═══════════════════════════════════════════════════════════════════════════════
709// 6. Encoding conversion functions
710// ═══════════════════════════════════════════════════════════════════════════════
711
712/// Input conversion: convert from handler's input encoding to UTF-8.
713///
714/// Calls the handler's `input.legacyFunc` callback. Returns bytes written or -1 on error.
715pub(crate) fn char_enc_in_func(
716    handler: *mut _xmlCharEncodingHandler,
717    out: &mut [u8],
718    in_data: &[u8],
719) -> c_int {
720    if handler.is_null() {
721        return -1;
722    }
723
724    let h = unsafe { &*handler };
725    let input_func = unsafe { h.input.legacyFunc };
726    let input_func = match input_func {
727        Some(f) => f,
728        None => return -1,
729    };
730
731    let mut outlen = out.len() as c_int;
732    let mut inlen = in_data.len() as c_int;
733
734    unsafe { input_func(out.as_mut_ptr(), &mut outlen, in_data.as_ptr(), &mut inlen) }
735}
736
737/// Output conversion: convert from UTF-8 to handler's output encoding.
738///
739/// Calls the handler's `output.legacyFunc` callback. Returns bytes written or -1 on error.
740pub(crate) fn char_enc_out_func(
741    handler: *mut _xmlCharEncodingHandler,
742    out: &mut [u8],
743    in_data: &[u8],
744) -> c_int {
745    if handler.is_null() {
746        return -1;
747    }
748
749    let h = unsafe { &*handler };
750    let output_func = unsafe { h.output.legacyFunc };
751    let output_func = match output_func {
752        Some(f) => f,
753        None => return -1,
754    };
755
756    let mut outlen = out.len() as c_int;
757    let mut inlen = in_data.len() as c_int;
758
759    unsafe { output_func(out.as_mut_ptr(), &mut outlen, in_data.as_ptr(), &mut inlen) }
760}
761
762/// Full input conversion (`xmlCharEncInFunc` equivalent).
763///
764/// Reads from the input `_xmlBuffer`, converts via the handler's `input.legacyFunc`,
765/// and appends the result to the output `_xmlBuffer`.
766///
767/// Returns the number of bytes written to the output buffer, or -1 on error.
768pub(crate) fn char_enc_in(
769    handler: *mut _xmlCharEncodingHandler,
770    out: *mut _xmlBuffer,
771    in_: *mut _xmlBuffer,
772) -> c_int {
773    if handler.is_null() || out.is_null() || in_.is_null() {
774        return -1;
775    }
776
777    let h = unsafe { &*handler };
778    let input_func = unsafe { h.input.legacyFunc };
779    let input_func = match input_func {
780        Some(f) => f,
781        None => return -1,
782    };
783
784    let in_buf = unsafe { &*in_ };
785    let out_buf = unsafe { &mut *out };
786
787    if in_buf.content.is_null() || in_buf.use_ == 0 {
788        return 0;
789    }
790
791    let in_data = unsafe { core::slice::from_raw_parts(in_buf.content, in_buf.use_ as usize) };
792
793    // Allocate an output buffer. A good heuristic is 2x input for UTF-16→UTF-8.
794    let out_capacity = (in_buf.use_ as usize).saturating_mul(3).max(256);
795    let mut out_vec = vec![0u8; out_capacity];
796    let mut out_len = out_capacity as c_int;
797    let mut in_len = in_buf.use_ as c_int;
798
799    let ret = unsafe {
800        input_func(
801            out_vec.as_mut_ptr(),
802            &mut out_len,
803            in_data.as_ptr(),
804            &mut in_len,
805        )
806    };
807
808    if ret < 0 {
809        return -1;
810    }
811
812    let written = ret as usize;
813
814    // Append to output buffer
815    append_to_xml_buffer(out_buf, &out_vec[..written]);
816
817    written as c_int
818}
819
820/// Full output conversion (`xmlCharEncOutFunc` equivalent).
821///
822/// Reads from the input `_xmlBuffer` (UTF-8), converts via the handler's
823/// `output.legacyFunc`, and appends the result to the output `_xmlBuffer`.
824///
825/// Returns the number of bytes written to the output buffer, or -1 on error.
826pub(crate) fn char_enc_out(
827    handler: *mut _xmlCharEncodingHandler,
828    out: *mut _xmlBuffer,
829    in_: *mut _xmlBuffer,
830) -> c_int {
831    if handler.is_null() || out.is_null() || in_.is_null() {
832        return -1;
833    }
834
835    let h = unsafe { &*handler };
836    let output_func = unsafe { h.output.legacyFunc };
837    let output_func = match output_func {
838        Some(f) => f,
839        None => return -1,
840    };
841
842    let in_buf = unsafe { &*in_ };
843    let out_buf = unsafe { &mut *out };
844
845    if in_buf.content.is_null() || in_buf.use_ == 0 {
846        return 0;
847    }
848
849    let in_data = unsafe { core::slice::from_raw_parts(in_buf.content, in_buf.use_ as usize) };
850
851    let out_capacity = (in_buf.use_ as usize).saturating_mul(3).max(256);
852    let mut out_vec = vec![0u8; out_capacity];
853    let mut out_len = out_capacity as c_int;
854    let mut in_len = in_buf.use_ as c_int;
855
856    let ret = unsafe {
857        output_func(
858            out_vec.as_mut_ptr(),
859            &mut out_len,
860            in_data.as_ptr(),
861            &mut in_len,
862        )
863    };
864
865    if ret < 0 {
866        return -1;
867    }
868
869    let written = ret as usize;
870
871    // Append to output buffer
872    append_to_xml_buffer(out_buf, &out_vec[..written]);
873
874    written as c_int
875}
876
877/// Append bytes to an `_xmlBuffer`, reallocating if needed.
878fn append_to_xml_buffer(buf: &mut _xmlBuffer, data: &[u8]) {
879    if data.is_empty() {
880        return;
881    }
882
883    let new_use = (buf.use_ as usize).saturating_add(data.len());
884    if new_use > buf.size as usize {
885        // Grow buffer: double or fit, whichever is larger
886        let new_size = (buf.size as usize).saturating_mul(2).max(new_use).max(256);
887        let new_content =
888            unsafe { xmlRealloc(buf.content as *mut c_void, new_size) as *mut xmlChar };
889        if new_content.is_null() {
890            return; // Allocation failure — silently skip
891        }
892        buf.content = new_content;
893        buf.size = new_size as c_uint;
894    }
895
896    unsafe {
897        ptr::copy_nonoverlapping(
898            data.as_ptr(),
899            buf.content.add(buf.use_ as usize),
900            data.len(),
901        );
902    }
903    buf.use_ = new_use as c_uint;
904}
905
906// ═══════════════════════════════════════════════════════════════════════════════
907// 7. Built-in encoding handler callbacks (extern "C")
908// ═══════════════════════════════════════════════════════════════════════════════
909
910// ── UTF-8 (identity) ──────────────────────────────────────────────────────
911
912/// UTF-8 input function: identity (input is already UTF-8).
913///
914/// Simply copies bytes from input to output, up to the available space.
915unsafe extern "C" fn utf8_input_func(
916    out: *mut c_uchar,
917    outlen: *mut c_int,
918    in_: *const c_uchar,
919    inlen: *mut c_int,
920) -> c_int {
921    let avail_out = *outlen as usize;
922    let avail_in = *inlen as usize;
923    let to_copy = avail_out.min(avail_in);
924
925    if to_copy > 0 {
926        ptr::copy_nonoverlapping(in_, out, to_copy);
927    }
928
929    *outlen = to_copy as c_int;
930    *inlen = to_copy as c_int;
931    to_copy as c_int
932}
933
934/// UTF-8 output function: identity (output is already UTF-8).
935unsafe extern "C" fn utf8_output_func(
936    out: *mut c_uchar,
937    outlen: *mut c_int,
938    in_: *const c_uchar,
939    inlen: *mut c_int,
940) -> c_int {
941    utf8_input_func(out, outlen, in_, inlen)
942}
943
944// ── UTF-16LE ──────────────────────────────────────────────────────────────
945
946/// UTF-16LE input function: convert UTF-16LE to UTF-8.
947unsafe extern "C" fn utf16le_input_func(
948    out: *mut c_uchar,
949    outlen: *mut c_int,
950    in_: *const c_uchar,
951    inlen: *mut c_int,
952) -> c_int {
953    if out.is_null() || outlen.is_null() || in_.is_null() || inlen.is_null() {
954        return -1;
955    }
956
957    let avail_in = *inlen as usize;
958    let avail_out = *outlen as usize;
959
960    if avail_in == 0 || avail_out == 0 {
961        *outlen = 0;
962        *inlen = 0;
963        return 0;
964    }
965
966    let in_data = core::slice::from_raw_parts(in_, avail_in);
967    let out_slice = core::slice::from_raw_parts_mut(out, avail_out);
968
969    // Use the safe wrapper
970    let result = match utf16le_to_utf8(in_data) {
971        Ok(v) => v,
972        Err(()) => return -1,
973    };
974
975    let written = result.len().min(avail_out);
976    if written > 0 {
977        ptr::copy_nonoverlapping(result.as_ptr(), out, written);
978    }
979
980    *outlen = written as c_int;
981    *inlen = avail_in as c_int; // All input consumed
982    written as c_int
983}
984
985/// UTF-16LE output function: convert UTF-8 to UTF-16LE.
986unsafe extern "C" fn utf16le_output_func(
987    out: *mut c_uchar,
988    outlen: *mut c_int,
989    in_: *const c_uchar,
990    inlen: *mut c_int,
991) -> c_int {
992    if out.is_null() || outlen.is_null() || in_.is_null() || inlen.is_null() {
993        return -1;
994    }
995
996    let avail_in = *inlen as usize;
997    let avail_out = *outlen as usize;
998
999    if avail_in == 0 || avail_out == 0 {
1000        *outlen = 0;
1001        *inlen = 0;
1002        return 0;
1003    }
1004
1005    let in_data = core::slice::from_raw_parts(in_, avail_in);
1006    let out_slice = core::slice::from_raw_parts_mut(out, avail_out);
1007
1008    let result = match utf8_to_utf16le(in_data) {
1009        Ok(v) => v,
1010        Err(()) => return -1,
1011    };
1012
1013    let written = result.len().min(avail_out);
1014    if written > 0 {
1015        ptr::copy_nonoverlapping(result.as_ptr(), out, written);
1016    }
1017
1018    *outlen = written as c_int;
1019    *inlen = avail_in as c_int;
1020    written as c_int
1021}
1022
1023// ── UTF-16BE ──────────────────────────────────────────────────────────────
1024
1025/// UTF-16BE input function: convert UTF-16BE to UTF-8.
1026unsafe extern "C" fn utf16be_input_func(
1027    out: *mut c_uchar,
1028    outlen: *mut c_int,
1029    in_: *const c_uchar,
1030    inlen: *mut c_int,
1031) -> c_int {
1032    if out.is_null() || outlen.is_null() || in_.is_null() || inlen.is_null() {
1033        return -1;
1034    }
1035
1036    let avail_in = *inlen as usize;
1037    let avail_out = *outlen as usize;
1038
1039    if avail_in == 0 || avail_out == 0 {
1040        *outlen = 0;
1041        *inlen = 0;
1042        return 0;
1043    }
1044
1045    let in_data = core::slice::from_raw_parts(in_, avail_in);
1046    let out_slice = core::slice::from_raw_parts_mut(out, avail_out);
1047
1048    let result = match utf16be_to_utf8(in_data) {
1049        Ok(v) => v,
1050        Err(()) => return -1,
1051    };
1052
1053    let written = result.len().min(avail_out);
1054    if written > 0 {
1055        ptr::copy_nonoverlapping(result.as_ptr(), out, written);
1056    }
1057
1058    *outlen = written as c_int;
1059    *inlen = avail_in as c_int;
1060    written as c_int
1061}
1062
1063/// UTF-16BE output function: convert UTF-8 to UTF-16BE.
1064unsafe extern "C" fn utf16be_output_func(
1065    out: *mut c_uchar,
1066    outlen: *mut c_int,
1067    in_: *const c_uchar,
1068    inlen: *mut c_int,
1069) -> c_int {
1070    if out.is_null() || outlen.is_null() || in_.is_null() || inlen.is_null() {
1071        return -1;
1072    }
1073
1074    let avail_in = *inlen as usize;
1075    let avail_out = *outlen as usize;
1076
1077    if avail_in == 0 || avail_out == 0 {
1078        *outlen = 0;
1079        *inlen = 0;
1080        return 0;
1081    }
1082
1083    let in_data = core::slice::from_raw_parts(in_, avail_in);
1084
1085    // First convert to UTF-16LE, then swap bytes
1086    let le_result = match utf8_to_utf16le(in_data) {
1087        Ok(v) => v,
1088        Err(()) => return -1,
1089    };
1090
1091    // Swap byte pairs to get UTF-16BE
1092    let mut result = le_result;
1093    for chunk in result.chunks_exact_mut(2) {
1094        chunk.swap(0, 1);
1095    }
1096
1097    let written = result.len().min(avail_out);
1098    if written > 0 {
1099        ptr::copy_nonoverlapping(result.as_ptr(), out, written);
1100    }
1101
1102    *outlen = written as c_int;
1103    *inlen = avail_in as c_int;
1104    written as c_int
1105}
1106
1107// ── ISO-8859-1 (Latin-1) ─────────────────────────────────────────────────
1108
1109/// Latin-1 input function: convert ISO-8859-1 to UTF-8.
1110unsafe extern "C" fn latin1_input_func(
1111    out: *mut c_uchar,
1112    outlen: *mut c_int,
1113    in_: *const c_uchar,
1114    inlen: *mut c_int,
1115) -> c_int {
1116    if out.is_null() || outlen.is_null() || in_.is_null() || inlen.is_null() {
1117        return -1;
1118    }
1119
1120    let avail_in = *inlen as usize;
1121    let avail_out = *outlen as usize;
1122
1123    if avail_in == 0 || avail_out == 0 {
1124        *outlen = 0;
1125        *inlen = 0;
1126        return 0;
1127    }
1128
1129    let in_data = core::slice::from_raw_parts(in_, avail_in);
1130    let out_slice = core::slice::from_raw_parts_mut(out, avail_out);
1131
1132    let mut in_pos = 0;
1133    let mut out_pos = 0;
1134
1135    while in_pos < avail_in && out_pos < avail_out {
1136        let byte = in_data[in_pos];
1137        in_pos += 1;
1138
1139        if byte < 0x80 {
1140            // Single byte UTF-8
1141            if out_pos < avail_out {
1142                out_slice[out_pos] = byte;
1143                out_pos += 1;
1144            } else {
1145                break;
1146            }
1147        } else {
1148            // Two byte UTF-8: 0xC0 | (byte >> 6), 0x80 | (byte & 0x3F)
1149            // For byte 0x80-0xFF, the encoding is 0xC2-0xC3 followed by continuation
1150            if out_pos + 1 < avail_out {
1151                out_slice[out_pos] = 0xC2 | (byte >> 6);
1152                out_slice[out_pos + 1] = 0x80 | (byte & 0x3F);
1153                out_pos += 2;
1154            } else {
1155                break;
1156            }
1157        }
1158    }
1159
1160    *outlen = out_pos as c_int;
1161    *inlen = in_pos as c_int;
1162    out_pos as c_int
1163}
1164
1165/// Latin-1 output function: convert UTF-8 to ISO-8859-1.
1166unsafe extern "C" fn latin1_output_func(
1167    out: *mut c_uchar,
1168    outlen: *mut c_int,
1169    in_: *const c_uchar,
1170    inlen: *mut c_int,
1171) -> c_int {
1172    if out.is_null() || outlen.is_null() || in_.is_null() || inlen.is_null() {
1173        return -1;
1174    }
1175
1176    let avail_in = *inlen as usize;
1177    let avail_out = *outlen as usize;
1178
1179    if avail_in == 0 || avail_out == 0 {
1180        *outlen = 0;
1181        *inlen = 0;
1182        return 0;
1183    }
1184
1185    let in_data = core::slice::from_raw_parts(in_, avail_in);
1186    let out_slice = core::slice::from_raw_parts_mut(out, avail_out);
1187
1188    let mut in_pos = 0;
1189    let mut out_pos = 0;
1190
1191    while in_pos < avail_in && out_pos < avail_out {
1192        let byte = in_data[in_pos];
1193        in_pos += 1;
1194
1195        if byte < 0x80 {
1196            // ASCII — direct mapping
1197            out_slice[out_pos] = byte;
1198            out_pos += 1;
1199        } else if byte >= 0xC2 && byte <= 0xC3 {
1200            // Two-byte UTF-8 for codepoints U+0080–U+00FF
1201            if in_pos < avail_in {
1202                let second = in_data[in_pos];
1203                in_pos += 1;
1204                if second & 0xC0 != 0x80 {
1205                    return -1; // Invalid continuation byte
1206                }
1207                let cp = ((byte as u32 & 0x1F) << 6) | (second as u32 & 0x3F);
1208                if cp > 0xFF {
1209                    return -1; // Outside Latin-1 range
1210                }
1211                out_slice[out_pos] = cp as u8;
1212                out_pos += 1;
1213            } else {
1214                return -1; // Truncated
1215            }
1216        } else if byte >= 0x80 && byte <= 0xBF {
1217            // Unexpected continuation byte
1218            return -1;
1219        } else {
1220            // Multi-byte sequence for codepoints > U+00FF
1221            // Skip the rest of the sequence and return error
1222            return -1;
1223        }
1224    }
1225
1226    *outlen = out_pos as c_int;
1227    *inlen = in_pos as c_int;
1228    out_pos as c_int
1229}
1230
1231// ── ASCII ─────────────────────────────────────────────────────────────────
1232
1233/// ASCII input function: verify and pass through ASCII data to UTF-8.
1234unsafe extern "C" fn ascii_input_func(
1235    out: *mut c_uchar,
1236    outlen: *mut c_int,
1237    in_: *const c_uchar,
1238    inlen: *mut c_int,
1239) -> c_int {
1240    if out.is_null() || outlen.is_null() || in_.is_null() || inlen.is_null() {
1241        return -1;
1242    }
1243
1244    let avail_in = *inlen as usize;
1245    let avail_out = *outlen as usize;
1246
1247    if avail_in == 0 || avail_out == 0 {
1248        *outlen = 0;
1249        *inlen = 0;
1250        return 0;
1251    }
1252
1253    let in_data = core::slice::from_raw_parts(in_, avail_in);
1254    let out_slice = core::slice::from_raw_parts_mut(out, avail_out);
1255
1256    let mut pos = 0;
1257    while pos < avail_in && pos < avail_out {
1258        let byte = in_data[pos];
1259        if byte > 0x7F {
1260            return -1; // Not valid ASCII
1261        }
1262        out_slice[pos] = byte;
1263        pos += 1;
1264    }
1265
1266    *outlen = pos as c_int;
1267    *inlen = pos as c_int;
1268    pos as c_int
1269}
1270
1271/// ASCII output function: verify and pass through UTF-8 data that is ASCII.
1272unsafe extern "C" fn ascii_output_func(
1273    out: *mut c_uchar,
1274    outlen: *mut c_int,
1275    in_: *const c_uchar,
1276    inlen: *mut c_int,
1277) -> c_int {
1278    // For output, ASCII handler requires that input is already ASCII
1279    ascii_input_func(out, outlen, in_, inlen)
1280}
1281
1282// ═══════════════════════════════════════════════════════════════════════════════
1283// 8. ABI export functions (called from exports_xml2.rs)
1284// ═══════════════════════════════════════════════════════════════════════════════
1285
1286/// `xmlFindCharEncodingHandler` implementation.
1287///
1288/// Finds an encoding handler by name. Returns a pointer to the handler,
1289/// or `ptr::null_mut()` if not found.
1290pub(crate) fn xmlFindCharEncodingHandler(name: *const c_char) -> *mut _xmlCharEncodingHandler {
1291    if name.is_null() {
1292        return ptr::null_mut();
1293    }
1294    find_encoding_handler(name as *const xmlChar)
1295}
1296
1297/// `xmlGetCharEncodingName` implementation.
1298///
1299/// Returns the canonical name for an encoding, or `ptr::null()` if unknown.
1300pub(crate) fn xmlGetCharEncodingName(enc: xmlCharEncoding) -> *const c_char {
1301    // Return null-terminated C strings using static CStr literals
1302    match enc {
1303        xmlCharEncoding::XML_CHAR_ENCODING_UTF8 => c"UTF-8".as_ptr(),
1304        xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE => c"UTF-16LE".as_ptr(),
1305        xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE => c"UTF-16BE".as_ptr(),
1306        xmlCharEncoding::XML_CHAR_ENCODING_UCS4LE => c"UCS-4LE".as_ptr(),
1307        xmlCharEncoding::XML_CHAR_ENCODING_UCS4BE => c"UCS-4BE".as_ptr(),
1308        xmlCharEncoding::XML_CHAR_ENCODING_EBCDIC => c"EBCDIC".as_ptr(),
1309        xmlCharEncoding::XML_CHAR_ENCODING_UCS4_2143 => c"UCS-4-2143".as_ptr(),
1310        xmlCharEncoding::XML_CHAR_ENCODING_UCS4_3412 => c"UCS-4-3412".as_ptr(),
1311        xmlCharEncoding::XML_CHAR_ENCODING_UCS2 => c"UCS-2".as_ptr(),
1312        xmlCharEncoding::XML_CHAR_ENCODING_8859_1 => c"ISO-8859-1".as_ptr(),
1313        xmlCharEncoding::XML_CHAR_ENCODING_8859_2 => c"ISO-8859-2".as_ptr(),
1314        xmlCharEncoding::XML_CHAR_ENCODING_8859_3 => c"ISO-8859-3".as_ptr(),
1315        xmlCharEncoding::XML_CHAR_ENCODING_8859_4 => c"ISO-8859-4".as_ptr(),
1316        xmlCharEncoding::XML_CHAR_ENCODING_8859_5 => c"ISO-8859-5".as_ptr(),
1317        xmlCharEncoding::XML_CHAR_ENCODING_8859_6 => c"ISO-8859-6".as_ptr(),
1318        xmlCharEncoding::XML_CHAR_ENCODING_8859_7 => c"ISO-8859-7".as_ptr(),
1319        xmlCharEncoding::XML_CHAR_ENCODING_8859_8 => c"ISO-8859-8".as_ptr(),
1320        xmlCharEncoding::XML_CHAR_ENCODING_8859_9 => c"ISO-8859-9".as_ptr(),
1321        xmlCharEncoding::XML_CHAR_ENCODING_2022_JP => c"ISO-2022-JP".as_ptr(),
1322        xmlCharEncoding::XML_CHAR_ENCODING_SHIFT_JIS => c"SHIFT_JIS".as_ptr(),
1323        xmlCharEncoding::XML_CHAR_ENCODING_EUC_JP => c"EUC-JP".as_ptr(),
1324        xmlCharEncoding::XML_CHAR_ENCODING_ASCII => c"ASCII".as_ptr(),
1325        _ => ptr::null(),
1326    }
1327}
1328
1329/// `xmlParseCharEncoding` implementation.
1330///
1331/// Parses an encoding name string to an `xmlCharEncoding` enum value,
1332/// returned as `c_int`.
1333pub(crate) fn xmlParseCharEncoding(name: *const c_char) -> c_int {
1334    if name.is_null() {
1335        return xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int;
1336    }
1337    let bytes = unsafe { CStr::from_ptr(name).to_bytes() };
1338    encoding_from_name(bytes) as c_int
1339}
1340
1341// ── Encoding aliases (upstream encoding.c xmlAddEncodingAlias etc.) ──────────
1342//
1343// A global alias table maps alias names to canonical encoding names.
1344// Upstream keeps a static hash of aliases; the candidate uses a
1345// process-lifetime RwLock<HashMap>. Thread-safe; matches upstream's
1346// observable contract (add/del/get by name).
1347
1348static ENCODING_ALIASES: std::sync::OnceLock<
1349    parking_lot::RwLock<std::collections::HashMap<Vec<u8>, Vec<u8>>>,
1350> = std::sync::OnceLock::new();
1351
1352fn encoding_aliases() -> &'static parking_lot::RwLock<std::collections::HashMap<Vec<u8>, Vec<u8>>> {
1353    ENCODING_ALIASES.get_or_init(|| {
1354        let m = parking_lot::RwLock::new(std::collections::HashMap::new());
1355        m
1356    })
1357}
1358
1359/// `xmlAddEncodingAlias` implementation: register `alias` for `name`.
1360/// Returns 0 on success, -1 on error (NULL arguments).
1361pub(crate) fn add_encoding_alias(name: *const c_char, alias: *const c_char) -> c_int {
1362    if name.is_null() || alias.is_null() {
1363        return -1;
1364    }
1365    let n = unsafe { CStr::from_ptr(name).to_bytes().to_vec() };
1366    let a = unsafe { CStr::from_ptr(alias).to_bytes().to_vec() };
1367    encoding_aliases().write().insert(a, n);
1368    0
1369}
1370
1371/// `xmlDelEncodingAlias` implementation: remove `alias`.
1372/// Returns 0 on success, -1 if the alias does not exist.
1373pub(crate) fn del_encoding_alias(alias: *const c_char) -> c_int {
1374    if alias.is_null() {
1375        return -1;
1376    }
1377    let a = unsafe { CStr::from_ptr(alias).to_bytes().to_vec() };
1378    if encoding_aliases().write().remove(&a).is_some() {
1379        0
1380    } else {
1381        -1
1382    }
1383}
1384
1385/// `xmlGetEncodingAlias` implementation: return the canonical name for
1386/// `alias`, or NULL when not registered.
1387pub(crate) fn get_encoding_alias(alias: *const c_char) -> *const c_char {
1388    if alias.is_null() {
1389        return ptr::null();
1390    }
1391    let a = unsafe { CStr::from_ptr(alias).to_bytes().to_vec() };
1392    let guard = encoding_aliases().read();
1393    match guard.get(&a) {
1394        Some(v) => {
1395            // leak the canonical name: upstream returns a pointer valid for
1396            // the process lifetime (the alias hash owns the strings)
1397            let leaked: &'static [u8] = Box::leak(v.clone().into_boxed_slice());
1398            leaked.as_ptr() as *const c_char
1399        }
1400        None => ptr::null(),
1401    }
1402}
1403
1404/// `xmlCleanupEncodingAliases` implementation: drop all aliases.
1405pub(crate) fn cleanup_encoding_aliases() {
1406    encoding_aliases().write().clear();
1407}
1408
1409/// `xmlCharEncInFunc` implementation.
1410///
1411/// Converts the input buffer's encoding to UTF-8 using the given handler.
1412pub(crate) fn xmlCharEncInFunc(
1413    handler: *mut _xmlCharEncodingHandler,
1414    out: *mut _xmlBuffer,
1415    in_: *mut _xmlBuffer,
1416) -> c_int {
1417    char_enc_in(handler, out, in_)
1418}
1419
1420/// `xmlCharEncOutFunc` implementation.
1421///
1422/// Converts the input buffer from UTF-8 to the handler's output encoding.
1423pub(crate) fn xmlCharEncOutFunc(
1424    handler: *mut _xmlCharEncodingHandler,
1425    out: *mut _xmlBuffer,
1426    in_: *mut _xmlBuffer,
1427) -> c_int {
1428    char_enc_out(handler, out, in_)
1429}
1430
1431/// `xmlNewCharEncodingHandler` implementation.
1432///
1433/// Creates a new encoding handler with the given name and conversion functions.
1434/// The name string is duplicated. Returns a pointer to the new handler,
1435/// or `ptr::null_mut()` on allocation failure.
1436pub(crate) fn xmlNewCharEncodingHandler(
1437    name: *const c_char,
1438    input: xmlCharEncodingInputFunc,
1439    output: xmlCharEncodingOutputFunc,
1440) -> *mut _xmlCharEncodingHandler {
1441    if name.is_null() {
1442        return ptr::null_mut();
1443    }
1444
1445    let name_raw = unsafe { crate::abi::allocator::xmlMemStrdup(name) };
1446    if name_raw.is_null() {
1447        return ptr::null_mut();
1448    }
1449
1450    let handler =
1451        unsafe { xmlMalloc(size_of::<_xmlCharEncodingHandler>()) } as *mut _xmlCharEncodingHandler;
1452
1453    if handler.is_null() {
1454        unsafe { xmlFree(name_raw) };
1455        return ptr::null_mut();
1456    }
1457
1458    unsafe {
1459        ptr::write(
1460            handler,
1461            _xmlCharEncodingHandler {
1462                name: name_raw as *mut c_char,
1463                input: EncodingInputUnion {
1464                    legacyFunc: Some(input),
1465                },
1466                output: EncodingOutputUnion {
1467                    legacyFunc: Some(output),
1468                },
1469                inputCtxt: ptr::null_mut(),
1470                outputCtxt: ptr::null_mut(),
1471                ctxtDtor: None,
1472                flags: 0,
1473            },
1474        );
1475    }
1476
1477    handler
1478}
1479
1480/// `xmlDelEncodingHandler` implementation.
1481///
1482/// Frees an encoding handler previously created with `xmlNewCharEncodingHandler`.
1483pub(crate) fn xmlDelEncodingHandler(handler: *mut _xmlCharEncodingHandler) {
1484    if handler.is_null() {
1485        return;
1486    }
1487
1488    // Remove from registry if present
1489    {
1490        let mut handlers = ENCODING_HANDLERS.write();
1491        handlers.retain(|&h| h.0 != handler);
1492    }
1493
1494    unsafe {
1495        if !(*handler).name.is_null() {
1496            xmlFree((*handler).name as *mut c_void);
1497        }
1498        xmlFree(handler as *mut c_void);
1499    }
1500}
1501
1502/// `xmlInitCharEncodingHandlers` implementation.
1503pub(crate) fn xmlInitCharEncodingHandlers() {
1504    init_encodings();
1505}
1506
1507/// `xmlCleanupCharEncodingHandlers` implementation.
1508pub(crate) fn xmlCleanupCharEncodingHandlers() {
1509    cleanup_encodings();
1510}
1511
1512// ═══════════════════════════════════════════════════════════════════════════════
1513// Tests
1514// ═══════════════════════════════════════════════════════════════════════════════
1515
1516#[cfg(test)]
1517mod tests {
1518    use super::*;
1519
1520    // ── BOM detection ──────────────────────────────────────────────────────
1521
1522    #[test]
1523    fn test_detect_bom_utf8() {
1524        let data = [0xEF, 0xBB, 0xBF, b'<', b'?', b'x', b'm', b'l'];
1525        assert_eq!(
1526            detect_encoding_from_bom(&data),
1527            xmlCharEncoding::XML_CHAR_ENCODING_UTF8
1528        );
1529    }
1530
1531    #[test]
1532    fn test_detect_bom_utf16le() {
1533        let data = [0xFF, 0xFE, 0x00, 0x01];
1534        assert_eq!(
1535            detect_encoding_from_bom(&data),
1536            xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE
1537        );
1538    }
1539
1540    #[test]
1541    fn test_detect_bom_utf16be() {
1542        let data = [0xFE, 0xFF, 0x00, 0x01];
1543        assert_eq!(
1544            detect_encoding_from_bom(&data),
1545            xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE
1546        );
1547    }
1548
1549    #[test]
1550    fn test_detect_bom_none() {
1551        let data = b"<xml>";
1552        assert_eq!(
1553            detect_encoding_from_bom(data),
1554            xmlCharEncoding::XML_CHAR_ENCODING_NONE
1555        );
1556    }
1557
1558    #[test]
1559    fn test_detect_bom_empty() {
1560        assert_eq!(
1561            detect_encoding_from_bom(b""),
1562            xmlCharEncoding::XML_CHAR_ENCODING_NONE
1563        );
1564    }
1565
1566    // ── Encoding from declaration ──────────────────────────────────────────
1567
1568    #[test]
1569    fn test_detect_encoding_declaration_utf8() {
1570        let data = b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
1571        let result = detect_encoding_from_declaration(data);
1572        assert_eq!(result, Some(b"utf-8".to_vec()));
1573    }
1574
1575    #[test]
1576    fn test_detect_encoding_declaration_iso() {
1577        let data = b"<?xml version='1.0' encoding='ISO-8859-1'?>";
1578        let result = detect_encoding_from_declaration(data);
1579        assert_eq!(result, Some(b"iso-8859-1".to_vec()));
1580    }
1581
1582    #[test]
1583    fn test_detect_encoding_declaration_none() {
1584        let data = b"<?xml version=\"1.0\"?>";
1585        let result = detect_encoding_from_declaration(data);
1586        assert!(result.is_none());
1587    }
1588
1589    #[test]
1590    fn test_detect_encoding_declaration_no_xml() {
1591        let data = b"<root>";
1592        let result = detect_encoding_from_declaration(data);
1593        assert!(result.is_none());
1594    }
1595
1596    #[test]
1597    fn test_detect_encoding_declaration_with_bom() {
1598        let mut data = vec![0xEF, 0xBB, 0xBF];
1599        data.extend_from_slice(b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
1600        let result = detect_encoding_from_declaration(&data);
1601        assert_eq!(result, Some(b"utf-8".to_vec()));
1602    }
1603
1604    // ── Encoding from name ─────────────────────────────────────────────────
1605
1606    #[test]
1607    fn test_encoding_from_name_utf8() {
1608        assert_eq!(
1609            encoding_from_name(b"UTF-8"),
1610            xmlCharEncoding::XML_CHAR_ENCODING_UTF8
1611        );
1612        assert_eq!(
1613            encoding_from_name(b"utf8"),
1614            xmlCharEncoding::XML_CHAR_ENCODING_UTF8
1615        );
1616    }
1617
1618    #[test]
1619    fn test_encoding_from_name_utf16() {
1620        assert_eq!(
1621            encoding_from_name(b"UTF-16LE"),
1622            xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE
1623        );
1624        assert_eq!(
1625            encoding_from_name(b"UTF-16BE"),
1626            xmlCharEncoding::XML_CHAR_ENCODING_UTF16BE
1627        );
1628        assert_eq!(
1629            encoding_from_name(b"utf-16"),
1630            xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE
1631        );
1632    }
1633
1634    #[test]
1635    fn test_encoding_from_name_latin1() {
1636        assert_eq!(
1637            encoding_from_name(b"ISO-8859-1"),
1638            xmlCharEncoding::XML_CHAR_ENCODING_8859_1
1639        );
1640        assert_eq!(
1641            encoding_from_name(b"Latin1"),
1642            xmlCharEncoding::XML_CHAR_ENCODING_8859_1
1643        );
1644    }
1645
1646    #[test]
1647    fn test_encoding_from_name_ascii() {
1648        assert_eq!(
1649            encoding_from_name(b"ASCII"),
1650            xmlCharEncoding::XML_CHAR_ENCODING_ASCII
1651        );
1652        assert_eq!(
1653            encoding_from_name(b"US-ASCII"),
1654            xmlCharEncoding::XML_CHAR_ENCODING_ASCII
1655        );
1656    }
1657
1658    #[test]
1659    fn test_encoding_from_name_error() {
1660        assert_eq!(
1661            encoding_from_name(b"invalid-encoding"),
1662            xmlCharEncoding::XML_CHAR_ENCODING_ERROR
1663        );
1664    }
1665
1666    #[test]
1667    fn test_encoding_from_name_empty() {
1668        assert_eq!(
1669            encoding_from_name(b""),
1670            xmlCharEncoding::XML_CHAR_ENCODING_ERROR
1671        );
1672    }
1673
1674    // ── Encoding name ──────────────────────────────────────────────────────
1675
1676    #[test]
1677    fn test_encoding_name_utf8() {
1678        assert_eq!(
1679            encoding_name(xmlCharEncoding::XML_CHAR_ENCODING_UTF8),
1680            Some(b"UTF-8" as &[u8])
1681        );
1682    }
1683
1684    #[test]
1685    fn test_encoding_name_utf16le() {
1686        assert_eq!(
1687            encoding_name(xmlCharEncoding::XML_CHAR_ENCODING_UTF16LE),
1688            Some(b"UTF-16LE" as &[u8])
1689        );
1690    }
1691
1692    #[test]
1693    fn test_encoding_name_none() {
1694        assert!(encoding_name(xmlCharEncoding::XML_CHAR_ENCODING_NONE).is_none());
1695    }
1696
1697    #[test]
1698    fn test_encoding_name_error() {
1699        assert!(encoding_name(xmlCharEncoding::XML_CHAR_ENCODING_ERROR).is_none());
1700    }
1701
1702    // ── UTF-8 validation ───────────────────────────────────────────────────
1703
1704    #[test]
1705    fn test_utf8_valid_ascii() {
1706        assert!(utf8_valid(b"hello world"));
1707    }
1708
1709    #[test]
1710    fn test_utf8_valid_multi_byte() {
1711        assert!(utf8_valid("héllo wörld 🌍".as_bytes()));
1712    }
1713
1714    #[test]
1715    fn test_utf8_valid_empty() {
1716        assert!(utf8_valid(b""));
1717    }
1718
1719    #[test]
1720    fn test_utf8_invalid() {
1721        assert!(!utf8_valid(&[0xFF, 0xFE, 0x00]));
1722    }
1723
1724    // ── XML char validation ────────────────────────────────────────────────
1725
1726    #[test]
1727    fn test_valid_xml_chars() {
1728        assert!(is_valid_xml_char(0x9)); // Tab
1729        assert!(is_valid_xml_char(0xA)); // LF
1730        assert!(is_valid_xml_char(0xD)); // CR
1731        assert!(is_valid_xml_char(0x20)); // Space
1732        assert!(is_valid_xml_char(0x41)); // 'A'
1733        assert!(is_valid_xml_char(0xD7FF));
1734        assert!(is_valid_xml_char(0xE000));
1735        assert!(is_valid_xml_char(0xFFFD));
1736        assert!(is_valid_xml_char(0x10000));
1737        assert!(is_valid_xml_char(0x10FFFF));
1738    }
1739
1740    #[test]
1741    fn test_invalid_xml_chars() {
1742        assert!(!is_valid_xml_char(0x00));
1743        assert!(!is_valid_xml_char(0x08));
1744        assert!(!is_valid_xml_char(0x0B));
1745        assert!(!is_valid_xml_char(0x0C));
1746        assert!(!is_valid_xml_char(0x0E));
1747        assert!(!is_valid_xml_char(0x1F));
1748        assert!(!is_valid_xml_char(0xD800)); // Surrogate
1749        assert!(!is_valid_xml_char(0xDFFF)); // Surrogate
1750        assert!(!is_valid_xml_char(0xFFFE));
1751        assert!(!is_valid_xml_char(0xFFFF));
1752        assert!(!is_valid_xml_char(0x110000));
1753    }
1754
1755    // ── UTF-16LE to UTF-8 ──────────────────────────────────────────────────
1756
1757    #[test]
1758    fn test_utf16le_to_utf8_ascii() {
1759        // "AB" in UTF-16LE
1760        let data = [b'A', 0x00, b'B', 0x00];
1761        let result = utf16le_to_utf8(&data).unwrap();
1762        assert_eq!(result, b"AB");
1763    }
1764
1765    #[test]
1766    fn test_utf16le_to_utf8_bom() {
1767        let mut data = vec![0xFF, 0xFE]; // BOM
1768        data.extend_from_slice(&[b'A', 0x00, b'B', 0x00]);
1769        let result = utf16le_to_utf8(&data).unwrap();
1770        assert_eq!(result, b"AB");
1771    }
1772
1773    #[test]
1774    fn test_utf16le_to_utf8_bmp() {
1775        // U+00E9 (é) in UTF-16LE = 0xE9 0x00
1776        let data = [0xE9, 0x00];
1777        let result = utf16le_to_utf8(&data).unwrap();
1778        assert_eq!(result, "é".as_bytes());
1779    }
1780
1781    #[test]
1782    fn test_utf16le_to_utf8_supplementary() {
1783        // U+1F600 (😀) in UTF-16LE = 0x3D 0xD8 0x00 0xDE
1784        let data = [0x3D, 0xD8, 0x00, 0xDE];
1785        let result = utf16le_to_utf8(&data).unwrap();
1786        assert_eq!(result, "😀".as_bytes());
1787    }
1788
1789    #[test]
1790    fn test_utf16le_to_utf8_unpaired_surrogate() {
1791        let data = [0x00, 0xD8]; // High surrogate without low
1792        assert!(utf16le_to_utf8(&data).is_err());
1793    }
1794
1795    #[test]
1796    fn test_utf16le_to_utf8_truncated() {
1797        let data = [0x00]; // Odd length
1798        assert!(utf16le_to_utf8(&data).is_err());
1799    }
1800
1801    #[test]
1802    fn test_utf16le_to_utf8_empty() {
1803        let result = utf16le_to_utf8(b"").unwrap();
1804        assert!(result.is_empty());
1805    }
1806
1807    // ── UTF-16BE to UTF-8 ──────────────────────────────────────────────────
1808
1809    #[test]
1810    fn test_utf16be_to_utf8_ascii() {
1811        let data = [0x00, b'A', 0x00, b'B'];
1812        let result = utf16be_to_utf8(&data).unwrap();
1813        assert_eq!(result, b"AB");
1814    }
1815
1816    #[test]
1817    fn test_utf16be_to_utf8_bom() {
1818        let mut data = vec![0xFE, 0xFF]; // BOM
1819        data.extend_from_slice(&[0x00, b'A', 0x00, b'B']);
1820        let result = utf16be_to_utf8(&data).unwrap();
1821        assert_eq!(result, b"AB");
1822    }
1823
1824    #[test]
1825    fn test_utf16be_to_utf8_supplementary() {
1826        // U+1F600 (😀) in UTF-16BE = 0xD8 0x3D 0xDE 0x00
1827        let data = [0xD8, 0x3D, 0xDE, 0x00];
1828        let result = utf16be_to_utf8(&data).unwrap();
1829        assert_eq!(result, "😀".as_bytes());
1830    }
1831
1832    #[test]
1833    fn test_utf16be_to_utf8_empty() {
1834        let result = utf16be_to_utf8(b"").unwrap();
1835        assert!(result.is_empty());
1836    }
1837
1838    // ── UTF-8 to UTF-16LE ──────────────────────────────────────────────────
1839
1840    #[test]
1841    fn test_utf8_to_utf16le_ascii() {
1842        let result = utf8_to_utf16le(b"AB").unwrap();
1843        assert_eq!(result, [b'A', 0x00, b'B', 0x00]);
1844    }
1845
1846    #[test]
1847    fn test_utf8_to_utf16le_bmp() {
1848        let result = utf8_to_utf16le("é".as_bytes()).unwrap();
1849        assert_eq!(result, [0xE9, 0x00]);
1850    }
1851
1852    #[test]
1853    fn test_utf8_to_utf16le_supplementary() {
1854        let result = utf8_to_utf16le("😀".as_bytes()).unwrap();
1855        assert_eq!(result, [0x3D, 0xD8, 0x00, 0xDE]);
1856    }
1857
1858    #[test]
1859    fn test_utf8_to_utf16le_invalid_utf8() {
1860        assert!(utf8_to_utf16le(&[0xFF]).is_err());
1861    }
1862
1863    #[test]
1864    fn test_utf8_to_utf16le_empty() {
1865        let result = utf8_to_utf16le(b"").unwrap();
1866        assert!(result.is_empty());
1867    }
1868
1869    // ── Latin-1 to UTF-8 ───────────────────────────────────────────────────
1870
1871    #[test]
1872    fn test_latin1_to_utf8_ascii() {
1873        let result = latin1_to_utf8(b"ABC");
1874        assert_eq!(result, b"ABC");
1875    }
1876
1877    #[test]
1878    fn test_latin1_to_utf8_accented() {
1879        // 0xE9 = é in Latin-1
1880        let result = latin1_to_utf8(&[0xE9]);
1881        assert_eq!(result, "é".as_bytes());
1882    }
1883
1884    #[test]
1885    fn test_latin1_to_utf8_all_255() {
1886        let result = latin1_to_utf8(&[0xFF]);
1887        // U+00FF = ÿ, UTF-8: 0xC3 0xBF
1888        assert_eq!(result, [0xC3, 0xBF]);
1889    }
1890
1891    #[test]
1892    fn test_latin1_to_utf8_empty() {
1893        let result = latin1_to_utf8(b"");
1894        assert!(result.is_empty());
1895    }
1896
1897    #[test]
1898    fn test_latin1_to_utf8_mixed() {
1899        let result = latin1_to_utf8(b"caf\xE9");
1900        assert_eq!(result, "café".as_bytes());
1901    }
1902
1903    // ── UTF-8 to Latin-1 ───────────────────────────────────────────────────
1904
1905    #[test]
1906    fn test_utf8_to_latin1_ascii() {
1907        let result = utf8_to_latin1(b"ABC").unwrap();
1908        assert_eq!(result, b"ABC");
1909    }
1910
1911    #[test]
1912    fn test_utf8_to_latin1_accented() {
1913        let result = utf8_to_latin1("é".as_bytes()).unwrap();
1914        assert_eq!(result, [0xE9]);
1915    }
1916
1917    #[test]
1918    fn test_utf8_to_latin1_out_of_range() {
1919        assert!(utf8_to_latin1("€".as_bytes()).is_err()); // U+20AC not in Latin-1
1920    }
1921
1922    #[test]
1923    fn test_utf8_to_latin1_invalid_utf8() {
1924        assert!(utf8_to_latin1(&[0xFF]).is_err());
1925    }
1926
1927    #[test]
1928    fn test_utf8_to_latin1_empty() {
1929        let result = utf8_to_latin1(b"").unwrap();
1930        assert!(result.is_empty());
1931    }
1932
1933    // ── Encoding handler registry ──────────────────────────────────────────
1934
1935    #[test]
1936    fn test_init_and_find_encodings() {
1937        init_encodings();
1938
1939        let utf8_name: *const xmlChar = b"UTF-8\0".as_ptr();
1940        assert!(!find_encoding_handler(utf8_name).is_null());
1941
1942        let utf16le_name: *const xmlChar = b"UTF-16LE\0".as_ptr();
1943        assert!(!find_encoding_handler(utf16le_name).is_null());
1944
1945        let utf16be_name: *const xmlChar = b"UTF-16BE\0".as_ptr();
1946        assert!(!find_encoding_handler(utf16be_name).is_null());
1947
1948        let latin1_name: *const xmlChar = b"ISO-8859-1\0".as_ptr();
1949        assert!(!find_encoding_handler(latin1_name).is_null());
1950
1951        let ascii_name: *const xmlChar = b"ASCII\0".as_ptr();
1952        assert!(!find_encoding_handler(ascii_name).is_null());
1953
1954        // Case insensitive
1955        let lower_name: *const xmlChar = b"utf-8\0".as_ptr();
1956        assert!(!find_encoding_handler(lower_name).is_null());
1957    }
1958
1959    #[test]
1960    fn test_find_encoding_handler_not_found() {
1961        let name: *const xmlChar = b"NONEXISTENT\0".as_ptr();
1962        assert!(find_encoding_handler(name).is_null());
1963    }
1964
1965    #[test]
1966    fn test_find_encoding_handler_null() {
1967        assert!(find_encoding_handler(ptr::null()).is_null());
1968    }
1969
1970    #[test]
1971    fn test_add_encoding_handler() {
1972        let handler = unsafe {
1973            xmlMalloc(size_of::<_xmlCharEncodingHandler>()) as *mut _xmlCharEncodingHandler
1974        };
1975        assert!(!handler.is_null());
1976
1977        let name =
1978            unsafe { crate::abi::allocator::xmlMemStrdup(b"TEST-ENC\0".as_ptr() as *const c_char) };
1979        unsafe {
1980            ptr::write(
1981                handler,
1982                _xmlCharEncodingHandler {
1983                    name: name as *mut c_char,
1984                    input: EncodingInputUnion { legacyFunc: None },
1985                    output: EncodingOutputUnion { legacyFunc: None },
1986                    inputCtxt: ptr::null_mut(),
1987                    outputCtxt: ptr::null_mut(),
1988                    ctxtDtor: None,
1989                    flags: 0,
1990                },
1991            );
1992        }
1993
1994        assert_eq!(add_encoding_handler(handler), 0);
1995
1996        let found = find_encoding_handler(b"TEST-ENC\0".as_ptr() as *const xmlChar);
1997        assert_eq!(found, handler);
1998
1999        // Remove from registry before freeing to avoid dangling pointers
2000        {
2001            let mut handlers = ENCODING_HANDLERS.write();
2002            handlers.retain(|&h| h.0 != handler);
2003        }
2004
2005        unsafe {
2006            xmlFree(name as *mut c_void);
2007            xmlFree(handler as *mut c_void);
2008        }
2009    }
2010
2011    // ── Conversion round-trips ─────────────────────────────────────────────
2012
2013    #[test]
2014    fn test_utf16le_roundtrip() {
2015        let original = b"Hello, World! UTF-16LE test: \xC3\xA9\xF0\x9F\x98\x80";
2016        let utf16 = utf8_to_utf16le(original).unwrap();
2017        let back = utf16le_to_utf8(&utf16).unwrap();
2018        assert_eq!(original.to_vec(), back);
2019    }
2020
2021    #[test]
2022    fn test_utf16be_roundtrip() {
2023        let original = b"Hello, World! UTF-16BE test: \xC3\xA9\xF0\x9F\x98\x80";
2024        let utf16le = utf8_to_utf16le(original).unwrap();
2025        // Convert LE to BE by swapping bytes
2026        let mut utf16be = utf16le.clone();
2027        for chunk in utf16be.chunks_exact_mut(2) {
2028            chunk.swap(0, 1);
2029        }
2030        let back = utf16be_to_utf8(&utf16be).unwrap();
2031        assert_eq!(original.to_vec(), back);
2032    }
2033
2034    #[test]
2035    fn test_latin1_roundtrip() {
2036        let original: Vec<u8> = (0x00..=0xFF).collect();
2037        let utf8 = latin1_to_utf8(&original);
2038        let back = utf8_to_latin1(&utf8).unwrap();
2039        assert_eq!(original, back);
2040    }
2041
2042    // ── Built-in handler callbacks ─────────────────────────────────────────
2043
2044    #[test]
2045    fn test_utf8_handler_identity() {
2046        let input = b"Hello, UTF-8!";
2047        let mut output = [0u8; 64];
2048        let mut outlen = output.len() as c_int;
2049        let mut inlen = input.len() as c_int;
2050
2051        let ret = unsafe {
2052            utf8_input_func(output.as_mut_ptr(), &mut outlen, input.as_ptr(), &mut inlen)
2053        };
2054
2055        assert_eq!(ret, input.len() as c_int);
2056        assert_eq!(&output[..ret as usize], input);
2057        assert_eq!(inlen, input.len() as c_int);
2058    }
2059
2060    #[test]
2061    fn test_utf16le_handler_roundtrip() {
2062        init_encodings();
2063
2064        let original = b"Hello UTF-16LE!";
2065        let mut utf16_buf = [0u8; 128];
2066        let mut outlen = utf16_buf.len() as c_int;
2067        let mut inlen = original.len() as c_int;
2068
2069        let written = unsafe {
2070            utf16le_output_func(
2071                utf16_buf.as_mut_ptr(),
2072                &mut outlen,
2073                original.as_ptr(),
2074                &mut inlen,
2075            )
2076        };
2077        assert!(written > 0);
2078
2079        // Now decode back
2080        let mut decoded = [0u8; 128];
2081        let mut outlen2 = decoded.len() as c_int;
2082        let mut inlen2 = written;
2083
2084        let written2 = unsafe {
2085            utf16le_input_func(
2086                decoded.as_mut_ptr(),
2087                &mut outlen2,
2088                utf16_buf.as_ptr(),
2089                &mut inlen2,
2090            )
2091        };
2092        assert_eq!(written2 as usize, original.len());
2093        assert_eq!(&decoded[..written2 as usize], original);
2094    }
2095
2096    // ── xmlBuffer operations ───────────────────────────────────────────────
2097
2098    #[test]
2099    fn test_append_to_xml_buffer() {
2100        unsafe {
2101            let content = xmlMalloc(64) as *mut xmlChar;
2102            assert!(!content.is_null());
2103
2104            let mut buf = _xmlBuffer {
2105                content,
2106                use_: 0,
2107                size: 64,
2108                alloc: 0,
2109                contentIO: ptr::null_mut(),
2110            };
2111
2112            append_to_xml_buffer(&mut buf, b"Hello");
2113            assert_eq!(buf.use_, 5);
2114            let slice = core::slice::from_raw_parts(buf.content, 5);
2115            assert_eq!(slice, b"Hello");
2116
2117            append_to_xml_buffer(&mut buf, b" World");
2118            assert_eq!(buf.use_, 11);
2119            let slice = core::slice::from_raw_parts(buf.content, 11);
2120            assert_eq!(slice, b"Hello World");
2121
2122            xmlFree(buf.content as *mut c_void);
2123        }
2124    }
2125
2126    // ── ABI export functions ───────────────────────────────────────────────
2127
2128    #[test]
2129    fn test_xml_parse_char_encoding() {
2130        let name = b"UTF-8\0".as_ptr() as *const c_char;
2131        assert_eq!(
2132            xmlParseCharEncoding(name),
2133            xmlCharEncoding::XML_CHAR_ENCODING_UTF8 as c_int
2134        );
2135
2136        let name = b"ISO-8859-1\0".as_ptr() as *const c_char;
2137        assert_eq!(
2138            xmlParseCharEncoding(name),
2139            xmlCharEncoding::XML_CHAR_ENCODING_8859_1 as c_int
2140        );
2141
2142        assert_eq!(
2143            xmlParseCharEncoding(ptr::null()),
2144            xmlCharEncoding::XML_CHAR_ENCODING_NONE as c_int
2145        );
2146    }
2147
2148    #[test]
2149    fn test_xml_new_and_del_encoding_handler() {
2150        let name = b"TestEnc\0".as_ptr() as *const c_char;
2151        let handler = xmlNewCharEncodingHandler(
2152            name,
2153            utf8_input_func as xmlCharEncodingInputFunc,
2154            utf8_output_func as xmlCharEncodingOutputFunc,
2155        );
2156        assert!(!handler.is_null());
2157
2158        unsafe {
2159            assert!(!(*handler).name.is_null());
2160            let cstr = CStr::from_ptr((*handler).name);
2161            assert_eq!(cstr.to_bytes(), b"TestEnc");
2162        }
2163
2164        xmlDelEncodingHandler(handler);
2165    }
2166
2167    #[test]
2168    fn test_xml_init_and_cleanup() {
2169        xmlInitCharEncodingHandlers();
2170
2171        let name: *const xmlChar = b"UTF-8\0".as_ptr();
2172        assert!(!find_encoding_handler(name).is_null());
2173
2174        xmlCleanupCharEncodingHandlers();
2175        // After cleanup, handlers should be empty
2176    }
2177}