char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport encoding extension to typescriptf2e5e0e

main
6.6 KiB160 linesraw
1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::convert::Infallible;
4use std::io;
5use std::rc::Rc;
6
7use avril_core::serde_v8::{JsBuffer, ToJsBuffer};
8use avril_core::{Extension, Op, v8};
9use encoding_rs::{CoderResult, Decoder, DecoderResult, Encoding, REPLACEMENT, UTF_8};
10
11type DecodeArgs = (Option<u64>, String, bool, bool, JsBuffer, bool);
12
13struct State {
14    decoder: Decoder,
15    fatal: bool,
16}
17
18impl State {
19    fn decode(&mut self, bytes: &[u8], stream: bool) -> io::Result<(String, bool)> {
20        let capacity = self
21            .decoder
22            .max_utf8_buffer_length(bytes.len())
23            .ok_or_else(|| io::Error::other("Decoded string is too large"))?;
24        let mut output = String::new();
25        output.try_reserve(capacity).map_err(io::Error::other)?;
26        let invalid = if self.fatal {
27            let (result, _) =
28                self.decoder
29                    .decode_to_string_without_replacement(bytes, &mut output, !stream);
30            match result {
31                DecoderResult::InputEmpty => false,
32                // Browsers and WPT discard the bad chunk's remainder but retain codec state.
33                DecoderResult::Malformed(_, _) => true,
34                DecoderResult::OutputFull => {
35                    return Err(io::Error::other("Decoder output exceeded its size bound"));
36                }
37            }
38        } else {
39            let (result, _, _) = self.decoder.decode_to_string(bytes, &mut output, !stream);
40            if result == CoderResult::OutputFull {
41                return Err(io::Error::other("Decoder output exceeded its size bound"));
42            }
43            false
44        };
45        Ok((output, invalid))
46    }
47}
48
49#[derive(Default)]
50struct Decoders {
51    next: u64,
52    states: HashMap<u64, State>,
53}
54
55pub fn init() -> Extension {
56    let decoders = Rc::new(RefCell::new(Decoders::default()));
57    let decode = decoders.clone();
58    Extension {
59        name: "ext:encoding",
60        dependencies: &["ext:web", "ext:webstreams"],
61        ops: vec![
62            Op::new("op_encoding_label", |label: String| {
63                Ok::<_, Infallible>(
64                    Encoding::for_label(label.as_bytes())
65                        .filter(|encoding| *encoding != REPLACEMENT)
66                        .map(|encoding| encoding.name().to_ascii_lowercase()),
67                )
68            }),
69            Op::new(
70                "op_encoding_decode",
71                move |(id, label, fatal, ignore_bom, bytes, stream): DecodeArgs| {
72                    let mut decoders = decode.borrow_mut();
73                    let mut state = if let Some(id) = id {
74                        decoders
75                            .states
76                            .remove(&id)
77                            .ok_or_else(|| io::Error::other("Decoder is closed"))?
78                    } else {
79                        let encoding = Encoding::for_label(label.as_bytes())
80                            .filter(|encoding| *encoding != REPLACEMENT)
81                            .ok_or_else(|| io::Error::other("Invalid encoding label"))?;
82                        let decoder = if ignore_bom {
83                            encoding.new_decoder_without_bom_handling()
84                        } else {
85                            encoding.new_decoder_with_bom_removal()
86                        };
87                        State { decoder, fatal }
88                    };
89                    let (text, invalid) = state.decode(&bytes, stream)?;
90                    let id = if stream {
91                        let id = match id {
92                            Some(id) => id,
93                            None => {
94                                let id = decoders
95                                    .next
96                                    .checked_add(1)
97                                    .filter(|id| *id <= 9_007_199_254_740_991)
98                                    .ok_or_else(|| {
99                                        io::Error::other("Encoding resource IDs exhausted")
100                                    })?;
101                                decoders.next = id;
102                                id
103                            }
104                        };
105                        decoders.states.insert(id, state);
106                        Some(id)
107                    } else {
108                        None
109                    };
110                    Ok::<_, io::Error>((text, invalid, id))
111                },
112            ),
113            Op::new("op_encoding_close", move |id: u64| {
114                decoders.borrow_mut().states.remove(&id);
115                Ok::<_, Infallible>(())
116            }),
117            Op::try_raw("op_encoding_decode_utf8", |scope, args| {
118                let bytes: JsBuffer =
119                    avril_core::serde_v8::from_v8(scope, args.get(0)).map_err(io::Error::other)?;
120                // Body collection already owns fixed, unshared bytes; no snapshot or decoder state.
121                let text = if bytes.is_ascii() {
122                    v8::String::new_from_one_byte(scope, &bytes, v8::NewStringType::Normal)
123                } else {
124                    let (text, _) = UTF_8.decode_with_bom_removal(&bytes);
125                    v8::String::new(scope, &text)
126                }
127                .ok_or_else(|| io::Error::other("Decoded string is too large"))?;
128                Ok::<_, io::Error>(text.into())
129            }),
130            Op::new("op_encoding_encode", |text: String| {
131                Ok::<_, Infallible>(ToJsBuffer::from(text.into_bytes()))
132            }),
133            Op::try_raw("op_encoding_encode_into", |scope, args| {
134                let text = v8::Local::<v8::String>::try_from(args.get(0))
135                    .map_err(|_| io::Error::other("Expected an encoding source string"))?;
136                let mut destination: JsBuffer =
137                    avril_core::serde_v8::from_v8(scope, args.get(1)).map_err(io::Error::other)?;
138                let mut read = 0;
139                let written = text.write_utf8_v2(
140                    scope,
141                    &mut destination,
142                    v8::WriteFlags::kReplaceInvalidUtf8,
143                    Some(&mut read),
144                );
145                #[derive(serde::Serialize)]
146                struct Counts {
147                    read: usize,
148                    written: usize,
149                }
150                avril_core::serde_v8::to_v8(scope, Counts { read, written })
151                    .map_err(io::Error::other)
152            }),
153        ],
154        modules: &[(
155            "ext:encoding/main.js",
156            include_str!(concat!(env!("OUT_DIR"), "/encoding.js")),
157        )],
158        entry_point: Some("ext:encoding/main.js"),
159    }
160}