1use encoding_rs::Encoding;
6use fhp_core::error::EncodingError;
7
8pub fn decode(input: &[u8], encoding: &'static Encoding) -> Result<String, EncodingError> {
30 let input = strip_bom(input, encoding);
32
33 let (cow, _actual_encoding, had_errors) = encoding.decode(input);
34
35 if had_errors {
36 let offset = find_first_error_offset(input, encoding);
38 return Err(EncodingError::MalformedInput {
39 encoding: encoding.name(),
40 offset,
41 });
42 }
43
44 Ok(cow.into_owned())
45}
46
47pub fn decode_or_detect(input: &[u8]) -> Result<(String, &'static Encoding), EncodingError> {
66 let encoding = crate::detect::detect(input);
67 let text = decode(input, encoding)?;
68 Ok((text, encoding))
69}
70
71fn strip_bom<'a>(input: &'a [u8], encoding: &'static Encoding) -> &'a [u8] {
73 if encoding == encoding_rs::UTF_8
74 && input.len() >= 3
75 && input[0] == 0xEF
76 && input[1] == 0xBB
77 && input[2] == 0xBF
78 {
79 return &input[3..];
80 }
81 if encoding == encoding_rs::UTF_16LE && input.len() >= 2 && input[0] == 0xFF && input[1] == 0xFE
82 {
83 return &input[2..];
84 }
85 if encoding == encoding_rs::UTF_16BE && input.len() >= 2 && input[0] == 0xFE && input[1] == 0xFF
86 {
87 return &input[2..];
88 }
89 input
90}
91
92fn find_first_error_offset(input: &[u8], encoding: &'static Encoding) -> usize {
97 let mut decoder = encoding.new_decoder_without_bom_handling();
99 let mut output = vec![0u8; 1024];
100 let mut pos = 0usize;
105
106 for chunk in input.chunks(256) {
107 let is_last = pos + chunk.len() >= input.len();
108 let (result, read, _) =
109 decoder.decode_to_utf8_without_replacement(chunk, &mut output, is_last);
110 if let encoding_rs::DecoderResult::Malformed(_, _) = result {
111 return pos + read;
112 }
113 pos += chunk.len();
114 }
115 0
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn decode_utf8() {
124 let text = decode(b"Hello, world!", encoding_rs::UTF_8).unwrap();
125 assert_eq!(text, "Hello, world!");
126 }
127
128 #[test]
129 fn decode_utf8_with_bom() {
130 let input = b"\xEF\xBB\xBFHello";
131 let text = decode(input, encoding_rs::UTF_8).unwrap();
132 assert_eq!(text, "Hello");
133 }
134
135 #[test]
136 fn decode_latin1() {
137 let input = b"caf\xe9";
139 let text = decode(input, encoding_rs::WINDOWS_1252).unwrap();
140 assert_eq!(text, "caf\u{00e9}");
141 }
142
143 #[test]
144 fn decode_windows_1252_turkish() {
145 let input: &[u8] = &[0xF6, 0xFC, 0xE7]; let text = decode(input, encoding_rs::WINDOWS_1254).unwrap();
149 assert_eq!(text, "\u{00f6}\u{00fc}\u{00e7}"); }
151
152 #[test]
153 fn decode_or_detect_utf8() {
154 let input = b"<html>Hello</html>";
155 let (text, enc) = decode_or_detect(input).unwrap();
156 assert_eq!(enc.name(), "UTF-8");
157 assert!(text.contains("Hello"));
158 }
159
160 #[test]
161 fn decode_or_detect_with_meta() {
162 let input =
163 b"<html><head><meta charset=\"windows-1254\"></head><body>\xFE\xF0\xFD</body></html>";
164 let (text, enc) = decode_or_detect(input).unwrap();
165 assert_eq!(enc.name(), "windows-1254");
166 assert!(text.contains('\u{015F}')); assert!(text.contains('\u{011F}')); assert!(text.contains('\u{0131}')); }
170
171 #[test]
172 fn decode_empty() {
173 let text = decode(b"", encoding_rs::UTF_8).unwrap();
174 assert_eq!(text, "");
175 }
176}