Skip to content

Commit

Permalink
Headers API constructor and methods
Browse files Browse the repository at this point in the history
- Reworked the append method to support the inner `hyper::header::Headers`'s HashMap `insert` method, which overwrites entries instead of appending.
- Filled out constructor as well as delete, get, has, and set methods.
- Updated relevant test expectations
  • Loading branch information
malisas committed Jul 30, 2016
1 parent 8a09a03 commit e631d3a
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 75 deletions.
148 changes: 131 additions & 17 deletions components/script/dom/headers.rs
Expand Up @@ -5,20 +5,21 @@
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::codegen::UnionTypes::HeadersOrByteStringSequenceSequence;
use dom::bindings::error::{Error, ErrorResult, Fallible};
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 hyper::header::Headers as HyperHeaders;
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>
header_list: DOMRefCell<HyperHeaders>
}

// https://fetch.spec.whatwg.org/#concept-headers-guard
Expand All @@ -36,12 +37,50 @@ impl Headers {
Headers {
reflector_: Reflector::new(),
guard: Guard::None,
header_list: DOMRefCell::new(hyper::header::Headers::new()),
header_list: DOMRefCell::new(HyperHeaders::new()),
}
}

pub fn new(global: GlobalRef) -> Root<Headers> {
reflect_dom_object(box Headers::new_inherited(), global, HeadersBinding::Wrap)
// https://fetch.spec.whatwg.org/#concept-headers-fill
pub fn new(global: GlobalRef, init: Option<HeadersBinding::HeadersInit>)
-> Fallible<Root<Headers>> {
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 Constructor(global: GlobalRef, init: Option<HeadersBinding::HeadersInit>)
-> Fallible<Root<Headers>> {
Headers::new(global, init)
}
}

Expand All @@ -50,32 +89,102 @@ impl HeadersMethods for Headers {
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));
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 {
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
let mut combined_value = self.header_list.borrow_mut().get_raw(&valid_name).unwrap()[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(())
}

// https://fetch.spec.whatwg.org/#dom-headers-delete
fn Delete(&self, name: ByteString) -> ErrorResult {
// Step 1
let valid_name = try!(validate_name(name));
// Step 2
if self.guard == Guard::Immutable {
return Err(Error::Type("Guard is immutable".to_string()));
}
// Step 3
if self.guard == Guard::Request && is_forbidden_header_name(&valid_name) {
return Ok(());
}
// Step 4
if self.guard == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name) {
return Ok(());
}
// Step 5
if self.guard == Guard::Response && is_forbidden_response_header(&valid_name) {
return Ok(());
}
// Step 6
self.header_list.borrow_mut().remove_raw(&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())
}))
}

// https://fetch.spec.whatwg.org/#dom-headers-has
fn Has(&self, name: ByteString) -> Fallible<bool> {
// Step 1
let valid_name = try!(validate_name(name));
// Step 2
Ok(self.header_list.borrow_mut().get_raw(&valid_name).is_some())
}

// https://fetch.spec.whatwg.org/#dom-headers-set
fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> {
// 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 {
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
// https://fetch.spec.whatwg.org/#concept-header-list-set
self.header_list.borrow_mut().set_raw(valid_name, vec![valid_value]);
return Ok(());
Ok(())
}
}

Expand Down Expand Up @@ -130,7 +239,7 @@ pub fn is_forbidden_header_name(name: &str) -> bool {
// 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
// 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."
Expand All @@ -147,14 +256,19 @@ pub fn is_forbidden_header_name(name: &str) -> bool {
// [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()));
}
let valid_name = try!(validate_name(name));
if !is_field_content(&value) {
return Err(Error::Type("Value is not valid".to_string()));
}
Ok((valid_name, value.into()))
}

