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