1use std::io::Cursor;
6use std::rc::Rc;
7use std::{fs, ptr, slice, str};
8
9use base::generic_channel::GenericSharedMemory;
10use constellation_traits::BlobImpl;
11use encoding_rs::{Encoding, UTF_8};
12use http::HeaderMap;
13use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE};
14use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
15use ipc_channel::router::ROUTER;
16use js::jsapi::{Heap, JS_ClearPendingException, JSObject, Value as JSValue};
17use js::jsval::{JSVal, UndefinedValue};
18use js::realm::CurrentRealm;
19use js::rust::HandleValue;
20use js::rust::wrappers::{JS_GetPendingException, JS_ParseJSON};
21use js::typedarray::{ArrayBufferU8, Uint8};
22use mime::{self, Mime};
23use mime_multipart_hyper1::{Node, read_multipart_body};
24use net_traits::request::{
25 BodyChunkRequest, BodyChunkResponse, BodySource as NetBodySource, RequestBody,
26};
27use url::form_urlencoded;
28
29use crate::dom::bindings::buffer_source::create_buffer_source;
30use crate::dom::bindings::codegen::Bindings::BlobBinding::Blob_Binding::BlobMethods;
31use crate::dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
32use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
33use crate::dom::bindings::error::{Error, Fallible};
34use crate::dom::bindings::inheritance::Castable;
35use crate::dom::bindings::refcounted::Trusted;
36use crate::dom::bindings::reflector::{DomGlobal, DomObject};
37use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
38use crate::dom::bindings::str::{DOMString, USVString};
39use crate::dom::bindings::trace::RootedTraceableBox;
40use crate::dom::blob::{Blob, normalize_type_string};
41use crate::dom::file::File;
42use crate::dom::formdata::FormData;
43use crate::dom::globalscope::GlobalScope;
44use crate::dom::html::htmlformelement::{encode_multipart_form_data, generate_boundary};
45use crate::dom::promise::Promise;
46use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
47use crate::dom::readablestream::{ReadableStream, get_read_promise_bytes, get_read_promise_done};
48use crate::dom::urlsearchparams::URLSearchParams;
49use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
50use crate::script_runtime::{CanGc, JSContext};
51use crate::task_source::SendableTaskSource;
52
53pub(crate) fn clone_body_stream_for_dom_body(
55 original_body_stream: &MutNullableDom<ReadableStream>,
56 cloned_body_stream: &MutNullableDom<ReadableStream>,
57 can_gc: CanGc,
58) -> Fallible<()> {
59 let Some(stream) = original_body_stream.get() else {
62 return Ok(());
63 };
64
65 let branches = stream.tee(true, can_gc)?;
67 let out1 = &*branches[0];
68 let out2 = &*branches[1];
69
70 original_body_stream.set(Some(out1));
73 cloned_body_stream.set(Some(out2));
74
75 Ok(())
76}
77
78#[derive(Clone, PartialEq)]
81pub(crate) enum BodySource {
82 Null,
84 Object,
88}
89
90enum StopReading {
92 Error,
94 Done,
96}
97
98#[derive(Clone)]
104struct TransmitBodyConnectHandler {
105 stream: Trusted<ReadableStream>,
106 task_source: SendableTaskSource,
107 bytes_sender: Option<IpcSender<BodyChunkResponse>>,
108 control_sender: Option<IpcSender<BodyChunkRequest>>,
109 in_memory: Option<GenericSharedMemory>,
110 in_memory_done: bool,
111 source: BodySource,
112}
113
114impl TransmitBodyConnectHandler {
115 pub(crate) fn new(
116 stream: Trusted<ReadableStream>,
117 task_source: SendableTaskSource,
118 control_sender: IpcSender<BodyChunkRequest>,
119 in_memory: Option<GenericSharedMemory>,
120 source: BodySource,
121 ) -> TransmitBodyConnectHandler {
122 TransmitBodyConnectHandler {
123 stream,
124 task_source,
125 bytes_sender: None,
126 control_sender: Some(control_sender),
127 in_memory,
128 in_memory_done: false,
129 source,
130 }
131 }
132
133 pub(crate) fn reset_in_memory_done(&mut self) {
136 self.in_memory_done = false;
137 }
138
139 fn re_extract(&mut self, chunk_request_receiver: IpcReceiver<BodyChunkRequest>) {
142 let mut body_handler = self.clone();
143 body_handler.reset_in_memory_done();
144
145 ROUTER.add_typed_route(
146 chunk_request_receiver,
147 Box::new(move |message| {
148 let request = message.unwrap();
149 match request {
150 BodyChunkRequest::Connect(sender) => {
151 body_handler.start_reading(sender);
152 },
153 BodyChunkRequest::Extract(receiver) => {
154 body_handler.re_extract(receiver);
155 },
156 BodyChunkRequest::Chunk => body_handler.transmit_source(),
157 BodyChunkRequest::Done => {
160 body_handler.stop_reading(StopReading::Done);
161 },
162 BodyChunkRequest::Error => {
165 body_handler.stop_reading(StopReading::Error);
166 },
167 }
168 }),
169 );
170 }
171
172 fn transmit_source(&mut self) {
179 if self.in_memory_done {
180 self.stop_reading(StopReading::Done);
182 return;
183 }
184
185 if let BodySource::Null = self.source {
186 panic!("ReadableStream(Null) sources should not re-direct.");
187 }
188
189 if let Some(bytes) = self.in_memory.clone() {
190 self.in_memory_done = true;
192 let _ = self
193 .bytes_sender
194 .as_ref()
195 .expect("No bytes sender to transmit source.")
196 .send(BodyChunkResponse::Chunk(bytes));
197 return;
198 }
199 warn!("Re-directs for file-based Blobs not supported yet.");
200 }
201
202 fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
205 self.bytes_sender = Some(sender);
206
207 if self.source == BodySource::Null {
209 let stream = self.stream.clone();
210 self.task_source
211 .queue(task!(start_reading_request_body_stream: move || {
212 let rooted_stream = stream.root();
214
215 rooted_stream.acquire_default_reader(CanGc::note())
219 .expect("Couldn't acquire a reader for the body stream.");
220
221 }));
223 }
224 }
225
226 fn stop_reading(&mut self, reason: StopReading) {
231 let bytes_sender = self
232 .bytes_sender
233 .take()
234 .expect("Stop reading called multiple times on TransmitBodyConnectHandler.");
235 match reason {
236 StopReading::Error => {
237 let _ = bytes_sender.send(BodyChunkResponse::Error);
238 },
239 StopReading::Done => {
240 let _ = bytes_sender.send(BodyChunkResponse::Done);
241 },
242 }
243 let _ = self.control_sender.take();
244 }
245
246 fn transmit_body_chunk(&mut self) {
248 if self.in_memory_done {
249 self.stop_reading(StopReading::Done);
251 return;
252 }
253
254 let stream = self.stream.clone();
255 let control_sender = self.control_sender.clone();
256 let bytes_sender = self
257 .bytes_sender
258 .clone()
259 .expect("No bytes sender to transmit chunk.");
260
261 if let Some(bytes) = self.in_memory.clone() {
263 let _ = bytes_sender.send(BodyChunkResponse::Chunk(bytes));
264 self.in_memory_done = true;
267 return;
268 }
269
270 self.task_source.queue(
271 task!(setup_native_body_promise_handler: move || {
272 let rooted_stream = stream.root();
273 let global = rooted_stream.global();
274 let cx = GlobalScope::get_cx();
275
276 let promise = rooted_stream.read_a_chunk(CanGc::note());
278
279 rooted!(in(*cx) let mut promise_handler = Some(TransmitBodyPromiseHandler {
283 bytes_sender: bytes_sender.clone(),
284 stream: Dom::from_ref(&rooted_stream.clone()),
285 control_sender: control_sender.clone().unwrap(),
286 }));
287
288 rooted!(in(*cx) let mut rejection_handler = Some(TransmitBodyPromiseRejectionHandler {
289 bytes_sender,
290 stream: Dom::from_ref(&rooted_stream.clone()),
291 control_sender: control_sender.unwrap(),
292 }));
293
294 let handler =
295 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());
296
297 let realm = enter_realm(&*global);
298 let comp = InRealm::Entered(&realm);
299 promise.append_native_handler(&handler, comp, CanGc::note());
300 })
301 );
302 }
303}
304
305#[derive(Clone, JSTraceable, MallocSizeOf)]
308#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
309struct TransmitBodyPromiseHandler {
310 #[ignore_malloc_size_of = "Channels are hard"]
311 #[no_trace]
312 bytes_sender: IpcSender<BodyChunkResponse>,
313 stream: Dom<ReadableStream>,
314 #[ignore_malloc_size_of = "Channels are hard"]
315 #[no_trace]
316 control_sender: IpcSender<BodyChunkRequest>,
317}
318
319impl js::gc::Rootable for TransmitBodyPromiseHandler {}
320
321impl Callback for TransmitBodyPromiseHandler {
322 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
324 let can_gc = CanGc::from_cx(cx);
325 let _realm = InRealm::Already(&cx.into());
326 let cx = cx.into();
327 let is_done = match get_read_promise_done(cx, &v, can_gc) {
328 Ok(is_done) => is_done,
329 Err(_) => {
330 let _ = self.control_sender.send(BodyChunkRequest::Done);
333 return self.stream.stop_reading(can_gc);
334 },
335 };
336
337 if is_done {
338 let _ = self.control_sender.send(BodyChunkRequest::Done);
341 return self.stream.stop_reading(can_gc);
342 }
343
344 let chunk = match get_read_promise_bytes(cx, &v, can_gc) {
345 Ok(chunk) => chunk,
346 Err(_) => {
347 let _ = self.control_sender.send(BodyChunkRequest::Error);
349 return self.stream.stop_reading(can_gc);
350 },
351 };
352
353 let _ = self
357 .bytes_sender
358 .send(BodyChunkResponse::Chunk(GenericSharedMemory::from_bytes(
359 &chunk,
360 )));
361 }
362}
363
364#[derive(Clone, JSTraceable, MallocSizeOf)]
367#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
368struct TransmitBodyPromiseRejectionHandler {
369 #[ignore_malloc_size_of = "Channels are hard"]
370 #[no_trace]
371 bytes_sender: IpcSender<BodyChunkResponse>,
372 stream: Dom<ReadableStream>,
373 #[ignore_malloc_size_of = "Channels are hard"]
374 #[no_trace]
375 control_sender: IpcSender<BodyChunkRequest>,
376}
377
378impl js::gc::Rootable for TransmitBodyPromiseRejectionHandler {}
379
380impl Callback for TransmitBodyPromiseRejectionHandler {
381 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
383 let _ = self.control_sender.send(BodyChunkRequest::Error);
385 self.stream.stop_reading(CanGc::from_cx(cx));
386 }
387}
388
389pub(crate) struct ExtractedBody {
391 pub(crate) stream: DomRoot<ReadableStream>,
393 pub(crate) source: BodySource,
395 pub(crate) total_bytes: Option<usize>,
397 pub(crate) content_type: Option<DOMString>,
399}
400
401impl ExtractedBody {
402 pub(crate) fn into_net_request_body(self) -> (RequestBody, DomRoot<ReadableStream>) {
414 let ExtractedBody {
415 stream,
416 total_bytes,
417 content_type: _,
418 source,
419 } = self;
420
421 let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
424
425 let trusted_stream = Trusted::new(&*stream);
426
427 let global = stream.global();
428 let task_source = global.task_manager().networking_task_source();
429
430 let in_memory = stream.get_in_memory_bytes();
432
433 let net_source = match source {
434 BodySource::Null => NetBodySource::Null,
435 _ => NetBodySource::Object,
436 };
437
438 let mut body_handler = TransmitBodyConnectHandler::new(
439 trusted_stream,
440 task_source.into(),
441 chunk_request_sender.clone(),
442 in_memory,
443 source,
444 );
445
446 ROUTER.add_typed_route(
447 chunk_request_receiver,
448 Box::new(move |message| {
449 match message.unwrap() {
450 BodyChunkRequest::Connect(sender) => {
451 body_handler.start_reading(sender);
452 },
453 BodyChunkRequest::Extract(receiver) => {
454 body_handler.re_extract(receiver);
455 },
456 BodyChunkRequest::Chunk => body_handler.transmit_body_chunk(),
457 BodyChunkRequest::Done => {
460 body_handler.stop_reading(StopReading::Done);
461 },
462 BodyChunkRequest::Error => {
465 body_handler.stop_reading(StopReading::Error);
466 },
467 }
468 }),
469 );
470
471 let request_body = RequestBody::new(chunk_request_sender, net_source, total_bytes);
474
475 (request_body, stream)
477 }
478
479 pub(crate) fn in_memory(&self) -> bool {
481 self.stream.in_memory()
482 }
483}
484
485pub(crate) trait Extractable {
487 fn extract(
488 &self,
489 global: &GlobalScope,
490 keep_alive: bool,
491 can_gc: CanGc,
492 ) -> Fallible<ExtractedBody>;
493}
494
495impl Extractable for BodyInit {
496 fn extract(
498 &self,
499 global: &GlobalScope,
500 keep_alive: bool,
501 can_gc: CanGc,
502 ) -> Fallible<ExtractedBody> {
503 match self {
504 BodyInit::String(s) => s.extract(global, keep_alive, can_gc),
505 BodyInit::URLSearchParams(usp) => usp.extract(global, keep_alive, can_gc),
506 BodyInit::Blob(b) => b.extract(global, keep_alive, can_gc),
507 BodyInit::FormData(formdata) => formdata.extract(global, keep_alive, can_gc),
508 BodyInit::ArrayBuffer(typedarray) => {
509 let bytes = typedarray.to_vec();
510 let total_bytes = bytes.len();
511 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
512 Ok(ExtractedBody {
513 stream,
514 total_bytes: Some(total_bytes),
515 content_type: None,
516 source: BodySource::Object,
517 })
518 },
519 BodyInit::ArrayBufferView(typedarray) => {
520 let bytes = typedarray.to_vec();
521 let total_bytes = bytes.len();
522 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
523 Ok(ExtractedBody {
524 stream,
525 total_bytes: Some(total_bytes),
526 content_type: None,
527 source: BodySource::Object,
528 })
529 },
530 BodyInit::ReadableStream(stream) => {
531 if keep_alive {
533 return Err(Error::Type(
534 "The body's stream is for a keepalive request".to_string(),
535 ));
536 }
537 if stream.is_locked() || stream.is_disturbed() {
539 return Err(Error::Type(
540 "The body's stream is disturbed or locked".to_string(),
541 ));
542 }
543
544 Ok(ExtractedBody {
545 stream: stream.clone(),
546 total_bytes: None,
547 content_type: None,
548 source: BodySource::Null,
549 })
550 },
551 }
552 }
553}
554
555impl Extractable for Vec<u8> {
556 fn extract(
557 &self,
558 global: &GlobalScope,
559 _keep_alive: bool,
560 can_gc: CanGc,
561 ) -> Fallible<ExtractedBody> {
562 let bytes = self.clone();
563 let total_bytes = self.len();
564 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
565 Ok(ExtractedBody {
566 stream,
567 total_bytes: Some(total_bytes),
568 content_type: None,
569 source: BodySource::Object,
571 })
572 }
573}
574
575impl Extractable for Blob {
576 fn extract(
577 &self,
578 _global: &GlobalScope,
579 _keep_alive: bool,
580 can_gc: CanGc,
581 ) -> Fallible<ExtractedBody> {
582 let blob_type = self.Type();
583 let content_type = if blob_type.is_empty() {
584 None
585 } else {
586 Some(blob_type)
587 };
588 let total_bytes = self.Size() as usize;
589 let stream = self.get_stream(can_gc)?;
590 Ok(ExtractedBody {
591 stream,
592 total_bytes: Some(total_bytes),
593 content_type,
594 source: BodySource::Object,
595 })
596 }
597}
598
599impl Extractable for DOMString {
600 fn extract(
601 &self,
602 global: &GlobalScope,
603 _keep_alive: bool,
604 can_gc: CanGc,
605 ) -> Fallible<ExtractedBody> {
606 let bytes = self.as_bytes().to_owned();
607 let total_bytes = bytes.len();
608 let content_type = Some(DOMString::from("text/plain;charset=UTF-8"));
609 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
610 Ok(ExtractedBody {
611 stream,
612 total_bytes: Some(total_bytes),
613 content_type,
614 source: BodySource::Object,
615 })
616 }
617}
618
619impl Extractable for FormData {
620 fn extract(
621 &self,
622 global: &GlobalScope,
623 _keep_alive: bool,
624 can_gc: CanGc,
625 ) -> Fallible<ExtractedBody> {
626 let boundary = generate_boundary();
627 let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
628 let total_bytes = bytes.len();
629 let content_type = Some(DOMString::from(format!(
630 "multipart/form-data; boundary={}",
631 boundary
632 )));
633 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
634 Ok(ExtractedBody {
635 stream,
636 total_bytes: Some(total_bytes),
637 content_type,
638 source: BodySource::Object,
639 })
640 }
641}
642
643impl Extractable for URLSearchParams {
644 fn extract(
645 &self,
646 global: &GlobalScope,
647 _keep_alive: bool,
648 can_gc: CanGc,
649 ) -> Fallible<ExtractedBody> {
650 let bytes = self.serialize_utf8().into_bytes();
651 let total_bytes = bytes.len();
652 let content_type = Some(DOMString::from(
653 "application/x-www-form-urlencoded;charset=UTF-8",
654 ));
655 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
656 Ok(ExtractedBody {
657 stream,
658 total_bytes: Some(total_bytes),
659 content_type,
660 source: BodySource::Object,
661 })
662 }
663}
664
665#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
666pub(crate) enum BodyType {
667 Blob,
668 Bytes,
669 FormData,
670 Json,
671 Text,
672 ArrayBuffer,
673}
674
675pub(crate) enum FetchedData {
676 Text(String),
677 Json(RootedTraceableBox<Heap<JSValue>>),
678 BlobData(DomRoot<Blob>),
679 Bytes(RootedTraceableBox<Heap<*mut JSObject>>),
680 FormData(DomRoot<FormData>),
681 ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>),
682 JSException(RootedTraceableBox<Heap<JSVal>>),
683}
684
685pub(crate) fn consume_body<T: BodyMixin + DomObject>(
691 object: &T,
692 body_type: BodyType,
693 can_gc: CanGc,
694) -> Rc<Promise> {
695 let global = object.global();
696 let cx = GlobalScope::get_cx();
697
698 let realm = enter_realm(&*global);
700 let comp = InRealm::Entered(&realm);
701
702 let promise = Promise::new_in_current_realm(comp, can_gc);
705
706 if object.is_unusable() {
708 promise.reject_error(
709 Error::Type("The body's stream is disturbed or locked".to_string()),
710 can_gc,
711 );
712 return promise;
713 }
714
715 let stream = match object.body() {
716 Some(stream) => stream,
717 None => {
718 resolve_result_promise(
720 body_type,
721 &promise,
722 object.get_mime_type(can_gc),
723 Vec::with_capacity(0),
724 cx,
725 can_gc,
726 );
727 return promise;
728 },
729 };
730
731 if stream.is_errored() {
748 rooted!(in(*cx) let mut stored_error = UndefinedValue());
749 stream.get_stored_error(stored_error.handle_mut());
750 promise.reject(cx, stored_error.handle(), can_gc);
751 return promise;
752 }
753
754 let reader = match stream.acquire_default_reader(can_gc) {
759 Ok(r) => r,
760 Err(e) => {
761 promise.reject_error(e, can_gc);
762 return promise;
763 },
764 };
765
766 let error_promise = promise.clone();
768
769 let mime_type = object.get_mime_type(can_gc);
773 let success_promise = promise.clone();
774
775 reader.read_all_bytes(
780 cx,
781 Rc::new(move |bytes: &[u8]| {
782 resolve_result_promise(
783 body_type,
784 &success_promise,
785 mime_type.clone(),
786 bytes.to_vec(),
787 cx,
788 can_gc,
789 );
790 }),
791 Rc::new(move |cx, v| {
792 error_promise.reject(cx, v, can_gc);
793 }),
794 can_gc,
795 );
796
797 promise
798}
799
800fn resolve_result_promise(
803 body_type: BodyType,
804 promise: &Promise,
805 mime_type: Vec<u8>,
806 body: Vec<u8>,
807 cx: JSContext,
808 can_gc: CanGc,
809) {
810 let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type, can_gc);
811
812 match pkg_data_results {
813 Ok(results) => {
814 match results {
815 FetchedData::Text(s) => promise.resolve_native(&USVString(s), can_gc),
816 FetchedData::Json(j) => promise.resolve_native(&j, can_gc),
817 FetchedData::BlobData(b) => promise.resolve_native(&b, can_gc),
818 FetchedData::FormData(f) => promise.resolve_native(&f, can_gc),
819 FetchedData::Bytes(b) => promise.resolve_native(&b, can_gc),
820 FetchedData::ArrayBuffer(a) => promise.resolve_native(&a, can_gc),
821 FetchedData::JSException(e) => promise.reject_native(&e.handle(), can_gc),
822 };
823 },
824 Err(err) => promise.reject_error(err, can_gc),
825 }
826}
827
828fn run_package_data_algorithm(
832 cx: JSContext,
833 bytes: Vec<u8>,
834 body_type: BodyType,
835 mime_type: Vec<u8>,
836 can_gc: CanGc,
837) -> Fallible<FetchedData> {
838 let mime = &*mime_type;
839 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
840 let global = GlobalScope::from_safe_context(cx, InRealm::Already(&in_realm_proof));
841 match body_type {
842 BodyType::Text => run_text_data_algorithm(bytes),
843 BodyType::Json => run_json_data_algorithm(cx, bytes),
844 BodyType::Blob => run_blob_data_algorithm(&global, bytes, mime, can_gc),
845 BodyType::FormData => run_form_data_algorithm(&global, bytes, mime, can_gc),
846 BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes, can_gc),
847 BodyType::Bytes => run_bytes_data_algorithm(cx, bytes, can_gc),
848 }
849}
850
851fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
853 let no_bom_bytes = if bytes.starts_with(b"\xEF\xBB\xBF") {
856 &bytes[3..]
857 } else {
858 &bytes
859 };
860 Ok(FetchedData::Text(
861 String::from_utf8_lossy(no_bom_bytes).into_owned(),
862 ))
863}
864
865#[expect(unsafe_code)]
866fn run_json_data_algorithm(cx: JSContext, bytes: Vec<u8>) -> Fallible<FetchedData> {
868 let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8);
873 rooted!(in(*cx) let mut rval = UndefinedValue());
874 unsafe {
875 if !JS_ParseJSON(
876 *cx,
877 json_text.as_ptr(),
878 json_text.len() as u32,
879 rval.handle_mut(),
880 ) {
881 rooted!(in(*cx) let mut exception = UndefinedValue());
882 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
883 JS_ClearPendingException(*cx);
884 return Ok(FetchedData::JSException(RootedTraceableBox::from_box(
885 Heap::boxed(exception.get()),
886 )));
887 }
888 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(rval.get()));
889 Ok(FetchedData::Json(rooted_heap))
890 }
891}
892
893fn run_blob_data_algorithm(
895 root: &GlobalScope,
896 bytes: Vec<u8>,
897 mime: &[u8],
898 can_gc: CanGc,
899) -> Fallible<FetchedData> {
900 let mime_string = if let Ok(s) = String::from_utf8(mime.to_vec()) {
901 s
902 } else {
903 "".to_string()
904 };
905 let blob = Blob::new(
906 root,
907 BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
908 can_gc,
909 );
910 Ok(FetchedData::BlobData(blob))
911}
912
913fn extract_name_from_content_disposition(headers: &HeaderMap) -> Option<String> {
914 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
915
916 for part in cd.split(';').map(|s| s.trim()) {
917 if let Some(rest) = part.strip_prefix("name=") {
918 let v = rest.trim();
919 let v = v.strip_prefix('"').unwrap_or(v);
920 let v = v.strip_suffix('"').unwrap_or(v);
921 return Some(v.to_string());
922 }
923 }
924 None
925}
926
927fn extract_filename_from_content_disposition(headers: &HeaderMap) -> Option<String> {
928 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
929 if let Some(index) = cd.find("filename=") {
930 let start = index + "filename=".len();
931 return Some(
932 cd.get(start..)
933 .unwrap_or_default()
934 .trim_matches('"')
935 .to_owned(),
936 );
937 }
938 if let Some(index) = cd.find("filename*=UTF-8''") {
939 let start = index + "filename*=UTF-8''".len();
940 return Some(
941 cd.get(start..)
942 .unwrap_or_default()
943 .trim_matches('"')
944 .to_owned(),
945 );
946 }
947 None
948}
949
950fn content_type_from_headers(headers: &HeaderMap) -> Result<String, Error> {
951 match headers.get(CONTENT_TYPE) {
952 Some(value) => Ok(value
953 .to_str()
954 .map_err(|_| Error::Type("Inappropriate MIME-type for Body".to_string()))?
955 .to_string()),
956 None => Ok("text/plain".to_string()),
957 }
958}
959
960fn append_form_data_entry_from_part(
961 root: &GlobalScope,
962 formdata: &FormData,
963 headers: &HeaderMap,
964 body: Vec<u8>,
965 can_gc: CanGc,
966) -> Fallible<()> {
967 let Some(name) = extract_name_from_content_disposition(headers) else {
968 return Ok(());
969 };
970 let filename = extract_filename_from_content_disposition(headers);
972 if let Some(filename) = filename {
973 let content_type = content_type_from_headers(headers)?;
979 let file = File::new(
980 root,
981 BlobImpl::new_from_bytes(body, normalize_type_string(&content_type)),
982 DOMString::from(filename),
983 None,
984 can_gc,
985 );
986 let blob = file.upcast::<Blob>();
987 formdata.Append_(USVString(name), blob, None);
988 } else {
989 let (value, _) = UTF_8.decode_without_bom_handling(&body);
992 formdata.Append(USVString(name), USVString(value.to_string()));
993 }
994 Ok(())
995}
996
997fn append_multipart_nodes(
998 root: &GlobalScope,
999 formdata: &FormData,
1000 nodes: Vec<Node>,
1001 can_gc: CanGc,
1002) -> Fallible<()> {
1003 for node in nodes {
1004 match node {
1005 Node::Part(part) => {
1006 append_form_data_entry_from_part(root, formdata, &part.headers, part.body, can_gc)?;
1007 },
1008 Node::File(file_part) => {
1009 let body = fs::read(&file_part.path)
1010 .map_err(|_| Error::Type("file part could not be read".to_string()))?;
1011 append_form_data_entry_from_part(root, formdata, &file_part.headers, body, can_gc)?;
1012 },
1013 Node::Multipart((_, inner)) => {
1014 append_multipart_nodes(root, formdata, inner, can_gc)?;
1015 },
1016 }
1017 }
1018 Ok(())
1019}
1020
1021fn run_form_data_algorithm(
1023 root: &GlobalScope,
1024 bytes: Vec<u8>,
1025 mime: &[u8],
1026 can_gc: CanGc,
1027) -> Fallible<FetchedData> {
1028 let mime_str = str::from_utf8(mime).unwrap_or_default();
1031 let mime: Mime = mime_str
1032 .parse()
1033 .map_err(|_| Error::Type("Inappropriate MIME-type for Body".to_string()))?;
1034
1035 if mime.type_() == mime::MULTIPART && mime.subtype() == mime::FORM_DATA {
1039 let mut headers = HeaderMap::new();
1043 headers.insert(
1044 CONTENT_TYPE,
1045 mime_str
1046 .parse()
1047 .map_err(|_| Error::Type("Inappropriate MIME-type for Body".to_string()))?,
1048 );
1049
1050 if let Some(boundary) = mime.get_param(mime::BOUNDARY) {
1051 let closing_boundary = format!("--{}--", boundary.as_str()).into_bytes();
1052 let trimmed_bytes = bytes.strip_suffix(b"\r\n").unwrap_or(&bytes);
1053 if trimmed_bytes == closing_boundary {
1054 let formdata = FormData::new(None, root, can_gc);
1055 return Ok(FetchedData::FormData(formdata));
1056 }
1057 }
1058
1059 let mut cursor = Cursor::new(bytes);
1060 let nodes = read_multipart_body(&mut cursor, &headers, false)
1062 .map_err(|_| Error::Type("Inappropriate MIME-type for Body".to_string()))?;
1063 let formdata = FormData::new(None, root, can_gc);
1068
1069 append_multipart_nodes(root, &formdata, nodes, can_gc)?;
1070
1071 return Ok(FetchedData::FormData(formdata));
1072 }
1073
1074 if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED {
1075 let entries = form_urlencoded::parse(&bytes);
1080 let formdata = FormData::new(None, root, can_gc);
1081 for (k, e) in entries {
1082 formdata.Append(USVString(k.into_owned()), USVString(e.into_owned()));
1083 }
1084 return Ok(FetchedData::FormData(formdata));
1085 }
1086
1087 Err(Error::Type("Inappropriate MIME-type for Body".to_string()))
1089}
1090
1091fn run_bytes_data_algorithm(cx: JSContext, bytes: Vec<u8>, can_gc: CanGc) -> Fallible<FetchedData> {
1093 rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1094
1095 create_buffer_source::<Uint8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
1096 .map_err(|_| Error::JSFailed)?;
1097
1098 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1099 Ok(FetchedData::Bytes(rooted_heap))
1100}
1101
1102pub(crate) fn run_array_buffer_data_algorithm(
1104 cx: JSContext,
1105 bytes: Vec<u8>,
1106 can_gc: CanGc,
1107) -> Fallible<FetchedData> {
1108 rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1109
1110 create_buffer_source::<ArrayBufferU8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
1111 .map_err(|_| Error::JSFailed)?;
1112
1113 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1114 Ok(FetchedData::ArrayBuffer(rooted_heap))
1115}
1116
1117#[expect(unsafe_code)]
1118pub(crate) fn decode_to_utf16_with_bom_removal(
1119 bytes: &[u8],
1120 encoding: &'static Encoding,
1121) -> Vec<u16> {
1122 let mut decoder = encoding.new_decoder_with_bom_removal();
1123 let capacity = decoder
1124 .max_utf16_buffer_length(bytes.len())
1125 .expect("Overflow");
1126 let mut utf16 = Vec::with_capacity(capacity);
1127 let extra = unsafe { slice::from_raw_parts_mut(utf16.as_mut_ptr(), capacity) };
1128 let (_, read, written, _) = decoder.decode_to_utf16(bytes, extra, true);
1129 assert_eq!(read, bytes.len());
1130 unsafe { utf16.set_len(written) }
1131 utf16
1132}
1133
1134pub(crate) trait BodyMixin {
1136 fn is_body_used(&self) -> bool;
1138 fn is_unusable(&self) -> bool;
1140 fn body(&self) -> Option<DomRoot<ReadableStream>>;
1142 fn get_mime_type(&self, can_gc: CanGc) -> Vec<u8>;
1144}