From fabe2b8f7e5693d705427959a07a380d52c16e26 Mon Sep 17 00:00:00 2001 From: Jeena Lee Date: Tue, 19 Jul 2016 18:38:02 -0700 Subject: [PATCH 1/2] Implement the Request API for the Fetch API. This commit adds new files related to implementing the [Request API](https://fetch.spec.whatwg.org/#request-class). This commit also changes the expected web platform tests results. It also modifies the following files: components/net_traits/request.rs HeapSizeOf is implemented in net_traits/request so that dom::request can be used as a wrapper around net_traits::request::Request. components/script/dom/headers.rs Several methods are added to Headers so that request can access and modify some of the headers fields. --- components/net_traits/request.rs | 26 +- components/script/dom/bindings/trace.rs | 2 + components/script/dom/headers.rs | 135 +-- components/script/dom/mod.rs | 1 + components/script/dom/request.rs | 820 ++++++++++++++++++ components/script/dom/webidls/Body.webidl | 19 + components/script/dom/webidls/Request.webidl | 108 +++ .../fetch/api/request/request-error.html.ini | 57 -- .../api/request/request-headers.html.ini | 141 --- .../api/request/request-init-001.sub.html.ini | 96 -- .../api/request/request-structure.html.ini | 22 +- .../wpt/mozilla/tests/mozilla/interfaces.html | 1 + .../tests/mozilla/interfaces.worker.js | 1 + 13 files changed, 1070 insertions(+), 359 deletions(-) create mode 100644 components/script/dom/request.rs create mode 100644 components/script/dom/webidls/Body.webidl create mode 100644 components/script/dom/webidls/Request.webidl diff --git a/components/net_traits/request.rs b/components/net_traits/request.rs index bf7e22d2d0b..6b9d4618fc2 100644 --- a/components/net_traits/request.rs +++ b/components/net_traits/request.rs @@ -10,7 +10,7 @@ use std::mem::swap; use url::{Origin as UrlOrigin, Url}; /// An [initiator](https://fetch.spec.whatwg.org/#concept-request-initiator) -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, HeapSizeOf)] pub enum Initiator { None, Download, @@ -20,14 +20,14 @@ pub enum Initiator { } /// A request [type](https://fetch.spec.whatwg.org/#concept-request-type) -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, HeapSizeOf)] pub enum Type { None, Audio, Font, Image, Script, Style, Track, Video } /// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination) -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)] pub enum Destination { None, Document, Embed, Font, Image, Manifest, Media, Object, Report, Script, ServiceWorker, @@ -35,14 +35,14 @@ pub enum Destination { } /// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin) -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, HeapSizeOf)] pub enum Origin { Client, Origin(UrlOrigin) } /// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer) -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, HeapSizeOf)] pub enum Referer { NoReferer, /// Default referer if nothing is specified @@ -51,7 +51,7 @@ pub enum Referer { } /// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode) -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)] pub enum RequestMode { Navigate, SameOrigin, @@ -60,7 +60,7 @@ pub enum RequestMode { } /// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode) -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, HeapSizeOf)] pub enum CredentialsMode { Omit, CredentialsSameOrigin, @@ -68,7 +68,7 @@ pub enum CredentialsMode { } /// [Cache mode](https://fetch.spec.whatwg.org/#concept-request-cache-mode) -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, HeapSizeOf)] pub enum CacheMode { Default, NoStore, @@ -79,7 +79,7 @@ pub enum CacheMode { } /// [Redirect mode](https://fetch.spec.whatwg.org/#concept-request-redirect-mode) -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, HeapSizeOf)] pub enum RedirectMode { Follow, Error, @@ -87,7 +87,7 @@ pub enum RedirectMode { } /// [Response tainting](https://fetch.spec.whatwg.org/#concept-request-response-tainting) -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, HeapSizeOf)] pub enum ResponseTainting { Basic, CORSTainting, @@ -95,7 +95,7 @@ pub enum ResponseTainting { } /// [Window](https://fetch.spec.whatwg.org/#concept-request-window) -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, HeapSizeOf)] pub enum Window { NoWindow, Client, @@ -134,11 +134,13 @@ pub struct RequestInit { } /// A [Request](https://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec -#[derive(Clone)] +#[derive(Clone, HeapSizeOf)] pub struct Request { + #[ignore_heap_size_of = "Defined in hyper"] pub method: RefCell, pub local_urls_only: bool, pub sandboxed_storage_area_urls: bool, + #[ignore_heap_size_of = "Defined in hyper"] pub headers: RefCell, pub unsafe_request: bool, pub body: RefCell>>, diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 618061fe769..89b3ab88e11 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -60,6 +60,7 @@ use msg::constellation_msg::{FrameType, PipelineId, SubpageId, WindowSizeType, R use net_traits::filemanager_thread::{SelectedFileId, RelativePos}; use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread}; +use net_traits::request::Request; use net_traits::response::HttpsState; use net_traits::storage_thread::StorageType; use net_traits::{Metadata, NetworkError, ResourceThreads}; @@ -325,6 +326,7 @@ no_jsmanaged_fields!(AttrIdentifier); no_jsmanaged_fields!(AttrValue); no_jsmanaged_fields!(ElementSnapshot); no_jsmanaged_fields!(HttpsState); +no_jsmanaged_fields!(Request); no_jsmanaged_fields!(SharedRt); no_jsmanaged_fields!(TouchpadPressurePhase); no_jsmanaged_fields!(USVString); diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index 4ae212aee78..3329952e0a9 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -12,18 +12,19 @@ use dom::bindings::js::Root; use dom::bindings::reflector::{Reflector, reflect_dom_object}; use dom::bindings::str::{ByteString, is_token}; use hyper::header::Headers as HyperHeaders; +use std::cell::Cell; use std::result::Result; #[dom_struct] pub struct Headers { reflector_: Reflector, - guard: Guard, + guard: Cell, #[ignore_heap_size_of = "Defined in hyper"] header_list: DOMRefCell } // https://fetch.spec.whatwg.org/#concept-headers-guard -#[derive(JSTraceable, HeapSizeOf, PartialEq)] +#[derive(Copy, Clone, JSTraceable, HeapSizeOf, PartialEq)] pub enum Guard { Immutable, Request, @@ -36,76 +37,46 @@ impl Headers { pub fn new_inherited() -> Headers { Headers { reflector_: Reflector::new(), - guard: Guard::None, + guard: Cell::new(Guard::None), header_list: DOMRefCell::new(HyperHeaders::new()), } } - // https://fetch.spec.whatwg.org/#concept-headers-fill - pub fn new(global: GlobalRef, init: Option) - -> Fallible> { - let dom_headers_new = reflect_dom_object(box Headers::new_inherited(), global, HeadersBinding::Wrap); - match init { - // Step 1 - Some(HeadersOrByteStringSequenceSequence::Headers(h)) => { - // header_list_copy has type hyper::header::Headers - let header_list_copy = h.header_list.clone(); - for header in header_list_copy.borrow().iter() { - try!(dom_headers_new.Append( - ByteString::new(Vec::from(header.name())), - ByteString::new(Vec::from(header.value_string().into_bytes())) - )); - } - Ok(dom_headers_new) - }, - // Step 2 - Some(HeadersOrByteStringSequenceSequence::ByteStringSequenceSequence(v)) => { - for mut seq in v { - if seq.len() == 2 { - let val = seq.pop().unwrap(); - let name = seq.pop().unwrap(); - try!(dom_headers_new.Append(name, val)); - } else { - return Err(Error::Type( - format!("Each header object must be a sequence of length 2 - found one with length {}", - seq.len()))); - } - } - Ok(dom_headers_new) - }, - // Step 3 TODO constructor for when init is an open-ended dictionary - None => Ok(dom_headers_new), - } + pub fn new(global: GlobalRef) -> Root { + reflect_dom_object(box Headers::new_inherited(), global, HeadersBinding::Wrap) } + // https://fetch.spec.whatwg.org/#dom-headers pub fn Constructor(global: GlobalRef, init: Option) -> Fallible> { - Headers::new(global, init) + let dom_headers_new = Headers::new(global); + try!(dom_headers_new.fill(init)); + Ok(dom_headers_new) } } impl HeadersMethods for Headers { // https://fetch.spec.whatwg.org/#concept-headers-append - fn Append(&self, name: ByteString, value: ByteString) -> Result<(), Error> { + fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult { // Step 1 let value = normalize_value(value); // Step 2 let (mut valid_name, valid_value) = try!(validate_name_and_value(name, value)); valid_name = valid_name.to_lowercase(); // Step 3 - if self.guard == Guard::Immutable { + if self.guard.get() == Guard::Immutable { return Err(Error::Type("Guard is immutable".to_string())); } // Step 4 - if self.guard == Guard::Request && is_forbidden_header_name(&valid_name) { + if self.guard.get() == Guard::Request && is_forbidden_header_name(&valid_name) { return Ok(()); } // Step 5 - if self.guard == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name) { + if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name) { return Ok(()); } // Step 6 - if self.guard == Guard::Response && is_forbidden_response_header(&valid_name) { + if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { return Ok(()); } // Step 7 @@ -121,19 +92,19 @@ impl HeadersMethods for Headers { // Step 1 let valid_name = try!(validate_name(name)); // Step 2 - if self.guard == Guard::Immutable { + if self.guard.get() == Guard::Immutable { return Err(Error::Type("Guard is immutable".to_string())); } // Step 3 - if self.guard == Guard::Request && is_forbidden_header_name(&valid_name) { + if self.guard.get() == Guard::Request && is_forbidden_header_name(&valid_name) { return Ok(()); } // Step 4 - if self.guard == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name) { + if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name) { return Ok(()); } // Step 5 - if self.guard == Guard::Response && is_forbidden_response_header(&valid_name) { + if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { return Ok(()); } // Step 6 @@ -166,19 +137,19 @@ impl HeadersMethods for Headers { let (mut valid_name, valid_value) = try!(validate_name_and_value(name, value)); valid_name = valid_name.to_lowercase(); // Step 3 - if self.guard == Guard::Immutable { + if self.guard.get() == Guard::Immutable { return Err(Error::Type("Guard is immutable".to_string())); } // Step 4 - if self.guard == Guard::Request && is_forbidden_header_name(&valid_name) { + if self.guard.get() == Guard::Request && is_forbidden_header_name(&valid_name) { return Ok(()); } // Step 5 - if self.guard == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name) { + if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name) { return Ok(()); } // Step 6 - if self.guard == Guard::Response && is_forbidden_response_header(&valid_name) { + if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { return Ok(()); } // Step 7 @@ -188,6 +159,66 @@ impl HeadersMethods for Headers { } } +impl Headers { + // https://fetch.spec.whatwg.org/#concept-headers-fill + pub fn fill(&self, filler: Option) -> ErrorResult { + match filler { + // Step 1 + Some(HeadersOrByteStringSequenceSequence::Headers(h)) => { + // header_list_copy has type hyper::header::Headers + let header_list_copy = h.header_list.clone(); + for header in h.header_list.borrow().iter() { + try!(self.Append( + ByteString::new(Vec::from(header.name())), + ByteString::new(Vec::from(header.value_string().into_bytes())) + )); + } + Ok(()) + }, + // Step 2 + Some(HeadersOrByteStringSequenceSequence::ByteStringSequenceSequence(v)) => { + for mut seq in v { + if seq.len() == 2 { + let val = seq.pop().unwrap(); + let name = seq.pop().unwrap(); + try!(self.Append(name, val)); + } else { + return Err(Error::Type( + format!("Each header object must be a sequence of length 2 - found one with length {}", + seq.len()))); + } + } + Ok(()) + }, + // Step 3 TODO constructor for when init is an open-ended dictionary + None => Ok(()), + } + } + + pub fn for_request(global: GlobalRef) -> Root { + let headers_for_request = Headers::new(global); + headers_for_request.guard.set(Guard::Request); + headers_for_request + } + + pub fn set_guard(&self, new_guard: Guard) { + self.guard.set(new_guard) + } + + pub fn get_guard(&self) -> Guard { + self.guard.get() + } + + pub fn empty_header_list(&self) { + *self.header_list.borrow_mut() = HyperHeaders::new(); + } + + // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type + pub fn extract_mime_type(&self) -> Vec { + self.header_list.borrow().get_raw("content-type").map_or(vec![], |v| v[0].clone()) + } +} + // TODO // "Content-Type" once parsed, the value should be // `application/x-www-form-urlencoded`, `multipart/form-data`, diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index a2112526228..c2121c84139 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -368,6 +368,7 @@ pub mod processinginstruction; pub mod progressevent; pub mod radionodelist; pub mod range; +pub mod request; pub mod screen; pub mod serviceworker; pub mod serviceworkercontainer; diff --git a/components/script/dom/request.rs b/components/script/dom/request.rs new file mode 100644 index 00000000000..a764aec6265 --- /dev/null +++ b/components/script/dom/request.rs @@ -0,0 +1,820 @@ +/* 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 dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::RequestBinding; +use dom::bindings::codegen::Bindings::RequestBinding::ReferrerPolicy; +use dom::bindings::codegen::Bindings::RequestBinding::RequestCache; +use dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials; +use dom::bindings::codegen::Bindings::RequestBinding::RequestDestination; +use dom::bindings::codegen::Bindings::RequestBinding::RequestInfo; +use dom::bindings::codegen::Bindings::RequestBinding::RequestInit; +use dom::bindings::codegen::Bindings::RequestBinding::RequestMethods; +use dom::bindings::codegen::Bindings::RequestBinding::RequestMode; +use dom::bindings::codegen::Bindings::RequestBinding::RequestRedirect; +use dom::bindings::codegen::Bindings::RequestBinding::RequestType; +use dom::bindings::codegen::UnionTypes::HeadersOrByteStringSequenceSequence; +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, DOMString}; +use dom::headers::{Headers, Guard}; +use hyper; +use msg::constellation_msg::{ReferrerPolicy as MsgReferrerPolicy}; +use net_traits::request::CacheMode as NetTraitsRequestCache; +use net_traits::request::CredentialsMode as NetTraitsRequestCredentials; +use net_traits::request::Destination as NetTraitsRequestDestination; +use net_traits::request::RedirectMode as NetTraitsRequestRedirect; +use net_traits::request::Referer as NetTraitsRequestReferer; +use net_traits::request::Request as NetTraitsRequest; +use net_traits::request::RequestMode as NetTraitsRequestMode; +use net_traits::request::Type as NetTraitsRequestType; +use net_traits::request::{Origin, Window}; +use std::cell::{Cell, Ref}; +use url::Url; + +#[dom_struct] +pub struct Request { + reflector_: Reflector, + request: DOMRefCell, + body_used: Cell, + headers: MutNullableHeap>, + mime_type: DOMRefCell>, +} + +impl Request { + fn new_inherited(global: GlobalRef, + url: Url, + is_service_worker_global_scope: bool) -> Request { + Request { + reflector_: Reflector::new(), + request: DOMRefCell::new( + net_request_from_global(global, + url, + is_service_worker_global_scope)), + body_used: Cell::new(false), + headers: Default::default(), + mime_type: DOMRefCell::new("".to_string().into_bytes()), + } + } + + pub fn new(global: GlobalRef, + url: Url, + is_service_worker_global_scope: bool) -> Root { + reflect_dom_object(box Request::new_inherited(global, + url, + is_service_worker_global_scope), + global, RequestBinding::Wrap) + } + + // https://fetch.spec.whatwg.org/#dom-request + pub fn Constructor(global: GlobalRef, + input: RequestInfo, + init: &RequestInit) + -> Fallible> { + // Step 1 + let temporary_request: NetTraitsRequest; + + // Step 2 + let mut fallback_mode: Option = None; + + // Step 3 + let mut fallback_credentials: Option = None; + + // Step 4 + // TODO: `entry settings object` is not implemented in Servo yet. + let base_url = global.get_url(); + + match input { + // Step 5 + RequestInfo::USVString(USVString(ref usv_string)) => { + // Step 5.1 + let parsed_url = base_url.join(&usv_string); + // Step 5.2 + if parsed_url.is_err() { + return Err(Error::Type("Url could not be parsed".to_string())) + } + // Step 5.3 + let url = parsed_url.unwrap(); + if includes_credentials(&url) { + return Err(Error::Type("Url includes credentials".to_string())) + } + // Step 5.4 + temporary_request = net_request_from_global(global, + url, + false); + // Step 5.5 + fallback_mode = Some(NetTraitsRequestMode::CORSMode); + // Step 5.6 + fallback_credentials = Some(NetTraitsRequestCredentials::Omit); + } + // Step 6 + RequestInfo::Request(ref input_request) => { + // Step 6.1 + if request_is_disturbed(input_request) || request_is_locked(input_request) { + return Err(Error::Type("Input is disturbed or locked".to_string())) + } + // Step 6.2 + temporary_request = input_request.request.borrow().clone(); + } + } + + // Step 7 + // TODO: `entry settings object` is not implemented yet. + let origin = global.get_url().origin(); + + // Step 8 + let mut window = Window::Client; + + // Step 9 + // TODO: `environment settings object` is not implemented in Servo yet. + + // Step 10 + if !init.window.is_undefined() && !init.window.is_null() { + return Err(Error::Type("Window is present and is not null".to_string())) + } + + // Step 11 + if !init.window.is_undefined() { + window = Window::NoWindow; + } + + // Step 12 + let mut request: NetTraitsRequest; + request = net_request_from_global(global, + get_current_url(&temporary_request).unwrap().clone(), + false); + request.method = temporary_request.method; + request.headers = temporary_request.headers.clone(); + request.unsafe_request = true; + request.window.set(window); + // TODO: `entry settings object` is not implemented in Servo yet. + *request.origin.borrow_mut() = Origin::Client; + request.omit_origin_header = temporary_request.omit_origin_header; + request.same_origin_data.set(true); + request.referer = temporary_request.referer; + request.referrer_policy = temporary_request.referrer_policy; + request.mode = temporary_request.mode; + request.credentials_mode = temporary_request.credentials_mode; + request.cache_mode = temporary_request.cache_mode; + request.redirect_mode = temporary_request.redirect_mode; + request.integrity_metadata = temporary_request.integrity_metadata; + + // Step 13 + if init.body.is_some() || + init.cache.is_some() || + init.credentials.is_some() || + init.integrity.is_some() || + init.headers.is_some() || + init.method.is_some() || + init.mode.is_some() || + init.redirect.is_some() || + init.referrer.is_some() || + init.referrerPolicy.is_some() || + !init.window.is_undefined() { + // Step 13.1 + if request.mode == NetTraitsRequestMode::Navigate { + return Err(Error::Type( + "Init is present and request mode is 'navigate'".to_string())); + } + // Step 13.2 + request.omit_origin_header.set(false); + // Step 13.3 + *request.referer.borrow_mut() = NetTraitsRequestReferer::Client; + // Step 13.4 + request.referrer_policy.set(None); + } + + // Step 14 + if let Some(init_referrer) = init.referrer.as_ref() { + // Step 14.1 + let ref referrer = init_referrer.0; + // Step 14.2 + if referrer.is_empty() { + *request.referer.borrow_mut() = NetTraitsRequestReferer::NoReferer; + } else { + // Step 14.3 + let parsed_referrer = base_url.join(referrer); + // Step 14.4 + if parsed_referrer.is_err() { + return Err(Error::Type( + "Failed to parse referrer url".to_string())); + } + // Step 14.5 + if let Ok(parsed_referrer) = parsed_referrer { + if parsed_referrer.cannot_be_a_base() && + parsed_referrer.scheme() == "about" && + parsed_referrer.path() == "client" { + *request.referer.borrow_mut() = NetTraitsRequestReferer::Client; + } else { + // Step 14.6 + if parsed_referrer.origin() != origin { + return Err(Error::Type( + "RequestInit's referrer has invalid origin".to_string())); + } + // TODO: Requires Step 7. + + // Step 14.7 + *request.referer.borrow_mut() = NetTraitsRequestReferer::RefererUrl(parsed_referrer); + } + } + } + } + + // Step 15 + if let Some(init_referrerpolicy) = init.referrerPolicy.as_ref() { + let init_referrer_policy = init_referrerpolicy.clone().into(); + request.referrer_policy.set(Some(init_referrer_policy)); + } + + // Step 16 + let mode = init.mode.as_ref().map(|m| m.clone().into()).or(fallback_mode); + + // Step 17 + if let Some(NetTraitsRequestMode::Navigate) = mode { + return Err(Error::Type("Request mode is Navigate".to_string())); + } + + // Step 18 + if let Some(m) = mode { + request.mode = m; + } + + // Step 19 + let credentials = init.credentials.as_ref().map(|m| m.clone().into()).or(fallback_credentials); + + // Step 20 + if let Some(c) = credentials { + request.credentials_mode = c; + } + + // Step 21 + if let Some(init_cache) = init.cache.as_ref() { + let cache = init_cache.clone().into(); + request.cache_mode.set(cache); + } + + // Step 22 + if request.cache_mode.get() == NetTraitsRequestCache::OnlyIfCached { + if request.mode != NetTraitsRequestMode::SameOrigin { + return Err(Error::Type( + "Cache is 'only-if-cached' and mode is not 'same-origin'".to_string())); + } + } + + // Step 23 + if let Some(init_redirect) = init.redirect.as_ref() { + let redirect = init_redirect.clone().into(); + request.redirect_mode.set(redirect); + } + + // Step 24 + if let Some(init_integrity) = init.integrity.as_ref() { + let integrity = init_integrity.clone().to_string(); + *request.integrity_metadata.borrow_mut() = integrity; + } + + // Step 25 + if let Some(init_method) = init.method.as_ref() { + // Step 25.1 + if !is_method(&init_method) { + return Err(Error::Type("Method is not a method".to_string())); + } + if is_forbidden_method(&init_method) { + return Err(Error::Type("Method is forbidden".to_string())); + } + // Step 25.2 + let method_lower = init_method.to_lower(); + let method_string = match method_lower.as_str() { + Some(s) => s, + None => return Err(Error::Type("Method is not a valid UTF8".to_string())), + }; + let normalized_method = normalize_method(method_string); + // Step 25.3 + let hyper_method = normalized_method_to_typed_method(&normalized_method); + *request.method.borrow_mut() = hyper_method; + } + + // Step 26 + let r = Request::from_net_request(global, + false, + request); + r.headers.or_init(|| Headers::for_request(r.global().r())); + + // Step 27 + let mut headers_copy = r.Headers(); + + // This is equivalent to the specification's concept of + // "associated headers list". + if let RequestInfo::Request(ref input_request) = input { + headers_copy = input_request.Headers(); + } + + // Step 28 + if let Some(possible_header) = init.headers.as_ref() { + if let &HeadersOrByteStringSequenceSequence::Headers(ref init_headers) = possible_header { + headers_copy = init_headers.clone(); + } + } + + // Step 29 + r.Headers().empty_header_list(); + + // Step 30 + if r.request.borrow().mode == NetTraitsRequestMode::NoCORS { + let borrowed_request = r.request.borrow(); + // Step 30.1 + if !is_cors_safelisted_method(&borrowed_request.method.borrow()) { + return Err(Error::Type( + "The mode is 'no-cors' but the method is not a cors-safelisted method".to_string())); + } + // Step 30.2 + if !borrowed_request.integrity_metadata.borrow().is_empty() { + return Err(Error::Type("Integrity metadata is not an empty string".to_string())); + } + // Step 30.3 + r.Headers().set_guard(Guard::RequestNoCors); + } + + // Step 31 + r.Headers().fill(Some(HeadersOrByteStringSequenceSequence::Headers(headers_copy))); + + // Step 32 + let input_body = if let RequestInfo::Request(ref input_request) = input { + let input_request_request = input_request.request.borrow(); + let body = input_request_request.body.borrow(); + body.clone() + } else { + None + }; + + // Step 33 + if let Some(init_body_option) = init.body.as_ref() { + if init_body_option.is_some() || input_body.is_some() { + let req = r.request.borrow(); + let req_method = req.method.borrow(); + match &*req_method { + &hyper::method::Method::Get => return Err(Error::Type( + "Init's body is non-null, and request method is GET".to_string())), + &hyper::method::Method::Head => return Err(Error::Type( + "Init's body is non-null, and request method is HEAD".to_string())), + _ => {}, + } + } + } + + // Step 34 + // TODO: `ReadableStream` object is not implemented in Servo yet. + + // Step 35 + { + let borrowed_request = r.request.borrow(); + *borrowed_request.body.borrow_mut() = input_body; + } + + // Step 36 + let extracted_mime_type = r.Headers().extract_mime_type(); + *r.mime_type.borrow_mut() = extracted_mime_type; + + // Step 37 + // TODO: `ReadableStream` object is not implemented in Servo yet. + + // Step 38 + Ok(r) + } +} + +impl Request { + fn from_net_request(global: GlobalRef, + is_service_worker_global_scope: bool, + net_request: NetTraitsRequest) -> Root { + let r = Request::new(global, + net_request.current_url(), + is_service_worker_global_scope); + *r.request.borrow_mut() = net_request; + r + } + + fn clone_from(r: &Request) -> Root { + let req = r.request.borrow(); + let url = req.url(); + let is_service_worker_global_scope = req.is_service_worker_global_scope; + let body_used = r.body_used.get(); + let mime_type = r.mime_type.borrow().clone(); + let headers_guard = r.Headers().get_guard(); + let r_clone = reflect_dom_object( + box Request::new_inherited(r.global().r(), + url, + is_service_worker_global_scope), + r.global().r(), RequestBinding::Wrap); + r_clone.request.borrow_mut().pipeline_id.set(req.pipeline_id.get()); + { + let mut borrowed_r_request = r_clone.request.borrow_mut(); + *borrowed_r_request.origin.borrow_mut() = req.origin.borrow().clone(); + } + *r_clone.request.borrow_mut() = req.clone(); + r_clone.body_used.set(body_used); + *r_clone.mime_type.borrow_mut() = mime_type; + r_clone.Headers().set_guard(headers_guard); + r_clone + } +} + +fn net_request_from_global(global: GlobalRef, + url: Url, + is_service_worker_global_scope: bool) -> NetTraitsRequest { + let origin = Origin::Origin(global.get_url().origin()); + let pipeline_id = global.pipeline(); + NetTraitsRequest::new(url, + Some(origin), + is_service_worker_global_scope, + Some(pipeline_id)) +} + +// https://fetch.spec.whatwg.org/#concept-request-current-url +fn get_current_url(req: &NetTraitsRequest) -> Option> { + let url_list = req.url_list.borrow(); + if url_list.len() > 0 { + Some(Ref::map(url_list, |urls| urls.last().unwrap())) + } else { + None + } +} + +fn normalized_method_to_typed_method(m: &str) -> hyper::method::Method { + match m { + "DELETE" => hyper::method::Method::Delete, + "GET" => hyper::method::Method::Get, + "HEAD" => hyper::method::Method::Head, + "OPTIONS" => hyper::method::Method::Options, + "POST" => hyper::method::Method::Post, + "PUT" => hyper::method::Method::Put, + a => hyper::method::Method::Extension(a.to_string()) + } +} + +// https://fetch.spec.whatwg.org/#concept-method-normalize +fn normalize_method(m: &str) -> String { + match m { + "delete" => "DELETE".to_string(), + "get" => "GET".to_string(), + "head" => "HEAD".to_string(), + "options" => "OPTIONS".to_string(), + "post" => "POST".to_string(), + "put" => "PUT".to_string(), + a => a.to_string(), + } +} + +// https://fetch.spec.whatwg.org/#concept-method +fn is_method(m: &ByteString) -> bool { + match m.to_lower().as_str() { + Some("get") => true, + Some("head") => true, + Some("post") => true, + Some("put") => true, + Some("delete") => true, + Some("connect") => true, + Some("options") => true, + Some("trace") => true, + _ => false, + } +} + +// https://fetch.spec.whatwg.org/#forbidden-method +fn is_forbidden_method(m: &ByteString) -> bool { + match m.to_lower().as_str() { + Some("connect") => true, + Some("trace") => true, + Some("track") => true, + _ => false, + } +} + +// https://fetch.spec.whatwg.org/#cors-safelisted-method +fn is_cors_safelisted_method(m: &hyper::method::Method) -> bool { + m == &hyper::method::Method::Get || + m == &hyper::method::Method::Head || + m == &hyper::method::Method::Post +} + +// https://url.spec.whatwg.org/#include-credentials +fn includes_credentials(input: &Url) -> bool { + !input.username().is_empty() || input.password().is_some() +} + +// TODO: `Readable Stream` object is not implemented in Servo yet. +// https://fetch.spec.whatwg.org/#concept-body-disturbed +fn request_is_disturbed(input: &Request) -> bool { + false +} + +// TODO: `Readable Stream` object is not implemented in Servo yet. +// https://fetch.spec.whatwg.org/#concept-body-locked +fn request_is_locked(input: &Request) -> bool { + false +} + +impl RequestMethods for Request { + // https://fetch.spec.whatwg.org/#dom-request-method + fn Method(&self) -> ByteString { + let r = self.request.borrow(); + let m = r.method.borrow(); + ByteString::new(m.as_ref().as_bytes().into()) + } + + // https://fetch.spec.whatwg.org/#dom-request-url + fn Url(&self) -> USVString { + let r = self.request.borrow(); + let url = r.url_list.borrow(); + USVString(url.get(0).map_or("", |u| u.as_str()).into()) + } + + // https://fetch.spec.whatwg.org/#dom-request-headers + fn Headers(&self) -> Root { + self.headers.or_init(|| Headers::new(self.global().r())) + } + + // https://fetch.spec.whatwg.org/#dom-request-type + fn Type(&self) -> RequestType { + self.request.borrow().type_.into() + } + + // https://fetch.spec.whatwg.org/#dom-request-destination + fn Destination(&self) -> RequestDestination { + self.request.borrow().destination.into() + } + + // https://fetch.spec.whatwg.org/#dom-request-referrer + fn Referrer(&self) -> USVString { + let r = self.request.borrow(); + let referrer = r.referer.borrow(); + USVString(match &*referrer { + &NetTraitsRequestReferer::NoReferer => String::from("no-referrer"), + &NetTraitsRequestReferer::Client => String::from("client"), + &NetTraitsRequestReferer::RefererUrl(ref u) => { + let u_c = u.clone(); + u_c.into_string() + } + }) + } + + // https://fetch.spec.whatwg.org/#dom-request-referrerpolicy + fn ReferrerPolicy(&self) -> ReferrerPolicy { + self.request.borrow().referrer_policy.get().map(|m| m.into()).unwrap_or(ReferrerPolicy::_empty) + } + + // https://fetch.spec.whatwg.org/#dom-request-mode + fn Mode(&self) -> RequestMode { + self.request.borrow().mode.into() + } + + // https://fetch.spec.whatwg.org/#dom-request-credentials + fn Credentials(&self) -> RequestCredentials { + let r = self.request.borrow().clone(); + r.credentials_mode.into() + } + + // https://fetch.spec.whatwg.org/#dom-request-cache + fn Cache(&self) -> RequestCache { + let r = self.request.borrow().clone(); + r.cache_mode.get().into() + } + + // https://fetch.spec.whatwg.org/#dom-request-redirect + fn Redirect(&self) -> RequestRedirect { + let r = self.request.borrow().clone(); + r.redirect_mode.get().into() + } + + // https://fetch.spec.whatwg.org/#dom-request-integrity + fn Integrity(&self) -> DOMString { + let r = self.request.borrow(); + let integrity = r.integrity_metadata.borrow(); + DOMString::from_string(integrity.clone()) + } + + // https://fetch.spec.whatwg.org/#dom-body-bodyused + fn BodyUsed(&self) -> bool { + self.body_used.get() + } + + // https://fetch.spec.whatwg.org/#dom-request-clone + fn Clone(&self) -> Fallible> { + // Step 1 + if request_is_locked(self) { + return Err(Error::Type("Request is locked".to_string())); + } + if request_is_disturbed(self) { + return Err(Error::Type("Request is disturbed".to_string())); + } + + // Step 2 + Ok(Request::clone_from(self)) + } +} + +impl Into for RequestCache { + fn into(self) -> NetTraitsRequestCache { + match self { + RequestCache::Default => NetTraitsRequestCache::Default, + RequestCache::No_store => NetTraitsRequestCache::NoStore, + RequestCache::Reload => NetTraitsRequestCache::Reload, + RequestCache::No_cache => NetTraitsRequestCache::NoCache, + RequestCache::Force_cache => NetTraitsRequestCache::ForceCache, + RequestCache::Only_if_cached => NetTraitsRequestCache::OnlyIfCached, + } + } +} + +impl Into for NetTraitsRequestCache { + fn into(self) -> RequestCache { + match self { + NetTraitsRequestCache::Default => RequestCache::Default, + NetTraitsRequestCache::NoStore => RequestCache::No_store, + NetTraitsRequestCache::Reload => RequestCache::Reload, + NetTraitsRequestCache::NoCache => RequestCache::No_cache, + NetTraitsRequestCache::ForceCache => RequestCache::Force_cache, + NetTraitsRequestCache::OnlyIfCached => RequestCache::Only_if_cached, + } + } +} + +impl Into for RequestCredentials { + fn into(self) -> NetTraitsRequestCredentials { + match self { + RequestCredentials::Omit => NetTraitsRequestCredentials::Omit, + RequestCredentials::Same_origin => NetTraitsRequestCredentials::CredentialsSameOrigin, + RequestCredentials::Include => NetTraitsRequestCredentials::Include, + } + } +} + +impl Into for NetTraitsRequestCredentials { + fn into(self) -> RequestCredentials { + match self { + NetTraitsRequestCredentials::Omit => RequestCredentials::Omit, + NetTraitsRequestCredentials::CredentialsSameOrigin => RequestCredentials::Same_origin, + NetTraitsRequestCredentials::Include => RequestCredentials::Include, + } + } +} + +impl Into for RequestDestination { + fn into(self) -> NetTraitsRequestDestination { + match self { + RequestDestination::_empty => NetTraitsRequestDestination::None, + RequestDestination::Document => NetTraitsRequestDestination::Document, + RequestDestination::Embed => NetTraitsRequestDestination::Embed, + RequestDestination::Font => NetTraitsRequestDestination::Font, + RequestDestination::Image => NetTraitsRequestDestination::Image, + RequestDestination::Manifest => NetTraitsRequestDestination::Manifest, + RequestDestination::Media => NetTraitsRequestDestination::Media, + RequestDestination::Object => NetTraitsRequestDestination::Object, + RequestDestination::Report => NetTraitsRequestDestination::Report, + RequestDestination::Script => NetTraitsRequestDestination::Script, + RequestDestination::Serviceworker => NetTraitsRequestDestination::ServiceWorker, + RequestDestination::Sharedworker => NetTraitsRequestDestination::SharedWorker, + RequestDestination::Style => NetTraitsRequestDestination::Style, + RequestDestination::Worker => NetTraitsRequestDestination::Worker, + RequestDestination::Xslt => NetTraitsRequestDestination::XSLT, + } + } +} + +impl Into for NetTraitsRequestDestination { + fn into(self) -> RequestDestination { + match self { + NetTraitsRequestDestination::None => RequestDestination::_empty, + NetTraitsRequestDestination::Document => RequestDestination::Document, + NetTraitsRequestDestination::Embed => RequestDestination::Embed, + NetTraitsRequestDestination::Font => RequestDestination::Font, + NetTraitsRequestDestination::Image => RequestDestination::Image, + NetTraitsRequestDestination::Manifest => RequestDestination::Manifest, + NetTraitsRequestDestination::Media => RequestDestination::Media, + NetTraitsRequestDestination::Object => RequestDestination::Object, + NetTraitsRequestDestination::Report => RequestDestination::Report, + NetTraitsRequestDestination::Script => RequestDestination::Script, + NetTraitsRequestDestination::ServiceWorker => RequestDestination::Serviceworker, + NetTraitsRequestDestination::SharedWorker => RequestDestination::Sharedworker, + NetTraitsRequestDestination::Style => RequestDestination::Style, + NetTraitsRequestDestination::XSLT => RequestDestination::Xslt, + NetTraitsRequestDestination::Worker => RequestDestination::Worker, + } + } +} + +impl Into for RequestType { + fn into(self) -> NetTraitsRequestType { + match self { + RequestType::_empty => NetTraitsRequestType::None, + RequestType::Audio => NetTraitsRequestType::Audio, + RequestType::Font => NetTraitsRequestType::Font, + RequestType::Image => NetTraitsRequestType::Image, + RequestType::Script => NetTraitsRequestType::Script, + RequestType::Style => NetTraitsRequestType::Style, + RequestType::Track => NetTraitsRequestType::Track, + RequestType::Video => NetTraitsRequestType::Video, + } + } +} + +impl Into for NetTraitsRequestType { + fn into(self) -> RequestType { + match self { + NetTraitsRequestType::None => RequestType::_empty, + NetTraitsRequestType::Audio => RequestType::Audio, + NetTraitsRequestType::Font => RequestType::Font, + NetTraitsRequestType::Image => RequestType::Image, + NetTraitsRequestType::Script => RequestType::Script, + NetTraitsRequestType::Style => RequestType::Style, + NetTraitsRequestType::Track => RequestType::Track, + NetTraitsRequestType::Video => RequestType::Video, + } + } +} + +impl Into for RequestMode { + fn into(self) -> NetTraitsRequestMode { + match self { + RequestMode::Navigate => NetTraitsRequestMode::Navigate, + RequestMode::Same_origin => NetTraitsRequestMode::SameOrigin, + RequestMode::No_cors => NetTraitsRequestMode::NoCORS, + RequestMode::Cors => NetTraitsRequestMode::CORSMode, + } + } +} + +impl Into for NetTraitsRequestMode { + fn into(self) -> RequestMode { + match self { + NetTraitsRequestMode::Navigate => RequestMode::Navigate, + NetTraitsRequestMode::SameOrigin => RequestMode::Same_origin, + NetTraitsRequestMode::NoCORS => RequestMode::No_cors, + NetTraitsRequestMode::CORSMode => RequestMode::Cors, + } + } +} + +// TODO +// When whatwg/fetch PR #346 is merged, fix this. +impl Into for ReferrerPolicy { + fn into(self) -> MsgReferrerPolicy { + match self { + ReferrerPolicy::_empty => MsgReferrerPolicy::NoReferrer, + ReferrerPolicy::No_referrer => MsgReferrerPolicy::NoReferrer, + ReferrerPolicy::No_referrer_when_downgrade => + MsgReferrerPolicy::NoReferrerWhenDowngrade, + ReferrerPolicy::Origin => MsgReferrerPolicy::Origin, + ReferrerPolicy::Origin_when_cross_origin => MsgReferrerPolicy::OriginWhenCrossOrigin, + ReferrerPolicy::Unsafe_url => MsgReferrerPolicy::UnsafeUrl, + } + } +} + +impl Into for MsgReferrerPolicy { + fn into(self) -> ReferrerPolicy { + match self { + MsgReferrerPolicy::NoReferrer => ReferrerPolicy::No_referrer, + MsgReferrerPolicy::NoReferrerWhenDowngrade => + ReferrerPolicy::No_referrer_when_downgrade, + MsgReferrerPolicy::Origin => ReferrerPolicy::Origin, + MsgReferrerPolicy::SameOrigin => ReferrerPolicy::Origin, + MsgReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicy::Origin_when_cross_origin, + MsgReferrerPolicy::UnsafeUrl => ReferrerPolicy::Unsafe_url, + } + } +} + +impl Into for RequestRedirect { + fn into(self) -> NetTraitsRequestRedirect { + match self { + RequestRedirect::Follow => NetTraitsRequestRedirect::Follow, + RequestRedirect::Error => NetTraitsRequestRedirect::Error, + RequestRedirect::Manual => NetTraitsRequestRedirect::Manual, + } + } +} + +impl Into for NetTraitsRequestRedirect { + fn into(self) -> RequestRedirect { + match self { + NetTraitsRequestRedirect::Follow => RequestRedirect::Follow, + NetTraitsRequestRedirect::Error => RequestRedirect::Error, + NetTraitsRequestRedirect::Manual => RequestRedirect::Manual, + } + } +} + +impl Clone for HeadersOrByteStringSequenceSequence { + fn clone(&self) -> HeadersOrByteStringSequenceSequence { + match self { + &HeadersOrByteStringSequenceSequence::Headers(ref h) => + HeadersOrByteStringSequenceSequence::Headers(h.clone()), + &HeadersOrByteStringSequenceSequence::ByteStringSequenceSequence(ref b) => + HeadersOrByteStringSequenceSequence::ByteStringSequenceSequence(b.clone()), + } + } +} diff --git a/components/script/dom/webidls/Body.webidl b/components/script/dom/webidls/Body.webidl new file mode 100644 index 00000000000..a020228a01d --- /dev/null +++ b/components/script/dom/webidls/Body.webidl @@ -0,0 +1,19 @@ +/* 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/. */ + +// https://fetch.spec.whatwg.org/#body + +[NoInterfaceObject, + Exposed=(Window,Worker)] + +interface Body { + readonly attribute boolean bodyUsed; + + // Servo does not support Promise at this moment. + // [NewObject] Promise arrayBuffer(); + // [NewObject] Promise blob(); + // [NewObject] Promise formData(); + // [NewObject] Promise json(); + // [NewObject] Promise text(); +}; diff --git a/components/script/dom/webidls/Request.webidl b/components/script/dom/webidls/Request.webidl new file mode 100644 index 00000000000..11393b07be0 --- /dev/null +++ b/components/script/dom/webidls/Request.webidl @@ -0,0 +1,108 @@ +/* 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/. */ + +// https://fetch.spec.whatwg.org/#request-class + +typedef (Request or USVString) RequestInfo; + +[Constructor(RequestInfo input, optional RequestInit init), + Exposed=(Window,Worker)] + +interface Request { + readonly attribute ByteString method; + readonly attribute USVString url; + [SameObject] readonly attribute Headers headers; + readonly attribute RequestType type; + readonly attribute RequestDestination destination; + readonly attribute USVString referrer; + readonly attribute ReferrerPolicy referrerPolicy; + readonly attribute RequestMode mode; + readonly attribute RequestCredentials credentials; + readonly attribute RequestCache cache; + readonly attribute RequestRedirect redirect; + readonly attribute DOMString integrity; + [NewObject, Throws] Request clone(); +}; + +Request implements Body; + +dictionary RequestInit { + ByteString method; + HeadersInit headers; + BodyInit? body; + USVString referrer; + ReferrerPolicy referrerPolicy; + RequestMode mode; + RequestCredentials credentials; + RequestCache cache; + RequestRedirect redirect; + DOMString integrity; + any window; // can only be set to null +}; + +enum RequestType { + "", + "audio", + "font", + "image", + "script", + "style", + "track", + "video" +}; + +enum RequestDestination { + "", + "document", + "embed", + "font", + "image", + "manifest", + "media", + "object", + "report", + "script", + "serviceworker", + "sharedworker", + "style", + "worker", + "xslt" +}; + +enum RequestMode { + "navigate", + "same-origin", + "no-cors", + "cors" +}; + +enum RequestCredentials { + "omit", + "same-origin", + "include" +}; + +enum RequestCache { + "default", + "no-store", + "reload", + "no-cache", + "force-cache", + "only-if-cached" +}; + +enum RequestRedirect { + "follow", + "error", + "manual" +}; + +enum ReferrerPolicy { + "", + "no-referrer", + "no-referrer-when-downgrade", + "origin", + "origin-when-cross-origin", + "unsafe-url" +}; diff --git a/tests/wpt/metadata/fetch/api/request/request-error.html.ini b/tests/wpt/metadata/fetch/api/request/request-error.html.ini index 4bb081e08d2..4d1b73005ca 100644 --- a/tests/wpt/metadata/fetch/api/request/request-error.html.ini +++ b/tests/wpt/metadata/fetch/api/request/request-error.html.ini @@ -1,62 +1,5 @@ [request-error.html] type: testharness - [RequestInit's window is not null] - expected: FAIL - - [Input URL is not valid] - expected: FAIL - - [Input URL has credentials] - expected: FAIL - - [RequestInit's mode is navigate] - expected: FAIL - - [RequestInit's referrer is invalid] - expected: FAIL - - [RequestInit's referrer has invalid origin] - expected: FAIL - - [RequestInit's method is invalid] - expected: FAIL - - [RequestInit's method is forbidden] - expected: FAIL - - [RequestInit's mode is no-cors and method is not simple] - expected: FAIL - - [RequestInit's mode is no-cors and integrity is not empty] - expected: FAIL - - [RequestInit's cache mode is only-if-cached and mode is not same-origin] - expected: FAIL - - [Request should get its content-type from the init request] - expected: FAIL - - [Request should not get its content-type from the init request if init headers are provided] - expected: FAIL - [Request should get its content-type from the body if none is provided] expected: FAIL - [Request should get its content-type from init headers if one is provided] - expected: FAIL - - [Bad referrerPolicy init parameter value] - expected: FAIL - - [Bad mode init parameter value] - expected: FAIL - - [Bad credentials init parameter value] - expected: FAIL - - [Bad cache init parameter value] - expected: FAIL - - [Bad redirect init parameter value] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/api/request/request-headers.html.ini b/tests/wpt/metadata/fetch/api/request/request-headers.html.ini index e02eb232122..b63c0f7c6d3 100644 --- a/tests/wpt/metadata/fetch/api/request/request-headers.html.ini +++ b/tests/wpt/metadata/fetch/api/request/request-headers.html.ini @@ -1,110 +1,5 @@ [request-headers.html] type: testharness - [Adding valid request header "Content-Type: OK"] - expected: FAIL - - [Adding valid request header "Potato: OK"] - expected: FAIL - - [Adding valid request header "proxy: OK"] - expected: FAIL - - [Adding valid request header "proxya: OK"] - expected: FAIL - - [Adding valid request header "sec: OK"] - expected: FAIL - - [Adding valid request header "secb: OK"] - expected: FAIL - - [Adding invalid request header "Accept-Charset: KO"] - expected: FAIL - - [Adding invalid request header "accept-charset: KO"] - expected: FAIL - - [Adding invalid request header "ACCEPT-ENCODING: KO"] - expected: FAIL - - [Adding invalid request header "Accept-Encoding: KO"] - expected: FAIL - - [Adding invalid request header "Access-Control-Request-Headers: KO"] - expected: FAIL - - [Adding invalid request header "Access-Control-Request-Method: KO"] - expected: FAIL - - [Adding invalid request header "Connection: KO"] - expected: FAIL - - [Adding invalid request header "Content-Length: KO"] - expected: FAIL - - [Adding invalid request header "Cookie: KO"] - expected: FAIL - - [Adding invalid request header "Cookie2: KO"] - expected: FAIL - - [Adding invalid request header "Date: KO"] - expected: FAIL - - [Adding invalid request header "DNT: KO"] - expected: FAIL - - [Adding invalid request header "Expect: KO"] - expected: FAIL - - [Adding invalid request header "Host: KO"] - expected: FAIL - - [Adding invalid request header "Keep-Alive: KO"] - expected: FAIL - - [Adding invalid request header "Origin: KO"] - expected: FAIL - - [Adding invalid request header "Referer: KO"] - expected: FAIL - - [Adding invalid request header "TE: KO"] - expected: FAIL - - [Adding invalid request header "Trailer: KO"] - expected: FAIL - - [Adding invalid request header "Transfer-Encoding: KO"] - expected: FAIL - - [Adding invalid request header "Upgrade: KO"] - expected: FAIL - - [Adding invalid request header "Via: KO"] - expected: FAIL - - [Adding invalid request header "Proxy-: KO"] - expected: FAIL - - [Adding invalid request header "proxy-a: KO"] - expected: FAIL - - [Adding invalid request header "Sec-: KO"] - expected: FAIL - - [Adding invalid request header "sec-b: KO"] - expected: FAIL - - [Adding valid no-cors request header "Accept: OK"] - expected: FAIL - - [Adding valid no-cors request header "Accept-Language: OK"] - expected: FAIL - - [Adding valid no-cors request header "content-language: OK"] - expected: FAIL - [Adding valid no-cors request header "content-type: application/x-www-form-urlencoded"] expected: FAIL @@ -123,42 +18,6 @@ [Adding valid no-cors request header "CONTENT-type: text/plain;charset=UTF-8"] expected: FAIL - [Adding invalid no-cors request header "Content-Type: KO"] - expected: FAIL - - [Adding invalid no-cors request header "Potato: KO"] - expected: FAIL - - [Adding invalid no-cors request header "proxy: KO"] - expected: FAIL - - [Adding invalid no-cors request header "proxya: KO"] - expected: FAIL - - [Adding invalid no-cors request header "sec: KO"] - expected: FAIL - - [Adding invalid no-cors request header "secb: KO"] - expected: FAIL - - [Check that request constructor is filtering headers provided as init parameter] - expected: FAIL - - [Check that no-cors request constructor is filtering headers provided as init parameter] - expected: FAIL - - [Check that no-cors request constructor is filtering headers provided as part of request parameter] - expected: FAIL - - [Request should get its content-type from the init request] - expected: FAIL - - [Request should not get its content-type from the init request if init headers are provided] - expected: FAIL - [Request should get its content-type from the body if none is provided] expected: FAIL - [Request should get its content-type from init headers if one is provided] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/api/request/request-init-001.sub.html.ini b/tests/wpt/metadata/fetch/api/request/request-init-001.sub.html.ini index dbfb5e957d5..b88e7f0450a 100644 --- a/tests/wpt/metadata/fetch/api/request/request-init-001.sub.html.ini +++ b/tests/wpt/metadata/fetch/api/request/request-init-001.sub.html.ini @@ -1,35 +1,5 @@ [request-init-001.sub.html] type: testharness - [Check method init value of GET and associated getter] - expected: FAIL - - [Check method init value of HEAD and associated getter] - expected: FAIL - - [Check method init value of POST and associated getter] - expected: FAIL - - [Check method init value of PUT and associated getter] - expected: FAIL - - [Check method init value of DELETE and associated getter] - expected: FAIL - - [Check method init value of OPTIONS and associated getter] - expected: FAIL - - [Check method init value of head and associated getter] - expected: FAIL - - [Check referrer init value of /relative/ressource and associated getter] - expected: FAIL - - [Check referrer init value of http://web-platform.test:8000/relative/ressource?query=true#fragment and associated getter] - expected: FAIL - - [Check referrer init value of http://web-platform.test:8000/ and associated getter] - expected: FAIL - [Check referrer init value of about:client and associated getter] expected: FAIL @@ -39,69 +9,3 @@ [Check referrerPolicy init value of and associated getter] expected: FAIL - [Check referrerPolicy init value of no-referrer and associated getter] - expected: FAIL - - [Check referrerPolicy init value of no-referrer-when-downgrade and associated getter] - expected: FAIL - - [Check referrerPolicy init value of origin and associated getter] - expected: FAIL - - [Check referrerPolicy init value of origin-when-cross-origin and associated getter] - expected: FAIL - - [Check referrerPolicy init value of unsafe-url and associated getter] - expected: FAIL - - [Check mode init value of same-origin and associated getter] - expected: FAIL - - [Check mode init value of no-cors and associated getter] - expected: FAIL - - [Check mode init value of cors and associated getter] - expected: FAIL - - [Check credentials init value of omit and associated getter] - expected: FAIL - - [Check credentials init value of same-origin and associated getter] - expected: FAIL - - [Check credentials init value of include and associated getter] - expected: FAIL - - [Check cache init value of default and associated getter] - expected: FAIL - - [Check cache init value of no-store and associated getter] - expected: FAIL - - [Check cache init value of reload and associated getter] - expected: FAIL - - [Check cache init value of no-cache and associated getter] - expected: FAIL - - [Check cache init value of force-cache and associated getter] - expected: FAIL - - [Check redirect init value of follow and associated getter] - expected: FAIL - - [Check redirect init value of error and associated getter] - expected: FAIL - - [Check redirect init value of manual and associated getter] - expected: FAIL - - [Check integrity init value of and associated getter] - expected: FAIL - - [Check integrity init value of AZERTYUIOP1234567890 and associated getter] - expected: FAIL - - [Check window init value of null and associated getter] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/api/request/request-structure.html.ini b/tests/wpt/metadata/fetch/api/request/request-structure.html.ini index 589a207239f..9d40c13ac0c 100644 --- a/tests/wpt/metadata/fetch/api/request/request-structure.html.ini +++ b/tests/wpt/metadata/fetch/api/request/request-structure.html.ini @@ -1,3 +1,23 @@ [request-structure.html] type: testharness - expected: TIMEOUT + [Request has arrayBuffer method] + expected: FAIL + + [Request has blob method] + expected: FAIL + + [Request has formData method] + expected: FAIL + + [Request has json method] + expected: FAIL + + [Request has text method] + expected: FAIL + + [Check headers attribute] + expected: FAIL + + [Check referrer attribute] + expected: FAIL + diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html index f037f9a879e..89f84429289 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html @@ -147,6 +147,7 @@ test_interfaces([ "ProgressEvent", "RadioNodeList", "Range", + "Request", "Screen", "Storage", "StorageEvent", diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js index 68d5cfbcedf..cd70470a120 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js @@ -89,6 +89,7 @@ test_interfaces([ "ProgressEvent", "RadioNodeList", "Range", + "Request", "Screen", "Storage", "StorageEvent", From 69f4cf680851e9690f8d93bd9fd44d1eca725882 Mon Sep 17 00:00:00 2001 From: Jeena Lee Date: Wed, 10 Aug 2016 15:30:12 -0700 Subject: [PATCH 2/2] Modify Headers API to correctly validate value. This commit modifies the headers API script to correctly validate value. As a result of this change, more wpt tests pass. The commit also changes the expected test results. --- components/script/dom/headers.rs | 23 +++++++++++-------- .../fetch/api/headers/headers-basic.html.ini | 9 -------- .../fetch/api/headers/headers-casing.html.ini | 9 -------- .../api/headers/headers-combine.html.ini | 6 ----- .../fetch/api/headers/headers-errors.html.ini | 3 --- .../api/headers/headers-normalize.html.ini | 6 ----- 6 files changed, 14 insertions(+), 42 deletions(-) diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index 3329952e0a9..cb3b4d13e48 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -80,8 +80,11 @@ impl HeadersMethods for Headers { return Ok(()); } // Step 7 - let mut combined_value = self.header_list.borrow_mut().get_raw(&valid_name).unwrap()[0].clone(); - combined_value.push(b","[0]); + let mut combined_value: Vec = vec![]; + if let Some(v) = self.header_list.borrow().get_raw(&valid_name) { + combined_value = v[0].clone(); + combined_value.push(b","[0]); + } combined_value.extend(valid_value.iter().cloned()); self.header_list.borrow_mut().set_raw(valid_name, vec![combined_value]); Ok(()) @@ -165,8 +168,6 @@ impl Headers { match filler { // Step 1 Some(HeadersOrByteStringSequenceSequence::Headers(h)) => { - // header_list_copy has type hyper::header::Headers - let header_list_copy = h.header_list.clone(); for header in h.header_list.borrow().iter() { try!(self.Append( ByteString::new(Vec::from(header.name())), @@ -346,20 +347,24 @@ fn is_field_name(name: &ByteString) -> bool { // field-content = field-vchar [ 1*( SP / HTAB / field-vchar ) // field-vchar ] fn is_field_content(value: &ByteString) -> bool { - if value.len() == 0 { + let value_len = value.len(); + + if value_len == 0 { return false; } if !is_field_vchar(value[0]) { return false; } - for &ch in &value[1..value.len() - 1] { - if !is_field_vchar(ch) || !is_space(ch) || !is_htab(ch) { - return false; + if value_len > 2 { + for &ch in &value[1..value_len - 1] { + if !is_field_vchar(ch) && !is_space(ch) && !is_htab(ch) { + return false; + } } } - if !is_field_vchar(value[value.len() - 1]) { + if !is_field_vchar(value[value_len - 1]) { return false; } diff --git a/tests/wpt/metadata/fetch/api/headers/headers-basic.html.ini b/tests/wpt/metadata/fetch/api/headers/headers-basic.html.ini index 16379eaa71a..69a663ec45f 100644 --- a/tests/wpt/metadata/fetch/api/headers/headers-basic.html.ini +++ b/tests/wpt/metadata/fetch/api/headers/headers-basic.html.ini @@ -3,21 +3,12 @@ [Create headers from empty object] expected: FAIL - [Create headers with sequence] - expected: FAIL - [Create headers with OpenEndedDictionary] expected: FAIL [Create headers with existing headers] expected: FAIL - [Check append method] - expected: FAIL - - [Check set method] - expected: FAIL - [Check has method] expected: FAIL diff --git a/tests/wpt/metadata/fetch/api/headers/headers-casing.html.ini b/tests/wpt/metadata/fetch/api/headers/headers-casing.html.ini index c9b0c365129..12f926380bf 100644 --- a/tests/wpt/metadata/fetch/api/headers/headers-casing.html.ini +++ b/tests/wpt/metadata/fetch/api/headers/headers-casing.html.ini @@ -3,12 +3,3 @@ [Create headers, names use characters with different case] expected: FAIL - [Check append method, names use characters with different case] - expected: FAIL - - [Check set method, names use characters with different case] - expected: FAIL - - [Check delete method, names use characters with different case] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/api/headers/headers-combine.html.ini b/tests/wpt/metadata/fetch/api/headers/headers-combine.html.ini index 1403b64a1c8..f12bddcc5c9 100644 --- a/tests/wpt/metadata/fetch/api/headers/headers-combine.html.ini +++ b/tests/wpt/metadata/fetch/api/headers/headers-combine.html.ini @@ -3,12 +3,6 @@ [Create headers using same name for different values] expected: FAIL - [Check delete and has methods when using same name for different values] - expected: FAIL - - [Check set methods when called with already used name] - expected: FAIL - [Check append methods when called with already used name] expected: FAIL diff --git a/tests/wpt/metadata/fetch/api/headers/headers-errors.html.ini b/tests/wpt/metadata/fetch/api/headers/headers-errors.html.ini index 96439e158ec..3941ca87bcf 100644 --- a/tests/wpt/metadata/fetch/api/headers/headers-errors.html.ini +++ b/tests/wpt/metadata/fetch/api/headers/headers-errors.html.ini @@ -1,8 +1,5 @@ [headers-errors.html] type: testharness - [Headers forEach throws if argument is not callable] - expected: FAIL - [Headers forEach loop should stop if callback is throwing exception] expected: FAIL diff --git a/tests/wpt/metadata/fetch/api/headers/headers-normalize.html.ini b/tests/wpt/metadata/fetch/api/headers/headers-normalize.html.ini index 1ba80cd1437..4c333d52194 100644 --- a/tests/wpt/metadata/fetch/api/headers/headers-normalize.html.ini +++ b/tests/wpt/metadata/fetch/api/headers/headers-normalize.html.ini @@ -3,9 +3,3 @@ [Create headers with not normalized values] expected: FAIL - [Check append method whith not normalized values] - expected: FAIL - - [Check set method whith not normalized values] - expected: FAIL -