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::cformat;
15use servo_url::ServoUrl;
16use url::Position;
17
18use crate::body::{
19 BodyMixin, BodyType, Extractable, ExtractedBody, clone_body_stream_for_dom_body, consume_body,
20};
21use crate::dom::bindings::cell::DomRefCell;
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, Reflector, reflect_dom_object_with_proto_and_cx};
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::promise::Promise;
35use crate::dom::stream::readablestream::ReadableStream;
36use crate::dom::stream::underlyingsourcecontainer::UnderlyingSourceType;
37use crate::script_runtime::{CanGc, StreamConsumer};
38
39#[dom_struct]
40pub(crate) struct Response {
41 reflector_: Reflector,
42 headers_reflector: MutNullableDom<Headers>,
43 #[no_trace]
44 status: DomRefCell<HttpStatus>,
45 response_type: DomRefCell<DOMResponseType>,
46 #[no_trace]
50 url: DomRefCell<Option<ServoUrl>>,
51 #[no_trace]
52 url_list: DomRefCell<Vec<ServoUrl>>,
53 body_stream: MutNullableDom<ReadableStream>,
55 fetch_body_stream: MutNullableDom<ReadableStream>,
58 #[ignore_malloc_size_of = "StreamConsumer"]
59 stream_consumer: DomRefCell<Option<StreamConsumer>>,
60 redirected: Cell<bool>,
62 is_body_empty: Cell<bool>,
63}
64
65impl Response {
66 pub(crate) fn new_inherited(cx: &mut js::context::JSContext, global: &GlobalScope) -> Response {
67 let stream = ReadableStream::new_with_external_underlying_source(
68 cx,
69 global,
70 UnderlyingSourceType::FetchResponse,
71 )
72 .expect("Failed to create ReadableStream with external underlying source");
73 Response {
74 reflector_: Reflector::new(),
75 headers_reflector: Default::default(),
76 status: DomRefCell::new(HttpStatus::default()),
77 response_type: DomRefCell::new(DOMResponseType::Default),
78 url: DomRefCell::new(None),
79 url_list: DomRefCell::new(vec![]),
80 body_stream: MutNullableDom::new(Some(&*stream)),
81 fetch_body_stream: MutNullableDom::new(Some(&*stream)),
82 stream_consumer: DomRefCell::new(None),
83 redirected: Cell::new(false),
84 is_body_empty: Cell::new(true),
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, error: Error, can_gc: CanGc) {
107 if let Some(body) = self.fetch_body_stream.get() {
108 body.error_native(error, can_gc);
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, can_gc: CanGc) -> Vec<u8> {
143 let headers = self.Headers(can_gc);
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 if body_init.is_some() {
182 response.is_body_empty.set(false);
183 }
184
185 response
188 .Headers(CanGc::from_cx(cx))
189 .set_guard(Guard::Response);
190
191 let body_with_type = match body_init {
194 Some(body) => Some(body.extract(cx, global, false)?),
195 None => None,
196 };
197
198 initialize_response(cx, global, body_with_type, init, response)
200 }
201
202 fn Error(cx: &mut js::context::JSContext, global: &GlobalScope) -> DomRoot<Response> {
204 let response = Response::new(cx, global);
205 *response.response_type.borrow_mut() = DOMResponseType::Error;
206 response
207 .Headers(CanGc::from_cx(cx))
208 .set_guard(Guard::Immutable);
209 *response.status.borrow_mut() = HttpStatus::new_error();
210 response
211 }
212
213 fn Redirect(
215 cx: &mut js::context::JSContext,
216 global: &GlobalScope,
217 url: USVString,
218 status: u16,
219 ) -> Fallible<DomRoot<Response>> {
220 let base_url = global.api_base_url();
222 let parsed_url = base_url.join(&url.0);
223
224 let url = match parsed_url {
226 Ok(url) => url,
227 Err(_) => return Err(Error::Type(c"ServoUrl could not be parsed".to_owned())),
228 };
229
230 if !is_redirect_status(status) {
232 return Err(Error::Range(c"status is not a redirect status".to_owned()));
233 }
234
235 let response = Response::new(cx, global);
238
239 *response.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
241
242 let url_bytestring =
244 ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
245 response
246 .Headers(CanGc::from_cx(cx))
247 .Set(ByteString::new(b"Location".to_vec()), url_bytestring)?;
248
249 response
252 .Headers(CanGc::from_cx(cx))
253 .set_guard(Guard::Immutable);
254
255 Ok(response)
257 }
258
259 fn CreateFromJson(
261 cx: &mut js::context::JSContext,
262 global: &GlobalScope,
263 data: HandleValue,
264 init: &ResponseBinding::ResponseInit,
265 ) -> Fallible<DomRoot<Response>> {
266 let json_str = serialize_jsval_to_json_utf8(cx.into(), data)?;
268
269 let body_init = BodyInit::String(json_str);
273 let mut body = body_init.extract(cx, global, false)?;
274
275 let response = Response::new(cx, global);
278 response
279 .Headers(CanGc::from_cx(cx))
280 .set_guard(Guard::Response);
281
282 body.content_type = Some("application/json".into());
284 initialize_response(cx, global, Some(body), init, response)
285 }
286
287 fn Type(&self) -> DOMResponseType {
289 *self.response_type.borrow() }
291
292 fn Url(&self) -> USVString {
294 USVString(String::from(
295 (*self.url.borrow())
296 .as_ref()
297 .map(serialize_without_fragment)
298 .unwrap_or(""),
299 ))
300 }
301
302 fn Redirected(&self) -> bool {
309 self.redirected.get()
310 }
311
312 fn Status(&self) -> u16 {
314 self.status.borrow().raw_code()
315 }
316
317 fn Ok(&self) -> bool {
319 self.status.borrow().is_success()
320 }
321
322 fn StatusText(&self) -> ByteString {
324 ByteString::new(self.status.borrow().message().to_vec())
325 }
326
327 fn Headers(&self, can_gc: CanGc) -> DomRoot<Headers> {
329 self.headers_reflector
330 .or_init(|| Headers::for_response(&self.global(), can_gc))
331 }
332
333 fn Clone(&self, cx: &mut js::context::JSContext) -> Fallible<DomRoot<Response>> {
335 if self.is_unusable() {
337 return Err(Error::Type(c"cannot clone a disturbed response".to_owned()));
338 }
339
340 let new_response = Response::new(cx, &self.global());
342 new_response
343 .Headers(CanGc::from_cx(cx))
344 .copy_from_headers(self.Headers(CanGc::from_cx(cx)))?;
345 new_response
346 .Headers(CanGc::from_cx(cx))
347 .set_guard(self.Headers(CanGc::from_cx(cx)).get_guard());
348
349 *new_response.response_type.borrow_mut() = *self.response_type.borrow();
350 new_response
351 .status
352 .borrow_mut()
353 .clone_from(&self.status.borrow());
354 new_response.url.borrow_mut().clone_from(&self.url.borrow());
355 new_response
356 .url_list
357 .borrow_mut()
358 .clone_from(&self.url_list.borrow());
359 new_response.is_body_empty.set(self.is_body_empty.get());
360
361 clone_body_stream_for_dom_body(cx, &self.body_stream, &new_response.body_stream)?;
364 new_response.fetch_body_stream.set(None);
366
367 Ok(new_response)
368 }
369
370 fn BodyUsed(&self) -> bool {
372 !self.is_body_empty.get() && self.is_body_used()
373 }
374
375 fn GetBody(&self) -> Option<DomRoot<ReadableStream>> {
377 self.body()
378 }
379
380 fn Text(&self, can_gc: CanGc) -> Rc<Promise> {
382 consume_body(self, BodyType::Text, can_gc)
383 }
384
385 fn Blob(&self, can_gc: CanGc) -> Rc<Promise> {
387 consume_body(self, BodyType::Blob, can_gc)
388 }
389
390 fn FormData(&self, can_gc: CanGc) -> Rc<Promise> {
392 consume_body(self, BodyType::FormData, can_gc)
393 }
394
395 fn Json(&self, can_gc: CanGc) -> Rc<Promise> {
397 consume_body(self, BodyType::Json, can_gc)
398 }
399
400 fn ArrayBuffer(&self, can_gc: CanGc) -> Rc<Promise> {
402 consume_body(self, BodyType::ArrayBuffer, can_gc)
403 }
404
405 fn Bytes(&self, can_gc: CanGc) -> std::rc::Rc<Promise> {
407 consume_body(self, BodyType::Bytes, can_gc)
408 }
409}
410
411fn initialize_response(
413 cx: &mut js::context::JSContext,
414 global: &GlobalScope,
415 body: Option<ExtractedBody>,
416 init: &ResponseBinding::ResponseInit,
417 response: DomRoot<Response>,
418) -> Result<DomRoot<Response>, Error> {
419 if init.status < 200 || init.status > 599 {
421 return Err(Error::Range(cformat!(
422 "init's status member should be in the range 200 to 599, inclusive, but is {}",
423 init.status
424 )));
425 }
426
427 if !is_valid_status_text(&init.statusText) {
430 return Err(Error::Type(
431 c"init's statusText member does not match the reason-phrase token production"
432 .to_owned(),
433 ));
434 }
435
436 *response.status.borrow_mut() =
439 HttpStatus::new_raw(init.status, init.statusText.clone().into());
440
441 if let Some(ref headers_member) = init.headers {
443 response
444 .Headers(CanGc::from_cx(cx))
445 .fill(Some(headers_member.clone()))?;
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 response.is_body_empty.set(false);
461
462 if let Some(content_type_contents) = &body.content_type {
465 if !response
466 .Headers(CanGc::from_cx(cx))
467 .Has(ByteString::new(b"Content-Type".to_vec()))
468 .unwrap()
469 {
470 response.Headers(CanGc::from_cx(cx)).Append(
471 ByteString::new(b"Content-Type".to_vec()),
472 ByteString::new(content_type_contents.as_bytes().to_vec()),
473 )?;
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, chunk: Vec<u8>, can_gc: CanGc) {
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(chunk, can_gc);
558 }
559 }
560
561 pub(crate) fn finish(&self, can_gc: CanGc) {
562 if let Some(body) = self.fetch_body_stream.get() {
563 body.controller_close_native(can_gc);
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}