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};
21
22use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
23use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
24use crate::dom::bindings::iterable::Iterable;
25use crate::dom::bindings::root::DomRoot;
26use crate::dom::bindings::str::{ByteString, is_token};
27use crate::dom::globalscope::GlobalScope;
28
29#[dom_struct]
30pub(crate) struct Headers {
31 reflector_: Reflector,
32 guard: Cell<Guard>,
33 #[no_trace]
34 header_list: DomRefCell<HyperHeaders>,
35}
36
37#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
39pub(crate) enum Guard {
40 Immutable,
41 Request,
42 RequestNoCors,
43 Response,
44 None,
45}
46
47impl Headers {
48 pub(crate) fn new_inherited() -> Headers {
49 Headers {
50 reflector_: Reflector::new(),
51 guard: Cell::new(Guard::None),
52 header_list: DomRefCell::new(HyperHeaders::new()),
53 }
54 }
55
56 pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
57 Self::new_with_proto(cx, global, None)
58 }
59
60 fn new_with_proto(
61 cx: &mut JSContext,
62 global: &GlobalScope,
63 proto: Option<HandleObject>,
64 ) -> DomRoot<Headers> {
65 reflect_dom_object_with_proto(cx, Box::new(Headers::new_inherited()), global, proto)
66 }
67}
68
69impl HeadersMethods<crate::DomTypeHolder> for Headers {
70 fn Constructor(
72 cx: &mut JSContext,
73 global: &GlobalScope,
74 proto: Option<HandleObject>,
75 init: Option<HeadersInit>,
76 ) -> Fallible<DomRoot<Headers>> {
77 let dom_headers_new = Headers::new_with_proto(cx, global, proto);
78 dom_headers_new.fill(init)?;
79 Ok(dom_headers_new)
80 }
81
82 fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult {
84 let value = trim_http_whitespace(&value);
86
87 let Some((mut valid_name, valid_value)) =
89 self.validate_name_and_value(name, ByteString::new(value.into()))?
90 else {
91 return Ok(());
92 };
93
94 valid_name = valid_name.to_lowercase();
95
96 if self.guard.get() == Guard::RequestNoCors {
98 let tmp_value = if let Some(mut value) =
100 get_value_from_header_list(&valid_name, &self.header_list.borrow())
101 {
102 value.extend(b", ");
104 value.extend(valid_value.to_vec());
105 value
106 } else {
107 valid_value.to_vec()
109 };
110 if !is_cors_safelisted_request_header(&valid_name, &tmp_value) {
112 return Ok(());
113 }
114 }
115
116 match (
118 HeaderName::from_str(&valid_name),
119 HeaderValue::from_bytes(&valid_value),
120 ) {
121 (Ok(name), Ok(value)) => {
122 self.header_list.borrow_mut().append(name, value);
123 },
124 _ => {
125 warn!("Could not set header \"{valid_name:?}: {valid_value:?}\"");
126 },
127 };
128
129 if self.guard.get() == Guard::RequestNoCors {
131 self.remove_privileged_no_cors_request_headers();
132 }
133
134 Ok(())
135 }
136
137 fn Delete(&self, name: ByteString) -> ErrorResult {
139 let name_and_value = self.validate_name_and_value(name, ByteString::new(vec![]))?;
141 let Some((mut valid_name, _valid_value)) = name_and_value else {
142 return Ok(());
143 };
144
145 valid_name = valid_name.to_lowercase();
146
147 if self.guard.get() == Guard::RequestNoCors &&
150 !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec())
151 {
152 return Ok(());
153 }
154
155 self.header_list.borrow_mut().remove(valid_name);
158
159 if self.guard.get() == Guard::RequestNoCors {
161 self.remove_privileged_no_cors_request_headers();
162 }
163
164 Ok(())
165 }
166
167 fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> {
169 let valid_name = validate_name(name)?;
171
172 Ok(
174 get_value_from_header_list(&valid_name, &self.header_list.borrow())
175 .map(ByteString::new),
176 )
177 }
178
179 fn GetSetCookie(&self) -> Vec<ByteString> {
181 self.header_list
185 .borrow()
186 .get_all("set-cookie")
187 .iter()
188 .map(|v| ByteString::new(v.as_bytes().to_vec()))
189 .collect()
190 }
191
192 fn Has(&self, name: ByteString) -> Fallible<bool> {
194 let valid_name = validate_name(name)?;
196 Ok(self.header_list.borrow_mut().get(&valid_name).is_some())
198 }
199
200 fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> {
202 let value = trim_http_whitespace(&value);
204
205 let Some((mut valid_name, valid_value)) =
207 self.validate_name_and_value(name, ByteString::new(value.into()))?
208 else {
209 return Ok(());
210 };
211 valid_name = valid_name.to_lowercase();
212
213 if self.guard.get() == Guard::RequestNoCors &&
216 !is_cors_safelisted_request_header(&valid_name, &valid_value.to_vec())
217 {
218 return Ok(());
219 }
220
221 match (
224 HeaderName::from_str(&valid_name),
225 HeaderValue::from_bytes(&valid_value),
226 ) {
227 (Ok(name), Ok(value)) => {
228 self.header_list.borrow_mut().insert(name, value);
229 },
230 _ => {
231 warn!("Could not set header: \"{valid_name:?}: {valid_value:?}\"");
232 },
233 };
234
235 if self.guard.get() == Guard::RequestNoCors {
237 self.remove_privileged_no_cors_request_headers();
238 }
239
240 Ok(())
241 }
242}
243
244impl Headers {
245 pub(crate) fn copy_from_headers(&self, headers: &Headers) -> ErrorResult {
246 for (name, value) in headers.header_list.borrow().iter() {
247 self.Append(
248 ByteString::new(Vec::from(name.as_str())),
249 ByteString::new(Vec::from(value.as_bytes())),
250 )?;
251 }
252 Ok(())
253 }
254
255 pub(crate) fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult {
257 match filler {
258 Some(HeadersInit::ByteStringSequenceSequence(v)) => {
259 for mut seq in v {
260 if seq.len() == 2 {
261 let val = seq.pop().unwrap();
262 let name = seq.pop().unwrap();
263 self.Append(name, val)?;
264 } else {
265 return Err(Error::Type(cformat!(
266 "Each header object must be a sequence of length 2 - found one with length {}",
267 seq.len()
268 )));
269 }
270 }
271 Ok(())
272 },
273 Some(HeadersInit::ByteStringByteStringRecord(m)) => {
274 for (key, value) in m.iter() {
275 self.Append(key.clone(), value.clone())?;
276 }
277 Ok(())
278 },
279 None => Ok(()),
280 }
281 }
282
283 pub(crate) fn for_request(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
284 let headers_for_request = Headers::new(cx, global);
285 headers_for_request.guard.set(Guard::Request);
286 headers_for_request
287 }
288
289 pub(crate) fn for_response(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
290 let headers_for_response = Headers::new(cx, global);
291 headers_for_response.guard.set(Guard::Response);
292 headers_for_response
293 }
294
295 pub(crate) fn set_guard(&self, new_guard: Guard) {
296 self.guard.set(new_guard)
297 }
298
299 pub(crate) fn get_guard(&self) -> Guard {
300 self.guard.get()
301 }
302
303 pub(crate) fn set_headers(&self, hyper_headers: HyperHeaders) {
304 *self.header_list.borrow_mut() = hyper_headers;
305 }
306
307 pub(crate) fn get_headers_list(&self) -> HyperHeaders {
308 self.header_list.borrow_mut().clone()
309 }
310
311 pub(crate) fn extract_mime_type(&self) -> Vec<u8> {
313 extract_mime_type(&self.header_list.borrow()).unwrap_or_default()
314 }
315
316 pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec<u8>)> {
318 let borrowed_header_list = self.header_list.borrow();
319 let mut header_vec = vec![];
320
321 for name in borrowed_header_list.keys() {
322 let name = name.as_str();
323 if name == "set-cookie" {
324 for value in borrowed_header_list.get_all(name).iter() {
325 header_vec.push((name.to_owned(), value.as_bytes().to_vec()));
326 }
327 } else if let Some(value) = get_value_from_header_list(name, &borrowed_header_list) {
328 header_vec.push((name.to_owned(), value));
329 }
330 }
331
332 header_vec.sort_by(|a, b| a.0.cmp(&b.0));
333 header_vec
334 }
335
336 pub(crate) fn remove_privileged_no_cors_request_headers(&self) {
338 self.header_list.borrow_mut().remove("range");
340 }
341
342 pub(crate) fn validate_name_and_value(
344 &self,
345 name: ByteString,
346 value: ByteString,
347 ) -> Fallible<Option<(String, ByteString)>> {
348 let valid_name = validate_name(name)?;
350 if !is_legal_header_value(&value) {
351 return Err(Error::Type(c"Header value is not valid".to_owned()));
352 }
353 if self.guard.get() == Guard::Immutable {
355 return Err(Error::Type(c"Guard is immutable".to_owned()));
356 }
357 if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, &value) {
359 return Ok(None);
360 }
361 if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
363 return Ok(None);
364 }
365
366 Ok(Some((valid_name, value)))
367 }
368}
369
370impl Iterable for Headers {
371 type Key = ByteString;
372 type Value = ByteString;
373
374 fn get_iterable_length(&self, _cx: &mut JSContext) -> u32 {
375 let sorted_header_vec = self.sort_and_combine();
376 sorted_header_vec.len() as u32
377 }
378
379 fn get_value_at_index(&self, _cx: &mut JSContext, index: u32) -> ByteString {
380 let sorted_header_vec = self.sort_and_combine();
381 let value = sorted_header_vec[index as usize].1.clone();
382 ByteString::new(value)
383 }
384
385 fn get_key_at_index(&self, _cx: &mut JSContext, index: u32) -> ByteString {
386 let sorted_header_vec = self.sort_and_combine();
387 let key = sorted_header_vec[index as usize].0.clone();
388 ByteString::new(key.into_bytes().to_vec())
389 }
390}
391
392pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
397 let forbidden_header_names = [
398 "accept-charset",
399 "accept-encoding",
400 "access-control-request-headers",
401 "access-control-request-method",
402 "connection",
403 "content-length",
404 "cookie",
405 "cookie2",
406 "date",
407 "dnt",
408 "expect",
409 "host",
410 "keep-alive",
411 "origin",
412 "referer",
413 "set-cookie",
414 "te",
415 "trailer",
416 "transfer-encoding",
417 "upgrade",
418 "via",
419 "access-control-request-private-network",
423 ];
424
425 let lowercase_name = name.to_lowercase();
428
429 if forbidden_header_names.contains(&lowercase_name.as_str()) {
430 return true;
431 }
432
433 let forbidden_header_prefixes = ["sec-", "proxy-"];
434
435 if forbidden_header_prefixes
437 .iter()
438 .any(|prefix| lowercase_name.starts_with(prefix))
439 {
440 return true;
441 }
442
443 let potentially_forbidden_header_names = [
444 "x-http-method",
445 "x-http-method-override",
446 "x-method-override",
447 ];
448
449 if potentially_forbidden_header_names
451 .iter()
452 .any(|header| *header == lowercase_name)
453 {
454 let parsed_values = get_decode_and_split_header_value(value.to_vec());
456
457 return parsed_values
460 .iter()
461 .any(|s| is_forbidden_method(s.as_bytes()));
462 }
463
464 false
466}
467
468fn is_forbidden_response_header(name: &str) -> bool {
470 let name = name.to_ascii_lowercase();
472 matches!(name.as_str(), "set-cookie" | "set-cookie2")
473}
474
475fn validate_name(name: ByteString) -> Fallible<String> {
476 if !is_field_name(&name) {
477 return Err(Error::Type(c"Name is not valid".to_owned()));
478 }
479 match String::from_utf8(name.into()) {
480 Ok(ns) => Ok(ns),
481 _ => Err(Error::Type(c"Non-UTF8 header name found".to_owned())),
482 }
483}
484
485fn is_field_name(name: &ByteString) -> bool {
487 is_token(name)
488}
489
490fn is_legal_header_value(value: &[u8]) -> bool {
499 let value_len = value.len();
500 if value_len == 0 {
501 return true;
502 }
503 match value[0] {
504 b' ' | b'\t' => return false,
505 _ => {},
506 };
507 match value[value_len - 1] {
508 b' ' | b'\t' => return false,
509 _ => {},
510 };
511 for &ch in value {
512 match ch {
513 b'\0' | b'\n' | b'\r' => return false,
514 _ => {},
515 }
516 }
517 true
518 }
532
533pub(crate) fn is_vchar(x: u8) -> bool {
535 matches!(x, 0x21..=0x7E)
536}
537
538pub(crate) fn is_obs_text(x: u8) -> bool {
540 matches!(x, 0x80..=0xFF)
541}