fn validate_name(name: ByteString) -> Result<String, Error> {
if !is_field_name(&name) {
return Err(Error::Type("Name is not valid".to_string()));
}
match String::from_utf8(name.into()) {
Ok(ns) => Ok((ns, value.into())),
Ok(ns) => Ok(ns),
_ => Err(Error::Type("Non-UTF8 header name found".to_string())),
}
}
Expand Down
24 changes: 13 additions & 11 deletions components/script/dom/webidls/Headers.webidl
Expand Up @@ -4,19 +4,21 @@

// https://fetch.spec.whatwg.org/#headers-class

/* typedef (Headers or sequence<sequence<ByteString>>) HeadersInit; */

/* [Constructor(optional HeadersInit init),*/
[Exposed=(Window,Worker)]
// TODO support OpenEndedDictionary<ByteString>
typedef (Headers or sequence<sequence<ByteString>>) HeadersInit;

[Constructor(optional HeadersInit init),
Exposed=(Window,Worker)]
interface Headers {
[Throws]
void append(ByteString name, ByteString value);
[Throws]
void delete(ByteString name);
[Throws]
ByteString? get(ByteString name);
[Throws]
boolean has(ByteString name);
[Throws]
void set(ByteString name, ByteString value);
// iterable<ByteString, ByteString>; // TODO see issue #12628
};

/* void delete(ByteString name);
* ByteString? get(ByteString name);
* boolean has(ByteString name);
* void set(ByteString name, ByteString value);
* iterable<ByteString, ByteString>;
* }; */
9 changes: 1 addition & 8 deletions tests/wpt/metadata/fetch/api/headers/headers-basic.html.ini
@@ -1,11 +1,5 @@
[headers-basic.html]
type: testharness
[Create headers from no parameter]
expected: FAIL

[Create headers from undefined parameter]
expected: FAIL

[Create headers from empty object]
expected: FAIL

Expand All @@ -15,7 +9,7 @@
[Create headers with OpenEndedDictionary]
expected: FAIL

[Create headers whith existing headers]
[Create headers with existing headers]
expected: FAIL

[Check append method]
Expand Down Expand Up @@ -47,4 +41,3 @@

[Check forEach method]
expected: FAIL

37 changes: 0 additions & 37 deletions tests/wpt/metadata/fetch/api/headers/headers-errors.html.ini
@@ -1,44 +1,7 @@
[headers-errors.html]
type: testharness
[Check headers get with an invalid name invalidĀ]
expected: FAIL

[Check headers get with an invalid name [object Object\]]
expected: FAIL

[Check headers delete with an invalid name invalidĀ]
expected: FAIL

[Check headers delete with an invalid name [object Object\]]
expected: FAIL

[Check headers has with an invalid name invalidĀ]
expected: FAIL

[Check headers has with an invalid name [object Object\]]
expected: FAIL

[Check headers set with an invalid name invalidĀ]
expected: FAIL

[Check headers set with an invalid name [object Object\]]
expected: FAIL

[Check headers set with an invalid value invalidĀ]
expected: FAIL

[Check headers append with an invalid name invalidĀ]
expected: FAIL

[Check headers append with an invalid name [object Object\]]
expected: FAIL

[Check headers append with an invalid value invalidĀ]
expected: FAIL

[Headers forEach throws if argument is not callable]
expected: FAIL

[Headers forEach loop should stop if callback is throwing exception]
expected: FAIL

10 changes: 9 additions & 1 deletion tests/wpt/metadata/fetch/api/headers/headers-structure.html.ini
@@ -1,3 +1,11 @@
[headers-structure.html]
type: testharness
expected: TIMEOUT
expected: OK
[Headers has entries method]
expected: FAIL

[Headers has keys method]
expected: FAIL

[Headers has values method]
expected: FAIL
Expand Up @@ -66,7 +66,7 @@
assert_equals(headers2.get(name), String(headerDict[name]),
"name: " + name + " has value: " + headerDict[name]);
}
}, "Create headers whith existing headers");
}, "Create headers with existing headers");

test(function() {
var headers = new Headers();
Expand Down

0 comments on commit e631d3a

Please sign in to comment.