Skip to main content

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