aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
Diffstat (limited to 'components/script')
-rw-r--r--components/script/dom/bindings/str.rs2
-rw-r--r--components/script/dom/headers.rs251
-rw-r--r--components/script/dom/mod.rs1
-rw-r--r--components/script/dom/webidls/Headers.webidl22
-rw-r--r--components/script/dom/xmlhttprequest.rs18
5 files changed, 278 insertions, 16 deletions
diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs
index 5d79d29b77b..c73a08d182b 100644
--- a/components/script/dom/bindings/str.rs
+++ b/components/script/dom/bindings/str.rs
@@ -15,7 +15,7 @@ use std::str::{Bytes, FromStr};
use string_cache::Atom;
/// Encapsulates the IDL `ByteString` type.
-#[derive(JSTraceable, Clone, Eq, PartialEq, HeapSizeOf)]
+#[derive(JSTraceable, Clone, Eq, PartialEq, HeapSizeOf, Debug)]
pub struct ByteString(Vec<u8>);
impl ByteString {
diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs
new file mode 100644
index 00000000000..e97edc00572
--- /dev/null
+++ b/components/script/dom/headers.rs
@@ -0,0 +1,251 @@
+/* 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::HeadersBinding;
+use dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
+use dom::bindings::error::Error;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::Root;
+use dom::bindings::reflector::{Reflector, reflect_dom_object};
+use dom::bindings::str::{ByteString, is_token};
+use hyper;
+use std::result::Result;
+
+#[dom_struct]
+pub struct Headers {
+ reflector_: Reflector,
+ guard: Guard,
+ #[ignore_heap_size_of = "Defined in hyper"]
+ header_list: DOMRefCell<hyper::header::Headers>
+}
+
+// https://fetch.spec.whatwg.org/#concept-headers-guard
+#[derive(JSTraceable, HeapSizeOf, PartialEq)]
+pub enum Guard {
+ Immutable,
+ Request,
+ RequestNoCors,
+ Response,
+ None,
+}
+
+impl Headers {
+ pub fn new_inherited() -> Headers {
+ Headers {
+ reflector_: Reflector::new(),
+ guard: Guard::None,
+ header_list: DOMRefCell::new(hyper::header::Headers::new()),
+ }
+ }
+
+ pub fn new(global: GlobalRef) -> Root<Headers> {
+ reflect_dom_object(box Headers::new_inherited(), global, HeadersBinding::Wrap)
+ }
+}
+
+impl HeadersMethods for Headers {
+ // https://fetch.spec.whatwg.org/#concept-headers-append
+ fn Append(&self, name: ByteString, value: ByteString) -> Result<(), Error> {
+ // Step 1
+ let value = normalize_value(value);
+
+ // Step 2
+ let (valid_name, valid_value) = try!(validate_name_and_value(name, value));
+ // Step 3
+ if self.guard == Guard::Immutable {
+ return Err(Error::Type("Guard is immutable".to_string()));
+ }
+
+ // Step 4
+ if self.guard == Guard::Request && is_forbidden_header_name(&valid_name) {
+ return Ok(());
+ }
+
+ // Step 5
+ if self.guard == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name) {
+ return Ok(());
+ }
+
+ // Step 6
+ if self.guard == Guard::Response && is_forbidden_response_header(&valid_name) {
+ return Ok(());
+ }
+
+ // Step 7
+ self.header_list.borrow_mut().set_raw(valid_name, vec![valid_value]);
+ return Ok(());
+ }
+}
+
+// TODO
+// "Content-Type" once parsed, the value should be
+// `application/x-www-form-urlencoded`, `multipart/form-data`,
+// or `text/plain`.
+// "DPR", "Downlink", "Save-Data", "Viewport-Width", "Width":
+// once parsed, the value should not be failure.
+// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
+fn is_cors_safelisted_request_header(name: &str) -> bool {
+ match name {
+ "accept" |
+ "accept-language" |
+ "content-language" => true,
+ _ => false,
+ }
+}
+
+// https://fetch.spec.whatwg.org/#forbidden-response-header-name
+fn is_forbidden_response_header(name: &str) -> bool {
+ match name {
+ "set-cookie" |
+ "set-cookie2" => true,
+ _ => false,
+ }
+}
+
+// https://fetch.spec.whatwg.org/#forbidden-header-name
+pub fn is_forbidden_header_name(name: &str) -> bool {
+ let disallowed_headers =
+ ["accept-charset", "accept-encoding",
+ "access-control-request-headers",
+ "access-control-request-method",
+ "connection", "content-length",
+ "cookie", "cookie2", "date", "dnt",
+ "expect", "host", "keep-alive", "origin",
+ "referer", "te", "trailer", "transfer-encoding",
+ "upgrade", "via"];
+
+ let disallowed_header_prefixes = ["sec-", "proxy-"];
+
+ disallowed_headers.iter().any(|header| *header == name) ||
+ disallowed_header_prefixes.iter().any(|prefix| name.starts_with(prefix))
+}
+
+// There is some unresolved confusion over the definition of a name and a value.
+// The fetch spec [1] defines a name as "a case-insensitive byte
+// sequence that matches the field-name token production. The token
+// productions are viewable in [2]." A field-name is defined as a
+// token, which is defined in [3].
+// ISSUE 1:
+// It defines a value as "a byte sequence that matches the field-content token production."
+// To note, there is a difference between field-content and
+// field-value (which is made up of fied-content and obs-fold). The
+// current definition does not allow for obs-fold (which are white
+// space and newlines) in values. So perhaps a value should be defined
+// as "a byte sequence that matches the field-value token production."
+// However, this would then allow values made up entirely of white space and newlines.
+// RELATED ISSUE 2:
+// According to a previously filed Errata ID: 4189 in [4], "the
+// specified field-value rule does not allow single field-vchar
+// surrounded by whitespace anywhere". They provided a fix for the
+// field-content production, but ISSUE 1 has still not been resolved.
+// The production definitions likely need to be re-written.
+// [1] https://fetch.spec.whatwg.org/#concept-header-value
+// [2] https://tools.ietf.org/html/rfc7230#section-3.2
+// [3] https://tools.ietf.org/html/rfc7230#section-3.2.6
+// [4] https://www.rfc-editor.org/errata_search.php?rfc=7230
+fn validate_name_and_value(name: ByteString, value: ByteString)
+ -> Result<(String, Vec<u8>), Error> {
+ if !is_field_name(&name) {
+ return Err(Error::Type("Name is not valid".to_string()));
+ }
+ if !is_field_content(&value) {
+ return Err(Error::Type("Value is not valid".to_string()));
+ }
+ match String::from_utf8(name.into()) {
+ Ok(ns) => Ok((ns, value.into())),
+ _ => Err(Error::Type("Non-UTF8 header name found".to_string())),
+ }
+}
+
+// Removes trailing and leading HTTP whitespace bytes.
+// https://fetch.spec.whatwg.org/#concept-header-value-normalize
+pub fn normalize_value(value: ByteString) -> ByteString {
+ match (index_of_first_non_whitespace(&value), index_of_last_non_whitespace(&value)) {
+ (Some(begin), Some(end)) => ByteString::new(value[begin..end + 1].to_owned()),
+ _ => ByteString::new(vec![]),
+ }
+}
+
+fn is_HTTP_whitespace(byte: u8) -> bool {
+ byte == b'\t' || byte == b'\n' || byte == b'\r' || byte == b' '
+}
+
+fn index_of_first_non_whitespace(value: &ByteString) -> Option<usize> {
+ for (index, &byte) in value.iter().enumerate() {
+ if !is_HTTP_whitespace(byte) {
+ return Some(index);
+ }
+ }
+ None
+}
+
+fn index_of_last_non_whitespace(value: &ByteString) -> Option<usize> {
+ for (index, &byte) in value.iter().enumerate().rev() {
+ if !is_HTTP_whitespace(byte) {
+ return Some(index);
+ }
+ }
+ None
+}
+
+// http://tools.ietf.org/html/rfc7230#section-3.2
+fn is_field_name(name: &ByteString) -> bool {
+ is_token(&*name)
+}
+
+// https://tools.ietf.org/html/rfc7230#section-3.2
+// http://www.rfc-editor.org/errata_search.php?rfc=7230
+// Errata ID: 4189
+// field-content = field-vchar [ 1*( SP / HTAB / field-vchar )
+// field-vchar ]
+fn is_field_content(value: &ByteString) -> bool {
+ 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 !is_field_vchar(value[value.len() - 1]) {
+ return false;
+ }
+
+ return true;
+}
+
+fn is_space(x: u8) -> bool {
+ x == b' '
+}
+
+fn is_htab(x: u8) -> bool {
+ x == b'\t'
+}
+
+// https://tools.ietf.org/html/rfc7230#section-3.2
+fn is_field_vchar(x: u8) -> bool {
+ is_vchar(x) || is_obs_text(x)
+}
+
+// https://tools.ietf.org/html/rfc5234#appendix-B.1
+fn is_vchar(x: u8) -> bool {
+ match x {
+ 0x21...0x7E => true,
+ _ => false,
+ }
+}
+
+// http://tools.ietf.org/html/rfc7230#section-3.2.6
+fn is_obs_text(x: u8) -> bool {
+ match x {
+ 0x80...0xFF => true,
+ _ => false,
+ }
+}
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index d76721ed040..172026c4d8e 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -269,6 +269,7 @@ pub mod focusevent;
pub mod forcetouchevent;
pub mod formdata;
pub mod hashchangeevent;
+pub mod headers;
pub mod htmlanchorelement;
pub mod htmlappletelement;
pub mod htmlareaelement;
diff --git a/components/script/dom/webidls/Headers.webidl b/components/script/dom/webidls/Headers.webidl
new file mode 100644
index 00000000000..038dbe46f74
--- /dev/null
+++ b/components/script/dom/webidls/Headers.webidl
@@ -0,0 +1,22 @@
+/* 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/#headers-class
+
+/* typedef (Headers or sequence<sequence<ByteString>>) HeadersInit; */
+
+/* [Constructor(optional HeadersInit init),*/
+ [Exposed=(Window,Worker)]
+
+interface Headers {
+ [Throws]
+ void append(ByteString name, ByteString value);
+};
+
+/* void delete(ByteString name);
+ * ByteString? get(ByteString name);
+ * boolean has(ByteString name);
+ * void set(ByteString name, ByteString value);
+ * iterable<ByteString, ByteString>;
+ * }; */
diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs
index 9ac02936673..5cef702c01a 100644
--- a/components/script/dom/xmlhttprequest.rs
+++ b/components/script/dom/xmlhttprequest.rs
@@ -25,6 +25,7 @@ use dom::document::DocumentSource;
use dom::document::{Document, IsHTMLDocument};
use dom::event::{Event, EventBubbles, EventCancelable};
use dom::eventtarget::EventTarget;
+use dom::headers::is_forbidden_header_name;
use dom::progressevent::ProgressEvent;
use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
use dom::xmlhttprequestupload::XMLHttpRequestUpload;
@@ -409,21 +410,8 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
// Step 5
// Disallowed headers and header prefixes:
// https://fetch.spec.whatwg.org/#forbidden-header-name
- let disallowedHeaders =
- ["accept-charset", "accept-encoding",
- "access-control-request-headers",
- "access-control-request-method",
- "connection", "content-length",
- "cookie", "cookie2", "date", "dnt",
- "expect", "host", "keep-alive", "origin",
- "referer", "te", "trailer", "transfer-encoding",
- "upgrade", "via"];
-
- let disallowedHeaderPrefixes = ["sec-", "proxy-"];
-
- if disallowedHeaders.iter().any(|header| *header == s) ||
- disallowedHeaderPrefixes.iter().any(|prefix| s.starts_with(prefix)) {
- return Ok(())
+ if is_forbidden_header_name(s) {
+ return Ok(());
} else {
s
}