Skip to content

Commit 6b76cfe

Browse files
committed
feat: Implement UTF8={ACCEPT,ONLY}
1 parent f00c147 commit 6b76cfe

File tree

18 files changed

+234
-40
lines changed

18 files changed

+234
-40
lines changed

imap-codec/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ ext_id = ["imap-types/ext_id"]
7474
ext_login_referrals = ["imap-types/ext_login_referrals"]
7575
ext_mailbox_referrals = ["imap-types/ext_mailbox_referrals"]
7676
ext_metadata = ["imap-types/ext_metadata"]
77+
ext_utf8 = ["imap-types/ext_utf8"]
7778
# </Forward to imap-types>
7879

7980
[dependencies]

imap-codec/fuzz/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ ext_id = ["imap-codec/ext_id"]
2424
ext_login_referrals = ["imap-codec/ext_login_referrals"]
2525
ext_mailbox_referrals = ["imap-codec/ext_mailbox_referrals"]
2626
ext_metadata = ["imap-codec/ext_metadata"]
27+
ext_utf8 = ["imap-codec/ext_utf8"]
2728

2829
# IMAP quirks
2930
quirk_crlf_relaxed = ["imap-codec/quirk_crlf_relaxed"]
@@ -37,6 +38,7 @@ ext = [
3738
#"ext_login_referrals",
3839
#"ext_mailbox_referrals",
3940
"ext_metadata",
41+
"ext_utf8",
4042
]
4143
# Enable `Debug`-printing during parsing. This is useful to analyze crashes.
4244
debug = []

imap-codec/src/codec/decode.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
//!
1010
//! Have a look at the [parse_command](https://github.com/duesee/imap-codec/blob/main/imap-codec/examples/parse_command.rs) example to see how a real-world application could decode IMAP.
1111
12-
use std::num::{ParseIntError, TryFromIntError};
12+
use std::{
13+
num::{ParseIntError, TryFromIntError},
14+
str::Utf8Error,
15+
};
1316

1417
use imap_types::{
1518
IntoStatic,
@@ -50,6 +53,7 @@ pub(crate) enum IMAPErrorKind<'a> {
5053
},
5154
BadNumber,
5255
BadBase64,
56+
BadUtf8,
5357
BadDateTime,
5458
LiteralContainsNull,
5559
RecursionLimitExceeded,
@@ -99,6 +103,15 @@ impl<I> FromExternalError<I, base64::DecodeError> for IMAPParseError<'_, I> {
99103
}
100104
}
101105

106+
impl<I> FromExternalError<I, Utf8Error> for IMAPParseError<'_, I> {
107+
fn from_external_error(input: I, _: ErrorKind, _: Utf8Error) -> Self {
108+
Self {
109+
input,
110+
kind: IMAPErrorKind::BadUtf8,
111+
}
112+
}
113+
}
114+
102115
/// Decoder.
103116
///
104117
/// Implemented for types that know how to decode a specific IMAP message. See [implementors](trait.Decoder.html#implementors).

imap-codec/src/codec/encode.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,8 @@ impl EncodeIntoContext for IString<'_> {
788788
match self {
789789
Self::Literal(val) => val.encode_ctx(ctx),
790790
Self::Quoted(val) => val.encode_ctx(ctx),
791+
#[cfg(feature = "ext_utf8")]
792+
Self::QuotedUtf8(val) => val.encode_ctx(ctx),
791793
}
792794
}
793795
}

imap-codec/src/core.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ use nom::{
2727
};
2828

2929
use crate::decode::{IMAPErrorKind, IMAPParseError, IMAPResult};
30+
#[cfg(feature = "ext_utf8")]
31+
use crate::extensions::utf8::quoted_utf8;
3032

3133
// ----- number -----
3234

