1#![allow(clippy::declare_interior_mutable_const)]
8
9use std::cmp::Ordering;
10use std::fmt::{Display, Formatter};
11use std::str::FromStr as _;
12use std::{fmt, str, vec};
13
14use http::header::{
15 ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCESS_CONTROL_ALLOW_HEADERS,
16 ACCESS_CONTROL_ALLOW_METHODS, CONNECTION, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_TYPE, COOKIE, DATE, DNT,
17 EXPECT, Entry, HOST, HeaderMap, HeaderName, HeaderValue, ORIGIN, RANGE, REFERER, SET_COOKIE, TE, TRAILER,
18 TRANSFER_ENCODING, UPGRADE, VIA,
19};
20use ion::class::Reflector;
21use ion::conversions::{FromValue, ToValue};
22use ion::function::Opt;
23use ion::string::byte::{ByteString, VisibleAscii};
24use ion::symbol::WellKnownSymbolCode;
25use ion::{
26 Array, ClassDefinition as _, Context, Error, ErrorKind, FromValue, JSIterator, Object, OwnedKey, Result, Value,
27 js_class,
28};
29use mime::{APPLICATION, FORM_DATA, MULTIPART, Mime, PLAIN, TEXT, WWW_FORM_URLENCODED};
30
31#[derive(FromValue)]
32#[ion(inherit)]
33pub enum Header {
34 Multiple(Vec<String>),
35 Single(String),
36}
37
38impl Display for Header {
39 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
40 match self {
41 Header::Multiple(vec) => {
42 for str in vec {
43 f.write_str(str)?;
44 f.write_str(", ")?;
45 }
46 Ok(())
47 }
48 Header::Single(str) => f.write_str(str),
49 }
50 }
51}
52
53impl ToValue<'_> for Header {
54 fn to_value(&self, cx: &Context, value: &mut Value) {
55 self.to_string().to_value(cx, value);
56 }
57}
58
59pub struct HeaderEntry {
60 name: ByteString<VisibleAscii>,
61 value: ByteString<VisibleAscii>,
62}
63
64impl<'cx> FromValue<'cx> for HeaderEntry {
65 type Config = ();
66 fn from_value(cx: &'cx Context, value: &Value, _: bool, _: ()) -> Result<HeaderEntry> {
67 let vec: Vec<ByteString<VisibleAscii>> = Vec::from_value(cx, value, false, ())?;
68 let boxed: Box<[ByteString<VisibleAscii>; 2]> = vec
69 .try_into()
70 .map_err(|_| Error::new("Expected Header Entry with Length 2", ErrorKind::Type))?;
71 let [name, value] = *boxed;
72 Ok(HeaderEntry { name, value })
73 }
74}
75
76impl ToValue<'_> for HeaderEntry {
77 fn to_value(&self, cx: &Context, value: &mut Value) {
78 let array = Array::new(cx);
79 array.set_as(cx, 0, &self.name);
80 array.set_as(cx, 1, &self.value);
81 array.to_value(cx, value);
82 }
83}
84
85pub struct HeadersObject(HeaderMap);
86
87impl<'cx> FromValue<'cx> for HeadersObject {
88 type Config = ();
89 fn from_value(cx: &'cx Context, value: &Value, _: bool, _: ()) -> Result<HeadersObject> {
90 let object = Object::from_value(cx, value, true, ())?;
91 let mut headers = HeaderMap::new();
92 append_to_headers(cx, &mut headers, &object)?;
93 Ok(HeadersObject(headers))
94 }
95}
96
97#[derive(Default, FromValue)]
98#[ion(inherit)]
99pub enum HeadersInit<'cx> {
100 Existing(&'cx Headers),
101 Array(Vec<HeaderEntry>),
102 Object(HeadersObject),
103 #[ion(skip)]
104 #[default]
105 Empty,
106}
107
108impl HeadersInit<'_> {
109 pub(crate) fn into_headers(self, mut headers: HeaderMap, kind: HeadersKind) -> Result<Headers> {
110 match self {
111 HeadersInit::Existing(existing) => {
112 headers.extend(existing.headers.iter().map(|(name, value)| (name.clone(), value.clone())));
113 Ok(Headers {
114 reflector: Reflector::default(),
115 headers,
116 kind,
117 })
118 }
119 HeadersInit::Array(vec) => Headers::from_array(vec, headers, kind),
120 HeadersInit::Object(object) => {
121 let mut name = None;
122 for (nm, value) in object.0 {
123 if let nm @ Some(_) = nm {
124 name = nm;
125 }
126 append_header(&mut headers, name.clone().unwrap(), value, kind)?;
127 }
128 Ok(Headers {
129 reflector: Reflector::default(),
130 headers,
131 kind,
132 })
133 }
134 HeadersInit::Empty => Ok(Headers {
135 reflector: Reflector::default(),
136 headers,
137 kind,
138 }),
139 }
140 }
141}
142
143#[derive(Copy, Clone, Debug, Default, PartialEq)]
144pub enum HeadersKind {
145 Immutable,
146 Request,
147 RequestNoCors,
148 Response,
149 #[default]
150 None,
151}
152
153#[js_class]
154#[derive(Debug, Default)]
155pub struct Headers {
156 pub(crate) reflector: Reflector,
157 #[trace(no_trace)]
158 pub(crate) headers: HeaderMap,
159 #[trace(no_trace)]
160 pub(crate) kind: HeadersKind,
161}
162
163impl Headers {
164 pub fn new(kind: HeadersKind) -> Headers {
165 Headers { kind, ..Headers::default() }
166 }
167
168 pub fn from_array(vec: Vec<HeaderEntry>, mut headers: HeaderMap, kind: HeadersKind) -> Result<Headers> {
169 for entry in vec {
170 let name = HeaderName::from_bytes(&entry.name)?;
171 let value = HeaderValue::from_bytes(&entry.value)?;
172 append_header(&mut headers, name, value, kind)?;
173 }
174 Ok(Headers {
175 reflector: Reflector::default(),
176 headers,
177 kind,
178 })
179 }
180}
181
182#[js_class]
183impl Headers {
184 #[ion(constructor)]
185 pub fn constructor(Opt(init): Opt<HeadersInit>) -> Result<Headers> {
186 init.unwrap_or_default().into_headers(HeaderMap::default(), HeadersKind::None)
187 }
188
189 pub fn append(&mut self, name: ByteString<VisibleAscii>, value: ByteString<VisibleAscii>) -> Result<()> {
190 if self.kind != HeadersKind::Immutable {
191 let name = HeaderName::from_bytes(&name)?;
192 let value = HeaderValue::from_bytes(&value)?;
193 self.headers.append(name, value);
194 Ok(())
195 } else {
196 Err(Error::new("Cannot Modify Readonly Headers", None))
197 }
198 }
199
200 pub fn delete(&mut self, name: ByteString<VisibleAscii>) -> Result<()> {
201 let name = HeaderName::from_bytes(&name)?;
202 if !validate_header(&name, &HeaderValue::from_static(""), self.kind)? {
203 return Ok(());
204 }
205
206 if self.kind == HeadersKind::RequestNoCors
207 && !NO_CORS_SAFELISTED_REQUEST_HEADERS.contains(&name)
208 && name != RANGE
209 {
210 return Ok(());
211 }
212
213 remove_all_header_entries(&mut self.headers, &name);
214 remove_privileged_no_cors_headers(&mut self.headers, self.kind);
215 Ok(())
216 }
217
218 pub fn get(&self, name: ByteString<VisibleAscii>) -> Result<Option<Header>> {
219 let name = HeaderName::from_bytes(&name)?;
220 Ok(get_header(&self.headers, &name))
221 }
222
223 pub fn get_set_cookie(&self) -> Vec<String> {
224 let header = get_header(&self.headers, &SET_COOKIE);
225 header.map_or_else(Vec::new, |header| match header {
226 Header::Multiple(vec) => vec,
227 Header::Single(str) => vec![str],
228 })
229 }
230
231 pub fn has(&self, name: ByteString<VisibleAscii>) -> Result<bool> {
232 let name = HeaderName::from_bytes(&name)?;
233 Ok(self.headers.contains_key(name))
234 }
235
236 pub fn set(&mut self, name: ByteString<VisibleAscii>, value: ByteString<VisibleAscii>) -> Result<()> {
237 let name = HeaderName::from_bytes(&name)?;
238 let value = HeaderValue::from_bytes(&value)?;
239 if !validate_header(&name, &HeaderValue::from_static(""), self.kind)? {
240 return Ok(());
241 }
242 if self.kind == HeadersKind::RequestNoCors
243 && !validate_no_cors_safelisted_request_header(&mut self.headers, &name, &value)
244 {
245 return Ok(());
246 }
247 self.headers.insert(name, value);
248 remove_privileged_no_cors_headers(&mut self.headers, self.kind);
249 Ok(())
250 }
251
252 #[ion(name = WellKnownSymbolCode::Iterator)]
253 pub fn iterator(&self, cx: &Context) -> ion::Iterator {
254 let cookies: Vec<_> = self.headers.get_all(&SET_COOKIE).iter().map(HeaderValue::clone).collect();
255
256 let mut keys: Vec<_> = self.headers.keys().map(|name| name.as_str().to_ascii_lowercase()).collect();
257 keys.reserve(cookies.len());
258 for _ in 0..cookies.len() {
259 keys.push(String::from(SET_COOKIE.as_str()));
260 }
261 keys.sort();
262
263 let this = self.reflector.get().as_value(cx);
264 ion::Iterator::new(
265 HeadersIterator {
266 keys: keys.into_iter(),
267 cookies: cookies.into_iter(),
268 },
269 &this,
270 )
271 }
272}
273
274pub struct HeadersIterator {
275 keys: vec::IntoIter<String>,
276 cookies: vec::IntoIter<HeaderValue>,
277}
278
279impl JSIterator for HeadersIterator {
280 fn next_value<'cx>(&mut self, cx: &'cx Context, private: &Value<'cx>) -> Option<Value<'cx>> {
281 let object = private.to_object(cx);
282 let headers = Headers::get_private(cx, &object).unwrap();
283
284 let key = self.keys.next()?;
285 if key == SET_COOKIE.as_str() {
286 self.cookies.next().map(|value| [key.as_str(), value.to_str().unwrap()].as_value(cx))
287 } else {
288 get_header(&headers.headers, &HeaderName::from_bytes(key.as_bytes()).unwrap())
289 .map(|value| [key.as_str(), &value.to_string()].as_value(cx))
290 }
291 }
292}
293
294const COOKIE2: HeaderName = HeaderName::from_static("cookie2");
295pub(crate) const SET_COOKIE2: HeaderName = HeaderName::from_static("set-cookie2");
296const KEEP_ALIVE: HeaderName = HeaderName::from_static("keep-alive");
297
298const X_HTTP_METHOD: HeaderName = HeaderName::from_static("x-http-method");
299const X_HTTP_METHOD_OVERRIDE: HeaderName = HeaderName::from_static("x-http-method-override");
300const X_METHOD_OVERRIDE: HeaderName = HeaderName::from_static("x-method-override");
301
302static FORBIDDEN_REQUEST_HEADERS: [HeaderName; 21] = [
303 ACCEPT_CHARSET,
304 ACCEPT_ENCODING,
305 ACCESS_CONTROL_ALLOW_HEADERS,
306 ACCESS_CONTROL_ALLOW_METHODS,
307 CONNECTION,
308 CONTENT_LENGTH,
309 COOKIE,
310 COOKIE2,
311 DATE,
312 DNT,
313 EXPECT,
314 HOST,
315 KEEP_ALIVE,
316 ORIGIN,
317 REFERER,
318 SET_COOKIE,
319 TE,
320 TRAILER,
321 TRANSFER_ENCODING,
322 UPGRADE,
323 VIA,
324];
325
326static FORBIDDEN_REQUEST_HEADER_METHODS: [HeaderName; 3] = [X_HTTP_METHOD, X_HTTP_METHOD_OVERRIDE, X_METHOD_OVERRIDE];
327pub(crate) static FORBIDDEN_RESPONSE_HEADERS: [HeaderName; 2] = [SET_COOKIE, SET_COOKIE2];
328
329static NO_CORS_SAFELISTED_REQUEST_HEADERS: [HeaderName; 4] = [ACCEPT, ACCEPT_LANGUAGE, CONTENT_LANGUAGE, CONTENT_TYPE];
330
331fn validate_header(name: &HeaderName, value: &HeaderValue, kind: HeadersKind) -> Result<bool> {
332 if kind == HeadersKind::Immutable {
333 return Err(Error::new("Headers cannot be modified", ErrorKind::Type));
334 }
335
336 if FORBIDDEN_REQUEST_HEADERS.contains(name) {
337 return Ok(false);
338 }
339 if name.as_str().starts_with("proxy-") || name.as_str().starts_with("sec-") {
340 return Ok(false);
341 }
342 if FORBIDDEN_REQUEST_HEADER_METHODS.contains(name) {
343 let value = split_value(value);
344 if value.iter().any(|v| v == "CONNECT" || v == "TRACE" || v == "TRACK") {
345 return Ok(false);
346 }
347 }
348
349 if FORBIDDEN_RESPONSE_HEADERS.contains(name) {
350 return Ok(false);
351 }
352
353 Ok(true)
354}
355
356fn validate_no_cors_safelisted_request_header(headers: &mut HeaderMap, name: &HeaderName, value: &HeaderValue) -> bool {
357 if !NO_CORS_SAFELISTED_REQUEST_HEADERS.contains(name) {
358 return false;
359 }
360
361 let temp = get_header(headers, name);
362 let str = value.to_str().unwrap();
363 let temp = match temp {
364 Some(temp) => format!("{temp}, {str}"),
365 None => String::from(str),
366 };
367 if temp.len() > 128 {
368 return false;
369 }
370
371 let unsafe_header_byte = temp.as_bytes().iter().any(|b| {
372 (*b < b' ' && *b != b'\t')
373 || matches!(
374 b,
375 b'"' | b'(' | b')' | b':' | b'<' | b'>' | b'?' | b'@' | b'[' | b']' | b'{' | b'}' | 0x7F
376 )
377 });
378 if name == ACCEPT {
379 if unsafe_header_byte {
380 return false;
381 }
382 } else if name == ACCEPT_LANGUAGE || name == CONTENT_LANGUAGE {
383 let cond = temp.as_bytes().iter().all(
384 |b| matches!(b, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b' ' | b'*' | b',' | b'-' | b'.' | b';' | b'='),
385 );
386 if !cond {
387 return false;
388 }
389 } else if name == CONTENT_TYPE {
390 if unsafe_header_byte {
391 return false;
392 }
393 let mime = Mime::from_str(str);
394 match mime {
395 Ok(mime) => {
396 if !(mime.type_() == APPLICATION && mime.subtype() == WWW_FORM_URLENCODED
397 || mime.type_() == MULTIPART && mime.subtype() == FORM_DATA
398 || mime.type_() == TEXT && mime.subtype() == PLAIN)
399 {
400 return false;
401 }
402 }
403 Err(_) => return false,
404 }
405 } else if name == RANGE {
406 if !str.starts_with("bytes=") {
407 return false;
408 }
409 let str = &str[5..];
410 let digit = str.char_indices().find_map(|(i, c)| c.is_ascii_digit().then_some(i + 1));
411 let digit = digit.unwrap_or_default();
412 let start = str[0..digit].parse::<usize>().ok();
413 if str.as_bytes()[digit] != b'-' {
414 return false;
415 }
416
417 let str = &str[digit..];
418 let digit = str.char_indices().find_map(|(i, c)| c.is_ascii_digit().then_some(i + 1));
419 let digit = digit.unwrap_or_default();
420 let end = str[0..digit].parse::<usize>().ok();
421 if digit != str.len() {
422 return false;
423 }
424 match (start, end) {
425 (None, _) => return false,
426 (Some(start), Some(end)) if start > end => return false,
427 _ => (),
428 }
429 } else {
430 return false;
431 }
432
433 true
434}
435
436fn append_header(headers: &mut HeaderMap, name: HeaderName, value: HeaderValue, kind: HeadersKind) -> Result<()> {
437 if !validate_header(&name, &value, kind)? {
438 return Ok(());
439 }
440
441 if kind == HeadersKind::RequestNoCors && !validate_no_cors_safelisted_request_header(headers, &name, &value) {
442 return Ok(());
443 }
444
445 headers.append(name, value);
446 remove_privileged_no_cors_headers(headers, kind);
447 Ok(())
448}
449
450fn remove_privileged_no_cors_headers(headers: &mut HeaderMap, kind: HeadersKind) {
451 if kind == HeadersKind::RequestNoCors {
452 remove_all_header_entries(headers, &RANGE);
453 }
454}
455
456fn append_to_headers(cx: &Context, headers: &mut HeaderMap, obj: &Object) -> Result<()> {
457 for key in obj.keys(cx, None).map(|key| key.to_owned_key(cx)) {
458 let key = match key {
459 Ok(OwnedKey::Int(i)) => i.to_string(),
460 Ok(OwnedKey::String(s)) => s,
461 _ => continue,
462 };
463
464 let name = HeaderName::from_str(&key.to_lowercase())?;
465 let value = obj.get(cx, &key)?.unwrap();
466 if let Ok(array) = Array::from_value(cx, &value, false, ()) {
467 let vec: Vec<_> = array
468 .to_vec(cx)
469 .into_iter()
470 .map(|v| String::from_value(cx, &v, false, ()))
471 .collect::<Result<_>>()?;
472 let str = vec.join(", ");
473 let value = HeaderValue::from_str(&str)?;
474 headers.insert(name, value);
475 } else if let Ok(str) = String::from_value(cx, &value, false, ()) {
476 let value = HeaderValue::from_str(&str)?;
477 headers.insert(name, value);
478 } else {
479 return Err(Error::new("Could not convert value to Header Value", ErrorKind::Type));
480 }
481 }
482 Ok(())
483}
484
485pub(crate) fn remove_all_header_entries(headers: &mut HeaderMap, name: &HeaderName) {
486 match headers.entry(name) {
487 Entry::Occupied(o) => {
488 o.remove_entry_mult();
489 }
490 Entry::Vacant(_) => {}
491 }
492}
493
494fn get_header(headers: &HeaderMap, name: &HeaderName) -> Option<Header> {
495 let split = headers.get_all(name).into_iter().map(split_value);
496 let mut values = Vec::with_capacity(split.size_hint().0);
497 for value in split {
498 values.extend(value);
499 }
500 match values.len().cmp(&1) {
501 Ordering::Less => None,
502 Ordering::Equal => Some(Header::Single(values.pop().unwrap())),
503 Ordering::Greater => Some(Header::Multiple(values)),
504 }
505}
506
507fn split_value(value: &HeaderValue) -> Vec<String> {
508 let mut quoted = false;
509 let mut escaped = false;
510 let mut result = vec![String::new()];
511
512 for char in str::from_utf8(value.as_bytes()).unwrap().chars() {
513 let len = result.len();
514 if char == '"' && !escaped {
515 quoted = !quoted;
516 } else if char == ',' && !quoted {
517 let str = &mut result[len - 1];
518 *str = String::from(str.trim());
519 result.push(String::new());
520 } else {
521 result[len - 1].push(char);
522 }
523 escaped = char == '\\';
524 }
525 result
526}