aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/response.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/response.rs')
-rw-r--r--components/script/dom/response.rs290
1 files changed, 290 insertions, 0 deletions
diff --git a/components/script/dom/response.rs b/components/script/dom/response.rs
new file mode 100644
index 00000000000..acfc181283a
--- /dev/null
+++ b/components/script/dom/response.rs
@@ -0,0 +1,290 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use core::cell::Cell;
+use dom::bindings::cell::DOMRefCell;
+use dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
+use dom::bindings::codegen::Bindings::ResponseBinding;
+use dom::bindings::codegen::Bindings::ResponseBinding::{ResponseMethods, ResponseType as DOMResponseType};
+use dom::bindings::error::{Error, Fallible};
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JS, MutNullableHeap, Root};
+use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object};
+use dom::bindings::str::{ByteString, USVString};
+use dom::headers::{Headers, Guard};
+use dom::headers::{is_vchar, is_obs_text};
+use hyper::status::StatusCode;
+use net_traits::response::{ResponseBody as NetTraitsResponseBody};
+use std::str::FromStr;
+use url::Position;
+use url::Url;
+
+#[dom_struct]
+pub struct Response {
+ reflector_: Reflector,
+ headers_reflector: MutNullableHeap<JS<Headers>>,
+ mime_type: DOMRefCell<Vec<u8>>,
+ body_used: Cell<bool>,
+ /// `None` can be considered a StatusCode of `0`.
+ #[ignore_heap_size_of = "Defined in hyper"]
+ status: DOMRefCell<Option<StatusCode>>,
+ raw_status: DOMRefCell<Option<(u16, Vec<u8>)>>,
+ response_type: DOMRefCell<DOMResponseType>,
+ url: DOMRefCell<Option<Url>>,
+ url_list: DOMRefCell<Vec<Url>>,
+ // For now use the existing NetTraitsResponseBody enum, until body
+ // is implemented.
+ body: DOMRefCell<NetTraitsResponseBody>,
+}
+
+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),
+ }
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response
+ pub fn new(global: GlobalRef) -> Root<Response> {
+ reflect_dom_object(box Response::new_inherited(), global, ResponseBinding::Wrap)
+ }
+
+ pub fn Constructor(global: GlobalRef, _body: Option<USVString>, init: &ResponseBinding::ResponseInit)
+ -> Fallible<Root<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));
+
+ // 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
+ // TODO: Figure out how/if we should make r's response's
+ // header list and r's Headers object the same thing. For
+ // now just working with r's Headers object. Also, the
+ // header list should already be empty so this step may be
+ // unnecessary.
+ r.Headers().empty_header_list();
+
+ // Step 6.2
+ try!(r.Headers().fill(Some(headers_member.clone())));
+ }
+
+ // Step 7
+ if let Some(_) = _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.2
+ let content_type: Option<ByteString> = None;
+
+ // Step 7.3
+ // TODO: Extract body and implement step 7.3.
+
+ // Step 7.4
+ if let Some(content_type_contents) = content_type {
+ if !r.Headers().Has(ByteString::new(b"Content-Type".to_vec())).unwrap() {
+ try!(r.Headers().Append(ByteString::new(b"Content-Type".to_vec()), content_type_contents));
+ }
+ };
+ }
+
+ // 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: GlobalRef) -> Root<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: GlobalRef, url: USVString, status: u16) -> Fallible<Root<Response>> {
+ // Step 1
+ // TODO: `entry settings object` is not implemented in Servo yet.
+ let base_url = global.get_url();
+ let parsed_url = base_url.join(&url.0);
+
+ // Step 2
+ let url = match parsed_url {
+ Ok(url) => url,
+ Err(_) => return Err(Error::Type("Url 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));
+ *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()));
+ try!(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/#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.to_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) -> Root<Headers> {
+ self.headers_reflector.or_init(|| Headers::for_response(self.global().r()))
+ }
+
+ // https://fetch.spec.whatwg.org/#dom-response-clone
+ fn Clone(&self) -> Fallible<Root<Response>> {
+ // Step 1
+ // TODO: This step relies on body and stream, which are still unimplemented.
+
+ // Step 2
+ let new_response = Response::new(self.global().r());
+ new_response.Headers().set_guard(self.Headers().get_guard());
+
+ // 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()
+ }
+}
+
+fn serialize_without_fragment(url: &Url) -> &str {
+ &url[..Position::AfterQuery]
+}