ferrijs_std/text/
text_decoder.rs1use crate::encoding::Encoder;
4use crate::utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt};
5use rquickjs::{atom::PredefinedAtom, function::Opt, Ctx, Object, Result, Value};
6use std::cell::{Cell, RefCell};
7
8#[rquickjs::class]
9#[derive(rquickjs::class::Trace, rquickjs::JsLifetime)]
10pub struct TextDecoder {
11 #[qjs(skip_trace)]
12 encoder: Encoder,
13 fatal: bool,
14 ignore_bom: bool,
15 #[qjs(skip_trace)]
16 pending: RefCell<Vec<u8>>,
17 #[qjs(skip_trace)]
18 bom_seen: Cell<bool>,
19}
20
21fn utf8_incomplete_tail(bytes: &[u8]) -> usize {
25 let len = bytes.len();
26 for i in 1..=4.min(len) {
27 let b = bytes[len - i];
28 if b < 0x80 {
29 return 0;
30 }
31 if b >= 0xC0 {
32 let expected = match b {
33 0xC2..=0xDF => 2,
34 0xE0..=0xEF => 3,
35 0xF0..=0xF4 => 4,
36 _ => return 0,
37 };
38 if i >= expected {
39 return 0;
40 }
41 let tail = &bytes[len - i + 1..];
43 for (j, &c) in tail.iter().enumerate() {
44 if j == 0 {
45 let valid = match b {
47 0xE0 => (0xA0..=0xBF).contains(&c),
48 0xED => (0x80..=0x9F).contains(&c),
49 0xF0 => (0x90..=0xBF).contains(&c),
50 0xF4 => (0x80..=0x8F).contains(&c),
51 _ => (0x80..=0xBF).contains(&c),
52 };
53 if !valid {
54 return 0;
55 }
56 } else if c & 0xC0 != 0x80 {
57 return 0;
58 }
59 }
60 return i;
61 }
62 }
63 0
64}
65
66#[rquickjs::methods]
67impl<'js> TextDecoder {
68 #[qjs(constructor)]
69 pub fn new(ctx: Ctx<'js>, label: Opt<String>, options: Opt<Object<'js>>) -> Result<Self> {
70 let mut fatal = false;
71 let mut ignore_bom = false;
72
73 let encoder = Encoder::from_optional_web_label(label.as_deref()).or_throw_range(&ctx, "")?;
74
75 if let Some(opts) = options.0 {
76 if let Some(opt) = opts.get_optional("fatal")? {
77 fatal = opt;
78 }
79 if let Some(opt) = opts.get_optional("ignoreBOM")? {
80 ignore_bom = opt;
81 }
82 }
83
84 Ok(TextDecoder {
85 encoder,
86 fatal,
87 ignore_bom,
88 pending: RefCell::new(Vec::new()),
89 bom_seen: Cell::new(false),
90 })
91 }
92
93 #[qjs(get)]
94 pub(crate) fn encoding(&self) -> &str {
95 self.encoder.as_label()
96 }
97
98 #[qjs(get)]
99 pub(crate) fn fatal(&self) -> bool {
100 self.fatal
101 }
102
103 #[qjs(get, rename = "ignoreBOM")]
104 pub(crate) fn ignore_bom(&self) -> bool {
105 self.ignore_bom
106 }
107
108 #[qjs(prop, rename = PredefinedAtom::SymbolToStringTag, configurable)]
109 pub fn to_string_tag() -> &'static str {
110 stringify!(TextDecoder)
111 }
112
113 pub fn decode(
114 &self,
115 ctx: Ctx<'js>,
116 bytes: Opt<ObjectBytes<'js>>,
117 options: Opt<Value<'js>>,
118 ) -> Result<String> {
119 let mut stream = false;
120 if let Some(opts) = options.0.as_ref().and_then(|v| v.as_object()) {
121 if let Some(s) = opts.get_optional("stream")? {
122 stream = s;
123 }
124 }
125
126 let input_bytes: &[u8] = bytes
132 .0
133 .as_ref()
134 .and_then(ObjectBytes::as_bytes_opt)
135 .unwrap_or(&[]);
136
137 let mut pending = self.pending.borrow_mut();
138
139 let combined: Vec<u8>;
141 let mut data: &[u8] = if pending.is_empty() {
142 input_bytes
143 } else {
144 pending.extend_from_slice(input_bytes);
145 combined = std::mem::take(&mut *pending);
146 &combined
147 };
148
149 if !stream {
150 self.bom_seen.set(false);
151 }
152
153 if !self.ignore_bom && !self.bom_seen.get() {
155 let skip = match self.encoder {
156 Encoder::Utf8 if data.starts_with(&[0xEF, 0xBB, 0xBF]) => 3,
157 Encoder::Utf16le if data.starts_with(&[0xFF, 0xFE]) => 2,
158 Encoder::Utf16be if data.starts_with(&[0xFE, 0xFF]) => 2,
159 _ => 0,
160 };
161
162 if skip > 0 {
163 self.bom_seen.set(true);
164 data = &data[skip..];
165 } else if stream
166 && match self.encoder {
167 Encoder::Utf8 => data == [0xEF] || data == [0xEF, 0xBB],
168 Encoder::Utf16le => data == [0xFF],
169 Encoder::Utf16be => data == [0xFE],
170 _ => false,
171 }
172 {
173 *pending = data.to_vec();
175 return Ok(String::new());
176 } else if !data.is_empty() {
177 self.bom_seen.set(true); }
179 }
180
181 let mut decode_end = data.len();
182
183 if stream {
184 match self.encoder {
185 Encoder::Utf8 => {
186 decode_end -= utf8_incomplete_tail(data);
187 },
188 Encoder::Utf16le | Encoder::Utf16be => {
189 let odd = data.len() % 2;
191 decode_end -= odd;
192 if decode_end >= 2 {
194 let last_u16 = if matches!(self.encoder, Encoder::Utf16le) {
195 u16::from_le_bytes([data[decode_end - 2], data[decode_end - 1]])
196 } else {
197 u16::from_be_bytes([data[decode_end - 2], data[decode_end - 1]])
198 };
199 if (0xD800..=0xDBFF).contains(&last_u16) {
200 decode_end -= 2;
201 }
202 }
203 },
204 _ => {},
205 }
206
207 if decode_end < data.len() {
208 *pending = data[decode_end..].to_vec();
209 }
210 }
211
212 self.encoder
213 .encode_to_string(&data[..decode_end], !self.fatal)
214 .or_throw_type(&ctx, "")
215 }
216}