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