runtime/globals/file/
blob.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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}