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