From 7bbdaec84b3ce72fd38f6c732a52dd945216ebc0 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Wed, 22 Feb 2023 20:47:14 -0800 Subject: [PATCH] Add matching rust implementation for Go HashMessage. --- Cargo.lock | 117 +++++++++++++++++++++++++++++++++++-------- Cargo.toml | 3 +- hash_test.go | 24 +++++++++ src/bin/mailparse.rs | 16 ++---- src/lib.rs | 53 ++++++++++++++++++++ 5 files changed, 180 insertions(+), 33 deletions(-) create mode 100644 hash_test.go create mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d558dd9..ba7f083 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,60 +13,112 @@ dependencies = [ [[package]] name = "base64" -version = "0.10.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "byteorder", + "generic-array", ] -[[package]] -name = "byteorder" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" - [[package]] name = "cfg-if" -version = "0.1.10" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "charset" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f426e64df1c3de26cbf44593c6ffff5dbfd43bbf9de0d075058558126b3fc73" +checksum = "18e9079d1a12a2cc2bffb5db039c43661836ead4082120d5844f02555aca2d46" dependencies = [ "base64", "encoding_rs", ] +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "email" version = "0.1.0" dependencies = [ "mailparse", "regex", + "sha1", ] [[package]] name = "encoding_rs" -version = "0.8.20" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if", ] [[package]] -name = "mailparse" -version = "0.9.2" +name = "generic-array" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a60bad00d8aa905d31cf239f207ad4ef16c963ea53cf522d5fd7dc7f3ecfe2" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "mailparse" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b56570f5f8c0047260d1c8b5b331f62eb9c660b9dd4071a8c46f8c7d3f280aa" dependencies = [ - "base64", "charset", + "data-encoding", "quoted_printable", ] @@ -78,9 +130,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "quoted_printable" -version = "0.4.1" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86cedf331228892e747bb85beb130b6bb23fc628c40dde9ea01eb6becea3c798" +checksum = "a24039f627d8285853cc90dcddf8c1ebfaa91f834566948872b225b9a28ed1b6" [[package]] name = "regex" @@ -98,3 +150,26 @@ name = "regex-syntax" version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/Cargo.toml b/Cargo.toml index e7fa65e..1071d42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -mailparse = "0.9.2" +mailparse = "0.14.0" regex = "1.7.0" +sha1 = "0.10.5" diff --git a/hash_test.go b/hash_test.go new file mode 100644 index 0000000..3485493 --- /dev/null +++ b/hash_test.go @@ -0,0 +1,24 @@ +package email + +import ( + "net/mail" + "testing" +) + +func TestHashReader(t *testing.T) { + got, err := HashMessage(&mail.Message{ + Header: mail.Header{ + "Subject": []string{"Test"}, + "From": []string{"me@myself.com"}, + "To": []string{"you@yourself.com"}, + }, + Body: nil, + }) + if err != nil { + t.Errorf("Failed to parse: %v", err) + } + want := "1b25d59ed0ade6a762145c58643717477b054fd1" + if got != want { + t.Errorf("HashReader(msg) = %s; want %s", got, want) + } +} diff --git a/src/bin/mailparse.rs b/src/bin/mailparse.rs index f2bf164..0d8de5c 100644 --- a/src/bin/mailparse.rs +++ b/src/bin/mailparse.rs @@ -1,12 +1,6 @@ -use std::env; -use std::error::Error; -use std::fs::File; -use std::io::prelude::*; -use std::process::exit; -use std::slice::Iter; +use std::{env, error::Error, fs::File, io::prelude::*, process::exit, slice::Iter}; -use mailparse::dateparse; -use mailparse::MailHeaderMap; +use mailparse::{dateparse, MailHeaderMap}; fn newline(b: &u8) -> bool { *b == b'\n' @@ -57,15 +51,15 @@ fn parse_mbox(mbox_bytes: &Vec) -> Result<(), Box> { let mail = mailparse::parse_mail(mail_bytes).unwrap(); println!( "{:?} {:?} from {:?}", - match mail.headers.get_first_value("Date")? { + match mail.headers.get_first_value("Date") { Some(date) => date, None => "NO DATE".to_string(), }, - match mail.headers.get_first_value("Subject")? { + match mail.headers.get_first_value("Subject") { Some(subject) => subject, None => "NO SUBJECT".to_string(), }, - match mail.headers.get_first_value("From")? { + match mail.headers.get_first_value("From") { Some(from) => from, None => "NO FROM".to_string(), }, diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7b19339 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,53 @@ +use mailparse::{parse_headers, MailHeader, MailHeaderMap}; +use sha1::{Digest, Sha1}; + +// Keep these sorted to match Go implementation. +const IMPORTANT_HEADERS: &[&str] = &[ + "cc", + "date", + "from", + "message-id", + "received", + "subject", + "to", +]; + +pub fn hash_headers(hdrs: &[MailHeader]) -> String { + // create a Sha1 object + let mut hasher = Sha1::new(); + + for h in IMPORTANT_HEADERS { + if let Some(v) = hdrs.get_first_value(h) { + eprintln!("V [{}]", v); + hasher.update(v); + } + } + format!("{:x}", hasher.finalize()) +} + +#[cfg(test)] +mod tests { + use mailparse::{parse_headers, MailHeader, MailHeaderMap}; + #[test] + fn hash_headers() { + let (hdrs, _) = parse_headers( + concat!( + "Subject: Test\n", + "From: me@myself.com\n", + "To: you@yourself.com" + ) + .as_bytes(), + ) + .unwrap(); + assert_eq!(hdrs[1].get_key(), "From"); + assert_eq!( + hdrs.get_first_value("To"), + Some("you@yourself.com".to_string()) + ); + + assert_eq!( + super::hash_headers(&hdrs), + "1b25d59ed0ade6a762145c58643717477b054fd1" + ); + } +}