1use std::cell::Cell;
6use std::str::{self, FromStr};
7
8use dom_struct::dom_struct;
9use http::header::{HeaderMap as HyperHeaders, HeaderName, HeaderValue};
10use js::context::JSContext;
11use js::rust::HandleObject;
12use net_traits::fetch::headers::{
13 extract_mime_type, get_decode_and_split_header_value, get_value_from_header_list,
14 is_forbidden_method,
15};
16use net_traits::request::is_cors_safelisted_request_header;
17use net_traits::trim_http_whitespace;
18use script_bindings::cell::DomRefCell;
19use script_bindings::cformat;
20use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
21use style::str::starts_with_ignore_ascii_case;
22
23use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
24use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
25use crate::dom::bindings::iterable::Iterable;
26use crate::dom::bindings::root::DomRoot;
27use crate::dom::bindings::str::{ByteString, is_token};
28use crate::dom::globalscope::GlobalScope;
29
30#[dom_struct]
31pub(crate) struct Headers {
32 reflector_: Reflector,
33 guard: Cell<Guard>,
34 #[no_trace]
35 header_list: DomRefCell<HyperHeaders>,
36}
37
38#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
40pub(crate) enum Guard {
41 Immutable,
42 Request,
43 RequestNoCors,
44 Response,
45 None,
46}
47
48impl Headers {
49 pub(crate) fn new_inherited() -> Headers {
50 Headers {
51 reflector_: Reflector::new(),
52 guard: Cell::new(Guard::None),
53 header_list: DomRefCell::new(HyperHeaders::new()),
54 }
55 }
56
57 pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
58 Self::new_with_proto(cx, global, None)
59 }
60
61 fn new_with_proto(
62 cx: &mut JSContext,
63 global: &GlobalScope,
64 proto: Option<HandleObject>,
65 ) -> DomRoot<Headers> {
66 reflect_dom_object_with_proto(cx, Box::new(Headers::new_inherited()), global, proto)
67 }
68}
69
70impl HeadersMethods<crate::DomTypeHolder> for Headers {
71 fn Constructor(
73 cx: &mut JSContext,
74 global: &GlobalScope,
75 proto: Option<HandleObject>,
76 init: Option<HeadersInit>,
77 ) -> Fallible<DomRoot<Headers>> {
78 let dom_headers_new = Headers::new_with_proto(cx, global, proto);
79 dom_headers_new.fill(init)?;
80 Ok(dom_headers_new)
81 }
82
83 fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult {
85 let value = trim_http_whitespace(&value);
87
88 let Some((mut valid_name, valid_value)) =
90 self.validate_name_and_value(name, ByteString::new(value.into()))?
91 else {
92 return Ok(());
93 };
94
95 valid_name.make_ascii_lowercase();
97
98 if self.guard.get() == Guard::RequestNoCors {
100 let tmp_value = if let Some(mut value) =
102 get_value_from_header_list(&valid_name, &self.header_list.borrow())
103 {
104 value.extend(b", ");
106 value.extend(valid_value.to_vec());
107 value
108 } else {
109 valid_value.to_vec()
111 };
112 if !is_cors_safelisted_request_header(&valid_name, &tmp_value) {
114 return Ok(());
115 }
116 }
117
118 match (
120 HeaderName::from_str(&valid_name),
121 HeaderValue::from_bytes(&valid_value),
122 ) {
123 (Ok(name), Ok(value)) => {
124 self.header_list.borrow_mut().append(name, value);
125 },
126 _ => {
127 warn!("Could not set header \"{valid_name:?}: {valid_value:?}\"");
128 },
129 };
130
131 if self.guard.get() == Guard::RequestNoCors {
133 self.remove_privileged_no_cors_request_headers();
134 }
135
136 Ok(())
137 }
138
139 fn Delete(&self, name: ByteString) -> ErrorResult {
141 let name_and_value = self.validate_name_and_value(name, ByteString::new(vec![]))?;
143 let Some((mut valid_name, _valid_value)) = name_and_value else {
144 return Ok(());
145 };
146
147 valid_name.make_ascii_lowercase();
149
150 if self.guard.get() == Guard::RequestNoCors &&
153 !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec())
154 {
155 return Ok(());
156 }
157
158 self.header_list.borrow_mut().remove(valid_name);
161
162 if self.guard.get() == Guard::RequestNoCors {
164 self.remove_privileged_no_cors_request_headers();
165 }
166
167 Ok(())
168 }
169
170 fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> {
172 let valid_name = validate_name(name)?;
174
175 Ok(
177 get_value_from_header_list(&valid_name, &self.header_list.borrow())
178 .map(ByteString::new),
179 )
180 }
181
182 fn GetSetCookie(&self) -> Vec<ByteString> {
184 self.header_list
188 .borrow()
189 .get_all("set-cookie")
190 .iter()
191 .map(|v| ByteString::new(v.as_bytes().to_vec()))
192 .collect()
193 }
194
195 fn Has(&self, name: ByteString) -> Fallible<bool> {
197 let valid_name = validate_name(name)?;
199 Ok(self.header_list.borrow_mut().get(&valid_name).is_some())
201 }
202
203 fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> {
205 let value = trim_http_whitespace(&value);
207
208 let Some((mut valid_name, valid_value)) =
210 self.validate_name_and_value(name, ByteString::new(value.into()))?
211 else {
212 return Ok(());
213 };
214 valid_name.make_ascii_lowercase();
216
217 if self.guard.get() == Guard::RequestNoCors &&
220 !is_cors_safelisted_request_header(&valid_name, &valid_value.to_vec())
221 {
222 return Ok(());
223 }
224
225 match (
228 HeaderName::from_str(&valid_name),
229 HeaderValue::from_bytes(&valid_value),
230 ) {
231 (Ok(name), Ok(value)) => {
232 self.header_list.borrow_mut().insert(name, value);
233 },
234 _ => {
235 warn!("Could not set header: \"{valid_name:?}: {valid_value:?}\"");
236 },
237 };
238
239 if self.guard.get() == Guard::RequestNoCors {
241 self.remove_privileged_no_cors_request_headers();
242 }
243
244 Ok(())
245 }
246}
247
248impl Headers {
249 pub(crate) fn copy_from_headers(&self, headers: &Headers) -> ErrorResult {
250 for (name, value) in headers.header_list.borrow().iter() {
251 self.Append(
252 ByteString::new(Vec::from(name.as_str())),
253 ByteString::new(Vec::from(value.as_bytes())),
254 )?;
255 }
256 Ok(())
257 }
258
259 pub(crate) fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult {
261 match filler {
262 Some(HeadersInit::ByteStringSequenceSequence(v)) => {
263 for mut seq in v {
264 if seq.len() == 2 {
265 let val = seq.pop().unwrap();
266 let name = seq.pop().unwrap();
267 self.Append(name, val)?;
268 } else {
269 return Err(Error::Type(cformat!(
270 "Each header object must be a sequence of length 2 - found one with length {}",
271 seq.len()
272 )));
273 }
274 }
275 Ok(())
276 },
277 Some(HeadersInit::ByteStringByteStringRecord(m)) => {
278 for (key, value) in m.iter() {
279 self.Append(key.clone(), value.clone())?;
280 }
281 Ok(())
282 },
283 None => Ok(()),
284 }
285 }
286
287 pub(crate) fn for_request(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
288 let headers_for_request = Headers::new(cx, global);
289 headers_for_request.guard.set(Guard::Request);
290 headers_for_request
291 }
292
293 pub(crate) fn for_response(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
294 let headers_for_response = Headers::new(cx, global);
295 headers_for_response.guard.set(Guard::Response);
296 headers_for_response
297 }
298
299 pub(crate) fn set_guard(&self, new_guard: Guard) {
300 self.guard.set(new_guard)
301 }
302
303 pub(crate) fn get_guard(&self) -> Guard {
304 self.guard.get()
305 }
306
307 pub(crate) fn set_headers(&self, hyper_headers: HyperHeaders) {
308 *self.header_list.borrow_mut() = hyper_headers;
309 }
310
311 pub(crate) fn get_headers_list(&self) -> HyperHeaders {
312 self.header_list.borrow_mut().clone()
313 }
314
315 pub(crate) fn extract_mime_type(&self) -> Vec<u8> {
317 extract_mime_type(&self.header_list.borrow()).unwrap_or_default()
318 }
319
320 pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec<u8>)> {
322 let borrowed_header_list = self.header_list.borrow();
323 let mut header_vec = vec![];
324
325 for name in borrowed_header_list.keys() {
326 let name = name.as_str();
327 if name == "set-cookie" {
328 for value in borrowed_header_list.get_all(name).iter() {
329 header_vec.push((name.to_owned(), value.as_bytes().to_vec()));
330 }
331 } else if let Some(value) = get_value_from_header_list(name, &borrowed_header_list) {
332 header_vec.push((name.to_owned(), value));
333 }
334 }
335
336 header_vec.sort_by(|a, b| a.0.cmp(&b.0));
337 header_vec
338 }
339
340 pub(crate) fn remove_privileged_no_cors_request_headers(&self) {
342 self.header_list.borrow_mut().remove("range");
344 }
345
346 pub(crate) fn validate_name_and_value(
348 &self,
349 name: ByteString,
350 value: ByteString,
351 ) -> Fallible<Option<(String, ByteString)>> {
352 let valid_name = validate_name(name)?;
354 if !is_legal_header_value(&value) {
355 return Err(Error::Type(c"Header value is not valid".to_owned()));
356 }
357 if self.guard.get() == Guard::Immutable {
359 return Err(Error::Type(c"Guard is immutable".to_owned()));
360 }
361 if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, &value) {
363 return Ok(None);
364 }
365 if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
367 return Ok(None);
368 }
369
370 Ok(Some((valid_name, value)))
371 }
372}
373
374impl Iterable for Headers {
375 type Key = ByteString;
376 type Value = ByteString;
377
378 fn get_iterable_length(&self, _cx: &mut JSContext) -> u32 {
379 let sorted_header_vec = self.sort_and_combine();
380 sorted_header_vec.len() as u32
381 }
382
383 fn get_value_at_index(&self, _cx: &mut JSContext, index: u32) -> ByteString {
384 let sorted_header_vec = self.sort_and_combine();
385 ByteString::new(sorted_header_vec.into_iter().nth(index as usize).unwrap().1)
386 }
387
388 fn get_key_at_index(&self, _cx: &mut JSContext, index: u32) -> ByteString {
389 let sorted_header_vec = self.sort_and_combine();
390 ByteString::new(
391 sorted_header_vec
392 .into_iter()
393 .nth(index as usize)
394 .unwrap()
395 .0
396 .into_bytes(),
397 )
398 }
399}
400
401pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
406 let forbidden_header_names = [
407 "accept-charset",
408 "accept-encoding",
409 "access-control-request-headers",
410 "access-control-request-method",
411 "connection",
412 "content-length",
413 "cookie",
414 "cookie2",
415 "date",
416 "dnt",
417 "expect",
418 "host",
419 "keep-alive",
420 "origin",
421 "referer",
422 "set-cookie",
423 "te",
424 "trailer",
425 "transfer-encoding",
426 "upgrade",
427 "via",
428 "access-control-request-private-network",
432 ];
433
434 if forbidden_header_names
437 .into_iter()
438 .any(|header| name.eq_ignore_ascii_case(header))
439 {
440 return true;
441 }
442
443 let forbidden_header_prefixes = ["sec-", "proxy-"];
444
445 if forbidden_header_prefixes
447 .into_iter()
448 .any(|prefix| starts_with_ignore_ascii_case(name, prefix))
449 {
450 return true;
451 }
452
453 let potentially_forbidden_header_names = [
454 "x-http-method",
455 "x-http-method-override",
456 "x-method-override",
457 ];
458
459 if potentially_forbidden_header_names
461 .into_iter()
462 .any(|header| name.eq_ignore_ascii_case(header))
463 {
464 let parsed_values = get_decode_and_split_header_value(value.to_vec());
466
467 return parsed_values
470 .iter()
471 .any(|s| is_forbidden_method(s.as_bytes()));
472 }
473
474 false
476}
477
478fn is_forbidden_response_header(name: &str) -> bool {
480 name.eq_ignore_ascii_case("set-cookie") || name.eq_ignore_ascii_case("set-cookie2")
482}
483
484fn validate_name(name: ByteString) -> Fallible<String> {
485 if !is_field_name(&name) {
486 return Err(Error::Type(c"Name is not valid".to_owned()));
487 }
488 match String::from_utf8(name.into()) {
489 Ok(ns) => Ok(ns),
490 _ => Err(Error::Type(c"Non-UTF8 header name found".to_owned())),
491 }
492}
493
494fn is_field_name(name: &ByteString) -> bool {
496 is_token(name)
497}
498
499fn is_legal_header_value(value: &[u8]) -> bool {
508 let value_len = value.len();
509 if value_len == 0 {
510 return true;
511 }
512 match value[0] {
513 b' ' | b'\t' => return false,
514 _ => {},
515 };
516 match value[value_len - 1] {
517 b' ' | b'\t' => return false,
518 _ => {},
519 };
520 for &ch in value {
521 match ch {
522 b'\0' | b'\n' | b'\r' => return false,
523 _ => {},
524 }
525 }
526 true
527 }
541
542pub(crate) fn is_vchar(x: u8) -> bool {
544 matches!(x, 0x21..=0x7E)
545}
546
547pub(crate) fn is_obs_text(x: u8) -> bool {
549 matches!(x, 0x80..=0xFF)
550}