1use std::cell::Cell;
6use std::rc::Rc;
7use std::str::FromStr;
8
9use bytes::Bytes;
10use dom_struct::dom_struct;
11use http::header::HeaderMap as HyperHeaders;
12use hyper_serde::Serde;
13use js::rust::{HandleObject, HandleValue};
14use net_traits::http_status::HttpStatus;
15use script_bindings::cell::DomRefCell;
16use script_bindings::cformat;
17use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
18use servo_url::ServoUrl;
19use url::Position;
20
21use crate::dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
22use crate::dom::bindings::codegen::Bindings::ResponseBinding;
23use crate::dom::bindings::codegen::Bindings::ResponseBinding::{
24 ResponseMethods, ResponseType as DOMResponseType,
25};
26use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
27use crate::dom::bindings::error::{Error, Fallible};
28use crate::dom::bindings::reflector::DomGlobal;
29use crate::dom::bindings::root::{DomRoot, MutNullableDom};
30use crate::dom::bindings::str::{ByteString, USVString, serialize_jsval_to_json_utf8};
31use crate::dom::globalscope::GlobalScope;
32use crate::dom::headers::{Guard, Headers, is_obs_text, is_vchar};
33use crate::dom::promise::Promise;
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(stream: DomRoot<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(Some(&*stream)),
77 fetch_body_stream: MutNullableDom::new(Some(&*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 let stream = ReadableStream::new_with_external_underlying_source(
94 cx,
95 global,
96 UnderlyingSourceType::FetchResponse,
97 )
98 .expect("Failed to create ReadableStream with external underlying source");
99 reflect_dom_object_with_proto(cx, Box::new(Response::new_inherited(stream)), global, proto)
100 }
101
102 pub(crate) fn error_stream(&self, cx: &mut js::context::JSContext, error: Error) {
103 if let Some(body) = self.fetch_body_stream.get() {
104 body.error_native(cx, error);
105 }
106 }
107
108 pub(crate) fn is_disturbed(&self) -> bool {
109 let body_stream = self.body_stream.get();
110 body_stream
111 .as_ref()
112 .is_some_and(|stream| stream.is_disturbed())
113 }
114
115 pub(crate) fn is_locked(&self) -> bool {
116 let body_stream = self.body_stream.get();
117 body_stream
118 .as_ref()
119 .is_some_and(|stream| stream.is_locked())
120 }
121}
122
123impl BodyMixin for Response {
124 fn is_body_used(&self) -> bool {
125 self.is_disturbed()
126 }
127
128 fn is_unusable(&self) -> bool {
129 self.body_stream
130 .get()
131 .is_some_and(|stream| stream.is_disturbed() || stream.is_locked())
132 }
133
134 fn body(&self) -> Option<DomRoot<ReadableStream>> {
135 self.body_stream.get()
136 }
137
138 fn get_mime_type(&self, cx: &mut js::context::JSContext) -> Vec<u8> {
139 let headers = self.Headers(cx);
140 headers.extract_mime_type()
141 }
142}
143
144fn is_redirect_status(status: u16) -> bool {
146 status == 301 || status == 302 || status == 303 || status == 307 || status == 308
147}
148
149fn is_valid_status_text(status_text: &ByteString) -> bool {
151 for byte in status_text.iter() {
153 if !(*byte == b'\t' || *byte == b' ' || is_vchar(*byte) || is_obs_text(*byte)) {
154 return false;
155 }
156 }
157 true
158}
159
160fn is_null_body_status(status: u16) -> bool {
162 status == 101 || status == 204 || status == 205 || status == 304
163}
164
165impl ResponseMethods<crate::DomTypeHolder> for Response {
166 fn Constructor(
168 cx: &mut js::context::JSContext,
169 global: &GlobalScope,
170 proto: Option<HandleObject>,
171 body_init: Option<BodyInit>,
172 init: &ResponseBinding::ResponseInit,
173 ) -> Fallible<DomRoot<Response>> {
174 let response = Response::new_with_proto(cx, global, proto);
177
178 response.Headers(cx).set_guard(Guard::Response);
181
182 let body_with_type = match body_init {
185 Some(body) => Some(body.extract(cx, global, false)?),
186 None => None,
187 };
188
189 initialize_response(cx, body_with_type, init, response)
191 }
192
193 fn Error(cx: &mut js::context::JSContext, global: &GlobalScope) -> DomRoot<Response> {
195 let response = Response::new(cx, global);
196 *response.response_type.borrow_mut() = DOMResponseType::Error;
197 response.Headers(cx).set_guard(Guard::Immutable);
198 *response.status.borrow_mut() = HttpStatus::new_error();
199 response
200 }
201
202 fn Redirect(
204 cx: &mut js::context::JSContext,
205 global: &GlobalScope,
206 url: USVString,
207 status: u16,
208 ) -> Fallible<DomRoot<Response>> {
209 let base_url = global.api_base_url();
211 let parsed_url = base_url.join(&url.0);
212
213 let url = match parsed_url {
215 Ok(url) => url,
216 Err(_) => return Err(Error::Type(c"ServoUrl could not be parsed".to_owned())),
217 };
218
219 if !is_redirect_status(status) {
221 return Err(Error::Range(c"status is not a redirect status".to_owned()));
222 }
223
224 let response = Response::new(cx, global);
227
228 *response.status.borrow_mut() = HttpStatus::new_raw(status, vec![]);
230
231 let url_bytestring =
233 ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
234 response
235 .Headers(cx)
236 .Set(ByteString::new(b"Location".to_vec()), url_bytestring)?;
237
238 response.Headers(cx).set_guard(Guard::Immutable);
241
242 Ok(response)
244 }
245
246 fn CreateFromJson(
248 cx: &mut js::context::JSContext,
249 global: &GlobalScope,
250 data: HandleValue,
251 init: &ResponseBinding::ResponseInit,
252 ) -> Fallible<DomRoot<Response>> {
253 let json_str = serialize_jsval_to_json_utf8(cx, data)?;
255
256 let body_init = BodyInit::String(json_str);
260 let mut body = body_init.extract(cx, global, false)?;
261
262 let response = Response::new(cx, global);
265 response.Headers(cx).set_guard(Guard::Response);
266
267 body.content_type = Some("application/json".into());
269 initialize_response(cx, Some(body), init, response)
270 }
271
272 fn Type(&self) -> DOMResponseType {
274 *self.response_type.borrow() }
276
277 fn Url(&self) -> USVString {
279 USVString(String::from(
280 (*self.url.borrow())
281 .as_ref()
282 .map(serialize_without_fragment)
283 .unwrap_or(""),
284 ))
285 }
286
287 fn Redirected(&self) -> bool {
294 self.redirected.get()
295 }
296
297 fn Status(&self) -> u16 {
299 self.status.borrow().raw_code()
300 }
301
302 fn Ok(&self) -> bool {
304 self.status.borrow().is_success()
305 }
306
307 fn StatusText(&self) -> ByteString {
309 ByteString::new(self.status.borrow().message().to_vec())
310 }
311
312 fn Headers(&self, cx: &mut js::context::JSContext) -> DomRoot<Headers> {
314 self.headers_reflector
315 .or_init(|| Headers::for_response(cx, &self.global()))
316 }
317
318 fn Clone(&self, cx: &mut js::context::JSContext) -> Fallible<DomRoot<Response>> {
320 if self.is_unusable() {
322 return Err(Error::Type(c"cannot clone a disturbed response".to_owned()));
323 }
324
325 let new_response = Response::new(cx, &self.global());
327 new_response
328 .Headers(cx)
329 .copy_from_headers(&self.Headers(cx))?;
330 new_response
331 .Headers(cx)
332 .set_guard(self.Headers(cx).get_guard());
333
334 *new_response.response_type.borrow_mut() = *self.response_type.borrow();
335 new_response
336 .status
337 .borrow_mut()
338 .clone_from(&self.status.borrow());
339 new_response.url.borrow_mut().clone_from(&self.url.borrow());
340 new_response
341 .url_list
342 .borrow_mut()
343 .clone_from(&self.url_list.borrow());
344
345 clone_body_stream_for_dom_body(cx, &self.body_stream, &new_response.body_stream)?;
348 new_response.fetch_body_stream.set(None);
350
351 Ok(new_response)
352 }
353
354 fn BodyUsed(&self) -> bool {
356 self.is_body_used()
357 }
358
359 fn GetBody(&self) -> Option<DomRoot<ReadableStream>> {
361 self.body()
362 }
363
364 fn Text(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
366 consume_body(cx, self, BodyType::Text)
367 }
368
369 fn Blob(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
371 consume_body(cx, self, BodyType::Blob)
372 }
373
374 fn FormData(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
376 consume_body(cx, self, BodyType::FormData)
377 }
378
379 fn Json(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
381 consume_body(cx, self, BodyType::Json)
382 }
383
384 fn ArrayBuffer(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
386 consume_body(cx, self, BodyType::ArrayBuffer)
387 }
388
389 fn Bytes(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
391 consume_body(cx, self, BodyType::Bytes)
392 }
393
394 fn TextStream(&self, cx: &mut js::context::JSContext) -> Fallible<DomRoot<ReadableStream>> {
396 body_text_stream(cx, self)
397 }
398}
399
400fn initialize_response(
402 cx: &mut js::context::JSContext,
403 body: Option<ExtractedBody>,
404 init: &ResponseBinding::ResponseInit,
405 response: DomRoot<Response>,
406) -> Result<DomRoot<Response>, Error> {
407 if init.status < 200 || init.status > 599 {
409 return Err(Error::Range(cformat!(
410 "init's status member should be in the range 200 to 599, inclusive, but is {}",
411 init.status
412 )));
413 }
414
415 if !is_valid_status_text(&init.statusText) {
418 return Err(Error::Type(
419 c"init's statusText member does not match the reason-phrase token production"
420 .to_owned(),
421 ));
422 }
423
424 *response.status.borrow_mut() =
427 HttpStatus::new_raw(init.status, init.statusText.clone().into());
428
429 if let Some(ref headers_member) = init.headers {
431 response.Headers(cx).fill(Some(headers_member.clone()))?;
432 }
433
434 if let Some(ref body) = body {
436 if is_null_body_status(init.status) {
438 return Err(Error::Type(
439 c"Body is non-null but init's status member is a null body status".to_owned(),
440 ));
441 };
442
443 response.body_stream.set(Some(&*body.stream));
445 response.fetch_body_stream.set(Some(&*body.stream));
446
447 if let Some(content_type_contents) = &body.content_type &&
450 !response
451 .Headers(cx)
452 .Has(ByteString::new(b"Content-Type".to_vec()))
453 .unwrap()
454 {
455 response.Headers(cx).Append(
456 ByteString::new(b"Content-Type".to_vec()),
457 ByteString::new(content_type_contents.as_bytes().to_vec()),
458 )?;
459 };
460 } else {
461 response.body_stream.set(None);
462 response.fetch_body_stream.set(None);
463 }
464
465 Ok(response)
466}
467
468fn serialize_without_fragment(url: &ServoUrl) -> &str {
469 &url[..Position::AfterQuery]
470}
471
472impl Response {
473 pub(crate) fn set_type(
474 &self,
475 cx: &mut js::context::JSContext,
476 new_response_type: DOMResponseType,
477 ) {
478 *self.response_type.borrow_mut() = new_response_type;
479 self.set_response_members_by_type(cx, new_response_type);
480 }
481
482 pub(crate) fn set_headers(
483 &self,
484 cx: &mut js::context::JSContext,
485 option_hyper_headers: Option<Serde<HyperHeaders>>,
486 ) {
487 self.Headers(cx).set_headers(match option_hyper_headers {
488 Some(hyper_headers) => hyper_headers.into_inner(),
489 None => HyperHeaders::new(),
490 });
491 }
492
493 pub(crate) fn set_status(&self, status: &HttpStatus) {
494 self.status.borrow_mut().clone_from(status);
495 }
496
497 pub(crate) fn set_final_url(&self, final_url: ServoUrl) {
498 *self.url.borrow_mut() = Some(final_url);
499 }
500
501 pub(crate) fn set_redirected(&self, is_redirected: bool) {
502 self.redirected.set(is_redirected);
503 }
504
505 fn set_response_members_by_type(
506 &self,
507 cx: &mut js::context::JSContext,
508 response_type: DOMResponseType,
509 ) {
510 match response_type {
511 DOMResponseType::Error => {
512 *self.status.borrow_mut() = HttpStatus::new_error();
513 self.set_headers(cx, None);
514 },
515 DOMResponseType::Opaque => {
516 *self.url_list.borrow_mut() = vec![];
517 *self.status.borrow_mut() = HttpStatus::new_error();
518 self.set_headers(cx, None);
519 self.body_stream.set(None);
520 self.fetch_body_stream.set(None);
521 },
522 DOMResponseType::Opaqueredirect => {
523 *self.status.borrow_mut() = HttpStatus::new_error();
524 self.set_headers(cx, None);
525 self.body_stream.set(None);
526 self.fetch_body_stream.set(None);
527 },
528 DOMResponseType::Default => {},
529 DOMResponseType::Basic => {},
530 DOMResponseType::Cors => {},
531 }
532 }
533
534 pub(crate) fn set_stream_consumer(&self, sc: Option<StreamConsumer>) {
535 *self.stream_consumer.borrow_mut() = sc;
536 }
537
538 pub(crate) fn stream_chunk(&self, cx: &mut js::context::JSContext, chunk: Bytes) {
539 if let Some(stream_consumer) = self.stream_consumer.borrow().as_ref() {
541 stream_consumer.consume_chunk(&chunk);
542 } else if let Some(body) = self.fetch_body_stream.get() {
543 body.enqueue_native(cx, chunk.to_vec());
544 }
545 }
546
547 pub(crate) fn finish(&self, cx: &mut js::context::JSContext) {
548 if let Some(body) = self.fetch_body_stream.get() {
549 body.controller_close_native(cx);
550 }
551 let stream_consumer = self.stream_consumer.borrow_mut().take();
552 if let Some(stream_consumer) = stream_consumer {
553 stream_consumer.stream_end();
554 }
555 }
556}