Skip to content

Commit c711513

Browse files
committed
Head, Request, and Response to use HeaderList.
1 parent c9e91b0 commit c711513

File tree

9 files changed

+287
-187
lines changed

9 files changed

+287
-187
lines changed

src/ascii_string.rs

+16-6
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,10 @@ impl Display for AsciiString {
6767
write!(f, "{}", self.0)
6868
}
6969
}
70-
71-
fn try_from_error(bytes: impl AsRef<[u8]>) -> String {
72-
format!(
73-
"`AsciiString::try_from` called with non-ASCII value: \"{}\"",
74-
escape_and_elide(bytes.as_ref(), 100)
75-
)
70+
impl From<AsciiString> for String {
71+
fn from(ascii_string: AsciiString) -> Self {
72+
ascii_string.0
73+
}
7674
}
7775

7876
impl From<i8> for AsciiString {
@@ -115,6 +113,18 @@ impl From<u64> for AsciiString {
115113
Self(n.to_string())
116114
}
117115
}
116+
impl From<usize> for AsciiString {
117+
fn from(n: usize) -> Self {
118+
Self(n.to_string())
119+
}
120+
}
121+
122+
fn try_from_error(bytes: impl AsRef<[u8]>) -> String {
123+
format!(
124+
"`AsciiString::try_from` called with non-ASCII value: \"{}\"",
125+
escape_and_elide(bytes.as_ref(), 100)
126+
)
127+
}
118128

119129
impl TryFrom<char> for AsciiString {
120130
type Error = String;

src/head.rs

+14-18
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::http_error::HttpError;
22
use crate::util::find_slice;
3+
use crate::{AsciiString, Header, HeaderList};
34
use fixed_buffer::FixedBuf;
45
use futures_io::AsyncRead;
56
use futures_lite::AsyncReadExt;
67
use safe_regex::{regex, Matcher2, Matcher3};
7-
use std::collections::HashMap;
88
use url::Url;
99

1010
fn trim_trailing_cr(bytes: &[u8]) -> &[u8] {
@@ -49,7 +49,7 @@ pub enum HeadError {
4949
pub struct Head {
5050
pub method: String,
5151
pub url: Url,
52-
pub headers_lowercase: HashMap<String, String>,
52+
pub headers: HeaderList,
5353
}
5454
impl Head {
5555
fn read_head_bytes<const BUF_SIZE: usize>(
@@ -101,7 +101,7 @@ impl Head {
101101
bytes.iter().map(|&b| b as char).collect()
102102
}
103103

104-
fn parse_header_line(line: &[u8]) -> Result<(String, String), HeadError> {
104+
fn parse_header_line(line: &[u8]) -> Result<Header, HeadError> {
105105
// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
106106
// header-field = field-name ":" OWS field-value OWS
107107
// field-name = token
@@ -128,9 +128,11 @@ impl Head {
128128
let (name_bytes, value_bytes) = matcher
129129
.match_slices(line)
130130
.ok_or(HeadError::MalformedHeader)?;
131-
let name_lowercase = std::str::from_utf8(name_bytes).unwrap().to_lowercase();
132-
let value = Self::latin1_bytes_to_utf8(trim_whitespace(value_bytes));
133-
Ok((name_lowercase, value))
131+
let name_string = String::from_utf8(name_bytes.to_vec()).unwrap();
132+
let value_string = Self::latin1_bytes_to_utf8(trim_whitespace(value_bytes));
133+
let name = AsciiString::try_from(name_string).unwrap();
134+
let value = AsciiString::try_from(value_string).unwrap();
135+
Ok(Header::new(name, value))
134136
}
135137

136138
/// # Errors
@@ -144,32 +146,26 @@ impl Head {
144146
let mut lines = head.split(|b| *b == b'\n').map(trim_trailing_cr);
145147
let request_line = lines.next().ok_or(HeadError::MissingRequestLine)?;
146148
let (method, url) = Self::parse_request_line(request_line)?;
147-
let mut headers_lowercase: HashMap<String, String> = HashMap::new();
149+
let mut headers = HeaderList::new();
148150
for line in lines {
149-
let (name_lowercase, value) = Self::parse_header_line(line)?;
150-
headers_lowercase.insert(name_lowercase, value);
151+
let header = Self::parse_header_line(line)?;
152+
headers.push(header);
151153
}
152154
Ok(Self {
153155
method,
154156
url,
155-
headers_lowercase,
157+
headers,
156158
})
157159
}
158160
}
159161
impl core::fmt::Debug for Head {
160162
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
161-
let mut headers: Vec<String> = self
162-
.headers_lowercase
163-
.iter()
164-
.map(|(n, v)| format!("{}: {:?}", n, v))
165-
.collect();
166-
headers.sort();
167163
write!(
168164
f,
169-
"Head{{method={:?}, path={:?}, headers={{{}}}}}",
165+
"Head{{method={:?}, path={:?}, headers={:?}}}",
170166
self.method,
171167
self.url.path(),
172-
headers.join(", ")
168+
self.headers
173169
)
174170
}
175171
}

src/headers.rs

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use crate::util::escape_and_elide;
2+
use crate::AsciiString;
3+
use core::fmt::{Debug, Display, Formatter};
4+
use std::ops::{Deref, DerefMut};
5+
6+
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
7+
pub struct Header {
8+
pub name: AsciiString,
9+
pub value: AsciiString,
10+
}
11+
impl Header {
12+
#[must_use]
13+
pub fn new(name: AsciiString, value: AsciiString) -> Self {
14+
Self { name, value }
15+
}
16+
}
17+
impl Debug for Header {
18+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
19+
write!(
20+
f,
21+
"Header({}:{})",
22+
escape_and_elide(self.name.as_bytes(), 30),
23+
escape_and_elide(self.value.as_bytes(), 1000)
24+
)
25+
}
26+
}
27+
impl Display for Header {
28+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
29+
write!(f, "{}:{}", self.name.as_str(), self.value.as_str())
30+
}
31+
}
32+
33+
#[derive(Clone, Eq, PartialEq)]
34+
pub struct HeaderList(pub Vec<Header>);
35+
impl HeaderList {
36+
#[must_use]
37+
pub fn new() -> Self {
38+
Self(Vec::new())
39+
}
40+
41+
/// Adds a header.
42+
///
43+
/// You can call this multiple times to add multiple headers with the same name.
44+
///
45+
/// The [HTTP spec](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4)
46+
/// limits header names to US-ASCII and header values to US-ASCII or ISO-8859-1.
47+
///
48+
/// # Panics
49+
/// Panics when `name` is not US-ASCII.
50+
pub fn add(&mut self, name: impl AsRef<str>, value: AsciiString) {
51+
self.0
52+
.push(Header::new(name.as_ref().try_into().unwrap(), value));
53+
}
54+
55+
/// Searches for a header that matches `name`.
56+
/// Uses a case-insensitive comparison.
57+
///
58+
/// Returns the value of the header.
59+
///
60+
/// Returns `None` when multiple headers matched or none matched.
61+
pub fn get_only(&self, name: impl AsRef<str>) -> Option<&AsciiString> {
62+
let mut value = None;
63+
for header in &self.0 {
64+
if header.name.eq_ignore_ascii_case(name.as_ref()) {
65+
if value.is_some() {
66+
return None;
67+
}
68+
value = Some(&header.value);
69+
}
70+
}
71+
value
72+
}
73+
74+
/// Looks for headers with names that match `name`.
75+
/// Uses a case-insensitive comparison.
76+
/// Returns the values of the matching headers.
77+
pub fn get_all(&self, name: impl AsRef<str>) -> Vec<&AsciiString> {
78+
let mut headers = Vec::new();
79+
for header in &self.0 {
80+
if header.name.eq_ignore_ascii_case(name.as_ref()) {
81+
headers.push(&header.value);
82+
}
83+
}
84+
headers
85+
}
86+
87+
/// Removes all headers with the specified `name`.
88+
/// Uses a case-insensitive comparison.
89+
///
90+
/// When only one header matched, returns the value of that header.
91+
///
92+
/// Returns `None` when multiple headers matched or none matched.
93+
pub fn remove_only(&mut self, name: impl AsRef<str>) -> Option<AsciiString> {
94+
let mut iter = self.remove_all(name).into_iter();
95+
match (iter.next(), iter.next()) {
96+
(Some(value), None) => Some(value),
97+
_ => None,
98+
}
99+
}
100+
101+
/// Removes all headers with the specified `name`.
102+
/// Uses a case-insensitive comparison.
103+
///
104+
/// Returns the values of the headers.
105+
pub fn remove_all(&mut self, name: impl AsRef<str>) -> Vec<AsciiString> {
106+
let mut values = Vec::new();
107+
let mut n = 0;
108+
while n < self.0.len() {
109+
if self.0[n].name.eq_ignore_ascii_case(name.as_ref()) {
110+
let header = self.0.swap_remove(n);
111+
values.push(header.value);
112+
} else {
113+
n += 1;
114+
}
115+
}
116+
values
117+
}
118+
}
119+
impl Debug for HeaderList {
120+
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
121+
let mut strings: Vec<String> = self
122+
.iter()
123+
.map(|h| format!("{}: {:?}", h.name, h.value.as_str()))
124+
.collect();
125+
strings.sort();
126+
write!(f, "{{{}}}", strings.join(", "),)
127+
}
128+
}
129+
impl Default for HeaderList {
130+
fn default() -> Self {
131+
Self::new()
132+
}
133+
}
134+
impl Deref for HeaderList {
135+
type Target = Vec<Header>;
136+
137+
fn deref(&self) -> &Self::Target {
138+
&self.0
139+
}
140+
}
141+
impl DerefMut for HeaderList {
142+
fn deref_mut(&mut self) -> &mut Self::Target {
143+
&mut self.0
144+
}
145+
}
146+
impl<'x> IntoIterator for &'x HeaderList {
147+
type Item = &'x Header;
148+
type IntoIter = core::slice::Iter<'x, Header>;
149+
150+
fn into_iter(self) -> Self::IntoIter {
151+
self.0.iter()
152+
}
153+
}
154+
impl<'x> IntoIterator for &'x mut HeaderList {
155+
type Item = &'x mut Header;
156+
type IntoIter = core::slice::IterMut<'x, Header>;
157+
158+
fn into_iter(self) -> Self::IntoIter {
159+
self.0.iter_mut()
160+
}
161+
}

src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ mod body_async_reader;
168168
mod body_reader;
169169
mod content_type;
170170
mod head;
171+
mod headers;
171172
mod http_conn;
172173
mod http_error;
173174
mod request;
@@ -184,6 +185,7 @@ pub use crate::ascii_string::AsciiString;
184185
pub use crate::body_async_reader::BodyAsyncReader;
185186
pub use crate::body_reader::BodyReader;
186187
pub use crate::content_type::ContentType;
188+
pub use crate::headers::{Header, HeaderList};
187189
pub use crate::http_conn::HttpConn;
188190
pub use crate::request::Request;
189191
pub use crate::request_body::RequestBody;
@@ -201,6 +203,7 @@ pub mod internal {
201203
pub use crate::body_reader::*;
202204
pub use crate::content_type::*;
203205
pub use crate::head::*;
206+
pub use crate::headers::*;
204207
pub use crate::http_conn::*;
205208
pub use crate::http_error::*;
206209
pub use crate::request::*;

0 commit comments

Comments
 (0)