Repository: lettre/lettre (language: rust)

Issue:
Drop remaining uses of the `regex` crate
With #763 and #765 reducing our reliance on the regex crate I think it's now possible for it to be removed entirely at some point. This removes the need to have a runtime regex engine and speed up compile times.


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index c81242290..8a84ccd31 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 81015dc3a..7e47865c3 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<Regex> = Lazy::new(|| Regex::new("(\r\n)+$").unwrap());
-    static RE_DOUBLE_SPACE: Lazy<Regex> = Lazy::new(|| Regex::new("[\\t ]+").unwrap());
-    static RE_SPACE_EOL: Lazy<Regex> = Lazy::new(|| Regex::new("[\t ]\r\n").unwrap());
     match canonicalization {
-        DkimCanonicalizationType::Simple => RE.replace(body, &b"\r\n"[..]),
+        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 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),
+            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?= <test+ezrz@example.net>\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();


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 21908bc7a..ca7693b40 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