1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! Ordered HTTP header fields with case-insensitive name matching.
use crate::error::{ProtoError, Result};
/// A header name and its unmodified field value.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HeaderField {
/// ASCII lowercase header name.
pub name: String,
/// Header field value.
pub value: String,
}
/// Ordered header fields, including repeated fields.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Headers {
fields: Vec<HeaderField>,
}
impl Headers {
/// Creates an empty header list.
pub fn new() -> Self {
Self::default()
}
/// Creates a header list with room for `cap` fields.
///
/// # Panics
/// Panics if the requested allocation exceeds the platform capacity limit.
pub fn with_capacity(cap: usize) -> Self {
Self {
fields: Vec::with_capacity(cap),
}
}
/// Replaces all fields with this name, retaining the first field's position.
pub fn insert(&mut self, name: impl Into<String>, value: impl Into<String>) {
let name = name.into().to_ascii_lowercase();
let value = value.into();
if let Some(index) = self.fields.iter().position(|field| field.name == name) {
self.fields[index].value = value;
let mut position = 0;
self.fields.retain(|field| {
let retain = position == index || field.name != name;
position += 1;
retain
});
} else {
self.fields.push(HeaderField { name, value });
}
}
/// Appends a field without replacing fields with the same name.
pub fn append(&mut self, name: impl Into<String>, value: impl Into<String>) {
self.fields.push(HeaderField {
name: name.into().to_ascii_lowercase(),
value: value.into(),
});
}
/// Returns the first value with a case-insensitively matching name.
pub fn get(&self, name: &str) -> Option<&str> {
self.fields
.iter()
.find(|field| field.name.eq_ignore_ascii_case(name))
.map(|field| field.value.as_str())
}
/// Returns all matching values in insertion order.
pub fn get_all(&self, name: &str) -> Vec<&str> {
self.fields
.iter()
.filter(|field| field.name.eq_ignore_ascii_case(name))
.map(|field| field.value.as_str())
.collect()
}
/// Reports whether at least one matching field exists.
pub fn contains(&self, name: &str) -> bool {
self.get(name).is_some()
}
/// Removes every matching field and returns the first removed value.
pub fn remove(&mut self, name: &str) -> Option<String> {
let first = self
.fields
.iter()
.position(|field| field.name.eq_ignore_ascii_case(name))?;
let value = self.fields.remove(first).value;
self.fields
.retain(|field| !field.name.eq_ignore_ascii_case(name));
Some(value)
}
/// Iterates over fields in insertion order.
pub fn iter(&self) -> std::slice::Iter<'_, HeaderField> {
self.fields.iter()
}
/// Returns the number of fields, including duplicates.
pub fn len(&self) -> usize {
self.fields.len()
}
/// Reports whether the header list is empty.
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
/// Removes all fields while retaining the allocated field capacity.
pub fn clear(&mut self) {
self.fields.clear();
}
/// Returns the aggregate name and value length in bytes.
pub fn byte_size(&self) -> usize {
self.fields.iter().fold(0usize, |size, field| {
size.saturating_add(field.name.len())
.saturating_add(field.value.len())
})
}
/// Parses a consistent decimal length across all field lines and comma lists.
///
/// # Errors
/// Returns an invalid-header error for empty, nondecimal, overflowing or
/// conflicting values. Only HTTP optional whitespace surrounds each value.
pub fn content_length(&self) -> Result<Option<usize>> {
let mut length = None;
for field in &self.fields {
if !field.name.eq_ignore_ascii_case("content-length") {
continue;
}
for part in field.value.split(',') {
let part = part.trim_matches([' ', '\t']);
if part.is_empty() || !part.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(ProtoError::InvalidHeader("invalid content-length".into()));
}
let parsed = part.parse::<usize>().map_err(|_| {
ProtoError::InvalidHeader("content-length exceeds addressable size".into())
})?;
if length.is_some_and(|previous| previous != parsed) {
return Err(ProtoError::InvalidHeader(
"conflicting content-length values".into(),
));
}
length = Some(parsed);
}
}
Ok(length)
}
/// Reports whether the final transfer coding token is `chunked`.
///
/// Transfer codings are combined in field order. A substring, parameterized
/// token or a coding preceding another coding does not indicate chunk framing.
pub fn is_chunked(&self) -> bool {
self.fields
.iter()
.filter(|field| field.name.eq_ignore_ascii_case("transfer-encoding"))
.flat_map(|field| field.value.split(','))
.next_back()
.is_some_and(|token| {
token
.trim_matches([' ', '\t'])
.eq_ignore_ascii_case("chunked")
})
}
}
/// Converts typed header representations to and from an ordered header list.
pub trait HeaderMapExt: Sized {
/// Converts the typed representation into fields.
fn to_headers(&self) -> Headers;
/// Parses typed values from header fields.
///
/// # Errors
/// Returns an error when required fields are absent or field values are invalid.
fn from_headers(headers: &Headers) -> Result<Self>;
}