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, JSObject, Value as JSValue};
15use js::jsval::{JSVal, UndefinedValue};
16use js::realm::CurrentRealm;
17use js::rust::HandleValue;
18use js::rust::wrappers2::{JS_ClearPendingException, JS_GetPendingException, JS_ParseJSON};
19use js::typedarray::{ArrayBufferU8, Uint8};
20use mime::{self, Mime};
21use net_traits::request::{
22 BodyChunkRequest, BodyChunkResponse, BodySource as NetBodySource, RequestBody,
23};
24use script_bindings::reflector::DomObject;
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;
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::mime_multipart::{Node, read_multipart_body};
50use crate::realms::enter_auto_realm;
51use crate::script_runtime::CanGc;
52use crate::task_source::SendableTaskSource;
53
54pub(crate) fn clone_body_stream_for_dom_body(
56 cx: &mut js::context::JSContext,
57 original_body_stream: &MutNullableDom<ReadableStream>,
58 cloned_body_stream: &MutNullableDom<ReadableStream>,
59) -> Fallible<()> {
60 let Some(stream) = original_body_stream.get() else {
63 return Ok(());
64 };
65
66 let branches = stream.tee(cx, true)?;
68 let out1 = &*branches[0];
69 let out2 = &*branches[1];
70
71 original_body_stream.set(Some(out1));
74 cloned_body_stream.set(Some(out2));
75
76 Ok(())
77}
78
79#[derive(Clone, PartialEq)]
82pub(crate) enum BodySource {
83 Null,
85 Object,
89}
90
91enum StopReading {
93 Error,
95 Done,
97}
98
99#[derive(Clone)]
105struct TransmitBodyConnectHandler {
106 stream: Trusted<ReadableStream>,
107 task_source: SendableTaskSource,
108 bytes_sender: Option<IpcSender<BodyChunkResponse>>,
109 control_sender: Option<IpcSender<BodyChunkRequest>>,
110 in_memory: Option<GenericSharedMemory>,
111 in_memory_done: bool,
112 source: BodySource,
113}
114
115impl TransmitBodyConnectHandler {
116 pub(crate) fn new(
117 stream: Trusted<ReadableStream>,
118 task_source: SendableTaskSource,
119 control_sender: IpcSender<BodyChunkRequest>,
120 in_memory: Option<GenericSharedMemory>,
121 source: BodySource,
122 ) -> TransmitBodyConnectHandler {
123 TransmitBodyConnectHandler {
124 stream,
125 task_source,
126 bytes_sender: None,
127 control_sender: Some(control_sender),
128 in_memory,
129 in_memory_done: false,
130 source,
131 }
132 }
133
134 pub(crate) fn reset_in_memory_done(&mut self) {
137 self.in_memory_done = false;
138 }
139
140 fn re_extract(&mut self, chunk_request_receiver: IpcReceiver<BodyChunkRequest>) {
143 let mut body_handler = self.clone();
144 body_handler.reset_in_memory_done();
145
146 ROUTER.add_typed_route(
147 chunk_request_receiver,
148 Box::new(move |message| {
149 let request = message.unwrap();
150 match request {
151 BodyChunkRequest::Connect(sender) => {
152 body_handler.start_reading(sender);
153 },
154 BodyChunkRequest::Extract(receiver) => {
155 body_handler.re_extract(receiver);
156 },
157 BodyChunkRequest::Chunk => body_handler.transmit_source(),
158 BodyChunkRequest::Done => {
161 body_handler.stop_reading(StopReading::Done);
162 },
163 BodyChunkRequest::Error => {
166 body_handler.stop_reading(StopReading::Error);
167 },
168 }
169 }),
170 );
171 }
172
173 fn transmit_source(&mut self) {
180 if self.in_memory_done {
181 self.stop_reading(StopReading::Done);
183 return;
184 }
185
186 if let BodySource::Null = self.source {
187 panic!("ReadableStream(Null) sources should not re-direct.");
188 }
189
190 if let Some(bytes) = self.in_memory.clone() {
191 self.in_memory_done = true;
193 let _ = self
194 .bytes_sender
195 .as_ref()
196 .expect("No bytes sender to transmit source.")
197 .send(BodyChunkResponse::Chunk(bytes));
198 return;
199 }
200 warn!("Re-directs for file-based Blobs not supported yet.");
201 }
202
203 fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
206 self.bytes_sender = Some(sender);
207
208 if self.source == BodySource::Null {
210 let stream = self.stream.clone();
211 self.task_source
212 .queue(task!(start_reading_request_body_stream: move |cx| {
213 let rooted_stream = stream.root();
215
216 rooted_stream.acquire_default_reader(cx)
220 .expect("Couldn't acquire a reader for the body stream.");
221
222 }));
224 }
225 }
226
227 fn stop_reading(&mut self, reason: StopReading) {
232 let bytes_sender = self
233 .bytes_sender
234 .take()
235 .expect("Stop reading called multiple times on TransmitBodyConnectHandler.");
236 match reason {
237 StopReading::Error => {
238 let _ = bytes_sender.send(BodyChunkResponse::Error);
239 },
240 StopReading::Done => {
241 let _ = bytes_sender.send(BodyChunkResponse::Done);
242 },
243 }
244 let _ = self.control_sender.take();
245 }
246
247 fn transmit_body_chunk(&mut self) {
249 if self.in_memory_done {
250 self.stop_reading(StopReading::Done);
252 return;
253 }
254
255 let stream = self.stream.clone();
256 let control_sender = self.control_sender.clone();
257 let bytes_sender = self
258 .bytes_sender
259 .clone()
260 .expect("No bytes sender to transmit chunk.");
261
262 if let Some(bytes) = self.in_memory.clone() {
264 let _ = bytes_sender.send(BodyChunkResponse::Chunk(bytes));
265 self.in_memory_done = true;
268 return;
269 }
270
271 self.task_source.queue(
272 task!(setup_native_body_promise_handler: move |cx| {
273 let rooted_stream = stream.root();
274 let global = rooted_stream.global();
275
276 let promise = rooted_stream.read_a_chunk(cx);
278
279 rooted!(&in(cx) let mut promise_handler = Some(TransmitBodyPromiseHandler {
283 bytes_sender: bytes_sender.clone(),
284 stream: Dom::from_ref(&rooted_stream),
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),
291 control_sender: control_sender.unwrap(),
292 }));
293
294 let handler =
295 PromiseNativeHandler::new(cx, &global, promise_handler.take().map(|h| Box::new(h) as Box<_>), rejection_handler.take().map(|h| Box::new(h) as Box<_>));
296
297 let mut realm = enter_auto_realm(cx, &*global);
298 let realm = &mut realm.current_realm();
299 promise.append_native_handler(realm, &handler);
300 })
301 );
302 }
303}
304
305#[derive(Clone, JSTraceable, MallocSizeOf)]
308#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
309struct TransmitBodyPromiseHandler {
310 #[no_trace]
311 bytes_sender: IpcSender<BodyChunkResponse>,
312 stream: Dom<ReadableStream>,
313 #[no_trace]
314 control_sender: IpcSender<BodyChunkRequest>,
315}
316
317impl js::gc::Rootable for TransmitBodyPromiseHandler {}
318
319impl Callback for TransmitBodyPromiseHandler {
320 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
322 let is_done = match get_read_promise_done(cx, &v) {
323 Ok(is_done) => is_done,
324 Err(_) => {
325 let _ = self.control_sender.send(BodyChunkRequest::Done);
328 return self.stream.stop_reading(cx);
329 },
330 };
331
332 if is_done {
333 let _ = self.control_sender.send(BodyChunkRequest::Done);
336 return self.stream.stop_reading(cx);
337 }
338
339 let chunk = match get_read_promise_bytes(cx, &v) {
340 Ok(chunk) => chunk,
341 Err(_) => {
342 let _ = self.control_sender.send(BodyChunkRequest::Error);
344 return self.stream.stop_reading(cx);
345 },
346 };
347
348 let _ = self
352 .bytes_sender
353 .send(BodyChunkResponse::Chunk(GenericSharedMemory::from_vec(
354 chunk,
355 )));
356 }
357}
358
359#[derive(Clone, JSTraceable, MallocSizeOf)]
362#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
363struct TransmitBodyPromiseRejectionHandler {
364 #[no_trace]
365 bytes_sender: IpcSender<BodyChunkResponse>,
366 stream: Dom<ReadableStream>,
367 #[no_trace]
368 control_sender: IpcSender<BodyChunkRequest>,
369}
370
371impl js::gc::Rootable for TransmitBodyPromiseRejectionHandler {}
372
373impl Callback for TransmitBodyPromiseRejectionHandler {
374 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
376 let _ = self.control_sender.send(BodyChunkRequest::Error);
378 self.stream.stop_reading(cx);
379 }
380}
381
382pub(crate) struct ExtractedBody {
384 pub(crate) stream: DomRoot<ReadableStream>,
386 pub(crate) source: BodySource,
388 pub(crate) total_bytes: Option<usize>,
390 pub(crate) content_type: Option<DOMString>,
392}
393
394impl ExtractedBody {
395 pub(crate) fn into_net_request_body(self) -> (RequestBody, DomRoot<ReadableStream>) {
407 let ExtractedBody {
408 stream,
409 total_bytes,
410 content_type: _,
411 source,
412 } = self;
413
414 let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
417
418 let trusted_stream = Trusted::new(&*stream);
419
420 let global = stream.global();
421 let task_source = global.task_manager().networking_task_source();
422
423 let in_memory = stream.get_in_memory_bytes().or_else(|| {
426 if total_bytes == Some(0) {
427 Some(GenericSharedMemory::from_bytes(&[]))
428 } else {
429 None
430 }
431 });
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 cx: &mut js::context::JSContext,
490 global: &GlobalScope,
491 keep_alive: bool,
492 ) -> Fallible<ExtractedBody>;
493}
494
495fn stream_from_body_init_bytes(
497 cx: &mut js::context::JSContext,
498 global: &GlobalScope,
499 bytes: Vec<u8>,
500) -> Fallible<DomRoot<ReadableStream>> {
501 ReadableStream::new_from_bytes_with_byte_reading_support(cx, global, bytes)
506}
507
508impl Extractable for BodyInit {
509 fn extract(
511 &self,
512 cx: &mut js::context::JSContext,
513 global: &GlobalScope,
514 keep_alive: bool,
515 ) -> Fallible<ExtractedBody> {
516 match self {
517 BodyInit::String(s) => s.extract(cx, global, keep_alive),
518 BodyInit::URLSearchParams(usp) => usp.extract(cx, global, keep_alive),
519 BodyInit::Blob(b) => b.extract(cx, global, keep_alive),
520 BodyInit::FormData(formdata) => formdata.extract(cx, global, keep_alive),
521 BodyInit::ArrayBuffer(typedarray) => {
522 let bytes = typedarray.to_vec();
523 let total_bytes = bytes.len();
524 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
525 Ok(ExtractedBody {
526 stream,
527 total_bytes: Some(total_bytes),
528 content_type: None,
529 source: BodySource::Object,
530 })
531 },
532 BodyInit::ArrayBufferView(typedarray) => {
533 let bytes = typedarray.to_vec();
534 let total_bytes = bytes.len();
535 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
536 Ok(ExtractedBody {
537 stream,
538 total_bytes: Some(total_bytes),
539 content_type: None,
540 source: BodySource::Object,
541 })
542 },
543 BodyInit::ReadableStream(stream) => {
544 if keep_alive {
546 return Err(Error::Type(
547 c"The body's stream is for a keepalive request".to_owned(),
548 ));
549 }
550 if stream.is_locked() || stream.is_disturbed() {
552 return Err(Error::Type(
553 c"The body's stream is disturbed or locked".to_owned(),
554 ));
555 }
556
557 Ok(ExtractedBody {
558 stream: stream.clone(),
559 total_bytes: None,
560 content_type: None,
561 source: BodySource::Null,
562 })
563 },
564 }
565 }
566}
567
568impl Extractable for Vec<u8> {
569 fn extract(
570 &self,
571 cx: &mut js::context::JSContext,
572 global: &GlobalScope,
573 _keep_alive: bool,
574 ) -> Fallible<ExtractedBody> {
575 let bytes = self.clone();
576 let total_bytes = self.len();
577 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
578 Ok(ExtractedBody {
579 stream,
580 total_bytes: Some(total_bytes),
581 content_type: None,
582 source: BodySource::Object,
584 })
585 }
586}
587
588impl Extractable for Blob {
589 fn extract(
590 &self,
591 cx: &mut js::context::JSContext,
592 _global: &GlobalScope,
593 _keep_alive: bool,
594 ) -> Fallible<ExtractedBody> {
595 let blob_type = self.Type();
596 let content_type = if blob_type.is_empty() {
597 None
598 } else {
599 Some(blob_type)
600 };
601 let total_bytes = self.Size() as usize;
602 let stream = self.get_stream(cx)?;
603 Ok(ExtractedBody {
604 stream,
605 total_bytes: Some(total_bytes),
606 content_type,
607 source: BodySource::Object,
608 })
609 }
610}
611
612impl Extractable for DOMString {
613 fn extract(
614 &self,
615 cx: &mut js::context::JSContext,
616 global: &GlobalScope,
617 _keep_alive: bool,
618 ) -> Fallible<ExtractedBody> {
619 let bytes = self.as_bytes().to_owned();
620 let total_bytes = bytes.len();
621 let content_type = Some(DOMString::from("text/plain;charset=UTF-8"));
622 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
623 Ok(ExtractedBody {
624 stream,
625 total_bytes: Some(total_bytes),
626 content_type,
627 source: BodySource::Object,
628 })
629 }
630}
631
632impl Extractable for FormData {
633 fn extract(
634 &self,
635 cx: &mut js::context::JSContext,
636 global: &GlobalScope,
637 _keep_alive: bool,
638 ) -> Fallible<ExtractedBody> {
639 let boundary = generate_boundary();
640 let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
641 let total_bytes = bytes.len();
642 let content_type = Some(DOMString::from(format!(
643 "multipart/form-data; boundary={}",
644 boundary
645 )));
646 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
647 Ok(ExtractedBody {
648 stream,
649 total_bytes: Some(total_bytes),
650 content_type,
651 source: BodySource::Object,
652 })
653 }
654}
655
656impl Extractable for URLSearchParams {
657 fn extract(
658 &self,
659 cx: &mut js::context::JSContext,
660 global: &GlobalScope,
661 _keep_alive: bool,
662 ) -> Fallible<ExtractedBody> {
663 let bytes = self.serialize_utf8().into_bytes();
664 let total_bytes = bytes.len();
665 let content_type = Some(DOMString::from(
666 "application/x-www-form-urlencoded;charset=UTF-8",
667 ));
668 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
669 Ok(ExtractedBody {
670 stream,
671 total_bytes: Some(total_bytes),
672 content_type,
673 source: BodySource::Object,
674 })
675 }
676}
677
678#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
679pub(crate) enum BodyType {
680 Blob,
681 Bytes,
682 FormData,
683 Json,
684 Text,
685 ArrayBuffer,
686}
687
688pub(crate) enum FetchedData {
689 Text(String),
690 Json(RootedTraceableBox<Heap<JSValue>>),
691 BlobData(DomRoot<Blob>),
692 Bytes(RootedTraceableBox<Heap<*mut JSObject>>),
693 FormData(DomRoot<FormData>),
694 ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>),
695 JSException(RootedTraceableBox<Heap<JSVal>>),
696}
697
698pub(crate) fn consume_body<T: BodyMixin + DomObject>(
704 cx: &mut js::context::JSContext,
705 object: &T,
706 body_type: BodyType,
707) -> Rc<Promise> {
708 let global = object.global();
709
710 let mut realm = enter_auto_realm(cx, &*global);
712 let cx: &mut _ = &mut realm.current_realm();
713
714 let promise = Promise::new_in_realm(cx);
717
718 if object.is_unusable() {
720 promise.reject_error_with_cx(
721 cx,
722 Error::Type(c"The body's stream is disturbed or locked".to_owned()),
723 );
724 return promise;
725 }
726
727 let stream = match object.body() {
728 Some(stream) => stream,
729 None => {
730 let mime_type = object.get_mime_type(cx);
732 resolve_result_promise(cx, body_type, &promise, mime_type, Vec::with_capacity(0));
733 return promise;
734 },
735 };
736
737 if stream.is_errored() {
754 rooted!(&in(cx) let mut stored_error = UndefinedValue());
755 stream.get_stored_error(stored_error.handle_mut());
756 promise.reject_with_cx(cx, stored_error.handle());
757 return promise;
758 }
759
760 let reader = match stream.acquire_default_reader(cx) {
765 Ok(r) => r,
766 Err(e) => {
767 promise.reject_error_with_cx(cx, e);
768 return promise;
769 },
770 };
771
772 let error_promise = promise.clone();
774
775 let mime_type = object.get_mime_type(cx);
779 let success_promise = promise.clone();
780
781 reader.read_all_bytes(
786 cx,
787 Rc::new(move |cx, bytes: &[u8]| {
788 resolve_result_promise(
789 cx,
790 body_type,
791 &success_promise,
792 mime_type.clone(),
793 bytes.to_vec(),
794 );
795 }),
796 Rc::new(move |cx, v| {
797 error_promise.reject_with_cx(cx, v);
798 }),
799 );
800
801 promise
802}
803
804fn resolve_result_promise(
807 cx: &mut js::context::JSContext,
808 body_type: BodyType,
809 promise: &Promise,
810 mime_type: Vec<u8>,
811 body: Vec<u8>,
812) {
813 let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type);
814
815 match pkg_data_results {
816 Ok(results) => {
817 match results {
818 FetchedData::Text(s) => promise.resolve_native_with_cx(cx, &USVString(s)),
819 FetchedData::Json(j) => promise.resolve_native_with_cx(cx, &j),
820 FetchedData::BlobData(b) => promise.resolve_native_with_cx(cx, &b),
821 FetchedData::FormData(f) => promise.resolve_native_with_cx(cx, &f),
822 FetchedData::Bytes(b) => promise.resolve_native_with_cx(cx, &b),
823 FetchedData::ArrayBuffer(a) => promise.resolve_native_with_cx(cx, &a),
824 FetchedData::JSException(e) => {
825 promise.reject_native(&e.handle(), CanGc::from_cx(cx))
826 },
827 };
828 },
829 Err(err) => promise.reject_error_with_cx(cx, err),
830 }
831}
832
833fn run_package_data_algorithm(
837 cx: &mut js::context::JSContext,
838 bytes: Vec<u8>,
839 body_type: BodyType,
840 mime_type: Vec<u8>,
841) -> Fallible<FetchedData> {
842 let mime = &*mime_type;
843 let realm = CurrentRealm::assert(cx);
844 let global = GlobalScope::from_current_realm(&realm);
845 match body_type {
846 BodyType::Text => run_text_data_algorithm(bytes),
847 BodyType::Json => run_json_data_algorithm(cx, bytes),
848 BodyType::Blob => run_blob_data_algorithm(cx, &global, bytes, mime),
849 BodyType::FormData => run_form_data_algorithm(cx, &global, bytes, mime),
850 BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes),
851 BodyType::Bytes => run_bytes_data_algorithm(cx, bytes),
852 }
853}
854
855fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
857 let no_bom_bytes = if bytes.starts_with(b"\xEF\xBB\xBF") {
860 &bytes[3..]
861 } else {
862 &bytes
863 };
864 Ok(FetchedData::Text(
865 String::from_utf8_lossy(no_bom_bytes).into_owned(),
866 ))
867}
868
869#[expect(unsafe_code)]
870fn run_json_data_algorithm(
872 cx: &mut js::context::JSContext,
873 bytes: Vec<u8>,
874) -> Fallible<FetchedData> {
875 let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8);
880 rooted!(&in(cx) let mut rval = UndefinedValue());
881 unsafe {
882 if !JS_ParseJSON(
883 cx,
884 json_text.as_ptr(),
885 json_text.len() as u32,
886 rval.handle_mut(),
887 ) {
888 rooted!(&in(cx) let mut exception = UndefinedValue());
889 assert!(JS_GetPendingException(cx, exception.handle_mut()));
890 JS_ClearPendingException(cx);
891 return Ok(FetchedData::JSException(RootedTraceableBox::from_box(
892 Heap::boxed(exception.get()),
893 )));
894 }
895 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(rval.get()));
896 Ok(FetchedData::Json(rooted_heap))
897 }
898}
899
900fn run_blob_data_algorithm(
902 cx: &mut js::context::JSContext,
903 root: &GlobalScope,
904 bytes: Vec<u8>,
905 mime: &[u8],
906) -> Fallible<FetchedData> {
907 let mime_string = if let Ok(s) = String::from_utf8(mime.to_vec()) {
908 s
909 } else {
910 "".to_string()
911 };
912 let blob = Blob::new(
913 cx,
914 root,
915 BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
916 );
917 Ok(FetchedData::BlobData(blob))
918}
919
920fn extract_name_from_content_disposition(headers: &HeaderMap) -> Option<String> {
921 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
922
923 for part in cd.split(';').map(|s| s.trim()) {
924 if let Some(rest) = part.strip_prefix("name=") {
925 let v = rest.trim();
926 let v = v.strip_prefix('"').unwrap_or(v);
927 let v = v.strip_suffix('"').unwrap_or(v);
928 return Some(v.to_string());
929 }
930 }
931 None
932}
933
934fn extract_filename_from_content_disposition(headers: &HeaderMap) -> Option<String> {
935 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
936 if let Some(index) = cd.find("filename=") {
937 let start = index + "filename=".len();
938 return Some(
939 cd.get(start..)
940 .unwrap_or_default()
941 .trim_matches('"')
942 .to_owned(),
943 );
944 }
945 if let Some(index) = cd.find("filename*=UTF-8''") {
946 let start = index + "filename*=UTF-8''".len();
947 return Some(
948 cd.get(start..)
949 .unwrap_or_default()
950 .trim_matches('"')
951 .to_owned(),
952 );
953 }
954 None
955}
956
957fn content_type_from_headers(headers: &HeaderMap) -> Result<String, Error> {
958 match headers.get(CONTENT_TYPE) {
959 Some(value) => Ok(value
960 .to_str()
961 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?
962 .to_string()),
963 None => Ok("text/plain".to_string()),
964 }
965}
966
967fn append_form_data_entry_from_part(
968 cx: &mut js::context::JSContext,
969 root: &GlobalScope,
970 formdata: &FormData,
971 headers: &HeaderMap,
972 body: Vec<u8>,
973) -> Fallible<()> {
974 let Some(name) = extract_name_from_content_disposition(headers) else {
975 return Ok(());
976 };
977 let filename = extract_filename_from_content_disposition(headers);
979 if let Some(filename) = filename {
980 let content_type = content_type_from_headers(headers)?;
986 let file = File::new(
987 root,
988 BlobImpl::new_from_bytes(body, normalize_type_string(&content_type)),
989 DOMString::from(filename),
990 None,
991 CanGc::from_cx(cx),
992 );
993 let blob = file.upcast::<Blob>();
994 formdata.Append_(USVString(name), blob, None);
995 } else {
996 let (value, _) = UTF_8.decode_without_bom_handling(&body);
999 formdata.Append(USVString(name), USVString(value.to_string()));
1000 }
1001 Ok(())
1002}
1003
1004fn append_multipart_nodes(
1005 cx: &mut js::context::JSContext,
1006 root: &GlobalScope,
1007 formdata: &FormData,
1008 nodes: Vec<Node>,
1009) -> Fallible<()> {
1010 for node in nodes {
1011 match node {
1012 Node::Part(part) => {
1013 append_form_data_entry_from_part(cx, root, formdata, &part.headers, part.body)?;
1014 },
1015 Node::File(file_part) => {
1016 let body = fs::read(&file_part.path)
1017 .map_err(|_| Error::Type(c"file part could not be read".to_owned()))?;
1018 append_form_data_entry_from_part(cx, root, formdata, &file_part.headers, body)?;
1019 },
1020 Node::Multipart((_, inner)) => {
1021 append_multipart_nodes(cx, root, formdata, inner)?;
1022 },
1023 }
1024 }
1025 Ok(())
1026}
1027
1028fn run_form_data_algorithm(
1030 cx: &mut js::context::JSContext,
1031 root: &GlobalScope,
1032 bytes: Vec<u8>,
1033 mime: &[u8],
1034) -> Fallible<FetchedData> {
1035 let mime_str = str::from_utf8(mime).unwrap_or_default();
1038 let mime: Mime = mime_str
1039 .parse()
1040 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1041
1042 if mime.type_() == mime::MULTIPART && mime.subtype() == mime::FORM_DATA {
1046 let mut headers = HeaderMap::new();
1050 headers.insert(
1051 CONTENT_TYPE,
1052 mime_str
1053 .parse()
1054 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?,
1055 );
1056
1057 if let Some(boundary) = mime.get_param(mime::BOUNDARY) {
1058 let closing_boundary = format!("--{}--", boundary.as_str()).into_bytes();
1059 let trimmed_bytes = bytes.strip_suffix(b"\r\n").unwrap_or(&bytes);
1060 if trimmed_bytes == closing_boundary {
1061 let formdata = FormData::new(None, root, CanGc::from_cx(cx));
1062 return Ok(FetchedData::FormData(formdata));
1063 }
1064 }
1065
1066 let mut cursor = Cursor::new(bytes);
1067 let nodes = read_multipart_body(&mut cursor, &headers, false)
1069 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1070 let formdata = FormData::new(None, root, CanGc::from_cx(cx));
1075
1076 append_multipart_nodes(cx, root, &formdata, nodes)?;
1077
1078 return Ok(FetchedData::FormData(formdata));
1079 }
1080
1081 if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED {
1082 let entries = form_urlencoded::parse(&bytes);
1087 let formdata = FormData::new(None, root, CanGc::from_cx(cx));
1088 for (k, e) in entries {
1089 formdata.Append(USVString(k.into_owned()), USVString(e.into_owned()));
1090 }
1091 return Ok(FetchedData::FormData(formdata));
1092 }
1093
1094 Err(Error::Type(c"Inappropriate MIME-type for Body".to_owned()))
1096}
1097
1098fn run_bytes_data_algorithm(
1100 cx: &mut js::context::JSContext,
1101 bytes: Vec<u8>,
1102) -> Fallible<FetchedData> {
1103 rooted!(&in(cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1104
1105 create_buffer_source::<Uint8>(
1106 cx.into(),
1107 &bytes,
1108 array_buffer_ptr.handle_mut(),
1109 CanGc::from_cx(cx),
1110 )
1111 .map_err(|_| Error::JSFailed)?;
1112
1113 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1114 Ok(FetchedData::Bytes(rooted_heap))
1115}
1116
1117pub(crate) fn run_array_buffer_data_algorithm(
1119 cx: &mut js::context::JSContext,
1120 bytes: Vec<u8>,
1121) -> Fallible<FetchedData> {
1122 rooted!(&in(cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1123
1124 create_buffer_source::<ArrayBufferU8>(
1125 cx.into(),
1126 &bytes,
1127 array_buffer_ptr.handle_mut(),
1128 CanGc::from_cx(cx),
1129 )
1130 .map_err(|_| Error::JSFailed)?;
1131
1132 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1133 Ok(FetchedData::ArrayBuffer(rooted_heap))
1134}
1135
1136#[expect(unsafe_code)]
1137pub(crate) fn decode_to_utf16_with_bom_removal(
1138 bytes: &[u8],
1139 encoding: &'static Encoding,
1140) -> Vec<u16> {
1141 let mut decoder = encoding.new_decoder_with_bom_removal();
1142 let capacity = decoder
1143 .max_utf16_buffer_length(bytes.len())
1144 .expect("Overflow");
1145 let mut utf16 = Vec::with_capacity(capacity);
1146 let extra = unsafe { slice::from_raw_parts_mut(utf16.as_mut_ptr(), capacity) };
1147 let (_, read, written, _) = decoder.decode_to_utf16(bytes, extra, true);
1148 assert_eq!(read, bytes.len());
1149 unsafe { utf16.set_len(written) }
1150 utf16
1151}
1152
1153pub(crate) trait BodyMixin {
1155 fn is_body_used(&self) -> bool;
1157 fn is_unusable(&self) -> bool;
1159 fn body(&self) -> Option<DomRoot<ReadableStream>>;
1161 fn get_mime_type(&self, cx: &mut js::context::JSContext) -> Vec<u8>;
1163}