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.as_ref())?;
80 Ok(dom_headers_new)
81 }
82
83 fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult {
85 self.append(name, &value)
86 }
87
88 fn Delete(&self, name: ByteString) -> ErrorResult {
90 let Some(mut valid_name) = self.validate_name_and_value(name, b"")? else {
92 return Ok(());
93 };
94
95 valid_name.make_ascii_lowercase();
97
98 if self.guard.get() == Guard::RequestNoCors &&
101 !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec())
102 {
103 return Ok(());
104 }
105
106 self.header_list.borrow_mut().remove(valid_name);
109
110 if self.guard.get() == Guard::RequestNoCors {
112 self.remove_privileged_no_cors_request_headers();
113 }
114
115 Ok(())
116 }
117
118 fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> {
120 let valid_name = validate_name(name)?;
122
123 Ok(
125 get_value_from_header_list(&valid_name, &self.header_list.borrow())
126 .map(ByteString::new),
127 )
128 }
129
130 fn GetSetCookie(&self) -> Vec<ByteString> {
132 self.header_list
136 .borrow()
137 .get_all("set-cookie")
138 .iter()
139 .map(|v| ByteString::new(v.as_bytes().to_vec()))
140 .collect()
141 }
142
143 fn Has(&self, name: ByteString) -> Fallible<bool> {
145 let valid_name = validate_name(name)?;
147 Ok(self.header_list.borrow_mut().get(&valid_name).is_some())
149 }
150
151 fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> {
153 let value = trim_http_whitespace(&value);
155
156 let Some(mut valid_name) = self.validate_name_and_value(name, value)? else {
158 return Ok(());
159 };
160 valid_name.make_ascii_lowercase();
162
163 if self.guard.get() == Guard::RequestNoCors &&
166 !is_cors_safelisted_request_header(&valid_name, &value)
167 {
168 return Ok(());
169 }
170
171 match (
174 HeaderName::from_str(&valid_name),
175 HeaderValue::from_bytes(value),
176 ) {
177 (Ok(name), Ok(value)) => {
178 self.header_list.borrow_mut().insert(name, value);
179 },
180 _ => {
181 warn!("Could not set header: \"{valid_name:?}: {value:?}\"");
182 },
183 };
184
185 if self.guard.get() == Guard::RequestNoCors {
187 self.remove_privileged_no_cors_request_headers();
188 }
189
190 Ok(())
191 }
192}
193
194impl Headers {
195 pub(crate) fn copy_from_headers(&self, headers: &Headers) -> ErrorResult {
196 for (name, value) in headers.header_list.borrow().iter() {
197 self.Append(
198 ByteString::new(Vec::from(name.as_str())),
199 ByteString::new(Vec::from(value.as_bytes())),
200 )?;
201 }
202 Ok(())
203 }
204
205 fn append(&self, name: ByteString, value: &ByteString) -> ErrorResult {
206 let value = trim_http_whitespace(value);
208
209 let Some(mut valid_name) = self.validate_name_and_value(name, value)? else {
211 return Ok(());
212 };
213
214 valid_name.make_ascii_lowercase();
216
217 if self.guard.get() == Guard::RequestNoCors {
219 let tmp_value = if let Some(mut tmp_value) =
221 get_value_from_header_list(&valid_name, &self.header_list.borrow())
222 {
223 tmp_value.extend(b", ");
225 tmp_value.extend(value);
226 tmp_value
227 } else {
228 value.to_vec()
230 };
231 if !is_cors_safelisted_request_header(&valid_name, &tmp_value) {
233 return Ok(());
234 }
235 }
236
237 match (
239 HeaderName::from_str(&valid_name),
240 HeaderValue::from_bytes(value),
241 ) {
242 (Ok(name), Ok(value)) => {
243 self.header_list.borrow_mut().append(name, value);
244 },
245 _ => {
246 warn!("Could not set header \"{valid_name:?}: {value:?}\"");
247 },
248 };
249
250 if self.guard.get() == Guard::RequestNoCors {
252 self.remove_privileged_no_cors_request_headers();
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 seq in v {
263 if seq.len() == 2 {
264 self.append(seq[0].clone(), &seq[1])?;
265 } else {
266 return Err(Error::Type(cformat!(
267 "Each header object must be a sequence of length 2 - found one with length {}",
268 seq.len()
269 )));
270 }
271 }
272 Ok(())
273 },
274 Some(HeadersInit::ByteStringByteStringRecord(m)) => {
275 for (key, value) in m.iter() {
276 self.append(key.clone(), value)?;
277 }
278 Ok(())
279 },
280 None => Ok(()),
281 }
282 }
283
284 pub(crate) fn for_request(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
285 let headers_for_request = Headers::new(cx, global);
286 headers_for_request.guard.set(Guard::Request);
287 headers_for_request
288 }
289
290 pub(crate) fn for_response(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Headers> {
291 let headers_for_response = Headers::new(cx, global);
292 headers_for_response.guard.set(Guard::Response);
293 headers_for_response
294 }
295
296 pub(crate) fn set_guard(&self, new_guard: Guard) {
297 self.guard.set(new_guard)
298 }
299
300 pub(crate) fn get_guard(&self) -> Guard {
301 self.guard.get()
302 }
303
304 pub(crate) fn set_headers(&self, hyper_headers: HyperHeaders) {
305 *self.header_list.borrow_mut() = hyper_headers;
306 }
307
308 pub(crate) fn get_headers_list(&self) -> HyperHeaders {
309 self.header_list.borrow_mut().clone()
310 }
311
312 pub(crate) fn extract_mime_type(&self) -> Vec<u8> {
314 extract_mime_type(&self.header_list.borrow()).unwrap_or_default()
315 }
316
317 pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec<u8>)> {
319 let borrowed_header_list = self.header_list.borrow();
320 let mut header_vec = vec![];
321
322 for name in borrowed_header_list.keys() {
323 let name = name.as_str();
324 if name == "set-cookie" {
325 for value in borrowed_header_list.get_all(name).iter() {
326 header_vec.push((name.to_owned(), value.as_bytes().to_vec()));
327 }
328 } else if let Some(value) = get_value_from_header_list(name, &borrowed_header_list) {
329 header_vec.push((name.to_owned(), value));
330 }
331 }
332
333 header_vec.sort_by(|a, b| a.0.cmp(&b.0));
334 header_vec
335 }
336
337 pub(crate) fn remove_privileged_no_cors_request_headers(&self) {
339 self.header_list.borrow_mut().remove("range");
341 }
342
343 pub(crate) fn validate_name_and_value(
345 &self,
346 name: ByteString,
347 value: &[u8],
348 ) -> Fallible<Option<String>> {
349 let valid_name = validate_name(name)?;
351 if !is_legal_header_value(value) {
352 return Err(Error::Type(c"Header value is not valid".to_owned()));
353 }
354 if self.guard.get() == Guard::Immutable {
356 return Err(Error::Type(c"Guard is immutable".to_owned()));
357 }
358 if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, value) {
360 return Ok(None);
361 }
362 if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
364 return Ok(None);
365 }
366
367 Ok(Some(valid_name))
368 }
369}
370
371impl Iterable for Headers {
372 type Key = ByteString;
373 type Value = ByteString;
374
375 fn get_iterable_length(&self, _cx: &mut JSContext) -> u32 {
376 let sorted_header_vec = self.sort_and_combine();
377 sorted_header_vec.len() as u32
378 }
379
380 fn get_value_at_index(&self, _cx: &mut JSContext, index: u32) -> ByteString {
381 let sorted_header_vec = self.sort_and_combine();
382 ByteString::new(sorted_header_vec.into_iter().nth(index as usize).unwrap().1)
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 ByteString::new(
388 sorted_header_vec
389 .into_iter()
390 .nth(index as usize)
391 .unwrap()
392 .0
393 .into_bytes(),
394 )
395 }
396}
397
398pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
403 let forbidden_header_names = [
404 "accept-charset",
405 "accept-encoding",
406 "access-control-request-headers",
407 "access-control-request-method",
408 "connection",
409 "content-length",
410 "cookie",
411 "cookie2",
412 "date",
413 "dnt",
414 "expect",
415 "host",
416 "keep-alive",
417 "origin",
418 "referer",
419 "set-cookie",
420 "te",
421 "trailer",
422 "transfer-encoding",
423 "upgrade",
424 "via",
425 "access-control-request-private-network",
429 ];
430
431 if forbidden_header_names
434 .into_iter()
435 .any(|header| name.eq_ignore_ascii_case(header))
436 {
437 return true;
438 }
439
440 let forbidden_header_prefixes = ["sec-", "proxy-"];
441
442 if forbidden_header_prefixes
444 .into_iter()
445 .any(|prefix| starts_with_ignore_ascii_case(name, prefix))
446 {
447 return true;
448 }
449
450 let potentially_forbidden_header_names = [
451 "x-http-method",
452 "x-http-method-override",
453 "x-method-override",
454 ];
455
456 if potentially_forbidden_header_names
458 .into_iter()
459 .any(|header| name.eq_ignore_ascii_case(header))
460 {
461 let parsed_values = get_decode_and_split_header_value(value.to_vec());
463
464 return parsed_values
467 .iter()
468 .any(|s| is_forbidden_method(s.as_bytes()));
469 }
470
471 false
473}
474
475fn is_forbidden_response_header(name: &str) -> bool {
477 name.eq_ignore_ascii_case("set-cookie") || name.eq_ignore_ascii_case("set-cookie2")
479}
480
481fn validate_name(name: ByteString) -> Fallible<String> {
482 if !is_field_name(&name) {
483 return Err(Error::Type(c"Name is not valid".to_owned()));
484 }
485 match String::from_utf8(name.into()) {
486 Ok(ns) => Ok(ns),
487 _ => Err(Error::Type(c"Non-UTF8 header name found".to_owned())),
488 }
489}
490
491fn is_field_name(name: &ByteString) -> bool {
493 is_token(name)
494}
495
496fn is_legal_header_value(value: &[u8]) -> bool {
505 let value_len = value.len();
506 if value_len == 0 {
507 return true;
508 }
509 match value[0] {
510 b' ' | b'\t' => return false,
511 _ => {},
512 };
513 match value[value_len - 1] {
514 b' ' | b'\t' => return false,
515 _ => {},
516 };
517 for &ch in value {
518 match ch {
519 b'\0' | b'\n' | b'\r' => return false,
520 _ => {},
521 }
522 }
523 true
524 }
538
539pub(crate) fn is_vchar(x: u8) -> bool {
541 matches!(x, 0x21..=0x7E)
542}
543
544pub(crate) fn is_obs_text(x: u8) -> bool {
546 matches!(x, 0x80..=0xFF)
547}