script/dom/
response.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::cell::Cell;
6use std::rc::Rc;
7use std::str::FromStr;
8
9use dom_struct::dom_struct;
10use http::header::HeaderMap as HyperHeaders;
11use hyper_serde::Serde;
12use js::rust::{HandleObject, HandleValue};
13use net_traits::http_status::HttpStatus;
14use servo_url::ServoUrl;
15use url::Position;
16
17use crate::body::{
18    BodyMixin, BodyType, Extractable, ExtractedBody, clone_body_stream_for_dom_body, consume_body,
19};
20use crate::dom::bindings::cell::DomRefCell;
21use crate::dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
22use crate::dom::bindings::codegen::Bindings::ResponseBinding;
23use crate::dom::bindings::codegen::Bindings::ResponseBinding::{
24    ResponseMethods, ResponseType as DOMResponseType,
25};
26use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
27use crate::dom::bindings::error::{Error, Fallible};
28use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
29use crate::dom::bindings::root::{DomRoot, MutNullableDom};
30use crate::dom::bindings::str::{ByteString, USVString, serialize_jsval_to_json_utf8};
31use crate::dom::globalscope::GlobalScope;
32use crate::dom::headers::{Guard, Headers, is_obs_text, is_vchar};
33use crate::dom::promise::Promise;
34use crate::dom::stream::readablestream::ReadableStream;
35use crate::dom::stream::underlyingsourcecontainer::UnderlyingSourceType;
36use crate::script_runtime::{CanGc, JSContext, StreamConsumer};
37
38#[dom_struct]
39pub(crate) struct Response {
40    reflector_: Reflector,
41    headers_reflector: MutNullableDom<Headers>,
42    #[no_trace]
43    status: DomRefCell<HttpStatus>,
44    response_type: DomRefCell<DOMResponseType>,
45    #[no_trace]
46    url: DomRefCell<Option<ServoUrl>>,
47    #[no_trace]
48    url_list: DomRefCell<Vec<ServoUrl>>,
49    /// The stream of <https://fetch.spec.whatwg.org/#body>.
50    body_stream: MutNullableDom<ReadableStream>,
51    /// The stream that receives network delivered bytes for Fetch responses.
52    /// This must remain stable even if `body_stream` is replaced by `tee()` branches during `clone()`.
53    fetch_body_stream: MutNullableDom<ReadableStream>,
54    #[ignore_malloc_size_of = "StreamConsumer"]
55    stream_consumer: DomRefCell<Option<StreamConsumer>>,
56    redirected: Cell<bool>,
57    is_body_empty: Cell<bool>,
58}
59
60impl Response {
61    pub(crate) fn new_inherited(global: &GlobalScope, can_gc: CanGc) -> Response {
62        let stream = ReadableStream::new_with_external_underlying_source(
63            global,
64            UnderlyingSourceType::FetchResponse,
65            can_gc,
66        )
67        .expect("Failed to create ReadableStream with external underlying source");
68        Response {
69            reflector_: Reflector::new(),
70            headers_reflector: Default::default(),
71            status: DomRefCell::new(HttpStatus::default()),
72            response_type: DomRefCell::new(DOMResponseType::Default),
73            url: DomRefCell::new(None),
74            url_list: DomRefCell::new(vec![]),
75            body_stream: MutNullableDom::new(Some(&*stream)),
76            fetch_body_stream: MutNullableDom::new(Some(&*stream)),
77            stream_consumer: DomRefCell::new(None),
78            redirected: Cell::new(false),
79            is_body_empty: Cell::new(true),
80        }
81    }
82
83    /// <https://fetch.spec.whatwg.org/#dom-response>
84    pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Response> {
85        Self::new_with_proto(global, None, can_gc)
86    }
87
88    fn new_with_proto(
89        global: &GlobalScope,
90        proto: Option<HandleObject>,
91        can_gc: CanGc,
92    ) -> DomRoot<Response> {
93        reflect_dom_object_with_proto(
94            Box::new(Response::new_inherited(global, can_gc)),
95            global,
96            proto,
97            can_gc,
98        )
99    }
100
101    pub(crate) fn error_stream(&self, error: Error, can_gc: CanGc) {
102        if let Some(body) = self.fetch_body_stream.get() {
103            body.error_native(error, can_gc);
104        }
105    }
106
107    pub(crate) fn is_disturbed(&self) -> bool {
108        let body_stream = self.body_stream.get();
109        body_stream
110            .as_ref()
111            .is_some_and(|stream| stream.is_disturbed())
112    }
113
114    pub(crate) fn is_locked(&self) -> bool {
115        let body_stream = self.body_stream.get();
116        body_stream
117            .as_ref()
118            .is_some_and(|stream| stream.is_locked())
119    }
120}
121
122impl BodyMixin for Response {
123    fn is_body_used(&self) -> bool {
124        self.is_disturbed()
125    }
126
127    fn is_unusable(&self) -> bool {
128        self.body_stream
129            .get()
130            .is_some_and(|stream| stream.is_disturbed() || stream.is_locked())
131    }
132
133    fn body(&self) -> Option<DomRoot<ReadableStream>> {
134        self.body_stream.get()
135    }
136
137    fn get_mime_type(&self, can_gc: CanGc) -> Vec<u8> {
138        let headers = self.Headers(can_gc);
139        headers.extract_mime_type()
140    }
141}
142
143/// <https://fetch.spec.whatwg.org/#redirect-status>
144fn is_redirect_status(status: u16) -> bool {
145    status == 301 || status == 302 || status == 303 || status == 307 || status == 308
146}
147
148/// <https://tools.ietf.org/html/rfc7230#section-3.1.2>
149fn is_valid_status_text(status_text: &ByteString) -> bool {
150    // reason-phrase  = *( HTAB / SP / VCHAR / obs-text )
151    for byte in status_text.iter() {
152        if !(*byte == b'\t' || *byte == b' ' || is_vchar(*byte) || is_obs_text(*byte)) {
153            return false;
154        }
155    }
156    true
157}
158
159/// <https://fetch.spec.whatwg.org/#null-body-status>
160fn is_null_body_status(status: u16) -> bool {
161    status == 101 || status == 204 || status == 205 || status == 304
162}
163
164impl ResponseMethods<crate::DomTypeHolder> for Response {
165    /// <https://fetch.spec.whatwg.org/#dom-response>
166    fn Constructor(
167        global: &GlobalScope,
168        proto: Option<HandleObject>,
169        can_gc: CanGc,
170        body_init: Option<BodyInit>,
171        init: &ResponseBinding::ResponseInit,
172    ) -> Fallible<DomRoot<Response>> {
173        // 1. Set this’s response to a new response.
174        // Our Response/Body types don't actually hold onto an internal fetch Response.
175        let response = Response::new_with_proto(global, proto, can_gc);
176        if body_init.is_some() {
177            response.is_body_empty.set(false);
178        }
179
180        // 2. Set this’s headers to a new Headers object with this’s relevant realm,
181        // whose header list is this’s response’s header list and guard is "response".
182        response.Headers(can_gc).set_guard(Guard::Response);
183
184        // 3. Let bodyWithType be null.
185        // 4. If body is non-null, then set bodyWithType to the result of extracting body.
186        let body_with_type = match body_init {
187            Some(body) => Some(body.extract(global, false, can_gc)?),
188            None => None,
189        };
190
191        // 5. Perform *initialize a response* given this, init, and bodyWithType.
192        initialize_response(global, can_gc, body_with_type, init, response)
193    }
194
195    /// <https://fetch.spec.whatwg.org/#dom-response-error>
196    fn Error(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Response> {
197        let response = Response::new(global, can_gc);
198        *response.response_type.borrow_mut() = DOMResponseType::Error;
199        response.Headers(can_gc).set_guard(Guard::Immutable);
200        *response.status.borrow_mut() = HttpStatus::new_error();
201        response
202    }
203
204    /// <https://fetch.spec.whatwg.org/#dom-response-redirect>
205    fn Redirect(
206        global: &GlobalScope,
207        url: USVString,
208        status: u16,
209        can_gc: CanGc,
210    ) -> Fallible<DomRoot<Response>> {
211        // Step 1
212        let base_url = global.api_base_url();
213        let parsed_url = base_url.join(&url.0);
214
215        // Step 2
216        let url = match parsed_url {
217            Ok(url) => url,
218            Err(_) => return Err(Error::Type("ServoUrl could not be parsed".to_string())),
219        };
220
221        // Step 3
222        if !is_redirect_status(status) {
223            return Err(Error::Range("status is not a redirect status".to_string()));
224        }
225
226        // Step 4
227        // see Step 4 continued
228        let response = Response::new(global, can_gc);
229
230        // Step 5
231        *response.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
232
233        // Step 6
234        let url_bytestring =
235            ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
236        response
237            .Headers(can_gc)
238            .Set(ByteString::new(b"Location".to_vec()), url_bytestring)?;
239
240        // Step 4 continued
241        // Headers Guard is set to Immutable here to prevent error in Step 6
242        response.Headers(can_gc).set_guard(Guard::Immutable);
243
244        // Step 7
245        Ok(response)
246    }
247
248    /// <https://fetch.spec.whatwg.org/#dom-response-json>
249    fn CreateFromJson(
250        cx: JSContext,
251        global: &GlobalScope,
252        data: HandleValue,
253        init: &ResponseBinding::ResponseInit,
254        can_gc: CanGc,
255    ) -> Fallible<DomRoot<Response>> {
256        // 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
257        let json_str = serialize_jsval_to_json_utf8(cx, data)?;
258
259        // 2. Let body be the result of extracting bytes
260        // The spec's definition of JSON bytes is a UTF-8 encoding so using a DOMString here handles
261        // the encoding part.
262        let body_init = BodyInit::String(json_str);
263        let mut body = body_init.extract(global, false, can_gc)?;
264
265        // 3. Let responseObject be the result of creating a Response object, given a new response,
266        // "response", and the current realm.
267        let response = Response::new(global, can_gc);
268        response.Headers(can_gc).set_guard(Guard::Response);
269
270        // 4. Perform initialize a response given responseObject, init, and (body, "application/json").
271        body.content_type = Some("application/json".into());
272        initialize_response(global, can_gc, Some(body), init, response)
273    }
274
275    /// <https://fetch.spec.whatwg.org/#dom-response-type>
276    fn Type(&self) -> DOMResponseType {
277        *self.response_type.borrow() // into()
278    }
279
280    /// <https://fetch.spec.whatwg.org/#dom-response-url>
281    fn Url(&self) -> USVString {
282        USVString(String::from(
283            (*self.url.borrow())
284                .as_ref()
285                .map(serialize_without_fragment)
286                .unwrap_or(""),
287        ))
288    }
289
290    /// <https://fetch.spec.whatwg.org/#dom-response-redirected>
291    fn Redirected(&self) -> bool {
292        self.redirected.get()
293    }
294
295    /// <https://fetch.spec.whatwg.org/#dom-response-status>
296    fn Status(&self) -> u16 {
297        self.status.borrow().raw_code()
298    }
299
300    /// <https://fetch.spec.whatwg.org/#dom-response-ok>
301    fn Ok(&self) -> bool {
302        self.status.borrow().is_success()
303    }
304
305    /// <https://fetch.spec.whatwg.org/#dom-response-statustext>
306    fn StatusText(&self) -> ByteString {
307        ByteString::new(self.status.borrow().message().to_vec())
308    }
309
310    /// <https://fetch.spec.whatwg.org/#dom-response-headers>
311    fn Headers(&self, can_gc: CanGc) -> DomRoot<Headers> {
312        self.headers_reflector
313            .or_init(|| Headers::for_response(&self.global(), can_gc))
314    }
315
316    /// <https://fetch.spec.whatwg.org/#dom-response-clone>
317    fn Clone(&self, can_gc: CanGc) -> Fallible<DomRoot<Response>> {
318        // Step 1. If this is unusable, then throw a TypeError.
319        if self.is_unusable() {
320            return Err(Error::Type("cannot clone a disturbed response".to_string()));
321        }
322
323        // Step 2. Let clonedResponse be the result of cloning this’s response.
324        let new_response = Response::new(&self.global(), can_gc);
325        new_response
326            .Headers(can_gc)
327            .copy_from_headers(self.Headers(can_gc))?;
328        new_response
329            .Headers(can_gc)
330            .set_guard(self.Headers(can_gc).get_guard());
331
332        *new_response.response_type.borrow_mut() = *self.response_type.borrow();
333        new_response
334            .status
335            .borrow_mut()
336            .clone_from(&self.status.borrow());
337        new_response.url.borrow_mut().clone_from(&self.url.borrow());
338        new_response
339            .url_list
340            .borrow_mut()
341            .clone_from(&self.url_list.borrow());
342        new_response.is_body_empty.set(self.is_body_empty.get());
343
344        // Step 3. Return the result of creating a Response object,
345        // given clonedResponse, this’s headers’s guard, and this’s relevant realm.
346        clone_body_stream_for_dom_body(&self.body_stream, &new_response.body_stream, can_gc)?;
347        // The cloned response must not receive network chunks directly; it is fed via the tee branch.
348        new_response.fetch_body_stream.set(None);
349
350        Ok(new_response)
351    }
352
353    /// <https://fetch.spec.whatwg.org/#dom-body-bodyused>
354    fn BodyUsed(&self) -> bool {
355        !self.is_body_empty.get() && self.is_body_used()
356    }
357
358    /// <https://fetch.spec.whatwg.org/#dom-body-body>
359    fn GetBody(&self) -> Option<DomRoot<ReadableStream>> {
360        self.body()
361    }
362
363    /// <https://fetch.spec.whatwg.org/#dom-body-text>
364    fn Text(&self, can_gc: CanGc) -> Rc<Promise> {
365        consume_body(self, BodyType::Text, can_gc)
366    }
367
368    /// <https://fetch.spec.whatwg.org/#dom-body-blob>
369    fn Blob(&self, can_gc: CanGc) -> Rc<Promise> {
370        consume_body(self, BodyType::Blob, can_gc)
371    }
372
373    /// <https://fetch.spec.whatwg.org/#dom-body-formdata>
374    fn FormData(&self, can_gc: CanGc) -> Rc<Promise> {
375        consume_body(self, BodyType::FormData, can_gc)
376    }
377
378    /// <https://fetch.spec.whatwg.org/#dom-body-json>
379    fn Json(&self, can_gc: CanGc) -> Rc<Promise> {
380        consume_body(self, BodyType::Json, can_gc)
381    }
382
383    /// <https://fetch.spec.whatwg.org/#dom-body-arraybuffer>
384    fn ArrayBuffer(&self, can_gc: CanGc) -> Rc<Promise> {
385        consume_body(self, BodyType::ArrayBuffer, can_gc)
386    }
387
388    /// <https://fetch.spec.whatwg.org/#dom-body-bytes>
389    fn Bytes(&self, can_gc: CanGc) -> std::rc::Rc<Promise> {
390        consume_body(self, BodyType::Bytes, can_gc)
391    }
392}
393
394/// <https://fetch.spec.whatwg.org/#initialize-a-response>
395fn initialize_response(
396    global: &GlobalScope,
397    can_gc: CanGc,
398    body: Option<ExtractedBody>,
399    init: &ResponseBinding::ResponseInit,
400    response: DomRoot<Response>,
401) -> Result<DomRoot<Response>, Error> {
402    // 1. If init["status"] is not in the range 200 to 599, inclusive, then throw a RangeError.
403    if init.status < 200 || init.status > 599 {
404        return Err(Error::Range(format!(
405            "init's status member should be in the range 200 to 599, inclusive, but is {}",
406            init.status
407        )));
408    }
409
410    // 2. If init["statusText"] is not the empty string and does not match the reason-phrase token production,
411    // then throw a TypeError.
412    if !is_valid_status_text(&init.statusText) {
413        return Err(Error::Type(
414            "init's statusText member does not match the reason-phrase token production"
415                .to_string(),
416        ));
417    }
418
419    // 3. Set response’s response’s status to init["status"].
420    // 4. Set response’s response’s status message to init["statusText"].
421    *response.status.borrow_mut() =
422        HttpStatus::new_raw(init.status, init.statusText.clone().into());
423
424    // 5. If init["headers"] exists, then fill response’s headers with init["headers"].
425    if let Some(ref headers_member) = init.headers {
426        response
427            .Headers(can_gc)
428            .fill(Some(headers_member.clone()))?;
429    }
430
431    // 6. If body is non-null, then:
432    if let Some(ref body) = body {
433        // 6.1 If response’s status is a null body status, then throw a TypeError.
434        if is_null_body_status(init.status) {
435            return Err(Error::Type(
436                "Body is non-null but init's status member is a null body status".to_string(),
437            ));
438        };
439
440        // 6.2 Set response’s body to body’s body.
441        response.body_stream.set(Some(&*body.stream));
442        response.fetch_body_stream.set(Some(&*body.stream));
443        response.is_body_empty.set(false);
444
445        // 6.3 If body’s type is non-null and response’s header list does not contain `Content-Type`,
446        // then append (`Content-Type`, body’s type) to response’s header list.
447        if let Some(content_type_contents) = &body.content_type {
448            if !response
449                .Headers(can_gc)
450                .Has(ByteString::new(b"Content-Type".to_vec()))
451                .unwrap()
452            {
453                response.Headers(can_gc).Append(
454                    ByteString::new(b"Content-Type".to_vec()),
455                    ByteString::new(content_type_contents.as_bytes().to_vec()),
456                )?;
457            }
458        };
459    } else {
460        // Reset FetchResponse to an in-memory stream with empty byte sequence here for
461        // no-init-body case. This is because the Response/Body types here do not hold onto a
462        // fetch Response object.
463        let stream = ReadableStream::new_from_bytes(global, Vec::with_capacity(0), can_gc)?;
464        response.body_stream.set(Some(&*stream));
465        response.fetch_body_stream.set(Some(&*stream));
466    }
467
468    Ok(response)
469}
470
471fn serialize_without_fragment(url: &ServoUrl) -> &str {
472    &url[..Position::AfterQuery]
473}
474
475impl Response {
476    pub(crate) fn set_type(&self, new_response_type: DOMResponseType, can_gc: CanGc) {
477        *self.response_type.borrow_mut() = new_response_type;
478        self.set_response_members_by_type(new_response_type, can_gc);
479    }
480
481    pub(crate) fn set_headers(
482        &self,
483        option_hyper_headers: Option<Serde<HyperHeaders>>,
484        can_gc: CanGc,
485    ) {
486        self.Headers(can_gc)
487            .set_headers(match option_hyper_headers {
488                Some(hyper_headers) => hyper_headers.into_inner(),
489                None => HyperHeaders::new(),
490            });
491    }
492
493    pub(crate) fn set_status(&self, status: &HttpStatus) {
494        self.status.borrow_mut().clone_from(status);
495    }
496
497    pub(crate) fn set_final_url(&self, final_url: ServoUrl) {
498        *self.url.borrow_mut() = Some(final_url);
499    }
500
501    pub(crate) fn set_redirected(&self, is_redirected: bool) {
502        self.redirected.set(is_redirected);
503    }
504
505    fn set_response_members_by_type(&self, response_type: DOMResponseType, can_gc: CanGc) {
506        match response_type {
507            DOMResponseType::Error => {
508                *self.status.borrow_mut() = HttpStatus::new_error();
509                self.set_headers(None, can_gc);
510            },
511            DOMResponseType::Opaque => {
512                *self.url_list.borrow_mut() = vec![];
513                *self.status.borrow_mut() = HttpStatus::new_error();
514                self.set_headers(None, can_gc);
515                self.body_stream.set(None);
516                self.fetch_body_stream.set(None);
517            },
518            DOMResponseType::Opaqueredirect => {
519                *self.status.borrow_mut() = HttpStatus::new_error();
520                self.set_headers(None, can_gc);
521                self.body_stream.set(None);
522                self.fetch_body_stream.set(None);
523            },
524            DOMResponseType::Default => {},
525            DOMResponseType::Basic => {},
526            DOMResponseType::Cors => {},
527        }
528    }
529
530    pub(crate) fn set_stream_consumer(&self, sc: Option<StreamConsumer>) {
531        *self.stream_consumer.borrow_mut() = sc;
532    }
533
534    pub(crate) fn stream_chunk(&self, chunk: Vec<u8>, can_gc: CanGc) {
535        self.is_body_empty.set(false);
536        // Note, are these two actually mutually exclusive?
537        if let Some(stream_consumer) = self.stream_consumer.borrow().as_ref() {
538            stream_consumer.consume_chunk(chunk.as_slice());
539        } else if let Some(body) = self.fetch_body_stream.get() {
540            body.enqueue_native(chunk, can_gc);
541        }
542    }
543
544    pub(crate) fn finish(&self, can_gc: CanGc) {
545        if let Some(body) = self.fetch_body_stream.get() {
546            body.controller_close_native(can_gc);
547        }
548        let stream_consumer = self.stream_consumer.borrow_mut().take();
549        if let Some(stream_consumer) = stream_consumer {
550            stream_consumer.stream_end();
551        }
552    }
553}