1use std::str::FromStr;
8
9use bytes::{BufMut as _, Bytes, BytesMut};
10use encoding_rs::UTF_8;
11use ion::class::Reflector;
12use ion::conversions::FromValue;
13use ion::format::NEWLINE;
14use ion::function::{Clamp, Opt};
15use ion::typedarray::{ArrayBuffer, ArrayBufferView, ArrayBufferWrapper, Uint8ArrayWrapper};
16use ion::{ClassDefinition as _, Context, Error, ErrorKind, FromValue, Object, Promise, Result, Value, js_class};
17use mozjs::jsapi::JSObject;
18
19use crate::promise::future_to_promise;
20
21#[derive(Debug)]
22pub enum BufferSource<'cx> {
23 Buffer(ArrayBuffer<'cx>),
24 View(ArrayBufferView<'cx>),
25}
26
27impl BufferSource<'_> {
28 pub fn len(&self) -> usize {
29 unsafe { self.as_slice().len() }
30 }
31
32 pub fn is_empty(&self) -> bool {
33 self.len() == 0
34 }
35
36 pub fn is_shared(&self) -> bool {
37 match self {
38 BufferSource::Buffer(buffer) => buffer.is_shared(),
39 BufferSource::View(view) => view.is_shared(),
40 }
41 }
42
43 pub unsafe fn as_slice(&self) -> &[u8] {
44 match self {
45 BufferSource::Buffer(buffer) => unsafe { buffer.as_slice() },
46 BufferSource::View(view) => unsafe { view.as_slice() },
47 }
48 }
49
50 pub fn to_vec(&self) -> Vec<u8> {
51 unsafe { self.as_slice().to_vec() }
52 }
53
54 pub fn to_bytes(&self) -> Bytes {
55 Bytes::copy_from_slice(unsafe { self.as_slice() })
56 }
57}
58
59impl<'cx> FromValue<'cx> for BufferSource<'cx> {
60 type Config = bool;
61 fn from_value(cx: &'cx Context, value: &Value, strict: bool, allow_shared: bool) -> Result<BufferSource<'cx>> {
62 let obj = Object::from_value(cx, value, strict, ())?;
63 if let Some(buffer) = ArrayBuffer::from(cx.root(obj.handle().get())) {
64 if buffer.is_shared() && !allow_shared {
65 return Err(Error::new("Buffer Source cannot be shared", ErrorKind::Type));
66 }
67 Ok(BufferSource::Buffer(buffer))
68 } else if let Some(view) = ArrayBufferView::from(obj.into_local()) {
69 if view.is_shared() && !allow_shared {
70 return Err(Error::new("Buffer Source cannot be shared", ErrorKind::Type));
71 }
72 Ok(BufferSource::View(view))
73 } else {
74 Err(Error::new("Object is not a buffer source.", ErrorKind::Type))
75 }
76 }
77}
78
79#[derive(Debug, FromValue)]
80#[ion(inherit)]
81pub enum BlobPart<'cx> {
82 String(String),
83 BufferSource(#[ion(convert = false)] BufferSource<'cx>),
84 Blob(&'cx Blob),
85}
86
87#[derive(Clone, Copy, Debug, Default)]
88pub enum Endings {
89 #[default]
90 Transparent,
91 Native,
92}
93
94impl Endings {
95 fn convert(self, buffer: &mut BytesMut, string: &str) {
96 let string = string.as_bytes();
97 match self {
98 Endings::Transparent => buffer.extend(string),
99 Endings::Native => {
100 let mut i = 0;
101 while let Some(&b) = string.get(i) {
102 i += 1;
103 if b == b'\r' {
104 buffer.extend_from_slice(NEWLINE.as_bytes());
105 if string.get(i) == Some(&b'\n') {
106 i += 1;
107 }
108 } else if b == b'\n' {
109 buffer.extend_from_slice(NEWLINE.as_bytes());
110 } else {
111 buffer.put_u8(b);
112 }
113 }
114 }
115 }
116 }
117}
118
119impl FromStr for Endings {
120 type Err = Error;
121
122 fn from_str(endings: &str) -> Result<Endings> {
123 match endings {
124 "transparent" => Ok(Endings::Transparent),
125 "native" => Ok(Endings::Native),
126 _ => Err(Error::new("Invalid ending type for Blob", ErrorKind::Type)),
127 }
128 }
129}
130
131impl<'cx> FromValue<'cx> for Endings {
132 type Config = ();
133
134 fn from_value(cx: &'cx Context, value: &Value, strict: bool, _: ()) -> Result<Endings> {
135 let endings = String::from_value(cx, value, strict, ())?;
136 Endings::from_str(&endings)
137 }
138}
139
140#[derive(Debug, Default, FromValue)]
141pub struct BlobOptions {
142 #[ion(name = "type")]
143 kind: Option<String>,
144 #[ion(default)]
145 endings: Endings,
146}
147
148fn validate_kind(kind: Option<String>) -> Option<String> {
149 kind.filter(|kind| kind.as_bytes().iter().all(|b| matches!(b, 0x20..=0x7E)))
150 .map(|mut kind| {
151 kind.make_ascii_lowercase();
152 kind
153 })
154}
155
156#[derive(Debug)]
157#[js_class]
158pub struct Blob {
159 pub(crate) reflector: Reflector,
160 #[trace(no_trace)]
161 pub(crate) bytes: Bytes,
162 pub(crate) kind: Option<String>,
163}
164
165#[js_class]
166impl Blob {
167 #[ion(constructor)]
168 pub fn constructor(Opt(parts): Opt<Vec<BlobPart>>, Opt(options): Opt<BlobOptions>) -> Blob {
169 let options = options.unwrap_or_default();
170
171 let mut bytes = BytesMut::new();
172
173 if let Some(parts) = parts {
174 for part in parts {
175 match part {
176 BlobPart::String(str) => options.endings.convert(&mut bytes, &str),
177 BlobPart::BufferSource(source) => bytes.extend_from_slice(unsafe { source.as_slice() }),
178 BlobPart::Blob(blob) => bytes.extend_from_slice(&blob.bytes),
179 }
180 }
181 }
182
183 Blob {
184 reflector: Reflector::default(),
185 bytes: bytes.freeze(),
186 kind: validate_kind(options.kind),
187 }
188 }
189
190 #[ion(get)]
191 pub fn get_size(&self) -> u64 {
192 self.bytes.len() as u64
193 }
194
195 #[ion(get)]
196 pub fn get_type(&self) -> String {
197 self.kind.clone().unwrap_or_default()
198 }
199
200 pub fn slice(
201 &self, cx: &Context, Opt(start): Opt<Clamp<i64>>, Opt(end): Opt<Clamp<i64>>, Opt(kind): Opt<String>,
202 ) -> *mut JSObject {
203 let size = self.bytes.len() as i64;
204
205 let mut start = start.unwrap_or_default().0;
206 if start < 0 {
207 start = 0.max(size + start);
208 }
209 let start = usize::try_from(start.min(size)).unwrap();
210
211 let mut end = end.unwrap_or(Clamp(size)).0;
212 if end < 0 {
213 end = 0.max(size + end);
214 }
215 let end = usize::try_from(end.min(size)).unwrap();
216
217 let span = 0.max(end - start);
218 let bytes = self.bytes.slice(start..start + span);
219
220 let blob = Blob {
221 reflector: Reflector::default(),
222 bytes,
223 kind: validate_kind(kind),
224 };
225 Blob::new_object(cx, Box::new(blob))
226 }
227
228 pub fn text<'cx>(&self, cx: &'cx Context) -> Option<Promise<'cx>> {
229 let bytes = self.bytes.clone();
230 future_to_promise(cx, |_| async move { Ok::<_, ()>(UTF_8.decode(&bytes).0.into_owned()) })
231 }
232
233 #[ion(name = "arrayBuffer")]
234 pub fn array_buffer<'cx>(&self, cx: &'cx Context) -> Option<Promise<'cx>> {
235 let bytes = self.bytes.clone();
236 future_to_promise(
237 cx,
238 |_| async move { Ok::<_, ()>(ArrayBufferWrapper::from(bytes.to_vec())) },
239 )
240 }
241
242 pub fn bytes<'cx>(&self, cx: &'cx Context) -> Option<Promise<'cx>> {
243 let bytes = self.bytes.clone();
244 future_to_promise(
245 cx,
246 |_| async move { Ok::<_, ()>(Uint8ArrayWrapper::from(bytes.to_vec())) },
247 )
248 }
249}