aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/headers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom/headers.rs')
-rw-r--r--components/script/dom/headers.rs427
1 files changed, 246 insertions, 181 deletions
diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs
index ac5b332b51b..03fecd79c5a 100644
--- a/components/script/dom/headers.rs
+++ b/components/script/dom/headers.rs
@@ -1,32 +1,32 @@
/* 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::{HeadersInit, HeadersMethods, HeadersWrap};
-use dom::bindings::error::{Error, ErrorResult, Fallible};
-use dom::bindings::iterable::Iterable;
-use dom::bindings::js::Root;
-use dom::bindings::reflector::{Reflector, reflect_dom_object};
-use dom::bindings::str::{ByteString, is_token};
-use dom::globalscope::GlobalScope;
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
+use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
+use crate::dom::bindings::iterable::Iterable;
+use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::{is_token, ByteString};
+use crate::dom::globalscope::GlobalScope;
+use data_url::mime::Mime as DataUrlMime;
use dom_struct::dom_struct;
-use hyper::header::Headers as HyperHeaders;
-use mime::{Mime, TopLevel, SubLevel};
+use http::header::{HeaderMap as HyperHeaders, HeaderName, HeaderValue};
+use net_traits::request::is_cors_safelisted_request_header;
use std::cell::Cell;
-use std::result::Result;
-use std::str;
+use std::str::{self, FromStr};
#[dom_struct]
pub struct Headers {
reflector_: Reflector,
guard: Cell<Guard>,
- #[ignore_heap_size_of = "Defined in hyper"]
- header_list: DOMRefCell<HyperHeaders>
+ #[ignore_malloc_size_of = "Defined in hyper"]
+ header_list: DomRefCell<HyperHeaders>,
}
// https://fetch.spec.whatwg.org/#concept-headers-guard
-#[derive(Copy, Clone, JSTraceable, HeapSizeOf, PartialEq)]
+#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
pub enum Guard {
Immutable,
Request,
@@ -40,19 +40,22 @@ impl Headers {
Headers {
reflector_: Reflector::new(),
guard: Cell::new(Guard::None),
- header_list: DOMRefCell::new(HyperHeaders::new()),
+ header_list: DomRefCell::new(HyperHeaders::new()),
}
}
- pub fn new(global: &GlobalScope) -> Root<Headers> {
- reflect_dom_object(box Headers::new_inherited(), global, HeadersWrap)
+ pub fn new(global: &GlobalScope) -> DomRoot<Headers> {
+ reflect_dom_object(Box::new(Headers::new_inherited()), global)
}
// https://fetch.spec.whatwg.org/#dom-headers
- pub fn Constructor(global: &GlobalScope, init: Option<HeadersInit>)
- -> Fallible<Root<Headers>> {
+ #[allow(non_snake_case)]
+ pub fn Constructor(
+ global: &GlobalScope,
+ init: Option<HeadersInit>,
+ ) -> Fallible<DomRoot<Headers>> {
let dom_headers_new = Headers::new(global);
- try!(dom_headers_new.fill(init));
+ dom_headers_new.fill(init)?;
Ok(dom_headers_new)
}
}
@@ -63,7 +66,7 @@ impl HeadersMethods for Headers {
// Step 1
let value = normalize_value(value);
// Step 2
- let (mut valid_name, valid_value) = try!(validate_name_and_value(name, value));
+ let (mut valid_name, valid_value) = validate_name_and_value(name, value)?;
valid_name = valid_name.to_lowercase();
// Step 3
if self.guard.get() == Guard::Immutable {
@@ -74,7 +77,9 @@ impl HeadersMethods for Headers {
return Ok(());
}
// Step 5
- if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name, &valid_value) {
+ if self.guard.get() == Guard::RequestNoCors &&
+ !is_cors_safelisted_request_header(&valid_name, &valid_value)
+ {
return Ok(());
}
// Step 6
@@ -82,20 +87,40 @@ impl HeadersMethods for Headers {
return Ok(());
}
// Step 7
+ // FIXME: this is NOT what WHATWG says to do when appending
+ // another copy of an existing header. HyperHeaders
+ // might not expose the information we need to do it right.
let mut combined_value: Vec<u8> = vec![];
- if let Some(v) = self.header_list.borrow().get_raw(&valid_name) {
- combined_value = v[0].clone();
- combined_value.push(b',');
+ if let Some(v) = self
+ .header_list
+ .borrow()
+ .get(HeaderName::from_str(&valid_name).unwrap())
+ {
+ combined_value = v.as_bytes().to_vec();
+ combined_value.extend(b", ");
}
combined_value.extend(valid_value.iter().cloned());
- self.header_list.borrow_mut().set_raw(valid_name, vec![combined_value]);
+ match HeaderValue::from_bytes(&combined_value) {
+ Ok(value) => {
+ self.header_list
+ .borrow_mut()
+ .insert(HeaderName::from_str(&valid_name).unwrap(), value);
+ },
+ Err(_) => {
+ // can't add the header, but we don't need to panic the browser over it
+ warn!(
+ "Servo thinks \"{:?}\" is a valid HTTP header value but HeaderValue doesn't.",
+ combined_value
+ );
+ },
+ };
Ok(())
}
// https://fetch.spec.whatwg.org/#dom-headers-delete
fn Delete(&self, name: ByteString) -> ErrorResult {
// Step 1
- let valid_name = try!(validate_name(name));
+ let valid_name = validate_name(name)?;
// Step 2
if self.guard.get() == Guard::Immutable {
return Err(Error::Type("Guard is immutable".to_string()));
@@ -106,33 +131,36 @@ impl HeadersMethods for Headers {
}
// Step 4
if self.guard.get() == Guard::RequestNoCors &&
- !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec()) {
- return Ok(());
- }
+ !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec())
+ {
+ return Ok(());
+ }
// Step 5
if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
return Ok(());
}
// Step 6
- self.header_list.borrow_mut().remove_raw(&valid_name);
+ self.header_list.borrow_mut().remove(&valid_name);
Ok(())
}
// https://fetch.spec.whatwg.org/#dom-headers-get
fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> {
// Step 1
- let valid_name = &try!(validate_name(name));
- Ok(self.header_list.borrow().get_raw(&valid_name).map(|v| {
- ByteString::new(v[0].clone())
- }))
+ let valid_name = validate_name(name)?;
+ Ok(self
+ .header_list
+ .borrow()
+ .get(HeaderName::from_str(&valid_name).unwrap())
+ .map(|v| ByteString::new(v.as_bytes().to_vec())))
}
// https://fetch.spec.whatwg.org/#dom-headers-has
fn Has(&self, name: ByteString) -> Fallible<bool> {
// Step 1
- let valid_name = try!(validate_name(name));
+ let valid_name = validate_name(name)?;
// Step 2
- Ok(self.header_list.borrow_mut().get_raw(&valid_name).is_some())
+ Ok(self.header_list.borrow_mut().get(&valid_name).is_some())
}
// https://fetch.spec.whatwg.org/#dom-headers-set
@@ -140,7 +168,7 @@ impl HeadersMethods for Headers {
// Step 1
let value = normalize_value(value);
// Step 2
- let (mut valid_name, valid_value) = try!(validate_name_and_value(name, value));
+ let (mut valid_name, valid_value) = validate_name_and_value(name, value)?;
valid_name = valid_name.to_lowercase();
// Step 3
if self.guard.get() == Guard::Immutable {
@@ -151,7 +179,9 @@ impl HeadersMethods for Headers {
return Ok(());
}
// Step 5
- if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name, &valid_value) {
+ if self.guard.get() == Guard::RequestNoCors &&
+ !is_cors_safelisted_request_header(&valid_name, &valid_value)
+ {
return Ok(());
}
// Step 6
@@ -160,32 +190,34 @@ impl HeadersMethods for Headers {
}
// Step 7
// https://fetch.spec.whatwg.org/#concept-header-list-set
- self.header_list.borrow_mut().set_raw(valid_name, vec![valid_value]);
+ self.header_list.borrow_mut().insert(
+ HeaderName::from_str(&valid_name).unwrap(),
+ HeaderValue::from_bytes(&valid_value).unwrap(),
+ );
Ok(())
}
}
impl Headers {
+ pub fn copy_from_headers(&self, headers: DomRoot<Headers>) -> ErrorResult {
+ for (name, value) in headers.header_list.borrow().iter() {
+ self.Append(
+ ByteString::new(Vec::from(name.as_str())),
+ ByteString::new(Vec::from(value.as_bytes())),
+ )?;
+ }
+ Ok(())
+ }
+
// https://fetch.spec.whatwg.org/#concept-headers-fill
pub fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult {
match filler {
- // Step 1
- Some(HeadersInit::Headers(h)) => {
- 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(HeadersInit::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));
+ self.Append(name, val)?;
} else {
return Err(Error::Type(
format!("Each header object must be a sequence of length 2 - found one with length {}",
@@ -194,11 +226,9 @@ impl Headers {
}
Ok(())
},
- Some(HeadersInit::ByteStringMozMap(m)) => {
+ Some(HeadersInit::ByteStringByteStringRecord(m)) => {
for (key, value) in m.iter() {
- let key_vec = key.as_ref().to_string().into();
- let headers_key = ByteString::new(key_vec);
- try!(self.Append(headers_key, value.clone()));
+ self.Append(key.clone(), value.clone())?;
}
Ok(())
},
@@ -206,13 +236,13 @@ impl Headers {
}
}
- pub fn for_request(global: &GlobalScope) -> Root<Headers> {
+ pub fn for_request(global: &GlobalScope) -> DomRoot<Headers> {
let headers_for_request = Headers::new(global);
headers_for_request.guard.set(Guard::Request);
headers_for_request
}
- pub fn for_response(global: &GlobalScope) -> Root<Headers> {
+ pub fn for_response(global: &GlobalScope) -> DomRoot<Headers> {
let headers_for_response = Headers::new(global);
headers_for_response.guard.set(Guard::Response);
headers_for_response
@@ -234,18 +264,22 @@ impl Headers {
*self.header_list.borrow_mut() = hyper_headers;
}
+ pub fn get_headers_list(&self) -> HyperHeaders {
+ self.header_list.borrow_mut().clone()
+ }
+
// https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
pub fn extract_mime_type(&self) -> Vec<u8> {
- self.header_list.borrow().get_raw("content-type").map_or(vec![], |v| v[0].clone())
+ extract_mime_type(&*self.header_list.borrow()).unwrap_or(vec![])
}
- pub fn sort_header_list(&self) -> Vec<(String, String)> {
+ pub fn sort_header_list(&self) -> Vec<(String, Vec<u8>)> {
let borrowed_header_list = self.header_list.borrow();
let headers_iter = borrowed_header_list.iter();
let mut header_vec = vec![];
- for header in headers_iter {
- let name = header.name().to_string();
- let value = header.value_string();
+ for (name, value) in headers_iter {
+ let name = name.as_str().to_owned();
+ let value = value.as_bytes().to_vec();
let name_value = (name, value);
header_vec.push(name_value);
}
@@ -265,7 +299,7 @@ impl Iterable for Headers {
fn get_value_at_index(&self, n: u32) -> ByteString {
let sorted_header_vec = self.sort_header_list();
let value = sorted_header_vec[n as usize].1.clone();
- ByteString::new(value.into_bytes().to_vec())
+ ByteString::new(value)
}
fn get_key_at_index(&self, n: u32) -> ByteString {
@@ -275,94 +309,60 @@ impl Iterable for Headers {
}
}
-fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool {
- let value_string = if let Ok(s) = str::from_utf8(value) {
- s
- } else {
- return false;
- };
- let value_mime_result: Result<Mime, _> = value_string.parse();
- match value_mime_result {
- Err(_) => false,
- Ok(value_mime) => {
- match value_mime {
- Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _) |
- Mime(TopLevel::Multipart, SubLevel::FormData, _) |
- Mime(TopLevel::Text, SubLevel::Plain, _) => true,
- _ => false,
- }
- }
- }
-}
-
-// TODO: "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, value: &[u8]) -> bool {
- match name {
- "accept" |
- "accept-language" |
- "content-language" => true,
- "content-type" => is_cors_safelisted_request_content_type(value),
- _ => 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,
+ "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_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))
+ 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 field-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)
- -> Fallible<(String, Vec<u8>)> {
- let valid_name = try!(validate_name(name));
- if !is_field_content(&value) {
- return Err(Error::Type("Value is not valid".to_string()));
+//
+// As of December 2019, WHATWG has no formal grammar production for value;
+// https://fetch.spec.whatg.org/#concept-header-value just says not to have
+// newlines, nulls, or leading/trailing whitespace. It even allows
+// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this.
+// The HeaderValue class does not fully reflect this, so headers
+// containing bytes with values 1..31 or 127 can't be created, failing
+// WPT tests but probably not affecting anything important on the real Internet.
+fn validate_name_and_value(name: ByteString, value: ByteString) -> Fallible<(String, Vec<u8>)> {
+ let valid_name = validate_name(name)?;
+ if !is_legal_header_value(&value) {
+ return Err(Error::Type("Header value is not valid".to_string()));
}
Ok((valid_name, value.into()))
}
@@ -380,19 +380,22 @@ fn validate_name(name: ByteString) -> Fallible<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)) {
+ 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 {
+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) {
+ if !is_http_whitespace(byte) {
return Some(index);
}
}
@@ -401,7 +404,7 @@ fn index_of_first_non_whitespace(value: &ByteString) -> Option<usize> {
fn index_of_last_non_whitespace(value: &ByteString) -> Option<usize> {
for (index, &byte) in value.iter().enumerate().rev() {
- if !is_HTTP_whitespace(byte) {
+ if !is_http_whitespace(byte) {
return Some(index);
}
}
@@ -413,53 +416,46 @@ 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 {
+// https://fetch.spec.whatg.org/#concept-header-value
+fn is_legal_header_value(value: &ByteString) -> bool {
let value_len = value.len();
-
if value_len == 0 {
- return false;
- }
- if !is_field_vchar(value[0]) {
- return false;
+ return true;
}
-
- 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;
- }
+ match value[0] {
+ b' ' | b'\t' => return false,
+ _ => {},
+ };
+ match value[value_len - 1] {
+ b' ' | b'\t' => return false,
+ _ => {},
+ };
+ for &ch in &value[..] {
+ match ch {
+ b'\0' | b'\n' | b'\r' => 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)
+ true
+ // If accepting non-UTF8 header values causes breakage,
+ // removing the above "true" and uncommenting the below code
+ // would ameliorate it while still accepting most reasonable headers:
+ //match str::from_utf8(value) {
+ // Ok(_) => true,
+ // Err(_) => {
+ // warn!(
+ // "Rejecting spec-legal but non-UTF8 header value: {:?}",
+ // value
+ // );
+ // false
+ // },
+ // }
}
// https://tools.ietf.org/html/rfc5234#appendix-B.1
pub fn is_vchar(x: u8) -> bool {
match x {
- 0x21...0x7E => true,
+ 0x21..=0x7E => true,
_ => false,
}
}
@@ -467,7 +463,76 @@ pub fn is_vchar(x: u8) -> bool {
// http://tools.ietf.org/html/rfc7230#section-3.2.6
pub fn is_obs_text(x: u8) -> bool {
match x {
- 0x80...0xFF => true,
+ 0x80..=0xFF => true,
_ => false,
}
}
+
+// https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
+// This function uses data_url::Mime to parse the MIME Type because
+// mime::Mime does not provide a parser following the Fetch spec
+// see https://github.com/hyperium/mime/issues/106
+pub fn extract_mime_type(headers: &HyperHeaders) -> Option<Vec<u8>> {
+ let mut charset: Option<String> = None;
+ let mut essence: String = "".to_string();
+ let mut mime_type: Option<DataUrlMime> = None;
+
+ // Step 4
+ let headers_values = headers.get_all(http::header::CONTENT_TYPE).iter();
+
+ // Step 5
+ if headers_values.size_hint() == (0, Some(0)) {
+ return None;
+ }
+
+ // Step 6
+ for header_value in headers_values {
+ // Step 6.1
+ match DataUrlMime::from_str(header_value.to_str().unwrap_or("")) {
+ // Step 6.2
+ Err(_) => continue,
+ Ok(temp_mime) => {
+ let temp_essence = format!("{}/{}", temp_mime.type_, temp_mime.subtype);
+
+ // Step 6.2
+ if temp_essence == "*/*" {
+ continue;
+ }
+
+ let temp_charset = &temp_mime.get_parameter("charset");
+
+ // Step 6.3
+ mime_type = Some(DataUrlMime {
+ type_: temp_mime.type_.to_string(),
+ subtype: temp_mime.subtype.to_string(),
+ parameters: temp_mime.parameters.clone(),
+ });
+
+ // Step 6.4
+ if temp_essence != essence {
+ charset = temp_charset.map(|c| c.to_string());
+ essence = temp_essence.to_owned();
+ } else {
+ // Step 6.5
+ if temp_charset.is_none() && charset.is_some() {
+ let DataUrlMime {
+ type_: t,
+ subtype: st,
+ parameters: p,
+ } = mime_type.unwrap();
+ let mut params = p;
+ params.push(("charset".to_string(), charset.clone().unwrap()));
+ mime_type = Some(DataUrlMime {
+ type_: t.to_string(),
+ subtype: st.to_string(),
+ parameters: params,
+ })
+ }
+ }
+ },
+ }
+ }
+
+ // Step 7, 8
+ return mime_type.map(|m| format!("{}", m).into_bytes());
+}