1use std::ptr;
6use std::rc::Rc;
7
8use base::id::{BlobId, BlobIndex};
9use constellation_traits::{BlobData, BlobImpl};
10use dom_struct::dom_struct;
11use encoding_rs::UTF_8;
12use js::jsapi::JSObject;
13use js::rust::HandleObject;
14use js::typedarray::{ArrayBufferU8, Uint8};
15use net_traits::filemanager_thread::RelativePos;
16use rustc_hash::FxHashMap;
17use uuid::Uuid;
18
19use crate::dom::bindings::buffer_source::create_buffer_source;
20use crate::dom::bindings::codegen::Bindings::BlobBinding;
21use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
22use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
23use crate::dom::bindings::error::{Error, Fallible};
24use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
25use crate::dom::bindings::root::DomRoot;
26use crate::dom::bindings::serializable::Serializable;
27use crate::dom::bindings::str::DOMString;
28use crate::dom::bindings::structuredclone::StructuredData;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::promise::Promise;
31use crate::dom::stream::readablestream::ReadableStream;
32use crate::realms::{AlreadyInRealm, InRealm};
33use crate::script_runtime::CanGc;
34
35#[dom_struct]
37pub(crate) struct Blob {
38 reflector_: Reflector,
39 #[no_trace]
40 blob_id: BlobId,
41}
42
43impl Blob {
44 pub(crate) fn new(global: &GlobalScope, blob_impl: BlobImpl, can_gc: CanGc) -> DomRoot<Blob> {
45 Self::new_with_proto(global, None, blob_impl, can_gc)
46 }
47
48 fn new_with_proto(
49 global: &GlobalScope,
50 proto: Option<HandleObject>,
51 blob_impl: BlobImpl,
52 can_gc: CanGc,
53 ) -> DomRoot<Blob> {
54 let dom_blob = reflect_dom_object_with_proto(
55 Box::new(Blob::new_inherited(&blob_impl)),
56 global,
57 proto,
58 can_gc,
59 );
60 global.track_blob(&dom_blob, blob_impl);
61 dom_blob
62 }
63
64 pub(crate) fn new_inherited(blob_impl: &BlobImpl) -> Blob {
65 Blob {
66 reflector_: Reflector::new(),
67 blob_id: blob_impl.blob_id(),
68 }
69 }
70
71 pub(crate) fn get_bytes(&self) -> Result<Vec<u8>, ()> {
73 self.global().get_blob_bytes(&self.blob_id)
74 }
75
76 pub(crate) fn type_string(&self) -> String {
78 self.global().get_blob_type_string(&self.blob_id)
79 }
80
81 pub(crate) fn get_blob_url_id(&self) -> Uuid {
84 self.global().get_blob_url_id(&self.blob_id)
85 }
86
87 pub(crate) fn get_stream(&self, can_gc: CanGc) -> Fallible<DomRoot<ReadableStream>> {
89 self.global().get_blob_stream(&self.blob_id, can_gc)
90 }
91}
92
93impl Serializable for Blob {
94 type Index = BlobIndex;
95 type Data = BlobImpl;
96
97 fn serialize(&self) -> Result<(BlobId, BlobImpl), ()> {
99 let blob_id = self.blob_id;
100
101 let blob_impl = self.global().serialize_blob(&blob_id);
103
104 let new_blob_id = blob_impl.blob_id();
106
107 Ok((new_blob_id, blob_impl))
108 }
109
110 fn deserialize(
112 owner: &GlobalScope,
113 serialized: BlobImpl,
114 can_gc: CanGc,
115 ) -> Result<DomRoot<Self>, ()> {
116 let deserialized_blob = Blob::new(owner, serialized, can_gc);
117 Ok(deserialized_blob)
118 }
119
120 fn serialized_storage<'a>(
121 reader: StructuredData<'a, '_>,
122 ) -> &'a mut Option<FxHashMap<BlobId, Self::Data>> {
123 match reader {
124 StructuredData::Reader(r) => &mut r.blob_impls,
125 StructuredData::Writer(w) => &mut w.blobs,
126 }
127 }
128}
129
130#[expect(unsafe_code)]
133pub(crate) fn blob_parts_to_bytes(
134 mut blobparts: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>,
135) -> Result<Vec<u8>, ()> {
136 let mut ret = vec![];
137 for blobpart in &mut blobparts {
138 match blobpart {
139 ArrayBufferOrArrayBufferViewOrBlobOrString::String(s) => {
140 ret.extend_from_slice(&s.as_bytes());
141 },
142 ArrayBufferOrArrayBufferViewOrBlobOrString::Blob(b) => {
143 let bytes = b.get_bytes().unwrap_or(vec![]);
144 ret.extend(bytes);
145 },
146 ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBuffer(a) => unsafe {
147 let bytes = a.as_slice();
148 ret.extend(bytes);
149 },
150 ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBufferView(a) => unsafe {
151 let bytes = a.as_slice();
152 ret.extend(bytes);
153 },
154 }
155 }
156
157 Ok(ret)
158}
159
160impl BlobMethods<crate::DomTypeHolder> for Blob {
161 #[expect(non_snake_case)]
163 fn Constructor(
164 global: &GlobalScope,
165 proto: Option<HandleObject>,
166 can_gc: CanGc,
167 blobParts: Option<Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>>,
168 blobPropertyBag: &BlobBinding::BlobPropertyBag,
169 ) -> Fallible<DomRoot<Blob>> {
170 let bytes: Vec<u8> = match blobParts {
171 None => Vec::new(),
172 Some(blobparts) => match blob_parts_to_bytes(blobparts) {
173 Ok(bytes) => bytes,
174 Err(_) => return Err(Error::InvalidCharacter(None)),
175 },
176 };
177
178 let type_string = normalize_type_string(&blobPropertyBag.type_.str());
179 let blob_impl = BlobImpl::new_from_bytes(bytes, type_string);
180
181 Ok(Blob::new_with_proto(global, proto, blob_impl, can_gc))
182 }
183
184 fn Size(&self) -> u64 {
186 self.global().get_blob_size(&self.blob_id)
187 }
188
189 fn Type(&self) -> DOMString {
191 DOMString::from(self.type_string())
192 }
193
194 fn Stream(&self, can_gc: CanGc) -> Fallible<DomRoot<ReadableStream>> {
196 self.get_stream(can_gc)
197 }
198
199 fn Slice(
201 &self,
202 start: Option<i64>,
203 end: Option<i64>,
204 content_type: Option<DOMString>,
205 can_gc: CanGc,
206 ) -> DomRoot<Blob> {
207 let global = self.global();
208 let type_string = normalize_type_string(&content_type.unwrap_or_default().str());
209
210 let (parent, range) = match *global.get_blob_data(&self.blob_id) {
213 BlobData::Sliced(grandparent, parent_range) => {
214 let range = RelativePos {
215 start: parent_range.start + start.unwrap_or_default(),
216 end: end.map(|end| end + parent_range.start).or(parent_range.end),
217 };
218 (grandparent, range)
219 },
220 _ => (self.blob_id, RelativePos::from_opts(start, end)),
221 };
222
223 let blob_impl = BlobImpl::new_sliced(range, parent, type_string);
224 Blob::new(&global, blob_impl, can_gc)
225 }
226
227 fn Text(&self, can_gc: CanGc) -> Rc<Promise> {
229 let global = self.global();
230 let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
231 let p = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
232 let id = self.get_blob_url_id();
233 global.read_file_async(
234 id,
235 p.clone(),
236 Box::new(|promise, bytes| match bytes {
237 Ok(b) => {
238 let (text, _, _) = UTF_8.decode(&b);
239 let text = DOMString::from(text);
240 promise.resolve_native(&text, CanGc::note());
241 },
242 Err(e) => {
243 promise.reject_error(e, CanGc::note());
244 },
245 }),
246 );
247 p
248 }
249
250 fn ArrayBuffer(&self, in_realm: InRealm, can_gc: CanGc) -> Rc<Promise> {
252 let cx = GlobalScope::get_cx();
253 let promise = Promise::new_in_current_realm(in_realm, can_gc);
254
255 let stream = self.get_stream(can_gc);
257
258 let reader = match stream.and_then(|s| s.acquire_default_reader(can_gc)) {
261 Ok(reader) => reader,
262 Err(error) => {
263 promise.reject_error(error, can_gc);
264 return promise;
265 },
266 };
267
268 let success_promise = promise.clone();
270 let failure_promise = promise.clone();
271 reader.read_all_bytes(
272 cx,
273 Rc::new(move |bytes| {
274 rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
275 let array_buffer = create_buffer_source::<ArrayBufferU8>(
278 cx,
279 bytes,
280 js_object.handle_mut(),
281 can_gc,
282 )
283 .expect("Converting input to ArrayBufferU8 should never fail");
284 success_promise.resolve_native(&array_buffer, can_gc);
285 }),
286 Rc::new(move |cx, value| {
287 failure_promise.reject(cx, value, can_gc);
288 }),
289 can_gc,
290 );
291
292 promise
293 }
294
295 fn Bytes(&self, in_realm: InRealm, can_gc: CanGc) -> Rc<Promise> {
297 let cx = GlobalScope::get_cx();
298 let p = Promise::new_in_current_realm(in_realm, can_gc);
299
300 let stream = self.get_stream(can_gc);
302
303 let reader = match stream.and_then(|s| s.acquire_default_reader(can_gc)) {
306 Ok(r) => r,
307 Err(e) => {
308 p.reject_error(e, can_gc);
309 return p;
310 },
311 };
312
313 let p_success = p.clone();
315 let p_failure = p.clone();
316 reader.read_all_bytes(
317 cx,
318 Rc::new(move |bytes| {
319 rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
320 let arr = create_buffer_source::<Uint8>(cx, bytes, js_object.handle_mut(), can_gc)
321 .expect("Converting input to uint8 array should never fail");
322 p_success.resolve_native(&arr, can_gc);
323 }),
324 Rc::new(move |cx, v| {
325 p_failure.reject(cx, v, can_gc);
326 }),
327 can_gc,
328 );
329 p
330 }
331}
332
333pub(crate) fn normalize_type_string(s: &str) -> String {
339 if is_ascii_printable(s) {
340 s.to_ascii_lowercase()
341 } else {
345 "".to_string()
346 }
347}
348
349fn is_ascii_printable(string: &str) -> bool {
350 string.chars().all(|c| ('\x20'..='\x7E').contains(&c))
353}