1use std::rc::Rc;
6use std::{ptr, slice, str};
7
8use base::generic_channel::GenericSharedMemory;
9use constellation_traits::BlobImpl;
10use encoding_rs::{Encoding, UTF_8};
11use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
12use ipc_channel::router::ROUTER;
13use js::jsapi::{Heap, JS_ClearPendingException, JSObject, Value as JSValue};
14use js::jsval::{JSVal, UndefinedValue};
15use js::realm::CurrentRealm;
16use js::rust::HandleValue;
17use js::rust::wrappers::{JS_GetPendingException, JS_ParseJSON};
18use js::typedarray::{ArrayBufferU8, Uint8};
19use mime::{self, Mime};
20use net_traits::request::{
21 BodyChunkRequest, BodyChunkResponse, BodySource as NetBodySource, RequestBody,
22};
23use url::form_urlencoded;
24
25use crate::dom::bindings::buffer_source::create_buffer_source;
26use crate::dom::bindings::codegen::Bindings::BlobBinding::Blob_Binding::BlobMethods;
27use crate::dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
28use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
29use crate::dom::bindings::error::{Error, Fallible};
30use crate::dom::bindings::refcounted::Trusted;
31use crate::dom::bindings::reflector::{DomGlobal, DomObject};
32use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
33use crate::dom::bindings::str::{DOMString, USVString};
34use crate::dom::bindings::trace::RootedTraceableBox;
35use crate::dom::blob::{Blob, normalize_type_string};
36use crate::dom::formdata::FormData;
37use crate::dom::globalscope::GlobalScope;
38use crate::dom::html::htmlformelement::{encode_multipart_form_data, generate_boundary};
39use crate::dom::promise::Promise;
40use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
41use crate::dom::readablestream::{ReadableStream, get_read_promise_bytes, get_read_promise_done};
42use crate::dom::urlsearchparams::URLSearchParams;
43use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
44use crate::script_runtime::{CanGc, JSContext};
45use crate::task_source::SendableTaskSource;
46
47pub(crate) fn clone_body_stream_for_dom_body(
49 original_body_stream: &MutNullableDom<ReadableStream>,
50 cloned_body_stream: &MutNullableDom<ReadableStream>,
51 can_gc: CanGc,
52) -> Fallible<()> {
53 let Some(stream) = original_body_stream.get() else {
56 return Ok(());
57 };
58
59 let branches = stream.tee(true, can_gc)?;
61 let out1 = &*branches[0];
62 let out2 = &*branches[1];
63
64 original_body_stream.set(Some(out1));
67 cloned_body_stream.set(Some(out2));
68
69 Ok(())
70}
71
72#[derive(Clone, PartialEq)]
75pub(crate) enum BodySource {
76 Null,
78 Object,
82}
83
84enum StopReading {
86 Error,
88 Done,
90}
91
92#[derive(Clone)]
98struct TransmitBodyConnectHandler {
99 stream: Trusted<ReadableStream>,
100 task_source: SendableTaskSource,
101 bytes_sender: Option<IpcSender<BodyChunkResponse>>,
102 control_sender: Option<IpcSender<BodyChunkRequest>>,
103 in_memory: Option<GenericSharedMemory>,
104 in_memory_done: bool,
105 source: BodySource,
106}
107
108impl TransmitBodyConnectHandler {
109 pub(crate) fn new(
110 stream: Trusted<ReadableStream>,
111 task_source: SendableTaskSource,
112 control_sender: IpcSender<BodyChunkRequest>,
113 in_memory: Option<GenericSharedMemory>,
114 source: BodySource,
115 ) -> TransmitBodyConnectHandler {
116 TransmitBodyConnectHandler {
117 stream,
118 task_source,
119 bytes_sender: None,
120 control_sender: Some(control_sender),
121 in_memory,
122 in_memory_done: false,
123 source,
124 }
125 }
126
127 pub(crate) fn reset_in_memory_done(&mut self) {
130 self.in_memory_done = false;
131 }
132
133 fn re_extract(&mut self, chunk_request_receiver: IpcReceiver<BodyChunkRequest>) {
136 let mut body_handler = self.clone();
137 body_handler.reset_in_memory_done();
138
139 ROUTER.add_typed_route(
140 chunk_request_receiver,
141 Box::new(move |message| {
142 let request = message.unwrap();
143 match request {
144 BodyChunkRequest::Connect(sender) => {
145 body_handler.start_reading(sender);
146 },
147 BodyChunkRequest::Extract(receiver) => {
148 body_handler.re_extract(receiver);
149 },
150 BodyChunkRequest::Chunk => body_handler.transmit_source(),
151 BodyChunkRequest::Done => {
154 body_handler.stop_reading(StopReading::Done);
155 },
156 BodyChunkRequest::Error => {
159 body_handler.stop_reading(StopReading::Error);
160 },
161 }
162 }),
163 );
164 }
165
166 fn transmit_source(&mut self) {
173 if self.in_memory_done {
174 self.stop_reading(StopReading::Done);
176 return;
177 }
178
179 if let BodySource::Null = self.source {
180 panic!("ReadableStream(Null) sources should not re-direct.");
181 }
182
183 if let Some(bytes) = self.in_memory.clone() {
184 self.in_memory_done = true;
186 let _ = self
187 .bytes_sender
188 .as_ref()
189 .expect("No bytes sender to transmit source.")
190 .send(BodyChunkResponse::Chunk(bytes));
191 return;
192 }
193 warn!("Re-directs for file-based Blobs not supported yet.");
194 }
195
196 fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
199 self.bytes_sender = Some(sender);
200
201 if self.source == BodySource::Null {
203 let stream = self.stream.clone();
204 self.task_source
205 .queue(task!(start_reading_request_body_stream: move || {
206 let rooted_stream = stream.root();
208
209 rooted_stream.acquire_default_reader(CanGc::note())
213 .expect("Couldn't acquire a reader for the body stream.");
214
215 }));
217 }
218 }
219
220 fn stop_reading(&mut self, reason: StopReading) {
225 let bytes_sender = self
226 .bytes_sender
227 .take()
228 .expect("Stop reading called multiple times on TransmitBodyConnectHandler.");
229 match reason {
230 StopReading::Error => {
231 let _ = bytes_sender.send(BodyChunkResponse::Error);
232 },
233 StopReading::Done => {
234 let _ = bytes_sender.send(BodyChunkResponse::Done);
235 },
236 }
237 let _ = self.control_sender.take();
238 }
239
240 fn transmit_body_chunk(&mut self) {
242 if self.in_memory_done {
243 self.stop_reading(StopReading::Done);
245 return;
246 }
247
248 let stream = self.stream.clone();
249 let control_sender = self.control_sender.clone();
250 let bytes_sender = self
251 .bytes_sender
252 .clone()
253 .expect("No bytes sender to transmit chunk.");
254
255 if let Some(bytes) = self.in_memory.clone() {
257 let _ = bytes_sender.send(BodyChunkResponse::Chunk(bytes));
258 self.in_memory_done = true;
261 return;
262 }
263
264 self.task_source.queue(
265 task!(setup_native_body_promise_handler: move || {
266 let rooted_stream = stream.root();
267 let global = rooted_stream.global();
268 let cx = GlobalScope::get_cx();
269
270 let promise = rooted_stream.read_a_chunk(CanGc::note());
272
273 rooted!(in(*cx) let mut promise_handler = Some(TransmitBodyPromiseHandler {
277 bytes_sender: bytes_sender.clone(),
278 stream: Dom::from_ref(&rooted_stream.clone()),
279 control_sender: control_sender.clone().unwrap(),
280 }));
281
282 rooted!(in(*cx) let mut rejection_handler = Some(TransmitBodyPromiseRejectionHandler {
283 bytes_sender,
284 stream: Dom::from_ref(&rooted_stream.clone()),
285 control_sender: control_sender.unwrap(),
286 }));
287
288 let handler =
289 PromiseNativeHandler::new(&global, promise_handler.take().map(|h| Box::new(h) as Box<_>), rejection_handler.take().map(|h| Box::new(h) as Box<_>), CanGc::note());
290
291 let realm = enter_realm(&*global);
292 let comp = InRealm::Entered(&realm);
293 promise.append_native_handler(&handler, comp, CanGc::note());
294 })
295 );
296 }
297}
298
299#[derive(Clone, JSTraceable, MallocSizeOf)]
302#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
303struct TransmitBodyPromiseHandler {
304 #[ignore_malloc_size_of = "Channels are hard"]
305 #[no_trace]
306 bytes_sender: IpcSender<BodyChunkResponse>,
307 stream: Dom<ReadableStream>,
308 #[ignore_malloc_size_of = "Channels are hard"]
309 #[no_trace]
310 control_sender: IpcSender<BodyChunkRequest>,
311}
312
313impl js::gc::Rootable for TransmitBodyPromiseHandler {}
314
315impl Callback for TransmitBodyPromiseHandler {
316 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
318 let can_gc = CanGc::from_cx(cx);
319 let _realm = InRealm::Already(&cx.into());
320 let cx = cx.into();
321 let is_done = match get_read_promise_done(cx, &v, can_gc) {
322 Ok(is_done) => is_done,
323 Err(_) => {
324 let _ = self.control_sender.send(BodyChunkRequest::Done);
327 return self.stream.stop_reading(can_gc);
328 },
329 };
330
331 if is_done {
332 let _ = self.control_sender.send(BodyChunkRequest::Done);
335 return self.stream.stop_reading(can_gc);
336 }
337
338 let chunk = match get_read_promise_bytes(cx, &v, can_gc) {
339 Ok(chunk) => chunk,
340 Err(_) => {
341 let _ = self.control_sender.send(BodyChunkRequest::Error);
343 return self.stream.stop_reading(can_gc);
344 },
345 };
346
347 let _ = self
351 .bytes_sender
352 .send(BodyChunkResponse::Chunk(GenericSharedMemory::from_bytes(
353 &chunk,
354 )));
355 }
356}
357
358#[derive(Clone, JSTraceable, MallocSizeOf)]
361#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
362struct TransmitBodyPromiseRejectionHandler {
363 #[ignore_malloc_size_of = "Channels are hard"]
364 #[no_trace]
365 bytes_sender: IpcSender<BodyChunkResponse>,
366 stream: Dom<ReadableStream>,
367 #[ignore_malloc_size_of = "Channels are hard"]
368 #[no_trace]
369 control_sender: IpcSender<BodyChunkRequest>,
370}
371
372impl js::gc::Rootable for TransmitBodyPromiseRejectionHandler {}
373
374impl Callback for TransmitBodyPromiseRejectionHandler {
375 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
377 let _ = self.control_sender.send(BodyChunkRequest::Error);
379 self.stream.stop_reading(CanGc::from_cx(cx));
380 }
381}
382
383pub(crate) struct ExtractedBody {
385 pub(crate) stream: DomRoot<ReadableStream>,
387 pub(crate) source: BodySource,
389 pub(crate) total_bytes: Option<usize>,
391 pub(crate) content_type: Option<DOMString>,
393}
394
395impl ExtractedBody {
396 pub(crate) fn into_net_request_body(self) -> (RequestBody, DomRoot<ReadableStream>) {
408 let ExtractedBody {
409 stream,
410 total_bytes,
411 content_type: _,
412 source,
413 } = self;
414
415 let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
418
419 let trusted_stream = Trusted::new(&*stream);
420
421 let global = stream.global();
422 let task_source = global.task_manager().networking_task_source();
423
424 let in_memory = stream.get_in_memory_bytes();
426
427 let net_source = match source {
428 BodySource::Null => NetBodySource::Null,
429 _ => NetBodySource::Object,
430 };
431
432 let mut body_handler = TransmitBodyConnectHandler::new(
433 trusted_stream,
434 task_source.into(),
435 chunk_request_sender.clone(),
436 in_memory,
437 source,
438 );
439
440 ROUTER.add_typed_route(
441 chunk_request_receiver,
442 Box::new(move |message| {
443 match message.unwrap() {
444 BodyChunkRequest::Connect(sender) => {
445 body_handler.start_reading(sender);
446 },
447 BodyChunkRequest::Extract(receiver) => {
448 body_handler.re_extract(receiver);
449 },
450 BodyChunkRequest::Chunk => body_handler.transmit_body_chunk(),
451 BodyChunkRequest::Done => {
454 body_handler.stop_reading(StopReading::Done);
455 },
456 BodyChunkRequest::Error => {
459 body_handler.stop_reading(StopReading::Error);
460 },
461 }
462 }),
463 );
464
465 let request_body = RequestBody::new(chunk_request_sender, net_source, total_bytes);
468
469 (request_body, stream)
471 }
472
473 pub(crate) fn in_memory(&self) -> bool {
475 self.stream.in_memory()
476 }
477}
478
479pub(crate) trait Extractable {
481 fn extract(
482 &self,
483 global: &GlobalScope,
484 keep_alive: bool,
485 can_gc: CanGc,
486 ) -> Fallible<ExtractedBody>;
487}
488
489impl Extractable for BodyInit {
490 fn extract(
492 &self,
493 global: &GlobalScope,
494 keep_alive: bool,
495 can_gc: CanGc,
496 ) -> Fallible<ExtractedBody> {
497 match self {
498 BodyInit::String(s) => s.extract(global, keep_alive, can_gc),
499 BodyInit::URLSearchParams(usp) => usp.extract(global, keep_alive, can_gc),
500 BodyInit::Blob(b) => b.extract(global, keep_alive, can_gc),
501 BodyInit::FormData(formdata) => formdata.extract(global, keep_alive, can_gc),
502 BodyInit::ArrayBuffer(typedarray) => {
503 let bytes = typedarray.to_vec();
504 let total_bytes = bytes.len();
505 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
506 Ok(ExtractedBody {
507 stream,
508 total_bytes: Some(total_bytes),
509 content_type: None,
510 source: BodySource::Object,
511 })
512 },
513 BodyInit::ArrayBufferView(typedarray) => {
514 let bytes = typedarray.to_vec();
515 let total_bytes = bytes.len();
516 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
517 Ok(ExtractedBody {
518 stream,
519 total_bytes: Some(total_bytes),
520 content_type: None,
521 source: BodySource::Object,
522 })
523 },
524 BodyInit::ReadableStream(stream) => {
525 if keep_alive {
527 return Err(Error::Type(
528 "The body's stream is for a keepalive request".to_string(),
529 ));
530 }
531 if stream.is_locked() || stream.is_disturbed() {
533 return Err(Error::Type(
534 "The body's stream is disturbed or locked".to_string(),
535 ));
536 }
537
538 Ok(ExtractedBody {
539 stream: stream.clone(),
540 total_bytes: None,
541 content_type: None,
542 source: BodySource::Null,
543 })
544 },
545 }
546 }
547}
548
549impl Extractable for Vec<u8> {
550 fn extract(
551 &self,
552 global: &GlobalScope,
553 _keep_alive: bool,
554 can_gc: CanGc,
555 ) -> Fallible<ExtractedBody> {
556 let bytes = self.clone();
557 let total_bytes = self.len();
558 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
559 Ok(ExtractedBody {
560 stream,
561 total_bytes: Some(total_bytes),
562 content_type: None,
563 source: BodySource::Object,
565 })
566 }
567}
568
569impl Extractable for Blob {
570 fn extract(
571 &self,
572 _global: &GlobalScope,
573 _keep_alive: bool,
574 can_gc: CanGc,
575 ) -> Fallible<ExtractedBody> {
576 let blob_type = self.Type();
577 let content_type = if blob_type.is_empty() {
578 None
579 } else {
580 Some(blob_type)
581 };
582 let total_bytes = self.Size() as usize;
583 let stream = self.get_stream(can_gc)?;
584 Ok(ExtractedBody {
585 stream,
586 total_bytes: Some(total_bytes),
587 content_type,
588 source: BodySource::Object,
589 })
590 }
591}
592
593impl Extractable for DOMString {
594 fn extract(
595 &self,
596 global: &GlobalScope,
597 _keep_alive: bool,
598 can_gc: CanGc,
599 ) -> Fallible<ExtractedBody> {
600 let bytes = self.as_bytes().to_owned();
601 let total_bytes = bytes.len();
602 let content_type = Some(DOMString::from("text/plain;charset=UTF-8"));
603 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
604 Ok(ExtractedBody {
605 stream,
606 total_bytes: Some(total_bytes),
607 content_type,
608 source: BodySource::Object,
609 })
610 }
611}
612
613impl Extractable for FormData {
614 fn extract(
615 &self,
616 global: &GlobalScope,
617 _keep_alive: bool,
618 can_gc: CanGc,
619 ) -> Fallible<ExtractedBody> {
620 let boundary = generate_boundary();
621 let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
622 let total_bytes = bytes.len();
623 let content_type = Some(DOMString::from(format!(
624 "multipart/form-data; boundary={}",
625 boundary
626 )));
627 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
628 Ok(ExtractedBody {
629 stream,
630 total_bytes: Some(total_bytes),
631 content_type,
632 source: BodySource::Object,
633 })
634 }
635}
636
637impl Extractable for URLSearchParams {
638 fn extract(
639 &self,
640 global: &GlobalScope,
641 _keep_alive: bool,
642 can_gc: CanGc,
643 ) -> Fallible<ExtractedBody> {
644 let bytes = self.serialize_utf8().into_bytes();
645 let total_bytes = bytes.len();
646 let content_type = Some(DOMString::from(
647 "application/x-www-form-urlencoded;charset=UTF-8",
648 ));
649 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
650 Ok(ExtractedBody {
651 stream,
652 total_bytes: Some(total_bytes),
653 content_type,
654 source: BodySource::Object,
655 })
656 }
657}
658
659#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
660pub(crate) enum BodyType {
661 Blob,
662 Bytes,
663 FormData,
664 Json,
665 Text,
666 ArrayBuffer,
667}
668
669pub(crate) enum FetchedData {
670 Text(String),
671 Json(RootedTraceableBox<Heap<JSValue>>),
672 BlobData(DomRoot<Blob>),
673 Bytes(RootedTraceableBox<Heap<*mut JSObject>>),
674 FormData(DomRoot<FormData>),
675 ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>),
676 JSException(RootedTraceableBox<Heap<JSVal>>),
677}
678
679pub(crate) fn consume_body<T: BodyMixin + DomObject>(
685 object: &T,
686 body_type: BodyType,
687 can_gc: CanGc,
688) -> Rc<Promise> {
689 let global = object.global();
690 let cx = GlobalScope::get_cx();
691
692 let realm = enter_realm(&*global);
694 let comp = InRealm::Entered(&realm);
695
696 let promise = Promise::new_in_current_realm(comp, can_gc);
699
700 if object.is_unusable() {
702 promise.reject_error(
703 Error::Type("The body's stream is disturbed or locked".to_string()),
704 can_gc,
705 );
706 return promise;
707 }
708
709 let stream = match object.body() {
710 Some(stream) => stream,
711 None => {
712 resolve_result_promise(
714 body_type,
715 &promise,
716 object.get_mime_type(can_gc),
717 Vec::with_capacity(0),
718 cx,
719 can_gc,
720 );
721 return promise;
722 },
723 };
724
725 if stream.is_errored() {
742 rooted!(in(*cx) let mut stored_error = UndefinedValue());
743 stream.get_stored_error(stored_error.handle_mut());
744 promise.reject(cx, stored_error.handle(), can_gc);
745 return promise;
746 }
747
748 let reader = match stream.acquire_default_reader(can_gc) {
753 Ok(r) => r,
754 Err(e) => {
755 promise.reject_error(e, can_gc);
756 return promise;
757 },
758 };
759
760 let error_promise = promise.clone();
762
763 let mime_type = object.get_mime_type(can_gc);
767 let success_promise = promise.clone();
768
769 reader.read_all_bytes(
774 cx,
775 Rc::new(move |bytes: &[u8]| {
776 resolve_result_promise(
777 body_type,
778 &success_promise,
779 mime_type.clone(),
780 bytes.to_vec(),
781 cx,
782 can_gc,
783 );
784 }),
785 Rc::new(move |cx, v| {
786 error_promise.reject(cx, v, can_gc);
787 }),
788 can_gc,
789 );
790
791 promise
792}
793
794fn resolve_result_promise(
797 body_type: BodyType,
798 promise: &Promise,
799 mime_type: Vec<u8>,
800 body: Vec<u8>,
801 cx: JSContext,
802 can_gc: CanGc,
803) {
804 let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type, can_gc);
805
806 match pkg_data_results {
807 Ok(results) => {
808 match results {
809 FetchedData::Text(s) => promise.resolve_native(&USVString(s), can_gc),
810 FetchedData::Json(j) => promise.resolve_native(&j, can_gc),
811 FetchedData::BlobData(b) => promise.resolve_native(&b, can_gc),
812 FetchedData::FormData(f) => promise.resolve_native(&f, can_gc),
813 FetchedData::Bytes(b) => promise.resolve_native(&b, can_gc),
814 FetchedData::ArrayBuffer(a) => promise.resolve_native(&a, can_gc),
815 FetchedData::JSException(e) => promise.reject_native(&e.handle(), can_gc),
816 };
817 },
818 Err(err) => promise.reject_error(err, can_gc),
819 }
820}
821
822fn run_package_data_algorithm(
826 cx: JSContext,
827 bytes: Vec<u8>,
828 body_type: BodyType,
829 mime_type: Vec<u8>,
830 can_gc: CanGc,
831) -> Fallible<FetchedData> {
832 let mime = &*mime_type;
833 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
834 let global = GlobalScope::from_safe_context(cx, InRealm::Already(&in_realm_proof));
835 match body_type {
836 BodyType::Text => run_text_data_algorithm(bytes),
837 BodyType::Json => run_json_data_algorithm(cx, bytes),
838 BodyType::Blob => run_blob_data_algorithm(&global, bytes, mime, can_gc),
839 BodyType::FormData => run_form_data_algorithm(&global, bytes, mime, can_gc),
840 BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes, can_gc),
841 BodyType::Bytes => run_bytes_data_algorithm(cx, bytes, can_gc),
842 }
843}
844
845fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
847 let no_bom_bytes = if bytes.starts_with(b"\xEF\xBB\xBF") {
850 &bytes[3..]
851 } else {
852 &bytes
853 };
854 Ok(FetchedData::Text(
855 String::from_utf8_lossy(no_bom_bytes).into_owned(),
856 ))
857}
858
859#[expect(unsafe_code)]
860fn run_json_data_algorithm(cx: JSContext, bytes: Vec<u8>) -> Fallible<FetchedData> {
862 let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8);
867 rooted!(in(*cx) let mut rval = UndefinedValue());
868 unsafe {
869 if !JS_ParseJSON(
870 *cx,
871 json_text.as_ptr(),
872 json_text.len() as u32,
873 rval.handle_mut(),
874 ) {
875 rooted!(in(*cx) let mut exception = UndefinedValue());
876 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
877 JS_ClearPendingException(*cx);
878 return Ok(FetchedData::JSException(RootedTraceableBox::from_box(
879 Heap::boxed(exception.get()),
880 )));
881 }
882 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(rval.get()));
883 Ok(FetchedData::Json(rooted_heap))
884 }
885}
886
887fn run_blob_data_algorithm(
889 root: &GlobalScope,
890 bytes: Vec<u8>,
891 mime: &[u8],
892 can_gc: CanGc,
893) -> Fallible<FetchedData> {
894 let mime_string = if let Ok(s) = String::from_utf8(mime.to_vec()) {
895 s
896 } else {
897 "".to_string()
898 };
899 let blob = Blob::new(
900 root,
901 BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
902 can_gc,
903 );
904 Ok(FetchedData::BlobData(blob))
905}
906
907fn run_form_data_algorithm(
909 root: &GlobalScope,
910 bytes: Vec<u8>,
911 mime: &[u8],
912 can_gc: CanGc,
913) -> Fallible<FetchedData> {
914 let mime_str = str::from_utf8(mime).unwrap_or_default();
915 let mime: Mime = mime_str
916 .parse()
917 .map_err(|_| Error::Type("Inappropriate MIME-type for Body".to_string()))?;
918
919 if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED {
923 let entries = form_urlencoded::parse(&bytes);
924 let formdata = FormData::new(None, root, can_gc);
925 for (k, e) in entries {
926 formdata.Append(USVString(k.into_owned()), USVString(e.into_owned()));
927 }
928 return Ok(FetchedData::FormData(formdata));
929 }
930
931 Err(Error::Type("Inappropriate MIME-type for Body".to_string()))
932}
933
934fn run_bytes_data_algorithm(cx: JSContext, bytes: Vec<u8>, can_gc: CanGc) -> Fallible<FetchedData> {
936 rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
937
938 create_buffer_source::<Uint8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
939 .map_err(|_| Error::JSFailed)?;
940
941 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
942 Ok(FetchedData::Bytes(rooted_heap))
943}
944
945pub(crate) fn run_array_buffer_data_algorithm(
947 cx: JSContext,
948 bytes: Vec<u8>,
949 can_gc: CanGc,
950) -> Fallible<FetchedData> {
951 rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
952
953 create_buffer_source::<ArrayBufferU8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
954 .map_err(|_| Error::JSFailed)?;
955
956 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
957 Ok(FetchedData::ArrayBuffer(rooted_heap))
958}
959
960#[expect(unsafe_code)]
961pub(crate) fn decode_to_utf16_with_bom_removal(
962 bytes: &[u8],
963 encoding: &'static Encoding,
964) -> Vec<u16> {
965 let mut decoder = encoding.new_decoder_with_bom_removal();
966 let capacity = decoder
967 .max_utf16_buffer_length(bytes.len())
968 .expect("Overflow");
969 let mut utf16 = Vec::with_capacity(capacity);
970 let extra = unsafe { slice::from_raw_parts_mut(utf16.as_mut_ptr(), capacity) };
971 let (_, read, written, _) = decoder.decode_to_utf16(bytes, extra, true);
972 assert_eq!(read, bytes.len());
973 unsafe { utf16.set_len(written) }
974 utf16
975}
976
977pub(crate) trait BodyMixin {
979 fn is_body_used(&self) -> bool;
981 fn is_unusable(&self) -> bool;
983 fn body(&self) -> Option<DomRoot<ReadableStream>>;
985 fn get_mime_type(&self, can_gc: CanGc) -> Vec<u8>;
987}