Skip to content

Commit 087d6d4

Browse files
committed
servo: Merge #12467 - Add the append method for the Headers API (from jeenalee:jeena-headersAPI); r=jdm
<!-- Please describe your changes on the following line: --> This commit adds the append method for the Headers API. malisas and I are both contributors. There are a few TODOs related: - The script needs to parse the header value for certain header names to decide the header group it belongs - There are possible spec bugs that could change what a valid header value looks like (related: [issue page](whatwg/fetch#332)) There are WPT tests already written for the Headers API, but they will fail as the Headers API is not fully implemented. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [X] These changes do not require tests because tests for the Headers API already exists, but this commit does not implement the interface fully. The tests will fail. <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 03fa7f0ba533acc44100639ad85625810618df3a UltraBlame original commit: 9a892b1c7c7b04a342e32ba1e0a035a5cfcb328a
1 parent a01eb54 commit 087d6d4

File tree

8 files changed

+354
-16
lines changed

8 files changed

+354
-16
lines changed

servo/components/script/dom/bindings/str.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::str::{Bytes, FromStr};
1515
use string_cache::Atom;
1616

1717

18-
#[derive(JSTraceable, Clone, Eq, PartialEq, HeapSizeOf)]
18+
#[derive(JSTraceable, Clone, Eq, PartialEq, HeapSizeOf, Debug)]
1919
pub struct ByteString(Vec<u8>);
2020

2121
impl ByteString {
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
2+
3+
4+
5+
use dom::bindings::cell::DOMRefCell;
6+
use dom::bindings::codegen::Bindings::HeadersBinding;
7+
use dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
8+
use dom::bindings::error::Error;
9+
use dom::bindings::global::GlobalRef;
10+
use dom::bindings::js::Root;
11+
use dom::bindings::reflector::{Reflector, reflect_dom_object};
12+
use dom::bindings::str::{ByteString, is_token};
13+
use hyper;
14+
use std::result::Result;
15+
16+
#[dom_struct]
17+
pub struct Headers {
18+
reflector_: Reflector,
19+
guard: Guard,
20+
#[ignore_heap_size_of = "Defined in hyper"]
21+
header_list: DOMRefCell<hyper::header::Headers>
22+
}
23+
24+
25+
#[derive(JSTraceable, HeapSizeOf, PartialEq)]
26+
pub enum Guard {
27+
Immutable,
28+
Request,
29+
RequestNoCors,
30+
Response,
31+
None,
32+
}
33+
34+
impl Headers {
35+
pub fn new_inherited() -> Headers {
36+
Headers {
37+
reflector_: Reflector::new(),
38+
guard: Guard::None,
39+
header_list: DOMRefCell::new(hyper::header::Headers::new()),
40+
}
41+
}
42+
43+
pub fn new(global: GlobalRef) -> Root<Headers> {
44+
reflect_dom_object(box Headers::new_inherited(), global, HeadersBinding::Wrap)
45+
}
46+
}
47+
48+
impl HeadersMethods for Headers {
49+
// https://fetch.spec.whatwg.org/#concept-headers-append
50+
fn Append(&self, name: ByteString, value: ByteString) -> Result<(), Error> {
51+
// Step 1
52+
let value = normalize_value(value);
53+
54+
// Step 2
55+
let (valid_name, valid_value) = try!(validate_name_and_value(name, value));
56+
// Step 3
57+
if self.guard == Guard::Immutable {
58+
return Err(Error::Type("Guard is immutable".to_string()));
59+
}
60+
61+
// Step 4
62+
if self.guard == Guard::Request && is_forbidden_header_name(&valid_name) {
63+
return Ok(());
64+
}
65+
66+
// Step 5
67+
if self.guard == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name) {
68+
return Ok(());
69+
}
70+
71+
// Step 6
72+
if self.guard == Guard::Response && is_forbidden_response_header(&valid_name) {
73+
return Ok(());
74+
}
75+
76+
// Step 7
77+
self.header_list.borrow_mut().set_raw(valid_name, vec![valid_value]);
78+
return Ok(());
79+
}
80+
}
81+
82+
// TODO
83+
// "Content-Type" once parsed, the value should be
84+
// `application/x-www-form-urlencoded`, `multipart/form-data`,
85+
// or `text/plain`.
86+
// "DPR", "Downlink", "Save-Data", "Viewport-Width", "Width":
87+
// once parsed, the value should not be failure.
88+
// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
89+
fn is_cors_safelisted_request_header(name: &str) -> bool {
90+
match name {
91+
"accept" |
92+
"accept-language" |
93+
"content-language" => true,
94+
_ => false,
95+
}
96+
}
97+
98+
// https://fetch.spec.whatwg.org/#forbidden-response-header-name
99+
fn is_forbidden_response_header(name: &str) -> bool {
100+
match name {
101+
"set-cookie" |
102+
"set-cookie2" => true,
103+
_ => false,
104+
}
105+
}
106+
107+
// https://fetch.spec.whatwg.org/#forbidden-header-name
108+
pub fn is_forbidden_header_name(name: &str) -> bool {
109+
let disallowed_headers =
110+
["accept-charset", "accept-encoding",
111+
"access-control-request-headers",
112+
"access-control-request-method",
113+
"connection", "content-length",
114+
"cookie", "cookie2", "date", "dnt",
115+
"expect", "host", "keep-alive", "origin",
116+
"referer", "te", "trailer", "transfer-encoding",
117+
"upgrade", "via"];
118+
119+
let disallowed_header_prefixes = ["sec-", "proxy-"];
120+
121+
disallowed_headers.iter().any(|header| *header == name) ||
122+
disallowed_header_prefixes.iter().any(|prefix| name.starts_with(prefix))
123+
}
124+
125+
// There is some unresolved confusion over the definition of a name and a value.
126+
// The fetch spec [1] defines a name as "a case-insensitive byte
127+
// sequence that matches the field-name token production. The token
128+
// productions are viewable in [2]." A field-name is defined as a
129+
// token, which is defined in [3].
130+
// ISSUE 1:
131+
// It defines a value as "a byte sequence that matches the field-content token production."
132+
// To note, there is a difference between field-content and
133+
// field-value (which is made up of fied-content and obs-fold). The
134+
// current definition does not allow for obs-fold (which are white
135+
// space and newlines) in values. So perhaps a value should be defined
136+
// as "a byte sequence that matches the field-value token production."
137+
// However, this would then allow values made up entirely of white space and newlines.
138+
// RELATED ISSUE 2:
139+
// According to a previously filed Errata ID: 4189 in [4], "the
140+
// specified field-value rule does not allow single field-vchar
141+
// surrounded by whitespace anywhere". They provided a fix for the
142+
// field-content production, but ISSUE 1 has still not been resolved.
143+
// The production definitions likely need to be re-written.
144+
// [1] https://fetch.spec.whatwg.org/#concept-header-value
145+
// [2] https://tools.ietf.org/html/rfc7230#section-3.2
146+
// [3] https://tools.ietf.org/html/rfc7230#section-3.2.6
147+
// [4] https://www.rfc-editor.org/errata_search.php?rfc=7230
148+
fn validate_name_and_value(name: ByteString, value: ByteString)
149+
-> Result<(String, Vec<u8>), Error> {
150+
if !is_field_name(&name) {
151+
return Err(Error::Type("Name is not valid".to_string()));
152+
}
153+
if !is_field_content(&value) {
154+
return Err(Error::Type("Value is not valid".to_string()));
155+
}
156+
match String::from_utf8(name.into()) {
157+
Ok(ns) => Ok((ns, value.into())),
158+
_ => Err(Error::Type("Non-UTF8 header name found".to_string())),
159+
}
160+
}
161+
162+
// Removes trailing and leading HTTP whitespace bytes.
163+
// https://fetch.spec.whatwg.org/#concept-header-value-normalize
164+
pub fn normalize_value(value: ByteString) -> ByteString {
165+
match (index_of_first_non_whitespace(&value), index_of_last_non_whitespace(&value)) {
166+
(Some(begin), Some(end)) => ByteString::new(value[begin..end + 1].to_owned()),
167+
_ => ByteString::new(vec![]),
168+
}
169+
}
170+
171+
fn is_HTTP_whitespace(byte: u8) -> bool {
172+
byte == b'\t' || byte == b'\n' || byte == b'\r' || byte == b' '
173+
}
174+
175+
fn index_of_first_non_whitespace(value: &ByteString) -> Option<usize> {
176+
for (index, &byte) in value.iter().enumerate() {
177+
if !is_HTTP_whitespace(byte) {
178+
return Some(index);
179+
}
180+
}
181+
None
182+
}
183+
184+
fn index_of_last_non_whitespace(value: &ByteString) -> Option<usize> {
185+
for (index, &byte) in value.iter().enumerate().rev() {
186+
if !is_HTTP_whitespace(byte) {
187+
return Some(index);
188+
}
189+
}
190+
None
191+
}
192+
193+
// http://tools.ietf.org/html/rfc7230#section-3.2
194+
fn is_field_name(name: &ByteString) -> bool {
195+
is_token(&*name)
196+
}
197+
198+
// https://tools.ietf.org/html/rfc7230#section-3.2
199+
// http://www.rfc-editor.org/errata_search.php?rfc=7230
200+
// Errata ID: 4189
201+
// field-content = field-vchar [ 1*( SP / HTAB / field-vchar )
202+
// field-vchar ]
203+
fn is_field_content(value: &ByteString) -> bool {
204+
if value.len() == 0 {
205+
return false;
206+
}
207+
if !is_field_vchar(value[0]) {
208+
return false;
209+
}
210+
211+
for &ch in &value[1..value.len() - 1] {
212+
if !is_field_vchar(ch) || !is_space(ch) || !is_htab(ch) {
213+
return false;
214+
}
215+
}
216+
217+
if !is_field_vchar(value[value.len() - 1]) {
218+
return false;
219+
}
220+
221+
return true;
222+
}
223+
224+
fn is_space(x: u8) -> bool {
225+
x == b' '
226+
}
227+
228+
fn is_htab(x: u8) -> bool {
229+
x == b'\t'
230+
}
231+
232+
233+
fn is_field_vchar(x: u8) -> bool {
234+
is_vchar(x) || is_obs_text(x)
235+
}
236+
237+
238+
fn is_vchar(x: u8) -> bool {
239+
match x {
240+
0x21...0x7E => true,
241+
_ => false,
242+
}
243+
}
244+
245+
246+
fn is_obs_text(x: u8) -> bool {
247+
match x {
248+
0x80...0xFF => true,
249+
_ => false,
250+
}
251+
}

servo/components/script/dom/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ pub mod focusevent;
269269
pub mod forcetouchevent;
270270
pub mod formdata;
271271
pub mod hashchangeevent;
272+
pub mod headers;
272273
pub mod htmlanchorelement;
273274
pub mod htmlappletelement;
274275
pub mod htmlareaelement;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
// https://fetch.spec.whatwg.org/#headers-class
6+
7+
/* typedef (Headers or sequence<sequence<ByteString>>) HeadersInit; */
8+
9+
/* [Constructor(optional HeadersInit init),*/
10+
[Exposed=(Window,Worker)]
11+
12+
interface Headers {
13+
[Throws]
14+
void append(ByteString name, ByteString value);
15+
};
16+
17+
/* void delete(ByteString name);
18+
* ByteString? get(ByteString name);
19+
* boolean has(ByteString name);
20+
* void set(ByteString name, ByteString value);
21+
* iterable<ByteString, ByteString>;
22+
* }; */

servo/components/script/dom/xmlhttprequest.rs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use dom::document::DocumentSource;
2525
use dom::document::{Document, IsHTMLDocument};
2626
use dom::event::{Event, EventBubbles, EventCancelable};
2727
use dom::eventtarget::EventTarget;
28+
use dom::headers::is_forbidden_header_name;
2829
use dom::progressevent::ProgressEvent;
2930
use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
3031
use dom::xmlhttprequestupload::XMLHttpRequestUpload;
@@ -409,21 +410,8 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
409410
// Step 5
410411
// Disallowed headers and header prefixes:
411412
// https://fetch.spec.whatwg.org/#forbidden-header-name
412-
let disallowedHeaders =
413-
["accept-charset", "accept-encoding",
414-
"access-control-request-headers",
415-
"access-control-request-method",
416-
"connection", "content-length",
417-
"cookie", "cookie2", "date", "dnt",
418-
"expect", "host", "keep-alive", "origin",
419-
"referer", "te", "trailer", "transfer-encoding",
420-
"upgrade", "via"];
421-
422-
let disallowedHeaderPrefixes = ["sec-", "proxy-"];
423-
424-
if disallowedHeaders.iter().any(|header| *header == s) ||
425-
disallowedHeaderPrefixes.iter().any(|prefix| s.starts_with(prefix)) {
426-
return Ok(())
413+
if is_forbidden_header_name(s) {
414+
return Ok(());
427415
} else {
428416
s
429417
}

servo/python/tidy/servo_tidy/tidy.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"//drafts.csswg.org/cssom",
8989
"//drafts.fxtf.org",
9090
"//encoding.spec.whatwg.org",
91+
"//fetch.spec.whatwg.org",
9192
"//html.spec.whatwg.org",
9293
"//url.spec.whatwg.org",
9394
"//xhr.spec.whatwg.org",

0 commit comments

Comments
 (0)