1use std::io::Cursor;
6use std::rc::Rc;
7use std::{fs, ptr, 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, Debug, PartialEq)]
86pub(crate) enum BodySource {
87 Null,
89 Object,
93}
94
95#[derive(Clone)]
97enum SendableBody {
98 InMemory {
99 data: GenericSharedMemory,
100 done: bool,
101 },
102 Stream {
103 stream: Trusted<ReadableStream>,
104 task_source: SendableTaskSource,
105 source: BodySource,
106 },
107}
108
109#[derive(Clone)]
115struct TransmitBodyConnectHandler {
116 body: SendableBody,
117 bytes_sender: Option<IpcSender<BodyChunkResponse>>,
118}
119
120impl TransmitBodyConnectHandler {
121 fn new(body: SendableBody) -> TransmitBodyConnectHandler {
122 TransmitBodyConnectHandler {
123 body,
124 bytes_sender: None,
125 }
126 }
127
128 fn reset_in_memory_done(&mut self) {
131 if let SendableBody::InMemory { done, .. } = &mut self.body {
132 *done = false;
133 }
134 }
135
136 fn re_extract(&mut self, chunk_request_receiver: IpcReceiver<BodyChunkRequest>) {
139 let mut body_handler = self.clone();
140 body_handler.reset_in_memory_done();
141
142 ROUTER.add_typed_route(
143 chunk_request_receiver,
144 Box::new(move |message| {
145 let request = message.unwrap();
146 match request {
147 BodyChunkRequest::Connect(sender) => {
148 body_handler.start_reading(sender);
149 },
150 BodyChunkRequest::Extract(receiver) => {
151 body_handler.re_extract(receiver);
152 },
153 BodyChunkRequest::Chunk => body_handler.transmit_source(),
154 }
155 }),
156 );
157 }
158
159 fn transmit_source(&mut self) {
166 match &mut self.body {
167 SendableBody::InMemory { done: true, .. } => {
168 self.stop_reading();
170 },
171 SendableBody::InMemory { data, done } => {
172 *done = true;
174 let _ = self
175 .bytes_sender
176 .as_ref()
177 .expect("No bytes sender to transmit source.")
178 .send(BodyChunkResponse::Chunk(data.clone()));
179 },
180 SendableBody::Stream { source, .. } => {
181 assert_ne!(
182 *source,
183 BodySource::Null,
184 "ReadableStream(Null) sources should not re-direct."
185 );
186 warn!("Re-directs for file-based Blobs not supported yet.");
187 },
188 }
189 }
190
191 fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
194 self.bytes_sender = Some(sender);
195
196 if let SendableBody::Stream {
198 source: BodySource::Null,
199 stream,
200 task_source,
201 } = &self.body
202 {
203 let stream = stream.clone();
204 task_source.queue(task!(start_reading_request_body_stream: move |cx| {
205 let rooted_stream = stream.root();
207
208 rooted_stream.acquire_default_reader(cx)
212 .expect("Couldn't acquire a reader for the body stream.");
213
214 }));
216 }
217 }
218
219 fn stop_reading(&mut self) {
221 let bytes_sender = self
222 .bytes_sender
223 .take()
224 .expect("Stop reading called multiple times on TransmitBodyConnectHandler.");
225 let _ = bytes_sender.send(BodyChunkResponse::Done);
226 }
227
228 fn transmit_body_chunk(&mut self) {
230 let bytes_sender = self
231 .bytes_sender
232 .clone()
233 .expect("No bytes sender to transmit chunk.");
234
235 match &mut self.body {
236 SendableBody::InMemory { done: true, .. } => {
237 self.stop_reading();
239 },
240 SendableBody::InMemory { data, done } => {
241 let _ = bytes_sender.send(BodyChunkResponse::Chunk(data.clone()));
244 *done = true;
247 },
248 SendableBody::Stream {
249 stream,
250 task_source,
251 ..
252 } => {
253 let stream = stream.clone();
254 task_source.queue(task!(setup_native_body_promise_handler: move |cx| {
255 let rooted_stream = stream.root();
256 let global = rooted_stream.global();
257
258 let promise = rooted_stream.read_a_chunk(cx);
260
261 rooted!(&in(cx) let mut promise_handler = Some(TransmitBodyPromiseHandler {
265 bytes_sender: bytes_sender.clone(),
266 stream: Dom::from_ref(&rooted_stream),
267 }));
268
269 rooted!(&in(cx) let mut rejection_handler =
270 Some(TransmitBodyPromiseRejectionHandler {
271 bytes_sender,
272 stream: Dom::from_ref(&rooted_stream),
273 })
274 );
275
276 let handler = PromiseNativeHandler::new(
277 cx,
278 &global,
279 promise_handler.take().map(|handler| Box::new(handler) as Box<_>),
280 rejection_handler.take().map(|handler| Box::new(handler) as Box<_>),
281 );
282
283 let mut realm = enter_auto_realm(cx, &*global);
284 let realm = &mut realm.current_realm();
285 promise.append_native_handler(realm, &handler);
286 }));
287 },
288 }
289 }
290}
291
292#[derive(Clone, JSTraceable, MallocSizeOf)]
295#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
296struct TransmitBodyPromiseHandler {
297 #[no_trace]
298 bytes_sender: IpcSender<BodyChunkResponse>,
299 stream: Dom<ReadableStream>,
300}
301
302impl js::gc::Rootable for TransmitBodyPromiseHandler {}
303
304impl Callback for TransmitBodyPromiseHandler {
305 fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
307 let is_done = match get_read_promise_done(cx, &v) {
308 Ok(is_done) => is_done,
309 Err(_) => {
310 let _ = self.bytes_sender.send(BodyChunkResponse::Done);
313 return self.stream.stop_reading(cx);
314 },
315 };
316
317 if is_done {
318 let _ = self.bytes_sender.send(BodyChunkResponse::Done);
321 return self.stream.stop_reading(cx);
322 }
323
324 let chunk = match get_read_promise_bytes(cx, &v) {
325 Ok(chunk) => chunk,
326 Err(_) => {
327 let _ = self.bytes_sender.send(BodyChunkResponse::Error);
329 return self.stream.stop_reading(cx);
330 },
331 };
332
333 let _ = self
337 .bytes_sender
338 .send(BodyChunkResponse::Chunk(GenericSharedMemory::from_vec(
339 chunk,
340 )));
341 }
342}
343
344#[derive(Clone, JSTraceable, MallocSizeOf)]
347#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
348struct TransmitBodyPromiseRejectionHandler {
349 #[no_trace]
350 bytes_sender: IpcSender<BodyChunkResponse>,
351 stream: Dom<ReadableStream>,
352}
353
354impl js::gc::Rootable for TransmitBodyPromiseRejectionHandler {}
355
356impl Callback for TransmitBodyPromiseRejectionHandler {
357 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
359 let _ = self.bytes_sender.send(BodyChunkResponse::Error);
361 self.stream.stop_reading(cx);
362 }
363}
364
365pub(crate) struct ExtractedBody {
367 pub(crate) stream: DomRoot<ReadableStream>,
369 pub(crate) source: BodySource,
371 pub(crate) total_bytes: Option<usize>,
373 pub(crate) content_type: Option<DOMString>,
375}
376
377impl ExtractedBody {
378 pub(crate) fn into_net_request_body(
390 self,
391 cx: &mut JSContext,
392 ) -> (RequestBody, DomRoot<ReadableStream>) {
393 let ExtractedBody {
394 stream,
395 total_bytes,
396 content_type: _,
397 source,
398 } = self;
399
400 let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
403
404 let in_memory = stream.get_in_memory_bytes(cx).or_else(|| {
407 if total_bytes == Some(0) {
408 Some(GenericSharedMemory::from_bytes(&[]))
409 } else {
410 None
411 }
412 });
413
414 let net_source = match source {
415 BodySource::Null => NetBodySource::Null,
416 BodySource::Object => NetBodySource::Object,
417 };
418
419 let body = match in_memory {
420 Some(data) => SendableBody::InMemory { data, done: false },
421 None => {
422 let global = stream.global();
423 let task_manager = global.task_manager();
424 let task_source = task_manager.networking_task_source().into();
425 SendableBody::Stream {
426 stream: Trusted::new(&*stream),
427 task_source,
428 source,
429 }
430 },
431 };
432
433 let mut body_handler = TransmitBodyConnectHandler::new(body);
434 ROUTER.add_typed_route(
435 chunk_request_receiver,
436 Box::new(move |message| match message.unwrap() {
437 BodyChunkRequest::Connect(sender) => {
438 body_handler.start_reading(sender);
439 },
440 BodyChunkRequest::Extract(receiver) => {
441 body_handler.re_extract(receiver);
442 },
443 BodyChunkRequest::Chunk => body_handler.transmit_body_chunk(),
444 }),
445 );
446
447 let request_body = RequestBody::new(chunk_request_sender, net_source, total_bytes);
450
451 (request_body, stream)
453 }
454
455 pub(crate) fn in_memory(&self) -> bool {
457 self.stream.in_memory()
458 }
459}
460
461pub(crate) trait Extractable {
463 fn extract(
464 &self,
465 cx: &mut js::context::JSContext,
466 global: &GlobalScope,
467 keep_alive: bool,
468 ) -> Fallible<ExtractedBody>;
469}
470
471fn stream_from_body_init_bytes(
473 cx: &mut js::context::JSContext,
474 global: &GlobalScope,
475 bytes: Vec<u8>,
476) -> Fallible<DomRoot<ReadableStream>> {
477 ReadableStream::new_from_bytes_with_byte_reading_support(cx, global, bytes)
482}
483
484impl Extractable for BodyInit {
485 fn extract(
487 &self,
488 cx: &mut js::context::JSContext,
489 global: &GlobalScope,
490 keep_alive: bool,
491 ) -> Fallible<ExtractedBody> {
492 match self {
493 BodyInit::String(s) => s.extract(cx, global, keep_alive),
494 BodyInit::URLSearchParams(usp) => usp.extract(cx, global, keep_alive),
495 BodyInit::Blob(b) => b.extract(cx, global, keep_alive),
496 BodyInit::FormData(formdata) => formdata.extract(cx, global, keep_alive),
497 BodyInit::ArrayBuffer(typedarray) => {
498 let bytes = get_buffer_source_copy(typedarray.into());
500 let total_bytes = bytes.len();
501 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
502 Ok(ExtractedBody {
503 stream,
504 total_bytes: Some(total_bytes),
505 content_type: None,
506 source: BodySource::Object,
507 })
508 },
509 BodyInit::ArrayBufferView(typedarray) => {
510 let bytes = get_buffer_source_copy(typedarray.into());
512 let total_bytes = bytes.len();
513 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
514 Ok(ExtractedBody {
515 stream,
516 total_bytes: Some(total_bytes),
517 content_type: None,
518 source: BodySource::Object,
519 })
520 },
521 BodyInit::ReadableStream(stream) => {
522 if keep_alive {
524 return Err(Error::Type(
525 c"The body's stream is for a keepalive request".to_owned(),
526 ));
527 }
528 if stream.is_locked() || stream.is_disturbed() {
530 return Err(Error::Type(
531 c"The body's stream is disturbed or locked".to_owned(),
532 ));
533 }
534
535 Ok(ExtractedBody {
536 stream: stream.clone(),
537 total_bytes: None,
538 content_type: None,
539 source: BodySource::Null,
540 })
541 },
542 }
543 }
544}
545
546impl Extractable for Vec<u8> {
547 fn extract(
548 &self,
549 cx: &mut js::context::JSContext,
550 global: &GlobalScope,
551 _keep_alive: bool,
552 ) -> Fallible<ExtractedBody> {
553 let bytes = self.clone();
554 let total_bytes = self.len();
555 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
556 Ok(ExtractedBody {
557 stream,
558 total_bytes: Some(total_bytes),
559 content_type: None,
560 source: BodySource::Object,
562 })
563 }
564}
565
566impl Extractable for Blob {
567 fn extract(
568 &self,
569 cx: &mut js::context::JSContext,
570 _global: &GlobalScope,
571 _keep_alive: bool,
572 ) -> Fallible<ExtractedBody> {
573 let blob_type = self.Type();
574 let content_type = if blob_type.is_empty() {
575 None
576 } else {
577 Some(blob_type)
578 };
579 let total_bytes = self.Size() as usize;
580 let stream = self.get_stream(cx)?;
581 Ok(ExtractedBody {
582 stream,
583 total_bytes: Some(total_bytes),
584 content_type,
585 source: BodySource::Object,
586 })
587 }
588}
589
590impl Extractable for DOMString {
591 fn extract(
592 &self,
593 cx: &mut js::context::JSContext,
594 global: &GlobalScope,
595 _keep_alive: bool,
596 ) -> Fallible<ExtractedBody> {
597 let bytes = self.as_bytes(cx.no_gc()).to_owned();
598 let total_bytes = bytes.len();
599 let content_type = Some(DOMString::from_static("text/plain;charset=UTF-8"));
600 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
601 Ok(ExtractedBody {
602 stream,
603 total_bytes: Some(total_bytes),
604 content_type,
605 source: BodySource::Object,
606 })
607 }
608}
609
610impl Extractable for FormData {
611 fn extract(
612 &self,
613 cx: &mut js::context::JSContext,
614 global: &GlobalScope,
615 _keep_alive: bool,
616 ) -> Fallible<ExtractedBody> {
617 let boundary = generate_boundary();
618 let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
619 let total_bytes = bytes.len();
620 let content_type = Some(DOMString::from(format!(
621 "multipart/form-data; boundary={}",
622 boundary
623 )));
624 let stream = stream_from_body_init_bytes(cx, global, bytes)?;
625 Ok(ExtractedBody {
626 stream,
627 total_bytes: Some(total_bytes),
628 content_type,
629 source: BodySource::Object,
630 })
631 }
632}
633
634impl Extractable for URLSearchParams {
635 fn extract(
636 &self,
637 cx: &mut js::context::JSContext,
638 global: &GlobalScope,
639 _keep_alive: bool,
640 ) -> Fallible<ExtractedBody> {
641 let bytes = self.serialize_utf8().into_bytes();
642 let total_bytes = bytes.len();
643 let content_type = Some(DOMString::from_static(
644 "application/x-www-form-urlencoded;charset=UTF-8",
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
656#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
657pub(crate) enum BodyType {
658 Blob,
659 Bytes,
660 FormData,
661 Json,
662 Text,
663 ArrayBuffer,
664}
665
666pub(crate) enum FetchedData {
667 Text(String),
668 Json(RootedTraceableBox<Heap<JSValue>>),
669 BlobData(DomRoot<Blob>),
670 Bytes(RootedTraceableBox<Heap<*mut JSObject>>),
671 FormData(DomRoot<FormData>),
672 ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>),
673 JSException(RootedTraceableBox<Heap<JSVal>>),
674}
675
676pub(crate) fn consume_body<T: BodyMixin + DomObject>(
682 cx: &mut js::context::JSContext,
683 object: &T,
684 body_type: BodyType,
685) -> RootedPromise {
686 let global = object.global();
687
688 let mut realm = enter_auto_realm(cx, &*global);
690 let cx: &mut _ = &mut realm.current_realm();
691
692 let promise = Promise::new_in_realm(cx);
695
696 if object.is_unusable() {
698 promise.reject_error(
699 cx,
700 Error::Type(c"The body's stream is disturbed or locked".to_owned()),
701 );
702 return promise;
703 }
704
705 let stream = match object.body() {
706 Some(stream) => stream,
707 None => {
708 let mime_type = object.get_mime_type(cx);
710 resolve_result_promise(cx, body_type, &promise, mime_type, &[]);
711 return promise;
712 },
713 };
714
715 if stream.is_errored() {
732 rooted!(&in(cx) let mut stored_error = UndefinedValue());
733 stream.get_stored_error(stored_error.handle_mut());
734 promise.reject(cx, stored_error.handle());
735 return promise;
736 }
737
738 let reader = match stream.acquire_default_reader(cx) {
743 Ok(r) => r,
744 Err(e) => {
745 promise.reject_error(cx, e);
746 return promise;
747 },
748 };
749
750 let error_promise = promise.clone();
752
753 let mime_type = object.get_mime_type(cx);
757 let success_promise = promise.clone();
758
759 reader.read_all_bytes(
764 cx,
765 Rc::new(move |cx, bytes| {
766 resolve_result_promise(cx, body_type, &success_promise, mime_type.clone(), &bytes);
767 }),
768 Rc::new(move |cx, v| {
769 error_promise.reject(cx, v);
770 }),
771 );
772
773 promise
774}
775
776fn resolve_result_promise(
779 cx: &mut js::context::JSContext,
780 body_type: BodyType,
781 promise: &Promise,
782 mime_type: Vec<u8>,
783 body: &[u8],
784) {
785 let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type);
786
787 match pkg_data_results {
788 Ok(results) => {
789 match results {
790 FetchedData::Text(s) => promise.resolve_native(cx, &USVString(s)),
791 FetchedData::Json(j) => promise.resolve_native(cx, &j),
792 FetchedData::BlobData(b) => promise.resolve_native(cx, &b),
793 FetchedData::FormData(f) => promise.resolve_native(cx, &f),
794 FetchedData::Bytes(b) => promise.resolve_native(cx, &b),
795 FetchedData::ArrayBuffer(a) => promise.resolve_native(cx, &a),
796 FetchedData::JSException(e) => promise.reject_native(cx, &e.handle()),
797 };
798 },
799 Err(err) => promise.reject_error(cx, err),
800 }
801}
802
803fn run_package_data_algorithm(
807 cx: &mut js::context::JSContext,
808 bytes: &[u8],
809 body_type: BodyType,
810 mime_type: Vec<u8>,
811) -> Fallible<FetchedData> {
812 let mime = &*mime_type;
813 let mut realm = CurrentRealm::assert(cx);
814 let global = GlobalScope::from_current_realm(&mut realm);
815 match body_type {
816 BodyType::Text => run_text_data_algorithm(bytes),
817 BodyType::Json => run_json_data_algorithm(cx, bytes),
818 BodyType::Blob => run_blob_data_algorithm(cx, &global, bytes.to_vec(), mime),
819 BodyType::FormData => run_form_data_algorithm(cx, &global, bytes, mime),
820 BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes),
821 BodyType::Bytes => run_bytes_data_algorithm(cx, bytes),
822 }
823}
824
825fn run_text_data_algorithm(bytes: &[u8]) -> Fallible<FetchedData> {
827 let no_bom_bytes = if bytes.starts_with(b"\xEF\xBB\xBF") {
830 &bytes[3..]
831 } else {
832 bytes
833 };
834 Ok(FetchedData::Text(
835 String::from_utf8_lossy(no_bom_bytes).into_owned(),
836 ))
837}
838
839#[expect(unsafe_code)]
840fn run_json_data_algorithm(cx: &mut js::context::JSContext, bytes: &[u8]) -> Fallible<FetchedData> {
842 let json_text = decode_to_utf16_with_bom_removal(bytes, UTF_8);
847 rooted!(&in(cx) let mut rval = UndefinedValue());
848 unsafe {
849 if !JS_ParseJSON(
850 cx,
851 json_text.as_ptr(),
852 json_text.len() as u32,
853 rval.handle_mut(),
854 ) {
855 rooted!(&in(cx) let mut exception = UndefinedValue());
856 assert!(JS_GetPendingException(cx, exception.handle_mut()));
857 JS_ClearPendingException(cx);
858 return Ok(FetchedData::JSException(RootedTraceableBox::from_box(
859 Heap::boxed(exception.get()),
860 )));
861 }
862 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(rval.get()));
863 Ok(FetchedData::Json(rooted_heap))
864 }
865}
866
867fn run_blob_data_algorithm(
869 cx: &mut js::context::JSContext,
870 root: &GlobalScope,
871 bytes: Vec<u8>,
872 mime: &[u8],
873) -> Fallible<FetchedData> {
874 let mime_string = String::from_utf8(mime.to_vec()).unwrap_or_default();
875 let blob = Blob::new(
876 cx,
877 root,
878 BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
879 );
880 Ok(FetchedData::BlobData(blob))
881}
882
883fn extract_name_from_content_disposition(headers: &HeaderMap) -> Option<String> {
884 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
885
886 for part in cd.split(';').map(|s| s.trim()) {
887 if let Some(rest) = part.strip_prefix("name=") {
888 let v = rest.trim();
889 let v = v.strip_prefix('"').unwrap_or(v);
890 let v = v.strip_suffix('"').unwrap_or(v);
891 return Some(v.to_string());
892 }
893 }
894 None
895}
896
897fn extract_filename_from_content_disposition(headers: &HeaderMap) -> Option<String> {
898 let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
899 if let Some(index) = cd.find("filename=") {
900 let start = index + "filename=".len();
901 return Some(
902 cd.get(start..)
903 .unwrap_or_default()
904 .trim_matches('"')
905 .to_owned(),
906 );
907 }
908 if let Some(index) = cd.find("filename*=UTF-8''") {
909 let start = index + "filename*=UTF-8''".len();
910 return Some(
911 cd.get(start..)
912 .unwrap_or_default()
913 .trim_matches('"')
914 .to_owned(),
915 );
916 }
917 None
918}
919
920fn content_type_from_headers(headers: &HeaderMap) -> Result<String, Error> {
921 match headers.get(CONTENT_TYPE) {
922 Some(value) => Ok(value
923 .to_str()
924 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?
925 .to_string()),
926 None => Ok("text/plain".to_string()),
927 }
928}
929
930fn append_form_data_entry_from_part(
931 cx: &mut js::context::JSContext,
932 root: &GlobalScope,
933 formdata: &FormData,
934 headers: &HeaderMap,
935 body: Vec<u8>,
936) -> Fallible<()> {
937 let Some(name) = extract_name_from_content_disposition(headers) else {
938 return Ok(());
939 };
940 let filename = extract_filename_from_content_disposition(headers);
942 if let Some(filename) = filename {
943 let content_type = content_type_from_headers(headers)?;
949 let file = File::new(
950 cx,
951 root,
952 BlobImpl::new_from_bytes(body, normalize_type_string(&content_type)),
953 DOMString::from(filename),
954 None,
955 );
956 let blob = file.upcast::<Blob>();
957 formdata.Append_(cx, USVString(name), blob, None);
958 } else {
959 let (value, _) = UTF_8.decode_without_bom_handling(&body);
962 formdata.Append(cx, USVString(name), USVString(value.to_string()));
963 }
964 Ok(())
965}
966
967fn append_multipart_nodes(
968 cx: &mut js::context::JSContext,
969 root: &GlobalScope,
970 formdata: &FormData,
971 nodes: Vec<Node>,
972) -> Fallible<()> {
973 for node in nodes {
974 match node {
975 Node::Part(part) => {
976 append_form_data_entry_from_part(cx, root, formdata, &part.headers, part.body)?;
977 },
978 Node::File(file_part) => {
979 let body = fs::read(&file_part.path)
980 .map_err(|_| Error::Type(c"file part could not be read".to_owned()))?;
981 append_form_data_entry_from_part(cx, root, formdata, &file_part.headers, body)?;
982 },
983 Node::Multipart((_, inner)) => {
984 append_multipart_nodes(cx, root, formdata, inner)?;
985 },
986 }
987 }
988 Ok(())
989}
990
991fn run_form_data_algorithm(
993 cx: &mut js::context::JSContext,
994 root: &GlobalScope,
995 bytes: &[u8],
996 mime: &[u8],
997) -> Fallible<FetchedData> {
998 let mime_str = str::from_utf8(mime).unwrap_or_default();
1001 let mime: Mime = mime_str
1002 .parse()
1003 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1004
1005 if mime.type_() == mime::MULTIPART && mime.subtype() == mime::FORM_DATA {
1009 let mut headers = HeaderMap::new();
1013 headers.insert(
1014 CONTENT_TYPE,
1015 mime_str
1016 .parse()
1017 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?,
1018 );
1019
1020 if let Some(boundary) = mime.get_param(mime::BOUNDARY) {
1021 let closing_boundary = format!("--{}--", boundary.as_str()).into_bytes();
1022 let trimmed_bytes = bytes.strip_suffix(b"\r\n").unwrap_or(bytes);
1023 if trimmed_bytes == closing_boundary {
1024 let formdata = FormData::new(cx, None, root);
1025 return Ok(FetchedData::FormData(formdata));
1026 }
1027 }
1028
1029 let mut cursor = Cursor::new(bytes);
1030 let nodes = read_multipart_body(&mut cursor, &headers, false)
1032 .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1033 let formdata = FormData::new(cx, None, root);
1038
1039 append_multipart_nodes(cx, root, &formdata, nodes)?;
1040
1041 return Ok(FetchedData::FormData(formdata));
1042 }
1043
1044 if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED {
1045 let entries = form_urlencoded::parse(bytes);
1050 let formdata = FormData::new(cx, None, root);
1051 for (k, e) in entries {
1052 formdata.Append(cx, USVString(k.into_owned()), USVString(e.into_owned()));
1053 }
1054 return Ok(FetchedData::FormData(formdata));
1055 }
1056
1057 Err(Error::Type(c"Inappropriate MIME-type for Body".to_owned()))
1059}
1060
1061fn run_bytes_data_algorithm(
1063 cx: &mut js::context::JSContext,
1064 bytes: &[u8],
1065) -> Fallible<FetchedData> {
1066 rooted!(&in(cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1067
1068 create_buffer_source::<Uint8>(cx, bytes, array_buffer_ptr.handle_mut())
1069 .map_err(|_| Error::JSFailed)?;
1070
1071 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1072 Ok(FetchedData::Bytes(rooted_heap))
1073}
1074
1075pub(crate) fn run_array_buffer_data_algorithm(
1077 cx: &mut js::context::JSContext,
1078 bytes: &[u8],
1079) -> Fallible<FetchedData> {
1080 rooted!(&in(cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1081
1082 create_buffer_source::<ArrayBufferU8>(cx, bytes, array_buffer_ptr.handle_mut())
1083 .map_err(|_| Error::JSFailed)?;
1084
1085 let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1086 Ok(FetchedData::ArrayBuffer(rooted_heap))
1087}
1088
1089pub(crate) fn decode_to_utf16_with_bom_removal(
1090 bytes: &[u8],
1091 encoding: &'static Encoding,
1092) -> Vec<u16> {
1093 let mut decoder = encoding.new_decoder_with_bom_removal();
1094 let capacity = decoder
1095 .max_utf16_buffer_length(bytes.len())
1096 .expect("Overflow");
1097 let mut utf16 = vec![0u16; capacity];
1098 let (_, read, written, _) = decoder.decode_to_utf16(bytes, &mut utf16, true);
1099 assert_eq!(read, bytes.len());
1100 utf16.truncate(written);
1101 utf16
1102}
1103
1104pub(crate) trait BodyMixin {
1106 fn is_body_used(&self) -> bool;
1108 fn is_unusable(&self) -> bool;
1110 fn body(&self) -> Option<DomRoot<ReadableStream>>;
1112 fn get_mime_type(&self, cx: &mut js::context::JSContext) -> Vec<u8>;
1114}
1115
1116pub(crate) fn body_text_stream<T: BodyMixin + DomObject>(
1118 cx: &mut js::context::JSContext,
1119 object: &T,
1120) -> Fallible<DomRoot<ReadableStream>> {
1121 if object.is_unusable() {
1123 return Err(Error::Type(
1124 c"The body's stream is disturbed or locked".to_owned(),
1125 ));
1126 }
1127
1128 let Some(stream) = object.body() else {
1130 return ReadableStream::new_empty(cx, &object.global());
1133 };
1134
1135 let decoder =
1138 TextDecoderStream::new_with_proto(cx, &object.global(), None, UTF_8, false, false)?;
1139
1140 Ok(pipe_through(&stream, cx, &object.global(), &decoder))
1142}