Skip to content

Commit 8b2ad20

Browse files
committed
Hashing with hashify + cosmetic fixes
1 parent bc6a5b1 commit 8b2ad20

21 files changed

+408
-1948
lines changed

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
mail-parser 0.10.0
2+
================================
3+
- Perfect hashing using `hashify` crate rather than static `gperf` generated code.
4+
- Added `DkimSignature`, `ArcAuthenticationResults`, `ArcMessageSignature` and `ArcSeal` headers.
5+
- `HeaderName` is non-exhaustive.
6+
- Parse obsolete timezones (#95).
7+
- Fix: Folding ws between "Content-Type:" and "plain/text" leads to empty header (#96).
8+
- Fix: Multiline quoted continuations (closes #92).
9+
- Fix: Deserialize (#93).
10+
- Retain mbox IO errors (#91).
11+
- Hide concrete type behind impl type (#94).
12+
- Removed `ludicrous` feature, the Rust compiler is smart enough to optimize array lookups.
13+
114
mail-parser 0.9.4
215
================================
316
- Flexible parsing of charset names (#85).

Cargo.toml

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "mail-parser"
33
description = "Fast and robust e-mail parsing library for Rust"
4-
version = "0.9.4"
4+
version = "0.10.0"
55
edition = "2021"
66
authors = [ "Stalwart Labs <[email protected]>"]
77
license = "Apache-2.0 OR MIT"
@@ -13,6 +13,7 @@ readme = "README.md"
1313
resolver = "2"
1414

1515
[dependencies]
16+
hashify = { path = "../hashify" }
1617
encoding_rs = { version = "0.8", optional = true }
1718
serde = { version = "1.0", features = ["derive"], optional = true }
1819

@@ -27,7 +28,9 @@ chrono = "0.4"
2728
default = ["full_encoding"]
2829
full_encoding = ["encoding_rs"]
2930
serde_support = ["serde"]
30-
ludicrous_mode = []
3131

3232
[profile.bench]
3333
debug = true
34+
35+
[lib]
36+
doctest = false
File renamed without changes.
File renamed without changes.

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,4 @@ at your option.
277277

278278
## Copyright
279279

280-
Copyright (C) 2020-2022, Stalwart Labs Ltd.
280+
Copyright (C) 2020, Stalwart Labs LLC

src/core/address.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl<'x> Address<'x> {
7373
}
7474

7575
/// Returns an iterator over the addresses in the list, or the addresses in the groups.
76-
pub fn iter(&self) -> Box<dyn DoubleEndedIterator<Item = &Addr<'x>> + '_> {
76+
pub fn iter(&self) -> Box<dyn DoubleEndedIterator<Item = &Addr<'x>> + '_ + Sync + Send> {
7777
match self {
7878
Address::List(list) => Box::new(list.iter()),
7979
Address::Group(group) => {
@@ -88,13 +88,13 @@ impl<'x> Address<'x> {
8888
Address::List(list) => list.iter().any(|a| {
8989
a.address
9090
.as_ref()
91-
.map_or(false, |a| a.eq_ignore_ascii_case(addr))
91+
.is_some_and(|a| a.eq_ignore_ascii_case(addr))
9292
}),
9393
Address::Group(group) => group.iter().any(|group| {
9494
group.addresses.iter().any(|a| {
9595
a.address
9696
.as_ref()
97-
.map_or(false, |a| a.eq_ignore_ascii_case(addr))
97+
.is_some_and(|a| a.eq_ignore_ascii_case(addr))
9898
})
9999
}),
100100
}

src/core/header.rs

+28-4
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,10 @@ impl<'x> HeaderValue<'x> {
156156
}
157157
}
158158

159-
pub fn as_text_list(&self) -> Option<Vec<&str>> {
159+
pub fn as_text_list(&self) -> Option<&[Cow<'x, str>]> {
160160
match *self {
161-
HeaderValue::Text(ref s) => Some(vec![s.as_ref()]),
162-
HeaderValue::TextList(ref l) => Some(l.iter().map(|l| l.as_ref()).collect()),
161+
HeaderValue::Text(ref s) => Some(std::slice::from_ref(s)),
162+
HeaderValue::TextList(ref l) => Some(l.as_slice()),
163163
_ => None,
164164
}
165165
}
@@ -321,6 +321,10 @@ impl HeaderName<'_> {
321321
HeaderName::ListPost => HeaderName::ListPost,
322322
HeaderName::ListSubscribe => HeaderName::ListSubscribe,
323323
HeaderName::ListUnsubscribe => HeaderName::ListUnsubscribe,
324+
HeaderName::ArcAuthenticationResults => HeaderName::ArcAuthenticationResults,
325+
HeaderName::ArcMessageSignature => HeaderName::ArcMessageSignature,
326+
HeaderName::ArcSeal => HeaderName::ArcSeal,
327+
HeaderName::DkimSignature => HeaderName::DkimSignature,
324328
}
325329
}
326330

@@ -364,6 +368,10 @@ impl HeaderName<'_> {
364368
HeaderName::ListPost => HeaderName::ListPost,
365369
HeaderName::ListSubscribe => HeaderName::ListSubscribe,
366370
HeaderName::ListUnsubscribe => HeaderName::ListUnsubscribe,
371+
HeaderName::ArcAuthenticationResults => HeaderName::ArcAuthenticationResults,
372+
HeaderName::ArcMessageSignature => HeaderName::ArcMessageSignature,
373+
HeaderName::ArcSeal => HeaderName::ArcSeal,
374+
HeaderName::DkimSignature => HeaderName::DkimSignature,
367375
}
368376
}
369377

@@ -420,6 +428,10 @@ impl HeaderName<'_> {
420428
HeaderName::ListPost => "List-Post",
421429
HeaderName::ListSubscribe => "List-Subscribe",
422430
HeaderName::ListUnsubscribe => "List-Unsubscribe",
431+
HeaderName::ArcAuthenticationResults => "ARC-Authentication-Results",
432+
HeaderName::ArcMessageSignature => "ARC-Message-Signature",
433+
HeaderName::ArcSeal => "ARC-Seal",
434+
HeaderName::DkimSignature => "DKIM-Signature",
423435
HeaderName::Other(_) => "",
424436
}
425437
}
@@ -463,6 +475,10 @@ impl HeaderName<'_> {
463475
HeaderName::ListPost => "List-Post".len(),
464476
HeaderName::ListSubscribe => "List-Subscribe".len(),
465477
HeaderName::ListUnsubscribe => "List-Unsubscribe".len(),
478+
HeaderName::ArcAuthenticationResults => "ARC-Authentication-Results".len(),
479+
HeaderName::ArcMessageSignature => "ARC-Message-Signature".len(),
480+
HeaderName::ArcSeal => "ARC-Seal".len(),
481+
HeaderName::DkimSignature => "DKIM-Signature".len(),
466482
HeaderName::Other(other) => other.len(),
467483
}
468484
}
@@ -530,6 +546,10 @@ impl HeaderName<'_> {
530546
HeaderName::ListSubscribe => 35,
531547
HeaderName::ListUnsubscribe => 36,
532548
HeaderName::Other(_) => 37,
549+
HeaderName::ArcAuthenticationResults => 38,
550+
HeaderName::ArcMessageSignature => 39,
551+
HeaderName::ArcSeal => 40,
552+
HeaderName::DkimSignature => 41,
533553
}
534554
}
535555
}
@@ -804,7 +824,7 @@ impl<'x> ContentType<'x> {
804824
pub fn has_attribute(&self, name: &str) -> bool {
805825
self.attributes
806826
.as_ref()
807-
.map_or(false, |attr| attr.iter().any(|(key, _)| key == name))
827+
.is_some_and(|attr| attr.iter().any(|(key, _)| key == name))
808828
}
809829

810830
/// Returns ```true``` if the Content-Disposition type is "attachment"
@@ -1007,6 +1027,10 @@ impl From<u8> for HeaderName<'_> {
10071027
34 => HeaderName::ListPost,
10081028
35 => HeaderName::ListSubscribe,
10091029
36 => HeaderName::ListUnsubscribe,
1030+
38 => HeaderName::ArcAuthenticationResults,
1031+
39 => HeaderName::ArcMessageSignature,
1032+
40 => HeaderName::ArcSeal,
1033+
41 => HeaderName::DkimSignature,
10101034
_ => HeaderName::Other("".into()),
10111035
}
10121036
}

