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