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::rust::HandleObject;
11use net_traits::fetch::headers::{
12 extract_mime_type, get_decode_and_split_header_value, get_value_from_header_list,
13 is_forbidden_method,
14};
15use net_traits::request::is_cors_safelisted_request_header;
16use net_traits::trim_http_whitespace;
17
18use crate::dom::bindings::cell::DomRefCell;
19use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
20use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
21use crate::dom::bindings::iterable::Iterable;
22use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
23use crate::dom::bindings::root::DomRoot;
24use crate::dom::bindings::str::{ByteString, is_token};
25use crate::dom::globalscope::GlobalScope;
26use crate::script_runtime::CanGc;
27
28#[dom_struct]
29pub(crate) struct Headers {
30 reflector_: Reflector,
31 guard: Cell<Guard>,
32 #[ignore_malloc_size_of = "Defined in hyper"]
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(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Headers> {
57 Self::new_with_proto(global, None, can_gc)
58 }
59
60 fn new_with_proto(
61 global: &GlobalScope,
62 proto: Option<HandleObject>,
63 can_gc: CanGc,
64 ) -> DomRoot<Headers> {
65 reflect_dom_object_with_proto(Box::new(Headers::new_inherited()), global, proto, can_gc)
66 }
67}
68
69impl HeadersMethods<crate::DomTypeHolder> for Headers {
70 fn Constructor(
72 global: &GlobalScope,
73 proto: Option<HandleObject>,
74 can_gc: CanGc,
75 init: Option<HeadersInit>,
76 ) -> Fallible<DomRoot<Headers>> {
77 let dom_headers_new = Headers::new_with_proto(global, proto, can_gc);
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 HeaderValue::from_bytes(&valid_value) {
118 Ok(value) => {
119 self.header_list
120 .borrow_mut()
121 .append(HeaderName::from_str(&valid_name).unwrap(), value);
122 },
123 Err(_) => {
124 warn!(
126 "Servo thinks \"{:?}\" is a valid HTTP header value but HeaderValue doesn't.",
127 valid_value
128 );
129 },
130 };
131
132 if self.guard.get() == Guard::RequestNoCors {
134 self.remove_privileged_no_cors_request_headers();
135 }
136
137 Ok(())
138 }
139
140 fn Delete(&self, name: ByteString) -> ErrorResult {
142 let name_and_value = self.validate_name_and_value(name, ByteString::new(vec![]))?;
144 let Some((mut valid_name, _valid_value)) = name_and_value else {
145 return Ok(());
146 };
147
148 valid_name = valid_name.to_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 = valid_name.to_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 HeaderValue::from_bytes(&valid_value) {
227 Ok(value) => {
228 self.header_list
229 .borrow_mut()
230 .insert(HeaderName::from_str(&valid_name).unwrap(), value);
231 },
232 Err(_) => {
233 warn!(
235 "Servo thinks \"{:?}\" is a valid HTTP header value but HeaderValue doesn't.",
236 valid_value
237 );
238 },
239 };
240
241 if self.guard.get() == Guard::RequestNoCors {
243 self.remove_privileged_no_cors_request_headers();
244 }
245
246 Ok(())
247 }
248}
249
250impl Headers {
251 pub(crate) fn copy_from_headers(&self, headers: DomRoot<Headers>) -> ErrorResult {
252 for (name, value) in headers.header_list.borrow().iter() {
253 self.Append(
254 ByteString::new(Vec::from(name.as_str())),
255 ByteString::new(Vec::from(value.as_bytes())),
256 )?;
257 }
258 Ok(())
259 }
260
261 pub(crate) fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult {
263 match filler {
264 Some(HeadersInit::ByteStringSequenceSequence(v)) => {
265 for mut seq in v {
266 if seq.len() == 2 {
267 let val = seq.pop().unwrap();
268 let name = seq.pop().unwrap();
269 self.Append(name, val)?;
270 } else {
271 return Err(Error::Type(format!(
272 "Each header object must be a sequence of length 2 - found one with length {}",
273 seq.len()
274 )));
275 }
276 }
277 Ok(())
278 },
279 Some(HeadersInit::ByteStringByteStringRecord(m)) => {
280 for (key, value) in m.iter() {
281 self.Append(key.clone(), value.clone())?;
282 }
283 Ok(())
284 },
285 None => Ok(()),
286 }
287 }
288
289 pub(crate) fn for_request(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Headers> {
290 let headers_for_request = Headers::new(global, can_gc);
291 headers_for_request.guard.set(Guard::Request);
292 headers_for_request
293 }
294
295 pub(crate) fn for_response(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Headers> {
296 let headers_for_response = Headers::new(global, can_gc);
297 headers_for_response.guard.set(Guard::Response);
298 headers_for_response
299 }
300
301 pub(crate) fn set_guard(&self, new_guard: Guard) {
302 self.guard.set(new_guard)
303 }
304
305 pub(crate) fn get_guard(&self) -> Guard {
306 self.guard.get()
307 }
308
309 pub(crate) fn set_headers(&self, hyper_headers: HyperHeaders) {
310 *self.header_list.borrow_mut() = hyper_headers;
311 }
312
313 pub(crate) fn get_headers_list(&self) -> HyperHeaders {
314 self.header_list.borrow_mut().clone()
315 }
316
317 pub(crate) fn extract_mime_type(&self) -> Vec<u8> {
319 extract_mime_type(&self.header_list.borrow()).unwrap_or_default()
320 }
321
322 pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec<u8>)> {
324 let borrowed_header_list = self.header_list.borrow();
325 let mut header_vec = vec![];
326
327 for name in borrowed_header_list.keys() {
328 let name = name.as_str();
329 if name == "set-cookie" {
330 for value in borrowed_header_list.get_all(name).iter() {
331 header_vec.push((name.to_owned(), value.as_bytes().to_vec()));
332 }
333 } else if let Some(value) = get_value_from_header_list(name, &borrowed_header_list) {
334 header_vec.push((name.to_owned(), value));
335 }
336 }
337
338 header_vec.sort_by(|a, b| a.0.cmp(&b.0));
339 header_vec
340 }
341
342 pub(crate) fn remove_privileged_no_cors_request_headers(&self) {
344 self.header_list.borrow_mut().remove("range");
346 }
347
348 pub(crate) fn validate_name_and_value(
350 &self,
351 name: ByteString,
352 value: ByteString,
353 ) -> Fallible<Option<(String, ByteString)>> {
354 let valid_name = validate_name(name)?;
356 if !is_legal_header_value(&value) {
357 return Err(Error::Type("Header value is not valid".to_string()));
358 }
359 if self.guard.get() == Guard::Immutable {
361 return Err(Error::Type("Guard is immutable".to_string()));
362 }
363 if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, &value) {
365 return Ok(None);
366 }
367 if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
369 return Ok(None);
370 }
371
372 Ok(Some((valid_name, value)))
373 }
374}
375
376impl Iterable for Headers {
377 type Key = ByteString;
378 type Value = ByteString;
379
380 fn get_iterable_length(&self) -> u32 {
381 let sorted_header_vec = self.sort_and_combine();
382 sorted_header_vec.len() as u32
383 }
384
385 fn get_value_at_index(&self, n: u32) -> ByteString {
386 let sorted_header_vec = self.sort_and_combine();
387 let value = sorted_header_vec[n as usize].1.clone();
388 ByteString::new(value)
389 }
390
391 fn get_key_at_index(&self, n: u32) -> ByteString {
392 let sorted_header_vec = self.sort_and_combine();
393 let key = sorted_header_vec[n as usize].0.clone();
394 ByteString::new(key.into_bytes().to_vec())
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 let lowercase_name = name.to_lowercase();
434
435 if forbidden_header_names.contains(&lowercase_name.as_str()) {
436 return true;
437 }
438
439 let forbidden_header_prefixes = ["sec-", "proxy-"];
440
441 if forbidden_header_prefixes
443 .iter()
444 .any(|prefix| lowercase_name.starts_with(prefix))
445 {
446 return true;
447 }
448
449 let potentially_forbidden_header_names = [
450 "x-http-method",
451 "x-http-method-override",
452 "x-method-override",
453 ];
454
455 if potentially_forbidden_header_names
457 .iter()
458 .any(|header| *header == lowercase_name)
459 {
460 let parsed_values = get_decode_and_split_header_value(value.to_vec());
462
463 return parsed_values
466 .iter()
467 .any(|s| is_forbidden_method(s.as_bytes()));
468 }
469
470 false
472}
473
474fn is_forbidden_response_header(name: &str) -> bool {
476 let name = name.to_ascii_lowercase();
478 matches!(name.as_str(), "set-cookie" | "set-cookie2")
479}
480
481fn validate_name(name: ByteString) -> Fallible<String> {
482 if !is_field_name(&name) {
483 return Err(Error::Type("Name is not valid".to_string()));
484 }
485 match String::from_utf8(name.into()) {
486 Ok(ns) => Ok(ns),
487 _ => Err(Error::Type("Non-UTF8 header name found".to_string())),
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}