runtime/globals/fetch/
header.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7#![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}