Skip to content

Commit ac24bf4

Browse files
committed
feat: improved BodyHeaders parsing
Signed-off-by: darkseid <[email protected]>
1 parent a7e950f commit ac24bf4

File tree

7 files changed

+274
-279
lines changed

7 files changed

+274
-279
lines changed

header-plz/src/body_headers/content_encoding.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
pub const CHUNKED: &str = "chunked";
21
pub const BROTLI: &str = "br";
2+
pub const CHUNKED: &str = "chunked";
33
pub const COMPRESS: &str = "compress";
44
pub const DEFLATE: &str = "deflate";
55
pub const GZIP: &str = "gzip";
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use crate::body_headers::content_encoding::ContentEncoding;
2+
3+
#[cfg_attr(any(test, debug_assertions), derive(Debug, PartialEq, Eq, Clone))]
4+
pub struct EncodingInfo {
5+
header_index: usize,
6+
pub encoding: ContentEncoding,
7+
}
8+
9+
impl From<(usize, ContentEncoding)> for EncodingInfo {
10+
fn from((header_index, encoding): (usize, ContentEncoding)) -> Self {
11+
EncodingInfo {
12+
header_index,
13+
encoding,
14+
}
15+
}
16+
}
17+
18+
impl EncodingInfo {
19+
pub fn new(header_index: usize, encoding: ContentEncoding) -> Self {
20+
EncodingInfo {
21+
header_index,
22+
encoding,
23+
}
24+
}
25+
26+
pub fn iter_from_str(index: usize, val: &str) -> impl Iterator<Item = EncodingInfo> {
27+
val.split(',')
28+
.map(str::trim)
29+
.filter(|s| !s.is_empty())
30+
.map(ContentEncoding::from)
31+
.map(move |enc| EncodingInfo::from((index, enc)))
32+
}
33+
}
34+
35+
#[cfg(test)]
36+
mod test {
37+
use super::*;
38+
39+
#[test]
40+
fn test_encoding_info_iter_from_str() {
41+
let data = "gzip, deflate, br, compress,";
42+
let result: Vec<EncodingInfo> = EncodingInfo::iter_from_str(0, data).collect();
43+
let verify = vec![
44+
EncodingInfo::from((0, ContentEncoding::Gzip)),
45+
EncodingInfo::from((0, ContentEncoding::Deflate)),
46+
EncodingInfo::from((0, ContentEncoding::Brotli)),
47+
EncodingInfo::from((0, ContentEncoding::Compress)),
48+
];
49+
assert_eq!(result, verify);
50+
}
51+
}

header-plz/src/body_headers/encoding_struct.rs

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 61 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
use mime_plz::ContentType;
22

