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