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::context::JSContext;
15use js::jsapi::{Heap, JSObject, Value as JSValue};
16use js::jsval::{JSVal, UndefinedValue};
17use js::realm::CurrentRealm;
18use js::rust::HandleValue;
19use js::rust::wrappers2::{JS_ClearPendingException, JS_GetPendingException, JS_ParseJSON};
20use js::typedarray::{ArrayBufferU8, Uint8};
21use mime::{self, Mime};
22use net_traits::request::{
23 BodyChunkRequest, BodyChunkResponse, BodySource as NetBodySource, RequestBody,
24};
25use script_bindings::reflector::DomObject;
26use servo_base::generic_channel::GenericSharedMemory;
27use servo_constellation_traits::BlobImpl;
28use url::form_urlencoded;
29
30use crate::dom::RootedPromise;
31use crate::dom::bindings::buffer_source::{create_buffer_source, get_buffer_source_copy};
32use crate::dom::bindings::codegen::Bindings::BlobBinding::Blob_Binding::BlobMethods;
33use crate::dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
34use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
35use crate::dom::bindings::error::{Error, Fallible};
36use crate::dom::bindings::inheritance::Castable;
37use crate::dom::bindings::refcounted::Trusted;
38use crate::dom::bindings::reflector::DomGlobal;
39use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
40use crate::dom::bindings::str::{DOMString, USVString};
41use crate::dom::bindings::trace::RootedTraceableBox;
42use crate::dom::blob::{Blob, normalize_type_string};
43use crate::dom::encoding::textdecoderstream::TextDecoderStream;
44use crate::dom::file::File;
45use crate::dom::formdata::FormData;
46use crate::dom::globalscope::GlobalScope;
47use crate::dom::html::htmlformelement::{encode_multipart_form_data, generate_boundary};
48use crate::dom::promise::Promise;
49use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
50use crate::dom::readablestream::{
51 ReadableStream, get_read_promise_bytes, get_read_promise_done, pipe_through,
52};
53use crate::dom::urlsearchparams::URLSearchParams;
54use crate::fetch::mime_multipart::{Node, read_multipart_body};
55use crate::realms::enter_auto_realm;
56use crate::tasks::task_source::SendableTaskSource;
57
58pub(crate) fn clone_body_stream_for_dom_body(
60 cx: &mut js::context::JSContext,
61 original_body_stream: &MutNullableDom<ReadableStream>,
62 cloned_body_stream: &MutNullableDom<ReadableStream>,
63) -> Fallible<()> {
64 let Some(stream) = original_body_stream.get() else {
67 return Ok(());
68 };
69
70 let branches = stream.tee(cx, true)?;
72 let out1 = &*branches[0];
73 let out2 = &*branches[1];
74
75 original_body_stream.set(Some(out1));
78 cloned_body_stream.set(Some(out2));
79
80 Ok(())
81}
82
83#[derive(Clone, PartialEq)]
86pub(crate) enum BodySource {
87 Null,
89 Object,
93}
94
95enum StopReading {
97 Error,
99 Done,
101}
102
103#[derive(Clone)]
109struct TransmitBodyConnectHandler {
110 stream: Trusted<ReadableStream>,
111 task_source: SendableTaskSource,
112 bytes_sender: Option<IpcSender<BodyChunkResponse>>,
113 control_sender: Option<IpcSender<BodyChunkRequest>>,
114 in_memory: Option<GenericSharedMemory>,
115 in_memory_done: bool,
116 source: BodySource,
117}
118
119impl TransmitBodyConnectHandler {
120 pub(crate) fn new(
121 stream: Trusted<ReadableStream>,
122 task_source: SendableTaskSource,
123 control_sender: IpcSender<BodyChunkRequest>,
124 in_memory: Option<GenericSharedMemory>,
125 source: BodySource,
126 ) -> TransmitBodyConnectHandler {
127 TransmitBodyConnectHandler {
128 stream,
129 task_source,
130 bytes_sender: None,
131 control_sender: Some(control_sender),
132 in_memory,
133 in_memory_done: false,
134 source,
135 }
136 }
137
138 pub(crate) fn reset_in_memory_done(&mut self) {
141 self.in_memory_done = false;
142 }
143
144 fn re_extract(&mut self, chunk_request_receiver: IpcReceiver<BodyChunkRequest>) {
147 let mut body_handler = self.clone();
148 body_handler.reset_in_memory_done();
149
150 ROUTER.add_typed_route(
151 chunk_request_receiver,
152 Box::new(move |message| {
153 let request = message.unwrap();
154 match request {
155 BodyChunkRequest::Connect(sender) => {
156 body_handler.start_reading(sender);
157 },
158 BodyChunkRequest::Extract(receiver) => {
159 body_handler.re_extract(receiver);
160 },
161 BodyChunkRequest::Chunk => body_handler.transmit_source(),
162 BodyChunkRequest::Done => {
165 body_handler.stop_reading(StopReading::Done);
166 },
167 BodyChunkRequest::Error => {
170 body_handler.stop_reading(StopReading::Error);
171 },
172 }
173 }),
174 );
175 }
176
177 fn transmit_source(&mut self) {
184 if self.in_memory_done {
185 self.stop_reading(StopReading::Done);
187 return;
188 }
189
190 if let BodySource::Null = self.source {
191 panic!("ReadableStream(Null) sources should not re-direct.");
192 }
193
194 if let Some(bytes) = self.in_memory.clone() {
195 self.in_memory_done = true;
197 let _ = self
198 .bytes_sender
199 .as_ref()
200 .expect("No bytes sender to transmit source.")
201 .send(BodyChunkResponse::Chunk(bytes));
202 return;
203 }
204 warn!("Re-directs for file-based Blobs not supported yet.");
205 }
206
207 fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
210 self.bytes_sender = Some(sender);
211
212 if self.source == BodySource::Null {
214 let stream = self.stream.clone();
215 self.task_source
216 .queue(task!(start_reading_request_body_stream: move |cx| {
217 let rooted_stream = stream.root();
219
220 rooted_stream.acquire_default_reader(cx)
224 .expect("Couldn't acquire a reader for the body stream.");
225
226 }));
228 }
229 }
230
231 fn stop_reading(&mut self, reason: StopReading) {
236 let bytes_sender = self
237 .bytes_sender
238 .take()
239 .expect("Stop reading called multiple times on TransmitBodyConnectHandler.");
240 match reason {
241 StopReading::Error => {
242 let _ = bytes_sender.send(BodyChunkResponse::Error);
243 },
244 StopReading::Done => {
245 let _ = bytes_sender.send(BodyChunkResponse::Done);
246 },
247 }
248 let _ = self.control_sender.take();
249 }
250
251 fn transmit_body_chunk(&mut self) {
253 if self.in_memory_done {
254 self.stop_reading(StopReading::Done);
256 return;
257 }
258
259 let stream = self.stream.clone();
260 let control_sender = self.control_sender.clone();
261 let bytes_sender = self
262 .bytes_sender
263 .clone()
264 .expect("No bytes sender to transmit chunk.");
265
266 if let Some(bytes) = self.in_memory.clone() {
268 let _ = bytes_sender.send(BodyChunkResponse::Chunk(bytes));
269 self.in_memory_done = true;
272 return;
273 }
274
275 self.task_source.queue(
276 task!(setup_native_body_promise_handler: move |cx| {
277 let rooted_stream = stream.root();
278 let global = rooted_stream.global();
279
280 let promise = rooted_stream.read_a_chunk(cx);
282
283 rooted!(&in(cx) let mut promise_handler = Some(TransmitBodyPromiseHandler {
287 bytes_sender: bytes_sender.clone(),
288 stream: Dom::from_ref(&rooted_stream),
289 control_sender: control_sender.clone().unwrap(),
290 }));
291
292 rooted!(&in(cx) let mut rejection_handler = Some(TransmitBodyPromiseRejectionHandler {
293 bytes_sender,
294 stream: Dom::from_ref(&rooted_stream),
295 control_sender: control_sender.unwrap(),
296 }));
297
298 let handler =
299 PromiseNativeHandler::new(cx, &global, promise_handler.take().map(|h| Box::new(h) as Box<_>), rejection_handler.take().map(|h| Box::new(h) as Box<_>));
300
301 let mut realm = enter_auto_realm(cx, &*global);
302 let realm = &mut realm.current_realm();
303 promise.append_native_handler(realm, &handler);
304 })
305 );
306 }
307}
308
309#[derive(Clone, JSTraceable, MallocSizeOf)]
312#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
313struct TransmitBodyPromiseHandler {
314 #[no_trace]
315 bytes_sender: IpcSender<BodyChunkResponse>,
316 stream: Dom<ReadableStream>,
317 #[no_trace]
318 control_sender: IpcSender<BodyChunkRequest>,
319}
320
321impl js::gc::Rootable for TransmitBodyPromiseHandler {}
322
323impl Callback for TransmitBodyPromiseHandler {
324 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
326 let is_done = match get_read_promise_done(cx, &v) {
327 Ok(is_done) => is_done,
328 Err(_) => {
329 let _ = self.control_sender.send(BodyChunkRequest::Done);
332 return self.stream.stop_reading(cx);
333 },
334 };
335
336 if is_done {
337 let _ = self.control_sender.send(BodyChunkRequest::Done);
340 return self.stream.stop_reading(cx);
341 }
342
343 let chunk = match get_read_promise_bytes(cx, &v) {
344 Ok(chunk) => chunk,
345 Err(_) => {
346 let _ = self.control_sender.send(BodyChunkRequest::Error);
348 return self.stream.stop_reading(cx);
349 },
350 };
351
352 let _ = self
356 .bytes_sender
357 .send(BodyChunkResponse::Chunk(GenericSharedMemory::from_vec(
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(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(
411 self,
412 cx: &mut JSContext,
413 ) -> (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_manager = global.task_manager();
429 let task_source = task_manager.networking_task_source();
430
431 let in_memory = stream.get_in_memory_bytes(cx).or_else(|| {
434 if total_bytes == Some(0) {
435 Some(GenericSharedMemory::from_bytes(&[]))
436 } else {
437 None
438 }
439 });
440
441 let net_source = match source {
442 BodySource::Null => NetBodySource::Null,
443 _ => NetBodySource::Object,
444 };
445
446 let mut body_handler = TransmitBodyConnectHandler::new(
447 trusted_stream,
448 task_source.into(),
449 chunk_request_sender.clone(),
450 in_memory,
451 source,
452 );
453
454 ROUTER.add_typed_route(
455 chunk_request_receiver,
456 Box::new(move |message| {
457 match message.unwrap() {
458 BodyChunkRequest::Connect(sender) => {
459 body_handler.start_reading(sender);
460 },
461 BodyChunkRequest::Extract(receiver) => {
462 body_handler.re_extract(receiver);
463 },
464 BodyChunkRequest::Chunk => body_handler.transmit_body_chunk(),
465 BodyChunkRequest::Done => {
468 body_handler.stop_reading(StopReading::Done);
469 },
470 BodyChunkRequest::Error => {
473 body_handler.stop_reading(StopReading::Error);
474 },
475 }
476 }),
477 );
478
479 let request_body = RequestBody::new(chunk_request_sender, net_source, total_bytes);
482
483 (request_body, stream)
485 }
486
487 pub(crate) fn in_memory(&self) -> bool {
489 self.stream.in_memory()
490 }
491}
492
493pub(crate) trait Extractable {
495 fn extract(
496 &self,
497 cx: &mut js::context::JSContext,
498 global: &GlobalScope,
499 keep_alive: bool,
500 ) -> Fallible<ExtractedBody>;
501}
502
503fn stream_from_body_init_bytes(
505 cx: &mut js::context::JSContext,
506 global: &GlobalScope,
507 bytes: Vec<u8>,
508) -> Fallible<DomRoot<ReadableStream>> {
509 ReadableStream::new_from_bytes_with_byte_reading_support(cx, global, bytes)
514}
515
516impl Extractable for BodyInit {
517 fn extract(
519 &self,
520 cx: &mut js::context::JSContext,
521 global: &GlobalScope,
522 keep_alive: bool,
523 ) -> Fallible<ExtractedBody> {
524 match self {
525 BodyInit::String(s) => s.extract(cx, global, keep_alive),
526 BodyInit::URLSearchParams(usp) => usp.extract(cx, global, keep_alive),
527 BodyInit::Blob(b) => b.extract(cx, global, keep_alive),
528 BodyInit::FormData(formdata) => formdata.extract(cx, global, keep_alive),
529 BodyInit::ArrayBuffer(typedarray) => {
530 let bytes = get_buffer_source_copy(typedarray.into());
532 let total_bytes = bytes.len();
533 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
534 Ok(ExtractedBody {
535 stream,
536 total_bytes: Some(total_bytes),
537 content_type: None,
538 source: BodySource::Object,
539 })
540 },
541 BodyInit::ArrayBufferView(typedarray) => {
542 let bytes = get_buffer_source_copy(typedarray.into());
544 let total_bytes = bytes.len();
545 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
546 Ok(ExtractedBody {
547 stream,
548 total_bytes: Some(total_bytes),
549 content_type: None,
550 source: BodySource::Object,
551 })
552 },
553 BodyInit::ReadableStream(stream) => {
554 if keep_alive {
556 return Err(Error::Type(
557 c"The body's stream is for a keepalive request".to_owned(),
558 ));
559 }
560 if stream.is_locked() || stream.is_disturbed() {
562 return Err(Error::Type(
563 c"The body's stream is disturbed or locked".to_owned(),
564 ));
565 }
566
567 Ok(ExtractedBody {
568 stream: stream.clone(),
569 total_bytes: None,
570 content_type: None,
571 source: BodySource::Null,
572 })
573 },
574 }
575 }
576}
577
578impl Extractable for Vec<u8> {
579 fn extract(
580 &self,
581 cx: &mut js::context::JSContext,
582 global: &GlobalScope,
583 _keep_alive: bool,
584 ) -> Fallible<ExtractedBody> {
585 let bytes = self.clone();
586 let total_bytes = self.len();
587 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
588 Ok(ExtractedBody {
589 stream,
590 total_bytes: Some(total_bytes),
591 content_type: None,
592 source: BodySource::Object,
594 })
595 }
596}
597
598impl Extractable for Blob {
599 fn extract(
600 &self,
601 cx: &mut js::context::JSContext,
602 _global: &GlobalScope,
603 _keep_alive: bool,
604 ) -> Fallible<ExtractedBody> {
605 let blob_type = self.Type();
606 let content_type = if blob_type.is_empty() {
607 None
608 } else {
609 Some(blob_type)
610 };
611 let total_bytes = self.Size() as usize;
612 let stream = self.get_stream(cx)?;
613 Ok(ExtractedBody {
614 stream,
615 total_bytes: Some(total_bytes),
616 content_type,
617 source: BodySource::Object,
618 })
619 }
620}
621
622impl Extractable for DOMString {
623 fn extract(
624 &self,
625 cx: &mut js::context::JSContext,
626 global: &GlobalScope,
627 _keep_alive: bool,
628 ) -> Fallible<ExtractedBody> {
629 let bytes = self.as_bytes().to_owned();
630 let total_bytes = bytes.len();
631 let content_type = Some(DOMString::from_static("text/plain;charset=UTF-8"));
632 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
633 Ok(ExtractedBody {
634 stream,
635 total_bytes: Some(total_bytes),
636 content_type,
637 source: BodySource::Object,
638 })
639 }
640}
641
642impl Extractable for FormData {
643 fn extract(
644 &self,
645 cx: &mut js::context::JSContext,
646 global: &GlobalScope,
647 _keep_alive: bool,
648 ) -> Fallible<ExtractedBody> {
649 let boundary = generate_boundary();
650 let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
651 let total_bytes = bytes.len();
652 let content_type = Some(DOMString::from(format!(
653 "multipart/form-data; boundary={}",
654 boundary
655 )));
656 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
657 Ok(ExtractedBody {
658 stream,
659 total_bytes: Some(total_bytes),
660 content_type,
661 source: BodySource::Object,
662 })
663 }
664}
665
666impl Extractable for URLSearchParams {
667 fn extract(
668 &self,
669 cx: &mut js::context::JSContext,
670 global: &GlobalScope,
671 _keep_alive: bool,
672 ) -> Fallible<ExtractedBody> {
673 let bytes = self.serialize_utf8().into_bytes();
674 let total_bytes = bytes.len();
675 let content_type = Some(DOMString::from_static(
676 "application/x-www-form-urlencoded;charset=UTF-8",
677 ));
678 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
679 Ok(ExtractedBody {
680 stream,
681 total_bytes: Some(total_bytes),
682 content_type,
683 source: BodySource::Object,
684 })
685 }
686}
687
688#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
689pub(crate) enum BodyType {
690 Blob,
691 Bytes,
692 FormData,
693 Json,
694 Text,
695 ArrayBuffer,
696}
697
698pub(crate) enum FetchedData {
699 Text(String),
700 Json(RootedTraceableBox<Heap<JSValue>>),
701 BlobData(DomRoot<Blob>),
702 Bytes(RootedTraceableBox<Heap<*mut JSObject>>),
703 FormData(DomRoot<FormData>),
704 ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>),
705 JSException(RootedTraceableBox<Heap<JSVal>>),
706}
707
708pub(crate) fn consume_body<T: BodyMixin + DomObject>(
714 cx: &mut js::context::JSContext,
715 object: &T,
716 body_type: BodyType,
717) -> RootedPromise {
718 let global = object.global();
719
720 let mut realm = enter_auto_realm(cx, &*global);
722 let cx: &mut _ = &mut realm.current_realm();
723
724 let promise = Promise::new_in_realm_rooted(cx);
727
728 if object.is_unusable() {
730 promise.reject_error(
731 cx,
732 Error::Type(c"The body's stream is disturbed or locked".to_owned()),
733 );
734 return promise;
735 }
736
737 let stream = match object.body() {
738 Some(stream) => stream,
739 None => {
740 let mime_type = object.get_mime_type(cx);
742 resolve_result_promise(cx, body_type, &promise, mime_type, Vec::with_capacity(0));
743 return promise;
744 },
745 };
746
747 if stream.is_errored() {
764 rooted!(&in(cx) let mut stored_error = UndefinedValue());
765 stream.get_stored_error(stored_error.handle_mut());
766 promise.reject(cx, stored_error.handle());
767 return promise;
768 }
769
770 let reader = match stream.acquire_default_reader(cx) {
775 Ok(r) => r,
776 Err(e) => {
777 promise.reject_error(cx, e);
778 return promise;
779 },
780 };
781
782 let error_promise = promise.clone();
784
785 let mime_type = object.get_mime_type(cx);
789 let success_promise = promise.clone();
790
791 reader.read_all_bytes(
796 cx,
797 Rc::new(move |cx, bytes: &[u8]| {
798 resolve_result_promise(
799 cx,
800 body_type,
801 &success_promise,
802 mime_type.clone(),
803 bytes.to_vec(),
804 );
805 }),
806 Rc::new(move |cx, v| {
807 error_promise.reject(cx, v);
808 }),
809 );
810
811 promise
812}
813
814fn resolve_result_promise(
817 cx: &mut js::context::JSContext,
818 body_type: BodyType,
819 promise: &Promise,
820 mime_type: Vec<u8>,
821 body: Vec<u8>,
822) {
823 let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type);
824
825 match pkg_data_results {
826 Ok(results) => {
827 match results {
828 FetchedData::Text(s) => promise.resolve_native(cx, &USVString(s)),
829 FetchedData::Json(j) => promise.resolve_native(cx, &j),
830 FetchedData::BlobData(b) => promise.resolve_native(cx, &b),
831 FetchedData::FormData(f) => promise.resolve_native(cx, &f),
832 FetchedData::Bytes(b) => promise.resolve_native(cx, &b),
833 FetchedData::ArrayBuffer(a) => promise.resolve_native(cx, &a),
834 FetchedData::JSException(e) => promise.reject_native(cx, &e.handle()),
835 };
836 },
837 Err(err) => promise.reject_error(cx, err),
838 }
839}
840
841fn run_package_data_algorithm(
845 cx: &mut js::context::JSContext,
846 bytes: Vec<u8>,
847 body_type: BodyType,
848 mime_type: Vec<u8>,
849) -> Fallible<FetchedData> {
850 let mime = &*mime_type;
851 let mut realm = CurrentRealm::assert(cx);
852 let global = GlobalScope::from_current_realm(&mut realm);
853 match body_type {
854 BodyType::Text => run_text_data_algorithm(bytes),
855 BodyType::Json => run_json_data_algorithm(cx, bytes),
856 BodyType::Blob => run_blob_data_algorithm(cx, &global, bytes, mime),
857 BodyType::FormData => run_form_data_algorithm(cx, &global, bytes, mime),
858 BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes),
859 BodyType::Bytes => run_bytes_data_algorithm(cx, bytes),
860 }
861}
862
863fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
865 let no_bom_bytes = if bytes.starts_with(b"\xEF\xBB\xBF") {
868 &bytes[3..]
869 } else {
870 &bytes
871 };
872 Ok(FetchedData::Text(
873 String::from_utf8_lossy(no_bom_bytes).into_owned(),
874 ))
875}
876
877#[expect(unsafe_code)]
878fn run_json_data_algorithm(
880 cx: &mut js::context::JSContext,
881 bytes: Vec<u8>,
882) -> Fallible<FetchedData> {
883 let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8);
888 rooted!(&in(cx) let mut rval = UndefinedValue());
889 unsafe {
890 if !JS_ParseJSON(
891 cx,
892 json_text.as_ptr(),
893 json_text.len() as u32,
894 rval.handle_mut(),
895 ) {
896 rooted!(&in(cx) let mut exception = UndefinedValue());
897 assert!(JS_GetPendingException(cx, exception.handle_mut()));
898 JS_ClearPendingException(cx);
899 return Ok(FetchedData::JSException(RootedTraceableBox::from_box(
900 Heap::boxed(exception.get()),
901 )));
902 }
903 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(rval.get()));
904 Ok(FetchedData::Json(rooted_heap))
905 }
906}
907
908fn run_blob_data_algorithm(
910 cx: &mut js::context::JSContext,
911 root: &GlobalScope,
912 bytes: Vec<u8>,
913 mime: &[u8],
914) -> Fallible<FetchedData> {
915 let mime_string = String::from_utf8(mime.to_vec()).unwrap_or_default();
916 let blob = Blob::new(
917 cx,
918 root,
919 BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
920 );
921 Ok(FetchedData::BlobData(blob))
922}
923
924fn extract_name_from_content_disposition(headers: &HeaderMap) -> Option<String> {
925 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
926
927 for part in cd.split(';').map(|s| s.trim()) {
928 if let Some(rest) = part.strip_prefix("name=") {
929 let v = rest.trim();
930 let v = v.strip_prefix('"').unwrap_or(v);
931 let v = v.strip_suffix('"').unwrap_or(v);
932 return Some(v.to_string());
933 }
934 }
935 None
936}
937
938fn extract_filename_from_content_disposition(headers: &HeaderMap) -> Option<String> {
939 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
940 if let Some(index) = cd.find("filename=") {
941 let start = index + "filename=".len();
942 return Some(
943 cd.get(start..)
944 .unwrap_or_default()
945 .trim_matches('"')
946 .to_owned(),
947 );
948 }
949 if let Some(index) = cd.find("filename*=UTF-8''") {
950 let start = index + "filename*=UTF-8''".len();
951 return Some(
952 cd.get(start..)
953 .unwrap_or_default()
954 .trim_matches('"')
955 .to_owned(),
956 );
957 }
958 None
959}
960
961fn content_type_from_headers(headers: &HeaderMap) -> Result<String, Error> {
962 match headers.get(CONTENT_TYPE) {
963 Some(value) => Ok(value
964 .to_str()
965 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?
966 .to_string()),
967 None => Ok("text/plain".to_string()),
968 }
969}
970
971fn append_form_data_entry_from_part(
972 cx: &mut js::context::JSContext,
973 root: &GlobalScope,
974 formdata: &FormData,
975 headers: &HeaderMap,
976 body: Vec<u8>,
977) -> Fallible<()> {
978 let Some(name) = extract_name_from_content_disposition(headers) else {
979 return Ok(());
980 };
981 let filename = extract_filename_from_content_disposition(headers);
983 if let Some(filename) = filename {
984 let content_type = content_type_from_headers(headers)?;
990 let file = File::new(
991 cx,
992 root,
993 BlobImpl::new_from_bytes(body, normalize_type_string(&content_type)),
994 DOMString::from(filename),
995 None,
996 );
997 let blob = file.upcast::<Blob>();
998 formdata.Append_(cx, USVString(name), blob, None);
999 } else {
1000 let (value, _) = UTF_8.decode_without_bom_handling(&body);
1003 formdata.Append(cx, USVString(name), USVString(value.to_string()));
1004 }
1005 Ok(())
1006}
1007
1008fn append_multipart_nodes(
1009 cx: &mut js::context::JSContext,
1010 root: &GlobalScope,
1011 formdata: &FormData,
1012 nodes: Vec<Node>,
1013) -> Fallible<()> {
1014 for node in nodes {
1015 match node {
1016 Node::Part(part) => {
1017 append_form_data_entry_from_part(cx, root, formdata, &part.headers, part.body)?;
1018 },
1019 Node::File(file_part) => {
1020 let body = fs::read(&file_part.path)
1021 .map_err(|_| Error::Type(c"file part could not be read".to_owned()))?;
1022 append_form_data_entry_from_part(cx, root, formdata, &file_part.headers, body)?;
1023 },
1024 Node::Multipart((_, inner)) => {
1025 append_multipart_nodes(cx, root, formdata, inner)?;
1026 },
1027 }
1028 }
1029 Ok(())
1030}
1031
1032fn run_form_data_algorithm(
1034 cx: &mut js::context::JSContext,
1035 root: &GlobalScope,
1036 bytes: Vec<u8>,
1037 mime: &[u8],
1038) -> Fallible<FetchedData> {
1039 let mime_str = str::from_utf8(mime).unwrap_or_default();
1042 let mime: Mime = mime_str
1043 .parse()
1044 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1045
1046 if mime.type_() == mime::MULTIPART && mime.subtype() == mime::FORM_DATA {
1050 let mut headers = HeaderMap::new();
1054 headers.insert(
1055 CONTENT_TYPE,
1056 mime_str
1057 .parse()
1058 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?,
1059 );
1060
1061 if let Some(boundary) = mime.get_param(mime::BOUNDARY) {
1062 let closing_boundary = format!("--{}--", boundary.as_str()).into_bytes();
1063 let trimmed_bytes = bytes.strip_suffix(b"\r\n").unwrap_or(&bytes);
1064 if trimmed_bytes == closing_boundary {
1065 let formdata = FormData::new(cx, None, root);
1066 return Ok(FetchedData::FormData(formdata));
1067 }
1068 }
1069
1070 let mut cursor = Cursor::new(bytes);
1071 let nodes = read_multipart_body(&mut cursor, &headers, false)
1073 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1074 let formdata = FormData::new(cx, None, root);
1079
1080 append_multipart_nodes(cx, root, &formdata, nodes)?;
1081
1082 return Ok(FetchedData::FormData(formdata));
1083 }
1084
1085 if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED {
1086 let entries = form_urlencoded::parse(&bytes);
1091 let formdata = FormData::new(cx, None, root);
1092 for (k, e) in entries {
1093 formdata.Append(cx, USVString(k.into_owned()), USVString(e.into_owned()));
1094 }
1095 return Ok(FetchedData::FormData(formdata));
1096 }
1097
1098 Err(Error::Type(c"Inappropriate MIME-type for Body".to_owned()))
1100}
1101
1102fn run_bytes_data_algorithm(
1104 cx: &mut js::context::JSContext,
1105 bytes: Vec<u8>,
1106) -> Fallible<FetchedData> {
1107 rooted!(&in(cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1108
1109 create_buffer_source::<Uint8>(cx, &bytes, array_buffer_ptr.handle_mut())
1110 .map_err(|_| Error::JSFailed)?;
1111
1112 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1113 Ok(FetchedData::Bytes(rooted_heap))
1114}
1115
1116pub(crate) fn run_array_buffer_data_algorithm(
1118 cx: &mut js::context::JSContext,
1119 bytes: Vec<u8>,
1120) -> Fallible<FetchedData> {
1121 rooted!(&in(cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1122
1123 create_buffer_source::<ArrayBufferU8>(cx, &bytes, array_buffer_ptr.handle_mut())
1124 .map_err(|_| Error::JSFailed)?;
1125
1126 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1127 Ok(FetchedData::ArrayBuffer(rooted_heap))
1128}
1129
1130#[expect(unsafe_code)]
1131pub(crate) fn decode_to_utf16_with_bom_removal(
1132 bytes: &[u8],
1133 encoding: &'static Encoding,
1134) -> Vec<u16> {
1135 let mut decoder = encoding.new_decoder_with_bom_removal();
1136 let capacity = decoder
1137 .max_utf16_buffer_length(bytes.len())
1138 .expect("Overflow");
1139 let mut utf16 = Vec::with_capacity(capacity);
1140 let extra = unsafe { slice::from_raw_parts_mut(utf16.as_mut_ptr(), capacity) };
1141 let (_, read, written, _) = decoder.decode_to_utf16(bytes, extra, true);
1142 assert_eq!(read, bytes.len());
1143 unsafe { utf16.set_len(written) }
1144 utf16
1145}
1146
1147pub(crate) trait BodyMixin {
1149 fn is_body_used(&self) -> bool;
1151 fn is_unusable(&self) -> bool;
1153 fn body(&self) -> Option<DomRoot<ReadableStream>>;
1155 fn get_mime_type(&self, cx: &mut js::context::JSContext) -> Vec<u8>;
1157}
1158
1159pub(crate) fn body_text_stream<T: BodyMixin + DomObject>(
1161 cx: &mut js::context::JSContext,
1162 object: &T,
1163) -> Fallible<DomRoot<ReadableStream>> {
1164 if object.is_unusable() {
1166 return Err(Error::Type(
1167 c"The body's stream is disturbed or locked".to_owned(),
1168 ));
1169 }
1170
1171 let Some(stream) = object.body() else {
1173 return ReadableStream::new_empty(cx, &object.global());
1176 };
1177
1178 let decoder =
1181 TextDecoderStream::new_with_proto(cx, &object.global(), None, UTF_8, false, false)?;
1182
1183 Ok(pipe_through(&stream, cx, &object.global(), &decoder))
1185}