1use std::io::Cursor;
6use std::rc::Rc;
7use std::{fs, ptr, slice, str};
8
9use encoding_rs::{Encoding, UTF_8};
10use http::HeaderMap;
11use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE};
12use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
13use ipc_channel::router::ROUTER;
14use js::jsapi::{Heap, JS_ClearPendingException, JSObject, Value as JSValue};
15use js::jsval::{JSVal, UndefinedValue};
16use js::realm::CurrentRealm;
17use js::rust::HandleValue;
18use js::rust::wrappers::{JS_GetPendingException, JS_ParseJSON};
19use js::typedarray::{ArrayBufferU8, Uint8};
20use mime::{self, Mime};
21use mime_multipart_hyper1::{Node, read_multipart_body};
22use net_traits::request::{
23 BodyChunkRequest, BodyChunkResponse, BodySource as NetBodySource, RequestBody,
24};
25use servo_base::generic_channel::GenericSharedMemory;
26use servo_constellation_traits::BlobImpl;
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_auto_realm, 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 |cx| {
272 let rooted_stream = stream.root();
273 let global = rooted_stream.global();
274
275 let promise = rooted_stream.read_a_chunk(cx);
277
278 rooted!(&in(cx) let mut promise_handler = Some(TransmitBodyPromiseHandler {
282 bytes_sender: bytes_sender.clone(),
283 stream: Dom::from_ref(&rooted_stream),
284 control_sender: control_sender.clone().unwrap(),
285 }));
286
287 rooted!(&in(cx) let mut rejection_handler = Some(TransmitBodyPromiseRejectionHandler {
288 bytes_sender,
289 stream: Dom::from_ref(&rooted_stream),
290 control_sender: control_sender.unwrap(),
291 }));
292
293 let handler =
294 PromiseNativeHandler::new(&global, promise_handler.take().map(|h| Box::new(h) as Box<_>), rejection_handler.take().map(|h| Box::new(h) as Box<_>), CanGc::from_cx(cx));
295
296 let mut realm = enter_auto_realm(cx, &*global);
297 let realm = &mut realm.current_realm();
298 let in_realm_proof = realm.into();
299 let comp = InRealm::Already(&in_realm_proof);
300 promise.append_native_handler(&handler, comp, CanGc::from_cx(realm));
301 })
302 );
303 }
304}
305
306#[derive(Clone, JSTraceable, MallocSizeOf)]
309#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
310struct TransmitBodyPromiseHandler {
311 #[no_trace]
312 bytes_sender: IpcSender<BodyChunkResponse>,
313 stream: Dom<ReadableStream>,
314 #[no_trace]
315 control_sender: IpcSender<BodyChunkRequest>,
316}
317
318impl js::gc::Rootable for TransmitBodyPromiseHandler {}
319
320impl Callback for TransmitBodyPromiseHandler {
321 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
323 let can_gc = CanGc::from_cx(cx);
324 let _realm = InRealm::Already(&cx.into());
325 let cx = cx.into();
326 let is_done = match get_read_promise_done(cx, &v, can_gc) {
327 Ok(is_done) => is_done,
328 Err(_) => {
329 let _ = self.control_sender.send(BodyChunkRequest::Done);
332 return self.stream.stop_reading(can_gc);
333 },
334 };
335
336 if is_done {
337 let _ = self.control_sender.send(BodyChunkRequest::Done);
340 return self.stream.stop_reading(can_gc);
341 }
342
343 let chunk = match get_read_promise_bytes(cx, &v, can_gc) {
344 Ok(chunk) => chunk,
345 Err(_) => {
346 let _ = self.control_sender.send(BodyChunkRequest::Error);
348 return self.stream.stop_reading(can_gc);
349 },
350 };
351
352 let _ = self
356 .bytes_sender
357 .send(BodyChunkResponse::Chunk(GenericSharedMemory::from_bytes(
358 &chunk,
359 )));
360 }
361}
362
363#[derive(Clone, JSTraceable, MallocSizeOf)]
366#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
367struct TransmitBodyPromiseRejectionHandler {
368 #[no_trace]
369 bytes_sender: IpcSender<BodyChunkResponse>,
370 stream: Dom<ReadableStream>,
371 #[no_trace]
372 control_sender: IpcSender<BodyChunkRequest>,
373}
374
375impl js::gc::Rootable for TransmitBodyPromiseRejectionHandler {}
376
377impl Callback for TransmitBodyPromiseRejectionHandler {
378 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
380 let _ = self.control_sender.send(BodyChunkRequest::Error);
382 self.stream.stop_reading(CanGc::from_cx(cx));
383 }
384}
385
386pub(crate) struct ExtractedBody {
388 pub(crate) stream: DomRoot<ReadableStream>,
390 pub(crate) source: BodySource,
392 pub(crate) total_bytes: Option<usize>,
394 pub(crate) content_type: Option<DOMString>,
396}
397
398impl ExtractedBody {
399 pub(crate) fn into_net_request_body(self) -> (RequestBody, DomRoot<ReadableStream>) {
411 let ExtractedBody {
412 stream,
413 total_bytes,
414 content_type: _,
415 source,
416 } = self;
417
418 let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
421
422 let trusted_stream = Trusted::new(&*stream);
423
424 let global = stream.global();
425 let task_source = global.task_manager().networking_task_source();
426
427 let in_memory = stream.get_in_memory_bytes();
429
430 let net_source = match source {
431 BodySource::Null => NetBodySource::Null,
432 _ => NetBodySource::Object,
433 };
434
435 let mut body_handler = TransmitBodyConnectHandler::new(
436 trusted_stream,
437 task_source.into(),
438 chunk_request_sender.clone(),
439 in_memory,
440 source,
441 );
442
443 ROUTER.add_typed_route(
444 chunk_request_receiver,
445 Box::new(move |message| {
446 match message.unwrap() {
447 BodyChunkRequest::Connect(sender) => {
448 body_handler.start_reading(sender);
449 },
450 BodyChunkRequest::Extract(receiver) => {
451 body_handler.re_extract(receiver);
452 },
453 BodyChunkRequest::Chunk => body_handler.transmit_body_chunk(),
454 BodyChunkRequest::Done => {
457 body_handler.stop_reading(StopReading::Done);
458 },
459 BodyChunkRequest::Error => {
462 body_handler.stop_reading(StopReading::Error);
463 },
464 }
465 }),
466 );
467
468 let request_body = RequestBody::new(chunk_request_sender, net_source, total_bytes);
471
472 (request_body, stream)
474 }
475
476 pub(crate) fn in_memory(&self) -> bool {
478 self.stream.in_memory()
479 }
480}
481
482pub(crate) trait Extractable {
484 fn extract(
485 &self,
486 global: &GlobalScope,
487 keep_alive: bool,
488 can_gc: CanGc,
489 ) -> Fallible<ExtractedBody>;
490}
491
492impl Extractable for BodyInit {
493 fn extract(
495 &self,
496 global: &GlobalScope,
497 keep_alive: bool,
498 can_gc: CanGc,
499 ) -> Fallible<ExtractedBody> {
500 match self {
501 BodyInit::String(s) => s.extract(global, keep_alive, can_gc),
502 BodyInit::URLSearchParams(usp) => usp.extract(global, keep_alive, can_gc),
503 BodyInit::Blob(b) => b.extract(global, keep_alive, can_gc),
504 BodyInit::FormData(formdata) => formdata.extract(global, keep_alive, can_gc),
505 BodyInit::ArrayBuffer(typedarray) => {
506 let bytes = typedarray.to_vec();
507 let total_bytes = bytes.len();
508 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
509 Ok(ExtractedBody {
510 stream,
511 total_bytes: Some(total_bytes),
512 content_type: None,
513 source: BodySource::Object,
514 })
515 },
516 BodyInit::ArrayBufferView(typedarray) => {
517 let bytes = typedarray.to_vec();
518 let total_bytes = bytes.len();
519 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
520 Ok(ExtractedBody {
521 stream,
522 total_bytes: Some(total_bytes),
523 content_type: None,
524 source: BodySource::Object,
525 })
526 },
527 BodyInit::ReadableStream(stream) => {
528 if keep_alive {
530 return Err(Error::Type(
531 c"The body's stream is for a keepalive request".to_owned(),
532 ));
533 }
534 if stream.is_locked() || stream.is_disturbed() {
536 return Err(Error::Type(
537 c"The body's stream is disturbed or locked".to_owned(),
538 ));
539 }
540
541 Ok(ExtractedBody {
542 stream: stream.clone(),
543 total_bytes: None,
544 content_type: None,
545 source: BodySource::Null,
546 })
547 },
548 }
549 }
550}
551
552impl Extractable for Vec<u8> {
553 fn extract(
554 &self,
555 global: &GlobalScope,
556 _keep_alive: bool,
557 can_gc: CanGc,
558 ) -> Fallible<ExtractedBody> {
559 let bytes = self.clone();
560 let total_bytes = self.len();
561 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
562 Ok(ExtractedBody {
563 stream,
564 total_bytes: Some(total_bytes),
565 content_type: None,
566 source: BodySource::Object,
568 })
569 }
570}
571
572impl Extractable for Blob {
573 fn extract(
574 &self,
575 _global: &GlobalScope,
576 _keep_alive: bool,
577 can_gc: CanGc,
578 ) -> Fallible<ExtractedBody> {
579 let blob_type = self.Type();
580 let content_type = if blob_type.is_empty() {
581 None
582 } else {
583 Some(blob_type)
584 };
585 let total_bytes = self.Size() as usize;
586 let stream = self.get_stream(can_gc)?;
587 Ok(ExtractedBody {
588 stream,
589 total_bytes: Some(total_bytes),
590 content_type,
591 source: BodySource::Object,
592 })
593 }
594}
595
596impl Extractable for DOMString {
597 fn extract(
598 &self,
599 global: &GlobalScope,
600 _keep_alive: bool,
601 can_gc: CanGc,
602 ) -> Fallible<ExtractedBody> {
603 let bytes = self.as_bytes().to_owned();
604 let total_bytes = bytes.len();
605 let content_type = Some(DOMString::from("text/plain;charset=UTF-8"));
606 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
607 Ok(ExtractedBody {
608 stream,
609 total_bytes: Some(total_bytes),
610 content_type,
611 source: BodySource::Object,
612 })
613 }
614}
615
616impl Extractable for FormData {
617 fn extract(
618 &self,
619 global: &GlobalScope,
620 _keep_alive: bool,
621 can_gc: CanGc,
622 ) -> Fallible<ExtractedBody> {
623 let boundary = generate_boundary();
624 let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
625 let total_bytes = bytes.len();
626 let content_type = Some(DOMString::from(format!(
627 "multipart/form-data; boundary={}",
628 boundary
629 )));
630 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
631 Ok(ExtractedBody {
632 stream,
633 total_bytes: Some(total_bytes),
634 content_type,
635 source: BodySource::Object,
636 })
637 }
638}
639
640impl Extractable for URLSearchParams {
641 fn extract(
642 &self,
643 global: &GlobalScope,
644 _keep_alive: bool,
645 can_gc: CanGc,
646 ) -> Fallible<ExtractedBody> {
647 let bytes = self.serialize_utf8().into_bytes();
648 let total_bytes = bytes.len();
649 let content_type = Some(DOMString::from(
650 "application/x-www-form-urlencoded;charset=UTF-8",
651 ));
652 let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
653 Ok(ExtractedBody {
654 stream,
655 total_bytes: Some(total_bytes),
656 content_type,
657 source: BodySource::Object,
658 })
659 }
660}
661
662#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
663pub(crate) enum BodyType {
664 Blob,
665 Bytes,
666 FormData,
667 Json,
668 Text,
669 ArrayBuffer,
670}
671
672pub(crate) enum FetchedData {
673 Text(String),
674 Json(RootedTraceableBox<Heap<JSValue>>),
675 BlobData(DomRoot<Blob>),
676 Bytes(RootedTraceableBox<Heap<*mut JSObject>>),
677 FormData(DomRoot<FormData>),
678 ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>),
679 JSException(RootedTraceableBox<Heap<JSVal>>),
680}
681
682pub(crate) fn consume_body<T: BodyMixin + DomObject>(
688 object: &T,
689 body_type: BodyType,
690 can_gc: CanGc,
691) -> Rc<Promise> {
692 let global = object.global();
693 let cx = GlobalScope::get_cx();
694
695 let realm = enter_realm(&*global);
697 let comp = InRealm::Entered(&realm);
698
699 let promise = Promise::new_in_current_realm(comp, can_gc);
702
703 if object.is_unusable() {
705 promise.reject_error(
706 Error::Type(c"The body's stream is disturbed or locked".to_owned()),
707 can_gc,
708 );
709 return promise;
710 }
711
712 let stream = match object.body() {
713 Some(stream) => stream,
714 None => {
715 resolve_result_promise(
717 body_type,
718 &promise,
719 object.get_mime_type(can_gc),
720 Vec::with_capacity(0),
721 cx,
722 can_gc,
723 );
724 return promise;
725 },
726 };
727
728 if stream.is_errored() {
745 rooted!(in(*cx) let mut stored_error = UndefinedValue());
746 stream.get_stored_error(stored_error.handle_mut());
747 promise.reject(cx, stored_error.handle(), can_gc);
748 return promise;
749 }
750
751 let reader = match stream.acquire_default_reader(can_gc) {
756 Ok(r) => r,
757 Err(e) => {
758 promise.reject_error(e, can_gc);
759 return promise;
760 },
761 };
762
763 let error_promise = promise.clone();
765
766 let mime_type = object.get_mime_type(can_gc);
770 let success_promise = promise.clone();
771
772 reader.read_all_bytes(
777 cx,
778 Rc::new(move |bytes: &[u8]| {
779 resolve_result_promise(
780 body_type,
781 &success_promise,
782 mime_type.clone(),
783 bytes.to_vec(),
784 cx,
785 can_gc,
786 );
787 }),
788 Rc::new(move |cx, v| {
789 error_promise.reject(cx, v, can_gc);
790 }),
791 can_gc,
792 );
793
794 promise
795}
796
797fn resolve_result_promise(
800 body_type: BodyType,
801 promise: &Promise,
802 mime_type: Vec<u8>,
803 body: Vec<u8>,
804 cx: JSContext,
805 can_gc: CanGc,
806) {
807 let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type, can_gc);
808
809 match pkg_data_results {
810 Ok(results) => {
811 match results {
812 FetchedData::Text(s) => promise.resolve_native(&USVString(s), can_gc),
813 FetchedData::Json(j) => promise.resolve_native(&j, can_gc),
814 FetchedData::BlobData(b) => promise.resolve_native(&b, can_gc),
815 FetchedData::FormData(f) => promise.resolve_native(&f, can_gc),
816 FetchedData::Bytes(b) => promise.resolve_native(&b, can_gc),
817 FetchedData::ArrayBuffer(a) => promise.resolve_native(&a, can_gc),
818 FetchedData::JSException(e) => promise.reject_native(&e.handle(), can_gc),
819 };
820 },
821 Err(err) => promise.reject_error(err, can_gc),
822 }
823}
824
825fn run_package_data_algorithm(
829 cx: JSContext,
830 bytes: Vec<u8>,
831 body_type: BodyType,
832 mime_type: Vec<u8>,
833 can_gc: CanGc,
834) -> Fallible<FetchedData> {
835 let mime = &*mime_type;
836 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
837 let global = GlobalScope::from_safe_context(cx, InRealm::Already(&in_realm_proof));
838 match body_type {
839 BodyType::Text => run_text_data_algorithm(bytes),
840 BodyType::Json => run_json_data_algorithm(cx, bytes),
841 BodyType::Blob => run_blob_data_algorithm(&global, bytes, mime, can_gc),
842 BodyType::FormData => run_form_data_algorithm(&global, bytes, mime, can_gc),
843 BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes, can_gc),
844 BodyType::Bytes => run_bytes_data_algorithm(cx, bytes, can_gc),
845 }
846}
847
848fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
850 let no_bom_bytes = if bytes.starts_with(b"\xEF\xBB\xBF") {
853 &bytes[3..]
854 } else {
855 &bytes
856 };
857 Ok(FetchedData::Text(
858 String::from_utf8_lossy(no_bom_bytes).into_owned(),
859 ))
860}
861
862#[expect(unsafe_code)]
863fn run_json_data_algorithm(cx: JSContext, bytes: Vec<u8>) -> Fallible<FetchedData> {
865 let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8);
870 rooted!(in(*cx) let mut rval = UndefinedValue());
871 unsafe {
872 if !JS_ParseJSON(
873 *cx,
874 json_text.as_ptr(),
875 json_text.len() as u32,
876 rval.handle_mut(),
877 ) {
878 rooted!(in(*cx) let mut exception = UndefinedValue());
879 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
880 JS_ClearPendingException(*cx);
881 return Ok(FetchedData::JSException(RootedTraceableBox::from_box(
882 Heap::boxed(exception.get()),
883 )));
884 }
885 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(rval.get()));
886 Ok(FetchedData::Json(rooted_heap))
887 }
888}
889
890fn run_blob_data_algorithm(
892 root: &GlobalScope,
893 bytes: Vec<u8>,
894 mime: &[u8],
895 can_gc: CanGc,
896) -> Fallible<FetchedData> {
897 let mime_string = if let Ok(s) = String::from_utf8(mime.to_vec()) {
898 s
899 } else {
900 "".to_string()
901 };
902 let blob = Blob::new(
903 root,
904 BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
905 can_gc,
906 );
907 Ok(FetchedData::BlobData(blob))
908}
909
910fn extract_name_from_content_disposition(headers: &HeaderMap) -> Option<String> {
911 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
912
913 for part in cd.split(';').map(|s| s.trim()) {
914 if let Some(rest) = part.strip_prefix("name=") {
915 let v = rest.trim();
916 let v = v.strip_prefix('"').unwrap_or(v);
917 let v = v.strip_suffix('"').unwrap_or(v);
918 return Some(v.to_string());
919 }
920 }
921 None
922}
923
924fn extract_filename_from_content_disposition(headers: &HeaderMap) -> Option<String> {
925 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
926 if let Some(index) = cd.find("filename=") {
927 let start = index + "filename=".len();
928 return Some(
929 cd.get(start..)
930 .unwrap_or_default()
931 .trim_matches('"')
932 .to_owned(),
933 );
934 }
935 if let Some(index) = cd.find("filename*=UTF-8''") {
936 let start = index + "filename*=UTF-8''".len();
937 return Some(
938 cd.get(start..)
939 .unwrap_or_default()
940 .trim_matches('"')
941 .to_owned(),
942 );
943 }
944 None
945}
946
947fn content_type_from_headers(headers: &HeaderMap) -> Result<String, Error> {
948 match headers.get(CONTENT_TYPE) {
949 Some(value) => Ok(value
950 .to_str()
951 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?
952 .to_string()),
953 None => Ok("text/plain".to_string()),
954 }
955}
956
957fn append_form_data_entry_from_part(
958 root: &GlobalScope,
959 formdata: &FormData,
960 headers: &HeaderMap,
961 body: Vec<u8>,
962 can_gc: CanGc,
963) -> Fallible<()> {
964 let Some(name) = extract_name_from_content_disposition(headers) else {
965 return Ok(());
966 };
967 let filename = extract_filename_from_content_disposition(headers);
969 if let Some(filename) = filename {
970 let content_type = content_type_from_headers(headers)?;
976 let file = File::new(
977 root,
978 BlobImpl::new_from_bytes(body, normalize_type_string(&content_type)),
979 DOMString::from(filename),
980 None,
981 can_gc,
982 );
983 let blob = file.upcast::<Blob>();
984 formdata.Append_(USVString(name), blob, None);
985 } else {
986 let (value, _) = UTF_8.decode_without_bom_handling(&body);
989 formdata.Append(USVString(name), USVString(value.to_string()));
990 }
991 Ok(())
992}
993
994fn append_multipart_nodes(
995 root: &GlobalScope,
996 formdata: &FormData,
997 nodes: Vec<Node>,
998 can_gc: CanGc,
999) -> Fallible<()> {
1000 for node in nodes {
1001 match node {
1002 Node::Part(part) => {
1003 append_form_data_entry_from_part(root, formdata, &part.headers, part.body, can_gc)?;
1004 },
1005 Node::File(file_part) => {
1006 let body = fs::read(&file_part.path)
1007 .map_err(|_| Error::Type(c"file part could not be read".to_owned()))?;
1008 append_form_data_entry_from_part(root, formdata, &file_part.headers, body, can_gc)?;
1009 },
1010 Node::Multipart((_, inner)) => {
1011 append_multipart_nodes(root, formdata, inner, can_gc)?;
1012 },
1013 }
1014 }
1015 Ok(())
1016}
1017
1018fn run_form_data_algorithm(
1020 root: &GlobalScope,
1021 bytes: Vec<u8>,
1022 mime: &[u8],
1023 can_gc: CanGc,
1024) -> Fallible<FetchedData> {
1025 let mime_str = str::from_utf8(mime).unwrap_or_default();
1028 let mime: Mime = mime_str
1029 .parse()
1030 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1031
1032 if mime.type_() == mime::MULTIPART && mime.subtype() == mime::FORM_DATA {
1036 let mut headers = HeaderMap::new();
1040 headers.insert(
1041 CONTENT_TYPE,
1042 mime_str
1043 .parse()
1044 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?,
1045 );
1046
1047 if let Some(boundary) = mime.get_param(mime::BOUNDARY) {
1048 let closing_boundary = format!("--{}--", boundary.as_str()).into_bytes();
1049 let trimmed_bytes = bytes.strip_suffix(b"\r\n").unwrap_or(&bytes);
1050 if trimmed_bytes == closing_boundary {
1051 let formdata = FormData::new(None, root, can_gc);
1052 return Ok(FetchedData::FormData(formdata));
1053 }
1054 }
1055
1056 let mut cursor = Cursor::new(bytes);
1057 let nodes = read_multipart_body(&mut cursor, &headers, false)
1059 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1060 let formdata = FormData::new(None, root, can_gc);
1065
1066 append_multipart_nodes(root, &formdata, nodes, can_gc)?;
1067
1068 return Ok(FetchedData::FormData(formdata));
1069 }
1070
1071 if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED {
1072 let entries = form_urlencoded::parse(&bytes);
1077 let formdata = FormData::new(None, root, can_gc);
1078 for (k, e) in entries {
1079 formdata.Append(USVString(k.into_owned()), USVString(e.into_owned()));
1080 }
1081 return Ok(FetchedData::FormData(formdata));
1082 }
1083
1084 Err(Error::Type(c"Inappropriate MIME-type for Body".to_owned()))
1086}
1087
1088fn run_bytes_data_algorithm(cx: JSContext, bytes: Vec<u8>, can_gc: CanGc) -> Fallible<FetchedData> {
1090 rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1091
1092 create_buffer_source::<Uint8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
1093 .map_err(|_| Error::JSFailed)?;
1094
1095 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1096 Ok(FetchedData::Bytes(rooted_heap))
1097}
1098
1099pub(crate) fn run_array_buffer_data_algorithm(
1101 cx: JSContext,
1102 bytes: Vec<u8>,
1103 can_gc: CanGc,
1104) -> Fallible<FetchedData> {
1105 rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1106
1107 create_buffer_source::<ArrayBufferU8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
1108 .map_err(|_| Error::JSFailed)?;
1109
1110 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1111 Ok(FetchedData::ArrayBuffer(rooted_heap))
1112}
1113
1114#[expect(unsafe_code)]
1115pub(crate) fn decode_to_utf16_with_bom_removal(
1116 bytes: &[u8],
1117 encoding: &'static Encoding,
1118) -> Vec<u16> {
1119 let mut decoder = encoding.new_decoder_with_bom_removal();
1120 let capacity = decoder
1121 .max_utf16_buffer_length(bytes.len())
1122 .expect("Overflow");
1123 let mut utf16 = Vec::with_capacity(capacity);
1124 let extra = unsafe { slice::from_raw_parts_mut(utf16.as_mut_ptr(), capacity) };
1125 let (_, read, written, _) = decoder.decode_to_utf16(bytes, extra, true);
1126 assert_eq!(read, bytes.len());
1127 unsafe { utf16.set_len(written) }
1128 utf16
1129}
1130
1131pub(crate) trait BodyMixin {
1133 fn is_body_used(&self) -> bool;
1135 fn is_unusable(&self) -> bool;
1137 fn body(&self) -> Option<DomRoot<ReadableStream>>;
1139 fn get_mime_type(&self, can_gc: CanGc) -> Vec<u8>;
1141}