src/core/message.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ impl<'x> Message<'x> {
9393
pub fn header_values(
9494
&self,
9595
name: impl Into<HeaderName<'x>>,
96-
) -> impl Iterator<Item = &HeaderValue<'x>> {
96+
) -> impl Iterator<Item = &HeaderValue<'x>> + Sync + Send {
9797
let name = name.into();
9898
self.parts[0].headers.iter().filter_map(move |header| {
9999
if header.name == name {
@@ -105,7 +105,7 @@ impl<'x> Message<'x> {
105105
}
106106

107107
/// Returns all headers in raw format
108-
pub fn headers_raw(&self) -> impl Iterator<Item = (&str, &str)> {
108+
pub fn headers_raw(&self) -> impl Iterator<Item = (&str, &str)> + Sync + Send {
109109
self.parts[0].headers.iter().filter_map(move |header| {
110110
Some((
111111
header.name.as_str(),
@@ -449,17 +449,17 @@ impl<'x> Message<'x> {
449449
}
450450

451451
/// Returns an Interator over the text body parts
452-
pub fn text_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> {
452+
pub fn text_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
453453
BodyPartIterator::new(self, &self.text_body)
454454
}
455455

456456
/// Returns an Interator over the HTML body parts
457-
pub fn html_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> {
457+
pub fn html_bodies(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
458458
BodyPartIterator::new(self, &self.html_body)
459459
}
460460

461461
/// Returns an Interator over the attachments
462-
pub fn attachments(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> {
462+
pub fn attachments(&self) -> impl Iterator<Item = &'_ MessagePart<'_>> + Sync + Send {
463463
AttachmentIterator::new(self)
464464
}
465465

src/decoders/base64.rs

-66
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,6 @@ pub fn base64_decode_stream<'x>(
2929
let mut buf = Vec::with_capacity(stream_len / 4 * 3);
3030

3131
for &ch in stream {
32-
#[cfg(feature = "ludicrous_mode")]
33-
let val = unsafe {
34-
*BASE64_MAP
35-
.get_unchecked(byte_count as usize)
36-
.get_unchecked(ch as usize)
37-
};
38-
#[cfg(not(feature = "ludicrous_mode"))]
3932
let val = BASE64_MAP[byte_count as usize][ch as usize];
4033

4134
if val < 0x01ffffff {
@@ -47,32 +40,17 @@ pub fn base64_decode_stream<'x>(
4740
chunk |= val;
4841

4942
if byte_count == 0 {
50-
#[cfg(feature = "ludicrous_mode")]
51-
unsafe {
52-
buf.extend_from_slice(chunk.to_le_bytes().get_unchecked(0..3));
53-
}
54-
#[cfg(not(feature = "ludicrous_mode"))]
5543
buf.extend_from_slice(&chunk.to_le_bytes()[0..3]);
5644
}
5745
}
5846
} else {
5947
match ch {
6048
b'=' => match byte_count {
6149
1 | 2 => {
62-
#[cfg(feature = "ludicrous_mode")]
63-
unsafe {
64-
buf.push(*chunk.to_le_bytes().get_unchecked(0));
65-
}
66-
#[cfg(not(feature = "ludicrous_mode"))]
6750
buf.push(chunk.to_le_bytes()[0]);
6851
byte_count = 0;
6952
}
7053
3 => {
71-
#[cfg(feature = "ludicrous_mode")]
72-
unsafe {
73-
buf.extend_from_slice(chunk.to_le_bytes().get_unchecked(0..2));
74-
}
75-
#[cfg(not(feature = "ludicrous_mode"))]
7654
buf.extend_from_slice(&chunk.to_le_bytes()[0..2]);
7755
byte_count = 0;
7856
}
@@ -103,13 +81,6 @@ impl<'x> MessageStream<'x> {
10381
self.checkpoint();
10482

10583
while let Some(&ch) = self.next() {
106-
#[cfg(feature = "ludicrous_mode")]
107-
let val = unsafe {
108-
*BASE64_MAP
109-
.get_unchecked(byte_count as usize)
110-
.get_unchecked(ch as usize)
111-
};
112-
#[cfg(not(feature = "ludicrous_mode"))]
11384
let val = BASE64_MAP[byte_count as usize][ch as usize];
11485

11586
if val < 0x01ffffff {
@@ -121,32 +92,17 @@ impl<'x> MessageStream<'x> {
12192
chunk |= val;
12293

12394
if byte_count == 0 {
124-
#[cfg(feature = "ludicrous_mode")]
125-
unsafe {
126-
buf.extend_from_slice(chunk.to_le_bytes().get_unchecked(0..3));
127-
}
128-
#[cfg(not(feature = "ludicrous_mode"))]
12995
buf.extend_from_slice(&chunk.to_le_bytes()[0..3]);
13096
}
13197
}
13298
} else {
13399
match ch {
134100
b'=' => match byte_count {
135101
1 | 2 => {
136-
#[cfg(feature = "ludicrous_mode")]
137-
unsafe {
138-
buf.push(*chunk.to_le_bytes().get_unchecked(0));
139-
}
140-
#[cfg(not(feature = "ludicrous_mode"))]
141102
buf.push(chunk.to_le_bytes()[0]);
142103
byte_count = 0;
143104
}
144105
3 => {
145-
#[cfg(feature = "ludicrous_mode")]
146-
unsafe {
147-
buf.extend_from_slice(chunk.to_le_bytes().get_unchecked(0..2));
148-
}
149-
#[cfg(not(feature = "ludicrous_mode"))]
150106
buf.extend_from_slice(&chunk.to_le_bytes()[0..2]);
151107
byte_count = 0;
152108
}
@@ -215,20 +171,10 @@ impl<'x> MessageStream<'x> {
215171
b'=' => {
216172
match byte_count {
217173
1 | 2 => {
218-
#[cfg(feature = "ludicrous_mode")]
219-
unsafe {
220-
buf.push(*chunk.to_le_bytes().get_unchecked(0));
221-
}
222-
#[cfg(not(feature = "ludicrous_mode"))]
223174
buf.push(chunk.to_le_bytes()[0]);
224175
byte_count = 0;
225176
}
226177
3 => {
227-
#[cfg(feature = "ludicrous_mode")]
228-
unsafe {
229-
buf.extend_from_slice(chunk.to_le_bytes().get_unchecked(0..2));
230-
}
231-
#[cfg(not(feature = "ludicrous_mode"))]
232178
buf.extend_from_slice(&chunk.to_le_bytes()[0..2]);
233179
byte_count = 0;
234180
}
@@ -253,13 +199,6 @@ impl<'x> MessageStream<'x> {
253199
}
254200
b' ' | b'\t' | b'\r' => (),
255201
_ => {
256-
#[cfg(feature = "ludicrous_mode")]
257-
let val = unsafe {
258-
*BASE64_MAP
259-
.get_unchecked(byte_count as usize)
260-
.get_unchecked(ch as usize)
261-
};
262-
#[cfg(not(feature = "ludicrous_mode"))]
263202
let val = BASE64_MAP[byte_count as usize][ch as usize];
264203

265204
if val < 0x01ffffff {
@@ -271,11 +210,6 @@ impl<'x> MessageStream<'x> {
271210
chunk |= val;
272211

273212
if byte_count == 0 {
274-
#[cfg(feature = "ludicrous_mode")]
275-
unsafe {
276-
buf.extend_from_slice(chunk.to_le_bytes().get_unchecked(0..3));
277-
}
278-
#[cfg(not(feature = "ludicrous_mode"))]
279213
buf.extend_from_slice(&chunk.to_le_bytes()[0..3]);
280214
}
281215
}

0 commit comments

Comments
 (0)