Skip to main content

script/
body.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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
57/// <https://fetch.spec.whatwg.org/#concept-body-clone>
58pub(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    // To clone a body *body*, run these steps:
64
65    let Some(stream) = original_body_stream.get() else {
66        return Ok(());
67    };
68
69    // step 1. Let « out1, out2 » be the result of teeing body’s stream.
70    let branches = stream.tee(cx, true)?;
71    let out1 = &*branches[0];
72    let out2 = &*branches[1];
73
74    // step 2. Set body’s stream to out1.
75    // step 3. Return a body whose stream is out2 and other members are copied from body.
76    original_body_stream.set(Some(out1));
77    cloned_body_stream.set(Some(out2));
78
79    Ok(())
80}
81
82/// The Dom object, or ReadableStream, that is the source of a body.
83/// <https://fetch.spec.whatwg.org/#concept-body-source>
84#[derive(Clone, PartialEq)]
85pub(crate) enum BodySource {
86    /// A ReadableStream comes with a null-source.
87    Null,
88    /// Another Dom object as source,
89    /// TODO: store the actual object
90    /// and re-extract a stream on re-direct.
91    Object,
92}
93
94/// The reason to stop reading from the body.
95enum StopReading {
96    /// The stream has errored.
97    Error,
98    /// The stream is done.
99    Done,
100}
101
102/// The IPC route handler
103/// for <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
104/// This route runs in the script process,
105/// and will queue tasks to perform operations
106/// on the stream and transmit body chunks over IPC.
107#[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    /// Reset `in_memory_done`, called when a stream is
138    /// re-extracted from the source to support a re-direct.
139    pub(crate) fn reset_in_memory_done(&mut self) {
140        self.in_memory_done = false;
141    }
142
143    /// Re-extract the source to support streaming it again for a re-direct.
144    /// TODO: actually re-extract the source, instead of just cloning data, to support Blob.
145    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                    // Note: this is actually sent from this process
162                    // by the TransmitBodyPromiseHandler when reading stops.
163                    BodyChunkRequest::Done => {
164                        body_handler.stop_reading(StopReading::Done);
165                    },
166                    // Note: this is actually sent from this process
167                    // by the TransmitBodyPromiseHandler when the stream errors.
168                    BodyChunkRequest::Error => {
169                        body_handler.stop_reading(StopReading::Error);
170                    },
171                }
172            }),
173        );
174    }
175
176    /// In case of re-direct, and of a source available in memory,
177    /// send it all in one chunk.
178    ///
179    /// TODO: this method should be deprecated
180    /// in favor of making `re_extract` actually re-extract a stream from the source.
181    /// See #26686
182    fn transmit_source(&mut self) {
183        if self.in_memory_done {
184            // Step 5.1.3
185            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            // The memoized bytes are sent so we mark it as done again
195            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    /// Take the IPC sender sent by `net`, so we can send body chunks with it.
207    /// Also the entry point to <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
208    fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
209        self.bytes_sender = Some(sender);
210
211        // If we're using an actual ReadableStream, acquire a reader for it.
212        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                    // Step 1, Let body be request’s body.
217                    let rooted_stream = stream.root();
218
219                    // TODO: Step 2, If body is null.
220
221                    // Step 3, get a reader for stream.
222                    rooted_stream.acquire_default_reader(cx)
223                        .expect("Couldn't acquire a reader for the body stream.");
224
225                    // Note: this algorithm continues when the first chunk is requested by `net`.
226                }));
227        }
228    }
229
230    /// Drop the IPC sender sent by `net`
231    /// It is important to drop the control_sender as this will allow us to clean ourselves up.
232    /// Otherwise, the following cycle will happen: The control sender is owned by us which keeps the control receiver
233    /// alive in the router which keeps us alive.
234    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    /// Step 4 and following of <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
251    fn transmit_body_chunk(&mut self) {
252        if self.in_memory_done {
253            // Step 5.1.3
254            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        // In case of the data being in-memory, send everything in one chunk, by-passing SpiderMonkey.
266        if let Some(bytes) = self.in_memory.clone() {
267            let _ = bytes_sender.send(BodyChunkResponse::Chunk(bytes));
268            // Mark this body as `done` so that we can stop reading in the next tick,
269            // matching the behavior of the promise-based flow
270            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                // Step 4, the result of reading a chunk from body’s stream with reader.
280                let promise = rooted_stream.read_a_chunk(cx);
281
282                // Step 5, the parallel steps waiting for and handling the result of the read promise,
283                // are a combination of the promise native handler here,
284                // and the corresponding IPC route in `component::net::http_loader`.
285                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/// The handler of read promises of body streams used in
309/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
310#[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    /// Step 5 of <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
324    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                // Step 5.5, the "otherwise" steps.
329                // TODO: terminate fetch.
330                let _ = self.control_sender.send(BodyChunkRequest::Done);
331                return self.stream.stop_reading(cx);
332            },
333        };
334
335        if is_done {
336            // Step 5.3, the "done" steps.
337            // TODO: queue a fetch task on request to process request end-of-body.
338            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                // Step 5.5, the "otherwise" steps.
346                let _ = self.control_sender.send(BodyChunkRequest::Error);
347                return self.stream.stop_reading(cx);
348            },
349        };
350
351        // Step 5.1 and 5.2, transmit chunk.
352        // Send the chunk to the body transmitter in net::http_loader::obtain_response.
353        // TODO: queue a fetch task on request to process request body for request.
354        let _ = self
355            .bytes_sender
356            .send(BodyChunkResponse::Chunk(GenericSharedMemory::from_vec(
357                chunk,
358            )));
359    }
360}
361
362/// The handler of read promises rejection of body streams used in
363/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
364#[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    /// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
378    fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
379        // Step 5.4, the "rejection" steps.
380        let _ = self.control_sender.send(BodyChunkRequest::Error);
381        self.stream.stop_reading(cx);
382    }
383}
384
385/// <https://fetch.spec.whatwg.org/#body-with-type>
386pub(crate) struct ExtractedBody {
387    /// <https://fetch.spec.whatwg.org/#concept-body-stream>
388    pub(crate) stream: DomRoot<ReadableStream>,
389    /// <https://fetch.spec.whatwg.org/#concept-body-source>
390    pub(crate) source: BodySource,
391    /// <https://fetch.spec.whatwg.org/#concept-body-total-bytes>
392    pub(crate) total_bytes: Option<usize>,
393    /// <https://fetch.spec.whatwg.org/#body-with-type-type>
394    pub(crate) content_type: Option<DOMString>,
395}
396
397impl ExtractedBody {
398    /// Build a request body from the extracted body,
399    /// to be sent over IPC to net to use with `concept-request-transmit-body`,
400    /// see <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
401    ///
402    /// Also returning the corresponding readable stream,
403    /// to be stored on the request in script,
404    /// and potentially used as part of `consume_body`,
405    /// see <https://fetch.spec.whatwg.org/#concept-body-consume-body>
406    ///
407    /// Transmitting a body over fetch, and consuming it in script,
408    /// are mutually exclusive operations, since each will lock the stream to a reader.
409    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        // First, setup some infra to be used to transmit body
421        //  from `components::script` to `components::net`.
422        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        // In case of the data being in-memory, send everything in one chunk, by-passing SM.
431        // Empty extracted bodies are always representable as an in-memory empty payload.
432        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                    // Note: this is actually sent from this process
465                    // by the TransmitBodyPromiseHandler when reading stops.
466                    BodyChunkRequest::Done => {
467                        body_handler.stop_reading(StopReading::Done);
468                    },
469                    // Note: this is actually sent from this process
470                    // by the TransmitBodyPromiseHandler when the stream errors.
471                    BodyChunkRequest::Error => {
472                        body_handler.stop_reading(StopReading::Error);
473                    },
474                }
475            }),
476        );
477
478        // Return `components::net` view into this request body,
479        // which can be used by `net` to transmit it over the network.
480        let request_body = RequestBody::new(chunk_request_sender, net_source, total_bytes);
481
482        // Also return the stream for this body, which can be used by script to consume it.
483        (request_body, stream)
484    }
485
486    /// Is the data of the stream of this extracted body available in memory?
487    pub(crate) fn in_memory(&self) -> bool {
488        self.stream.in_memory()
489    }
490}
491
492/// <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
493pub(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
502/// Part of <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
503fn stream_from_body_init_bytes(
504    cx: &mut js::context::JSContext,
505    global: &GlobalScope,
506    bytes: Vec<u8>,
507) -> Fallible<DomRoot<ReadableStream>> {
508    // Step 4: "Otherwise, set stream to a new ReadableStream object, and set up stream with byte reading support."
509    // Step 11: "If source is a byte sequence, then set action to a step that returns source and length to source’s length."
510    // Step 12.1: "Whenever one or more bytes are available and stream is not errored, enqueue the result of creating a Uint8Array from the available bytes into stream."
511    // Step 12.1: "When running action is done, close stream."
512    ReadableStream::new_from_bytes_with_byte_reading_support(cx, global, bytes)
513}
514
515impl Extractable for BodyInit {
516    /// <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
517    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                // Set source to a copy of the bytes held by object.
530                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                // Set source to a copy of the bytes held by object.
542                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 keepalive is true, then throw a TypeError.
554                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 object is disturbed or locked, then throw a TypeError.
560                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            // A vec is used only in `submit_entity_body`.
592            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
707/// <https://fetch.spec.whatwg.org/#concept-body-consume-body>
708/// <https://fetch.spec.whatwg.org/#body-fully-read>
709/// A combination of parts of both algorithms,
710/// `body-fully-read` can be fully implemented, and separated, later,
711/// see #36049.
712pub(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    // Enter the realm of the object whose body is being consumed.
720    let mut realm = enter_auto_realm(cx, &*global);
721    let cx: &mut _ = &mut realm.current_realm();
722
723    // Let promise be a new promise.
724    // Note: re-ordered so we can return the promise below.
725    let promise = Promise::new_in_realm(cx);
726
727    // If object is unusable, then return a promise rejected with a TypeError.
728    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            // If object’s body is null, then run successSteps with an empty byte sequence.
740            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    // <https://fetch.spec.whatwg.org/#concept-body-consume-body>
747    // Otherwise, fully read object’s body given successSteps, errorSteps, and object’s relevant global object.
748    //
749    // <https://fetch.spec.whatwg.org/#body-fully-read>
750    // Let reader be the result of getting a reader for body’s stream.
751    // Read all bytes from reader, given successSteps and errorSteps.
752    //
753    // <https://streams.spec.whatwg.org/#readable-stream-default-reader-read>
754    // Set stream.[[disturbed]] to true.
755    // Otherwise, if stream.[[state]] is "errored", perform readRequest’s error steps given stream.[[storedError]].
756    //
757    // If the body stream is already errored (for example, the fetch was aborted after the Response exists),
758    // the normal fully read path would reject with [[storedError]] but would also mark the stream disturbed.
759    // Once the stream is disturbed, later calls reject with TypeError ("disturbed or locked") instead of the
760    // original AbortError. This early return rejects with the same [[storedError]] without disturbing the
761    // stream, so repeated calls (for example, calling text() twice) keep rejecting with AbortError.
762    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    // Note: from `fully_read`.
770    // Let reader be the result of getting a reader for body’s stream.
771    // If that threw an exception,
772    // then run errorSteps with that exception and return.
773    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 errorSteps given error be to reject promise with error.
782    let error_promise = promise.clone();
783
784    // Let successSteps given a byte sequence data be to resolve promise
785    // with the result of running convertBytesToJSValue with data.
786    // If that threw an exception, then run errorSteps with that exception.
787    let mime_type = object.get_mime_type(cx);
788    let success_promise = promise.clone();
789
790    // Read all bytes from reader, given successSteps and errorSteps.
791    // Note: spec uses an intermediary concept of `fully_read`,
792    // which seems useful when invoking fetch from other places.
793    // TODO: #36049
794    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
813/// The success steps of
814/// <https://fetch.spec.whatwg.org/#concept-body-consume-body>.
815fn 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
840/// The algorithm that takes a byte sequence
841/// and returns a JavaScript value or throws an exception of
842/// <https://fetch.spec.whatwg.org/#concept-body-consume-body>.
843fn 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
862/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A4>
863fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
864    // This implements the Encoding standard's "decode UTF-8", which removes the
865    // BOM if present.
866    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)]
877/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A3>
878fn run_json_data_algorithm(
879    cx: &mut js::context::JSContext,
880    bytes: Vec<u8>,
881) -> Fallible<FetchedData> {
882    // The JSON spec allows implementations to either ignore UTF-8 BOM or treat it as an error.
883    // `JS_ParseJSON` treats this as an error, so it is necessary for us to strip it if present.
884    //
885    // https://datatracker.ietf.org/doc/html/rfc8259#section-8.1
886    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
907/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A0>
908fn 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    // A part whose `Content-Disposition` header contains a `name` parameter whose value is `_charset_` is parsed like any other part. It does not change the encoding.
985    let filename = extract_filename_from_content_disposition(headers);
986    if let Some(filename) = filename {
987        // Each part whose `Content-Disposition` header contains a `filename` parameter must be parsed into an entry whose value is a File object whose contents are the contents of the part.
988        //
989        // The name attribute of the File object must have the value of the `filename` parameter of the part.
990        //
991        // The type attribute of the File object must have the value of the `Content-Type` header of the part if the part has such header, and `text/plain` (the default defined by [RFC7578] section 4.4) otherwise.
992        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        // Each part whose `Content-Disposition` header does not contain a `filename` parameter must be parsed into an entry whose value is the UTF-8 decoded without BOM content of the part. This is done regardless of the presence or the value of a `Content-Type` header and regardless of the presence or the value of a `charset` parameter.
1004
1005        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
1035/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A2>
1036fn run_form_data_algorithm(
1037    cx: &mut js::context::JSContext,
1038    root: &GlobalScope,
1039    bytes: Vec<u8>,
1040    mime: &[u8],
1041) -> Fallible<FetchedData> {
1042    // The formData() method steps are to return the result of running consume body
1043    // with this and the following steps given a byte sequence bytes:
1044    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    // Let mimeType be the result of get the MIME type with this.
1050    //
1051    // If mimeType is non-null, then switch on mimeType’s essence and run the corresponding steps:
1052    if mime.type_() == mime::MULTIPART && mime.subtype() == mime::FORM_DATA {
1053        // "multipart/form-data"
1054        // Parse bytes, using the value of the `boundary` parameter from mimeType,
1055        // per the rules set forth in Returning Values from Forms: multipart/form-data. [RFC7578]
1056        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        // If that fails for some reason, then throw a TypeError.
1075        let nodes = read_multipart_body(&mut cursor, &headers, false)
1076            .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1077        // The above is a rough approximation of what is needed for `multipart/form-data`,
1078        // a more detailed parsing specification is to be written. Volunteers welcome.
1079
1080        // Return a new FormData object, appending each entry, resulting from the parsing operation, to its entry list.
1081        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        // "application/x-www-form-urlencoded"
1090        // Let entries be the result of parsing bytes.
1091        //
1092        // Return a new FormData object whose entry list is entries.
1093        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    // Throw a TypeError.
1102    Err(Error::Type(c"Inappropriate MIME-type for Body".to_owned()))
1103}
1104
1105/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A1>
1106fn 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
1119/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body>
1120pub(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
1150/// <https://fetch.spec.whatwg.org/#body>
1151pub(crate) trait BodyMixin {
1152    /// <https://fetch.spec.whatwg.org/#dom-body-bodyused>
1153    fn is_body_used(&self) -> bool;
1154    /// <https://fetch.spec.whatwg.org/#body-unusable>
1155    fn is_unusable(&self) -> bool;
1156    /// <https://fetch.spec.whatwg.org/#dom-body-body>
1157    fn body(&self) -> Option<DomRoot<ReadableStream>>;
1158    /// <https://fetch.spec.whatwg.org/#concept-body-mime-type>
1159    fn get_mime_type(&self, cx: &mut js::context::JSContext) -> Vec<u8>;
1160}
1161
1162/// <https://fetch.spec.whatwg.org/#dom-body-textstream>
1163pub(crate) fn body_text_stream<T: BodyMixin + DomObject>(
1164    cx: &mut js::context::JSContext,
1165    object: &T,
1166) -> Fallible<DomRoot<ReadableStream>> {
1167    // Step 1: If this is unusable, then throw a TypeError.
1168    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    // Step 3: Let stream be this’s body’s stream.
1175    let Some(stream) = object.body() else {
1176        // Step 2: If this's body is null:
1177        // set up a ReadableStream emptyStream, close it, and return it.
1178        return ReadableStream::new_empty(cx, &object.global());
1179    };
1180
1181    // Step 4: Let decoder be a new TextDecoderStream object in this’s relevant realm.
1182    // Step 5: Set up decoder with UTF-8.
1183    let decoder =
1184        TextDecoderStream::new_with_proto(cx, &object.global(), None, UTF_8, false, false)?;
1185
1186    // Step 6. Return the result of stream, piped through decoder.
1187    Ok(pipe_through(&stream, cx, &object.global(), &decoder))
1188}