1use std::ptr;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use encoding_rs::UTF_8;
10use js::context::JSContext;
11use js::jsapi::JSObject;
12use js::realm::CurrentRealm;
13use js::rust::HandleObject;
14use js::typedarray::{ArrayBufferU8, Uint8};
15use net_traits::filemanager_thread::RelativePos;
16use rustc_hash::FxHashMap;
17use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
18use servo_base::id::{BlobId, BlobIndex};
19use servo_constellation_traits::{BlobData, BlobImpl};
20use uuid::Uuid;
21
22use crate::dom::bindings::buffer_source::create_buffer_source;
23use crate::dom::bindings::codegen::Bindings::BlobBinding;
24use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
25use crate::dom::bindings::codegen::UnionTypes::ArrayBufferOrArrayBufferViewOrBlobOrString;
26use crate::dom::bindings::error::{Error, Fallible};
27use crate::dom::bindings::reflector::DomGlobal;
28use crate::dom::bindings::root::DomRoot;
29use crate::dom::bindings::serializable::Serializable;
30use crate::dom::bindings::str::DOMString;
31use crate::dom::bindings::structuredclone::StructuredData;
32use crate::dom::encoding::textdecoderstream::TextDecoderStream;
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::promise::Promise;
35use crate::dom::stream::readablestream::{ReadableStream, pipe_through};
36
37#[dom_struct]
39pub(crate) struct Blob {
40 reflector_: Reflector,
41 #[no_trace]
42 blob_id: BlobId,
43}
44
45impl Blob {
46 pub(crate) fn new(
47 cx: &mut JSContext,
48 global: &GlobalScope,
49 blob_impl: BlobImpl,
50 ) -> DomRoot<Blob> {
51 Self::new_with_proto(cx, global, None, blob_impl)
52 }
53
54 fn new_with_proto(
55 cx: &mut JSContext,
56 global: &GlobalScope,
57 proto: Option<HandleObject>,
58 blob_impl: BlobImpl,
59 ) -> DomRoot<Blob> {
60 let dom_blob = reflect_dom_object_with_proto_and_cx(
61 Box::new(Blob::new_inherited(&blob_impl)),
62 global,
63 proto,
64 cx,
65 );
66 global.track_blob(&dom_blob, blob_impl);
67 dom_blob
68 }
69
70 pub(crate) fn new_inherited(blob_impl: &BlobImpl) -> Blob {
71 Blob {
72 reflector_: Reflector::new(),
73 blob_id: blob_impl.blob_id(),
74 }
75 }
76
77 pub(crate) fn get_bytes(&self) -> Result<Vec<u8>, ()> {
79 self.global().get_blob_bytes(&self.blob_id)
80 }
81
82 pub(crate) fn type_string(&self) -> String {
84 self.global().get_blob_type_string(&self.blob_id)
85 }
86
87 pub(crate) fn get_blob_url_id(&self) -> Uuid {
90 self.global().get_blob_url_id(&self.blob_id)
91 }
92
93 pub(crate) fn get_stream(&self, cx: &mut JSContext) -> Fallible<DomRoot<ReadableStream>> {
95 self.global().get_blob_stream(cx, &self.blob_id)
96 }
97}
98
99impl Serializable for Blob {
100 type Index = BlobIndex;
101 type Data = BlobImpl;
102
103 fn serialize(&self) -> Result<(BlobId, BlobImpl), ()> {
105 let blob_id = self.blob_id;
106
107 let blob_impl = self.global().serialize_blob(&blob_id);
109
110 let new_blob_id = blob_impl.blob_id();
112
113 Ok((new_blob_id, blob_impl))
114 }
115
116 fn deserialize(
118 cx: &mut JSContext,
119 owner: &GlobalScope,
120 serialized: BlobImpl,
121 ) -> Result<DomRoot<Self>, ()> {
122 Ok(Blob::new(cx, owner, serialized))
123 }
124
125 fn serialized_storage<'a>(
126 reader: StructuredData<'a, '_>,
127 ) -> &'a mut Option<FxHashMap<BlobId, Self::Data>> {
128 match reader {
129 StructuredData::Reader(r) => &mut r.blob_impls,
130 StructuredData::Writer(w) => &mut w.blobs,
131 }
132 }
133}
134
135fn convert_line_endings_to_native(s: &[u8]) -> Vec<u8> {
137 let native_line_ending: &[u8] = if cfg!(target_os = "windows") {
138 b"\r\n"
142 } else {
143 b"\n"
145 };
146
147 let len = s.len();
148 let mut result = Vec::with_capacity(len);
150
151 let mut position = 0;
153
154 let collect_a_sequence_of_code_points = |position: &mut usize| -> &[u8] {
156 let start = *position;
157 while *position < len && s[*position] != b'\r' && s[*position] != b'\n' {
158 *position += 1;
159 }
160 &s[start..*position]
161 };
162
163 result.extend_from_slice(collect_a_sequence_of_code_points(&mut position));
167
168 while position < len {
170 let byte = s[position];
171 if byte == b'\r' {
173 result.extend_from_slice(native_line_ending);
175 position += 1;
177 if position < len && s[position] == b'\n' {
180 position += 1;
181 }
182 }
183 else if byte == b'\n' {
185 position += 1;
187 result.extend_from_slice(native_line_ending);
188 }
189
190 result.extend_from_slice(collect_a_sequence_of_code_points(&mut position));
194 }
195
196 result
198}
199
200#[expect(unsafe_code, deprecated)]
202pub(crate) fn process_blob_parts(
203 mut blobparts: Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>,
204 endings: BlobBinding::EndingType,
205) -> Result<Vec<u8>, ()> {
206 let mut bytes = vec![];
208 for blobpart in &mut blobparts {
210 match blobpart {
211 ArrayBufferOrArrayBufferViewOrBlobOrString::String(s) => {
213 if endings == BlobBinding::EndingType::Native {
217 let converted = convert_line_endings_to_native(&s.as_bytes());
218 bytes.extend(converted);
220 } else {
221 bytes.extend_from_slice(&s.as_bytes());
223 }
224 },
225 ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBuffer(a) => unsafe {
229 let array_bytes = a.as_slice();
230 bytes.extend(array_bytes);
231 },
232 ArrayBufferOrArrayBufferViewOrBlobOrString::ArrayBufferView(a) => unsafe {
233 let view_bytes = a.as_slice();
234 bytes.extend(view_bytes);
235 },
236 ArrayBufferOrArrayBufferViewOrBlobOrString::Blob(b) => {
238 let blob_bytes = b.get_bytes().unwrap_or(vec![]);
239 bytes.extend(blob_bytes);
240 },
241 }
242 }
243
244 Ok(bytes)
246}
247
248impl BlobMethods<crate::DomTypeHolder> for Blob {
249 #[expect(non_snake_case)]
251 fn Constructor(
252 cx: &mut JSContext,
253 global: &GlobalScope,
254 proto: Option<HandleObject>,
255 blobParts: Option<Vec<ArrayBufferOrArrayBufferViewOrBlobOrString>>,
256 blobPropertyBag: &BlobBinding::BlobPropertyBag,
257 ) -> Fallible<DomRoot<Blob>> {
258 let bytes: Vec<u8> = match blobParts {
259 None => Vec::new(),
260 Some(blobparts) => match process_blob_parts(blobparts, blobPropertyBag.endings) {
261 Ok(bytes) => bytes,
262 Err(_) => return Err(Error::InvalidCharacter(None)),
263 },
264 };
265
266 let type_string = normalize_type_string(&blobPropertyBag.type_.str());
267 let blob_impl = BlobImpl::new_from_bytes(bytes, type_string);
268
269 Ok(Blob::new_with_proto(cx, global, proto, blob_impl))
270 }
271
272 fn Size(&self) -> u64 {
274 self.global().get_blob_size(&self.blob_id)
275 }
276
277 fn Type(&self) -> DOMString {
279 DOMString::from(self.type_string())
280 }
281
282 fn Stream(&self, cx: &mut JSContext) -> Fallible<DomRoot<ReadableStream>> {
284 self.get_stream(cx)
285 }
286
287 fn TextStream(&self, cx: &mut JSContext) -> Fallible<DomRoot<ReadableStream>> {
289 let stream = self.get_stream(cx)?;
291 let decoder = TextDecoderStream::new_with_proto(
294 cx,
295 &self.global(),
296 None,
297 UTF_8,
298 false, false, )?;
301 Ok(pipe_through(&stream, cx, &self.global(), &decoder))
303 }
304
305 fn Slice(
307 &self,
308 cx: &mut JSContext,
309 start: Option<i64>,
310 end: Option<i64>,
311 content_type: Option<DOMString>,
312 ) -> DomRoot<Blob> {
313 let global = self.global();
314 let type_string = normalize_type_string(&content_type.unwrap_or_default().str());
315
316 let (parent, range) = match *global.get_blob_data(&self.blob_id) {
319 BlobData::Sliced(grandparent, parent_range) => {
320 let range = RelativePos {
321 start: parent_range.start + start.unwrap_or_default(),
322 end: end.map(|end| end + parent_range.start).or(parent_range.end),
323 };
324 (grandparent, range)
325 },
326 _ => (self.blob_id, RelativePos::from_opts(start, end)),
327 };
328
329 let blob_impl = BlobImpl::new_sliced(range, parent, type_string);
330 Blob::new(cx, &global, blob_impl)
331 }
332
333 fn Text(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
335 let global = self.global();
336 let p = Promise::new_in_realm(cx);
337 let id = self.get_blob_url_id();
338 global.read_file_async(
339 id,
340 p.clone(),
341 Box::new(|cx, promise, bytes| match bytes {
342 Ok(b) => {
343 let (text, _) = UTF_8.decode_with_bom_removal(&b);
344 let text = DOMString::from(text);
345 promise.resolve_native(cx, &text);
346 },
347 Err(e) => {
348 promise.reject_error(cx, e);
349 },
350 }),
351 );
352 p
353 }
354
355 fn ArrayBuffer(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
357 let promise = Promise::new_in_realm(cx);
358
359 let stream = self.get_stream(cx);
361
362 let reader = match stream.and_then(|s| s.acquire_default_reader(cx)) {
365 Ok(reader) => reader,
366 Err(error) => {
367 promise.reject_error(cx, error);
368 return promise;
369 },
370 };
371
372 let success_promise = promise.clone();
374 let failure_promise = promise.clone();
375 reader.read_all_bytes(
376 cx,
377 Rc::new(move |cx, bytes| {
378 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
379 let array_buffer =
382 create_buffer_source::<ArrayBufferU8>(cx, bytes, js_object.handle_mut())
383 .expect("Converting input to ArrayBufferU8 should never fail");
384 success_promise.resolve_native(cx, &array_buffer);
385 }),
386 Rc::new(move |cx, value| {
387 failure_promise.reject(cx, value);
388 }),
389 );
390
391 promise
392 }
393
394 fn Bytes(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
396 let p = Promise::new_in_realm(cx);
397
398 let stream = self.get_stream(cx);
400
401 let reader = match stream.and_then(|s| s.acquire_default_reader(cx)) {
404 Ok(r) => r,
405 Err(e) => {
406 p.reject_error(cx, e);
407 return p;
408 },
409 };
410
411 let p_success = p.clone();
413 let p_failure = p.clone();
414 reader.read_all_bytes(
415 cx,
416 Rc::new(move |cx, bytes| {
417 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
418 let arr = create_buffer_source::<Uint8>(cx, bytes, js_object.handle_mut())
419 .expect("Converting input to uint8 array should never fail");
420 p_success.resolve_native(cx, &arr);
421 }),
422 Rc::new(move |cx, v| {
423 p_failure.reject(cx, v);
424 }),
425 );
426 p
427 }
428}
429
430pub(crate) fn normalize_type_string(s: &str) -> String {
436 if is_ascii_printable(s) {
437 s.to_ascii_lowercase()
438 } else {
442 "".to_string()
443 }
444}
445
446fn is_ascii_printable(string: &str) -> bool {
447 string.chars().all(|c| ('\x20'..='\x7E').contains(&c))
450}