@@ -71,7 +73,13 @@ pub(crate) fn nz_number(input: &[u8]) -> IMAPResult<&[u8], NonZeroU32> {
7173

7274
/// `string = quoted / literal`
7375
pub(crate) fn string(input: &[u8]) -> IMAPResult<&[u8], IString> {
74-
alt((map(quoted, IString::Quoted), map(literal, IString::Literal)))(input)
76+
alt((
77+
map(quoted, IString::Quoted),
78+
// quoted_utf8 must come after quoted but before literal.
79+
#[cfg(feature = "ext_utf8")]
80+
map(quoted_utf8, IString::QuotedUtf8),
81+
map(literal, IString::Literal),
82+
))(input)
7583
}
7684

7785
/// `quoted = DQUOTE *QUOTED-CHAR DQUOTE`

imap-codec/src/extensions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ pub mod sort;
1515
pub mod thread;
1616
pub mod uidplus;
1717
pub mod unselect;
18+
#[cfg(feature = "ext_utf8")]
19+
pub mod utf8;

imap-codec/src/extensions/enable.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,14 @@ impl EncodeIntoContext for CapabilityEnable<'_> {
7474

7575
#[cfg(test)]
7676
mod tests {
77-
use imap_types::{
78-
command::Command,
79-
core::Atom,
80-
extensions::enable::{CapabilityEnable, Utf8Kind},
81-
};
77+
#[cfg(feature = "ext_utf8")]
78+
use imap_types::extensions::utf8::Utf8Kind;
79+
use imap_types::{command::Command, core::Atom, extensions::enable::CapabilityEnable};
8280

8381
use super::*;
8482
use crate::testing::kat_inverse_command;
8583

84+
#[cfg(feature = "ext_utf8")]
8685
#[test]
8786
fn test_parse_enable() {
8887
let got = enable(b"enable UTF8=ACCEPT\r\n").unwrap().1;
@@ -95,6 +94,7 @@ mod tests {
9594
#[test]
9695
fn test_kat_inverse_command_enable() {
9796
kat_inverse_command(&[
97+
#[cfg(feature = "ext_utf8")]
9898
(
9999
b"A ENABLE UTF8=ONLY\r\n".as_ref(),
100100
b"".as_ref(),
@@ -104,6 +104,7 @@ mod tests {
104104
)
105105
.unwrap(),
106106
),
107+
#[cfg(feature = "ext_utf8")]
107108
(
108109
b"A ENABLE UTF8=ACCEPT\r\n?",
109110
b"?".as_ref(),

imap-codec/src/extensions/utf8.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use std::{io::Write, str::from_utf8};
2+
3+
use abnf_core::streaming::dquote;
4+
use imap_types::{
5+
extensions::utf8::QuotedUtf8,
6+
utils::{escape_quoted, indicators::is_quoted_specials, unescape_quoted},
7+
};
8+
use nom::{
9+
bytes::streaming::{escaped, take_while1},
10+
character::streaming::one_of,
11+
combinator::map_res,
12+
sequence::tuple,
13+
};
14+
15+
use crate::{
16+
decode::IMAPResult,
17+
encode::{EncodeContext, EncodeIntoContext},
18+
};
19+
20+
impl EncodeIntoContext for QuotedUtf8<'_> {
21+
fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
22+
write!(ctx, "\"{}\"", escape_quoted(self.0.as_ref()))
23+
}
24+
}
25+
26+
/// ```abnf
27+
/// ; QUOTED-CHAR is not modified, as it will affect other RFC 3501 ABNF non-terminals.
28+
/// quoted =/ DQUOTE *uQUOTED-CHAR DQUOTE
29+
///
30+
/// uQUOTED-CHAR = QUOTED-CHAR / UTF8-2 / UTF8-3 / UTF8-4
31+
/// UTF8-2 = <Defined in Section 4 of RFC 3629>
32+
/// UTF8-3 = <Defined in Section 4 of RFC 3629>
33+
/// UTF8-4 = <Defined in Section 4 of RFC 3629>
34+
/// ```
35+
///
36+
/// This function only allocates a new String, when needed, i.e. when
37+
/// quoted chars need to be replaced.
38+
pub(crate) fn quoted_utf8(input: &[u8]) -> IMAPResult<&[u8], QuotedUtf8> {
39+
let mut parser = tuple((
40+
dquote,
41+
map_res(
42+
escaped(
43+
take_while1(|c| !is_quoted_specials(c)),
44+
'\\',
45+
one_of("\\\""),
46+
),
47+
from_utf8,
48+
),
49+
dquote,
50+
));
51+
52+
let (remaining, (_, quoted_utf8, _)) = parser(input)?;
53+
54+
Ok((remaining, QuotedUtf8(unescape_quoted(quoted_utf8))))
55+
}
56+
57+
#[cfg(test)]
58+
mod test {
59+
use imap_types::extensions::utf8::QuotedUtf8;
60+
61+
use super::quoted_utf8;
62+
63+
#[test]
64+
fn test_quoted_utf8() {
65+
assert_eq!(
66+
(b"".as_ref(), QuotedUtf8("äö¹".into())),
67+
quoted_utf8("\"äö¹\"".as_bytes()).unwrap()
68+
);
69+
}
70+
}

imap-types/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ext_id = []
2626
ext_login_referrals = []
2727
ext_mailbox_referrals = []
2828
ext_metadata = []
29+
ext_utf8 = []
2930

3031
[dependencies]
3132
arbitrary = { version = "1.4.2", optional = true, default-features = false, features = ["derive"] }

imap-types/fuzz/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ ext_id = ["imap-types/ext_id"]
2020
ext_login_referrals = ["imap-types/ext_login_referrals"]
2121
ext_mailbox_referrals = ["imap-types/ext_mailbox_referrals"]
2222
ext_metadata = ["imap-types/ext_metadata"]
23+
ext_utf8 = ["imap-types/ext_utf8"]
2324
# </Forward to imap-types>
2425

2526
# Use (most) IMAP extensions.
@@ -30,6 +31,7 @@ ext = [
3031
#"ext_login_referrals",
3132
#"ext_mailbox_referrals",
3233
"ext_metadata",
34+
"ext_utf8",
3335
]
3436
# Enable `Debug`-printing during parsing. This is useful to analyze crashes.
3537
debug = []

0 commit comments

Comments
 (0)