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.make_ascii_lowercase();
96
97 if self.guard.get() == Guard::RequestNoCors {
99 let tmp_value = if let Some(mut value) =
101 get_value_from_header_list(&valid_name, &self.header_list.borrow())
102 {
103 value.extend(b", ");
105 value.extend(valid_value.to_vec());
106 value
107 } else {
108 valid_value.to_vec()
110 };
111 if !is_cors_safelisted_request_header(&valid_name, &tmp_value) {
113 return Ok(());
114 }
115 }
116
117 match (
119 HeaderName::from_str(&valid_name),
120 HeaderValue::from_bytes(&valid_value),
121 ) {
122 (Ok(name), Ok(value)) => {
123 self.header_list.borrow_mut().append(name, value);
124 },
125 _ => {
126 warn!("Could not set header \"{valid_name:?}: {valid_value:?}\"");
127 },
128 };
129
130 if self.guard.get() == Guard::RequestNoCors {
132 self.remove_privileged_no_cors_request_headers();
133 }
134
135 Ok(())
136 }
137
138 fn Delete(&self, name: ByteString) -> ErrorResult {
140 let name_and_value = self.validate_name_and_value(name, ByteString::new(vec![]))?;
142 let Some((mut valid_name, _valid_value)) = name_and_value else {
143 return Ok(());
144 };
145
146 valid_name.make_ascii_lowercase();
148
149 if self.guard.get() == Guard::RequestNoCors &&
152 !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec())
153 {
154 return Ok(());
155 }
156
157 self.header_list.borrow_mut().remove(valid_name);
160
161 if self.guard.get() == Guard::RequestNoCors {
163 self.remove_privileged_no_cors_request_headers();
164 }
165
166 Ok(())
167 }
168
169 fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> {
171 let valid_name = validate_name(name)?;
173
174 Ok(
176 get_value_from_header_list(&valid_name, &self.header_list.borrow())
177 .map(ByteString::new),
178 )
179 }
180
181 fn GetSetCookie(&self) -> Vec<ByteString> {
183 self.header_list
187 .borrow()
188 .get_all("set-cookie")
189 .iter()
190 .map(|v| ByteString::new(v.as_bytes().to_vec()))
191 .collect()
192 }
193
194 fn Has(&self, name: ByteString) -> Fallible<bool> {
196 let valid_name = validate_name(name)?;
198 Ok(self.header_list.borrow_mut().get(&valid_name).is_some())
200 }
201
202 fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> {
204 let value = trim_http_whitespace(&value);
206
207 let Some((mut valid_name, valid_value)) =
209 self.validate_name_and_value(name, ByteString::new(value.into()))?
210 else {
211 return Ok(());
212 };
213 valid_name.make_ascii_lowercase();
215
216 if self.guard.get() == Guard::RequestNoCors &&
219 !is_cors_safelisted_request_header(&valid_name, &valid_value.to_vec())
220 {
221 return Ok(());
222 }
223
224 match (
227 HeaderName::from_str(&valid_name),
228 HeaderValue::from_bytes(&valid_value),
229 ) {
230 (Ok(name), Ok(value)) => {
231 self.header_list.borrow_mut().insert(name, value);
232 },
233 _ => {
234 warn!("Could not set header: \"{valid_name:?}: {valid_value:?}\"");
235 },
236 };
237
238 if self.guard.get() == Guard::RequestNoCors {
240 self.remove_privileged_no_cors_request_headers();
241 }
242
243 Ok(())
244 }
245}
246
247impl Headers {
248 pub(crate) fn copy_from_headers(&self, headers: &Headers) -> ErrorResult {
249 for (name, value) in headers.header_list.borrow().iter() {
250 self.Append(
251 ByteString::new(Vec::from(name.as_str())),
252 ByteString::new(Vec::from(value.as_bytes())),
253 )?;
254 }
255 Ok(())
256 }
257
258 pub(crate) fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult {
260 match filler {
261 Some(HeadersInit::ByteStringSequenceSequence(v)) => {
262 for mut seq in v {
263 if seq.len() == 2 {
264 let val = seq.pop().unwrap();
265 let name = seq.pop().unwrap();
266 self.Append(name, val)?;
267 } else {
268 return Err(Error::Type(cformat!(
269 "Each header object must be a sequence of length 2 - found one with length {}",
270 seq.len()
271 )));
272 }
273 }
274 Ok(())
275 },
276 Some(HeadersInit::ByteStringByteStringRecord(m)) => {
277 for (key, value) in m.iter() {
278 self.Append(key.clone(), value.clone())?;
279 }
280 Ok(())
281 },
282 None => Ok(()),
283 }
284 }
285
286 pub(crate) fn for_request(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
287 let headers_for_request = Headers::new(cx, global);
288 headers_for_request.guard.set(Guard::Request);
289 headers_for_request
290 }
291
292 pub(crate) fn for_response(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
293 let headers_for_response = Headers::new(cx, global);
294 headers_for_response.guard.set(Guard::Response);
295 headers_for_response
296 }
297
298 pub(crate) fn set_guard(&self, new_guard: Guard) {
299 self.guard.set(new_guard)
300 }
301
302 pub(crate) fn get_guard(&self) -> Guard {
303 self.guard.get()
304 }
305
306 pub(crate) fn set_headers(&self, hyper_headers: HyperHeaders) {
307 *self.header_list.borrow_mut() = hyper_headers;
308 }
309
310 pub(crate) fn get_headers_list(&self) -> HyperHeaders {
311 self.header_list.borrow_mut().clone()
312 }
313
314 pub(crate) fn extract_mime_type(&self) -> Vec<u8> {
316 extract_mime_type(&self.header_list.borrow()).unwrap_or_default()
317 }
318
319 pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec<u8>)> {
321 let borrowed_header_list = self.header_list.borrow();
322 let mut header_vec = vec![];
323
324 for name in borrowed_header_list.keys() {
325 let name = name.as_str();
326 if name == "set-cookie" {
327 for value in borrowed_header_list.get_all(name).iter() {
328 header_vec.push((name.to_owned(), value.as_bytes().to_vec()));
329 }
330 } else if let Some(value) = get_value_from_header_list(name, &borrowed_header_list) {
331 header_vec.push((name.to_owned(), value));
332 }
333 }
334
335 header_vec.sort_by(|a, b| a.0.cmp(&b.0));
336 header_vec
337 }
338
339 pub(crate) fn remove_privileged_no_cors_request_headers(&self) {
341 self.header_list.borrow_mut().remove("range");
343 }
344
345 pub(crate) fn validate_name_and_value(
347 &self,
348 name: ByteString,
349 value: ByteString,
350 ) -> Fallible<Option<(String, ByteString)>> {
351 let valid_name = validate_name(name)?;
353 if !is_legal_header_value(&value) {
354 return Err(Error::Type(c"Header value is not valid".to_owned()));
355 }
356 if self.guard.get() == Guard::Immutable {
358 return Err(Error::Type(c"Guard is immutable".to_owned()));
359 }
360 if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, &value) {
362 return Ok(None);
363 }
364 if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
366 return Ok(None);
367 }
368
369 Ok(Some((valid_name, value)))
370 }
371}
372
373impl Iterable for Headers {
374 type Key = ByteString;
375 type Value = ByteString;
376
377 fn get_iterable_length(&self, _cx: &mut JSContext) -> u32 {
378 let sorted_header_vec = self.sort_and_combine();
379 sorted_header_vec.len() as u32
380 }
381
382 fn get_value_at_index(&self, _cx: &mut JSContext, index: u32) -> ByteString {
383 let sorted_header_vec = self.sort_and_combine();
384 ByteString::new(sorted_header_vec.into_iter().nth(index as usize).unwrap().1)
385 }
386
387 fn get_key_at_index(&self, _cx: &mut JSContext, index: u32) -> ByteString {
388 let sorted_header_vec = self.sort_and_combine();
389 ByteString::new(
390 sorted_header_vec
391 .into_iter()
392 .nth(index as usize)
393 .unwrap()
394 .0
395 .into_bytes(),
396 )
397 }
398}
399
400pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
405 let forbidden_header_names = [
406 "accept-charset",
407 "accept-encoding",
408 "access-control-request-headers",
409 "access-control-request-method",
410 "connection",
411 "content-length",
412 "cookie",
413 "cookie2",
414 "date",
415 "dnt",
416 "expect",
417 "host",
418 "keep-alive",
419 "origin",
420 "referer",
421 "set-cookie",
422 "te",
423 "trailer",
424 "transfer-encoding",
425 "upgrade",
426 "via",
427 "access-control-request-private-network",
431 ];
432
433 let lowercase_name = name.to_lowercase();
436
437 if forbidden_header_names.contains(&lowercase_name.as_str()) {
438 return true;
439 }
440
441 let forbidden_header_prefixes = ["sec-", "proxy-"];
442
443 if forbidden_header_prefixes
445 .iter()
446 .any(|prefix| lowercase_name.starts_with(prefix))
447 {
448 return true;
449 }
450
451 let potentially_forbidden_header_names = [
452 "x-http-method",
453 "x-http-method-override",
454 "x-method-override",
455 ];
456
457 if potentially_forbidden_header_names
459 .iter()
460 .any(|header| *header == lowercase_name)
461 {
462 let parsed_values = get_decode_and_split_header_value(value.to_vec());
464
465 return parsed_values
468 .iter()
469 .any(|s| is_forbidden_method(s.as_bytes()));
470 }
471
472 false
474}
475
476fn is_forbidden_response_header(name: &str) -> bool {
478 let name = name.to_ascii_lowercase();
480 matches!(name.as_str(), "set-cookie" | "set-cookie2")
481}
482
483fn validate_name(name: ByteString) -> Fallible<String> {
484 if !is_field_name(&name) {
485 return Err(Error::Type(c"Name is not valid".to_owned()));
486 }
487 match String::from_utf8(name.into()) {
488 Ok(ns) => Ok(ns),
489 _ => Err(Error::Type(c"Non-UTF8 header name found".to_owned())),
490 }
491}
492
493fn is_field_name(name: &ByteString) -> bool {
495 is_token(name)
496}
497
498fn is_legal_header_value(value: &[u8]) -> bool {
507 let value_len = value.len();
508 if value_len == 0 {
509 return true;
510 }
511 match value[0] {
512 b' ' | b'\t' => return false,
513 _ => {},
514 };
515 match value[value_len - 1] {
516 b' ' | b'\t' => return false,
517 _ => {},
518 };
519 for &ch in value {
520 match ch {
521 b'\0' | b'\n' | b'\r' => return false,
522 _ => {},
523 }
524 }
525 true
526 }
540
541pub(crate) fn is_vchar(x: u8) -> bool {
543 matches!(x, 0x21..=0x7E)
544}
545
546pub(crate) fn is_obs_text(x: u8) -> bool {
548 matches!(x, 0x80..=0xFF)
549}