1use std::cell::Cell;
6use std::str::FromStr;
7
8use bytes::Bytes;
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 script_bindings::cell::DomRefCell;
15use script_bindings::cformat;
16use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
17use script_bindings::str::DOMString;
18use servo_url::ServoUrl;
19use url::Position;
20
21use crate::dom::RootedPromise;
22use crate::dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
23use crate::dom::bindings::codegen::Bindings::ResponseBinding;
24use crate::dom::bindings::codegen::Bindings::ResponseBinding::{
25 ResponseMethods, ResponseType as DOMResponseType,
26};
27use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
28use crate::dom::bindings::error::{Error, Fallible};
29use crate::dom::bindings::reflector::DomGlobal;
30use crate::dom::bindings::root::{DomRoot, MutNullableDom};
31use crate::dom::bindings::str::{ByteString, USVString, serialize_jsval_to_json_utf8};
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::headers::{Guard, Headers, is_obs_text, is_vchar};
34use crate::dom::stream::readablestream::ReadableStream;
35use crate::dom::stream::underlyingsourcecontainer::UnderlyingSourceType;
36use crate::fetch::body::{
37 BodyMixin, BodyType, Extractable, ExtractedBody, body_text_stream,
38 clone_body_stream_for_dom_body, consume_body,
39};
40use crate::runtime::script_runtime::StreamConsumer;
41
42#[dom_struct]
43pub(crate) struct Response {
44 reflector_: Reflector,
45 headers_reflector: MutNullableDom<Headers>,
46 #[no_trace]
47 status: DomRefCell<HttpStatus>,
48 response_type: DomRefCell<DOMResponseType>,
49 #[no_trace]
53 url: DomRefCell<Option<ServoUrl>>,
54 #[no_trace]
55 url_list: DomRefCell<Vec<ServoUrl>>,
56 body_stream: MutNullableDom<ReadableStream>,
58 fetch_body_stream: MutNullableDom<ReadableStream>,
61 #[ignore_malloc_size_of = "StreamConsumer"]
62 stream_consumer: DomRefCell<Option<StreamConsumer>>,
63 redirected: Cell<bool>,
65}
66
67impl Response {
68 pub(crate) fn new_inherited(body_stream: Option<&ReadableStream>) -> Response {
69 Response {
70 reflector_: Reflector::new(),
71 headers_reflector: Default::default(),
72 status: DomRefCell::new(HttpStatus::default()),
73 response_type: DomRefCell::new(DOMResponseType::Default),
74 url: DomRefCell::new(None),
75 url_list: DomRefCell::new(vec![]),
76 body_stream: MutNullableDom::new(body_stream),
77 fetch_body_stream: MutNullableDom::new(body_stream),
78 stream_consumer: DomRefCell::new(None),
79 redirected: Cell::new(false),
80 }
81 }
82
83 pub(crate) fn new(cx: &mut js::context::JSContext, global: &GlobalScope) -> DomRoot<Response> {
85 Self::new_with_proto(cx, global, None)
86 }
87
88 fn new_with_proto(
89 cx: &mut js::context::JSContext,
90 global: &GlobalScope,
91 proto: Option<HandleObject>,
92 ) -> DomRoot<Response> {
93 reflect_dom_object_with_proto(cx, Box::new(Response::new_inherited(None)), global, proto)
94 }
95
96 pub(crate) fn new_fetch_response(
99 cx: &mut js::context::JSContext,
100 global: &GlobalScope,
101 ) -> DomRoot<Response> {
102 let stream = ReadableStream::new_with_external_underlying_source(
103 cx,
104 global,
105 UnderlyingSourceType::FetchResponse,
106 )
107 .expect("Failed to create ReadableStream with external underlying source");
108 reflect_dom_object_with_proto(
109 cx,
110 Box::new(Response::new_inherited(Some(&stream))),
111 global,
112 None,
113 )
114 }
115
116 pub(crate) fn error_stream(&self, cx: &mut js::context::JSContext, error: Error) {
117 if let Some(body) = self.fetch_body_stream.get() {
118 body.error_native(cx, error);
119 }
120 }
121
122 pub(crate) fn is_disturbed(&self) -> bool {
123 let body_stream = self.body_stream.get();
124 body_stream
125 .as_ref()
126 .is_some_and(|stream| stream.is_disturbed())
127 }
128
129 pub(crate) fn is_locked(&self) -> bool {
130 let body_stream = self.body_stream.get();
131 body_stream
132 .as_ref()
133 .is_some_and(|stream| stream.is_locked())
134 }
135}
136
137impl BodyMixin for Response {
138 fn is_body_used(&self) -> bool {
139 self.is_disturbed()
140 }
141
142 fn is_unusable(&self) -> bool {
143 self.body_stream
144 .get()
145 .is_some_and(|stream| stream.is_disturbed() || stream.is_locked())
146 }
147
148 fn body(&self) -> Option<DomRoot<ReadableStream>> {
149 self.body_stream.get()
150 }
151
152 fn get_mime_type(&self, cx: &mut js::context::JSContext) -> Vec<u8> {
153 let headers = self.Headers(cx);
154 headers.extract_mime_type()
155 }
156}
157
158fn is_redirect_status(status: u16) -> bool {
160 status == 301 || status == 302 || status == 303 || status == 307 || status == 308
161}
162
163fn is_valid_status_text(status_text: &ByteString) -> bool {
165 for byte in status_text.iter() {
167 if !(*byte == b'\t' || *byte == b' ' || is_vchar(*byte) || is_obs_text(*byte)) {
168 return false;
169 }
170 }
171 true
172}
173
174fn is_null_body_status(status: u16) -> bool {
176 status == 101 || status == 204 || status == 205 || status == 304
177}
178
179impl ResponseMethods<crate::DomTypeHolder> for Response {
180 fn Constructor(
182 cx: &mut js::context::JSContext,
183 global: &GlobalScope,
184 proto: Option<HandleObject>,
185 body_init: Option<BodyInit>,
186 init: &ResponseBinding::ResponseInit,
187 ) -> Fallible<DomRoot<Response>> {
188 let response = Response::new_with_proto(cx, global, proto);
191
192 response.Headers(cx).set_guard(Guard::Response);
195
196 let body_with_type = match body_init {
199 Some(body) => Some(body.extract(cx, global, false)?),
200 None => None,
201 };
202
203 initialize_response(cx, body_with_type, init, response)
205 }
206
207 fn Error(cx: &mut js::context::JSContext, global: &GlobalScope) -> DomRoot<Response> {
209 let response = Response::new(cx, global);
210 *response.response_type.borrow_mut() = DOMResponseType::Error;
211 response.Headers(cx).set_guard(Guard::Immutable);
212 *response.status.borrow_mut() = HttpStatus::new_error();
213 response
214 }
215
216 fn Redirect(
218 cx: &mut js::context::JSContext,
219 global: &GlobalScope,
220 url: USVString,
221 status: u16,
222 ) -> Fallible<DomRoot<Response>> {
223 let base_url = global.api_base_url();
225 let parsed_url = base_url.join(&url.0);
226
227 let url = match parsed_url {
229 Ok(url) => url,
230 Err(_) => return Err(Error::Type(c"ServoUrl could not be parsed".to_owned())),
231 };
232
233 if !is_redirect_status(status) {
235 return Err(Error::Range(c"status is not a redirect status".to_owned()));
236 }
237
238 let response = Response::new(cx, global);
241
242 *response.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
244
245 let url_bytestring =
247 ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
248 response
249 .Headers(cx)
250 .Set(ByteString::new(b"Location".to_vec()), url_bytestring)?;
251
252 response.Headers(cx).set_guard(Guard::Immutable);
255
256 Ok(response)
258 }
259
260 fn CreateFromJson(
262 cx: &mut js::context::JSContext,
263 global: &GlobalScope,
264 data: HandleValue,
265 init: &ResponseBinding::ResponseInit,
266 ) -> Fallible<DomRoot<Response>> {
267 let json_str = serialize_jsval_to_json_utf8(cx, data)?;
269
270 let body_init = BodyInit::String(json_str);
274 let mut body = body_init.extract(cx, global, false)?;
275
276 let response = Response::new(cx, global);
279 response.Headers(cx).set_guard(Guard::Response);
280
281 body.content_type = Some(DOMString::from_static("application/json"));
283 initialize_response(cx, Some(body), init, response)
284 }
285
286 fn Type(&self) -> DOMResponseType {
288 *self.response_type.borrow() }
290
291 fn Url(&self) -> USVString {
293 USVString(String::from(
294 (*self.url.borrow())
295 .as_ref()
296 .map(serialize_without_fragment)
297 .unwrap_or(""),
298 ))
299 }
300
301 fn Redirected(&self) -> bool {
308 self.redirected.get()
309 }
310
311 fn Status(&self) -> u16 {
313 self.status.borrow().raw_code()
314 }
315
316 fn Ok(&self) -> bool {
318 self.status.borrow().is_success()
319 }
320
321 fn StatusText(&self) -> ByteString {
323 ByteString::new(self.status.borrow().message().to_vec())
324 }
325
326 fn Headers(&self, cx: &mut js::context::JSContext) -> DomRoot<Headers> {
328 self.headers_reflector
329 .or_init(|| Headers::for_response(cx, &self.global()))
330 }
331
332 fn Clone(&self, cx: &mut js::context::JSContext) -> Fallible<DomRoot<Response>> {
334 if self.is_unusable() {
336 return Err(Error::Type(c"cannot clone a disturbed response".to_owned()));
337 }
338
339 let new_response = Response::new(cx, &self.global());
341 new_response
342 .Headers(cx)
343 .copy_from_headers(&self.Headers(cx))?;
344 new_response
345 .Headers(cx)
346 .set_guard(self.Headers(cx).get_guard());
347
348 *new_response.response_type.borrow_mut() = *self.response_type.borrow();
349 new_response
350 .status
351 .borrow_mut()
352 .clone_from(&self.status.borrow());
353 new_response.url.borrow_mut().clone_from(&self.url.borrow());
354 new_response
355 .url_list
356 .borrow_mut()
357 .clone_from(&self.url_list.borrow());
358
359 clone_body_stream_for_dom_body(cx, &self.body_stream, &new_response.body_stream)?;
362 new_response.fetch_body_stream.set(None);
364
365 Ok(new_response)
366 }
367
368 fn BodyUsed(&self) -> bool {
370 self.is_body_used()
371 }
372
373 fn GetBody(&self) -> Option<DomRoot<ReadableStream>> {
375 self.body()
376 }
377
378 fn Text(&self, cx: &mut js::context::JSContext) -> RootedPromise {
380 consume_body(cx, self, BodyType::Text)
381 }
382
383 fn Blob(&self, cx: &mut js::context::JSContext) -> RootedPromise {
385 consume_body(cx, self, BodyType::Blob)
386 }
387
388 fn FormData(&self, cx: &mut js::context::JSContext) -> RootedPromise {
390 consume_body(cx, self, BodyType::FormData)
391 }
392
393 fn Json(&self, cx: &mut js::context::JSContext) -> RootedPromise {
395 consume_body(cx, self, BodyType::Json)
396 }
397
398 fn ArrayBuffer(&self, cx: &mut js::context::JSContext) -> RootedPromise {
400 consume_body(cx, self, BodyType::ArrayBuffer)
401 }
402
403 fn Bytes(&self, cx: &mut js::context::JSContext) -> RootedPromise {
405 consume_body(cx, self, BodyType::Bytes)
406 }
407
408 fn TextStream(&self, cx: &mut js::context::JSContext) -> Fallible<DomRoot<ReadableStream>> {
410 body_text_stream(cx, self)
411 }
412}
413
414fn initialize_response(
416 cx: &mut js::context::JSContext,
417 body: Option<ExtractedBody>,
418 init: &ResponseBinding::ResponseInit,
419 response: DomRoot<Response>,
420) -> Result<DomRoot<Response>, Error> {
421 if init.status < 200 || init.status > 599 {
423 return Err(Error::Range(cformat!(
424 "init's status member should be in the range 200 to 599, inclusive, but is {}",
425 init.status
426 )));
427 }
428
429 if !is_valid_status_text(&init.statusText) {
432 return Err(Error::Type(
433 c"init's statusText member does not match the reason-phrase token production"
434 .to_owned(),
435 ));
436 }
437
438 *response.status.borrow_mut() =
441 HttpStatus::new_raw(init.status, init.statusText.clone().into());
442
443 if let Some(ref headers_member) = init.headers {
445 response.Headers(cx).fill(Some(headers_member))?;
446 }
447
448 if let Some(ref body) = body {
450 if is_null_body_status(init.status) {
452 return Err(Error::Type(
453 c"Body is non-null but init's status member is a null body status".to_owned(),
454 ));
455 };
456
457 response.body_stream.set(Some(&*body.stream));
459 response.fetch_body_stream.set(Some(&*body.stream));
460
461 if let Some(content_type_contents) = &body.content_type &&
464 !response
465 .Headers(cx)
466 .Has(ByteString::new(b"Content-Type".to_vec()))
467 .unwrap()
468 {
469 response.Headers(cx).Append(
470 ByteString::new(b"Content-Type".to_vec()),
471 ByteString::new(content_type_contents.as_bytes(cx.no_gc()).to_vec()),
472 )?;
473 };
474 } else {
475 response.body_stream.set(None);
476 response.fetch_body_stream.set(None);
477 }
478
479 Ok(response)
480}
481
482fn serialize_without_fragment(url: &ServoUrl) -> &str {
483 &url[..Position::AfterQuery]
484}
485
486impl Response {
487 pub(crate) fn set_type(
488 &self,
489 cx: &mut js::context::JSContext,
490 new_response_type: DOMResponseType,
491 ) {
492 *self.response_type.borrow_mut() = new_response_type;
493 self.set_response_members_by_type(cx, new_response_type);
494 }
495
496 pub(crate) fn set_headers(
497 &self,
498 cx: &mut js::context::JSContext,
499 option_hyper_headers: Option<Serde<HyperHeaders>>,
500 ) {
501 self.Headers(cx).set_headers(match option_hyper_headers {
502 Some(hyper_headers) => hyper_headers.into_inner(),
503 None => HyperHeaders::new(),
504 });
505 }
506
507 pub(crate) fn set_status(&self, status: &HttpStatus) {
508 self.status.borrow_mut().clone_from(status);
509 }
510
511 pub(crate) fn set_final_url(&self, final_url: ServoUrl) {
512 *self.url.borrow_mut() = Some(final_url);
513 }
514
515 pub(crate) fn set_redirected(&self, is_redirected: bool) {
516 self.redirected.set(is_redirected);
517 }
518
519 fn set_response_members_by_type(
520 &self,
521 cx: &mut js::context::JSContext,
522 response_type: DOMResponseType,
523 ) {
524 match response_type {
525 DOMResponseType::Error => {
526 *self.status.borrow_mut() = HttpStatus::new_error();
527 self.set_headers(cx, None);
528 },
529 DOMResponseType::Opaque => {
530 *self.url_list.borrow_mut() = vec![];
531 *self.status.borrow_mut() = HttpStatus::new_error();
532 self.set_headers(cx, None);
533 self.body_stream.set(None);
534 self.fetch_body_stream.set(None);
535 },
536 DOMResponseType::Opaqueredirect => {
537 *self.status.borrow_mut() = HttpStatus::new_error();
538 self.set_headers(cx, None);
539 self.body_stream.set(None);
540 self.fetch_body_stream.set(None);
541 },
542 DOMResponseType::Default => {},
543 DOMResponseType::Basic => {},
544 DOMResponseType::Cors => {},
545 }
546 }
547
548 pub(crate) fn set_stream_consumer(&self, sc: Option<StreamConsumer>) {
549 *self.stream_consumer.borrow_mut() = sc;
550 }
551
552 pub(crate) fn stream_chunk(&self, cx: &mut js::context::JSContext, chunk: Bytes) {
553 if let Some(stream_consumer) = self.stream_consumer.borrow().as_ref() {
555 stream_consumer.consume_chunk(&chunk);
556 } else if let Some(body) = self.fetch_body_stream.get() {
557 body.enqueue_native(cx, chunk.to_vec());
558 }
559 }
560
561 pub(crate) fn finish(&self, cx: &mut js::context::JSContext) {
562 if let Some(body) = self.fetch_body_stream.get() {
563 body.controller_close_native(cx);
564 }
565 let stream_consumer = self.stream_consumer.borrow_mut().take();
566 if let Some(stream_consumer) = stream_consumer {
567 stream_consumer.stream_end();
568 }
569 }
570}