mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
450 lines
15 KiB
Rust
450 lines
15 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use crate::body::{consume_body, consume_body_with_promise, BodyOperations, BodyType};
|
|
use crate::dom::bindings::cell::DomRefCell;
|
|
use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
|
|
use crate::dom::bindings::codegen::Bindings::ResponseBinding;
|
|
use crate::dom::bindings::codegen::Bindings::ResponseBinding::{
|
|
ResponseMethods, ResponseType as DOMResponseType,
|
|
};
|
|
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
|
|
use crate::dom::bindings::error::{Error, Fallible};
|
|
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
|
|
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
|
|
use crate::dom::bindings::str::{ByteString, USVString};
|
|
use crate::dom::globalscope::GlobalScope;
|
|
use crate::dom::headers::{is_obs_text, is_vchar};
|
|
use crate::dom::headers::{Guard, Headers};
|
|
use crate::dom::promise::Promise;
|
|
use crate::dom::xmlhttprequest::Extractable;
|
|
use dom_struct::dom_struct;
|
|
use http::header::HeaderMap as HyperHeaders;
|
|
use hyper::StatusCode;
|
|
use hyper_serde::Serde;
|
|
use net_traits::response::ResponseBody as NetTraitsResponseBody;
|
|
use servo_url::ServoUrl;
|
|
use std::cell::{Cell, Ref};
|
|
use std::mem;
|
|
use std::rc::Rc;
|
|
use std::str::FromStr;
|
|
use url::Position;
|
|
|
|
#[dom_struct]
|
|
pub struct Response {
|
|
reflector_: Reflector,
|
|
headers_reflector: MutNullableDom<Headers>,
|
|
mime_type: DomRefCell<Vec<u8>>,
|
|
body_used: Cell<bool>,
|
|
/// `None` can be considered a StatusCode of `0`.
|
|
#[ignore_malloc_size_of = "Defined in hyper"]
|
|
status: DomRefCell<Option<StatusCode>>,
|
|
raw_status: DomRefCell<Option<(u16, Vec<u8>)>>,
|
|
response_type: DomRefCell<DOMResponseType>,
|
|
url: DomRefCell<Option<ServoUrl>>,
|
|
url_list: DomRefCell<Vec<ServoUrl>>,
|
|
// For now use the existing NetTraitsResponseBody enum
|
|
body: DomRefCell<NetTraitsResponseBody>,
|
|
#[ignore_malloc_size_of = "Rc"]
|
|
body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>,
|
|
}
|
|
|
|
impl Response {
|
|
pub fn new_inherited() -> Response {
|
|
Response {
|
|
reflector_: Reflector::new(),
|
|
headers_reflector: Default::default(),
|
|
mime_type: DomRefCell::new("".to_string().into_bytes()),
|
|
body_used: Cell::new(false),
|
|
status: DomRefCell::new(Some(StatusCode::OK)),
|
|
raw_status: DomRefCell::new(Some((200, b"OK".to_vec()))),
|
|
response_type: DomRefCell::new(DOMResponseType::Default),
|
|
url: DomRefCell::new(None),
|
|
url_list: DomRefCell::new(vec![]),
|
|
body: DomRefCell::new(NetTraitsResponseBody::Empty),
|
|
body_promise: DomRefCell::new(None),
|
|
}
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response
|
|
pub fn new(global: &GlobalScope) -> DomRoot<Response> {
|
|
reflect_dom_object(
|
|
Box::new(Response::new_inherited()),
|
|
global,
|
|
ResponseBinding::Wrap,
|
|
)
|
|
}
|
|
|
|
pub fn Constructor(
|
|
global: &GlobalScope,
|
|
body: Option<BodyInit>,
|
|
init: &ResponseBinding::ResponseInit,
|
|
) -> Fallible<DomRoot<Response>> {
|
|
// Step 1
|
|
if init.status < 200 || init.status > 599 {
|
|
return Err(Error::Range(format!(
|
|
"init's status member should be in the range 200 to 599, inclusive, but is {}",
|
|
init.status
|
|
)));
|
|
}
|
|
|
|
// Step 2
|
|
if !is_valid_status_text(&init.statusText) {
|
|
return Err(Error::Type(
|
|
"init's statusText member does not match the reason-phrase token production"
|
|
.to_string(),
|
|
));
|
|
}
|
|
|
|
// Step 3
|
|
let r = Response::new(global);
|
|
|
|
// Step 4
|
|
*r.status.borrow_mut() = Some(StatusCode::from_u16(init.status).unwrap());
|
|
|
|
// Step 5
|
|
*r.raw_status.borrow_mut() = Some((init.status, init.statusText.clone().into()));
|
|
|
|
// Step 6
|
|
if let Some(ref headers_member) = init.headers {
|
|
// Step 6.1
|
|
r.Headers().empty_header_list();
|
|
|
|
// Step 6.2
|
|
r.Headers().fill(Some(headers_member.clone()))?;
|
|
}
|
|
|
|
// Step 7
|
|
if let Some(ref body) = body {
|
|
// Step 7.1
|
|
if is_null_body_status(init.status) {
|
|
return Err(Error::Type(
|
|
"Body is non-null but init's status member is a null body status".to_string(),
|
|
));
|
|
};
|
|
|
|
// Step 7.3
|
|
let (extracted_body, content_type) = body.extract();
|
|
*r.body.borrow_mut() = NetTraitsResponseBody::Done(extracted_body);
|
|
|
|
// Step 7.4
|
|
if let Some(content_type_contents) = content_type {
|
|
if !r
|
|
.Headers()
|
|
.Has(ByteString::new(b"Content-Type".to_vec()))
|
|
.unwrap()
|
|
{
|
|
r.Headers().Append(
|
|
ByteString::new(b"Content-Type".to_vec()),
|
|
ByteString::new(content_type_contents.as_bytes().to_vec()),
|
|
)?;
|
|
}
|
|
};
|
|
}
|
|
|
|
// Step 8
|
|
*r.mime_type.borrow_mut() = r.Headers().extract_mime_type();
|
|
|
|
// Step 9
|
|
// TODO: `entry settings object` is not implemented in Servo yet.
|
|
|
|
// Step 10
|
|
// TODO: Write this step once Promises are merged in
|
|
|
|
// Step 11
|
|
Ok(r)
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response-error
|
|
pub fn Error(global: &GlobalScope) -> DomRoot<Response> {
|
|
let r = Response::new(global);
|
|
*r.response_type.borrow_mut() = DOMResponseType::Error;
|
|
r.Headers().set_guard(Guard::Immutable);
|
|
*r.raw_status.borrow_mut() = Some((0, b"".to_vec()));
|
|
r
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response-redirect
|
|
pub fn Redirect(
|
|
global: &GlobalScope,
|
|
url: USVString,
|
|
status: u16,
|
|
) -> Fallible<DomRoot<Response>> {
|
|
// Step 1
|
|
let base_url = global.api_base_url();
|
|
let parsed_url = base_url.join(&url.0);
|
|
|
|
// Step 2
|
|
let url = match parsed_url {
|
|
Ok(url) => url,
|
|
Err(_) => return Err(Error::Type("ServoUrl could not be parsed".to_string())),
|
|
};
|
|
|
|
// Step 3
|
|
if !is_redirect_status(status) {
|
|
return Err(Error::Range("status is not a redirect status".to_string()));
|
|
}
|
|
|
|
// Step 4
|
|
// see Step 4 continued
|
|
let r = Response::new(global);
|
|
|
|
// Step 5
|
|
*r.status.borrow_mut() = Some(StatusCode::from_u16(status).unwrap());
|
|
*r.raw_status.borrow_mut() = Some((status, b"".to_vec()));
|
|
|
|
// Step 6
|
|
let url_bytestring =
|
|
ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec()));
|
|
r.Headers()
|
|
.Set(ByteString::new(b"Location".to_vec()), url_bytestring)?;
|
|
|
|
// Step 4 continued
|
|
// Headers Guard is set to Immutable here to prevent error in Step 6
|
|
r.Headers().set_guard(Guard::Immutable);
|
|
|
|
// Step 7
|
|
Ok(r)
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-body-locked
|
|
fn locked(&self) -> bool {
|
|
// TODO: ReadableStream is unimplemented. Just return false
|
|
// for now.
|
|
false
|
|
}
|
|
}
|
|
|
|
impl BodyOperations for Response {
|
|
fn get_body_used(&self) -> bool {
|
|
self.BodyUsed()
|
|
}
|
|
|
|
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) {
|
|
assert!(self.body_promise.borrow().is_none());
|
|
self.body_used.set(true);
|
|
*self.body_promise.borrow_mut() = Some((p.clone(), body_type));
|
|
}
|
|
|
|
fn is_locked(&self) -> bool {
|
|
self.locked()
|
|
}
|
|
|
|
fn take_body(&self) -> Option<Vec<u8>> {
|
|
let body = mem::replace(&mut *self.body.borrow_mut(), NetTraitsResponseBody::Empty);
|
|
match body {
|
|
NetTraitsResponseBody::Done(bytes) => Some(bytes),
|
|
body => {
|
|
mem::replace(&mut *self.body.borrow_mut(), body);
|
|
None
|
|
},
|
|
}
|
|
}
|
|
|
|
fn get_mime_type(&self) -> Ref<Vec<u8>> {
|
|
self.mime_type.borrow()
|
|
}
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#redirect-status
|
|
fn is_redirect_status(status: u16) -> bool {
|
|
status == 301 || status == 302 || status == 303 || status == 307 || status == 308
|
|
}
|
|
|
|
// https://tools.ietf.org/html/rfc7230#section-3.1.2
|
|
fn is_valid_status_text(status_text: &ByteString) -> bool {
|
|
// reason-phrase = *( HTAB / SP / VCHAR / obs-text )
|
|
for byte in status_text.iter() {
|
|
if !(*byte == b'\t' || *byte == b' ' || is_vchar(*byte) || is_obs_text(*byte)) {
|
|
return false;
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#null-body-status
|
|
fn is_null_body_status(status: u16) -> bool {
|
|
status == 101 || status == 204 || status == 205 || status == 304
|
|
}
|
|
|
|
impl ResponseMethods for Response {
|
|
// https://fetch.spec.whatwg.org/#dom-response-type
|
|
fn Type(&self) -> DOMResponseType {
|
|
*self.response_type.borrow() //into()
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response-url
|
|
fn Url(&self) -> USVString {
|
|
USVString(String::from(
|
|
(*self.url.borrow())
|
|
.as_ref()
|
|
.map(|u| serialize_without_fragment(u))
|
|
.unwrap_or(""),
|
|
))
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response-redirected
|
|
fn Redirected(&self) -> bool {
|
|
let url_list_len = self.url_list.borrow().len();
|
|
url_list_len > 1
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response-status
|
|
fn Status(&self) -> u16 {
|
|
match *self.raw_status.borrow() {
|
|
Some((s, _)) => s,
|
|
None => 0,
|
|
}
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response-ok
|
|
fn Ok(&self) -> bool {
|
|
match *self.status.borrow() {
|
|
Some(s) => {
|
|
let status_num = s.as_u16();
|
|
return status_num >= 200 && status_num <= 299;
|
|
},
|
|
None => false,
|
|
}
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response-statustext
|
|
fn StatusText(&self) -> ByteString {
|
|
match *self.raw_status.borrow() {
|
|
Some((_, ref st)) => ByteString::new(st.clone()),
|
|
None => ByteString::new(b"OK".to_vec()),
|
|
}
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response-headers
|
|
fn Headers(&self) -> DomRoot<Headers> {
|
|
self.headers_reflector
|
|
.or_init(|| Headers::for_response(&self.global()))
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-response-clone
|
|
fn Clone(&self) -> Fallible<DomRoot<Response>> {
|
|
// Step 1
|
|
if self.is_locked() || self.body_used.get() {
|
|
return Err(Error::Type("cannot clone a disturbed response".to_string()));
|
|
}
|
|
|
|
// Step 2
|
|
let new_response = Response::new(&self.global());
|
|
new_response.Headers().set_guard(self.Headers().get_guard());
|
|
new_response
|
|
.Headers()
|
|
.fill(Some(HeadersInit::Headers(self.Headers())))?;
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-response-clone
|
|
// Instead of storing a net_traits::Response internally, we
|
|
// only store the relevant fields, and only clone them here
|
|
*new_response.response_type.borrow_mut() = self.response_type.borrow().clone();
|
|
*new_response.status.borrow_mut() = self.status.borrow().clone();
|
|
*new_response.raw_status.borrow_mut() = self.raw_status.borrow().clone();
|
|
*new_response.url.borrow_mut() = self.url.borrow().clone();
|
|
*new_response.url_list.borrow_mut() = self.url_list.borrow().clone();
|
|
|
|
if *self.body.borrow() != NetTraitsResponseBody::Empty {
|
|
*new_response.body.borrow_mut() = self.body.borrow().clone();
|
|
}
|
|
|
|
// Step 3
|
|
// TODO: This step relies on promises, which are still unimplemented.
|
|
|
|
// Step 4
|
|
Ok(new_response)
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
|
fn BodyUsed(&self) -> bool {
|
|
self.body_used.get()
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-body-text
|
|
fn Text(&self) -> Rc<Promise> {
|
|
consume_body(self, BodyType::Text)
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-body-blob
|
|
fn Blob(&self) -> Rc<Promise> {
|
|
consume_body(self, BodyType::Blob)
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-body-formdata
|
|
fn FormData(&self) -> Rc<Promise> {
|
|
consume_body(self, BodyType::FormData)
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-body-json
|
|
fn Json(&self) -> Rc<Promise> {
|
|
consume_body(self, BodyType::Json)
|
|
}
|
|
|
|
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
fn ArrayBuffer(&self) -> Rc<Promise> {
|
|
consume_body(self, BodyType::ArrayBuffer)
|
|
}
|
|
}
|
|
|
|
fn serialize_without_fragment(url: &ServoUrl) -> &str {
|
|
&url[..Position::AfterQuery]
|
|
}
|
|
|
|
impl Response {
|
|
pub fn set_type(&self, new_response_type: DOMResponseType) {
|
|
*self.response_type.borrow_mut() = new_response_type;
|
|
self.set_response_members_by_type(new_response_type);
|
|
}
|
|
|
|
pub fn set_headers(&self, option_hyper_headers: Option<Serde<HyperHeaders>>) {
|
|
self.Headers().set_headers(match option_hyper_headers {
|
|
Some(hyper_headers) => hyper_headers.into_inner(),
|
|
None => HyperHeaders::new(),
|
|
});
|
|
}
|
|
|
|
pub fn set_raw_status(&self, status: Option<(u16, Vec<u8>)>) {
|
|
*self.raw_status.borrow_mut() = status;
|
|
}
|
|
|
|
pub fn set_final_url(&self, final_url: ServoUrl) {
|
|
*self.url.borrow_mut() = Some(final_url);
|
|
}
|
|
|
|
fn set_response_members_by_type(&self, response_type: DOMResponseType) {
|
|
match response_type {
|
|
DOMResponseType::Error => {
|
|
*self.status.borrow_mut() = None;
|
|
self.set_raw_status(None);
|
|
self.set_headers(None);
|
|
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
|
|
},
|
|
DOMResponseType::Opaque => {
|
|
*self.url_list.borrow_mut() = vec![];
|
|
*self.status.borrow_mut() = None;
|
|
self.set_raw_status(None);
|
|
self.set_headers(None);
|
|
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
|
|
},
|
|
DOMResponseType::Opaqueredirect => {
|
|
*self.status.borrow_mut() = None;
|
|
self.set_raw_status(None);
|
|
self.set_headers(None);
|
|
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
|
|
},
|
|
DOMResponseType::Default => {},
|
|
DOMResponseType::Basic => {},
|
|
DOMResponseType::Cors => {},
|
|
}
|
|
}
|
|
|
|
#[allow(unrooted_must_root)]
|
|
pub fn finish(&self, body: Vec<u8>) {
|
|
*self.body.borrow_mut() = NetTraitsResponseBody::Done(body);
|
|
if let Some((p, body_type)) = self.body_promise.borrow_mut().take() {
|
|
consume_body_with_promise(self, body_type, &p);
|
|
}
|
|
}
|
|
}
|