3-
use super::{
4-
BodyHeader,
5-
content_encoding::ContentEncoding,
6-
transfer_types::{TransferType, cl_to_transfer_type, parse_and_remove_chunked},
7-
};
3+
use super::{BodyHeader, content_encoding::ContentEncoding, transfer_types::TransferType};
84
use crate::{
5+
body_headers::encoding_info::EncodingInfo,
96
const_headers::*,
107
header_map::{HeaderMap, header::Header},
118
};
@@ -38,12 +35,21 @@ use crate::{
3835
*/
3936

4037
impl From<&HeaderMap> for Option<BodyHeader> {
41-
fn from(header_map: &HeaderMap) -> Option<BodyHeader> {
38+
fn from(header_map: &HeaderMap) -> Self {
39+
let bh = BodyHeader::from(header_map);
40+
bh.sanitize()
41+
}
42+
}
43+
44+
impl From<&HeaderMap> for BodyHeader {
45+
#[inline(always)]
46+
fn from(header_map: &HeaderMap) -> BodyHeader {
4247
let mut bh = BodyHeader::default();
4348
header_map
4449
.headers()
4550
.iter()
46-
.for_each(|header| parse_body_headers(&mut bh, header));
51+
.enumerate()
52+
.for_each(|(index, header)| parse_body_headers(&mut bh, index, header));
4753

4854
// if TransferType is Unknown, and if content_encoding or transfer_encoding
4955
// or content_type is present, then set TransferType to Close
@@ -54,68 +60,50 @@ impl From<&HeaderMap> for Option<BodyHeader> {
5460
{
5561
bh.transfer_type = Some(TransferType::Close);
5662
}
57-
bh.sanitize()
63+
bh
5864
}
5965
}
6066

61-
pub fn parse_body_headers(bh: &mut BodyHeader, header: &Header) {
67+
pub fn parse_body_headers(bh: &mut BodyHeader, index: usize, header: &Header) {
6268
let key = header.key_as_str();
6369
if (key.eq_ignore_ascii_case(CONTENT_LENGTH)) && bh.transfer_type.is_none() {
64-
let transfer_type = cl_to_transfer_type(header.value_as_str());
65-
bh.transfer_type = Some(transfer_type);
70+
let transfer_type = TransferType::from_cl(header.value_as_str());
71+
let _ = bh.transfer_type.get_or_insert(transfer_type);
6672
} else if key.eq_ignore_ascii_case(TRANSFER_ENCODING) {
67-
bh.transfer_encoding = match_compression(header.value_as_str());
68-
bh.transfer_type = parse_and_remove_chunked(&mut bh.transfer_encoding);
73+
let mut einfo_iter = EncodingInfo::iter_from_str(index, header.value_as_str());
74+
bh.transfer_encoding
75+
.get_or_insert_with(Vec::new)
76+
.extend(einfo_iter);
77+
if bh.is_chunked_te() {
78+
bh.transfer_type = Some(TransferType::Chunked)
79+
}
6980
} else if key.eq_ignore_ascii_case(CONTENT_ENCODING) {
70-
bh.content_encoding = match_compression(header.value_as_str());
81+
let einfo_iter = EncodingInfo::iter_from_str(index, header.value_as_str());
82+
bh.content_encoding
83+
.get_or_insert_with(Vec::new)
84+
.extend(einfo_iter);
7185
} else if key.eq_ignore_ascii_case(CONTENT_TYPE) {
7286
if let Some((main_type, _)) = header.value_as_str().split_once('/') {
7387
bh.content_type = Some(ContentType::from(main_type));
7488
}
7589
}
7690
}
7791

78-
// Convert compression header values to Vec<ContentEncoding>.
79-
pub fn match_compression(value: &str) -> Option<Vec<ContentEncoding>> {
80-
let encoding: Vec<ContentEncoding> = value
81-
.split(',')
82-
.map(|x| x.trim())
83-
.filter_map(|x| {
84-
if x.is_empty() {
85-
None
86-
} else {
87-
Some(ContentEncoding::from(x))
88-
}
89-
})
90-
.collect();
91-
Some(encoding)
92-
}
93-
9492
#[cfg(test)]
9593
mod tests {
96-
use bytes::BytesMut;
97-
9894
use super::*;
95+
use bytes::BytesMut;
9996

100-
#[test]
101-
fn test_match_compression() {
102-
let data = "gzip, deflate, br, compress,";
103-
let result = match_compression(data);
104-
let verify = vec![
105-
ContentEncoding::Gzip,
106-
ContentEncoding::Deflate,
107-
ContentEncoding::Brotli,
108-
ContentEncoding::Compress,
109-
];
110-
assert_eq!(result, Some(verify));
97+
fn build_body_header(input: &str) -> BodyHeader {
98+
let header_map = HeaderMap::from(BytesMut::from(input));
99+
BodyHeader::from(&header_map)
111100
}
112101

102+
// ---- Content Length
113103
#[test]
114-
fn test_header_map_to_body_headers_cl_only() {
115-
let data = "Content-Length: 10\r\n\r\n";
116-
let buf = BytesMut::from(data);
117-
let header_map = HeaderMap::from(buf);
118-
let result = Option::<BodyHeader>::from(&header_map).unwrap();
104+
fn test_body_header_from_header_map_cl() {
105+
let input = "Content-Length: 10\r\n\r\n";
106+
let result = build_body_header(input);
119107
let verify = BodyHeader {
120108
transfer_type: Some(TransferType::ContentLength(10)),
121109
..Default::default()
@@ -124,69 +112,68 @@ mod tests {
124112
}
125113

126114
#[test]
127-
fn test_header_map_to_body_headers_cl_invalid() {
128-
let data = "Content-Length: invalid\r\n\r\n";
129-
let buf = BytesMut::from(data);
130-
let header_map = HeaderMap::from(buf);
115+
fn test_body_header_from_header_map_cl_invalid() {
116+
let input = "Content-Length: invalid\r\n\r\n";
117+
let result = build_body_header(input);
131118
let verify = BodyHeader {
132119
transfer_type: Some(TransferType::Close),
133120
..Default::default()
134121
};
135-
let result = Option::<BodyHeader>::from(&header_map).unwrap();
136122
assert_eq!(result, verify);
137123
}
138124

125+
// ----- chunked
139126
#[test]
140-
fn test_header_map_to_body_headers_te_chunked() {
141-
let data = "Transfer-Encoding: chunked\r\n\r\n";
142-
let buf = BytesMut::from(data);
143-
let header_map = HeaderMap::from(buf);
127+
fn test_body_header_from_header_map_chunked() {
128+
let input = "Transfer-Encoding: chunked\r\n\r\n";
129+
let result = build_body_header(input);
130+
let einfo = EncodingInfo::new(0, ContentEncoding::Chunked);
144131
let verify = BodyHeader {
132+
transfer_encoding: Some(vec![einfo]),
145133
transfer_type: Some(TransferType::Chunked),
146134
..Default::default()
147135
};
148-
let result = Option::<BodyHeader>::from(&header_map).unwrap();
149136
assert_eq!(result, verify);
150137
}
151138

139+
// ----- chunked + cl
152140
#[test]
153-
fn test_header_map_to_body_headers_content_length_and_chunked() {
154-
let data = "Content-Length: 20\r\nTransfer-Encoding: chunked\r\n\r\n";
155-
let buf = BytesMut::from(data);
156-
let header_map = HeaderMap::from(buf);
141+
fn test_body_header_from_header_map_cl_and_chunked() {
142+
let input = "Content-Length: 20\r\nTransfer-Encoding: chunked\r\n\r\n";
143+
let result = build_body_header(input);
144+
let einfo = EncodingInfo::new(1, ContentEncoding::Chunked);
157145
let verify = BodyHeader {
158146
transfer_type: Some(TransferType::Chunked),
147+
transfer_encoding: Some(vec![einfo]),
159148
..Default::default()
160149
};
161-
let result = Option::<BodyHeader>::from(&header_map).unwrap();
162150
assert_eq!(result, verify);
163151
}
164152

153+
// ----- Content type
165154
#[test]
166-
fn test_header_map_to_body_headers_ct_only() {
167-
let data = "Content-Type: application/json\r\n\r\n";
168-
let buf = BytesMut::from(data);
169-
let header_map = HeaderMap::from(buf);
155+
fn test_body_header_from_header_map_ct_only() {
156+
let input = "Content-Type: application/json\r\n\r\n";
157+
let result = build_body_header(input);
170158
let verify = BodyHeader {
171159
content_type: Some(ContentType::Application),
172160
transfer_type: Some(TransferType::Close),
173161
..Default::default()
174162
};
175-
let result = Option::<BodyHeader>::from(&header_map).unwrap();
176163
assert_eq!(result, verify);
177164
}
178165

166+
// ----- Content Encoding
179167
#[test]
180-
fn test_header_map_to_body_headers_ce_only() {
181-
let data = "Content-Encoding: gzip\r\n\r\n";
182-
let buf = BytesMut::from(data);
183-
let header_map = HeaderMap::from(buf);
168+
fn test_body_headers_from_header_map_ce_only() {
169+
let input = "Content-Encoding: gzip\r\n\r\n";
170+
let result = build_body_header(input);
171+
let einfo = EncodingInfo::new(0, ContentEncoding::Gzip);
184172
let verify = BodyHeader {
185-
content_encoding: Some(vec![ContentEncoding::Gzip]),
173+
content_encoding: Some(vec![einfo]),
186174
transfer_type: Some(TransferType::Close),
187175
..Default::default()
188176
};
189-
let result = Option::<BodyHeader>::from(&header_map).unwrap();
190177
assert_eq!(result, verify);
191178
}
192179
}

header-plz/src/body_headers/mod.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use content_encoding::ContentEncoding;
22
use mime_plz::ContentType;
33
use transfer_types::TransferType;
4+
5+
use crate::body_headers::encoding_info::EncodingInfo;
46
pub mod content_encoding;
5-
mod encoding_struct;
7+
mod encoding_info;
68
pub mod transfer_types;
79

810
mod from_header_map;
@@ -11,9 +13,9 @@ pub mod parse;
1113
#[derive(Default)]
1214
#[cfg_attr(any(test, debug_assertions), derive(Debug, PartialEq, Eq, Clone))]
1315
pub struct BodyHeader {
14-
pub content_encoding: Option<Vec<ContentEncoding>>,
16+
pub content_encoding: Option<Vec<EncodingInfo>>,
1517
pub content_type: Option<ContentType>,
16-
pub transfer_encoding: Option<Vec<ContentEncoding>>,
18+
pub transfer_encoding: Option<Vec<EncodingInfo>>,
1719
pub transfer_type: Option<TransferType>,
1820
}
1921

@@ -33,6 +35,16 @@ impl BodyHeader {
3335
pub fn content_type(&self) -> ContentType {
3436
self.content_type.map_or(ContentType::Unknown, |ct| ct)
3537
}
38+
39+
pub fn is_chunked_te(&self) -> bool {
40+
self.transfer_encoding
41+
.as_ref()
42+
.map(|list| {
43+
list.iter()
44+
.any(|ei| ei.encoding == ContentEncoding::Chunked)
45+
})
46+
.unwrap_or(false)
47+
}
3648
}
3749

3850
#[cfg(test)]
@@ -43,9 +55,9 @@ mod tests {
4355
#[test]
4456
fn test_bodyheader_sanitize_all() {
4557
let body = BodyHeader {
46-
content_encoding: Some(vec![ContentEncoding::Gzip]),
58+
content_encoding: Some(vec![EncodingInfo::from((0, ContentEncoding::Gzip))]),
4759
content_type: Some(ContentType::Application),
48-
transfer_encoding: Some(vec![ContentEncoding::Gzip]),
60+
transfer_encoding: Some(vec![EncodingInfo::from((0, ContentEncoding::Gzip))]),
4961
transfer_type: Some(TransferType::Close),
5062
};
5163
let sbody = body.clone().sanitize();

0 commit comments

Comments
 (0)