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::jsapi::{Heap, JS_ClearPendingException, JSObject, Value as JSValue};
15use js::jsval::{JSVal, UndefinedValue};
16use js::realm::CurrentRealm;
17use js::rust::HandleValue;
18use js::rust::wrappers::{JS_GetPendingException, JS_ParseJSON};
19use js::typedarray::{ArrayBufferU8, Uint8};
20use mime::{self, Mime};
21use mime_multipart_hyper1::{Node, read_multipart_body};
22use net_traits::request::{
23    BodyChunkRequest, BodyChunkResponse, BodySource as NetBodySource, RequestBody,
24};
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, DomObject};
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::realms::{AlreadyInRealm, InRealm, enter_auto_realm, enter_realm};
50use crate::script_runtime::{CanGc, JSContext};
51use crate::task_source::SendableTaskSource;
52
53/// <https://fetch.spec.whatwg.org/#concept-body-clone>
54pub(crate) fn clone_body_stream_for_dom_body(
55    original_body_stream: &MutNullableDom<ReadableStream>,
56    cloned_body_stream: &MutNullableDom<ReadableStream>,
57    can_gc: CanGc,
58) -> Fallible<()> {
59    // To clone a body *body*, run these steps:
60
61    let Some(stream) = original_body_stream.get() else {
62        return Ok(());
63    };
64
65    // step 1. Let « out1, out2 » be the result of teeing body’s stream.
66    let branches = stream.tee(true, can_gc)?;
67    let out1 = &*branches[0];
68    let out2 = &*branches[1];
69
70    // step 2. Set body’s stream to out1.
71    // step 3. Return a body whose stream is out2 and other members are copied from body.
72    original_body_stream.set(Some(out1));
73    cloned_body_stream.set(Some(out2));
74
75    Ok(())
76}
77
78/// The Dom object, or ReadableStream, that is the source of a body.
79/// <https://fetch.spec.whatwg.org/#concept-body-source>
80#[derive(Clone, PartialEq)]
81pub(crate) enum BodySource {
82    /// A ReadableStream comes with a null-source.
83    Null,
84    /// Another Dom object as source,
85    /// TODO: store the actual object
86    /// and re-extract a stream on re-direct.
87    Object,
88}
89
90/// The reason to stop reading from the body.
91enum StopReading {
92    /// The stream has errored.
93    Error,
94    /// The stream is done.
95    Done,
96}
97
98/// The IPC route handler
99/// for <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
100/// This route runs in the script process,
101/// and will queue tasks to perform operations
102/// on the stream and transmit body chunks over IPC.
103#[derive(Clone)]
104struct TransmitBodyConnectHandler {
105    stream: Trusted<ReadableStream>,
106    task_source: SendableTaskSource,
107    bytes_sender: Option<IpcSender<BodyChunkResponse>>,
108    control_sender: Option<IpcSender<BodyChunkRequest>>,
109    in_memory: Option<GenericSharedMemory>,
110    in_memory_done: bool,
111    source: BodySource,
112}
113
114impl TransmitBodyConnectHandler {
115    pub(crate) fn new(
116        stream: Trusted<ReadableStream>,
117        task_source: SendableTaskSource,
118        control_sender: IpcSender<BodyChunkRequest>,
119        in_memory: Option<GenericSharedMemory>,
120        source: BodySource,
121    ) -> TransmitBodyConnectHandler {
122        TransmitBodyConnectHandler {
123            stream,
124            task_source,
125            bytes_sender: None,
126            control_sender: Some(control_sender),
127            in_memory,
128            in_memory_done: false,
129            source,
130        }
131    }
132
133    /// Reset `in_memory_done`, called when a stream is
134    /// re-extracted from the source to support a re-direct.
135    pub(crate) fn reset_in_memory_done(&mut self) {
136        self.in_memory_done = false;
137    }
138
139    /// Re-extract the source to support streaming it again for a re-direct.
140    /// TODO: actually re-extract the source, instead of just cloning data, to support Blob.
141    fn re_extract(&mut self, chunk_request_receiver: IpcReceiver<BodyChunkRequest>) {
142        let mut body_handler = self.clone();
143        body_handler.reset_in_memory_done();
144
145        ROUTER.add_typed_route(
146            chunk_request_receiver,
147            Box::new(move |message| {
148                let request = message.unwrap();
149                match request {
150                    BodyChunkRequest::Connect(sender) => {
151                        body_handler.start_reading(sender);
152                    },
153                    BodyChunkRequest::Extract(receiver) => {
154                        body_handler.re_extract(receiver);
155                    },
156                    BodyChunkRequest::Chunk => body_handler.transmit_source(),
157                    // Note: this is actually sent from this process
158                    // by the TransmitBodyPromiseHandler when reading stops.
159                    BodyChunkRequest::Done => {
160                        body_handler.stop_reading(StopReading::Done);
161                    },
162                    // Note: this is actually sent from this process
163                    // by the TransmitBodyPromiseHandler when the stream errors.
164                    BodyChunkRequest::Error => {
165                        body_handler.stop_reading(StopReading::Error);
166                    },
167                }
168            }),
169        );
170    }
171
172    /// In case of re-direct, and of a source available in memory,
173    /// send it all in one chunk.
174    ///
175    /// TODO: this method should be deprecated
176    /// in favor of making `re_extract` actually re-extract a stream from the source.
177    /// See #26686
178    fn transmit_source(&mut self) {
179        if self.in_memory_done {
180            // Step 5.1.3
181            self.stop_reading(StopReading::Done);
182            return;
183        }
184
185        if let BodySource::Null = self.source {
186            panic!("ReadableStream(Null) sources should not re-direct.");
187        }
188
189        if let Some(bytes) = self.in_memory.clone() {
190            // The memoized bytes are sent so we mark it as done again
191            self.in_memory_done = true;
192            let _ = self
193                .bytes_sender
194                .as_ref()
195                .expect("No bytes sender to transmit source.")
196                .send(BodyChunkResponse::Chunk(bytes));
197            return;
198        }
199        warn!("Re-directs for file-based Blobs not supported yet.");
200    }
201
202    /// Take the IPC sender sent by `net`, so we can send body chunks with it.
203    /// Also the entry point to <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
204    fn start_reading(&mut self, sender: IpcSender<BodyChunkResponse>) {
205        self.bytes_sender = Some(sender);
206
207        // If we're using an actual ReadableStream, acquire a reader for it.
208        if self.source == BodySource::Null {
209            let stream = self.stream.clone();
210            self.task_source
211                .queue(task!(start_reading_request_body_stream: move || {
212                    // Step 1, Let body be request’s body.
213                    let rooted_stream = stream.root();
214
215                    // TODO: Step 2, If body is null.
216
217                    // Step 3, get a reader for stream.
218                    rooted_stream.acquire_default_reader(CanGc::note())
219                        .expect("Couldn't acquire a reader for the body stream.");
220
221                    // Note: this algorithm continues when the first chunk is requested by `net`.
222                }));
223        }
224    }
225
226    /// Drop the IPC sender sent by `net`
227    /// It is important to drop the control_sender as this will allow us to clean ourselves up.
228    /// Otherwise, the following cycle will happen: The control sender is owned by us which keeps the control receiver
229    /// alive in the router which keeps us alive.
230    fn stop_reading(&mut self, reason: StopReading) {
231        let bytes_sender = self
232            .bytes_sender
233            .take()
234            .expect("Stop reading called multiple times on TransmitBodyConnectHandler.");
235        match reason {
236            StopReading::Error => {
237                let _ = bytes_sender.send(BodyChunkResponse::Error);
238            },
239            StopReading::Done => {
240                let _ = bytes_sender.send(BodyChunkResponse::Done);
241            },
242        }
243        let _ = self.control_sender.take();
244    }
245
246    /// Step 4 and following of <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
247    fn transmit_body_chunk(&mut self) {
248        if self.in_memory_done {
249            // Step 5.1.3
250            self.stop_reading(StopReading::Done);
251            return;
252        }
253
254        let stream = self.stream.clone();
255        let control_sender = self.control_sender.clone();
256        let bytes_sender = self
257            .bytes_sender
258            .clone()
259            .expect("No bytes sender to transmit chunk.");
260
261        // In case of the data being in-memory, send everything in one chunk, by-passing SpiderMonkey.
262        if let Some(bytes) = self.in_memory.clone() {
263            let _ = bytes_sender.send(BodyChunkResponse::Chunk(bytes));
264            // Mark this body as `done` so that we can stop reading in the next tick,
265            // matching the behavior of the promise-based flow
266            self.in_memory_done = true;
267            return;
268        }
269
270        self.task_source.queue(
271            task!(setup_native_body_promise_handler: move |cx| {
272                let rooted_stream = stream.root();
273                let global = rooted_stream.global();
274
275                // Step 4, the result of reading a chunk from body’s stream with reader.
276                let promise = rooted_stream.read_a_chunk(cx);
277
278                // Step 5, the parallel steps waiting for and handling the result of the read promise,
279                // are a combination of the promise native handler here,
280                // and the corresponding IPC route in `component::net::http_loader`.
281                rooted!(&in(cx) let mut promise_handler = Some(TransmitBodyPromiseHandler {
282                    bytes_sender: bytes_sender.clone(),
283                    stream: Dom::from_ref(&rooted_stream),
284                    control_sender: control_sender.clone().unwrap(),
285                }));
286
287                rooted!(&in(cx) let mut rejection_handler = Some(TransmitBodyPromiseRejectionHandler {
288                    bytes_sender,
289                    stream: Dom::from_ref(&rooted_stream),
290                    control_sender: control_sender.unwrap(),
291                }));
292
293                let handler =
294                    PromiseNativeHandler::new(&global, promise_handler.take().map(|h| Box::new(h) as Box<_>), rejection_handler.take().map(|h| Box::new(h) as Box<_>), CanGc::from_cx(cx));
295
296                let mut realm = enter_auto_realm(cx, &*global);
297                let realm = &mut realm.current_realm();
298                let in_realm_proof = realm.into();
299                let comp = InRealm::Already(&in_realm_proof);
300                promise.append_native_handler(&handler, comp, CanGc::from_cx(realm));
301            })
302        );
303    }
304}
305
306/// The handler of read promises of body streams used in
307/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
308#[derive(Clone, JSTraceable, MallocSizeOf)]
309#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
310struct TransmitBodyPromiseHandler {
311    #[no_trace]
312    bytes_sender: IpcSender<BodyChunkResponse>,
313    stream: Dom<ReadableStream>,
314    #[no_trace]
315    control_sender: IpcSender<BodyChunkRequest>,
316}
317
318impl js::gc::Rootable for TransmitBodyPromiseHandler {}
319
320impl Callback for TransmitBodyPromiseHandler {
321    /// Step 5 of <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
322    fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
323        let can_gc = CanGc::from_cx(cx);
324        let _realm = InRealm::Already(&cx.into());
325        let cx = cx.into();
326        let is_done = match get_read_promise_done(cx, &v, can_gc) {
327            Ok(is_done) => is_done,
328            Err(_) => {
329                // Step 5.5, the "otherwise" steps.
330                // TODO: terminate fetch.
331                let _ = self.control_sender.send(BodyChunkRequest::Done);
332                return self.stream.stop_reading(can_gc);
333            },
334        };
335
336        if is_done {
337            // Step 5.3, the "done" steps.
338            // TODO: queue a fetch task on request to process request end-of-body.
339            let _ = self.control_sender.send(BodyChunkRequest::Done);
340            return self.stream.stop_reading(can_gc);
341        }
342
343        let chunk = match get_read_promise_bytes(cx, &v, can_gc) {
344            Ok(chunk) => chunk,
345            Err(_) => {
346                // Step 5.5, the "otherwise" steps.
347                let _ = self.control_sender.send(BodyChunkRequest::Error);
348                return self.stream.stop_reading(can_gc);
349            },
350        };
351
352        // Step 5.1 and 5.2, transmit chunk.
353        // Send the chunk to the body transmitter in net::http_loader::obtain_response.
354        // TODO: queue a fetch task on request to process request body for request.
355        let _ = self
356            .bytes_sender
357            .send(BodyChunkResponse::Chunk(GenericSharedMemory::from_bytes(
358                &chunk,
359            )));
360    }
361}
362
363/// The handler of read promises rejection of body streams used in
364/// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
365#[derive(Clone, JSTraceable, MallocSizeOf)]
366#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
367struct TransmitBodyPromiseRejectionHandler {
368    #[no_trace]
369    bytes_sender: IpcSender<BodyChunkResponse>,
370    stream: Dom<ReadableStream>,
371    #[no_trace]
372    control_sender: IpcSender<BodyChunkRequest>,
373}
374
375impl js::gc::Rootable for TransmitBodyPromiseRejectionHandler {}
376
377impl Callback for TransmitBodyPromiseRejectionHandler {
378    /// <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
379    fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
380        // Step 5.4, the "rejection" steps.
381        let _ = self.control_sender.send(BodyChunkRequest::Error);
382        self.stream.stop_reading(CanGc::from_cx(cx));
383    }
384}
385
386/// <https://fetch.spec.whatwg.org/#body-with-type>
387pub(crate) struct ExtractedBody {
388    /// <https://fetch.spec.whatwg.org/#concept-body-stream>
389    pub(crate) stream: DomRoot<ReadableStream>,
390    /// <https://fetch.spec.whatwg.org/#concept-body-source>
391    pub(crate) source: BodySource,
392    /// <https://fetch.spec.whatwg.org/#concept-body-total-bytes>
393    pub(crate) total_bytes: Option<usize>,
394    /// <https://fetch.spec.whatwg.org/#body-with-type-type>
395    pub(crate) content_type: Option<DOMString>,
396}
397
398impl ExtractedBody {
399    /// Build a request body from the extracted body,
400    /// to be sent over IPC to net to use with `concept-request-transmit-body`,
401    /// see <https://fetch.spec.whatwg.org/#concept-request-transmit-body>.
402    ///
403    /// Also returning the corresponding readable stream,
404    /// to be stored on the request in script,
405    /// and potentially used as part of `consume_body`,
406    /// see <https://fetch.spec.whatwg.org/#concept-body-consume-body>
407    ///
408    /// Transmitting a body over fetch, and consuming it in script,
409    /// are mutually exclusive operations, since each will lock the stream to a reader.
410    pub(crate) fn into_net_request_body(self) -> (RequestBody, DomRoot<ReadableStream>) {
411        let ExtractedBody {
412            stream,
413            total_bytes,
414            content_type: _,
415            source,
416        } = self;
417
418        // First, setup some infra to be used to transmit body
419        //  from `components::script` to `components::net`.
420        let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
421
422        let trusted_stream = Trusted::new(&*stream);
423
424        let global = stream.global();
425        let task_source = global.task_manager().networking_task_source();
426
427        // In case of the data being in-memory, send everything in one chunk, by-passing SM.
428        let in_memory = stream.get_in_memory_bytes();
429
430        let net_source = match source {
431            BodySource::Null => NetBodySource::Null,
432            _ => NetBodySource::Object,
433        };
434
435        let mut body_handler = TransmitBodyConnectHandler::new(
436            trusted_stream,
437            task_source.into(),
438            chunk_request_sender.clone(),
439            in_memory,
440            source,
441        );
442
443        ROUTER.add_typed_route(
444            chunk_request_receiver,
445            Box::new(move |message| {
446                match message.unwrap() {
447                    BodyChunkRequest::Connect(sender) => {
448                        body_handler.start_reading(sender);
449                    },
450                    BodyChunkRequest::Extract(receiver) => {
451                        body_handler.re_extract(receiver);
452                    },
453                    BodyChunkRequest::Chunk => body_handler.transmit_body_chunk(),
454                    // Note: this is actually sent from this process
455                    // by the TransmitBodyPromiseHandler when reading stops.
456                    BodyChunkRequest::Done => {
457                        body_handler.stop_reading(StopReading::Done);
458                    },
459                    // Note: this is actually sent from this process
460                    // by the TransmitBodyPromiseHandler when the stream errors.
461                    BodyChunkRequest::Error => {
462                        body_handler.stop_reading(StopReading::Error);
463                    },
464                }
465            }),
466        );
467
468        // Return `components::net` view into this request body,
469        // which can be used by `net` to transmit it over the network.
470        let request_body = RequestBody::new(chunk_request_sender, net_source, total_bytes);
471
472        // Also return the stream for this body, which can be used by script to consume it.
473        (request_body, stream)
474    }
475
476    /// Is the data of the stream of this extracted body available in memory?
477    pub(crate) fn in_memory(&self) -> bool {
478        self.stream.in_memory()
479    }
480}
481
482/// <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
483pub(crate) trait Extractable {
484    fn extract(
485        &self,
486        global: &GlobalScope,
487        keep_alive: bool,
488        can_gc: CanGc,
489    ) -> Fallible<ExtractedBody>;
490}
491
492impl Extractable for BodyInit {
493    /// <https://fetch.spec.whatwg.org/#concept-bodyinit-extract>
494    fn extract(
495        &self,
496        global: &GlobalScope,
497        keep_alive: bool,
498        can_gc: CanGc,
499    ) -> Fallible<ExtractedBody> {
500        match self {
501            BodyInit::String(s) => s.extract(global, keep_alive, can_gc),
502            BodyInit::URLSearchParams(usp) => usp.extract(global, keep_alive, can_gc),
503            BodyInit::Blob(b) => b.extract(global, keep_alive, can_gc),
504            BodyInit::FormData(formdata) => formdata.extract(global, keep_alive, can_gc),
505            BodyInit::ArrayBuffer(typedarray) => {
506                let bytes = typedarray.to_vec();
507                let total_bytes = bytes.len();
508                let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
509                Ok(ExtractedBody {
510                    stream,
511                    total_bytes: Some(total_bytes),
512                    content_type: None,
513                    source: BodySource::Object,
514                })
515            },
516            BodyInit::ArrayBufferView(typedarray) => {
517                let bytes = typedarray.to_vec();
518                let total_bytes = bytes.len();
519                let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
520                Ok(ExtractedBody {
521                    stream,
522                    total_bytes: Some(total_bytes),
523                    content_type: None,
524                    source: BodySource::Object,
525                })
526            },
527            BodyInit::ReadableStream(stream) => {
528                // If keepalive is true, then throw a TypeError.
529                if keep_alive {
530                    return Err(Error::Type(
531                        c"The body's stream is for a keepalive request".to_owned(),
532                    ));
533                }
534                // If object is disturbed or locked, then throw a TypeError.
535                if stream.is_locked() || stream.is_disturbed() {
536                    return Err(Error::Type(
537                        c"The body's stream is disturbed or locked".to_owned(),
538                    ));
539                }
540
541                Ok(ExtractedBody {
542                    stream: stream.clone(),
543                    total_bytes: None,
544                    content_type: None,
545                    source: BodySource::Null,
546                })
547            },
548        }
549    }
550}
551
552impl Extractable for Vec<u8> {
553    fn extract(
554        &self,
555        global: &GlobalScope,
556        _keep_alive: bool,
557        can_gc: CanGc,
558    ) -> Fallible<ExtractedBody> {
559        let bytes = self.clone();
560        let total_bytes = self.len();
561        let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
562        Ok(ExtractedBody {
563            stream,
564            total_bytes: Some(total_bytes),
565            content_type: None,
566            // A vec is used only in `submit_entity_body`.
567            source: BodySource::Object,
568        })
569    }
570}
571
572impl Extractable for Blob {
573    fn extract(
574        &self,
575        _global: &GlobalScope,
576        _keep_alive: bool,
577        can_gc: CanGc,
578    ) -> Fallible<ExtractedBody> {
579        let blob_type = self.Type();
580        let content_type = if blob_type.is_empty() {
581            None
582        } else {
583            Some(blob_type)
584        };
585        let total_bytes = self.Size() as usize;
586        let stream = self.get_stream(can_gc)?;
587        Ok(ExtractedBody {
588            stream,
589            total_bytes: Some(total_bytes),
590            content_type,
591            source: BodySource::Object,
592        })
593    }
594}
595
596impl Extractable for DOMString {
597    fn extract(
598        &self,
599        global: &GlobalScope,
600        _keep_alive: bool,
601        can_gc: CanGc,
602    ) -> Fallible<ExtractedBody> {
603        let bytes = self.as_bytes().to_owned();
604        let total_bytes = bytes.len();
605        let content_type = Some(DOMString::from("text/plain;charset=UTF-8"));
606        let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
607        Ok(ExtractedBody {
608            stream,
609            total_bytes: Some(total_bytes),
610            content_type,
611            source: BodySource::Object,
612        })
613    }
614}
615
616impl Extractable for FormData {
617    fn extract(
618        &self,
619        global: &GlobalScope,
620        _keep_alive: bool,
621        can_gc: CanGc,
622    ) -> Fallible<ExtractedBody> {
623        let boundary = generate_boundary();
624        let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
625        let total_bytes = bytes.len();
626        let content_type = Some(DOMString::from(format!(
627            "multipart/form-data; boundary={}",
628            boundary
629        )));
630        let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
631        Ok(ExtractedBody {
632            stream,
633            total_bytes: Some(total_bytes),
634            content_type,
635            source: BodySource::Object,
636        })
637    }
638}
639
640impl Extractable for URLSearchParams {
641    fn extract(
642        &self,
643        global: &GlobalScope,
644        _keep_alive: bool,
645        can_gc: CanGc,
646    ) -> Fallible<ExtractedBody> {
647        let bytes = self.serialize_utf8().into_bytes();
648        let total_bytes = bytes.len();
649        let content_type = Some(DOMString::from(
650            "application/x-www-form-urlencoded;charset=UTF-8",
651        ));
652        let stream = ReadableStream::new_from_bytes(global, bytes, can_gc)?;
653        Ok(ExtractedBody {
654            stream,
655            total_bytes: Some(total_bytes),
656            content_type,
657            source: BodySource::Object,
658        })
659    }
660}
661
662#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
663pub(crate) enum BodyType {
664    Blob,
665    Bytes,
666    FormData,
667    Json,
668    Text,
669    ArrayBuffer,
670}
671
672pub(crate) enum FetchedData {
673    Text(String),
674    Json(RootedTraceableBox<Heap<JSValue>>),
675    BlobData(DomRoot<Blob>),
676    Bytes(RootedTraceableBox<Heap<*mut JSObject>>),
677    FormData(DomRoot<FormData>),
678    ArrayBuffer(RootedTraceableBox<Heap<*mut JSObject>>),
679    JSException(RootedTraceableBox<Heap<JSVal>>),
680}
681
682/// <https://fetch.spec.whatwg.org/#concept-body-consume-body>
683/// <https://fetch.spec.whatwg.org/#body-fully-read>
684/// A combination of parts of both algorithms,
685/// `body-fully-read` can be fully implemented, and separated, later,
686/// see #36049.
687pub(crate) fn consume_body<T: BodyMixin + DomObject>(
688    object: &T,
689    body_type: BodyType,
690    can_gc: CanGc,
691) -> Rc<Promise> {
692    let global = object.global();
693    let cx = GlobalScope::get_cx();
694
695    // Enter the realm of the object whose body is being consumed.
696    let realm = enter_realm(&*global);
697    let comp = InRealm::Entered(&realm);
698
699    // Let promise be a new promise.
700    // Note: re-ordered so we can return the promise below.
701    let promise = Promise::new_in_current_realm(comp, can_gc);
702
703    // If object is unusable, then return a promise rejected with a TypeError.
704    if object.is_unusable() {
705        promise.reject_error(
706            Error::Type(c"The body's stream is disturbed or locked".to_owned()),
707            can_gc,
708        );
709        return promise;
710    }
711
712    let stream = match object.body() {
713        Some(stream) => stream,
714        None => {
715            // If object’s body is null, then run successSteps with an empty byte sequence.
716            resolve_result_promise(
717                body_type,
718                &promise,
719                object.get_mime_type(can_gc),
720                Vec::with_capacity(0),
721                cx,
722                can_gc,
723            );
724            return promise;
725        },
726    };
727
728    // <https://fetch.spec.whatwg.org/#concept-body-consume-body>
729    // Otherwise, fully read object’s body given successSteps, errorSteps, and object’s relevant global object.
730    //
731    // <https://fetch.spec.whatwg.org/#body-fully-read>
732    // Let reader be the result of getting a reader for body’s stream.
733    // Read all bytes from reader, given successSteps and errorSteps.
734    //
735    // <https://streams.spec.whatwg.org/#readable-stream-default-reader-read>
736    // Set stream.[[disturbed]] to true.
737    // Otherwise, if stream.[[state]] is "errored", perform readRequest’s error steps given stream.[[storedError]].
738    //
739    // If the body stream is already errored (for example, the fetch was aborted after the Response exists),
740    // the normal fully read path would reject with [[storedError]] but would also mark the stream disturbed.
741    // Once the stream is disturbed, later calls reject with TypeError ("disturbed or locked") instead of the
742    // original AbortError. This early return rejects with the same [[storedError]] without disturbing the
743    // stream, so repeated calls (for example, calling text() twice) keep rejecting with AbortError.
744    if stream.is_errored() {
745        rooted!(in(*cx) let mut stored_error = UndefinedValue());
746        stream.get_stored_error(stored_error.handle_mut());
747        promise.reject(cx, stored_error.handle(), can_gc);
748        return promise;
749    }
750
751    // Note: from `fully_read`.
752    // Let reader be the result of getting a reader for body’s stream.
753    // If that threw an exception,
754    // then run errorSteps with that exception and return.
755    let reader = match stream.acquire_default_reader(can_gc) {
756        Ok(r) => r,
757        Err(e) => {
758            promise.reject_error(e, can_gc);
759            return promise;
760        },
761    };
762
763    // Let errorSteps given error be to reject promise with error.
764    let error_promise = promise.clone();
765
766    // Let successSteps given a byte sequence data be to resolve promise
767    // with the result of running convertBytesToJSValue with data.
768    // If that threw an exception, then run errorSteps with that exception.
769    let mime_type = object.get_mime_type(can_gc);
770    let success_promise = promise.clone();
771
772    // Read all bytes from reader, given successSteps and errorSteps.
773    // Note: spec uses an intermediary concept of `fully_read`,
774    // which seems useful when invoking fetch from other places.
775    // TODO: #36049
776    reader.read_all_bytes(
777        cx,
778        Rc::new(move |bytes: &[u8]| {
779            resolve_result_promise(
780                body_type,
781                &success_promise,
782                mime_type.clone(),
783                bytes.to_vec(),
784                cx,
785                can_gc,
786            );
787        }),
788        Rc::new(move |cx, v| {
789            error_promise.reject(cx, v, can_gc);
790        }),
791        can_gc,
792    );
793
794    promise
795}
796
797/// The success steps of
798/// <https://fetch.spec.whatwg.org/#concept-body-consume-body>.
799fn resolve_result_promise(
800    body_type: BodyType,
801    promise: &Promise,
802    mime_type: Vec<u8>,
803    body: Vec<u8>,
804    cx: JSContext,
805    can_gc: CanGc,
806) {
807    let pkg_data_results = run_package_data_algorithm(cx, body, body_type, mime_type, can_gc);
808
809    match pkg_data_results {
810        Ok(results) => {
811            match results {
812                FetchedData::Text(s) => promise.resolve_native(&USVString(s), can_gc),
813                FetchedData::Json(j) => promise.resolve_native(&j, can_gc),
814                FetchedData::BlobData(b) => promise.resolve_native(&b, can_gc),
815                FetchedData::FormData(f) => promise.resolve_native(&f, can_gc),
816                FetchedData::Bytes(b) => promise.resolve_native(&b, can_gc),
817                FetchedData::ArrayBuffer(a) => promise.resolve_native(&a, can_gc),
818                FetchedData::JSException(e) => promise.reject_native(&e.handle(), can_gc),
819            };
820        },
821        Err(err) => promise.reject_error(err, can_gc),
822    }
823}
824
825/// The algorithm that takes a byte sequence
826/// and returns a JavaScript value or throws an exception of
827/// <https://fetch.spec.whatwg.org/#concept-body-consume-body>.
828fn run_package_data_algorithm(
829    cx: JSContext,
830    bytes: Vec<u8>,
831    body_type: BodyType,
832    mime_type: Vec<u8>,
833    can_gc: CanGc,
834) -> Fallible<FetchedData> {
835    let mime = &*mime_type;
836    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
837    let global = GlobalScope::from_safe_context(cx, InRealm::Already(&in_realm_proof));
838    match body_type {
839        BodyType::Text => run_text_data_algorithm(bytes),
840        BodyType::Json => run_json_data_algorithm(cx, bytes),
841        BodyType::Blob => run_blob_data_algorithm(&global, bytes, mime, can_gc),
842        BodyType::FormData => run_form_data_algorithm(&global, bytes, mime, can_gc),
843        BodyType::ArrayBuffer => run_array_buffer_data_algorithm(cx, bytes, can_gc),
844        BodyType::Bytes => run_bytes_data_algorithm(cx, bytes, can_gc),
845    }
846}
847
848/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A4>
849fn run_text_data_algorithm(bytes: Vec<u8>) -> Fallible<FetchedData> {
850    // This implements the Encoding standard's "decode UTF-8", which removes the
851    // BOM if present.
852    let no_bom_bytes = if bytes.starts_with(b"\xEF\xBB\xBF") {
853        &bytes[3..]
854    } else {
855        &bytes
856    };
857    Ok(FetchedData::Text(
858        String::from_utf8_lossy(no_bom_bytes).into_owned(),
859    ))
860}
861
862#[expect(unsafe_code)]
863/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A3>
864fn run_json_data_algorithm(cx: JSContext, bytes: Vec<u8>) -> Fallible<FetchedData> {
865    // The JSON spec allows implementations to either ignore UTF-8 BOM or treat it as an error.
866    // `JS_ParseJSON` treats this as an error, so it is necessary for us to strip it if present.
867    //
868    // https://datatracker.ietf.org/doc/html/rfc8259#section-8.1
869    let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8);
870    rooted!(in(*cx) let mut rval = UndefinedValue());
871    unsafe {
872        if !JS_ParseJSON(
873            *cx,
874            json_text.as_ptr(),
875            json_text.len() as u32,
876            rval.handle_mut(),
877        ) {
878            rooted!(in(*cx) let mut exception = UndefinedValue());
879            assert!(JS_GetPendingException(*cx, exception.handle_mut()));
880            JS_ClearPendingException(*cx);
881            return Ok(FetchedData::JSException(RootedTraceableBox::from_box(
882                Heap::boxed(exception.get()),
883            )));
884        }
885        let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(rval.get()));
886        Ok(FetchedData::Json(rooted_heap))
887    }
888}
889
890/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A0>
891fn run_blob_data_algorithm(
892    root: &GlobalScope,
893    bytes: Vec<u8>,
894    mime: &[u8],
895    can_gc: CanGc,
896) -> Fallible<FetchedData> {
897    let mime_string = if let Ok(s) = String::from_utf8(mime.to_vec()) {
898        s
899    } else {
900        "".to_string()
901    };
902    let blob = Blob::new(
903        root,
904        BlobImpl::new_from_bytes(bytes, normalize_type_string(&mime_string)),
905        can_gc,
906    );
907    Ok(FetchedData::BlobData(blob))
908}
909
910fn extract_name_from_content_disposition(headers: &HeaderMap) -> Option<String> {
911    let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
912
913    for part in cd.split(';').map(|s| s.trim()) {
914        if let Some(rest) = part.strip_prefix("name=") {
915            let v = rest.trim();
916            let v = v.strip_prefix('"').unwrap_or(v);
917            let v = v.strip_suffix('"').unwrap_or(v);
918            return Some(v.to_string());
919        }
920    }
921    None
922}
923
924fn extract_filename_from_content_disposition(headers: &HeaderMap) -> Option<String> {
925    let cd = headers.get(CONTENT_DISPOSITION)?.to_str().ok()?;
926    if let Some(index) = cd.find("filename=") {
927        let start = index + "filename=".len();
928        return Some(
929            cd.get(start..)
930                .unwrap_or_default()
931                .trim_matches('"')
932                .to_owned(),
933        );
934    }
935    if let Some(index) = cd.find("filename*=UTF-8''") {
936        let start = index + "filename*=UTF-8''".len();
937        return Some(
938            cd.get(start..)
939                .unwrap_or_default()
940                .trim_matches('"')
941                .to_owned(),
942        );
943    }
944    None
945}
946
947fn content_type_from_headers(headers: &HeaderMap) -> Result<String, Error> {
948    match headers.get(CONTENT_TYPE) {
949        Some(value) => Ok(value
950            .to_str()
951            .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?
952            .to_string()),
953        None => Ok("text/plain".to_string()),
954    }
955}
956
957fn append_form_data_entry_from_part(
958    root: &GlobalScope,
959    formdata: &FormData,
960    headers: &HeaderMap,
961    body: Vec<u8>,
962    can_gc: CanGc,
963) -> Fallible<()> {
964    let Some(name) = extract_name_from_content_disposition(headers) else {
965        return Ok(());
966    };
967    // 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.
968    let filename = extract_filename_from_content_disposition(headers);
969    if let Some(filename) = filename {
970        // 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.
971        //
972        // The name attribute of the File object must have the value of the `filename` parameter of the part.
973        //
974        // 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.
975        let content_type = content_type_from_headers(headers)?;
976        let file = File::new(
977            root,
978            BlobImpl::new_from_bytes(body, normalize_type_string(&content_type)),
979            DOMString::from(filename),
980            None,
981            can_gc,
982        );
983        let blob = file.upcast::<Blob>();
984        formdata.Append_(USVString(name), blob, None);
985    } else {
986        // 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.
987
988        let (value, _) = UTF_8.decode_without_bom_handling(&body);
989        formdata.Append(USVString(name), USVString(value.to_string()));
990    }
991    Ok(())
992}
993
994fn append_multipart_nodes(
995    root: &GlobalScope,
996    formdata: &FormData,
997    nodes: Vec<Node>,
998    can_gc: CanGc,
999) -> Fallible<()> {
1000    for node in nodes {
1001        match node {
1002            Node::Part(part) => {
1003                append_form_data_entry_from_part(root, formdata, &part.headers, part.body, can_gc)?;
1004            },
1005            Node::File(file_part) => {
1006                let body = fs::read(&file_part.path)
1007                    .map_err(|_| Error::Type(c"file part could not be read".to_owned()))?;
1008                append_form_data_entry_from_part(root, formdata, &file_part.headers, body, can_gc)?;
1009            },
1010            Node::Multipart((_, inner)) => {
1011                append_multipart_nodes(root, formdata, inner, can_gc)?;
1012            },
1013        }
1014    }
1015    Ok(())
1016}
1017
1018/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A2>
1019fn run_form_data_algorithm(
1020    root: &GlobalScope,
1021    bytes: Vec<u8>,
1022    mime: &[u8],
1023    can_gc: CanGc,
1024) -> Fallible<FetchedData> {
1025    // The formData() method steps are to return the result of running consume body
1026    // with this and the following steps given a byte sequence bytes:
1027    let mime_str = str::from_utf8(mime).unwrap_or_default();
1028    let mime: Mime = mime_str
1029        .parse()
1030        .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1031
1032    // Let mimeType be the result of get the MIME type with this.
1033    //
1034    // If mimeType is non-null, then switch on mimeType’s essence and run the corresponding steps:
1035    if mime.type_() == mime::MULTIPART && mime.subtype() == mime::FORM_DATA {
1036        // "multipart/form-data"
1037        // Parse bytes, using the value of the `boundary` parameter from mimeType,
1038        // per the rules set forth in Returning Values from Forms: multipart/form-data. [RFC7578]
1039        let mut headers = HeaderMap::new();
1040        headers.insert(
1041            CONTENT_TYPE,
1042            mime_str
1043                .parse()
1044                .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?,
1045        );
1046
1047        if let Some(boundary) = mime.get_param(mime::BOUNDARY) {
1048            let closing_boundary = format!("--{}--", boundary.as_str()).into_bytes();
1049            let trimmed_bytes = bytes.strip_suffix(b"\r\n").unwrap_or(&bytes);
1050            if trimmed_bytes == closing_boundary {
1051                let formdata = FormData::new(None, root, can_gc);
1052                return Ok(FetchedData::FormData(formdata));
1053            }
1054        }
1055
1056        let mut cursor = Cursor::new(bytes);
1057        // If that fails for some reason, then throw a TypeError.
1058        let nodes = read_multipart_body(&mut cursor, &headers, false)
1059            .map_err(|_| Error::Type(c"Inappropriate MIME-type for Body".to_owned()))?;
1060        // The above is a rough approximation of what is needed for `multipart/form-data`,
1061        // a more detailed parsing specification is to be written. Volunteers welcome.
1062
1063        // Return a new FormData object, appending each entry, resulting from the parsing operation, to its entry list.
1064        let formdata = FormData::new(None, root, can_gc);
1065
1066        append_multipart_nodes(root, &formdata, nodes, can_gc)?;
1067
1068        return Ok(FetchedData::FormData(formdata));
1069    }
1070
1071    if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED {
1072        // "application/x-www-form-urlencoded"
1073        // Let entries be the result of parsing bytes.
1074        //
1075        // Return a new FormData object whose entry list is entries.
1076        let entries = form_urlencoded::parse(&bytes);
1077        let formdata = FormData::new(None, root, can_gc);
1078        for (k, e) in entries {
1079            formdata.Append(USVString(k.into_owned()), USVString(e.into_owned()));
1080        }
1081        return Ok(FetchedData::FormData(formdata));
1082    }
1083
1084    // Throw a TypeError.
1085    Err(Error::Type(c"Inappropriate MIME-type for Body".to_owned()))
1086}
1087
1088/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body%E2%91%A1>
1089fn run_bytes_data_algorithm(cx: JSContext, bytes: Vec<u8>, can_gc: CanGc) -> Fallible<FetchedData> {
1090    rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1091
1092    create_buffer_source::<Uint8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
1093        .map_err(|_| Error::JSFailed)?;
1094
1095    let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1096    Ok(FetchedData::Bytes(rooted_heap))
1097}
1098
1099/// <https://fetch.spec.whatwg.org/#ref-for-concept-body-consume-body>
1100pub(crate) fn run_array_buffer_data_algorithm(
1101    cx: JSContext,
1102    bytes: Vec<u8>,
1103    can_gc: CanGc,
1104) -> Fallible<FetchedData> {
1105    rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
1106
1107    create_buffer_source::<ArrayBufferU8>(cx, &bytes, array_buffer_ptr.handle_mut(), can_gc)
1108        .map_err(|_| Error::JSFailed)?;
1109
1110    let rooted_heap = RootedTraceableBox::from_box(Heap::boxed(array_buffer_ptr.get()));
1111    Ok(FetchedData::ArrayBuffer(rooted_heap))
1112}
1113
1114#[expect(unsafe_code)]
1115pub(crate) fn decode_to_utf16_with_bom_removal(
1116    bytes: &[u8],
1117    encoding: &'static Encoding,
1118) -> Vec<u16> {
1119    let mut decoder = encoding.new_decoder_with_bom_removal();
1120    let capacity = decoder
1121        .max_utf16_buffer_length(bytes.len())
1122        .expect("Overflow");
1123    let mut utf16 = Vec::with_capacity(capacity);
1124    let extra = unsafe { slice::from_raw_parts_mut(utf16.as_mut_ptr(), capacity) };
1125    let (_, read, written, _) = decoder.decode_to_utf16(bytes, extra, true);
1126    assert_eq!(read, bytes.len());
1127    unsafe { utf16.set_len(written) }
1128    utf16
1129}
1130
1131/// <https://fetch.spec.whatwg.org/#body>
1132pub(crate) trait BodyMixin {
1133    /// <https://fetch.spec.whatwg.org/#dom-body-bodyused>
1134    fn is_body_used(&self) -> bool;
1135    /// <https://fetch.spec.whatwg.org/#body-unusable>
1136    fn is_unusable(&self) -> bool;
1137    /// <https://fetch.spec.whatwg.org/#dom-body-body>
1138    fn body(&self) -> Option<DomRoot<ReadableStream>>;
1139    /// <https://fetch.spec.whatwg.org/#concept-body-mime-type>
1140    fn get_mime_type(&self, can_gc: CanGc) -> Vec<u8>;
1141}