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, clone_body_stream_for_dom_body, consume_body,
22};
23use crate::dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
24use crate::dom::bindings::codegen::Bindings::ResponseBinding;
25use crate::dom::bindings::codegen::Bindings::ResponseBinding::{
26 ResponseMethods, ResponseType as DOMResponseType,
27};
28use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
29use crate::dom::bindings::error::{Error, Fallible};
30use crate::dom::bindings::reflector::DomGlobal;
31use crate::dom::bindings::root::{DomRoot, MutNullableDom};
32use crate::dom::bindings::str::{ByteString, USVString, serialize_jsval_to_json_utf8};
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::headers::{Guard, Headers, is_obs_text, is_vchar};
35use crate::dom::promise::Promise;
36use crate::dom::stream::readablestream::ReadableStream;
37use crate::dom::stream::underlyingsourcecontainer::UnderlyingSourceType;
38use crate::script_runtime::{CanGc, StreamConsumer};
39
40#[dom_struct]
41pub(crate) struct Response {
42 reflector_: Reflector,
43 headers_reflector: MutNullableDom<Headers>,
44 #[no_trace]
45 status: DomRefCell<HttpStatus>,
46 response_type: DomRefCell<DOMResponseType>,
47 #[no_trace]
51 url: DomRefCell<Option<ServoUrl>>,
52 #[no_trace]
53 url_list: DomRefCell<Vec<ServoUrl>>,
54 body_stream: MutNullableDom<ReadableStream>,
56 fetch_body_stream: MutNullableDom<ReadableStream>,
59 #[ignore_malloc_size_of = "StreamConsumer"]
60 stream_consumer: DomRefCell<Option<StreamConsumer>>,
61 redirected: Cell<bool>,
63 is_body_empty: 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 is_body_empty: Cell::new(true),
86 }
87 }
88
89 pub(crate) fn new(cx: &mut js::context::JSContext, global: &GlobalScope) -> DomRoot<Response> {
91 Self::new_with_proto(cx, global, None)
92 }
93
94 fn new_with_proto(
95 cx: &mut js::context::JSContext,
96 global: &GlobalScope,
97 proto: Option<HandleObject>,
98 ) -> DomRoot<Response> {
99 reflect_dom_object_with_proto_and_cx(
100 Box::new(Response::new_inherited(cx, global)),
101 global,
102 proto,
103 cx,
104 )
105 }
106
107 pub(crate) fn error_stream(&self, cx: &mut js::context::JSContext, error: Error) {
108 if let Some(body) = self.fetch_body_stream.get() {
109 body.error_native(cx, error);
110 }
111 }
112
113 pub(crate) fn is_disturbed(&self) -> bool {
114 let body_stream = self.body_stream.get();
115 body_stream
116 .as_ref()
117 .is_some_and(|stream| stream.is_disturbed())
118 }
119
120 pub(crate) fn is_locked(&self) -> bool {
121 let body_stream = self.body_stream.get();
122 body_stream
123 .as_ref()
124 .is_some_and(|stream| stream.is_locked())
125 }
126}
127
128impl BodyMixin for Response {
129 fn is_body_used(&self) -> bool {
130 self.is_disturbed()
131 }
132
133 fn is_unusable(&self) -> bool {
134 self.body_stream
135 .get()
136 .is_some_and(|stream| stream.is_disturbed() || stream.is_locked())
137 }
138
139 fn body(&self) -> Option<DomRoot<ReadableStream>> {
140 self.body_stream.get()
141 }
142
143 fn get_mime_type(&self, cx: &mut js::context::JSContext) -> Vec<u8> {
144 let headers = self.Headers(CanGc::from_cx(cx));
145 headers.extract_mime_type()
146 }
147}
148
149fn is_redirect_status(status: u16) -> bool {
151 status == 301 || status == 302 || status == 303 || status == 307 || status == 308
152}
153
154fn is_valid_status_text(status_text: &ByteString) -> bool {
156 for byte in status_text.iter() {
158 if !(*byte == b'\t' || *byte == b' ' || is_vchar(*byte) || is_obs_text(*byte)) {
159 return false;
160 }
161 }
162 true
163}
164
165fn is_null_body_status(status: u16) -> bool {
167 status == 101 || status == 204 || status == 205 || status == 304
168}
169
170impl ResponseMethods<crate::DomTypeHolder> for Response {
171 fn Constructor(
173 cx: &mut js::context::JSContext,
174 global: &GlobalScope,
175 proto: Option<HandleObject>,
176 body_init: Option<BodyInit>,
177 init: &ResponseBinding::ResponseInit,
178 ) -> Fallible<DomRoot<Response>> {
179 let response = Response::new_with_proto(cx, global, proto);
182 if body_init.is_some() {
183 response.is_body_empty.set(false);
184 }
185
186 response
189 .Headers(CanGc::from_cx(cx))
190 .set_guard(Guard::Response);
191
192 let body_with_type = match body_init {
195 Some(body) => Some(body.extract(cx, global, false)?),
196 None => None,
197 };
198
199 initialize_response(cx, global, body_with_type, init, response)
201 }
202
203 fn Error(cx: &mut js::context::JSContext, global: &GlobalScope) -> DomRoot<Response> {
205 let response = Response::new(cx, global);
206 *response.response_type.borrow_mut() = DOMResponseType::Error;
207 response
208 .Headers(CanGc::from_cx(cx))
209 .set_guard(Guard::Immutable);
210 *response.status.borrow_mut() = HttpStatus::new_error();
211 response
212 }
213
214 fn Redirect(
216 cx: &mut js::context::JSContext,
217 global: &GlobalScope,
218 url: USVString,
219 status: u16,
220 ) -> Fallible<DomRoot<Response>> {
221 let base_url = global.api_base_url();
223 let parsed_url = base_url.join(&url.0);
224
225 let url = match parsed_url {
227 Ok(url) => url,
228 Err(_) => return Err(Error::Type(c"ServoUrl could not be parsed".to_owned())),
229 };
230
231 if !is_redirect_status(status) {
233 return Err(Error::Range(c"status is not a redirect status".to_owned()));
234 }
235
236 let response = Response::new(cx, global);
239
240 *response.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
242
243 let url_bytestring =
245 ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
246 response
247 .Headers(CanGc::from_cx(cx))
248 .Set(ByteString::new(b"Location".to_vec()), url_bytestring)?;
249
250 response
253 .Headers(CanGc::from_cx(cx))
254 .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.into(), 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
280 .Headers(CanGc::from_cx(cx))
281 .set_guard(Guard::Response);
282
283 body.content_type = Some("application/json".into());
285 initialize_response(cx, global, Some(body), init, response)
286 }
287
288 fn Type(&self) -> DOMResponseType {
290 *self.response_type.borrow() }
292
293 fn Url(&self) -> USVString {
295 USVString(String::from(
296 (*self.url.borrow())
297 .as_ref()
298 .map(serialize_without_fragment)
299 .unwrap_or(""),
300 ))
301 }
302
303 fn Redirected(&self) -> bool {
310 self.redirected.get()
311 }
312
313 fn Status(&self) -> u16 {
315 self.status.borrow().raw_code()
316 }
317
318 fn Ok(&self) -> bool {
320 self.status.borrow().is_success()
321 }
322
323 fn StatusText(&self) -> ByteString {
325 ByteString::new(self.status.borrow().message().to_vec())
326 }
327
328 fn Headers(&self, can_gc: CanGc) -> DomRoot<Headers> {
330 self.headers_reflector
331 .or_init(|| Headers::for_response(&self.global(), can_gc))
332 }
333
334 fn Clone(&self, cx: &mut js::context::JSContext) -> Fallible<DomRoot<Response>> {
336 if self.is_unusable() {
338 return Err(Error::Type(c"cannot clone a disturbed response".to_owned()));
339 }
340
341 let new_response = Response::new(cx, &self.global());
343 new_response
344 .Headers(CanGc::from_cx(cx))
345 .copy_from_headers(self.Headers(CanGc::from_cx(cx)))?;
346 new_response
347 .Headers(CanGc::from_cx(cx))
348 .set_guard(self.Headers(CanGc::from_cx(cx)).get_guard());
349
350 *new_response.response_type.borrow_mut() = *self.response_type.borrow();
351 new_response
352 .status
353 .borrow_mut()
354 .clone_from(&self.status.borrow());
355 new_response.url.borrow_mut().clone_from(&self.url.borrow());
356 new_response
357 .url_list
358 .borrow_mut()
359 .clone_from(&self.url_list.borrow());
360 new_response.is_body_empty.set(self.is_body_empty.get());
361
362 clone_body_stream_for_dom_body(cx, &self.body_stream, &new_response.body_stream)?;
365 new_response.fetch_body_stream.set(None);
367
368 Ok(new_response)
369 }
370
371 fn BodyUsed(&self) -> bool {
373 !self.is_body_empty.get() && self.is_body_used()
374 }
375
376 fn GetBody(&self) -> Option<DomRoot<ReadableStream>> {
378 self.body()
379 }
380
381 fn Text(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
383 consume_body(cx, self, BodyType::Text)
384 }
385
386 fn Blob(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
388 consume_body(cx, self, BodyType::Blob)
389 }
390
391 fn FormData(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
393 consume_body(cx, self, BodyType::FormData)
394 }
395
396 fn Json(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
398 consume_body(cx, self, BodyType::Json)
399 }
400
401 fn ArrayBuffer(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
403 consume_body(cx, self, BodyType::ArrayBuffer)
404 }
405
406 fn Bytes(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
408 consume_body(cx, self, BodyType::Bytes)
409 }
410}
411
412fn initialize_response(
414 cx: &mut js::context::JSContext,
415 global: &GlobalScope,
416 body: Option<ExtractedBody>,
417 init: &ResponseBinding::ResponseInit,
418 response: DomRoot<Response>,
419) -> Result<DomRoot<Response>, Error> {
420 if init.status < 200 || init.status > 599 {
422 return Err(Error::Range(cformat!(
423 "init's status member should be in the range 200 to 599, inclusive, but is {}",
424 init.status
425 )));
426 }
427
428 if !is_valid_status_text(&init.statusText) {
431 return Err(Error::Type(
432 c"init's statusText member does not match the reason-phrase token production"
433 .to_owned(),
434 ));
435 }
436
437 *response.status.borrow_mut() =
440 HttpStatus::new_raw(init.status, init.statusText.clone().into());
441
442 if let Some(ref headers_member) = init.headers {
444 response
445 .Headers(CanGc::from_cx(cx))
446 .fill(Some(headers_member.clone()))?;
447 }
448
449 if let Some(ref body) = body {
451 if is_null_body_status(init.status) {
453 return Err(Error::Type(
454 c"Body is non-null but init's status member is a null body status".to_owned(),
455 ));
456 };
457
458 response.body_stream.set(Some(&*body.stream));
460 response.fetch_body_stream.set(Some(&*body.stream));
461 response.is_body_empty.set(false);
462
463 if let Some(content_type_contents) = &body.content_type &&
466 !response
467 .Headers(CanGc::from_cx(cx))
468 .Has(ByteString::new(b"Content-Type".to_vec()))
469 .unwrap()
470 {
471 response.Headers(CanGc::from_cx(cx)).Append(
472 ByteString::new(b"Content-Type".to_vec()),
473 ByteString::new(content_type_contents.as_bytes().to_vec()),
474 )?;
475 };
476 } else {
477 let stream = ReadableStream::new_from_bytes(cx, global, Vec::with_capacity(0))?;
481 response.body_stream.set(Some(&*stream));
482 response.fetch_body_stream.set(Some(&*stream));
483 }
484
485 Ok(response)
486}
487
488fn serialize_without_fragment(url: &ServoUrl) -> &str {
489 &url[..Position::AfterQuery]
490}
491
492impl Response {
493 pub(crate) fn set_type(&self, new_response_type: DOMResponseType, can_gc: CanGc) {
494 *self.response_type.borrow_mut() = new_response_type;
495 self.set_response_members_by_type(new_response_type, can_gc);
496 }
497
498 pub(crate) fn set_headers(
499 &self,
500 option_hyper_headers: Option<Serde<HyperHeaders>>,
501 can_gc: CanGc,
502 ) {
503 self.Headers(can_gc)
504 .set_headers(match option_hyper_headers {
505 Some(hyper_headers) => hyper_headers.into_inner(),
506 None => HyperHeaders::new(),
507 });
508 }
509
510 pub(crate) fn set_status(&self, status: &HttpStatus) {
511 self.status.borrow_mut().clone_from(status);
512 }
513
514 pub(crate) fn set_final_url(&self, final_url: ServoUrl) {
515 *self.url.borrow_mut() = Some(final_url);
516 }
517
518 pub(crate) fn set_redirected(&self, is_redirected: bool) {
519 self.redirected.set(is_redirected);
520 }
521
522 fn set_response_members_by_type(&self, response_type: DOMResponseType, can_gc: CanGc) {
523 match response_type {
524 DOMResponseType::Error => {
525 *self.status.borrow_mut() = HttpStatus::new_error();
526 self.set_headers(None, can_gc);
527 },
528 DOMResponseType::Opaque => {
529 *self.url_list.borrow_mut() = vec![];
530 *self.status.borrow_mut() = HttpStatus::new_error();
531 self.set_headers(None, can_gc);
532 self.body_stream.set(None);
533 self.fetch_body_stream.set(None);
534 },
535 DOMResponseType::Opaqueredirect => {
536 *self.status.borrow_mut() = HttpStatus::new_error();
537 self.set_headers(None, can_gc);
538 self.body_stream.set(None);
539 self.fetch_body_stream.set(None);
540 },
541 DOMResponseType::Default => {},
542 DOMResponseType::Basic => {},
543 DOMResponseType::Cors => {},
544 }
545 }
546
547 pub(crate) fn set_stream_consumer(&self, sc: Option<StreamConsumer>) {
548 *self.stream_consumer.borrow_mut() = sc;
549 }
550
551 pub(crate) fn stream_chunk(&self, cx: &mut js::context::JSContext, chunk: Vec<u8>) {
552 self.is_body_empty.set(false);
553 if let Some(stream_consumer) = self.stream_consumer.borrow().as_ref() {
555 stream_consumer.consume_chunk(chunk.as_slice());
556 } else if let Some(body) = self.fetch_body_stream.get() {
557 body.enqueue_native(cx, chunk);
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}