diff --git a/imap-proto/src/parser/rfc3501/body.rs b/imap-proto/src/parser/rfc3501/body.rs index 1bdcf7d..618f749 100644 --- a/imap-proto/src/parser/rfc3501/body.rs +++ b/imap-proto/src/parser/rfc3501/body.rs @@ -7,6 +7,7 @@ use nom::{ sequence::{delimited, preceded, tuple}, IResult, }; +use std::borrow::Cow; use crate::{parser::core::*, types::*}; @@ -65,7 +66,7 @@ pub fn msg_att_body_section(i: &[u8]) -> IResult<&[u8], AttributeValue> { |(_, section, index, _, data)| AttributeValue::BodySection { section, index, - data, + data: data.map(Cow::Borrowed), }, )(i) } diff --git a/imap-proto/src/parser/rfc3501/body_structure.rs b/imap-proto/src/parser/rfc3501/body_structure.rs index 72a48d5..5a044cc 100644 --- a/imap-proto/src/parser/rfc3501/body_structure.rs +++ b/imap-proto/src/parser/rfc3501/body_structure.rs @@ -7,6 +7,7 @@ use nom::{ sequence::{delimited, preceded, tuple}, IResult, }; +use std::borrow::Cow; use crate::{ parser::{core::*, rfc3501::envelope}, @@ -35,8 +36,8 @@ fn body_fields(i: &[u8]) -> IResult<&[u8], BodyFields> { i, BodyFields { param, - id, - description, + id: id.map(Cow::Borrowed), + description: description.map(Cow::Borrowed), transfer_encoding, octets, }, @@ -60,10 +61,10 @@ fn body_ext_1part(i: &[u8]) -> IResult<&[u8], BodyExt1Part> { Ok(( i, BodyExt1Part { - md5, + md5: md5.map(Cow::Borrowed), disposition, language, - location, + location: location.map(Cow::Borrowed), extension, }, )) @@ -88,7 +89,7 @@ fn body_ext_mpart(i: &[u8]) -> IResult<&[u8], BodyExtMPart> { param, disposition, language, - location, + location: location.map(Cow::Borrowed), extension, }, )) @@ -109,15 +110,20 @@ fn body_encoding(i: &[u8]) -> IResult<&[u8], ContentEncoding> { )), char('"'), ), - map(string_utf8, |enc| ContentEncoding::Other(enc)), + map(string_utf8, |enc| { + ContentEncoding::Other(Cow::Borrowed(enc)) + }), ))(i) } -fn body_lang(i: &[u8]) -> IResult<&[u8], Option>> { +fn body_lang(i: &[u8]) -> IResult<&[u8], Option>>> { alt(( // body language seems to refer to RFC 3066 language tags, which should be ASCII-only - map(nstring_utf8, |v| v.map(|s| vec![s])), - map(parenthesized_nonempty_list(string_utf8), Option::from), + map(nstring_utf8, |v| v.map(|s| vec![Cow::Borrowed(s)])), + map( + parenthesized_nonempty_list(map(string_utf8, Cow::Borrowed)), + Option::from, + ), ))(i) } @@ -127,7 +133,7 @@ fn body_param(i: &[u8]) -> IResult<&[u8], BodyParams> { map( parenthesized_nonempty_list(map( tuple((string_utf8, tag(" "), string_utf8)), - |(key, _, val)| (key, val), + |(key, _, val)| (Cow::Borrowed(key), Cow::Borrowed(val)), )), Option::from, ), @@ -139,7 +145,7 @@ fn body_extension(i: &[u8]) -> IResult<&[u8], BodyExtension> { map(number, BodyExtension::Num), // Cannot find documentation on character encoding for body extension values. // So far, assuming UTF-8 seems fine, please report if you run into issues here. - map(nstring_utf8, BodyExtension::Str), + map(nstring_utf8, |v| BodyExtension::Str(v.map(Cow::Borrowed))), map( parenthesized_nonempty_list(body_extension), BodyExtension::List, @@ -152,7 +158,12 @@ fn body_disposition(i: &[u8]) -> IResult<&[u8], Option> { map(nil, |_| None), paren_delimited(map( tuple((string_utf8, tag(" "), body_param)), - |(ty, _, params)| Some(ContentDisposition { ty, params }), + |(ty, _, params)| { + Some(ContentDisposition { + ty: Cow::Borrowed(ty), + params, + }) + }, )), ))(i) } @@ -170,8 +181,8 @@ fn body_type_basic(i: &[u8]) -> IResult<&[u8], BodyStructure> { |(ty, _, subtype, _, fields, ext)| BodyStructure::Basic { common: BodyContentCommon { ty: ContentType { - ty, - subtype, + ty: Cow::Borrowed(ty), + subtype: Cow::Borrowed(subtype), params: fields.param, }, disposition: ext.disposition, @@ -205,8 +216,8 @@ fn body_type_text(i: &[u8]) -> IResult<&[u8], BodyStructure> { |(_, _, subtype, _, fields, _, lines, ext)| BodyStructure::Text { common: BodyContentCommon { ty: ContentType { - ty: "TEXT", - subtype, + ty: Cow::Borrowed("TEXT"), + subtype: Cow::Borrowed(subtype), params: fields.param, }, disposition: ext.disposition, @@ -243,8 +254,8 @@ fn body_type_message(i: &[u8]) -> IResult<&[u8], BodyStructure> { |(_, _, fields, _, envelope, _, body, _, lines, ext)| BodyStructure::Message { common: BodyContentCommon { ty: ContentType { - ty: "MESSAGE", - subtype: "RFC822", + ty: Cow::Borrowed("MESSAGE"), + subtype: Cow::Borrowed("RFC822"), params: fields.param, }, disposition: ext.disposition, @@ -272,8 +283,8 @@ fn body_type_multipart(i: &[u8]) -> IResult<&[u8], BodyStructure> { |(bodies, _, subtype, ext)| BodyStructure::Multipart { common: BodyContentCommon { ty: ContentType { - ty: "MULTIPART", - subtype, + ty: Cow::Borrowed("MULTIPART"), + subtype: Cow::Borrowed(subtype), params: ext.param, }, disposition: ext.disposition, @@ -310,9 +321,10 @@ mod tests { // body-fld-param SP body-fld-id SP body-fld-desc SP body-fld-enc SP body-fld-octets const BODY_FIELDS: &str = r#"("foo" "bar") "id" "desc" "7BIT" 1337"#; - const BODY_FIELD_PARAM_PAIR: (&str, &str) = ("foo", "bar"); - const BODY_FIELD_ID: Option<&str> = Some("id"); - const BODY_FIELD_DESC: Option<&str> = Some("desc"); + const BODY_FIELD_PARAM_PAIR: (Cow<'_, str>, Cow<'_, str>) = + (Cow::Borrowed("foo"), Cow::Borrowed("bar")); + const BODY_FIELD_ID: Option> = Some(Cow::Borrowed("id")); + const BODY_FIELD_DESC: Option> = Some(Cow::Borrowed("desc")); const BODY_FIELD_ENC: ContentEncoding = ContentEncoding::SevenBit; const BODY_FIELD_OCTETS: u32 = 1337; @@ -322,8 +334,8 @@ mod tests { BodyStructure::Text { common: BodyContentCommon { ty: ContentType { - ty: "TEXT", - subtype: "PLAIN", + ty: Cow::Borrowed("TEXT"), + subtype: Cow::Borrowed("PLAIN"), params: Some(vec![BODY_FIELD_PARAM_PAIR]), }, disposition: None, @@ -350,7 +362,7 @@ mod tests { assert_matches!( body_param(br#"("foo" "bar")"#), Ok((EMPTY, Some(param))) => { - assert_eq!(param, vec![("foo", "bar")]); + assert_eq!(param, vec![(Cow::Borrowed("foo"), Cow::Borrowed("bar"))]); } ); } @@ -378,7 +390,7 @@ mod tests { fn test_body_extension_data() { assert_matches!( body_extension(br#""blah""#), - Ok((EMPTY, BodyExtension::Str(Some("blah")))) + Ok((EMPTY, BodyExtension::Str(Some(Cow::Borrowed("blah"))))) ); assert_matches!( @@ -389,7 +401,7 @@ mod tests { assert_matches!( body_extension(br#"("hello")"#), Ok((EMPTY, BodyExtension::List(list))) => { - assert_eq!(list, vec![BodyExtension::Str(Some("hello"))]); + assert_eq!(list, vec![BodyExtension::Str(Some(Cow::Borrowed("hello")))]); } ); @@ -409,9 +421,9 @@ mod tests { body_disposition(br#"("attachment" ("FILENAME" "pages.pdf"))"#), Ok((EMPTY, Some(disposition))) => { assert_eq!(disposition, ContentDisposition { - ty: "attachment", + ty: Cow::Borrowed("attachment"), params: Some(vec![ - ("FILENAME", "pages.pdf") + (Cow::Borrowed("FILENAME"), Cow::Borrowed("pages.pdf")) ]) }); } @@ -453,13 +465,13 @@ mod tests { assert_eq!(basic, BodyStructure::Basic { common: BodyContentCommon { ty: ContentType { - ty: "APPLICATION", - subtype: "PDF", - params: Some(vec![("NAME", "pages.pdf")]) + ty: Cow::Borrowed("APPLICATION"), + subtype: Cow::Borrowed("PDF"), + params: Some(vec![(Cow::Borrowed("NAME"), Cow::Borrowed("pages.pdf"))]) }, disposition: Some(ContentDisposition { - ty: "attachment", - params: Some(vec![("FILENAME", "pages.pdf")]) + ty: Cow::Borrowed("attachment"), + params: Some(vec![(Cow::Borrowed("FILENAME"), Cow::Borrowed("pages.pdf"))]) }), language: None, location: None, @@ -507,8 +519,8 @@ mod tests { assert_eq!(multipart, BodyStructure::Multipart { common: BodyContentCommon { ty: ContentType { - ty: "MULTIPART", - subtype: "ALTERNATIVE", + ty: Cow::Borrowed("MULTIPART"), + subtype: Cow::Borrowed("ALTERNATIVE"), params: None }, language: None, diff --git a/imap-proto/src/parser/rfc3501/mod.rs b/imap-proto/src/parser/rfc3501/mod.rs index 942a2fa..4e2991e 100644 --- a/imap-proto/src/parser/rfc3501/mod.rs +++ b/imap-proto/src/parser/rfc3501/mod.rs @@ -4,6 +4,7 @@ //! INTERNET MESSAGE ACCESS PROTOCOL //! +use std::borrow::Cow; use std::str::from_utf8; use nom::{ @@ -72,7 +73,7 @@ fn flag(i: &[u8]) -> IResult<&[u8], &str> { alt((flag_extension, atom))(i) } -fn flag_list(i: &[u8]) -> IResult<&[u8], Vec<&str>> { +fn flag_list(i: &[u8]) -> IResult<&[u8], Vec>> { // Correct code is // parenthesized_list(flag)(i) // @@ -80,7 +81,7 @@ fn flag_list(i: &[u8]) -> IResult<&[u8], Vec<&str>> { // * FLAGS (\Answered \Flagged \Deleted \Seen \Draft \*) // // As a workaround, "\*" is allowed here. - parenthesized_list(flag_perm)(i) + parenthesized_list(map(flag_perm, Cow::Borrowed))(i) } fn flag_perm(i: &[u8]) -> IResult<&[u8], &str> { @@ -97,7 +98,7 @@ fn resp_text_code_badcharset(i: &[u8]) -> IResult<&[u8], ResponseCode> { tag_no_case(b"BADCHARSET"), opt(preceded( tag(b" "), - parenthesized_nonempty_list(astring_utf8), + parenthesized_nonempty_list(map(astring_utf8, Cow::Borrowed)), )), ), ResponseCode::BadCharset, @@ -116,7 +117,7 @@ fn resp_text_code_permanent_flags(i: &[u8]) -> IResult<&[u8], ResponseCode> { map( preceded( tag_no_case(b"PERMANENTFLAGS "), - parenthesized_list(flag_perm), + parenthesized_list(map(flag_perm, Cow::Borrowed)), ), ResponseCode::PermanentFlags, )(i) @@ -188,8 +189,11 @@ fn resp_text_code(i: &[u8]) -> IResult<&[u8], ResponseCode> { fn capability(i: &[u8]) -> IResult<&[u8], Capability> { alt(( map(tag_no_case(b"IMAP4rev1"), |_| Capability::Imap4rev1), - map(preceded(tag_no_case(b"AUTH="), atom), Capability::Auth), - map(atom, Capability::Atom), + map( + map(preceded(tag_no_case(b"AUTH="), atom), Cow::Borrowed), + Capability::Auth, + ), + map(map(atom, Cow::Borrowed), Capability::Atom), ))(i) } @@ -240,7 +244,7 @@ fn mailbox_data_exists(i: &[u8]) -> IResult<&[u8], MailboxDatum> { } #[allow(clippy::type_complexity)] -fn mailbox_list(i: &[u8]) -> IResult<&[u8], (Vec<&str>, Option<&str>, &str)> { +fn mailbox_list(i: &[u8]) -> IResult<&[u8], (Vec>, Option<&str>, &str)> { map( tuple(( flag_list, @@ -257,8 +261,8 @@ fn mailbox_data_list(i: &[u8]) -> IResult<&[u8], MailboxDatum> { map(preceded(tag_no_case("LIST "), mailbox_list), |data| { MailboxDatum::List { flags: data.0, - delimiter: data.1, - name: data.2, + delimiter: data.1.map(Cow::Borrowed), + name: Cow::Borrowed(data.2), } })(i) } @@ -267,8 +271,8 @@ fn mailbox_data_lsub(i: &[u8]) -> IResult<&[u8], MailboxDatum> { map(preceded(tag_no_case("LSUB "), mailbox_list), |data| { MailboxDatum::List { flags: data.0, - delimiter: data.1, - name: data.2, + delimiter: data.1.map(Cow::Borrowed), + name: Cow::Borrowed(data.2), } })(i) } @@ -308,7 +312,10 @@ fn status_att_list(i: &[u8]) -> IResult<&[u8], Vec> { fn mailbox_data_status(i: &[u8]) -> IResult<&[u8], MailboxDatum> { map( tuple((tag_no_case("STATUS "), mailbox, tag(" "), status_att_list)), - |(_, mailbox, _, status)| MailboxDatum::Status { mailbox, status }, + |(_, mailbox, _, status)| MailboxDatum::Status { + mailbox: Cow::Borrowed(mailbox), + status, + }, )(i) } @@ -345,10 +352,10 @@ fn address(i: &[u8]) -> IResult<&[u8], Address> { nstring, )), |(name, _, adl, _, mailbox, _, host)| Address { - name, - adl, - mailbox, - host, + name: name.map(Cow::Borrowed), + adl: adl.map(Cow::Borrowed), + mailbox: mailbox.map(Cow::Borrowed), + host: host.map(Cow::Borrowed), }, ))(i) } @@ -430,16 +437,16 @@ pub(crate) fn envelope(i: &[u8]) -> IResult<&[u8], Envelope> { _, message_id, )| Envelope { - date, - subject, + date: date.map(Cow::Borrowed), + subject: subject.map(Cow::Borrowed), from, sender, reply_to, to, cc, bcc, - in_reply_to, - message_id, + in_reply_to: in_reply_to.map(Cow::Borrowed), + message_id: message_id.map(Cow::Borrowed), }, ))(i) } @@ -453,7 +460,7 @@ fn msg_att_envelope(i: &[u8]) -> IResult<&[u8], AttributeValue> { fn msg_att_internal_date(i: &[u8]) -> IResult<&[u8], AttributeValue> { map( preceded(tag_no_case("INTERNALDATE "), nstring_utf8), - |date| AttributeValue::InternalDate(date.unwrap()), + |date| AttributeValue::InternalDate(Cow::Borrowed(date.unwrap())), )(i) } @@ -465,17 +472,16 @@ fn msg_att_flags(i: &[u8]) -> IResult<&[u8], AttributeValue> { } fn msg_att_rfc822(i: &[u8]) -> IResult<&[u8], AttributeValue> { - map( - preceded(tag_no_case("RFC822 "), nstring), - AttributeValue::Rfc822, - )(i) + map(preceded(tag_no_case("RFC822 "), nstring), |v| { + AttributeValue::Rfc822(v.map(Cow::Borrowed)) + })(i) } fn msg_att_rfc822_header(i: &[u8]) -> IResult<&[u8], AttributeValue> { // extra space workaround for DavMail map( tuple((tag_no_case("RFC822.HEADER "), opt(tag(b" ")), nstring)), - |(_, _, raw)| AttributeValue::Rfc822Header(raw), + |(_, _, raw)| AttributeValue::Rfc822Header(raw.map(Cow::Borrowed)), )(i) } @@ -487,10 +493,9 @@ fn msg_att_rfc822_size(i: &[u8]) -> IResult<&[u8], AttributeValue> { } fn msg_att_rfc822_text(i: &[u8]) -> IResult<&[u8], AttributeValue> { - map( - preceded(tag_no_case("RFC822.TEXT "), nstring), - AttributeValue::Rfc822Text, - )(i) + map(preceded(tag_no_case("RFC822.TEXT "), nstring), |v| { + AttributeValue::Rfc822Text(v.map(Cow::Borrowed)) + })(i) } fn msg_att_uid(i: &[u8]) -> IResult<&[u8], AttributeValue> { @@ -575,7 +580,7 @@ pub(crate) fn continue_req(i: &[u8]) -> IResult<&[u8], Response> { tuple((tag("+"), opt(tag(" ")), resp_text, tag("\r\n"))), |(_, _, text, _)| Response::Continue { code: text.0, - information: text.1, + information: text.1.map(Cow::Borrowed), }, )(i) } @@ -598,7 +603,7 @@ pub(crate) fn response_tagged(i: &[u8]) -> IResult<&[u8], Response> { tag, status, code: text.0, - information: text.1, + information: text.1.map(Cow::Borrowed), }, )(i) } @@ -616,7 +621,7 @@ fn resp_cond(i: &[u8]) -> IResult<&[u8], Response> { |(status, _, text)| Response::Data { status, code: text.0, - information: text.1, + information: text.1.map(Cow::Borrowed), }, )(i) } @@ -645,6 +650,7 @@ pub(crate) fn response_data(i: &[u8]) -> IResult<&[u8], Response> { mod tests { use crate::types::*; use assert_matches::assert_matches; + use std::borrow::Cow; #[test] fn test_list() { @@ -719,8 +725,8 @@ mod tests { super::capability_data(b"CAPABILITY XPIG-LATIN IMAP4rev1 STARTTLS AUTH=GSSAPI\r\n"), Ok((_, capabilities)) => { assert_eq!(capabilities, vec![ - Capability::Atom("XPIG-LATIN"), Capability::Imap4rev1, - Capability::Atom("STARTTLS"), Capability::Auth("GSSAPI") + Capability::Atom(Cow::Borrowed("XPIG-LATIN")), Capability::Imap4rev1, + Capability::Atom(Cow::Borrowed("STARTTLS")), Capability::Auth(Cow::Borrowed("GSSAPI")) ]) } ); @@ -729,7 +735,7 @@ mod tests { super::capability_data(b"CAPABILITY IMAP4rev1 AUTH=GSSAPI AUTH=PLAIN\r\n"), Ok((_, capabilities)) => { assert_eq!(capabilities, vec![ - Capability::Imap4rev1, Capability::Auth("GSSAPI"), Capability::Auth("PLAIN") + Capability::Imap4rev1, Capability::Auth(Cow::Borrowed("GSSAPI")), Capability::Auth(Cow::Borrowed("PLAIN")) ]) } ); diff --git a/imap-proto/src/parser/rfc5161.rs b/imap-proto/src/parser/rfc5161.rs index 3a1f7c4..b6ebdda 100644 --- a/imap-proto/src/parser/rfc5161.rs +++ b/imap-proto/src/parser/rfc5161.rs @@ -12,6 +12,7 @@ use nom::{ sequence::{preceded, tuple}, IResult, }; +use std::borrow::Cow; use crate::parser::core::atom; use crate::types::*; @@ -32,5 +33,5 @@ fn enabled_data(i: &[u8]) -> IResult<&[u8], Vec> { } fn capability(i: &[u8]) -> IResult<&[u8], Capability> { - map(atom, Capability::Atom)(i) + map(map(atom, Cow::Borrowed), Capability::Atom)(i) } diff --git a/imap-proto/src/parser/rfc5464.rs b/imap-proto/src/parser/rfc5464.rs index d8f1d31..0057b2b 100644 --- a/imap-proto/src/parser/rfc5464.rs +++ b/imap-proto/src/parser/rfc5464.rs @@ -12,6 +12,7 @@ use nom::{ sequence::tuple, IResult, }; +use std::borrow::Cow; use crate::{parser::core::*, types::*}; @@ -146,8 +147,8 @@ fn keyval_list(i: &[u8]) -> IResult<&[u8], Vec> { ))(i) } -fn entry_list(i: &[u8]) -> IResult<&[u8], Vec<&str>> { - separated_list0(tag(" "), map(entry_name, slice_to_str))(i) +fn entry_list(i: &[u8]) -> IResult<&[u8], Vec>> { + separated_list0(tag(" "), map(map(entry_name, slice_to_str), Cow::Borrowed))(i) } fn metadata_common(i: &[u8]) -> IResult<&[u8], &[u8]> { @@ -161,7 +162,7 @@ pub(crate) fn metadata_solicited(i: &[u8]) -> IResult<&[u8], Response> { Ok(( i, Response::MailboxData(MailboxDatum::MetadataSolicited { - mailbox: slice_to_str(mailbox), + mailbox: Cow::Borrowed(slice_to_str(mailbox)), values, }), )) @@ -173,7 +174,7 @@ pub(crate) fn metadata_unsolicited(i: &[u8]) -> IResult<&[u8], Response> { Ok(( i, Response::MailboxData(MailboxDatum::MetadataUnsolicited { - mailbox: slice_to_str(mailbox), + mailbox: Cow::Borrowed(slice_to_str(mailbox)), values, }), )) @@ -219,6 +220,7 @@ pub(crate) fn resp_text_code_metadata_no_private(i: &[u8]) -> IResult<&[u8], Res mod tests { use super::{metadata_solicited, metadata_unsolicited}; use crate::types::*; + use std::borrow::Cow; #[test] fn test_solicited_fail_1() { @@ -339,7 +341,7 @@ mod tests { Response::Data { status: Status::Ok, code: Some(ResponseCode::MetadataLongEntries(123)), - information: Some("Some entries omitted."), + information: Some(Cow::Borrowed("Some entries omitted.")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -351,7 +353,7 @@ mod tests { Response::Data { status: Status::No, code: Some(ResponseCode::MetadataMaxSize(123)), - information: Some("Annotation too large."), + information: Some(Cow::Borrowed("Annotation too large.")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -363,7 +365,7 @@ mod tests { Response::Data { status: Status::No, code: Some(ResponseCode::MetadataTooMany), - information: Some("Too many annotations."), + information: Some(Cow::Borrowed("Too many annotations.")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -375,7 +377,7 @@ mod tests { Response::Data { status: Status::No, code: Some(ResponseCode::MetadataNoPrivate), - information: Some("Private annotations not supported."), + information: Some(Cow::Borrowed("Private annotations not supported.")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), diff --git a/imap-proto/src/parser/tests.rs b/imap-proto/src/parser/tests.rs index fe66a50..9752e87 100644 --- a/imap-proto/src/parser/tests.rs +++ b/imap-proto/src/parser/tests.rs @@ -1,5 +1,6 @@ use super::{bodystructure::BodyStructParser, parse_response}; use crate::types::*; +use std::borrow::Cow; use std::num::NonZeroUsize; #[test] @@ -26,7 +27,7 @@ fn test_unseen() { Response::Data { status: Status::Ok, code: Some(ResponseCode::Unseen(3)), - information: Some("Message 3 is first unseen"), + information: Some(Cow::Borrowed("Message 3 is first unseen")), }, ) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -43,7 +44,7 @@ fn test_body_text() { &AttributeValue::BodySection { section: Some(SectionPath::Full(MessageSection::Text)), index: None, - data: Some(b"foo"), + data: Some(Cow::Borrowed(b"foo")), }, "body = {:?}", body @@ -101,7 +102,7 @@ fn test_notify() { _, Response::Continue { code: None, - information: Some("idling"), + information: Some(Cow::Borrowed("idling")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -169,7 +170,7 @@ fn test_response_codes() { Response::Data { status: Status::Ok, code: Some(ResponseCode::Alert), - information: Some("Alert!"), + information: Some(Cow::Borrowed("Alert!")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -181,7 +182,7 @@ fn test_response_codes() { Response::Data { status: Status::No, code: Some(ResponseCode::Parse), - information: Some("Something"), + information: Some(Cow::Borrowed("Something")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -193,12 +194,12 @@ fn test_response_codes() { Response::Data { status: Status::Ok, code: Some(ResponseCode::Capabilities(c)), - information: Some("Logged in"), + information: Some(Cow::Borrowed("Logged in")), }, )) => { assert_eq!(c.len(), 2); assert_eq!(c[0], Capability::Imap4rev1); - assert_eq!(c[1], Capability::Atom("IDLE")); + assert_eq!(c[1], Capability::Atom(Cow::Borrowed("IDLE"))); } rsp => panic!("unexpected response {:?}", rsp), } @@ -209,13 +210,13 @@ fn test_response_codes() { Response::Data { status: Status::Ok, code: Some(ResponseCode::Capabilities(c)), - information: Some("Logged in"), + information: Some(Cow::Borrowed("Logged in")), }, )) => { assert_eq!(c.len(), 3); - assert_eq!(c[0], Capability::Atom("UIDPLUS")); + assert_eq!(c[0], Capability::Atom(Cow::Borrowed("UIDPLUS"))); assert_eq!(c[1], Capability::Imap4rev1); - assert_eq!(c[2], Capability::Atom("IDLE")); + assert_eq!(c[2], Capability::Atom(Cow::Borrowed("IDLE"))); } rsp => panic!("unexpected response {:?}", rsp), } @@ -227,7 +228,7 @@ fn test_response_codes() { Response::Data { status: Status::Ok, code: None, - information: Some("[CAPABILITY UIDPLUS IDLE] Logged in"), + information: Some(Cow::Borrowed("[CAPABILITY UIDPLUS IDLE] Logged in")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -239,7 +240,7 @@ fn test_response_codes() { Response::Data { status: Status::No, code: Some(ResponseCode::BadCharset(None)), - information: Some("error"), + information: Some(Cow::Borrowed("error")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -251,7 +252,7 @@ fn test_response_codes() { Response::Data { status: Status::No, code: Some(ResponseCode::BadCharset(Some(v))), - information: Some("error"), + information: Some(Cow::Borrowed("error")), }, )) => { assert_eq!(v.len(), 2); @@ -267,7 +268,7 @@ fn test_response_codes() { Response::Data { status: Status::No, code: None, - information: Some("[BADCHARSET ()] error"), + information: Some(Cow::Borrowed("[BADCHARSET ()] error")), }, )) => {} rsp => panic!("unexpected response {:?}", rsp), @@ -315,8 +316,8 @@ fn test_enabled() { Ok((_, capabilities)) => assert_eq!( capabilities, Response::Capabilities(vec![ - Capability::Atom("QRESYNC"), - Capability::Atom("X-GOOD-IDEA"), + Capability::Atom(Cow::Borrowed("QRESYNC")), + Capability::Atom(Cow::Borrowed("X-GOOD-IDEA")), ]) ), rsp => panic!("Unexpected response: {:?}", rsp), @@ -332,12 +333,12 @@ fn test_flags() { Ok((_, capabilities)) => assert_eq!( capabilities, Response::MailboxData(MailboxDatum::Flags(vec![ - "\\Answered", - "\\Flagged", - "\\Deleted", - "\\Seen", - "\\Draft", - "\\*" + Cow::Borrowed("\\Answered"), + Cow::Borrowed("\\Flagged"), + Cow::Borrowed("\\Deleted"), + Cow::Borrowed("\\Seen"), + Cow::Borrowed("\\Draft"), + Cow::Borrowed("\\*") ])) ), rsp => panic!("Unexpected response: {:?}", rsp), @@ -402,7 +403,7 @@ fn test_uidplus() { Response::Data { status: Status::Ok, code: Some(ResponseCode::AppendUid(38505, uid_set)), - information: Some("APPEND completed"), + information: Some(Cow::Borrowed("APPEND completed")), }, )) if uid_set == [3955.into()] => {} rsp => panic!("Unexpected response: {:?}", rsp), @@ -415,7 +416,7 @@ fn test_uidplus() { Response::Data { status: Status::Ok, code: Some(ResponseCode::CopyUid(38505, uid_set_src, uid_set_dst)), - information: Some("Done"), + information: Some(Cow::Borrowed("Done")), }, )) if uid_set_src == [304.into(), (319..=320).into()] && uid_set_dst == [(3956..=3958).into()] => {} @@ -429,7 +430,7 @@ fn test_uidplus() { Response::Data { status: Status::No, code: Some(ResponseCode::UidNotSticky), - information: Some("Non-persistent UIDs"), + information: Some(Cow::Borrowed("Non-persistent UIDs")), }, )) => {} rsp => panic!("Unexpected response: {:?}", rsp), diff --git a/imap-proto/src/types.rs b/imap-proto/src/types.rs index 9acc39a..db51f1f 100644 --- a/imap-proto/src/types.rs +++ b/imap-proto/src/types.rs @@ -1,7 +1,12 @@ +use std::borrow::Cow; use std::ops::RangeInclusive; +fn to_owned_cow<'a, T: ?Sized + ToOwned>(c: Cow<'a, T>) -> Cow<'static, T> { + Cow::Owned(c.into_owned()) +} + #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Request<'a>(pub &'a [u8], pub &'a [u8]); +pub struct Request<'a>(pub Cow<'a, [u8]>, pub Cow<'a, [u8]>); #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum AttrMacro { @@ -16,18 +21,18 @@ pub enum Response<'a> { Capabilities(Vec>), Continue { code: Option>, - information: Option<&'a str>, + information: Option>, }, Done { tag: RequestId, status: Status, code: Option>, - information: Option<&'a str>, + information: Option>, }, Data { status: Status, code: Option>, - information: Option<&'a str>, + information: Option>, }, Expunge(u32), Vanished { @@ -57,11 +62,11 @@ pub enum Status { #[non_exhaustive] pub enum ResponseCode<'a> { Alert, - BadCharset(Option>), + BadCharset(Option>>), Capabilities(Vec>), HighestModSeq(u64), // RFC 4551, section 3.1.1 Parse, - PermanentFlags(Vec<&'a str>), + PermanentFlags(Vec>), ReadOnly, ReadWrite, TryCreate, @@ -92,6 +97,38 @@ impl From for UidSetMember { } } +impl<'a> ResponseCode<'a> { + pub fn into_owned(self) -> ResponseCode<'static> { + match self { + ResponseCode::Alert => ResponseCode::Alert, + ResponseCode::BadCharset(v) => { + ResponseCode::BadCharset(v.map(|vs| vs.into_iter().map(to_owned_cow).collect())) + } + ResponseCode::Capabilities(v) => { + ResponseCode::Capabilities(v.into_iter().map(Capability::into_owned).collect()) + } + ResponseCode::HighestModSeq(v) => ResponseCode::HighestModSeq(v), + ResponseCode::Parse => ResponseCode::Parse, + ResponseCode::PermanentFlags(v) => { + ResponseCode::PermanentFlags(v.into_iter().map(to_owned_cow).collect()) + } + ResponseCode::ReadOnly => ResponseCode::ReadOnly, + ResponseCode::ReadWrite => ResponseCode::ReadWrite, + ResponseCode::TryCreate => ResponseCode::TryCreate, + ResponseCode::UidNext(v) => ResponseCode::UidNext(v), + ResponseCode::UidValidity(v) => ResponseCode::UidValidity(v), + ResponseCode::Unseen(v) => ResponseCode::Unseen(v), + ResponseCode::AppendUid(a, b) => ResponseCode::AppendUid(a, b), + ResponseCode::CopyUid(a, b, c) => ResponseCode::CopyUid(a, b, c), + ResponseCode::UidNotSticky => ResponseCode::UidNotSticky, + ResponseCode::MetadataLongEntries(v) => ResponseCode::MetadataLongEntries(v), + ResponseCode::MetadataMaxSize(v) => ResponseCode::MetadataMaxSize(v), + ResponseCode::MetadataTooMany => ResponseCode::MetadataTooMany, + ResponseCode::MetadataNoPrivate => ResponseCode::MetadataNoPrivate, + } + } +} + #[derive(Debug, Eq, PartialEq)] #[non_exhaustive] pub enum StatusAttribute { @@ -112,33 +149,43 @@ pub struct Metadata { #[derive(Debug, Eq, PartialEq)] pub enum MailboxDatum<'a> { Exists(u32), - Flags(Vec<&'a str>), + Flags(Vec>), List { - flags: Vec<&'a str>, - delimiter: Option<&'a str>, - name: &'a str, + flags: Vec>, + delimiter: Option>, + name: Cow<'a, str>, }, Search(Vec), Status { - mailbox: &'a str, + mailbox: Cow<'a, str>, status: Vec, }, Recent(u32), MetadataSolicited { - mailbox: &'a str, + mailbox: Cow<'a, str>, values: Vec, }, MetadataUnsolicited { - mailbox: &'a str, - values: Vec<&'a str>, + mailbox: Cow<'a, str>, + values: Vec>, }, } #[derive(Debug, Eq, PartialEq, Hash)] pub enum Capability<'a> { Imap4rev1, - Auth(&'a str), - Atom(&'a str), + Auth(Cow<'a, str>), + Atom(Cow<'a, str>), +} + +impl<'a> Capability<'a> { + pub fn into_owned(self) -> Capability<'static> { + match self { + Capability::Imap4rev1 => Capability::Imap4rev1, + Capability::Auth(v) => Capability::Auth(to_owned_cow(v)), + Capability::Atom(v) => Capability::Atom(to_owned_cow(v)), + } + } } #[derive(Debug, Eq, PartialEq)] @@ -175,20 +222,48 @@ pub enum AttributeValue<'a> { BodySection { section: Option, index: Option, - data: Option<&'a [u8]>, + data: Option>, }, BodyStructure(BodyStructure<'a>), Envelope(Box>), - Flags(Vec<&'a str>), - InternalDate(&'a str), + Flags(Vec>), + InternalDate(Cow<'a, str>), ModSeq(u64), // RFC 4551, section 3.3.2 - Rfc822(Option<&'a [u8]>), - Rfc822Header(Option<&'a [u8]>), + Rfc822(Option>), + Rfc822Header(Option>), Rfc822Size(u32), - Rfc822Text(Option<&'a [u8]>), + Rfc822Text(Option>), Uid(u32), } +impl<'a> AttributeValue<'a> { + pub fn into_owned(self) -> AttributeValue<'static> { + match self { + AttributeValue::BodySection { + section, + index, + data, + } => AttributeValue::BodySection { + section, + index, + data: data.map(to_owned_cow), + }, + AttributeValue::BodyStructure(body) => AttributeValue::BodyStructure(body.into_owned()), + AttributeValue::Envelope(e) => AttributeValue::Envelope(Box::new(e.into_owned())), + AttributeValue::Flags(v) => { + AttributeValue::Flags(v.into_iter().map(to_owned_cow).collect()) + } + AttributeValue::InternalDate(v) => AttributeValue::InternalDate(to_owned_cow(v)), + AttributeValue::ModSeq(v) => AttributeValue::ModSeq(v), + AttributeValue::Rfc822(v) => AttributeValue::Rfc822(v.map(to_owned_cow)), + AttributeValue::Rfc822Header(v) => AttributeValue::Rfc822Header(v.map(to_owned_cow)), + AttributeValue::Rfc822Size(v) => AttributeValue::Rfc822Size(v), + AttributeValue::Rfc822Text(v) => AttributeValue::Rfc822Text(v.map(to_owned_cow)), + AttributeValue::Uid(v) => AttributeValue::Uid(v), + } + } +} + #[allow(clippy::large_enum_variant)] #[derive(Debug, Eq, PartialEq)] pub enum BodyStructure<'a> { @@ -218,36 +293,131 @@ pub enum BodyStructure<'a> { }, } +impl<'a> BodyStructure<'a> { + pub fn into_owned(self) -> BodyStructure<'static> { + match self { + BodyStructure::Basic { + common, + other, + extension, + } => BodyStructure::Basic { + common: common.into_owned(), + other: other.into_owned(), + extension: extension.map(|v| v.into_owned()), + }, + BodyStructure::Text { + common, + other, + lines, + extension, + } => BodyStructure::Text { + common: common.into_owned(), + other: other.into_owned(), + lines, + extension: extension.map(|v| v.into_owned()), + }, + BodyStructure::Message { + common, + other, + envelope, + body, + lines, + extension, + } => BodyStructure::Message { + common: common.into_owned(), + other: other.into_owned(), + envelope: envelope.into_owned(), + body: Box::new(body.into_owned()), + lines, + extension: extension.map(|v| v.into_owned()), + }, + BodyStructure::Multipart { + common, + bodies, + extension, + } => BodyStructure::Multipart { + common: common.into_owned(), + bodies: bodies.into_iter().map(|v| v.into_owned()).collect(), + extension: extension.map(|v| v.into_owned()), + }, + } + } +} + #[derive(Debug, Eq, PartialEq)] pub struct BodyContentCommon<'a> { pub ty: ContentType<'a>, pub disposition: Option>, - pub language: Option>, - pub location: Option<&'a str>, + pub language: Option>>, + pub location: Option>, +} + +impl<'a> BodyContentCommon<'a> { + pub fn into_owned(self) -> BodyContentCommon<'static> { + BodyContentCommon { + ty: self.ty.into_owned(), + disposition: self.disposition.map(|v| v.into_owned()), + language: self + .language + .map(|v| v.into_iter().map(to_owned_cow).collect()), + location: self.location.map(to_owned_cow), + } + } } #[derive(Debug, Eq, PartialEq)] pub struct BodyContentSinglePart<'a> { - pub id: Option<&'a str>, - pub md5: Option<&'a str>, - pub description: Option<&'a str>, + pub id: Option>, + pub md5: Option>, + pub description: Option>, pub transfer_encoding: ContentEncoding<'a>, pub octets: u32, } +impl<'a> BodyContentSinglePart<'a> { + pub fn into_owned(self) -> BodyContentSinglePart<'static> { + BodyContentSinglePart { + id: self.id.map(to_owned_cow), + md5: self.md5.map(to_owned_cow), + description: self.description.map(to_owned_cow), + transfer_encoding: self.transfer_encoding.into_owned(), + octets: self.octets, + } + } +} + #[derive(Debug, Eq, PartialEq)] pub struct ContentType<'a> { - pub ty: &'a str, - pub subtype: &'a str, + pub ty: Cow<'a, str>, + pub subtype: Cow<'a, str>, pub params: BodyParams<'a>, } +impl<'a> ContentType<'a> { + pub fn into_owned(self) -> ContentType<'static> { + ContentType { + ty: to_owned_cow(self.ty), + subtype: to_owned_cow(self.subtype), + params: body_param_owned(self.params), + } + } +} + #[derive(Debug, Eq, PartialEq)] pub struct ContentDisposition<'a> { - pub ty: &'a str, + pub ty: Cow<'a, str>, pub params: BodyParams<'a>, } +impl<'a> ContentDisposition<'a> { + pub fn into_owned(self) -> ContentDisposition<'static> { + ContentDisposition { + ty: to_owned_cow(self.ty), + params: body_param_owned(self.params), + } + } +} + #[derive(Debug, Eq, PartialEq)] pub enum ContentEncoding<'a> { SevenBit, @@ -255,38 +425,111 @@ pub enum ContentEncoding<'a> { Binary, Base64, QuotedPrintable, - Other(&'a str), + Other(Cow<'a, str>), +} + +impl<'a> ContentEncoding<'a> { + pub fn into_owned(self) -> ContentEncoding<'static> { + match self { + ContentEncoding::SevenBit => ContentEncoding::SevenBit, + ContentEncoding::EightBit => ContentEncoding::EightBit, + ContentEncoding::Binary => ContentEncoding::Binary, + ContentEncoding::Base64 => ContentEncoding::Base64, + ContentEncoding::QuotedPrintable => ContentEncoding::QuotedPrintable, + ContentEncoding::Other(v) => ContentEncoding::Other(to_owned_cow(v)), + } + } } #[derive(Debug, Eq, PartialEq)] pub enum BodyExtension<'a> { Num(u32), - Str(Option<&'a str>), + Str(Option>), List(Vec>), } -pub type BodyParams<'a> = Option>; +impl<'a> BodyExtension<'a> { + pub fn into_owned(self) -> BodyExtension<'static> { + match self { + BodyExtension::Num(v) => BodyExtension::Num(v), + BodyExtension::Str(v) => BodyExtension::Str(v.map(to_owned_cow)), + BodyExtension::List(v) => { + BodyExtension::List(v.into_iter().map(|v| v.into_owned()).collect()) + } + } + } +} + +pub type BodyParams<'a> = Option, Cow<'a, str>)>>; + +fn body_param_owned<'a>(v: BodyParams<'a>) -> BodyParams<'static> { + v.map(|v| { + v.into_iter() + .map(|(k, v)| (to_owned_cow(k), to_owned_cow(v))) + .collect() + }) +} #[derive(Debug, Eq, PartialEq)] pub struct Envelope<'a> { - pub date: Option<&'a [u8]>, - pub subject: Option<&'a [u8]>, + pub date: Option>, + pub subject: Option>, pub from: Option>>, pub sender: Option>>, pub reply_to: Option>>, pub to: Option>>, pub cc: Option>>, pub bcc: Option>>, - pub in_reply_to: Option<&'a [u8]>, - pub message_id: Option<&'a [u8]>, + pub in_reply_to: Option>, + pub message_id: Option>, +} + +impl<'a> Envelope<'a> { + pub fn into_owned(self) -> Envelope<'static> { + Envelope { + date: self.date.map(to_owned_cow), + subject: self.subject.map(to_owned_cow), + from: self + .from + .map(|v| v.into_iter().map(|v| v.into_owned()).collect()), + sender: self + .sender + .map(|v| v.into_iter().map(|v| v.into_owned()).collect()), + reply_to: self + .reply_to + .map(|v| v.into_iter().map(|v| v.into_owned()).collect()), + to: self + .to + .map(|v| v.into_iter().map(|v| v.into_owned()).collect()), + cc: self + .cc + .map(|v| v.into_iter().map(|v| v.into_owned()).collect()), + bcc: self + .bcc + .map(|v| v.into_iter().map(|v| v.into_owned()).collect()), + in_reply_to: self.in_reply_to.map(to_owned_cow), + message_id: self.message_id.map(to_owned_cow), + } + } } #[derive(Debug, Eq, PartialEq)] pub struct Address<'a> { - pub name: Option<&'a [u8]>, - pub adl: Option<&'a [u8]>, - pub mailbox: Option<&'a [u8]>, - pub host: Option<&'a [u8]>, + pub name: Option>, + pub adl: Option>, + pub mailbox: Option>, + pub host: Option>, +} + +impl<'a> Address<'a> { + pub fn into_owned(self) -> Address<'static> { + Address { + name: self.name.map(to_owned_cow), + adl: self.adl.map(to_owned_cow), + mailbox: self.mailbox.map(to_owned_cow), + host: self.host.map(to_owned_cow), + } + } } #[derive(Clone, Debug, Eq, PartialEq)] @@ -310,24 +553,64 @@ pub enum State { pub struct BodyFields<'a> { pub param: BodyParams<'a>, - pub id: Option<&'a str>, - pub description: Option<&'a str>, + pub id: Option>, + pub description: Option>, pub transfer_encoding: ContentEncoding<'a>, pub octets: u32, } +impl<'a> BodyFields<'a> { + pub fn into_owned(self) -> BodyFields<'static> { + BodyFields { + param: body_param_owned(self.param), + id: self.id.map(to_owned_cow), + description: self.description.map(to_owned_cow), + transfer_encoding: self.transfer_encoding.into_owned(), + octets: self.octets, + } + } +} + pub struct BodyExt1Part<'a> { - pub md5: Option<&'a str>, + pub md5: Option>, pub disposition: Option>, - pub language: Option>, - pub location: Option<&'a str>, + pub language: Option>>, + pub location: Option>, pub extension: Option>, } +impl<'a> BodyExt1Part<'a> { + pub fn into_owned(self) -> BodyExt1Part<'static> { + BodyExt1Part { + md5: self.md5.map(to_owned_cow), + disposition: self.disposition.map(|v| v.into_owned()), + language: self + .language + .map(|v| v.into_iter().map(to_owned_cow).collect()), + location: self.location.map(to_owned_cow), + extension: self.extension.map(|v| v.into_owned()), + } + } +} + pub struct BodyExtMPart<'a> { pub param: BodyParams<'a>, pub disposition: Option>, - pub language: Option>, - pub location: Option<&'a str>, + pub language: Option>>, + pub location: Option>, pub extension: Option>, } + +impl<'a> BodyExtMPart<'a> { + pub fn into_owned(self) -> BodyExtMPart<'static> { + BodyExtMPart { + param: body_param_owned(self.param), + disposition: self.disposition.map(|v| v.into_owned()), + language: self + .language + .map(|v| v.into_iter().map(to_owned_cow).collect()), + location: self.location.map(to_owned_cow), + extension: self.extension.map(|v| v.into_owned()), + } + } +} diff --git a/tokio-imap/examples/basic.rs b/tokio-imap/examples/basic.rs index e8305dc..2c1379b 100644 --- a/tokio-imap/examples/basic.rs +++ b/tokio-imap/examples/basic.rs @@ -80,8 +80,8 @@ async fn imap_fetch( async fn process_email(response_data: ResponseData) -> Result<(), io::Error> { if let Response::Fetch(_, ref attr_vals) = *response_data.parsed() { - for val in attr_vals.iter() { - match *val { + for val in attr_vals { + match val { AttributeValue::Uid(u) => { eprintln!("Message UID: {}", u); } diff --git a/tokio-imap/src/client.rs b/tokio-imap/src/client.rs index c8eb1d7..9a34ab3 100644 --- a/tokio-imap/src/client.rs +++ b/tokio-imap/src/client.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::io; use std::net::ToSocketAddrs; use std::pin::Pin; @@ -93,7 +94,10 @@ where ResponseStreamState::Start => { ready!(Pin::new(&mut me.client.transport).poll_ready(cx))?; let pinned = Pin::new(&mut me.client.transport); - pinned.start_send(&Request(me.request_id.as_bytes(), &me.cmd.args))?; + pinned.start_send(&Request( + Cow::Borrowed(me.request_id.as_bytes()), + Cow::Borrowed(&me.cmd.args), + ))?; *me.state = ResponseStreamState::Sending; } ResponseStreamState::Sending => { diff --git a/tokio-imap/src/codec.rs b/tokio-imap/src/codec.rs index 04c736f..ef3526d 100644 --- a/tokio-imap/src/codec.rs +++ b/tokio-imap/src/codec.rs @@ -52,9 +52,9 @@ impl<'a> Decoder for ImapCodec { impl<'a> Encoder<&'a Request<'a>> for ImapCodec { type Error = io::Error; fn encode(&mut self, msg: &Request, dst: &mut BytesMut) -> Result<(), io::Error> { - dst.put(msg.0); + dst.put(&*msg.0); dst.put_u8(b' '); - dst.put_slice(msg.1); + dst.put_slice(&*msg.1); dst.put_slice(b"\r\n"); Ok(()) }