ruma_common/
http_headers.rs1use std::borrow::Cow;
4
5use http::{HeaderValue, header::HeaderName};
6use web_time::{Duration, SystemTime, UNIX_EPOCH};
7
8mod content_disposition;
9mod rfc8187;
10
11pub use self::content_disposition::{
12 ContentDisposition, ContentDispositionParseError, ContentDispositionType, TokenString,
13 TokenStringParseError,
14};
15use crate::api::error::{HeaderDeserializationError, HeaderSerializationError};
16
17pub const APPLICATION_JSON: HeaderValue = HeaderValue::from_static("application/json");
19
20pub const APPLICATION_OCTET_STREAM: HeaderValue =
22 HeaderValue::from_static("application/octet-stream");
23
24pub const TEXT_PLAIN: HeaderValue = HeaderValue::from_static("text/plain");
26
27pub const TEXT_HTML_UTF8: HeaderValue = HeaderValue::from_static("text/html; charset=utf-8");
29
30pub const CROSS_ORIGIN_RESOURCE_POLICY: HeaderName =
34 HeaderName::from_static("cross-origin-resource-policy");
35
36pub const fn is_tchar(b: u8) -> bool {
40 b.is_ascii_alphanumeric()
41 || matches!(
42 b,
43 b'!' | b'#'
44 | b'$'
45 | b'%'
46 | b'&'
47 | b'\''
48 | b'*'
49 | b'+'
50 | b'-'
51 | b'.'
52 | b'^'
53 | b'_'
54 | b'`'
55 | b'|'
56 | b'~'
57 )
58}
59
60pub fn is_token(bytes: &[u8]) -> bool {
64 bytes.iter().all(|b| is_tchar(*b))
65}
66
67pub fn is_token_string(s: &str) -> bool {
71 is_token(s.as_bytes())
72}
73
74pub const fn is_vchar(c: char) -> bool {
78 matches!(c, '\x21'..='\x7E')
79}
80
81pub const fn is_ascii_string_quotable(c: char) -> bool {
88 is_vchar(c) || matches!(c, '\x09' | '\x20')
89}
90
91pub fn sanitize_for_ascii_quoted_string(value: &str) -> Cow<'_, str> {
95 if value.chars().all(is_ascii_string_quotable) {
96 return Cow::Borrowed(value);
97 }
98
99 Cow::Owned(value.chars().filter(|c| is_ascii_string_quotable(*c)).collect())
100}
101
102pub fn quote_ascii_string_if_required(value: &str) -> Cow<'_, str> {
109 if !value.is_empty() && is_token_string(value) {
110 return Cow::Borrowed(value);
111 }
112
113 let value = value.replace('\\', r#"\\"#).replace('"', r#"\""#);
114 Cow::Owned(format!("\"{value}\""))
115}
116
117pub fn unescape_string(s: &str) -> String {
119 let mut is_escaped = false;
120
121 s.chars()
122 .filter(|c| {
123 is_escaped = *c == '\\' && !is_escaped;
124 !is_escaped
125 })
126 .collect()
127}
128
129pub fn system_time_to_http_date(
131 time: &SystemTime,
132) -> Result<HeaderValue, HeaderSerializationError> {
133 let mut buffer = [0; 29];
134
135 let duration =
136 time.duration_since(UNIX_EPOCH).map_err(|_| HeaderSerializationError::InvalidHttpDate)?;
137 date_header::format(duration.as_secs(), &mut buffer)
138 .map_err(|_| HeaderSerializationError::InvalidHttpDate)?;
139
140 Ok(HeaderValue::from_bytes(&buffer).expect("date_header should produce a valid header value"))
141}
142
143pub fn http_date_to_system_time(
145 value: &HeaderValue,
146) -> Result<SystemTime, HeaderDeserializationError> {
147 let bytes = value.as_bytes();
148
149 let ts = date_header::parse(bytes).map_err(|_| HeaderDeserializationError::InvalidHttpDate)?;
150
151 UNIX_EPOCH
152 .checked_add(Duration::from_secs(ts))
153 .ok_or(HeaderDeserializationError::InvalidHttpDate)
154}