diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21908bc..ca7693b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -134,10 +134,10 @@ jobs: run: cargo test - name: Test with all features (-native-tls) - run: cargo test --no-default-features --features async-std,async-std1,async-std1-rustls-tls,async-trait,base64,boring,boring-tls,builder,dkim,ed25519-dalek,email-encoding,fastrand,file-transport,file-transport-envelope,futures-io,futures-rustls,futures-util,hostname,httpdate,mime,mime03,nom,once_cell,pool,quoted_printable,regex,rsa,rustls,rustls-pemfile,rustls-tls,sendmail-transport,serde,serde_json,sha2,smtp-transport,socket2,tokio1,tokio1-boring-tls,tokio1-rustls-tls,tokio1_boring,tokio1_crate,tokio1_rustls,tracing,uuid,webpki-roots + run: cargo test --no-default-features --features async-std,async-std1,async-std1-rustls-tls,async-trait,base64,boring,boring-tls,builder,dkim,ed25519-dalek,email-encoding,fastrand,file-transport,file-transport-envelope,futures-io,futures-rustls,futures-util,hostname,httpdate,mime,mime03,nom,once_cell,pool,quoted_printable,rsa,rustls,rustls-pemfile,rustls-tls,sendmail-transport,serde,serde_json,sha2,smtp-transport,socket2,tokio1,tokio1-boring-tls,tokio1-rustls-tls,tokio1_boring,tokio1_crate,tokio1_rustls,tracing,uuid,webpki-roots - name: Test with all features (-boring-tls) - run: cargo test --no-default-features --features async-std,async-std1,async-std1-rustls-tls,async-trait,base64,builder,dkim,ed25519-dalek,email-encoding,fastrand,file-transport,file-transport-envelope,futures-io,futures-rustls,futures-util,hostname,httpdate,mime,mime03,native-tls,nom,once_cell,pool,quoted_printable,regex,rsa,rustls,rustls-pemfile,rustls-tls,sendmail-transport,serde,serde_json,sha2,smtp-transport,socket2,tokio1,tokio1-native-tls,tokio1-rustls-tls,tokio1_crate,tokio1_native_tls_crate,tokio1_rustls,tracing,uuid,webpki-roots + run: cargo test --no-default-features --features async-std,async-std1,async-std1-rustls-tls,async-trait,base64,builder,dkim,ed25519-dalek,email-encoding,fastrand,file-transport,file-transport-envelope,futures-io,futures-rustls,futures-util,hostname,httpdate,mime,mime03,native-tls,nom,once_cell,pool,quoted_printable,rsa,rustls,rustls-pemfile,rustls-tls,sendmail-transport,serde,serde_json,sha2,smtp-transport,socket2,tokio1,tokio1-native-tls,tokio1-rustls-tls,tokio1_crate,tokio1_native_tls_crate,tokio1_rustls,tracing,uuid,webpki-roots # coverage: # name: Coverage diff --git a/Cargo.toml b/Cargo.toml index c812422..8a84ccd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,6 @@ tokio1_boring = { package = "tokio-boring", version = "2.1.4", optional = true } sha2 = { version = "0.10", optional = true } rsa = { version = "0.6.0", optional = true } ed25519-dalek = { version = "1.0.1", optional = true } -regex = { version = "1", default-features = false, features = ["std"], optional = true } # email formats email_address = { version = "0.2.1", default-features = false } @@ -115,7 +114,7 @@ tokio1-native-tls = ["tokio1", "native-tls", "tokio1_native_tls_crate"] tokio1-rustls-tls = ["tokio1", "rustls-tls", "tokio1_rustls"] tokio1-boring-tls = ["tokio1", "boring-tls", "tokio1_boring"] -dkim = ["base64", "sha2", "rsa", "ed25519-dalek", "regex", "once_cell"] +dkim = ["base64", "sha2", "rsa", "ed25519-dalek"] [package.metadata.docs.rs] all-features = true diff --git a/src/message/dkim.rs b/src/message/dkim.rs index 81015dc..7e47865 100644 --- a/src/message/dkim.rs +++ b/src/message/dkim.rs @@ -7,8 +7,6 @@ use std::{ }; use ed25519_dalek::Signer; -use once_cell::sync::Lazy; -use regex::bytes::Regex; use rsa::{pkcs1::DecodeRsaPrivateKey, Hash, PaddingScheme, RsaPrivateKey}; use sha2::{Digest, Sha256}; @@ -219,24 +217,34 @@ fn dkim_header_format( /// Canonicalize the body of an email fn dkim_canonicalize_body( - body: &[u8], + mut body: &[u8], canonicalization: DkimCanonicalizationType, ) -> Cow<'_, [u8]> { - static RE: Lazy = Lazy::new(|| Regex::new("(\r\n)+$").unwrap()); - static RE_DOUBLE_SPACE: Lazy = Lazy::new(|| Regex::new("[\\t ]+").unwrap()); - static RE_SPACE_EOL: Lazy = Lazy::new(|| Regex::new("[\t ]\r\n").unwrap()); match canonicalization { - DkimCanonicalizationType::Simple => RE.replace(body, &b"\r\n"[..]), - DkimCanonicalizationType::Relaxed => { - let body = RE_DOUBLE_SPACE.replace_all(body, &b" "[..]); - let body = match RE_SPACE_EOL.replace_all(&body, &b"\r\n"[..]) { - Cow::Borrowed(_body) => body, - Cow::Owned(body) => Cow::Owned(body), - }; - match RE.replace(&body, &b"\r\n"[..]) { - Cow::Borrowed(_body) => body, - Cow::Owned(body) => Cow::Owned(body), + DkimCanonicalizationType::Simple => { + // Remove empty lines at end + while body.ends_with(b"\r\n\r\n") { + body = &body[..body.len() - 2]; } + Cow::Borrowed(body) + } + DkimCanonicalizationType::Relaxed => { + let mut out = Vec::with_capacity(body.len()); + loop { + match body { + [b' ' | b'\t', b'\r', b'\n', ..] => {} + [b' ' | b'\t', b' ' | b'\t', ..] => {} + [b' ' | b'\t', ..] => out.push(b' '), + [c, ..] => out.push(*c), + [] => break, + } + body = &body[1..]; + } + // Remove empty lines at end + while out.ends_with(b"\r\n\r\n") { + out.truncate(out.len() - 2); + } + Cow::Owned(out) } } } @@ -416,8 +424,9 @@ mod test { header::{HeaderName, HeaderValue}, Header, Message, }, - dkim_canonicalize_headers, dkim_sign_fixed_time, DkimCanonicalization, - DkimCanonicalizationType, DkimConfig, DkimSigningAlgorithm, DkimSigningKey, + dkim_canonicalize_body, dkim_canonicalize_headers, dkim_sign_fixed_time, + DkimCanonicalization, DkimCanonicalizationType, DkimConfig, DkimSigningAlgorithm, + DkimSigningKey, }; use crate::StdError; @@ -490,6 +499,24 @@ cJ5Ku0OTwRtSMaseRPX+T4EfG1Caa/eunPPN4rh+CSup2BVVarOT assert_eq!(dkim_canonicalize_headers(["From", "Test"], &message.headers, DkimCanonicalizationType::Relaxed),"from:=?utf-8?b?VGVzdCBPJ0xlYXJ5?= \r\ntest:test test very very long with spaces and extra spaces will be folded to several lines\r\n") } + #[test] + fn test_body_simple_canonicalize() { + let body = b" C \r\nD \t E\r\n\r\n\r\n"; + assert_eq!( + dkim_canonicalize_body(body, DkimCanonicalizationType::Simple).into_owned(), + b" C \r\nD \t E\r\n" + ); + } + + #[test] + fn test_body_relaxed_canonicalize() { + let body = b" C \r\nD \t E\r\n\tF\r\n\t\r\n\r\n\r\n"; + assert_eq!( + dkim_canonicalize_body(body, DkimCanonicalizationType::Relaxed).into_owned(), + b" C\r\nD E\r\n F\r\n" + ); + } + #[test] fn test_signature_rsa_simple() { let mut message = test_message();