diff --git a/Cargo.lock b/Cargo.lock index 44cc64ae..239c3e2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7435,6 +7435,7 @@ dependencies = [ "psl", "reqwest", "rkyv", + "serde", "sha1", "sha2 0.10.9", "smtp-proto", @@ -7442,6 +7443,8 @@ dependencies = [ "tokio", "trc", "types", + "unicode-general-category", + "unicode-normalization", "unicode-security", "utils", ] @@ -8431,6 +8434,12 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.22" diff --git a/crates/email/src/message/ingest.rs b/crates/email/src/message/ingest.rs index 063d0f12..072c5899 100644 --- a/crates/email/src/message/ingest.rs +++ b/crates/email/src/message/ingest.rs @@ -315,8 +315,8 @@ impl EmailIngest for Server { } } - // Add Spam-Result header - const HEADER: &str = "X-Spam-Result"; + // Add Spam-Status header + const HEADER: &str = "X-Spam-Status"; let offset_field = extra_headers.len(); let offset_start = offset_field + HEADER.len() + 1; let result = if is_spam { "Yes" } else { "No" }; diff --git a/crates/nlp/src/classifier/sgd.rs b/crates/nlp/src/classifier/sgd.rs index ed964b51..16c6bfee 100644 --- a/crates/nlp/src/classifier/sgd.rs +++ b/crates/nlp/src/classifier/sgd.rs @@ -83,7 +83,7 @@ impl TextClassifier { } } - fn predict_proba_sample(&self, features: &Features) -> f32 { + pub fn predict_proba_sample(&self, features: &Features) -> f32 { let mut z: f32 = 0.0; for (idx, feature) in &features.0 { z += self.weights[*idx as usize] * *feature; diff --git a/crates/nlp/src/tokenizers/stream.rs b/crates/nlp/src/tokenizers/stream.rs index 8212c249..2f9ae378 100644 --- a/crates/nlp/src/tokenizers/stream.rs +++ b/crates/nlp/src/tokenizers/stream.rs @@ -73,6 +73,7 @@ impl WordStemTokenizer { } } +/* pub fn symbols(input: &str) -> bool { hashify::set!( input.as_bytes(), @@ -7824,6 +7825,7 @@ pub fn symbols(input: &str) -> bool { ) } +*/ #[cfg(test)] pub mod tests { use crate::tokenizers::{ diff --git a/crates/spam-filter/Cargo.toml b/crates/spam-filter/Cargo.toml index 652fe983..405de6ee 100644 --- a/crates/spam-filter/Cargo.toml +++ b/crates/spam-filter/Cargo.toml @@ -27,6 +27,9 @@ sha1 = "0.10" sha2 = "0.10.6" compact_str = "0.9.0" rkyv = { version = "0.8.10", features = ["little_endian"] } +serde = { version = "1.0", features = ["derive"]} +unicode-general-category = "1.1.0" +unicode-normalization = "0.1.25" [features] test_mode = [] diff --git a/crates/spam-filter/src/analysis/domain.rs b/crates/spam-filter/src/analysis/domain.rs index 557ef9ab..6a3d6c70 100644 --- a/crates/spam-filter/src/analysis/domain.rs +++ b/crates/spam-filter/src/analysis/domain.rs @@ -55,10 +55,15 @@ impl SpamFilterAnalyzeDomain for Server { .into_iter() .flatten() { - if let Host::Name(name) = host - && let Some(name) = Hostname::new(name.as_ref()).sld - { - domains.insert(ElementLocation::new(name, Location::HeaderReceived)); + if let Host::Name(name) = host { + let host = Hostname::new(name.as_ref()); + + if host.sld.is_some() { + domains.insert(ElementLocation::new( + host.fqdn, + Location::HeaderReceived, + )); + } } } } diff --git a/crates/spam-filter/src/analysis/init.rs b/crates/spam-filter/src/analysis/init.rs index 5378891a..42d820b0 100644 --- a/crates/spam-filter/src/analysis/init.rs +++ b/crates/spam-filter/src/analysis/init.rs @@ -86,7 +86,7 @@ impl SpamFilterInit for Server { from = header.value().as_address().and_then(|addrs| addrs.first()); } HeaderName::Other(name) - if input.is_train && !found_spam_status && name.eq("X-Spam-Status") => + if input.is_train && !found_spam_status && name.eq("X-Spam-Result") => { for token in header .value() diff --git a/crates/spam-filter/src/analysis/score.rs b/crates/spam-filter/src/analysis/score.rs index 8af9d549..e469e7e8 100644 --- a/crates/spam-filter/src/analysis/score.rs +++ b/crates/spam-filter/src/analysis/score.rs @@ -103,9 +103,7 @@ impl SpamFilterAnalyzeScore for Server { if total_results > 0 { avg_confidence /= total_results as f32; - } - if avg_confidence != 0.0 { let tag = avg_confidence.spam_tag(); let score = self .core @@ -132,7 +130,7 @@ impl SpamFilterAnalyzeScore for Server { } else { let mut headers = String::with_capacity(header_len + 40); results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap().then_with(|| a.0.cmp(b.0))); - headers.push_str("X-Spam-Status: "); + headers.push_str("X-Spam-Result: "); for (idx, (tag, score)) in results.into_iter().enumerate() { if idx > 0 { headers.push_str(",\r\n\t"); diff --git a/crates/spam-filter/src/modules/classifier.rs b/crates/spam-filter/src/modules/classifier.rs index 1d60deb8..15f966c7 100644 --- a/crates/spam-filter/src/modules/classifier.rs +++ b/crates/spam-filter/src/modules/classifier.rs @@ -8,19 +8,18 @@ use crate::analysis::domain::SpamFilterAnalyzeDomain; use crate::analysis::init::SpamFilterInit; use crate::analysis::is_trusted_domain; use crate::analysis::url::SpamFilterAnalyzeUrl; -use crate::{Email, IpParts, SpamFilterContext, TextPart, analysis::url::UrlParts}; +use crate::modules::html::{A, ALT, HREF, HtmlToken, IMG, SRC, TITLE}; +use crate::{Email, SpamFilterContext, TextPart}; use crate::{Hostname, SpamFilterInput}; use common::config::spamfilter::SpamClassifierModel; use common::{Server, config::spamfilter::Location, ipc::BroadcastEvent}; use mail_auth::DmarcResult; use mail_parser::{MessageParser, MimeHeaders}; use nlp::classifier::feature::Sample; +use nlp::tokenizers::types::TypesTokenizer; use nlp::{ classifier::{feature::Feature, sgd::TextClassifier}, - tokenizers::{ - stream::{WordStemTokenizer, symbols}, - types::TokenType, - }, + tokenizers::{stream::WordStemTokenizer, types::TokenType}, }; use std::time::Instant; use std::{ @@ -40,7 +39,8 @@ use store::{ use tokio::sync::{mpsc, oneshot}; use trc::{AddContext, SpamEvent}; use types::{blob_hash::BlobHash, collection::Collection, field::PrincipalField}; -use unicode_security::is_potential_mixed_script_confusable_char; +use unicode_general_category::{GeneralCategory, get_general_category}; +use unicode_normalization::UnicodeNormalization; use unicode_security::mixed_script::AugmentedScriptSet; pub trait SpamClassifier { @@ -372,7 +372,7 @@ impl SpamClassifier for Server { { has_prediction = true; model - .predict(&feature_builder.build(&tokens, account_id.into())) + .predict_proba_sample(&feature_builder.build(&tokens, account_id.into())) .into() } else { None @@ -384,7 +384,7 @@ impl SpamClassifier for Server { ctx.result.classifier_confidence = classifier_confidence; } else { // None of the recipients are local, default to global model prediction - let prediction = model.predict(&feature_builder.build(&tokens, None)); + let prediction = model.predict_proba_sample(&feature_builder.build(&tokens, None)); ctx.result.classifier_confidence = vec![prediction.into(); ctx.input.env_rcpt_to.len()]; } @@ -465,14 +465,17 @@ impl SpamClassifier for Server { value: url.host.fqdn.as_str().into(), }); } - if let Some(path) = url.parts.path_and_query() { - for token in path.as_str().split(|c: char| !c.is_alphanumeric()) { - if token.len() > 1 { - let token = truncate_word(token, MAX_TOKEN_LENGTH); - tokens.insert(Token::Url { - value: format!("_{token}").into(), - }); - } + for token in url + .parts + .path() + .split(['/', '.', '_']) + .filter(|v| v.chars().all(|ch| ch.is_alphabetic())) + { + if token.len() > 2 { + let token = truncate_word(token, MAX_TOKEN_LENGTH); + tokens.insert(Token::Url { + value: format!("_{token}").into(), + }); } } } @@ -488,7 +491,7 @@ impl SpamClassifier for Server { let host_sld = host.sld_or_default(); if !is_trusted_domain(self, host_sld, ctx.input.span_id).await { - if host_sld != host.fqdn { + if !host_sld.is_empty() && host_sld != host.fqdn { tokens.insert(Token::Hostname { value: host_sld.to_string().into(), }); @@ -518,10 +521,18 @@ impl SpamClassifier for Server { value: lower_prefix("!", truncate_word(ext, MAX_TOKEN_LENGTH)).into(), }); } - for token in name.split(|c: char| !c.is_alphanumeric()) { - if token.len() > 1 { - tokens.insert(Token::Attachment { - value: lower_prefix("_", truncate_word(token, MAX_TOKEN_LENGTH)).into(), + let name = name.to_lowercase(); + let word_tokenizer = WordStemTokenizer::new(&name); + for token in TypesTokenizer::new(&name) { + if let TokenType::Alphabetic(word) = token.word { + word_tokenizer.tokenize(word, |token| { + tokens.insert(Token::Attachment { + value: format!( + "_{}", + truncate_word(token.as_ref(), MAX_TOKEN_LENGTH) + ) + .into(), + }); }); } } @@ -550,6 +561,7 @@ impl SpamClassifier for Server { tokens.insert_type( &WordStemTokenizer::new(&ctx.output.subject_thread_lc), token, + false, ); } @@ -563,13 +575,14 @@ impl SpamClassifier for Server { .map(|idx| *idx as usize); let mut alt_tokens = Tokens::default(); for (idx, part) in ctx.output.text_parts.iter().enumerate() { - if Some(idx) == body_idx + let is_body = Some(idx) == body_idx; + if is_body || (!ctx.input.message.text_body.contains(&(idx as u32)) && !ctx.input.message.html_body.contains(&(idx as u32))) { - tokens.insert_text_part(part); + tokens.insert_text_part(part, is_body); } else { - alt_tokens.insert_text_part(part); + alt_tokens.insert_text_part(part, false); } } if !alt_tokens.0.is_empty() { @@ -586,15 +599,15 @@ impl SpamClassifier for Server { const MAX_TOKEN_LENGTH: usize = 16; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive( + Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, PartialOrd, Ord, +)] +#[serde(tag = "type", rename_all = "snake_case")] pub enum Token<'x> { - // User types Word { value: Cow<'x, str> }, Number { code: [u8; 2] }, Alphanumeric { code: [u8; 4] }, - Symbol { value: String }, - - // User and global types + UnicodeCategory { value: &'x str }, Sender { value: Cow<'x, str> }, Asn { number: [u8; 4] }, Url { value: Cow<'x, str> }, @@ -602,87 +615,124 @@ pub enum Token<'x> { Hostname { value: Cow<'x, str> }, Attachment { value: Cow<'x, str> }, MimeType { value: String }, + HtmlImage { src: &'x str }, + HtmlAnchor { href: &'x str }, } #[derive(Debug)] -pub struct Tokens<'x>(HashMap, f32, RandomState>); +pub struct Tokens<'x>(pub HashMap, f32, RandomState>); impl<'x> Tokens<'x> { - fn insert_text_part(&mut self, part: &'x TextPart<'x>) { + fn insert_text_part(&mut self, part: &'x TextPart<'x>, is_body: bool) { match part { TextPart::Plain { text_body, tokens } => { let word_tokenizer = WordStemTokenizer::new(text_body); for token in tokens { - self.insert_type(&word_tokenizer, token); + self.insert_type(&word_tokenizer, token, is_body); + } + + if is_body + && (tokens.is_empty() + || !tokens.iter().any(|t| matches!(t, TokenType::Alphabetic(_)))) + { + self.insert(Token::Word { + value: "_null".into(), + }); } } TextPart::Html { - text_body, tokens, .. + text_body, + tokens, + html_tokens, } => { let word_tokenizer = WordStemTokenizer::new(text_body); for token in tokens { - self.insert_type(&word_tokenizer, token); + self.insert_type(&word_tokenizer, token, is_body); + } + + if is_body { + if tokens.is_empty() + || !tokens.iter().any(|t| matches!(t, TokenType::Alphabetic(_))) + { + self.insert(Token::Word { + value: "_null".into(), + }); + } + + for token in html_tokens { + if let HtmlToken::StartTag { + name: A | IMG, + attributes, + .. + } = token + { + for (name, value) in attributes { + match (*name, value) { + (ALT | TITLE, Some(value)) => { + for token in TypesTokenizer::new(value) { + self.insert_type(&word_tokenizer, &token.word, is_body); + } + } + (SRC, Some(value)) => { + self.insert(Token::HtmlImage { + src: value.split_once(':').unwrap_or_default().0, + }); + } + (HREF, Some(value)) => { + self.insert(Token::HtmlAnchor { + href: value.split_once(':').unwrap_or_default().0, + }); + } + _ => {} + } + } + } + } } } TextPart::None => (), } } - fn insert_type( + fn insert_type, E, U, I>( &mut self, word_tokenizer: &WordStemTokenizer, - token: &'x TokenType, Email, UrlParts<'x>, IpParts>, + token: &TokenType, + is_body: bool, ) { match token { TokenType::Alphabetic(word) => { + let word = word.as_ref(); let mut set: Option = None; let mut has_confusables = false; - let mut is_lowercase = true; + let mut upper_count = 0; for ch in word.chars() { + if ch.is_uppercase() { + upper_count += 1; + } + has_confusables |= - !ch.is_ascii() && is_potential_mixed_script_confusable_char(ch); - is_lowercase &= ch.is_lowercase() || !ch.is_uppercase(); + !ch.is_ascii() && !std::iter::once(ch).nfc().eq(std::iter::once(ch).nfkc()); set.get_or_insert_default().intersect_with(ch.into()); } let is_mixed_script = set.is_some_and(|set| set.is_empty()); if (is_mixed_script || has_confusables) - && let Ok(word) = decancer::cure(word.as_ref(), decancer::Options::default()) + && let Ok(cured_word) = decancer::cure(word, decancer::Options::default()) { if word.len() > MAX_TOKEN_LENGTH { self.insert(Token::Word { - value: truncate_word(word.as_str(), MAX_TOKEN_LENGTH) + value: truncate_word(cured_word.as_str(), MAX_TOKEN_LENGTH) .to_string() .into(), }); } else { self.insert(Token::Word { - value: String::from(word).into(), + value: String::from(cured_word).into(), }); } - } else if is_lowercase { - word_tokenizer.tokenize(word, |value| match value { - Cow::Borrowed(value) => { - self.insert(Token::Word { - value: truncate_word(value, MAX_TOKEN_LENGTH).into(), - }); - } - Cow::Owned(value) => { - if value.len() <= MAX_TOKEN_LENGTH { - self.insert(Token::Word { - value: value.into(), - }); - } else { - self.insert(Token::Word { - value: truncate_word(&value, MAX_TOKEN_LENGTH) - .to_string() - .into(), - }); - } - } - }); } else { let word = word.to_lowercase(); word_tokenizer.tokenize(&word, |token| { @@ -693,13 +743,29 @@ impl<'x> Tokens<'x> { }); }); } + + if is_body { + self.insert(Token::Word { + value: "_word".into(), + }); + if word.len() == upper_count && word.len() > 3 { + self.insert(Token::Word { + value: "_allcaps".into(), + }); + } + } } TokenType::Alphanumeric(word) => { self.insert(Token::from_alphanumeric(word.as_ref())); } TokenType::UrlNoHost(url) => { - for token in url.to_lowercase().split(|c: char| !c.is_alphanumeric()) { - if token.len() > 1 { + for token in url + .as_ref() + .to_lowercase() + .split(['/', '.', '_']) + .filter(|v| v.chars().all(|ch| ch.is_alphabetic())) + { + if token.len() > 2 { let token = truncate_word(token, MAX_TOKEN_LENGTH); self.insert(Token::Url { value: format!("_{token}").into(), @@ -707,10 +773,22 @@ impl<'x> Tokens<'x> { } } } - TokenType::Other(ch) => { - let value = ch.to_string(); - if symbols(&value) { - self.insert(Token::Symbol { value }); + TokenType::Other(ch) | TokenType::Punctuation(ch) => { + let category = get_general_category(*ch); + if !matches!( + category, + GeneralCategory::ClosePunctuation + | GeneralCategory::ConnectorPunctuation + | GeneralCategory::DashPunctuation + | GeneralCategory::FinalPunctuation + | GeneralCategory::InitialPunctuation + | GeneralCategory::OpenPunctuation + | GeneralCategory::OtherPunctuation + | GeneralCategory::SpaceSeparator + ) { + self.insert(Token::UnicodeCategory { + value: category.abbreviation(), + }); } } TokenType::Integer(word) => { @@ -727,7 +805,6 @@ impl<'x> Tokens<'x> { TokenType::Email(_) | TokenType::Url(_) | TokenType::UrlNoScheme(_) - | TokenType::Punctuation(_) | TokenType::Space => {} } } @@ -741,29 +818,32 @@ impl<'x> Tokens<'x> { } fn insert_email(&mut self, email: &'x Email, is_sender: bool) { - if is_sender { - self.insert_if_missing(Token::Sender { - value: email.address.as_str().into(), - }); - self.insert_if_missing(Token::Sender { - value: email.domain_part.fqdn.as_str().into(), - }); - if let Some(sld) = &email.domain_part.sld - && sld != &email.domain_part.fqdn - { - self.insert_if_missing(Token::Sender { value: sld.into() }); - } - } else { - self.insert_if_missing(Token::Email { - value: email.address.as_str().into(), - }); - self.insert_if_missing(Token::Email { - value: email.domain_part.fqdn.as_str().into(), - }); - if let Some(sld) = &email.domain_part.sld - && sld != &email.domain_part.fqdn - { - self.insert_if_missing(Token::Email { value: sld.into() }); + if !email.address.is_empty() { + if is_sender { + self.insert_if_missing(Token::Sender { + value: email.address.as_str().into(), + }); + self.insert_if_missing(Token::Sender { + value: email.domain_part.fqdn.as_str().into(), + }); + if let Some(sld) = &email.domain_part.sld + && sld != &email.domain_part.fqdn + { + self.insert_if_missing(Token::Sender { value: sld.into() }); + } + } else { + self.insert_if_missing(Token::Email { + value: email.address.as_str().into(), + }); + self.insert_if_missing(Token::Email { + value: email.domain_part.fqdn.as_str().into(), + }); + if let Some(sld) = &email.domain_part.sld + && !sld.is_empty() + && sld != &email.domain_part.fqdn + { + self.insert_if_missing(Token::Email { value: sld.into() }); + } } } } @@ -950,7 +1030,7 @@ impl Feature for Token<'_> { Token::Word { .. } => 0, Token::Number { .. } => 1, Token::Alphanumeric { .. } => 2, - Token::Symbol { .. } => 3, + Token::UnicodeCategory { .. } => 3, Token::Sender { .. } => 4, Token::Asn { .. } => 5, Token::Url { .. } => 6, @@ -958,6 +1038,8 @@ impl Feature for Token<'_> { Token::Hostname { .. } => 8, Token::Attachment { .. } => 9, Token::MimeType { .. } => 10, + Token::HtmlImage { .. } => 11, + Token::HtmlAnchor { .. } => 12, } } @@ -966,7 +1048,7 @@ impl Feature for Token<'_> { Token::Word { value } => value.as_bytes(), Token::Number { code } => code, Token::Alphanumeric { code } => code, - Token::Symbol { value } => value.as_bytes(), + Token::UnicodeCategory { value } => value.as_bytes(), Token::Sender { value } => value.as_bytes(), Token::Asn { number } => number, Token::Url { value } => value.as_bytes(), @@ -974,6 +1056,8 @@ impl Feature for Token<'_> { Token::Hostname { value } => value.as_bytes(), Token::Attachment { value } => value.as_bytes(), Token::MimeType { value } => value.as_bytes(), + Token::HtmlImage { src } => src.as_bytes(), + Token::HtmlAnchor { href } => href.as_bytes(), } } } diff --git a/crates/spam-filter/src/modules/html.rs b/crates/spam-filter/src/modules/html.rs index 3b3d758e..88871bfd 100644 --- a/crates/spam-filter/src/modules/html.rs +++ b/crates/spam-filter/src/modules/html.rs @@ -4,10 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ - use mail_parser::decoders::html::add_html_token; -#[derive(Debug, Eq, PartialEq, Clone)] +#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] +#[serde(tag = "type")] pub enum HtmlToken { StartTag { name: u64, @@ -35,6 +35,12 @@ pub(crate) const META: u64 = (b'm' as u64) | ((b'e' as u64) << 8) | ((b't' as u64) << 16) | ((b'a' as u64) << 24); pub(crate) const LINK: u64 = (b'l' as u64) | ((b'i' as u64) << 8) | ((b'n' as u64) << 16) | ((b'k' as u64) << 24); +pub(crate) const ALT: u64 = (b'a' as u64) | ((b'l' as u64) << 8) | ((b't' as u64) << 16); +pub(crate) const TITLE: u64 = (b't' as u64) + | ((b'i' as u64) << 8) + | ((b't' as u64) << 16) + | ((b'l' as u64) << 24) + | ((b'e' as u64) << 32); pub(crate) const HREF: u64 = (b'h' as u64) | ((b'r' as u64) << 8) | ((b'e' as u64) << 16) | ((b'f' as u64) << 24); @@ -203,8 +209,8 @@ pub fn html_to_tokens(input: &str) -> Vec { match ch { b'>' if !in_quote => { if !value.is_empty() { - let value = String::from_utf8(value) - .unwrap_or_default(); + let value = + String::from_utf8(value).unwrap_or_default(); if let Some((_, v)) = attributes.last_mut() { *v = value.into(); } else { diff --git a/tests/resources/smtp/antispam/replies_out.test b/tests/resources/smtp/antispam/classifier.ham similarity index 98% rename from tests/resources/smtp/antispam/replies_out.test rename to tests/resources/smtp/antispam/classifier.ham index 31f325ef..ae8a5937 100644 --- a/tests/resources/smtp/antispam/replies_out.test +++ b/tests/resources/smtp/antispam/classifier.ham @@ -1,78 +1,59 @@ -expect - Message-ID: Subject: i have been trying to research via sa mirrors and search engines if a canned script exists giving clients access to their user_prefs options via a web based cgi interface numerous isps provide this feature to clients but so far i can find nothing our configuration uses amavis postfix and clamav for virus filtering and procmail with spamassassin for spam filtering i would prefer not to have to write a script myself but will appreciate any suggestions this URL email is sponsored by osdn tired of that same old cell phone get a new here for free URL _______________________________________________ spamassassin talk mailing list spamassassin talk URL URL -expect - Message-ID: mid2@foobar.org Subject: hello have you seen and discussed this article and his approach thank you URL hell there are no rules here we re trying to accomplish something thomas alva edison this URL email is sponsored by osdn tired of that same old cell phone get a new here for free URL _______________________________________________ spamassassin devel mailing list spamassassin devel URL URL -expect - Message-ID: Subject: hi all apologies for the possible silly question i don t think it is but but is eircom s adsl service nat ed and what implications would that have for voip i know there are difficulties with voip or connecting to clients connected to a nat ed network from the internet wild i e machines with static real ips any help pointers would be helpful cheers rgrds bernard bernard tyers national centre for sensor research p NUMBER NUMBER NUMBER NUMBER e bernard tyers URL w URL l nNUMBER _______________________________________________ iiu mailing list iiu URL URL -expect - Message-ID: Subject: can someone explain what type of operating system solaris is as ive never seen or used it i dont know wheather to get a server from sun or from dell i would prefer a linux based server and sun seems to be the one for that but im not sure if solaris is a distro of linux or a completely different operating system can someone explain kiall mac innes irish linux users group ilug URL URL for un subscription information list maintainer listmaster URL -expect - Message-ID: Subject: folks my first time posting have a bit of unix experience but am new to linux just got a new pc at home dell box with windows xp added a second hard disk for linux partitioned the disk and have installed suse NUMBER NUMBER from cd which went fine except it didn t pick up my monitor i have a dell branded eNUMBERfpp NUMBER lcd flat panel monitor and a nvidia geforceNUMBER tiNUMBER video card both of which are probably too new to feature in suse s default set i downloaded a driver from the nvidia website and installed it using rpm then i ran saxNUMBER as was recommended in some postings i found on the net but it still doesn t feature my video card in the available list what next another problem i have a dell branded keyboard and if i hit caps lock twice the whole machine crashes in linux not windows even the on off switch is inactive leaving me to reach for the power cable instead if anyone can help me in any way with these probs i d be really grateful i ve searched the net but have run out of ideas or should i be going for a different version of linux such as redhat opinions welcome thanks a lot peter irish linux users group ilug URL URL for un subscription information list maintainer listmaster URL -expect - Message-ID: Subject: has anyone seen heard of used some package that would let a random person go to a webpage create a mailing list then administer that list also of course let ppl sign up for the lists and manage their subscriptions similar to the old URL but i d like to have it running on my server not someone elses chris URL -expect - Message-ID: Subject: hi thank you for the useful replies i have found some interesting tutorials in the ibm developer connection URL and URL registration is needed i will post the same message on the web application security list as suggested by someone for now i thing i will use mdNUMBER for password checking i will use the approach described in secure programmin fo linux and unix how to i will separate the authentication module so i can change its implementation at anytime thank you again mario torre please avoid sending me word or powerpoint attachments see URL -expect - Message-ID: Subject: hehe sorry but if you hit caps lock twice the computer crashes theres one ive never heard before have you tryed dell support yet i think dell computers prefer redhat dell provide some computers pre loaded with red hat i dont know for sure tho so get someone elses opnion as well as mine original message from ilug admin URL mailto ilug admin URL on behalf of peter staunton sent NUMBER august NUMBER NUMBER NUMBER to ilug URL subject ilug newbie seeks advice suse NUMBER NUMBER folks my first time posting have a bit of unix experience but am new to linux just got a new pc at home dell box with windows xp added a second hard disk for linux partitioned the disk and have installed suse NUMBER NUMBER from cd which went fine except it didn t pick up my monitor i have a dell branded eNUMBERfpp NUMBER lcd flat panel monitor and a nvidia geforceNUMBER tiNUMBER video card both of which are probably too new to feature in suse s default set i downloaded a driver from the nvidia website and installed it using rpm then i ran saxNUMBER as was recommended in some postings i found on the net but it still doesn t feature my video card in the available list what next another problem i have a dell branded keyboard and if i hit caps lock twice the whole machine crashes in linux not windows even the on off switch is inactive leaving me to reach for the power cable instead if anyone can help me in any way with these probs i d be really grateful i ve searched the net but have run out of ideas or should i be going for a different version of linux such as redhat opinions welcome thanks a lot peter irish linux users group ilug URL URL for un subscription information list maintainer listmaster URL irish linux users group ilug URL URL for un subscription information list maintainer listmaster URL -expect - Message-ID: Subject: it will function as a router if that is what you wish it even looks like the modem s embedded os is some kind of linux being that it has interesting interfaces like ethNUMBER i don t use it as a router though i just have it do the absolute minimum dsl stuff and do all the really fun stuff like pppoe on my linux box also the manual tells you what the default password is don t forget to run pppoe over the alcatel speedtouch NUMBERi as in my case you have to have a bridge configured in the router modem s software this lists your vci values etc also does anyone know if the high end speedtouch with NUMBER ethernet ports can act as a full router or do i still need to run a pppoe stack on the linux box regards vin irish linux users group ilug URL URL for un subscription information list maintainer listmaster URL irish linux users group ilug URL URL for un subscription information list maintainer listmaster URL -expect - Message-ID: Subject: all is it just me or has there been a massive increase in the amount of email being falsely bounced around the place i ve already received email from a number of people i don t know asking why i am sending them email these can be explained by servers from russia and elsewhere coupled with the false emails i received myself it s really starting to annoy me am i the only one seeing an increase in recent weeks martin martin whelan déise design URL tel NUMBER NUMBER our core product déiseditor allows organisations to publish information to their web site in a fast and cost effective manner there is no need for a full time web developer as the site can be easily updated by the organisations own staff instant updates to keep site information fresh sites which are updated regularly bring users back visit URL for a demonstration déiseditor managing your information _______________________________________________ iiu mailing list iiu URL URL ,0 + diff --git a/tests/resources/smtp/antispam/classifier.spam b/tests/resources/smtp/antispam/classifier.spam new file mode 100644 index 00000000..9cadf341 --- /dev/null +++ b/tests/resources/smtp/antispam/classifier.spam @@ -0,0 +1,50 @@ +Subject: save up to NUMBER on life insurance + +why spend more than you have to life quote savings ensuring your family s financial security is very important life quote savings makes buying life insurance simple and affordable we provide free access to the very best companies and the lowest rates life quote savings is fast easy and saves you money let us help you get started with the best values in the country on new coverage you can save hundreds or even thousands of dollars by requesting a free quote from lifequote savings our service will take you less than NUMBER minutes to complete shop and compare save up to NUMBER on all types of life insurance hyperlink click here for your free quote protecting your family is the best investment you ll ever make if you are in receipt of this email in error and or wish to be removed from our list hyperlink please click here and type remove if you reside in any state which prohibits e mail solicitations for insurance please disregard this email + + +Subject: a powerhouse gifting program + +you don t want to miss get in with the founders the major players are on this one for once be where the players are this is your private invitation experts are calling this the fastest way to huge cash flow ever conceived leverage NUMBER NUMBER into NUMBER NUMBER over and over again the question here is you either want to be wealthy or you don t which one are you i am tossing you a financial lifeline and for your sake i hope you grab onto it and hold on tight for the ride of your life testimonials hear what average people are doing their first few days we ve received NUMBER NUMBER in NUMBER day and we are doing that over and over again q s in al i m a single mother in fl and i ve received NUMBER NUMBER in the last NUMBER days d s in fl i was not sure about this when i sent off my NUMBER NUMBER pledge but i got back NUMBER NUMBER the very next day l l in ky i didn t have the money so i found myself a partner to work this with we have received NUMBER NUMBER over the last NUMBER days i think i made the right decision don t you k c in fl i pick up NUMBER NUMBER my first day and i they gave me free leads and all the training you can too j w in ca announcing we will close your sales for you and help you get a fax blast immediately upon your entry you make the money free leads training don t wait call now fax back to NUMBER NUMBER NUMBER NUMBER or call NUMBER NUMBER NUMBER NUMBER name__________________________________phone___________________________________________ fax_____________________________________email____________________________________________ best time to call_________________________time zone________________________________________ this message is sent in compliance of the new e mail bill per section NUMBER paragraph a NUMBER c of s NUMBER further transmissions by the sender of this email may be stopped at no cost to you by sending a reply to this email address with the word remove in the subject line errors omissions and exceptions excluded this is not spam i have compiled this list from our replicate database relative to seattle marketing group the gigt or turbo team for the sole purpose of these communications your continued inclusion is only by your gracious permission if you wish to not receive this mail from me please send an email to tesrewinter URL with remove in the subject and you will be deleted immediately + + +Subject: help wanted + +we are a NUMBER year old fortune NUMBER company that is growing at a tremendous rate we are looking for individuals who want to work from home this is an opportunity to make an excellent income no experience is required we will train you so if you are looking to be employed from home with a career that has vast opportunities then go URL we are looking for energetic and self motivated people if that is you than click on the link and fill out the form and one of our employement specialist will contact you to be removed from our link simple go to URL + + +Subject: tired of the bull out there + +want to stop losing money want a real money maker receive NUMBER NUMBER NUMBER NUMBER today experts are calling this the fastest way to huge cash flow ever conceived a powerhouse gifting program you don t want to miss we work as a team this is your private invitation get in with the founders this is where the big boys play the major players are on this one for once be where the players are this is a system that will drive NUMBER NUMBER s to your doorstep in a short period of time leverage NUMBER NUMBER into NUMBER NUMBER over and over again the question here is you either want to be wealthy or you don t which one are you i am tossing you a financial lifeline and for your sake i hope you grab onto it and hold on tight for the ride of your life testimonials hear what average people are doing their first few days we ve received NUMBER NUMBER in NUMBER day and we are doing that over and over again q s in al i m a single mother in fl and i ve received NUMBER NUMBER in the last NUMBER days d s in fl i was not sure about this when i sent off my NUMBER NUMBER pledge but i got back NUMBER NUMBER the very next day l l in ky i didn t have the money so i found myself a partner to work this with we have received NUMBER NUMBER over the last NUMBER days i think i made the right decision don t you k c in fl i pick up NUMBER NUMBER my first day and i they gave me free leads and all the training you can too j w in ca this will be the most important call you make this year free leads training announcing we will close your sales for you and help you get a fax blast immediately upon your entry you make the money free leads training don t wait call now NUMBER NUMBER NUMBER NUMBER print and fax to NUMBER NUMBER NUMBER NUMBER or send an email requesting more information to successleads URL please include your name and telephone number receive NUMBER NUMBER free leads just for responding a NUMBER NUMBER value name___________________________________ phone___________________________________ fax_____________________________________ email___________________________________ this message is sent in compliance of the new e mail bill per section NUMBER paragraph a NUMBER c of s NUMBER further transmissions by the sender of this email may be stopped at no cost to you by sending a reply to this email address with the word remove in the subject line errors omissions and exceptions excluded this is not spam i have compiled this list from our replicate database relative to seattle marketing group the gigt or turbo team for the sole purpose of these communications your continued inclusion is only by your gracious permission if you wish to not receive this mail from me please send an email to tesrewinter URL with remove in the subject and you will be deleted immediately + + +Subject: cellular phone accessories + +all at below wholesale prices http NUMBER NUMBER NUMBER NUMBER NUMBER sites merchant sales hands free ear buds NUMBER NUMBER phone holsters NUMBER NUMBER booster antennas only NUMBER NUMBER phone cases NUMBER NUMBER car chargers NUMBER NUMBER face plates as low as NUMBER NUMBER lithium ion batteries as low as NUMBER NUMBER http NUMBER NUMBER NUMBER NUMBER NUMBER sites merchant sales click below for accessories on all nokia motorola lg nextel samsung qualcomm ericsson audiovox phones at below wholesale prices http NUMBER NUMBER NUMBER NUMBER NUMBER sites merchant sales if you need assistance please call us NUMBER NUMBER NUMBER to be removed from future mailings please send your remove request to remove me now NUMBER URL thank you and have a super day + + +Subject: conferencing made easy + +only NUMBER cents per minute including long distance no setup fees no contracts or monthly fees call anytime from anywhere to anywhere connects up to NUMBER participants simplicity in set up and administration operator help available NUMBER NUMBER the highest quality service for the lowest rate in the industry fill out the form below to find out how you can lower your phone bill every month required input field name web address company name state business phone home phone email address type of business to be removed from our distribution lists please hyperlink click here + + +Subject: dear friend + +i am mrs sese seko widow of late president mobutu sese seko of zaire now known as democratic republic of congo drc i am moved to write you this letter this was in confidence considering my presentcircumstance and situation i escaped along with my husband and two of our sons george kongolo and basher out of democratic republic of congo drc to abidjan cote d ivoire where my family and i settled while we later moved to settled in morroco where my husband later died of cancer disease however due to this situation we decided to changed most of my husband s billions of dollars deposited in swiss bank and other countries into other forms of money coded for safe purpose because the new head of state of dr mr laurent kabila has made arrangement with the swiss government and other european countries to freeze all my late husband s treasures deposited in some european countries hence my children and i decided laying low in africa to study the situation till when things gets better like now that president kabila is dead and the son taking over joseph kabila one of my late husband s chateaux in southern france was confiscated by the french government and as such i had to change my identity so that my investment will not be traced and confiscated i have deposited the sum eighteen million united state dollars us NUMBER NUMBER NUMBER NUMBER with a security company for safekeeping the funds are security coded to prevent them from knowing the content what i want you to do is to indicate your interest that you will assist us by receiving the money on our behalf acknowledge this message so that i can introduce you to my son kongolo who has the out modalities for the claim of the said funds i want you to assist in investing this money but i will not want my identity revealed i will also want to buy properties and stock in multi national companies and to engage in other safe and non speculative investments may i at this point emphasise the high level of confidentiality which this business demands and hope you will not betray the trust and confidence which i repose in you in conclusion if you want to assist us my son shall put you in the picture of the business tell you where the funds are currently being maintained and also discuss other modalities including remunerationfor your services for this reason kindly furnish us your contact information that is your personal telephone and fax number for confidential URL regards mrs m sese seko + + +Subject: lowest rates available for term life insurance + +take a moment and fill out our online form to see the low rate you qualify for save up to NUMBER from regular rates smokers accepted URL representing quality nationwide carriers act now to easily remove your address from the list go to URL please allow NUMBER NUMBER hours for removal + + +Subject: central bank of nigeria foreign remittance + +dept tinubu square lagos nigeria email smith_j URL NUMBERth of august NUMBER attn president ceo strictly private business proposal i am mr johnson s abu the bills and exchange director at the foreignremittance department of the central bank of nigeria i am writingyou this letter to ask for your support and cooperation to carrying thisbusiness opportunity in my department we discovered abandoned the sumof us NUMBER NUMBER NUMBER NUMBER thirty seven million four hundred thousand unitedstates dollars in an account that belong to one of our foreign customers an american late engr john creek junior an oil merchant with the federal government of nigeria who died along with his entire family of a wifeand two children in kenya airbus aNUMBER NUMBER flight kqNUMBER in novemberNUMBER since we heard of his death we have been expecting his next of kin tocome over and put claims for his money as the heir because we cannotrelease the fund from his account unless someone applies for claims asthe next of kin to the deceased as indicated in our banking guidelines unfortunately neither their family member nor distant relative hasappeared to claim the said fund upon this discovery i and other officialsin my department have agreed to make business with you release the totalamount into your account as the heir of the fund since no one came forit or discovered either maintained account with our bank other wisethe fund will be returned to the bank treasury as unclaimed fund we have agreed that our ratio of sharing will be as stated thus NUMBER for you as foreign partner and NUMBER for us the officials in my department upon the successful completion of this transfer my colleague and i willcome to your country and mind our share it is from our NUMBER we intendto import computer accessories into my country as way of recycling thefund to commence this transaction we require you to immediately indicateyour interest by calling me or sending me a fax immediately on the abovetelefax and enclose your private contact telephone fax full nameand address and your designated banking co ordinates to enable us fileletter of claim to the appropriate department for necessary approvalsbefore the transfer can be made note also this transaction must be kept strictly confidential becauseof its nature nb please remember to give me your phone and fax no mr johnson smith abu irish linux users group ilug URL URL for un subscription information list maintainer listmaster URL + + +Subject: dear stuart + +are you tired of searching for love in all the wrong places find love now at URL URL browse through thousands of personals in your area join for free URL search e mail chat use URL to meet cool guys and hot girls go NUMBER on NUMBER or use our private chat rooms click on the link to get started URL find love now you have received this email because you have registerd with emailrewardz or subscribed through one of our marketing partners if you have received this message in error or wish to stop receiving these great offers please click the remove link above to unsubscribe from these mailings please click here URL + + diff --git a/tests/resources/smtp/antispam/bayes_classify.test b/tests/resources/smtp/antispam/classifier.test similarity index 71% rename from tests/resources/smtp/antispam/bayes_classify.test rename to tests/resources/smtp/antispam/classifier.test index ac9c90ba..f4804f78 100644 --- a/tests/resources/smtp/antispam/bayes_classify.test +++ b/tests/resources/smtp/antispam/classifier.test @@ -1,19 +1,22 @@ -expect BAYES_SPAM +envelope_to hello@world.com +expect PROB_SPAM_HIGH Subject: save up to NUMBER on life insurance why spend more than you have to life quote savings ensuring your family s financial security is very important life quote savings makes buying life insurance simple and affordable we provide free access to the very best companies and the lowest rates life quote savings is fast easy and saves you money let us help you get started with the best values in the country on new coverage you can save hundreds or even thousands of dollars by requesting a free quote from lifequote savings our service will take you less than NUMBER minutes to complete shop and compare save up to NUMBER on all types of life insurance hyperlink click here for your free quote protecting your family is the best investment you ll ever make if you are in receipt of this email in error and or wish to be removed from our list hyperlink please click here and type remove if you reside in any state which prohibits e mail solicitations for insurance please disregard this email -expect BAYES_HAM +envelope_to hello@world.com +expect PROB_HAM_HIGH Subject: can someone explain what type of operating system solaris is as ive never seen or used it i dont know wheather to get a server from sun or from dell i would prefer a linux based server and sun seems to be the one for that but im not sure if solaris is a distro of linux or a completely different operating system can someone explain kiall mac innes irish linux users group ilug URL URL for un subscription information list maintainer listmaster URL -expect +envelope_to hello@world.com +expect PROB_HAM_LOW -Subject: classifier test +Subject: Lorem ipsum dolor sit amet, consectetur adipiscing elit -this is a novel text that the bayes classifier has never seen before, it should be classified as ham or non-ham +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/tests/resources/smtp/antispam/classifier_features.test b/tests/resources/smtp/antispam/classifier_features.test new file mode 100644 index 00000000..357f13c4 --- /dev/null +++ b/tests/resources/smtp/antispam/classifier_features.test @@ -0,0 +1,2807 @@ +From: bill@example.com +To: jdoe@example.com +Subject: TPS Report + +I'm going to need those TPS reports ASAP. So, if you could do that, that'd be great. + +[ + { + "type": "word", + "value": "_allcaps" + }, + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "asap" + }, + { + "type": "word", + "value": "could" + }, + { + "type": "word", + "value": "go" + }, + { + "type": "word", + "value": "great" + }, + { + "type": "word", + "value": "need" + }, + { + "type": "word", + "value": "report" + }, + { + "type": "word", + "value": "tps" + }, + { + "type": "sender", + "value": "bill@example.com" + }, + { + "type": "sender", + "value": "example.com" + } +] + +From: Hendrik +To: Harrie +Date: Sat, 11 Oct 2010 00:31:44 +0200 +Subject: One Two Three Four +Content-Type: multipart/mixed; boundary=AA + +This is a multi-part message in MIME format. +--AA +Content-Type: multipart/mixed; boundary=BB + +This is a multi-part message in MIME format. +--BB +Content-Type: text/plain; charset="us-ascii" + +This is the first message part containing +plain text. + +--BB +Content-Type: text/plain; charset="us-ascii" + +This is another plain text message part. + +--BB-- +This is the end of MIME multipart. + +--AA +Content-Type: text/html; charset="us-ascii" + + +This is a piece of HTML text. + + +--AA-- +This is the end of MIME multipart. + + +[ + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "anoth" + }, + { + "type": "word", + "value": "contain" + }, + { + "type": "word", + "value": "first" + }, + { + "type": "word", + "value": "four" + }, + { + "type": "word", + "value": "html" + }, + { + "type": "word", + "value": "messag" + }, + { + "type": "word", + "value": "one" + }, + { + "type": "word", + "value": "part" + }, + { + "type": "word", + "value": "piec" + }, + { + "type": "word", + "value": "plain" + }, + { + "type": "word", + "value": "text" + }, + { + "type": "word", + "value": "three" + }, + { + "type": "word", + "value": "two" + }, + { + "type": "sender", + "value": "example.com" + }, + { + "type": "sender", + "value": "hendrik@example.com" + }, + { + "type": "mime_type", + "value": "multipart/mixed" + }, + { + "type": "mime_type", + "value": "text/html" + }, + { + "type": "mime_type", + "value": "text/plain" + } +] + +Content-Type: text/html; charset="utf-8" +Subject: IPs in HTML are not urls + + +Das System wurde um 01.01.1970 08:28:00 für die IP-Adresse +123.123.123.123 gesperrt.
+
+Der Besucher hat versucht, sich mit folgenden Daten anzumelden.
+Partner: 12345678
+Portal: IP-Sperre einsehen + + + +[ + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "adress" + }, + { + "type": "word", + "value": "anzumeld" + }, + { + "type": "word", + "value": "besuch" + }, + { + "type": "word", + "value": "dat" + }, + { + "type": "word", + "value": "einseh" + }, + { + "type": "word", + "value": "folgend" + }, + { + "type": "word", + "value": "gesperrt" + }, + { + "type": "word", + "value": "html" + }, + { + "type": "word", + "value": "ip" + }, + { + "type": "word", + "value": "partn" + }, + { + "type": "word", + "value": "portal" + }, + { + "type": "word", + "value": "sperr" + }, + { + "type": "word", + "value": "syst" + }, + { + "type": "word", + "value": "url" + }, + { + "type": "word", + "value": "versucht" + }, + { + "type": "word", + "value": "wurd" + }, + { + "type": "number", + "code": [ + 105, + 2 + ] + }, + { + "type": "number", + "code": [ + 105, + 4 + ] + }, + { + "type": "number", + "code": [ + 105, + 8 + ] + }, + { + "type": "url", + "value": "!ip" + }, + { + "type": "url", + "value": "_example" + }, + { + "type": "url", + "value": "_php" + }, + { + "type": "url", + "value": "localhost.de" + }, + { + "type": "url", + "value": "www.localhost.de" + }, + { + "type": "mime_type", + "value": "text/html" + }, + { + "type": "html_anchor", + "href": "https" + } +] + +X-Spam-Result: DMARC_POLICY_ALLOW (-0.50), + TEST (0.0), + SOURCE_ASN_123 (1.00) +From: Client Services +To: user@domain.org +Subject: Tether Important Update ! +Content-Type: text/html +Content-Transfer-Encoding: quoted-printable + + + + + + + +3D"If + + +[ + { + "type": "word", + "value": "_allcaps" + }, + { + "type": "word", + "value": "_null" + }, + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "browser" + }, + { + "type": "word", + "value": "cashback" + }, + { + "type": "word", + "value": "click" + }, + { + "type": "word", + "value": "import" + }, + { + "type": "word", + "value": "messag" + }, + { + "type": "word", + "value": "open" + }, + { + "type": "word", + "value": "pleas" + }, + { + "type": "word", + "value": "read" + }, + { + "type": "word", + "value": "reward" + }, + { + "type": "word", + "value": "tether" + }, + { + "type": "word", + "value": "updat" + }, + { + "type": "sender", + "value": "noreply@tetheer.com" + }, + { + "type": "sender", + "value": "tetheer.com" + }, + { + "type": "asn", + "number": [ + 0, + 0, + 0, + 123 + ] + }, + { + "type": "url", + "value": "metaskwap.online" + }, + { + "type": "mime_type", + "value": "text/html" + }, + { + "type": "html_image", + "src": "data" + }, + { + "type": "html_anchor", + "href": "https" + } +] + +From: "BBVA" +Reply-To: noreply@grupokonecta.net +Content-Type: multipart/alternative; charset="UTF-8"; boundary="b1_3d217f30a568faa9ce3dd7dc73399561" +Content-Transfer-Encoding: quoted-printable + +--b1_3d217f30a568faa9ce3dd7dc73399561 +Content-Type: text/plain; format=flowed; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +Tarjeta de cr=C3=A9dito BBVA + +La tarjeta de cr=C3=A9dito para viajar con tus consumos +Pedila 100% online y empez=C3=A1 a disfrutar + +Conocer oferta +Un mundo de beneficios con las tarjetas de cr=C3=A9dito BBVA +compras en cuotas +Compras en cuotas +Pod=C3=A9s disfrutar hoy de los productos =E2=80=A8que quer=C3=A9s y pagarl= +os en cuotas +descuentos y reintegros +Descuentos y reintegros +Entretenimiento, gastronom=C3=ADa, farmacia, ropa =E2=80=A8y m=C3=A1s rubro= +s con promociones exclusivas +puntos bbva +Viajes con Puntos BBVA +Vuelos, alojamientos y mucho m=C3=A1s canjeando Puntos BBVA que sum=C3= +=A1s con tus compras +Conocer oferta +Descubr=C3=AD la tarjeta que mejor se adapta a vos +Todas las tarjetas + +Black + +Platinum + +Gold + +Internacional + +Todas las tarjetas +visa black +Tarjeta Visa Signature +L=C3=ADmites desde $600.000 + +15% extra en acumulaci=C3=B3n de Puntos BBVA +Acceso a salas VIP en aeropuertos +Asistencia en viajes con cobertura de hasta 250.000 USD +Extracci=C3=B3n de efectivo en el exterior +Seguro de robo en cajero y compra protegida +Atenci=C3=B3n personalizada para resolver tus consultas +Tarjetas adicionales sin costo +Es necesario un ingreso m=C3=ADnimo mensual de $200.000 + + Conocer m=C3=A1s +mastercard black +Tarjeta Mastercard Black +L=C3=ADmites desde $600.000 + +15% extra en acumulaci=C3=B3n de Puntos BBVA +Acceso a salas VIP en aeropuertos +Asistencia en viajes con cobertura de hasta 250.000 USD +Extracci=C3=B3n de efectivo en el exterior +Seguro de robo en cajero y compra protegida +Atenci=C3=B3n personalizada para resolver tus consultas +Tarjetas adicionales sin costo +Es necesario un ingreso m=C3=ADnimo mensual de $200.000 + Conocer m=C3=A1s +tarjeta platinum visa +Tarjeta Visa Platinum +L=C3=ADmites desde $350.000 + +5% extra en acumulaci=C3=B3n de Puntos BBVA +Asistencia en viajes con cobertura de hasta 170.000 USD +Extracci=C3=B3n de efectivo en el exterior +Atenci=C3=B3n personalizada para resolver tus consultas +Tarjetas adicionales sin costo +Es necesario un ingreso m=C3=ADnimo mensual de $120.000 + + Conocer m=C3=A1s +tarjeta platinum mastercard +Tarjeta Mastercard Platinum +L=C3=ADmites desde $350.000 + +5% extra en acumulaci=C3=B3n de Puntos BBVA +Asistencia en viajes con cobertura de hasta 50.000 USD y 30.000 EUR +Extracci=C3=B3n de efectivo en el exterior +Atenci=C3=B3n personalizada para resolver tus consultas +Tarjetas adicionales sin costo +Es necesario un ingreso m=C3=ADnimo mensual de $120.000 + + Conocer m=C3=A1s +tarjeta gold visa +Tarjeta Visa Gold +L=C3=ADmites desde $100.000 + +Puntos BBVA para viajar +Extracci=C3=B3n de efectivo en el exterior +Tarjetas adicionales sin costo +Es necesario un ingreso m=C3=ADnimo mensual de $20.000 + + Conocer m=C3=A1s +tarjeta gold mastercard +Tarjeta Mastercard Gold +L=C3=ADmites desde $100.000 + +Puntos BBVA para viajar +Extracci=C3=B3n de efectivo en el exterior +Tarjetas adicionales sin costo +Es necesario un ingreso m=C3=ADnimo mensual de $35.000 + + Conocer m=C3=A1s + +Tarjeta Visa Internacional +L=C3=ADmites desde $10.000 + +Puntos BBVA para viajar +Extracci=C3=B3n de efectivo en el exterior +Tarjetas adicionales sin costo +Es necesario un ingreso m=C3=ADnimo mensual de $20.000 + + Conocer m=C3=A1s + +Tarjeta Mastercard Internacional +L=C3=ADmites desde $10.000 + +Puntos BBVA para viajar +Extracci=C3=B3n de efectivo en el exterior +Tarjetas adicionales sin costo +Es necesario un ingreso m=C3=ADnimo mensual de $20.000 + + Conocer m=C3=A1s + +--b1_3d217f30a568faa9ce3dd7dc73399561 +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + + + + + +
+ + + + +
3D"CLICK
+
+ + + +--b1_3d217f30a568faa9ce3dd7dc73399561-- + + +[ + { + "type": "word", + "value": "_allcaps" + }, + { + "type": "word", + "value": "_null" + }, + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "acces" + }, + { + "type": "word", + "value": "acumul" + }, + { + "type": "word", + "value": "adapt" + }, + { + "type": "word", + "value": "adicional" + }, + { + "type": "word", + "value": "aeropuert" + }, + { + "type": "word", + "value": "aloj" + }, + { + "type": "word", + "value": "aqu" + }, + { + "type": "word", + "value": "asistent" + }, + { + "type": "word", + "value": "atencion" + }, + { + "type": "word", + "value": "bbva" + }, + { + "type": "word", + "value": "benefici" + }, + { + "type": "word", + "value": "black" + }, + { + "type": "word", + "value": "cajer" + }, + { + "type": "word", + "value": "canj" + }, + { + "type": "word", + "value": "click" + }, + { + "type": "word", + "value": "cobertur" + }, + { + "type": "word", + "value": "compr" + }, + { + "type": "word", + "value": "conoc" + }, + { + "type": "word", + "value": "consult" + }, + { + "type": "word", + "value": "consum" + }, + { + "type": "word", + "value": "cost" + }, + { + "type": "word", + "value": "credit" + }, + { + "type": "word", + "value": "cuot" + }, + { + "type": "word", + "value": "descubr" + }, + { + "type": "word", + "value": "descuent" + }, + { + "type": "word", + "value": "disfrut" + }, + { + "type": "word", + "value": "efect" + }, + { + "type": "word", + "value": "empez" + }, + { + "type": "word", + "value": "entreten" + }, + { + "type": "word", + "value": "es" + }, + { + "type": "word", + "value": "esperando" + }, + { + "type": "word", + "value": "estaba" + }, + { + "type": "word", + "value": "eur" + }, + { + "type": "word", + "value": "exclus" + }, + { + "type": "word", + "value": "exterior" + }, + { + "type": "word", + "value": "extra" + }, + { + "type": "word", + "value": "extraccion" + }, + { + "type": "word", + "value": "farmaci" + }, + { + "type": "word", + "value": "gastronom" + }, + { + "type": "word", + "value": "gold" + }, + { + "type": "word", + "value": "hoy" + }, + { + "type": "word", + "value": "iacut" + }, + { + "type": "word", + "value": "ingres" + }, + { + "type": "word", + "value": "internacional" + }, + { + "type": "word", + "value": "la" + }, + { + "type": "word", + "value": "limit" + }, + { + "type": "word", + "value": "mastercard" + }, + { + "type": "word", + "value": "mejor" + }, + { + "type": "word", + "value": "mensual" + }, + { + "type": "word", + "value": "minim" + }, + { + "type": "word", + "value": "mund" + }, + { + "type": "word", + "value": "necesari" + }, + { + "type": "word", + "value": "ofert" + }, + { + "type": "word", + "value": "onlin" + }, + { + "type": "word", + "value": "oportunidad" + }, + { + "type": "word", + "value": "pag" + }, + { + "type": "word", + "value": "pedil" + }, + { + "type": "word", + "value": "personaliz" + }, + { + "type": "word", + "value": "platinum" + }, + { + "type": "word", + "value": "podes" + }, + { + "type": "word", + "value": "product" + }, + { + "type": "word", + "value": "promocion" + }, + { + "type": "word", + "value": "proteg" + }, + { + "type": "word", + "value": "punt" + }, + { + "type": "word", + "value": "que" + }, + { + "type": "word", + "value": "queres" + }, + { + "type": "word", + "value": "reintegr" + }, + { + "type": "word", + "value": "resolv" + }, + { + "type": "word", + "value": "rob" + }, + { + "type": "word", + "value": "rop" + }, + { + "type": "word", + "value": "rubr" + }, + { + "type": "word", + "value": "sal" + }, + { + "type": "word", + "value": "segur" + }, + { + "type": "word", + "value": "signatur" + }, + { + "type": "word", + "value": "sumas" + }, + { + "type": "word", + "value": "tarjet" + }, + { + "type": "word", + "value": "tod" + }, + { + "type": "word", + "value": "usd" + }, + { + "type": "word", + "value": "viaj" + }, + { + "type": "word", + "value": "vip" + }, + { + "type": "word", + "value": "vis" + }, + { + "type": "word", + "value": "vos" + }, + { + "type": "word", + "value": "vuel" + }, + { + "type": "number", + "code": [ + 105, + 1 + ] + }, + { + "type": "number", + "code": [ + 105, + 2 + ] + }, + { + "type": "number", + "code": [ + 105, + 3 + ] + }, + { + "type": "unicode_category", + "value": "Sc" + }, + { + "type": "sender", + "value": "grupokonecta.net" + }, + { + "type": "sender", + "value": "noreply@grupokonecta.net" + }, + { + "type": "url", + "value": "_aff" + }, + { + "type": "url", + "value": "_jpg" + }, + { + "type": "url", + "value": "i.imgur.com" + }, + { + "type": "url", + "value": "imgur.com" + }, + { + "type": "url", + "value": "leadsinbx.com" + }, + { + "type": "url", + "value": "track.leadsinbx.com" + }, + { + "type": "mime_type", + "value": "multipart/alternative" + }, + { + "type": "mime_type", + "value": "text/html" + }, + { + "type": "mime_type", + "value": "text/plain" + }, + { + "type": "html_image", + "src": "https" + }, + { + "type": "html_anchor", + "href": "http" + } +] + +From: Spammer Systems Iran +Subject: =?utf-8?b?2KfZgdiy2YjZhtmH4oCM2YfYp9uMINin2LPZhdin2LHYqtix2YXbjNmEIHw=?= + =?utf-8?b?INiq2YjYs9i52Ycg24zYp9mB2KrZhyDYqtmI2LPYtyDYotix2qnYpw==?= +Message-Id: +To: spam@target.org +Reply-To: Spammer Systems Iran +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +vEⓡ𝔂 𝔽𝕌Ňℕy ţ乇𝕏𝓣 + +=D8=A7=D9=81=D8=B2=D9=88=D9=86=D9=87=E2=80=8C=D9=87=D8=A7=DB=8C SpammerMail= + =D8=AA=D9=88=D8=B3=D8=B9=D9=87 =DB=8C=D8=A7=D9=81=D8=AA=D9=87 =D8=AA=D9=88= +=D8=B3=D8=B7 =D8=A2=D8=B1=DA=A9=D8=A7 + +=D8=B1=D8=A7=DB=8C=D8=A7=D9=86 =D8=B3=D8=A7=D9=85=D8=A7=D9=86=D9=87 =D8=A2= +=D8=B1=DA=A9=D8=A7 | =D8=AA=D9=85=D8=A7=D8=B3: 91300476-021 | =D8=A7=DB=8C= +=D9=85=DB=8C=D9=84: info@spammy.ir + +[Telegram] [Instagram] [LinkedIn] [Email] + + +[ + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "email" + }, + { + "type": "word", + "value": "funny" + }, + { + "type": "word", + "value": "instagram" + }, + { + "type": "word", + "value": "linkedin" + }, + { + "type": "word", + "value": "spammermail" + }, + { + "type": "word", + "value": "telegram" + }, + { + "type": "word", + "value": "text" + }, + { + "type": "word", + "value": "very" + }, + { + "type": "word", + "value": "آرکا" + }, + { + "type": "word", + "value": "اسمارترمی" + }, + { + "type": "word", + "value": "افزونه" + }, + { + "type": "word", + "value": "ایمیل" + }, + { + "type": "word", + "value": "تماس" + }, + { + "type": "word", + "value": "توسط" + }, + { + "type": "word", + "value": "توسعه" + }, + { + "type": "word", + "value": "رایان" + }, + { + "type": "word", + "value": "سامانه" + }, + { + "type": "word", + "value": "های" + }, + { + "type": "word", + "value": "یافته" + }, + { + "type": "number", + "code": [ + 105, + 3 + ] + }, + { + "type": "number", + "code": [ + 105, + 8 + ] + }, + { + "type": "unicode_category", + "value": "Cf" + }, + { + "type": "unicode_category", + "value": "Sm" + }, + { + "type": "sender", + "value": "marketing@spammer.ir" + }, + { + "type": "sender", + "value": "spammer.ir" + }, + { + "type": "email", + "value": "info@spammy.ir" + }, + { + "type": "email", + "value": "spammy.ir" + }, + { + "type": "mime_type", + "value": "text/plain" + } +] + +Received: from localhost ([217.61.8.72]) + by Consip with ESMTP + id PMLhve2ETFdIAPMLyvhh0c; Sat, 29 Nov 2025 15:55:14 +0100 +Received: from zspmta-mint02.ad.aruba.it ([127.0.0.1]) + by localhost (zspmta-mint02.ad.aruba.it [127.0.0.1]) (amavis, port 10026) + with ESMTP id UV6fqMysWqKE; Sat, 29 Nov 2025 15:55:13 +0100 (CET) +Received: from zspmbx-mint11.ad.aruba.it (unknown [10.202.133.51]) + by zspmta-mint02.ad.aruba.it (Postfix) with ESMTP id 3042B120F77; + Sat, 29 Nov 2025 15:54:59 +0100 (CET) +Date: Sat, 29 Nov 2025 15:54:59 +0100 (CET) +From: gianfranco.mangini@interno.it +Reply-To: "Hr. Charles Jackson Jr." +Message-ID: <1933878358.10239097.1764428099117.JavaMail.zimbra@interno.it> +Subject: +Content-Type: multipart/alternative; + boundary="=_bf54163b-f3b6-421f-bc9d-b64439167a39" + +--=_bf54163b-f3b6-421f-bc9d-b64439167a39 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + + + +Hvorfor har du ikke modtaget donationen p=C3=A5 =E2=82=AC955.000,00 fra hr.= + Charles Jackson Jr.? Bankdirekt=C3=B8ren informerede mig i g=C3=A5r om, at= + en af =E2=80=8B=E2=80=8Bmodtagerne ikke havde gjort krav p=C3=A5 donatione= +n. Efter at have gennemg=C3=A5et mine optegnelser opdagede jeg, at du var b= +landt de ber=C3=B8rte, og jeg er meget ked af at h=C3=B8re dette. Bem=C3=A6= +rk venligst, at der ikke kr=C3=A6ves nogen betaling; et simpelt bekr=C3=A6f= +telsesstempel er alt, hvad der skal til for at pengene kan frigives og kred= +iteres din bankkonto inden for 24 timer.=20 + +Bem=C3=A6rk: For yderligere information og for at sikre, at din donation kr= +editeres inden for 24 timer, anbefaler jeg, at du sender mig dine oplysning= +er med det samme via e-mail til ferassutti34@gmail.com=20 + +Jeg =C3=B8nsker dig en velsignet m=C3=A5ned med stor succes.=20 +Hr. Charles Jackson Jr.=20 + +--=_bf54163b-f3b6-421f-bc9d-b64439167a39 +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +


Hvorfor har du ikke modtaget donationen p=C3=A5 =E2=82=AC9= +55.000,00 fra hr. Charles Jackson Jr.? Bankdirekt=C3=B8ren informerede mig = +i g=C3=A5r om, at en af =E2=80=8B=E2=80=8Bmodtagerne ikke havde gjort krav = +p=C3=A5 donationen. Efter at have gennemg=C3=A5et mine optegnelser opdagede= + jeg, at du var blandt de ber=C3=B8rte, og jeg er meget ked af at h=C3=B8re= + dette. Bem=C3=A6rk venligst, at der ikke kr=C3=A6ves nogen betaling; et si= +mpelt bekr=C3=A6ftelsesstempel er alt, hvad der skal til for at pengene kan= + frigives og krediteres din bankkonto inden for 24 timer.

Bem=C3=A6r= +k: For yderligere information og for at sikre, at din donation krediteres i= +nden for 24 timer, anbefaler jeg, at du sender mig dine oplysninger med det= + samme via e-mail til ferassutti34@gmail.com

Jeg =C3=B8nsker dig en = +velsignet m=C3=A5ned med stor succes.
Hr. Charles Jackson Jr.
+--=_bf54163b-f3b6-421f-bc9d-b64439167a39-- + +[ + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "anbefal" + }, + { + "type": "word", + "value": "bankdirektør" + }, + { + "type": "word", + "value": "bankkonto" + }, + { + "type": "word", + "value": "bekræftelsesstem" + }, + { + "type": "word", + "value": "bemærk" + }, + { + "type": "word", + "value": "berørt" + }, + { + "type": "word", + "value": "betaling" + }, + { + "type": "word", + "value": "bland" + }, + { + "type": "word", + "value": "charl" + }, + { + "type": "word", + "value": "din" + }, + { + "type": "word", + "value": "donation" + }, + { + "type": "word", + "value": "e" + }, + { + "type": "word", + "value": "frigiv" + }, + { + "type": "word", + "value": "gennemgå" + }, + { + "type": "word", + "value": "gjort" + }, + { + "type": "word", + "value": "går" + }, + { + "type": "word", + "value": "hr" + }, + { + "type": "word", + "value": "hvorfor" + }, + { + "type": "word", + "value": "hør" + }, + { + "type": "word", + "value": "ind" + }, + { + "type": "word", + "value": "inform" + }, + { + "type": "word", + "value": "information" + }, + { + "type": "word", + "value": "jackson" + }, + { + "type": "word", + "value": "jr" + }, + { + "type": "word", + "value": "kan" + }, + { + "type": "word", + "value": "ked" + }, + { + "type": "word", + "value": "krav" + }, + { + "type": "word", + "value": "kredit" + }, + { + "type": "word", + "value": "kræv" + }, + { + "type": "word", + "value": "mail" + }, + { + "type": "word", + "value": "modtag" + }, + { + "type": "word", + "value": "måned" + }, + { + "type": "word", + "value": "nog" + }, + { + "type": "word", + "value": "opdaged" + }, + { + "type": "word", + "value": "oplysning" + }, + { + "type": "word", + "value": "optegn" + }, + { + "type": "word", + "value": "peng" + }, + { + "type": "word", + "value": "sam" + }, + { + "type": "word", + "value": "send" + }, + { + "type": "word", + "value": "sikr" + }, + { + "type": "word", + "value": "simpelt" + }, + { + "type": "word", + "value": "stor" + }, + { + "type": "word", + "value": "suc" + }, + { + "type": "word", + "value": "tim" + }, + { + "type": "word", + "value": "velsign" + }, + { + "type": "word", + "value": "ven" + }, + { + "type": "word", + "value": "via" + }, + { + "type": "word", + "value": "yder" + }, + { + "type": "word", + "value": "ønsk" + }, + { + "type": "number", + "code": [ + 105, + 2 + ] + }, + { + "type": "number", + "code": [ + 105, + 3 + ] + }, + { + "type": "unicode_category", + "value": "Cf" + }, + { + "type": "unicode_category", + "value": "Sc" + }, + { + "type": "sender", + "value": "ferassutti34@gmail.com" + }, + { + "type": "sender", + "value": "gianfranco.mangini@interno.it" + }, + { + "type": "sender", + "value": "gmail.com" + }, + { + "type": "sender", + "value": "interno.it" + }, + { + "type": "hostname", + "value": "aruba.it" + }, + { + "type": "hostname", + "value": "interno.it" + }, + { + "type": "hostname", + "value": "zspmbx-mint11.ad.aruba.it" + }, + { + "type": "hostname", + "value": "zspmta-mint02.ad.aruba.it" + }, + { + "type": "mime_type", + "value": "multipart/alternative" + }, + { + "type": "mime_type", + "value": "text/html" + }, + { + "type": "mime_type", + "value": "text/plain" + } +] + +Delivered-To: mcfadden@domain.com +Received: from gamma.stellaryx.space (unknown [85.120.227.61] (AS6718 NAV COMMUNICATIONS SRL, RO)) + by mail.stalw.art (Stalwart SMTP) with ESMTP id 3D6018102E32AFD; + Tue, 25 Nov 2025 14:56:37 +0000 +Return-Path: <102356-235606-568806-22158-mcfadden=domain.com@mail.stellaryx.space> +Content-Type: multipart/alternative; boundary="4521ddb80d67f83dc7585cae40234a09_39856_8ade6" +Date: Tue, 25 Nov 2025 15:56:05 +0100 +From: "ZenFluff" +Reply-To: "ZenFluff" +Subject: Sleep better with FluffCo +To: +Message-ID: + +--4521ddb80d67f83dc7585cae40234a09_39856_8ade6 +Content-Type: text/plain; +Content-Transfer-Encoding: 8bit + +Sleep better with FluffCo + +http://stellaryx.space/Bm6NYOrhicX--9bFn47T2mFlb-Soxhs-FJ8RCnM_SXJyHmebLw + +http://stellaryx.space/Y46ntHIiWyxTreKwOcyT4txY9f-M-eCwfHuhx0SMoyGjXy1iuA + +--4521ddb80d67f83dc7585cae40234a09_39856_8ade6 +Content-Type: text/html; +Content-Transfer-Encoding: 8bit + + + + + Newsletter + + + +
+
Sleep better with FluffCo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +--4521ddb80d67f83dc7585cae40234a09_39856_8ade6-- + + +[ + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "better" + }, + { + "type": "word", + "value": "fluffco" + }, + { + "type": "word", + "value": "sleep" + }, + { + "type": "sender", + "value": "fluffcopartner@stellaryx.space" + }, + { + "type": "sender", + "value": "fluffcopromo@stellaryx.space" + }, + { + "type": "sender", + "value": "stellaryx.space" + }, + { + "type": "url", + "value": "_jpg" + }, + { + "type": "url", + "value": "_sxjyhmeblw" + }, + { + "type": "url", + "value": "stellaryx.space" + }, + { + "type": "url", + "value": "www.stellaryx.space" + }, + { + "type": "hostname", + "value": "gamma.stellaryx.space" + }, + { + "type": "hostname", + "value": "stellaryx.space" + }, + { + "type": "mime_type", + "value": "multipart/alternative" + }, + { + "type": "mime_type", + "value": "text/html" + }, + { + "type": "mime_type", + "value": "text/plain" + }, + { + "type": "html_image", + "src": "http" + }, + { + "type": "html_anchor", + "href": "http" + } +] + +Delivered-To: mcfadden@domain.com +Received: from cache.agelessknees.za.com (unknown [193.36.60.184] (AS210107 PLUSWEB SUNUCU INTERNET HIZMETLERI TICARET LIMITED SIRKETI, TR)) + by mail.stalw.art (Stalwart SMTP) with ESMTP id 3CB95BA83AFB5B8; + Sun, 9 Nov 2025 10:25:14 +0000 +Return-Path: <2993-2338-35418-95-mcfadden=domain.com@mail.agelessknees.za.com> +Content-Type: multipart/alternative; boundary="f2a4125f95dc25c4cd4f09657da6c1b1" +Date: Sun, 9 Nov 2025 02:05:37 -0800 +From: "ENLARGED PROSTATE" +Reply-To: "ENLARGED PROSTATE" +Subject: 90% Success Rate: Shrink Your Prostate by 68%... +To: +Message-ID: <8btb35y8w3xurryz-6647pok20gxdwmew-8a5a@agelessknees.za.com> + +--f2a4125f95dc25c4cd4f09657da6c1b1 +Content-Type: text/plain; +Content-Transfer-Encoding: 8bit + +http://agelessknees.za.com/WEIPdYsx_Fz316dLbPRtpgbW8tLjN6VeuG_xqNr-08jn + + +http://[Unsubscribe]] + +--f2a4125f95dc25c4cd4f09657da6c1b1 +Content-Type: text/html; +Content-Transfer-Encoding: 8bit + + + + + +
+
+Urologists are in complete shock after this classified 1970 study  has been
+accidentally released to the public.
 
+ 
+In the study, almost 90% of the men emptied their bladders fully…
+and stopped nighttime pee trips!
 
+ 
+And it’s because of a bizarre “Brazilian Jelly”... 
+
+click here to watch video, Picture
+
+Which not only helps you pee like a racehorse, but it also shrinks your
+prostate size by 68%,
almost overnight. 
+ 
+So, as you can imagine, this prostate-shrinking method
+is spreading like wildfire..
 
+ 
+And that’s why over 45,000 men have managed to
+get rid of prostate problems…
 
+ 

+Without painful medical procedures or Rapaflo, Uroxatral, and other toxic medications. So, while this video is still up…
+ 
+So, while this video is still up… 
+ 
+[WATCH NOW]
+
+to see how this Brazilian Jelly can help shrink your
+enlarged prostate as well.

+
+
+
+
+
+
+
+
+
+
+unsubscribe

+1770 Walnut Hill Drive Dayton, OH 45406 + + +--f2a4125f95dc25c4cd4f09657da6c1b1-- + + +[ + { + "type": "word", + "value": "_allcaps" + }, + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "accident" + }, + { + "type": "word", + "value": "almost" + }, + { + "type": "word", + "value": "also" + }, + { + "type": "word", + "value": "bizarr" + }, + { + "type": "word", + "value": "bladder" + }, + { + "type": "word", + "value": "brazilian" + }, + { + "type": "word", + "value": "classifi" + }, + { + "type": "word", + "value": "click" + }, + { + "type": "word", + "value": "complet" + }, + { + "type": "word", + "value": "dayton" + }, + { + "type": "word", + "value": "drive" + }, + { + "type": "word", + "value": "empti" + }, + { + "type": "word", + "value": "enlarg" + }, + { + "type": "word", + "value": "fulli" + }, + { + "type": "word", + "value": "get" + }, + { + "type": "word", + "value": "help" + }, + { + "type": "word", + "value": "hill" + }, + { + "type": "word", + "value": "http" + }, + { + "type": "word", + "value": "imagin" + }, + { + "type": "word", + "value": "jelli" + }, + { + "type": "word", + "value": "like" + }, + { + "type": "word", + "value": "manag" + }, + { + "type": "word", + "value": "medic" + }, + { + "type": "word", + "value": "men" + }, + { + "type": "word", + "value": "method" + }, + { + "type": "word", + "value": "nighttim" + }, + { + "type": "word", + "value": "oh" + }, + { + "type": "word", + "value": "overnight" + }, + { + "type": "word", + "value": "pain" + }, + { + "type": "word", + "value": "pee" + }, + { + "type": "word", + "value": "pictur" + }, + { + "type": "word", + "value": "problem" + }, + { + "type": "word", + "value": "procedur" + }, + { + "type": "word", + "value": "prostat" + }, + { + "type": "word", + "value": "public" + }, + { + "type": "word", + "value": "racehors" + }, + { + "type": "word", + "value": "rapaflo" + }, + { + "type": "word", + "value": "rate" + }, + { + "type": "word", + "value": "releas" + }, + { + "type": "word", + "value": "rid" + }, + { + "type": "word", + "value": "see" + }, + { + "type": "word", + "value": "shock" + }, + { + "type": "word", + "value": "shrink" + }, + { + "type": "word", + "value": "size" + }, + { + "type": "word", + "value": "spread" + }, + { + "type": "word", + "value": "still" + }, + { + "type": "word", + "value": "stop" + }, + { + "type": "word", + "value": "studi" + }, + { + "type": "word", + "value": "success" + }, + { + "type": "word", + "value": "toxic" + }, + { + "type": "word", + "value": "trip" + }, + { + "type": "word", + "value": "unsubscrib" + }, + { + "type": "word", + "value": "urologist" + }, + { + "type": "word", + "value": "uroxatr" + }, + { + "type": "word", + "value": "video" + }, + { + "type": "word", + "value": "walnut" + }, + { + "type": "word", + "value": "watch" + }, + { + "type": "word", + "value": "well" + }, + { + "type": "word", + "value": "wildfir" + }, + { + "type": "word", + "value": "without" + }, + { + "type": "number", + "code": [ + 105, + 2 + ] + }, + { + "type": "number", + "code": [ + 105, + 3 + ] + }, + { + "type": "number", + "code": [ + 105, + 4 + ] + }, + { + "type": "number", + "code": [ + 105, + 5 + ] + }, + { + "type": "sender", + "value": "agelessknees.za.com" + }, + { + "type": "sender", + "value": "prostate@agelessknees.za.com" + }, + { + "type": "url", + "value": "_png" + }, + { + "type": "url", + "value": "_weipdysx" + }, + { + "type": "url", + "value": "agelessknees.za.com" + }, + { + "type": "hostname", + "value": "agelessknees.za.com" + }, + { + "type": "hostname", + "value": "cache.agelessknees.za.com" + }, + { + "type": "mime_type", + "value": "multipart/alternative" + }, + { + "type": "mime_type", + "value": "text/html" + }, + { + "type": "mime_type", + "value": "text/plain" + }, + { + "type": "html_image", + "src": "http" + }, + { + "type": "html_anchor", + "href": "http" + } +] + +Delivered-To: hello@stalw.art +Received: from mail-wm1-x32d.google.com (mail-wm1-x32d.google.com [2a00:1450:4864:20::32d] (AS15169 Google LLC)) + (using TLSv1.3 with cipher TLS13_AES_256_GCM_SHA384) + by mail.stalw.art (Stalwart SMTP) with ESMTPS id 3BBB57D01CB793C; + Wed, 15 Oct 2025 18:31:22 +0000 +Return-Path: +Received: by mail-wm1-x32d.google.com with SMTP id 5b1f17b1804b1-4710683a644so7830385e9.0 + for ; Wed, 15 Oct 2025 11:31:19 -0700 (PDT) +Received: from 52669349336 named unknown by gmailapi.google.com with HTTPREST; + Wed, 15 Oct 2025 14:31:17 -0400 +Received: from 52669349336 named unknown by gmailapi.google.com with HTTPREST; + Wed, 15 Oct 2025 14:31:16 -0400 +MIME-Version: 1.0 +Sender: Yash from SpamTest +From: Yash from SpamTest +Reply-To: yashbansal@spamtest.com +Date: Wed, 15 Oct 2025 14:31:17 -0400 +Message-ID: +Subject: SpamTest Open-Source Sponsorships for Stalwart +To: Hello +Content-Type: multipart/alternative; boundary="000000000000d8b207064136b41e" + +--000000000000d8b207064136b41e +Content-Type: text/plain; charset="UTF-8" + +Hi Team, + +I'm Yash from SpamTest, a GenAI-powered quality engineering platform. +We've been following the excellent work you're doing with Stalwart and +would like to support your project through our Open Source Program. + +What we're offering: + + - Free SpamTest licenses for your testing infrastructure + - Financial sponsorship for your project + - Co-marketing initiative to amplify your project's reach + +In return, we'd appreciate featuring the SpamTest logo in the ReadMe file +and under your sponsors section. + +Would you be interested in a quick call to discuss how we can support? +Here's my Calendly: https://calendly.com/yashbansal-spamtest/ + + +Regards, +Yash +[image: beacon] + +--000000000000d8b207064136b41e +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +
= +Hi Team,

I'= +;m Yash from SpamTest, a=C2=A0GenAI-powered quality engineering platform.= + We've been following the excellent work you're doing with Stalwart= + and would like to support your project through our Open Source Program.

What we're o= +ffering:
  • Free SpamTest licenses for y= +our testing infrastructure
  • Financial sponsorship for your project
  • Co-marketing initiative to amplify your project's reach= +
In return, we'd app= +reciate featuring the SpamTest logo in the ReadMe file and under your spo= +nsors section.

Would you be interested in a quick call to discuss how we can support? Her= +e's my Calendly: https:/= +/calendly.com/yashbansal-spamtest/

Regards,
Yash
+3D"beacon" + +--000000000000d8b207064136b41e-- + + +[ + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "amplifi" + }, + { + "type": "word", + "value": "appreci" + }, + { + "type": "word", + "value": "beacon" + }, + { + "type": "word", + "value": "calend" + }, + { + "type": "word", + "value": "call" + }, + { + "type": "word", + "value": "co" + }, + { + "type": "word", + "value": "discuss" + }, + { + "type": "word", + "value": "engin" + }, + { + "type": "word", + "value": "excel" + }, + { + "type": "word", + "value": "featur" + }, + { + "type": "word", + "value": "file" + }, + { + "type": "word", + "value": "financi" + }, + { + "type": "word", + "value": "follow" + }, + { + "type": "word", + "value": "free" + }, + { + "type": "word", + "value": "genai" + }, + { + "type": "word", + "value": "hi" + }, + { + "type": "word", + "value": "imag" + }, + { + "type": "word", + "value": "infrastructur" + }, + { + "type": "word", + "value": "initi" + }, + { + "type": "word", + "value": "interest" + }, + { + "type": "word", + "value": "licens" + }, + { + "type": "word", + "value": "like" + }, + { + "type": "word", + "value": "logo" + }, + { + "type": "word", + "value": "market" + }, + { + "type": "word", + "value": "offer" + }, + { + "type": "word", + "value": "open" + }, + { + "type": "word", + "value": "platform" + }, + { + "type": "word", + "value": "power" + }, + { + "type": "word", + "value": "program" + }, + { + "type": "word", + "value": "project" + }, + { + "type": "word", + "value": "qualiti" + }, + { + "type": "word", + "value": "quick" + }, + { + "type": "word", + "value": "reach" + }, + { + "type": "word", + "value": "readm" + }, + { + "type": "word", + "value": "regard" + }, + { + "type": "word", + "value": "return" + }, + { + "type": "word", + "value": "section" + }, + { + "type": "word", + "value": "sourc" + }, + { + "type": "word", + "value": "spamtest" + }, + { + "type": "word", + "value": "sponsor" + }, + { + "type": "word", + "value": "sponsorship" + }, + { + "type": "word", + "value": "stalwart" + }, + { + "type": "word", + "value": "support" + }, + { + "type": "word", + "value": "team" + }, + { + "type": "word", + "value": "test" + }, + { + "type": "word", + "value": "work" + }, + { + "type": "word", + "value": "would" + }, + { + "type": "word", + "value": "yash" + }, + { + "type": "unicode_category", + "value": "Sm" + }, + { + "type": "sender", + "value": "spamtest.com" + }, + { + "type": "sender", + "value": "yashbansal@spamtest.com" + }, + { + "type": "url", + "value": "calendly.com" + }, + { + "type": "url", + "value": "spamtest-dot-yamm-track.appspot.com" + }, + { + "type": "hostname", + "value": "gmail.com" + }, + { + "type": "hostname", + "value": "gmailapi.google.com" + }, + { + "type": "hostname", + "value": "google.com" + }, + { + "type": "hostname", + "value": "mail-wm1-x32d.google.com" + }, + { + "type": "hostname", + "value": "mail.gmail.com" + }, + { + "type": "mime_type", + "value": "multipart/alternative" + }, + { + "type": "mime_type", + "value": "text/html" + }, + { + "type": "mime_type", + "value": "text/plain" + }, + { + "type": "html_image", + "src": "https" + }, + { + "type": "html_anchor", + "href": "https" + } +] + +Delivered-To: hello@stalw.art +Received: from Beijing--------chsi.com.cn (unknown [182.107.82.163] (AS4134 Chinanet, CN)) + by mail.stalw.art (Stalwart SMTP) with ESMTP id 3D6F265DB441EFD; + Thu, 27 Nov 2025 02:01:35 +0000 +Received-SPF: none (mail.stalw.art: no SPF records found for hello-------锟斤拷锟斤拷------kefu@beijing--------chsi.com.cn) + receiver=mail.stalw.art; client-ip=182.107.82.163; envelope-from="hello-------锟斤拷锟斤拷------kefu@beijing--------chsi.com.cn"; helo=Beijing--------chsi.com.cn; +Return-Path: +Message-ID: <187bbaa5967a07c4.15a3b47be71017b4.d92ade25f52781da@mail.stalw.art> +From: =?GB2312?B?zOGwzr36yf2439C9x+HLyciruOO2qDEwOjAxOjM0?= + +Subject: + =?GB2312?B?0afA+tGnzrvLq9akyKu5+rbAvNLL2bDssb6/xsu2yr+yqcq/ICC547jm?= AD hello +To: hello@stalw.art +Content-Type: multipart/mixed; + boundary="=_NextPart_2rfkindysadvnqw3nerasdf";charset="GB2312" +MIME-Version: 1.0 +Date: Thu, 27 Nov 2025 10:01:37 +0800 + +This is a multi-part message in MIME format + +--=_NextPart_2rfkindysadvnqw3nerasdf +Content-Type: text/plain +Content-Transfer-Encoding: 7bit + +10:01:34 hello AD + +--=_NextPart_2rfkindysadvnqw3nerasdf +Content-Type: application/octet-stream; + name="独家速办学历学位 学信网永久查询 本科硕博士高薪晋升职称轻松快速全搞定.txt" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; + filename="独家速办学历学位 学信网永久查询 本科硕博士高薪晋升职称轻松快速全搞定.txt" + +5YWo5Zu954us5a625p2D5aiB5Luj5Yqe77yB77yBDQoNCueLrOWutuWFqOWbvemrmOagoemZouez +u+S8mOi0qOi1hOa6kOa4oOmBk++8jOWFqOWbveeLrOWutuadg+WogeS7o+WKnu+8gQ0KDQrpq5jo +lqrlt6XkvZzvvIzkvJjljprogYzkvY3vvIzmj5Dmi5TmmYvljYfvvIzogYznp7Dor4TlrprvvIzm +iLflj6Plip7nkIbvvIzlh7rlm73np7vmsJEg6L275p2+5YWo5pCe5a6a77yB77yBDQoNCuS9oOaY +r+WQpuWboOS4uuayoeacieWkp+WtpuWtpuWOhuWSjOWtpuS9jeiAjOaJvuS4jeWIsOS4gOS7veeQ +huaDs+W3peS9nO+8jOaIluiAheWwveeuoeS9oOWcqOWunumZheW3peS9nOS4reenr+e0r+S6huS4 +sOWvjOe7j+mqjOWNtOWboOayoeacieWtpuWOhuWtpuS9jeivgeS5puiAjOWkseWOu+aPkOaLlOaZ +i+WNh+eahOacuuS8mj8g5oul5pyJ5LiA5Liq5aSn5a2m5a2m5Y6G77yI5a2m5L2N77yJ77yM56Gu +5L+d5L2g5Zyo5bel5L2c5LqL5Lia5LiK55qE5b+r6YCf5oiQ5Yqf77yBDQoNCuS4uuWuouaIt+in +o+WGs+WPkeWxleeTtumiiOacn+mXrumimO+8jOW/q+mAn+i9u+advuWunueOsOiBjOWKoeWNh+i/ +ge+8jOiBjOensOivhOWumuWyl+S9jeaZi+WNh++8jOWkh+WPl+engeS8geWkluS8geeMjuWktOaO +qOW0h++8jOaKlei1hOS6juWtpuWOhuWtpuS9jeS6p+eUn+eahOWbnuaKpeWSjOS7t+WAvOS5i+mr +mOi/nOi2heS7u+S9leaKlei1hOWTgeenje+8jOS4lOS4gOasoeaKlei1hOe7iOi6q+WPl+ebiuWP +r+aMgee7reWPkeWxle+8ge+8geaXouWPr+S7peeri+erv+ingeW9seWcqOiBjOWcuuWVhuWcuuWm +gumxvOW+l+awtO+8jOS5n+WPr+S7peS4uuS7iuWQjueahOaKpeiAg+WNh+i/geaPkOS+m+WdmuWu +nueahOWfuuehgO+8ge+8gQ0KDQrkvaDkuLrmsqHmnInlrabljobmib7kuI3liLDlpb3lt6XkvZzn +g6bmgbzlkJc/6L+Y5Zug5Li65rKh5pyJ5a2m5Y6G5peg5rOV5aSn5bmF5o+Q6auY5b6F6YGH5pS2 +5YWl6ICM5b+n6JmR5ZCX77yf6L+Y5Li65rKh5pyJ5q2j6KeE5aSn5a2m5a2m5Y6G5a2m5L2N6ICM +5peg5rOV5a6e546w6IGM56ew6K+E5a6a77yM5o+Q5ouU6YeN55So77yM5pmL57qn5Yqg6Jaq6ICM +54Om5oG85ZCX77yfIOWtpuWOhuaUueWPmOWRvei/kOWIm+mAoOS7t+WAvO+8ge+8geacrOWFrOWP +uOWPr+WKqeS9oOaIkOWKn+i+ieeFjO+8ge+8gQ0KDQrmnIDmnYPlqIHni6zlrrblnoTmlq3lhajl +m73pq5jmoKHpmaLns7votYTmupDmuKDpgZPvvIzlr7nmsYLogYzmmYvljYfot7Pmp73ljYfogYzm +tqjolqrmj5Dmi5Tpg73og73otbfliLDmnoHlhbboh7PlhbPph43opoHnmoTkvZznlKjvvIzku6Pn +kIbllYbliqDnm5/lvoXpgYfkvJjljprlm57miqXkuLDljprvvIHvvIHliJvpgKDku7flgLzov5zo +v5znianotoXmiYDlgLzvvIznu4jouqvlj5fnm4rvvIwg5pys56eR5Y2H56GV5aOr77yM56GV5aOr +5Y2H5Y2a5aOr77yM6IGM56ew5b6F6YGH57qn5Yir5Lmf6YO955u45bqU5b+r6YCf5o+Q5Y2H77yB +77yBDQoNCuWtpuS/oee9kemmlumhteacgOW6leagj+S4gOagj+KAnOiBlOezu+aIkeS7rOKAneS4 +iuacieWcsOWbvuaMh+W8leWcsOWdgOS7peWPiueUteivnemCrueuse+8jOWtpuS/oeWFrOWPuOWP +keW4g+W5v+WRiueahOmCrueuseaYr0BjaHNpLmNvbS5jbuWfn+WQjeWQjue8gOeahOWtpuS/oee9 +keacjeWKoeWZqO+8jOWbnuWkjeWNj+iuruS7peWPiuaUr+S7mOWuneaIlumTtuihjOi0puWPt+ea +hOmCrueuseaYr+WtpuS/oeWFrOWPuOWvueWkluWFrOW8gOeahOS8geS4mumCrueusWtlZnVAY2hz +aS5jb20uY24NCg0K5YWo5Zu954us5a625bi45bm05Yqe55CGOg0KDQrlhajml6XliLbnu5/mi5vp +h43ngrnpmaLmoKE5ODUgMjExIOWPjOS4gOa1gSDlhajlm73pq5jnrYnpmaLmoKHlrabljoblrabk +vY3or4HkuabvvIzmnKznp5Hlrabljoblj4zor4HlkKvlrablo6vlrabkvY3vvIjlt6Xlrablrabl +o6ss55CG5a2m5a2m5aOrLOWGnOWtpuWtpuWjqyznrqHnkIblrablrablo6ss57uP5rWO5a2m5a2m +5aOrLOWMu+WtpuWtpuWjqyzmlZnogrLlrablrablo6ss5paH5a2m5a2m5aOrLOazleWtpuWtpuWj +q+WtpuS9jeetie+8iQ0K5Y+M6K+B5YWo5pel5Yi257uf5oub56GV5aOrL+WcqOiBjOehleWjq+eg +lOeptueUn++8iOW3peWVhueuoeeQhuehleWjq++8iE1CQe+8ieaVmeiCsuehleWjq++8iE1FQe+8 +ieazleW+i+ehleWjq++8iEpN77yJ6YeR6J6N566h55CG56GV5aOrRk1CQe+8jOazleWtpuehleWj +q++8jOWFrOWFseeuoeeQhuehleWjq01QQe+8jOWFrOWFseWNq+eUn+ehleWjq++8jOW3peeoi+eh +leWjq++8jOS8muiuoeehleWjq01QQWNj77yM5bu6562R5a2m56GV5aOr77yM5Li05bqK5Yy75a2m +56GV5aOr77yM6Im65pyv56GV5aOrTUZB562J77yJ5YWo5pel5Yi25oiW5Zyo6IGM5Y+M6K+B5Y2a +5aOr5LiT5Lia5Z6L5Y2a5aOr77ya5bel56iL5Y2a5aOr77yIRW5nRO+8ieWMu+WtpuWNmuWjq++8 +iE1E77yJ5pWZ6IKy5Y2a5aOr77yIRWRE77yJ5a2m5pyv5Z6L5Y2a5aOr77ya5aaC5ZOy5a2m5Y2a +5aOrUGhE77yI57uP5rWO5a2m5Y2a5aOr77yMIOeuoeeQhuWtpuWNmuWjq++8jOS8muiuoeWtpuWN +muWjq+ivreiogOWtpuWNmuWjq++8jOS4tOW6iuWMu+WtpuWNmuWjq++8jOW3peWVhueuoeeQhuWN +muWjq++8jOmHkeiejeWtpuWNmuWjq+W/g+eQhuWtpuWNmuWjq++8jOekvuS8muWtpuWNmuWjq++8 +jOaWsOmXu+WtpuWNmuWjq++8jOazleWtpuWNmuWjq++8jOWFrOWFseeuoeeQhuWNmuWjq++8jOaV +meiCsuWtpuWNmuWjq++8jOiuoeeul+acuuWNmuWjq++8jOaWh+WtpuWNmuWjq+etie+8iQ0K5a2m +5L2N6K+B5Lmm77yI5a2m5aOr77yM56GV5aOrLCDljZrlo6vvvIkNCg0K5Z2H5o+Q5L6b5a6M5aSH +5a2m57GN5qGj5qGI5oiQ57up5Y2V77yM5aSn5a2m6Iux6K+t5Zub5YWt57qnY2V0NCxjZXQ25ZCI +5qC85oiQ57up5Y2V6K+B5piO77yM5rS+6YGj6K+B77yM55S15a2Q5rOo5YaM5YWl5a2m5L+h572R +5pWw5o2u5bqT77yM57uI6Lqr5rC45LmF5Y+v5p+l77yM5Y+v57uP5YWs6K+B5aSE5YWs6K+B77yM +56Gu5L+d6aG65Yip6YCa6L+H5ZCE56eN5b2i5byP55qE5a6h5p+l6aqM6K+B77yM5Y+v55So5LqO +5oql6ICD5YWs5Yqh5ZGY77yM5ZCE57G76LWE5qC86ICD6K+V77yM6ICD56CU77yM5Y2H6IGM77yM +6K+E6IGM56ew562J55So6YCU77yM5omA5Yqe6K+B5Lmm55Sx5YWo5Zu95ZCE5Zyw5Zu956uL5YWs +5Yqe6Zmi5qCh5YaF6YOo5rig6YGT5YWz57O75Yqe55CG77yM5qyi6L+O6ZW/5pyf5Luj55CG5ZCI +5L2c5Zue5oql5Liw5Y6a77yB5YWo5Zu96L+R55m+5a625Luj55CG5py65p6E77yM5Lia5Yqh6YGN +5biD5YWo5Zu977ya5YyX5LqsIOS4iua1tyDmt7HlnLMg5aSp5rSlIOadreW3niAg5Y2X5LqsICDl +jqbpl6ggIOW5v+W3niAg5q2m5rGJICDmiJDpg70gIOmDkeW3niDkuJzojp4gIOa1juWNlyAg56aP +5bee562J5Zyw5Yy677yB77yB6LWE5rqQ5oyB57ut5aKe6ZW/77yBDQoNCuacrOWFrOWPuOS4muWK +oemAgueUqOS6juWQhOexu+mrmOerr+WuouaIt+e+pCjkuJPkuJrlrp7ot7Xog73lipvlvLog5LyB +5Lia5Li75ZKM5ZCE6KGM5Lia6YeR6aKG562JKeeahOWtpuWOhuWtpuS9jeWumuWQkeS8mOWMluaV +tOWQiOWNh+e6p++8jOWbnuaKpeeOh+mrmO+8jOW5s+WPsOi1hOa6kOaWueWQkeeahOmAieaLqeWG +s+WumuS6huiBjOWcuuWVhuWcuuS4iueahOmjjueUn+awtOi1t+S4gOmprOW5s+W3ne+8ge+8gemA +ieaLqeavlOWKquWKm+mHjeimgSznq5nlnKjlt6jkurrogqnohoDkuIrmiY3og73po57lvpfmm7Tp +q5gs5Luj55CG5ZWG5pS/562W5LyY5Y6a6L+U5Yip5Liw5Y6a77yM5qyi6L+O6L2s5Y+R5o6o6I2Q +77yM6ZW/5pyf5qyi6L+O5ZCE55WM5oul5pyJ5a6i5oi36LWE5rqQ5rig6YGT55qE5Luj55CG5Yqg +55uf77yB77yBDQoNCuWFqOWll+aho+ahiOWtpuexjeWtpuWOhuWtpuS9jeS7t+agvO+8mg0KDQrk +u7fkvY3mjInkuI3lkIzmoIflh4Y5ODUgMjExIOWPjOS4gOa1gSDmma7pgJrph43ngrnlkI3niYzp +maLmoKHkuInmoaPlt67liKvlkozkuJPkuJrng63luqblt67liKvvvIzlhajluKblrabljoblrabk +vY3lrabnsY3moaPmoYjliqDlvIDpgJrlrabkv6HnvZHnu4jouqvmsLjkuYXmlbDmja7ms6jlhozm +n6Xor6INCg0KdW5kZXJncmFkdWF0ZSDmnKznp5Hlrablo6vlrabkvY3lj4zor4Ey5LiHNei1tyAg +5qC55o2u6Zmi5qCh5LiT5Lia54Ot6Zeo56iL5bqm5bGC5qyh6LCD5pW0ICDlpoLvvJrljJfkuqzl +jJfkuqznkIblt6XlpKflraYt6K6h566X5py65a2m6ZmiLeiuoeeul+acuuenkeWtpuS4juaKgOac +r+acrOenkS3lt6Xlrablrablo6vlrabkvY3vvIzkuIrmtbflkIzmtY7lpKflraYt57uP5rWO566h +55CG5a2m6ZmiLeeJqea1geeuoeeQhuacrOenkS3nrqHnkIblrablrablo6vlrabkvY0gIOWMl+S6 +rOWNj+WSjOWMu+WtpumZoi3kuLTluorljLvlraYt5Yy75a2m5a2m5aOr5a2m5L2NICDmuIXljY7l +pKflraYgIOS4reWbveS6uuawkeWkp+WtpiAg5YyX5Lqs5biI6IyD5aSn5a2mICDlpI3ml6blpKfl +raYgIOS4iua1t+S6pOmAmuWkp+WtpiDkuK3lsbHlpKflraYg5Y2O5Y2X55CG5bel5aSn5a2m5Lit +5Zu956eR5oqA5aSn5a2mICDkuK3ljZflpKflraYg5bGx5Lic5aSn5a2mIOWNl+S6rOWkp+WtpiDl +jY7kuK3np5HmioDlpKflraYgIOWbvemYsuenkeaKgOWkp+WtpiDljZflvIDlpKflraYg5Lit5Zu9 +5Yac5Lia5aSn5a2mIOetiSANCg0KZ3JhZHVhdGUg5YWo5pel5Yi256GV5aOr5Zyo6IGM56GV5aOr +M+S4hzUtLTXkuIfotbcgIOehleWjq+eglOeptueUn+WtpuWOhuWtpuS9jeWPjOivgSAg5Zyo6IGM +56CU56m255Sf5a2m5Y6G5a2m5L2N5Y+M6K+BIOWmguW3peWVhueuoeeQhuehleWjq01CQeOAgeWF +rOWFseeuoeeQhuehleWjq01QQSAgICAg5Y6f5aeL5a2m5Y6G5qC55o2u5LiN5ZCM5LiT5Lia6KaB +5rGC6ZyA6KaB5pys56eR5a2m5Y6G5oiW5a2m5aOr5a2m5L2NICDlpoLvvJrkuK3lm73kurrmsJHl +pKflraYt5ZWG5a2m6ZmiLeW3peWVhueuoeeQhuWtpuehleWjqyBNQkEsRU1CQe+8iOWcqOiBjCDl +hajml6XliLbvvIksIOWMl+S6rOmmlumDvee7j+a1jui0uOaYk+Wkp+Wtpi3nu4/mtY7lrabpmaIt +IOS6p+S4mue7j+a1juWtpu+8iOi0uOaYk+e7j+a1ju+8ieehleWjq++8iOWcqOiBjCDlhajml6Xl +iLbvvInvvIzljJfkuqzlpKflraYt5YWJ5Y2O566h55CG5a2m6ZmiLU1CQSBFTUJBICAg5LiK5rW3 +5aSN5pem5aSn5a2mLeaWsOmXu+WtpumZoi3mlrDpl7vlrabkuJPkuJrnoZXlo6sgICDljY7kuJzl +uIjojIPlpKflraYg5YyX5Lqs6Iiq56m66Iiq5aSp5aSn5a2mIOS4iua1t+i0oue7j+Wkp+WtpiAg +5q2m5rGJ5aSn5a2mICAg5rWZ5rGf5aSn5a2mIOetiQ0KDQpEciDljZrlo6vnoJTnqbbnlJ/lrabl +joblrabkvY3lj4zor4EgNuS4hyDotbcgIOmcgOacieehleWjq+WtpuWOhuaIluWtpuS9jSAg5aaC +77ya5YyX5Lqs5aSn5a2m57uP5rWO5a2m6Zmi57uP5rWO5a2m5Y2a5aOrICAg5Lit5aSu6LSi57uP +5aSn5a2m6YeR6J6N5a2m6Zmi6YeR6J6N5bel56iL5LiT5Lia5Y2a5aOrICAg5Lit5bGx5aSn5a2m +5Yy75a2m6Zmi5Yy75a2m5Y2a5aOrICDljY7ljZfnkIblt6XlpKflraYgICDmtZnmsZ/lpKflraYt +6K6h566X5py656eR5a2m5LiO5oqA5pyv5a2m6ZmiLeeUteWtkOS/oeaBr+W3peeoi+WNmuWjqyAg +5Y2X5Lqs5aSn5a2mLeWVhuWtpumZoi3lupTnlKjnu4/mtY7lrabljZrlo6sgICDlk4jlsJTmu6jl +t6XkuJrlpKflraYgICDkuK3lm73mtbfmtIvlpKflraYgICDlpKnmtKXlpKflraYgICDljqbpl6jl +pKflraYgICDkuK3lm73np5HlrabmioDmnK/lpKflrabnrYkNCg0K5Yqe55CG6Z2e5bi45b+r5o23 +77yMMS0z5Liq5bel5L2c5pel5Y2z5Y+v5Yqe5aW95qGj5qGI6K+B5Lmm5a2m5L2N5a2m57GN6Iux +6K+t6K+B5Lmm562J5Y6f5Lu25bm25rOo5YaM5byA6YCa5a2m5L+h572R6K6k6K+B5pWw5o2u5bqT +5p+l6K+i77yM57uI6Lqr5rC45LmF5pyJ5pWI5p+l6K+i77yM5pys5YWs5Y+45omL5py65Y+35b6u +5L+h5Y+36ZW/5pyf5a6e5ZCN6K6k6K+B77yM5LyB5Lia6YKu566x5a2m5L+h572R5Z+f5ZCN5pyN +5Yqh5ZmoQGNoc2kuY29tLmNuIOWunuWQjeWkh+ahiO+8jOWvueWFrOi0puWPt+aUtuasvu+8jOWF +qOmdouaUr+aMgeaUr+S7mOWuneW+ruS/oeaJq+eggeWSjOe9keS4iumTtuihjOaJi+acuumTtuih +jEFQUOaUr+S7mCzmrKLov47lhajlm73ku6PnkIbllYbliqDnm5/lkIjkvZzvvIHvvIENCg0K5Yqe +55CG5rWB56iLOg0KDQrlpIfpvZDnlLPor7fmnZDmlpnihpLlrqHmoLjpgJrov4fihpLpppbku5gz +MCXlrabnsY3ms6jlhozotLnnlKjvvIjlr7nlhazotKbmiLfmlLbmrL7vvInihpLlip7lpb3lj5Hp +gIHor4Hkuabmiavmj4/ku7bmn6Xor6Lpqozor4Hmu6HmhI/ihpLmlK/ku5jkvZnmrL7ihpLlj5Hp +obrkuLDlv6vpgJLmlLblj5blhajlpZfor4Hkuabljp/ku7bvvIzljJfkuqzkuIrmtbfmt7HlnLPl +nLDljLrpl6rpgIEgICANCg0K5YyX5Lqs5oC76YOo5Zyw5Z2A77ya5YyX5Lqs5biC6KW/5Z+O5Yy6 +6KW/55u06Zeo5aSW5aSn6KGXMTjlj7fph5HotLjlpKfljqZDM+W6p+OAgA0K5LiK5rW35YWs5Y+4 +5Zyw5Z2A77ya5LiK5rW35biC5rWm5Lic5paw5Yy65rWm5Lic5Y2X6LevMTA3OOWPt+S4reiejeWk +p+WOpjYwOA0K5rex5Zyz5Yqe5YWs5Zyw5Z2A77ya5rex5Zyz5biC5Y2X5bGx5Yy65rex5Zyz5aSn +5a2m5Z+O5a2m6IuR5aSn6YGTMTA2OOWPt0bmoIsxODA45a6kDQoNCuWtpuS/oee9keezu+aVmeiC +sumDqOaMh+WumuWUr+S4gOWtpuWOhuiupOivgeafpeivoue9keerme+8jOe9keWdgCB3d3cuY2hz +aS5jb20uY24gICANCg0K5pS25qy+6LSm5Y+3IA0K5oi35ZCN77ya5YyX5Lqs5a2m5L+h5ZKo6K+i +5pyN5Yqh5pyJ6ZmQ5YWs5Y+4ICAg5oi35ZCN77ya5rex5Zyz5biC5pm65L+h5paw5L+h5oGv5oqA +5pyv5pyJ6ZmQ5YWs5Y+4ICAg5oi35ZCN77ya5LiK5rW35a2m5L+h5pWZ6IKy56eR5oqA5pyJ6ZmQ +5YWs5Y+4DQrlvIDmiLfooYzvvJrkuK3lm73msJHnlJ/pk7booYzljJfkuqzluILopb/ln47ljLrl +ub/lronpl6jmlK/ooYwgIOW8gOaIt+ihjO+8muW3peWVhumTtuihjOa3seWcs+W4guWNl+WxseaU +r+ihjCAg5byA5oi36KGM77ya5oub5ZWG6ZO26KGM5LiK5rW35biC5rWm5Lic5aSn6YGT5pSv6KGM +DQoNCuWKnueQhuWtpuWOhuWtpuS9jeivt+iBlOezuyDljJfkuqzmgLvpg6jnlLXor506IDEzOTgz +MTI1MTUx77yI5b6u5L+h5ZCM5Y+377yJIOW+ruS/oe+8mmNoc2l4dyDnjovlu7rmtpvogIHluIgg +77yI5Li75Lu76LSf6LSj5Lq6ICDlrabkv6HnvZHmlbDmja7lupPnoJTlj5Hnu7TmiqTljYfnuqcg +77yJIOW+ruS/oeWPt++8mmNoc2l4dyAgICDpgq7nrrE6IGtlZnVAY2hzaS5jb20uY24gICAgICAg +IFFROjY2ODg4OCAgIA0KDQrlrqLmiLcv5Luj55CG5ZWG6YGN5biD5YWo5Zu977ya5YyX5LqsIOS4 +iua1tyDmt7HlnLMg5aSp5rSlIOadreW3niDljZfkuqwg6IuP5beeIOWOpumXqCDlub/lt54g6YeN +5bqGIOatpuaxiSDmiJDpg70g6YOR5beeIOS4nOiOniDpnZLlspsg5rWO5Y2XIOetieWQhOWkp+WM +ug0KDQrmt7vliqDlvq7kv6Hpobvnn6XvvJrliqDlvq7kv6Hlkqjor6Llip7nkIbliY3vvIzor7fl +hYjnoa7lrprlrqLmiLflubTpvoTvvJ/mhI/lkJHlrabljobnmoTmgKfotKjvvIjlhajml6XliLbn +u5/mi5sg6Ieq6ICD77yJ77yf6Zmi5qCh5Zyw5Yy65LiT5Lia77yfIOW3peS9nOS6uuWRmOS8muWF +iOaKpeS7t++8jOWGs+WumuWKnueQhueahOWuouaIt+ivt+aJk+W8gOWtpuS/oee9keeZu+W9lemm +lumhteW3puS4iuinkuWtpuWOhuafpeivoumhtemdouWQjuadpeeUteivne+8jOe7meaIkeS7rOWK +nuWFrOS8geS4mumCrueusWtlZnVAY2hzaS5jb20uY27lj5HpgIHlpIfpvZDnmoTnlLPlip7mnZDm +lpnpgq7ku7blkI7vvIzmiJHku6zlj6/ku6Xnu5nlrqLmiLfmn6XnnIvov5HmnJ/lip7lpb3nmoTl +rabljobmoLfmnKwg6L6T5YWl5aeT5ZCN6K+B5Lmm57yW5Y+35Y2z5Y+v5p+l6K+i6aqM6K+B77yM +5qyi6L+O5pyJ5a6i5oi36LWE5rqQ5a6e5Yqb55qE5py65p6E5Liq5Lq65Yqg55uf5Luj55CG5aSn +5bGV5a6P5Zu+77yB77yBDQoNCuacrOWFrOWPuOaJi+acuuWPt+W+ruS/oeWPt+mVv+acn+WunuWQ +jeiupOivge+8jOS8geS4mumCrueuseWtpuS/oee9keWfn+WQjeacjeWKoeWZqEBjaHNpLmNvbS5j +bumVv+acn+WunuWQjeWkh+ahiO+8jOWunuWQjei0puWPt+WFqOmdouaUr+aMgeaUr+S7mOWunemT +tuiBlOe9kemTtuaUr+S7mCzku6PnkIbllYbplb/mnJ/lkIjkvZzlronlhajlv6vmjbfvvIENCg0K +5pyA5aW955qE5Y+j56KR5ZKM5L+h6KqJLCDni6zlrrbpm4TljprotYTmupAs5bey5oiQ5Yqf5Li6 +5aSn6YeP5rW35YaF5aSW5a6i5oi35ZyG5ruh5LqG5qKm5oOz77yMIOS4gOOAgeaVmeiCsumDqOiu +pOivgee9keWSjOWtpuagoee9keWdh+WPr+S7peafpeivou+8jOWPr+S+m+eUqOS6uuWNleS9jeWS +jOacieWFs+mDqOmXqOeUteivneWSqOivouWSjOS4iue9keiwg+afpSAg5LqM44CB5pyJ5a6M5pW0 +6b2Q5YWo55qE5qGj5qGI44CB5a2m57GN44CB6ICD6K+V5oiQ57up5Y2V44CB5YWl5a2m55m76K6w +6KGo44CB5q+V5Lia55m76K6w6KGo562J44CC5a+55rGC6IGM44CB5bCx5Lia44CB5bqU6IGY44CB +5pmL57qn44CB5rao6Jaq44CB6IGM56ew6K+E5a6a44CB6LWE5qC85oql6ICD44CB562J57qn6K6k +6K+B44CB5Ye65Zu944CB55WZ5a2m44CB56e75rCR44CB5a2m5Y6GIOWFrOivgeetiemDveWFt+ac +ieaViOWKm+OAgiDkuInjgIHkv53or4Hlv6vmjbfku7fkvJjvvJrlm6DkuLrmmK/lrabmoKHnm7Tm +jqXlh7ror4Hnm7TmjqXlip7nkIbvvIzmiYDku6Xkv53or4Hkuoblh7ror4Hlv6vpgJ/vvIzku7fm +oLzkvJjmg6DjgIIg5biC5Zy65peg5Y+v6ZmQ6YeP77yM5qyi6L+O5Yqg55uf5Luj55CG77yM5LiA +5qyh5om56YeP5o+Q5Lqk5Yqe55CG5a6i5oi377yM5Y+v5p2l5pys5YWs5Y+46Z2i6LCI562+57qm +77yM5Luj55CG5ZWG5Yqg55uf5b6F6YGH5LyY5Y6a5Zue5oql5Liw5Y6a77yB77yBDQoNCui/keW5 +tOadpeWBh+ivgeS5puaXqeW3sue7j+W9u+W6leiiq+a3mOaxsO+8jOaXoOiuuue6uOW8oOinhOag +vOi0qOWcsOmYsuS8quawtOWNsOi/mOaYr+avleS4muivgeS5pueahOe8luWPt+WtpuS9jeivgeS5 +pueahOe8luWPt++8jOi/mOacieWtpuexjeWPt+aho+ahiOe8luWPt++8jOmDveaXqeW3suWFqOmD +qOiBlOe9keWIsOaVmeiCsumDqOWtpuS/oee9keeahOaVsOaNruW6k+S6hu+8jOaXoOiuuuaYr+aK +peiAg+i/mOaYr+W6lOiBmOmdouivleaIluaYr+WFrOivge+8jOebuOWFs+W3peS9nOS6uuWRmOmD +veaYr+eZu+mZhuWtpuS/oee9keaVsOaNruW6k+W5s+WPsOadpeafpemqjOWtpuWOhuivgeS5puea +hOecn+S8quOAgg0KDQrmnKzlpITni6zlrrbnmoTotYTmupDmnYPpmZDkvb/lvpflrqLmiLfkuI3n +lKjlho3ovpvoi6blpIfogIPogJfotLnml7bpl7Tnsr7lipvlj4LliqDmvKvplb/nuYHnkJDnmoTl +rabljobogIPor5XvvIzlj6ropoHkvaDlhbflpIfkuIDlrprnmoTkuJPkuJrln7rnoYDvvIzop4Tl +iJLorr7orqHmnIDkvbPnmoTogYzkuJrmlrnlkJHvvIzkuLrkuI3lkIzlrqLmiLfmjqjojZDorqLl +iLbkuI7ogYzkuJrlkozmnKrmnaXlj5HlsZXpq5jluqbljLnphY3nmoTlrabljoblrabkvY3vvIzn +u4jouqvmsLjkuYXlrabkv6HnvZHmn6Xor6LvvIzmnKzlrabljoblrabkvY3kuJrliqHmnIDpgILl +kIjlhbflpIfovoPlvLrlt6XkvZzog73lipvmnInovoPlpb3ku47kuJrlsaXljobnmoTpq5jnq6/l +rqLmiLfvvIzljIXmi6zmjIflrprpmaLns7vkuJPkuJrnmoTlnKjogYznu5/mi5vlhajml6XliLbm +nKznp5HnoZXlo6vljZrlo6vnoJTnqbbnlJ/np4HkurrlrprliLbvvIzluK7liqnlub/lpKfog73l +ipvlh7rkvJfnu4/mtY7kvJjotornmoTlrqLmiLflrp7njrDkuobogYzlnLrpo57ot4PllYblnLro +hb7po57ku5XpgJTlubPmraXpnZLkupHvvIHvvIHpgInmi6nmr5Tliqrlipvmm7Tph43opoHvvIzk +uI7ml7bkv7Hov5vnq5nlnKjlt6jkurrnmoTogqnohoDkuIrkvaDlj6/ku6Xpo57lvpfmm7Tpq5jv +vIHvvIEgDQoNCuWKnueQhuWtpuWOhuWtpuS9jeivt+iBlOezuyDljJfkuqzmgLvpg6jnlLXor506 +IDEzOTgzMTI1MTUx77yI5b6u5L+h5ZCM5Y+377yJIOW+ruS/oe+8mmNoc2l4dyDnjovlu7rmtpvo +gIHluIgg77yI5Li75Lu76LSf6LSj5Lq6ICDlrabkv6HnvZHmlbDmja7lupPnoJTlj5Hnu7TmiqTl +jYfnuqcg77yJIOW+ruS/oeWPt++8mmNoc2l4dyAgICDpgq7nrrE6IGtlZnVAY2hzaS5jb20uY27v +vIjkvIHkuJrpgq7nrrFsZDg4ODhAMTg4LmNvbe+8iSAgICAgICAgUVE6NjY4ODg4ICAgDQoNCua3 +u+WKoOW+ruS/oemhu+efpe+8muWKoOW+ruS/oeWSqOivouWKnueQhuWJje+8jOivt+WFiOehruWu +muWuouaIt+eahOW5tOm+hCDmiYDlip7mhI/lkJHlrabljobnmoTmgKfotKjvvIjlhajml6XliLbn +u5/mi5sg6Ieq6ICD77yJIOmZouagoeWcsOWMuuS4k+S4miDlt6XkvZzkurrlkZjkvJrlhYjlm57l +pI3miqXku7fvvIzlhrPlrprlip7nkIbnmoTlrqLmiLfor7fmiZPlvIDlrabkv6HnvZHnmbvlvZXp +ppbpobXlt6bkuIrop5Llrabljobmn6Xor6LpobXpnaLlkI7mnaXnlLXor53vvIznu5nmiJHku6zl +t6XkvZzpgq7nrrFrZWZ1QGNoc2kuY29tLmNu5Y+R6YCB5aSH6b2Q55qE55Sz5Yqe5p2Q5paZ6YKu +5Lu25ZCO77yM5oiR5Lus5Y+v5Lul57uZ5a6i5oi35p+l55yL5oiR5Lus6L+R5pyf5Yqe5aW955qE +5a2m5Y6G5qC35pys6L6T5YWl5aeT5ZCN5q+V5Lia6K+B5Lmm57yW5Y+35Y2z5Y+v5p+l6K+i6aqM +6K+B77yM5qyi6L+O5pyJ5a6i5oi36LWE5rqQ5a6e5Yqb55qE5py65p6E5Yqg55uf5Luj55CG5aSn +5bGV5a6P5Zu+ISENCg0K6ZmEOiDnlLPlip7lrabljobmiYDpnIDmnZDmlpkNCg0KMS7lrabljobm +gKfotKjvvIjnu5/mi5sgIOaIkOS6uuaVmeiCsi/lnKjogYwgIOiHquWtpuiAg+ivle+8iQ0KDQrp +maLmoKHlkI3np7DvvIjlkITlnLDljLrlm73nq4vlhazlip7pmaLmoKHvvIkNCg0K5a2m5Y6G5bGC +5qyh77yI5LiT56eRIOacrOenkeWtpuWjqyDnoZXlo6vnoJTnqbbnlJ8g5Y2a5aOr56CU56m255Sf +IOWmgk1CQSBFTUJBIOWQhOexu+W3peeoi+ehleWjq++8iQ0KDQrmr5XkuJrml7bpl7TvvIjoh6ro +gIPkuLrmr4/lubQ25pyI5bqVMTLmnIjlupXlkITmr5XkuJrnmbvorrDkuIDmrKEg57uf5oubL+aI +kOaVmS/lnKjogYzkuLrmr4/lubQ35pyI77yJDQoNCjIuIOiTneiJsuW6leS4pOWvuOaVsOeggeiv +geS7tuW9qeeFp++8iOWbvueJh+aWh+S7tuWPr+WOi+e8qeWQjueUqOmCruS7tumZhOS7tuS4iuS8 +oOWPkeadpe+8iQ0KDQrouqvku73or4HmraPpnaLmiavmj4/ku7bvvIjnlKjpgq7ku7bpmYTku7bk +uIrkvKDlj5HmnaXvvIkNCg0KMy4g5Y6f5aeL5a2m5Y6G5a2m5L2N5Y+R5p2l5LiO5ZCm6KeG5oiQ +5Lq65pWZ6IKyL+S4k+WNh+acrC/lnKjogYznoZXlo6vnrYnlrabljobnmoTkuI3lkIzlhbfkvZPo +poHmsYINCg== + +--=_NextPart_2rfkindysadvnqw3nerasdf-- + + +[ + { + "type": "word", + "value": "_word" + }, + { + "type": "word", + "value": "ad" + }, + { + "type": "word", + "value": "hello" + }, + { + "type": "word", + "value": "全国" + }, + { + "type": "word", + "value": "博士" + }, + { + "type": "word", + "value": "双" + }, + { + "type": "word", + "value": "学位" + }, + { + "type": "word", + "value": "学历" + }, + { + "type": "word", + "value": "广告" + }, + { + "type": "word", + "value": "本科" + }, + { + "type": "word", + "value": "独家" + }, + { + "type": "word", + "value": "硕士" + }, + { + "type": "word", + "value": "证" + }, + { + "type": "word", + "value": "速办" + }, + { + "type": "number", + "code": [ + 105, + 2 + ] + }, + { + "type": "sender", + "value": "beijing--------chsi.com.cn" + }, + { + "type": "sender", + "value": "hello-------北京------kefu@beijing--------chsi.com.cn" + }, + { + "type": "hostname", + "value": "beijing--------chsi.com.cn" + }, + { + "type": "attachment", + "value": "!txt" + }, + { + "type": "attachment", + "value": "_信" + }, + { + "type": "attachment", + "value": "_全" + }, + { + "type": "attachment", + "value": "_博士" + }, + { + "type": "attachment", + "value": "_学" + }, + { + "type": "attachment", + "value": "_学位" + }, + { + "type": "attachment", + "value": "_学历" + }, + { + "type": "attachment", + "value": "_快速" + }, + { + "type": "attachment", + "value": "_搞定" + }, + { + "type": "attachment", + "value": "_晋升" + }, + { + "type": "attachment", + "value": "_本科" + }, + { + "type": "attachment", + "value": "_查询" + }, + { + "type": "attachment", + "value": "_永久" + }, + { + "type": "attachment", + "value": "_独家" + }, + { + "type": "attachment", + "value": "_硕" + }, + { + "type": "attachment", + "value": "_网" + }, + { + "type": "attachment", + "value": "_职称" + }, + { + "type": "attachment", + "value": "_轻松" + }, + { + "type": "attachment", + "value": "_速办" + }, + { + "type": "attachment", + "value": "_高薪" + }, + { + "type": "mime_type", + "value": "application/octet-stream" + }, + { + "type": "mime_type", + "value": "multipart/mixed" + }, + { + "type": "mime_type", + "value": "text/plain" + } +] + diff --git a/tests/resources/smtp/antispam/classifier_html.test b/tests/resources/smtp/antispam/classifier_html.test new file mode 100644 index 00000000..477deb29 --- /dev/null +++ b/tests/resources/smtp/antispam/classifier_html.test @@ -0,0 +1,695 @@ +hello
world
+ +[ + { + "type": "StartTag", + "name": 1819112552, + "attributes": [], + "is_self_closing": false + }, + { + "type": "Text", + "text": "hello" + }, + { + "type": "StartTag", + "name": 29282, + "attributes": [], + "is_self_closing": true + }, + { + "type": "Text", + "text": "world" + }, + { + "type": "StartTag", + "name": 29282, + "attributes": [], + "is_self_closing": true + }, + { + "type": "EndTag", + "name": 1819112552 + } +] + +using <>
+ +[ + { + "type": "StartTag", + "name": 1819112552, + "attributes": [], + "is_self_closing": false + }, + { + "type": "Text", + "text": "using <>" + }, + { + "type": "StartTag", + "name": 29282, + "attributes": [], + "is_self_closing": true + }, + { + "type": "EndTag", + "name": 1819112552 + } +] + +test tag
+ +[ + { + "type": "Text", + "text": "test" + }, + { + "type": "StartTag", + "name": 7630702, + "attributes": [ + [ + 29282, + null + ] + ], + "is_self_closing": true + }, + { + "type": "Text", + "text": " tag" + }, + { + "type": "StartTag", + "name": 29282, + "attributes": [], + "is_self_closing": true + } +] + +<>< >>hello world< br + /> + +[ + { + "type": "StartTag", + "name": 6775156, + "attributes": [], + "is_self_closing": true + }, + { + "type": "Text", + "text": ">hello world" + }, + { + "type": "StartTag", + "name": 29282, + "attributes": [], + "is_self_closing": true + } +] + +ignore headxyz

<body>

+ +[ + { + "type": "StartTag", + "name": 1684104552, + "attributes": [], + "is_self_closing": false + }, + { + "type": "StartTag", + "name": 435611265396, + "attributes": [], + "is_self_closing": false + }, + { + "type": "Text", + "text": "ignore head" + }, + { + "type": "EndTag", + "name": 435611265396 + }, + { + "type": "StartTag", + "name": 7630702, + "attributes": [ + [ + 1684104552, + null + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "xyz" + }, + { + "type": "EndTag", + "name": 7630702 + }, + { + "type": "EndTag", + "name": 1684104552 + }, + { + "type": "StartTag", + "name": 12648, + "attributes": [], + "is_self_closing": false + }, + { + "type": "Text", + "text": "" + }, + { + "type": "EndTag", + "name": 12648 + } +] + +

what is ♥?

ßĂΒγ don't hurt me.

+ +[ + { + "type": "StartTag", + "name": 112, + "attributes": [], + "is_self_closing": false + }, + { + "type": "Text", + "text": "what is ♥?" + }, + { + "type": "EndTag", + "name": 112 + }, + { + "type": "StartTag", + "name": 112, + "attributes": [], + "is_self_closing": false + }, + { + "type": "Text", + "text": "ßĂΒγ don't hurt me." + }, + { + "type": "EndTag", + "name": 112 + } +] + +this is the actual text + +[ + { + "type": "Comment", + "text": "!--[if mso]> < < < < ignore > -> here --" + }, + { + "type": "Text", + "text": " the actual" + }, + { + "type": "Comment", + "text": "!--" + }, + { + "type": "Text", + "text": " text" + } +] + + < p > hello < / p > < p > world < / p > !!! < br > + +[ + { + "type": "StartTag", + "name": 112, + "attributes": [], + "is_self_closing": false + }, + { + "type": "Text", + "text": "hello" + }, + { + "type": "EndTag", + "name": 112 + }, + { + "type": "StartTag", + "name": 112, + "attributes": [], + "is_self_closing": false + }, + { + "type": "Text", + "text": " world" + }, + { + "type": "EndTag", + "name": 112 + }, + { + "type": "Text", + "text": " !!!" + }, + { + "type": "StartTag", + "name": 29282, + "attributes": [], + "is_self_closing": false + } +] + +

please unsubscribe here.

+ +[ + { + "type": "StartTag", + "name": 112, + "attributes": [], + "is_self_closing": false + }, + { + "type": "Text", + "text": "please unsubscribe" + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "#" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": " here" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "Text", + "text": "." + }, + { + "type": "EndTag", + "name": 112 + } +] + +texttexttexttext< a href = "e" >texttext< anchor href = "x">text + +[ + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "a" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "b" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "c" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "d" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "e" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 125779835187816, + "ignore" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 125822818283105, + "attributes": [ + [ + 1717924456, + "x" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + } +] + +texttexttexttext< a href = e >texttexttext + +[ + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "a" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "b" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "c" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "d" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "e" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 125779835187816, + "ignore" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 125822818283105, + "attributes": [ + [ + 1717924456, + "x" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + } +] + +text< a href = test ignore>text< a href = fudge href ignore>text a href = "unknown" + +[ + { + "type": "Comment", + "text": "!-- texttext--text--" + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "hello world" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "test" + ], + [ + 111542170183529, + null + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "fudge" + ], + [ + 1717924456, + null + ], + [ + 111542170183529, + null + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "text" + }, + { + "type": "EndTag", + "name": 97 + }, + { + "type": "StartTag", + "name": 97, + "attributes": [ + [ + 1717924456, + "foobar" + ] + ], + "is_self_closing": false + }, + { + "type": "Text", + "text": "a href = \"unknown\"" + }, + { + "type": "EndTag", + "name": 97 + } +] + diff --git a/tests/resources/smtp/antispam/combined.test b/tests/resources/smtp/antispam/combined.test index 1711d3e7..4580d5d8 100644 --- a/tests/resources/smtp/antispam/combined.test +++ b/tests/resources/smtp/antispam/combined.test @@ -7,7 +7,7 @@ spf_ehlo.result none dmarc.result none remote_ip 195.210.29.48 expect_header X-Spam-Result: ARC_NA (0.00), DKIM_NA (0.00), FROM_EQ_ENV_FROM (0.00), FROM_HAS_DN (0.00), HAS_DATA_URI (0.00), HAS_LINK_TO_LARGE_IMG (0.00), HTML_SHORT_1 (0.00), MID_RHS_MATCH_ENV_FROM (0.00), RCPT_COUNT_ONE (0.00), SPF_NA (0.00), SUBJECT_ENDS_EXCLAIM (0.00), TO_DN_NONE (0.00), TO_MATCH_ENVRCPT_ALL (0.00), RCVD_COUNT_ZERO (0.10), RCVD_NO_TLS_LAST (0.10), MIME_HTML_ONLY (0.20), HELO_NORES_A_OR_MX (0.30), AUTH_NA (1.00), DATE_IN_PAST (1.00), DMARC_NA (1.00), MID_RHS_MATCH_FROM (1.00), FROMHOST_NORES_A_OR_MX (1.50), HTML_SHORT_LINK_IMG_1 (2.00), RDNS_NONE (2.00), PYZOR (3.50) -expect_header X-Spam-Status: Yes, score=13.70 +expect_header X-Spam-Score: spam, score=13.70 From: Client Services To: licensing@stalw.art @@ -50,7 +50,7 @@ dmarc.result pass remote_ip 185.58.86.181 tls.version TLSv1.3 expect_header X-Spam-Result: DMARC_POLICY_ALLOW (-0.50), DKIM_ALLOW (-0.20), SPF_ALLOW (-0.20), MIME_GOOD (-0.10), ARC_NA (0.00), DKIM_SIGNED (0.00), FROM_EQ_ENV_FROM (0.00), FROM_HAS_DN (0.00), HAS_ATTACHMENT (0.00), HTML_SHORT_2 (0.00), RCPT_COUNT_ONE (0.00), RCVD_COUNT_THREE (0.00), TO_DN_EQ_ADDR_ALL (0.00), TO_MATCH_ENVRCPT_ALL (0.00), RCVD_NO_TLS_LAST (0.10), HELO_NORES_A_OR_MX (0.30), SUBJECT_ENDS_SPACES (0.50), URI_COUNT_ODD (0.50), DATE_IN_PAST (1.00), FORGED_RCVD_TRAIL (1.00), FROMHOST_NORES_A_OR_MX (1.50) -expect_header X-Spam-Status: No, score=3.90 +expect_header X-Spam-Score: ham, score=3.90 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tenthrevolution.com; s=mimecast20200102; t=1669138703; @@ -575,7 +575,7 @@ dmarc.policy reject remote_ip 51.89.165.39 tls.version TLS1_2 expect_header X-Spam-Result: DKIM_ALLOW (-0.20), HAS_LIST_UNSUB (-0.01), ARC_NA (0.00), DKIM_SIGNED (0.00), FROM_EQ_ENV_FROM (0.00), FROM_HAS_DN (0.00), HAS_EXTERNAL_IMG (0.00), HAS_LINK_TO_LARGE_IMG (0.00), HAS_REPLYTO (0.00), HTML_SHORT_1 (0.00), MID_RHS_MATCH_ENV_FROM (0.00), RCPT_COUNT_ONE (0.00), REPLYTO_ADDR_EQ_FROM (0.00), REPLYTO_EQ_FROM (0.00), SPF_SOFTFAIL (0.00), TO_DN_NONE (0.00), TO_MATCH_ENVRCPT_ALL (0.00), RCVD_COUNT_ZERO (0.10), RCVD_NO_TLS_LAST (0.10), HELO_NORES_A_OR_MX (0.30), DATE_IN_PAST (1.00), MID_RHS_MATCH_FROM (1.00), PARTS_DIFFER (1.00), FROMHOST_NORES_A_OR_MX (1.50), HTML_SHORT_LINK_IMG_1 (2.00), RDNS_NONE (2.00), VIOLATED_DIRECT_SPF (3.50), DMARC_POLICY_REJECT (4.00) -expect_header X-Spam-Status: Yes, score=16.29 +expect_header X-Spam-Score: spam, score=16.29 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=sectionalism; d=grupokonecta.net; h=To:Subject:Message-ID:Date:From:Reply-To:MIME-Version:List-Unsubscribe: @@ -791,8 +791,8 @@ dmarc.result pass dmarc.policy reject remote_ip 52.103.64.5 tls.version TLS1_2 -expect_header X-Spam-Result: DMARC_POLICY_ALLOW (-0.50), DKIM_ALLOW (-0.20), SPF_ALLOW (-0.20), ARC_NA (0.00), ARC_SIGNED (0.00), DKIM_SIGNED (0.00), FREEMAIL_FROM (0.00), FROM_EQ_ENV_FROM (0.00), FROM_HAS_DN (0.00), HAS_SEO_WORD (0.00), HAS_X_PRIO_ONE (0.00), HTML_SHORT_1 (0.00), MID_RHS_MATCH_ENV_FROMTLD (0.00), MID_RHS_MATCH_FROMTLD (0.00), RCPT_COUNT_ONE (0.00), RCPT_IN_BODY (0.00), RCVD_COUNT_TWO (0.00), TO_DN_EQ_ADDR_ALL (0.00), TO_MATCH_ENVRCPT_ALL (0.00), RCVD_NO_TLS_LAST (0.10), HELO_NORES_A_OR_MX (0.30), DATE_IN_PAST (1.00), HEADER_EMPTY_DELIMITER (1.00), FROMHOST_NORES_A_OR_MX (1.50), SEO_SPAM (5.00) -expect_header X-Spam-Status: Yes, score=8.00 +expect_header X-Spam-Result: DMARC_POLICY_ALLOW (-0.50), DKIM_ALLOW (-0.20), SPF_ALLOW (-0.20), ARC_NA (0.00), ARC_SIGNED (0.00), DKIM_SIGNED (0.00), FREEMAIL_FROM (0.00), FROM_EQ_ENV_FROM (0.00), FROM_HAS_DN (0.00), HAS_SEO_WORD (0.00), HAS_X_PRIO_ONE (0.00), HTML_SHORT_1 (0.00), MID_RHS_MATCH_ENV_FROMTLD (0.00), MID_RHS_MATCH_FROMTLD (0.00), RCPT_COUNT_ONE (0.00), RCPT_IN_BODY (0.00), RCVD_COUNT_TWO (0.00), TO_DN_EQ_ADDR_ALL (0.00), TO_MATCH_ENVRCPT_ALL (0.00), RCVD_NO_TLS_LAST (0.10), HELO_NORES_A_OR_MX (0.30), DATE_IN_PAST (1.00), FROMHOST_NORES_A_OR_MX (1.50), SEO_SPAM (5.00) +expect_header X-Spam-Score: spam, score=7.00 Return-Path: ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; @@ -1022,7 +1022,7 @@ dmarc.policy reject remote_ip 173.224.123.255 tls.version TLS1_2 expect_header X-Spam-Result: DMARC_POLICY_ALLOW (-0.50), DKIM_ALLOW (-0.20), SPF_ALLOW (-0.20), ARC_NA (0.00), DKIM_SIGNED (0.00), FROM_EQ_ENV_FROM (0.00), FROM_HAS_DN (0.00), HAS_EXTERNAL_IMG (0.00), HAS_REPLYTO (0.00), HAS_X_PRIO_THREE (0.00), HTML_SHORT_1 (0.00), RCPT_COUNT_ONE (0.00), REPLYTO_DN_EQ_FROM_DN (0.00), REPLYTO_DOM_EQ_FROM_DOM (0.00), TO_DN_ALL (0.00), TO_EQ_FROM (0.00), RCVD_COUNT_ZERO (0.10), RCVD_NO_TLS_LAST (0.10), HELO_NORES_A_OR_MX (0.30), MID_RHS_NOT_FQDN (0.50), UNPARSABLE_URL (0.50), DATE_IN_PAST (1.00), FROMHOST_NORES_A_OR_MX (1.50), DIRECT_TO_MX (2.00), FORGED_RECIPIENTS (2.00), SUBJ_ALL_CAPS (3.00) -expect_header X-Spam-Status: Yes, score=10.10 +expect_header X-Spam-Score: spam, score=10.10 Return-Path: DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=default; d=landeray.com; diff --git a/tests/resources/smtp/antispam/headers.test b/tests/resources/smtp/antispam/headers.test index 3aa7bc6a..a15423f2 100644 --- a/tests/resources/smtp/antispam/headers.test +++ b/tests/resources/smtp/antispam/headers.test @@ -53,6 +53,13 @@ expect HEADER_EMPTY_DELIMITER Subject:test +Test + +expect + +Subject: + test + Test expect MAILLIST diff --git a/tests/resources/smtp/antispam/replies_in.test b/tests/resources/smtp/antispam/replies_in.test deleted file mode 100644 index 3ba9f2a0..00000000 --- a/tests/resources/smtp/antispam/replies_in.test +++ /dev/null @@ -1,24 +0,0 @@ -expect TRUSTED_REPLY - -In-Reply-To: mid1@foobar.org -Subject: test - -test - - -expect TRUSTED_REPLY - -References: -Subject: test - -test - - -expect - -In-Reply-To: mid1@foobar.net -References: -Subject: test - -test - diff --git a/tests/resources/smtp/antispam/reputation.test b/tests/resources/smtp/antispam/reputation.test deleted file mode 100644 index 90fa57fa..00000000 --- a/tests/resources/smtp/antispam/reputation.test +++ /dev/null @@ -1,37 +0,0 @@ -remote_ip 10.0.0.1 -score 1.0 -final_score 1.0 -expect - -From: user@domain.org - -Test - - -remote_ip 10.0.0.1 -score 2.0 -final_score 1.45 -expect - -From: user@domain.org - -Test - - -remote_ip 10.0.0.1 -score 3.0 -final_score 2.1772727272727272 -expect - -From: user@domain.org - -Test - -remote_ip 10.0.0.1 -score -5.0 -final_score -1.5954545454545457 -expect - -From: user@domain.org - -Test diff --git a/tests/resources/smtp/antispam/spamtrap.test b/tests/resources/smtp/antispam/spamtrap.test index d607b631..b6f3c26a 100644 --- a/tests/resources/smtp/antispam/spamtrap.test +++ b/tests/resources/smtp/antispam/spamtrap.test @@ -6,93 +6,3 @@ Subject: save up to NUMBER on life insurance why spend more than you have to life quote savings ensuring your family s financial security is very important life quote savings makes buying life insurance simple and affordable we provide free access to the very best companies and the lowest rates life quote savings is fast easy and saves you money let us help you get started with the best values in the country on new coverage you can save hundreds or even thousands of dollars by requesting a free quote from lifequote savings our service will take you less than NUMBER minutes to complete shop and compare save up to NUMBER on all types of life insurance hyperlink click here for your free quote protecting your family is the best investment you ll ever make if you are in receipt of this email in error and or wish to be removed from our list hyperlink please click here and type remove if you reside in any state which prohibits e mail solicitations for insurance please disregard this email - -envelope_from spammer@domain.com -envelope_to spamtrap@foobar.org -expect SPAM_TRAP - -Subject: a powerhouse gifting program - -you don t want to miss get in with the founders the major players are on this one for once be where the players are this is your private invitation experts are calling this the fastest way to huge cash flow ever conceived leverage NUMBER NUMBER into NUMBER NUMBER over and over again the question here is you either want to be wealthy or you don t which one are you i am tossing you a financial lifeline and for your sake i hope you grab onto it and hold on tight for the ride of your life testimonials hear what average people are doing their first few days we ve received NUMBER NUMBER in NUMBER day and we are doing that over and over again q s in al i m a single mother in fl and i ve received NUMBER NUMBER in the last NUMBER days d s in fl i was not sure about this when i sent off my NUMBER NUMBER pledge but i got back NUMBER NUMBER the very next day l l in ky i didn t have the money so i found myself a partner to work this with we have received NUMBER NUMBER over the last NUMBER days i think i made the right decision don t you k c in fl i pick up NUMBER NUMBER my first day and i they gave me free leads and all the training you can too j w in ca announcing we will close your sales for you and help you get a fax blast immediately upon your entry you make the money free leads training don t wait call now fax back to NUMBER NUMBER NUMBER NUMBER or call NUMBER NUMBER NUMBER NUMBER name__________________________________phone___________________________________________ fax_____________________________________email____________________________________________ best time to call_________________________time zone________________________________________ this message is sent in compliance of the new e mail bill per section NUMBER paragraph a NUMBER c of s NUMBER further transmissions by the sender of this email may be stopped at no cost to you by sending a reply to this email address with the word remove in the subject line errors omissions and exceptions excluded this is not spam i have compiled this list from our replicate database relative to seattle marketing group the gigt or turbo team for the sole purpose of these communications your continued inclusion is only by your gracious permission if you wish to not receive this mail from me please send an email to tesrewinter URL with remove in the subject and you will be deleted immediately - - -envelope_from spammer@domain.com -envelope_to spamtrap@foobar.org -expect SPAM_TRAP - -Subject: help wanted - -we are a NUMBER year old fortune NUMBER company that is growing at a tremendous rate we are looking for individuals who want to work from home this is an opportunity to make an excellent income no experience is required we will train you so if you are looking to be employed from home with a career that has vast opportunities then go URL we are looking for energetic and self motivated people if that is you than click on the link and fill out the form and one of our employement specialist will contact you to be removed from our link simple go to URL - - -envelope_from spammer@domain.com -envelope_to spamtrap@foobar.org -expect SPAM_TRAP - -Subject: tired of the bull out there - -want to stop losing money want a real money maker receive NUMBER NUMBER NUMBER NUMBER today experts are calling this the fastest way to huge cash flow ever conceived a powerhouse gifting program you don t want to miss we work as a team this is your private invitation get in with the founders this is where the big boys play the major players are on this one for once be where the players are this is a system that will drive NUMBER NUMBER s to your doorstep in a short period of time leverage NUMBER NUMBER into NUMBER NUMBER over and over again the question here is you either want to be wealthy or you don t which one are you i am tossing you a financial lifeline and for your sake i hope you grab onto it and hold on tight for the ride of your life testimonials hear what average people are doing their first few days we ve received NUMBER NUMBER in NUMBER day and we are doing that over and over again q s in al i m a single mother in fl and i ve received NUMBER NUMBER in the last NUMBER days d s in fl i was not sure about this when i sent off my NUMBER NUMBER pledge but i got back NUMBER NUMBER the very next day l l in ky i didn t have the money so i found myself a partner to work this with we have received NUMBER NUMBER over the last NUMBER days i think i made the right decision don t you k c in fl i pick up NUMBER NUMBER my first day and i they gave me free leads and all the training you can too j w in ca this will be the most important call you make this year free leads training announcing we will close your sales for you and help you get a fax blast immediately upon your entry you make the money free leads training don t wait call now NUMBER NUMBER NUMBER NUMBER print and fax to NUMBER NUMBER NUMBER NUMBER or send an email requesting more information to successleads URL please include your name and telephone number receive NUMBER NUMBER free leads just for responding a NUMBER NUMBER value name___________________________________ phone___________________________________ fax_____________________________________ email___________________________________ this message is sent in compliance of the new e mail bill per section NUMBER paragraph a NUMBER c of s NUMBER further transmissions by the sender of this email may be stopped at no cost to you by sending a reply to this email address with the word remove in the subject line errors omissions and exceptions excluded this is not spam i have compiled this list from our replicate database relative to seattle marketing group the gigt or turbo team for the sole purpose of these communications your continued inclusion is only by your gracious permission if you wish to not receive this mail from me please send an email to tesrewinter URL with remove in the subject and you will be deleted immediately - - -envelope_from spammer@domain.com -envelope_to spamtrap@foobar.org -expect SPAM_TRAP - -Subject: cellular phone accessories - -all at below wholesale prices http NUMBER NUMBER NUMBER NUMBER NUMBER sites merchant sales hands free ear buds NUMBER NUMBER phone holsters NUMBER NUMBER booster antennas only NUMBER NUMBER phone cases NUMBER NUMBER car chargers NUMBER NUMBER face plates as low as NUMBER NUMBER lithium ion batteries as low as NUMBER NUMBER http NUMBER NUMBER NUMBER NUMBER NUMBER sites merchant sales click below for accessories on all nokia motorola lg nextel samsung qualcomm ericsson audiovox phones at below wholesale prices http NUMBER NUMBER NUMBER NUMBER NUMBER sites merchant sales if you need assistance please call us NUMBER NUMBER NUMBER to be removed from future mailings please send your remove request to remove me now NUMBER URL thank you and have a super day - - -envelope_from spammer@domain.com -envelope_to spamtrap@foobar.org -expect SPAM_TRAP - -Subject: conferencing made easy - -only NUMBER cents per minute including long distance no setup fees no contracts or monthly fees call anytime from anywhere to anywhere connects up to NUMBER participants simplicity in set up and administration operator help available NUMBER NUMBER the highest quality service for the lowest rate in the industry fill out the form below to find out how you can lower your phone bill every month required input field name web address company name state business phone home phone email address type of business to be removed from our distribution lists please hyperlink click here - - -envelope_from spammer@domain.com -envelope_to spamtrap@foobar.org -expect SPAM_TRAP - -Subject: dear friend - -i am mrs sese seko widow of late president mobutu sese seko of zaire now known as democratic republic of congo drc i am moved to write you this letter this was in confidence considering my presentcircumstance and situation i escaped along with my husband and two of our sons george kongolo and basher out of democratic republic of congo drc to abidjan cote d ivoire where my family and i settled while we later moved to settled in morroco where my husband later died of cancer disease however due to this situation we decided to changed most of my husband s billions of dollars deposited in swiss bank and other countries into other forms of money coded for safe purpose because the new head of state of dr mr laurent kabila has made arrangement with the swiss government and other european countries to freeze all my late husband s treasures deposited in some european countries hence my children and i decided laying low in africa to study the situation till when things gets better like now that president kabila is dead and the son taking over joseph kabila one of my late husband s chateaux in southern france was confiscated by the french government and as such i had to change my identity so that my investment will not be traced and confiscated i have deposited the sum eighteen million united state dollars us NUMBER NUMBER NUMBER NUMBER with a security company for safekeeping the funds are security coded to prevent them from knowing the content what i want you to do is to indicate your interest that you will assist us by receiving the money on our behalf acknowledge this message so that i can introduce you to my son kongolo who has the out modalities for the claim of the said funds i want you to assist in investing this money but i will not want my identity revealed i will also want to buy properties and stock in multi national companies and to engage in other safe and non speculative investments may i at this point emphasise the high level of confidentiality which this business demands and hope you will not betray the trust and confidence which i repose in you in conclusion if you want to assist us my son shall put you in the picture of the business tell you where the funds are currently being maintained and also discuss other modalities including remunerationfor your services for this reason kindly furnish us your contact information that is your personal telephone and fax number for confidential URL regards mrs m sese seko - - -envelope_from spammer@domain.com -envelope_to spamtrap@foobar.org -expect SPAM_TRAP - -Subject: lowest rates available for term life insurance - -take a moment and fill out our online form to see the low rate you qualify for save up to NUMBER from regular rates smokers accepted URL representing quality nationwide carriers act now to easily remove your address from the list go to URL please allow NUMBER NUMBER hours for removal - - -envelope_from spammer@domain.com -envelope_to spamtrap@foobar.org -expect SPAM_TRAP - -Subject: central bank of nigeria foreign remittance - -dept tinubu square lagos nigeria email smith_j URL NUMBERth of august NUMBER attn president ceo strictly private business proposal i am mr johnson s abu the bills and exchange director at the foreignremittance department of the central bank of nigeria i am writingyou this letter to ask for your support and cooperation to carrying thisbusiness opportunity in my department we discovered abandoned the sumof us NUMBER NUMBER NUMBER NUMBER thirty seven million four hundred thousand unitedstates dollars in an account that belong to one of our foreign customers an american late engr john creek junior an oil merchant with the federal government of nigeria who died along with his entire family of a wifeand two children in kenya airbus aNUMBER NUMBER flight kqNUMBER in novemberNUMBER since we heard of his death we have been expecting his next of kin tocome over and put claims for his money as the heir because we cannotrelease the fund from his account unless someone applies for claims asthe next of kin to the deceased as indicated in our banking guidelines unfortunately neither their family member nor distant relative hasappeared to claim the said fund upon this discovery i and other officialsin my department have agreed to make business with you release the totalamount into your account as the heir of the fund since no one came forit or discovered either maintained account with our bank other wisethe fund will be returned to the bank treasury as unclaimed fund we have agreed that our ratio of sharing will be as stated thus NUMBER for you as foreign partner and NUMBER for us the officials in my department upon the successful completion of this transfer my colleague and i willcome to your country and mind our share it is from our NUMBER we intendto import computer accessories into my country as way of recycling thefund to commence this transaction we require you to immediately indicateyour interest by calling me or sending me a fax immediately on the abovetelefax and enclose your private contact telephone fax full nameand address and your designated banking co ordinates to enable us fileletter of claim to the appropriate department for necessary approvalsbefore the transfer can be made note also this transaction must be kept strictly confidential becauseof its nature nb please remember to give me your phone and fax no mr johnson smith abu irish linux users group ilug URL URL for un subscription information list maintainer listmaster URL - - -envelope_from spammer@domain.com -envelope_to other@foobar.org -envelope_to spamtrap@foobar.org -expect SPAM_TRAP - -Subject: dear stuart - -are you tired of searching for love in all the wrong places find love now at URL URL browse through thousands of personals in your area join for free URL search e mail chat use URL to meet cool guys and hot girls go NUMBER on NUMBER or use our private chat rooms click on the link to get started URL find love now you have received this email because you have registerd with emailrewardz or subscribed through one of our marketing partners if you have received this message in error or wish to stop receiving these great offers please click the remove link above to unsubscribe from these mailings please click here URL - - -envelope_from spammer@domain.com -envelope_to other@foobar.org -expect - -Subject: test - -test diff --git a/tests/src/cluster/stress.rs b/tests/src/cluster/stress.rs index 3dde1dea..060b4444 100644 --- a/tests/src/cluster/stress.rs +++ b/tests/src/cluster/stress.rs @@ -19,8 +19,10 @@ use jmap_client::{ }; use std::{str::FromStr, sync::Arc, time::Duration}; use store::{ + ValueKey, rand::{self, Rng}, roaring::RoaringBitmap, + write::{AlignedBytes, Archive}, }; use types::{collection::Collection, id::Id}; @@ -227,7 +229,12 @@ async fn email_tests(server: Server, client: Arc) { for email_id in &email_ids_in_mailbox { if let Some(mailbox_tags) = server - .archive(TEST_USER_ID, Collection::Email, email_id) + .store() + .get_value::>(ValueKey::archive( + TEST_USER_ID, + Collection::Email, + email_id, + )) .await .unwrap() { diff --git a/tests/src/imap/bayes.rs b/tests/src/imap/bayes.rs index d16177c8..cc04de6b 100644 --- a/tests/src/imap/bayes.rs +++ b/tests/src/imap/bayes.rs @@ -10,21 +10,21 @@ use crate::{ jmap::{mail::delivery::SmtpConnection, wait_for_index}, smtp::session::VerifyResponse, }; -use common::KV_BAYES_MODEL_USER; use directory::backend::internal::manage::ManageDirectory; use imap_proto::ResponseType; -use nlp::bayes::{TokenHash, Weights}; pub async fn test(handle: &IMAPTest) { - println!("Running Bayes tests..."); + println!("Running Spam classifier tests..."); let mut imap = ImapConnection::connect(b"_x ").await; imap.assert_read(Type::Untagged, ResponseType::Ok).await; imap.send("AUTHENTICATE PLAIN AGJheWVzQGV4YW1wbGUuY29tAHNlY3JldA==") .await; imap.assert_read(Type::Tagged, ResponseType::Ok).await; + let todo = "fix + test jmap"; + // Make sure the bayes classifier is empty - let account_id = handle + /*let account_id = handle .server .store() .get_principal_id("bayes@example.com") @@ -93,7 +93,7 @@ pub async fn test(handle: &IMAPTest) { imap.send_ok("MOVE 10 INBOX").await; let w = handle.spam_weights(account_id).await; assert_eq!(w.ham, 11); - assert_eq!(w.spam, 10); + assert_eq!(w.spam, 10);*/ } impl ImapConnection { @@ -113,19 +113,6 @@ impl ImapConnection { } } -impl IMAPTest { - async fn spam_weights(&self, account_id: u32) -> Weights { - wait_for_index(&self.server).await; - - self.server - .in_memory_store() - .counter_get(TokenHash::default().serialize_account(KV_BAYES_MODEL_USER, account_id)) - .await - .map(Weights::from) - .unwrap() - } -} - const SPAM: [&str; 10] = [ concat!( "Subject: save up to NUMBER on life insurance\r\n\r\n wh", diff --git a/tests/src/jmap/auth/permissions.rs b/tests/src/jmap/auth/permissions.rs index a390fd18..dfbf3a67 100644 --- a/tests/src/jmap/auth/permissions.rs +++ b/tests/src/jmap/auth/permissions.rs @@ -14,7 +14,7 @@ use directory::{ Permission, Type, backend::internal::{PrincipalField, PrincipalSet, PrincipalUpdate, PrincipalValue}, }; -use email::message::delivery::{IngestMessage, LocalDeliveryStatus, MailDelivery}; +use email::message::delivery::{IngestMessage, IngestRecipient, LocalDeliveryStatus, MailDelivery}; use std::sync::Arc; pub async fn test(params: &JMAPTest) { @@ -619,7 +619,10 @@ pub async fn test(params: &JMAPTest) { .deliver_message(IngestMessage { sender_address: "bill@foobar.org".to_string(), sender_authenticated: true, - recipients: vec!["john@foobar.org".to_string()], + recipients: vec![IngestRecipient { + address: "john@foobar.org".to_string(), + is_spam: false + }], message_blob: message_blob.clone(), message_size: TEST_MESSAGE.len() as u64, session_id: 0, @@ -658,7 +661,10 @@ pub async fn test(params: &JMAPTest) { .deliver_message(IngestMessage { sender_address: "bill@foobar.org".to_string(), sender_authenticated: true, - recipients: vec!["john@foobar.org".to_string()], + recipients: vec![IngestRecipient { + address: "john@foobar.org".to_string(), + is_spam: false + }], message_blob: message_blob.clone(), message_size: TEST_MESSAGE.len() as u64, session_id: 0, @@ -684,7 +690,10 @@ pub async fn test(params: &JMAPTest) { .deliver_message(IngestMessage { sender_address: "bill@foobar.org".to_string(), sender_authenticated: true, - recipients: vec!["john@foobar.org".to_string()], + recipients: vec![IngestRecipient { + address: "john@foobar.org".to_string(), + is_spam: false + }], message_blob, message_size: TEST_MESSAGE.len() as u64, session_id: 0, diff --git a/tests/src/jmap/mail/crypto.rs b/tests/src/jmap/mail/crypto.rs index ff9af836..73703856 100644 --- a/tests/src/jmap/mail/crypto.rs +++ b/tests/src/jmap/mail/crypto.rs @@ -179,13 +179,14 @@ pub async fn import_certs_and_encrypt() { if method == EncryptionMethod::PGP && certs.len() == 2 { // PGP library won't encrypt using EC - certs.pop(); + let mut certs_ = certs.to_vec(); + certs_.pop(); + certs = certs_.into(); } let mut params = EncryptionParams { - method, - algo: Algorithm::Aes128, certs, + flags: method.flags(), }; for algo in [Algorithm::Aes128, Algorithm::Aes256] { @@ -193,7 +194,7 @@ pub async fn import_certs_and_encrypt() { .parse(b"Subject: test\r\ntest\r\n") .unwrap(); assert!(!message.is_encrypted()); - params.algo = algo; + params.flags = algo.flags() | method.flags(); let arch = Archive::deserialize_owned(Archiver::new(params.clone()).serialize().unwrap()) .unwrap(); diff --git a/tests/src/jmap/mail/delivery.rs b/tests/src/jmap/mail/delivery.rs index 0ec756cd..31b0b12c 100644 --- a/tests/src/jmap/mail/delivery.rs +++ b/tests/src/jmap/mail/delivery.rs @@ -16,6 +16,10 @@ use email::{ use groupware::DavResourceName; use jmap::blob::download::BlobDownload; use std::{sync::Arc, time::Duration}; +use store::{ + ValueKey, + write::{AlignedBytes, Archive}, +}; use tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Lines, ReadHalf, WriteHalf}, net::TcpStream, @@ -271,12 +275,13 @@ END:VCARD for document_id in cache.emails.items.iter().map(|e| e.document_id) { let archive = server - .archive_by_property( + .store() + .get_value::>(ValueKey::property( account_id, Collection::Email, document_id, - EmailField::Metadata.into(), - ) + EmailField::Metadata, + )) .await .unwrap() .unwrap(); diff --git a/tests/src/jmap/mail/query_changes.rs b/tests/src/jmap/mail/query_changes.rs index 0d0e9f4b..795ca1ac 100644 --- a/tests/src/jmap/mail/query_changes.rs +++ b/tests/src/jmap/mail/query_changes.rs @@ -18,8 +18,9 @@ use jmap_client::{ use jmap_proto::types::state::State; use std::str::FromStr; use store::{ + ValueKey, ahash::{AHashMap, AHashSet}, - write::BatchBuilder, + write::{AlignedBytes, Archive, BatchBuilder}, }; use types::{ collection::{Collection, SyncCollection}, @@ -137,11 +138,12 @@ pub async fn test(params: &mut JMAPTest) { //let new_thread_id = store::rand::random::(); let old_message_ = server - .archive( + .store() + .get_value::>(ValueKey::archive( account.id().document_id(), Collection::Email, id.document_id(), - ) + )) .await .unwrap() .unwrap(); diff --git a/tests/src/jmap/mail/thread_merge.rs b/tests/src/jmap/mail/thread_merge.rs index eafb6071..2a582be0 100644 --- a/tests/src/jmap/mail/thread_merge.rs +++ b/tests/src/jmap/mail/thread_merge.rs @@ -238,9 +238,8 @@ async fn test_multi_thread(params: &mut JMAPTest) { source: IngestSource::Smtp { deliver_to: "test@domain.org", is_sender_authenticated: true, + is_spam: false, }, - spam_classify: false, - spam_train: false, session_id: 0, }) .await diff --git a/tests/src/jmap/mod.rs b/tests/src/jmap/mod.rs index 449e1d57..a8fbffe4 100644 --- a/tests/src/jmap/mod.rs +++ b/tests/src/jmap/mod.rs @@ -22,7 +22,7 @@ use base64::{ engine::general_purpose::{self, STANDARD}, }; use common::{ - Caches, Core, Data, Inner, KV_BAYES_MODEL_GLOBAL, Server, + Caches, Core, Data, Inner, Server, config::{ server::{Listeners, ServerProtocol}, telemetry::Telemetry, @@ -260,13 +260,6 @@ pub async fn assert_is_empty(server: &Server) { // Wait for pending index tasks wait_for_index(server).await; - // Delete bayes model - server - .in_memory_store() - .key_delete_prefix(&[KV_BAYES_MODEL_GLOBAL]) - .await - .unwrap(); - // Assert is empty store_assert_is_empty(server.store(), server.core.storage.blob.clone(), false).await; search_store_destroy(server.search_store()).await; diff --git a/tests/src/smtp/inbound/antispam.rs b/tests/src/smtp/inbound/antispam.rs index fedbe1a0..d7087a76 100644 --- a/tests/src/smtp/inbound/antispam.rs +++ b/tests/src/smtp/inbound/antispam.rs @@ -11,7 +11,7 @@ use crate::{ }; use ahash::{AHashMap, AHashSet}; use common::{ - Core, + Core, Server, auth::AccessToken, config::spamfilter::SpamFilterAction, enterprise::{ @@ -22,7 +22,7 @@ use common::{ }, }, }; -use compact_str::{CompactString, ToCompactString}; +use email::message::ingest::EmailIngest; use http_proto::{JsonResponse, ToHttpResponse}; use hyper::Method; use mail_auth::{ @@ -33,19 +33,23 @@ use mail_parser::MessageParser; use smtp::core::{Session, SessionAddress}; use smtp_proto::{MAIL_BODY_8BITMIME, MAIL_SMTPUTF8}; use spam_filter::{ + SpamFilterInput, analysis::{ - bayes::SpamFilterAnalyzeBayes, date::SpamFilterAnalyzeDate, dmarc::SpamFilterAnalyzeDmarc, - domain::SpamFilterAnalyzeDomain, ehlo::SpamFilterAnalyzeEhlo, from::SpamFilterAnalyzeFrom, + classifier::SpamFilterAnalyzeClassify, date::SpamFilterAnalyzeDate, + dmarc::SpamFilterAnalyzeDmarc, domain::SpamFilterAnalyzeDomain, + ehlo::SpamFilterAnalyzeEhlo, from::SpamFilterAnalyzeFrom, headers::SpamFilterAnalyzeHeaders, html::SpamFilterAnalyzeHtml, init::SpamFilterInit, ip::SpamFilterAnalyzeIp, llm::SpamFilterAnalyzeLlm, messageid::SpamFilterAnalyzeMid, mime::SpamFilterAnalyzeMime, pyzor::SpamFilterAnalyzePyzor, received::SpamFilterAnalyzeReceived, recipient::SpamFilterAnalyzeRecipient, - replyto::SpamFilterAnalyzeReplyTo, reputation::SpamFilterAnalyzeReputation, - rules::SpamFilterAnalyzeRules, score::SpamFilterAnalyzeScore, - subject::SpamFilterAnalyzeSubject, trusted_reply::SpamFilterAnalyzeTrustedReply, + replyto::SpamFilterAnalyzeReplyTo, rules::SpamFilterAnalyzeRules, + score::SpamFilterAnalyzeScore, subject::SpamFilterAnalyzeSubject, url::SpamFilterAnalyzeUrl, }, - modules::html::{HtmlToken, html_to_tokens}, + modules::{ + classifier::{SpamClassifier, Token}, + html::{HtmlToken, html_to_tokens}, + }, }; use std::{ fs, @@ -53,7 +57,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use store::Stores; +use store::{Stores, write::BatchBuilder}; use utils::config::Config; const CONFIG: &str = r#" @@ -334,11 +338,10 @@ async fn antispam() { "bounce", "dmarc", "rbl", - "replies_out", - "replies_in", "spamtrap", - "bayes_classify", - "reputation", + //"classifier_html", + //"classifier_features", + "classifier", "pyzor", "llm", ] { @@ -348,8 +351,46 @@ async fn antispam() { { continue; } + println!("===== {test_name} ====="); let contents = fs::read_to_string(base_path.join(format!("{test_name}.test"))).unwrap(); + + match test_name { + "classifier_html" => { + html_tokens(contents); + continue; + } + "classifier_features" => { + classifier_features(&server, contents).await; + continue; + } + "classifier" => { + let mut batch = BatchBuilder::new(); + batch.with_account_id(u32::MAX); + for class in ["spam", "ham"] { + let contents = + fs::read_to_string(base_path.join(format!("classifier.{class}"))).unwrap(); + for sample in contents.split("") { + let sample = sample.trim_start(); + if sample.is_empty() { + continue; + } + + let (hash, blob_hold) = server + .put_temporary_blob(u32::MAX, sample.as_bytes(), 60) + .await + .unwrap(); + server.add_spam_sample(&mut batch, hash, class == "spam", false, 0); + batch.clear(blob_hold); + } + } + assert!(!batch.is_empty()); + server.store().write(batch.build_all()).await.unwrap(); + server.spam_train(false).await.unwrap(); + } + _ => {} + } + let mut lines = contents.lines(); let mut has_more = true; @@ -364,10 +405,8 @@ async fn antispam() { let mut dkim_signatures = vec![]; let mut dmarc_result = None; let mut dmarc_policy = None; - let mut expected_tags: AHashSet = AHashSet::new(); + let mut expected_tags: AHashSet = AHashSet::new(); let mut expect_headers = String::new(); - let mut score_set = 0.0; - let mut score_final = 0.0; let mut body_params = 0; let mut is_tls = false; @@ -458,11 +497,8 @@ async fn antispam() { dmarc_policy = Policy::from_str(value).into(); } "expect" => { - expected_tags.extend( - value - .split_ascii_whitespace() - .map(|v| v.to_uppercase().into()), - ); + expected_tags + .extend(value.split_ascii_whitespace().map(|v| v.to_uppercase())); } "expect_header" => { let value = value.trim(); @@ -473,12 +509,6 @@ async fn antispam() { expect_headers.push_str(value); } } - "score" => { - score_set = value.parse::().unwrap(); - } - "final_score" => { - score_final = value.parse::().unwrap(); - } "param.smtputf8" => { body_params |= MAIL_SMTPUTF8; } @@ -541,10 +571,10 @@ async fn antispam() { ) .await { - SpamFilterAction::Allow(header) => { + SpamFilterAction::Allow(score) => { let mut last_ch = 'x'; - let mut result = String::with_capacity(header.len()); - for ch in header.chars() { + let mut result = String::with_capacity(score.headers.len()); + for ch in score.headers.chars() { if !ch.is_whitespace() { if last_ch.is_whitespace() { result.push(' '); @@ -650,25 +680,21 @@ async fn antispam() { server.spam_filter_analyze_ip(&mut spam_ctx).await; server.spam_filter_analyze_domain(&mut spam_ctx).await; } - "replies_out" => { - server.spam_filter_analyze_reply_out(&mut spam_ctx).await; - } - "replies_in" => { - server.spam_filter_analyze_reply_in(&mut spam_ctx).await; - } "spamtrap" => { server.spam_filter_analyze_spam_trap(&mut spam_ctx).await; server.spam_filter_finalize(&mut spam_ctx).await; } - "bayes_classify" => { - server - .spam_filter_analyze_bayes_classify(&mut spam_ctx) - .await; - } - "reputation" => { - spam_ctx.result.score = score_set; - server.spam_filter_analyze_reputation(&mut spam_ctx).await; - assert_eq!(spam_ctx.result.score, score_final); + "classifier" => { + server.spam_filter_analyze_classify(&mut spam_ctx).await; + match server.spam_filter_finalize(&mut spam_ctx).await { + SpamFilterAction::Allow(r) => spam_ctx.result.tags.extend( + r.headers + .split_ascii_whitespace() + .filter(|t| t.starts_with("PROB_")) + .map(|t| t.to_string()), + ), + _ => unreachable!(), + } } "pyzor" => { server.spam_filter_analyze_pyzor(&mut spam_ctx).await; @@ -701,6 +727,75 @@ async fn antispam() { } } +async fn classifier_features(server: &Server, contents: String) { + let mut num_tests = 0; + + for test in contents.split("") { + let test = test.trim(); + if test.is_empty() { + continue; + } + + let (input, expected) = test.split_once("").unwrap(); + let input = input.trim(); + let expected = expected.trim(); + + // Build features + let message = MessageParser::new().parse(input).unwrap_or_default(); + let mut ctx = + server.spam_filter_init(SpamFilterInput::from_message(&message, 0).train_mode()); + server.spam_filter_analyze_domain(&mut ctx).await; + server.spam_filter_analyze_url(&mut ctx).await; + let mut tokens = server + .spam_build_tokens(&ctx) + .await + .0 + .into_keys() + .collect::>(); + tokens.sort(); + + assert!(!tokens.is_empty(), "No tokens parsed for input: {}", input); + let expected_tokens: Vec> = serde_json::from_str(expected).unwrap(); + + if tokens != expected_tokens { + eprintln!("Input: {}", input); + eprintln!("Expected Tokens: {}", expected); + eprintln!( + "Parsed Tokens: {}", + serde_json::to_string_pretty(&tokens).unwrap() + ); + panic!("Tokens do not match"); + } + num_tests += 1; + } + + assert_eq!(num_tests, 11, "Expected number of tests to run"); +} + +fn html_tokens(contents: String) { + let mut num_tests = 0; + + for test in contents.split("") { + let test = test.trim(); + if test.is_empty() { + continue; + } + + let (input, expected) = test.split_once("").unwrap(); + let input = input.trim(); + let expected = expected.trim(); + + let tokens = html_to_tokens(input); + assert!(!tokens.is_empty(), "No tokens parsed for input: {}", input); + let expected_tokens: Vec = serde_json::from_str(expected).unwrap(); + + assert_eq!(tokens, expected_tokens, "Input: {}", input); + num_tests += 1; + } + + assert_eq!(num_tests, 12, "Expected number of tests to run"); +} + trait ParseConfigValue: Sized { fn from_str(value: &str) -> Self; } @@ -770,465 +865,3 @@ impl ParseConfigValue for Policy { } } } - -#[test] -fn html_tokens() { - for (input, expected) in [ - ( - "hello
world
", - vec![ - HtmlToken::StartTag { - name: 1819112552, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::Text { - text: "hello".to_compact_string(), - }, - HtmlToken::StartTag { - name: 29282, - attributes: vec![], - is_self_closing: true, - }, - HtmlToken::Text { - text: "world".to_compact_string(), - }, - HtmlToken::StartTag { - name: 29282, - attributes: vec![], - is_self_closing: true, - }, - HtmlToken::EndTag { name: 1819112552 }, - ], - ), - ( - "using <>
", - vec![ - HtmlToken::StartTag { - name: 1819112552, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::Text { - text: "using <>".to_compact_string(), - }, - HtmlToken::StartTag { - name: 29282, - attributes: vec![], - is_self_closing: true, - }, - HtmlToken::EndTag { name: 1819112552 }, - ], - ), - ( - "test tag
", - vec![ - HtmlToken::Text { - text: "test".to_compact_string(), - }, - HtmlToken::StartTag { - name: 7630702, - attributes: vec![(29282, None)], - is_self_closing: true, - }, - HtmlToken::Text { - text: " tag".to_compact_string(), - }, - HtmlToken::StartTag { - name: 29282, - attributes: vec![], - is_self_closing: true, - }, - ], - ), - ( - "<>< >>hello world< br \n />", - vec![ - HtmlToken::StartTag { - name: 6775156, - attributes: vec![], - is_self_closing: true, - }, - HtmlToken::Text { - text: ">hello world".to_compact_string(), - }, - HtmlToken::StartTag { - name: 29282, - attributes: vec![], - is_self_closing: true, - }, - ], - ), - ( - concat!( - "ignore headxyz

<body><", - "/h1>" - ), - vec![ - HtmlToken::StartTag { - name: 1684104552, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::StartTag { - name: 435611265396, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::Text { - text: "ignore head".to_compact_string(), - }, - HtmlToken::EndTag { name: 435611265396 }, - HtmlToken::StartTag { - name: 7630702, - attributes: vec![(1684104552, None)], - is_self_closing: false, - }, - HtmlToken::Text { - text: "xyz".to_compact_string(), - }, - HtmlToken::EndTag { name: 7630702 }, - HtmlToken::EndTag { name: 1684104552 }, - HtmlToken::StartTag { - name: 12648, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::Text { - text: "".to_compact_string(), - }, - HtmlToken::EndTag { name: 12648 }, - ], - ), - ( - concat!( - "

what is ♥?

ß&", - "Abreve;Βγ don't hurt me.", - "

" - ), - vec![ - HtmlToken::StartTag { - name: 112, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::Text { - text: "what is ♥?".to_compact_string(), - }, - HtmlToken::EndTag { name: 112 }, - HtmlToken::StartTag { - name: 112, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::Text { - text: "ßĂΒγ don't hurt me.".to_compact_string(), - }, - HtmlToken::EndTag { name: 112 }, - ], - ), - ( - concat!( - "this is the actual text" - ), - vec![ - HtmlToken::Comment { - text: concat!( - "!--[if mso]> < < < < ignore > -> here --".to_compact_string(), - }, - HtmlToken::Text { - text: " the actual".to_compact_string(), - }, - HtmlToken::Comment { - text: "!--".to_compact_string(), - }, - HtmlToken::Text { - text: " text".to_compact_string(), - }, - ], - ), - ( - concat!( - " < p > hello < / p > < p > world < / ", - "p > !!! < br > " - ), - vec![ - HtmlToken::StartTag { - name: 112, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::Text { - text: "hello".to_compact_string(), - }, - HtmlToken::EndTag { name: 112 }, - HtmlToken::StartTag { - name: 112, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::Text { - text: " world".to_compact_string(), - }, - HtmlToken::EndTag { name: 112 }, - HtmlToken::Text { - text: " !!!".to_compact_string(), - }, - HtmlToken::StartTag { - name: 29282, - attributes: vec![], - is_self_closing: false, - }, - ], - ), - ( - concat!("

please unsubscribe here.

"), - vec![ - HtmlToken::StartTag { - name: 112, - attributes: vec![], - is_self_closing: false, - }, - HtmlToken::Text { - text: "please unsubscribe".to_compact_string(), - }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("#".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: " here".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::Text { - text: ".".to_compact_string(), - }, - HtmlToken::EndTag { name: 112 }, - ], - ), - ( - concat!( - "texttexttexttext", - "< a href = \"e\" >texttext< anchor href = \"x\">t", - "ext" - ), - vec![ - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("a".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("b".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("c".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("d".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("e".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(125779835187816, Some("ignore".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 125822818283105, - attributes: vec![(1717924456, Some("x".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - ], - ), - ( - concat!( - "texttexttexttext< a ", - "href = e >textt", - "exttext" - ), - vec![ - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("a".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("b".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("c".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("d".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("e".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(125779835187816, Some("ignore".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 125822818283105, - attributes: vec![(1717924456, Some("x".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - ], - ), - ( - concat!( - "text< a href = test igno", - "re>text< a href = fudge href ignor", - "e>text a href = \"unkn", - "own\" " - ), - vec![ - HtmlToken::Comment { - text: "!-- texttext--text--" - .to_compact_string(), - }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("hello world".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![ - (1717924456, Some("test".to_compact_string())), - (111542170183529, None), - ], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![ - (1717924456, Some("fudge".to_compact_string())), - (1717924456, None), - (111542170183529, None), - ], - is_self_closing: false, - }, - HtmlToken::Text { - text: "text".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - HtmlToken::StartTag { - name: 97, - attributes: vec![(1717924456, Some("foobar".to_compact_string()))], - is_self_closing: false, - }, - HtmlToken::Text { - text: "a href = \"unknown\"".to_compact_string(), - }, - HtmlToken::EndTag { name: 97 }, - ], - ), - ] { - assert_eq!(expected, html_to_tokens(input), "failed for {input:?}"); - } -} diff --git a/tests/src/smtp/inbound/scripts.rs b/tests/src/smtp/inbound/scripts.rs index 641bb486..1cd71456 100644 --- a/tests/src/smtp/inbound/scripts.rs +++ b/tests/src/smtp/inbound/scripts.rs @@ -243,7 +243,7 @@ async fn sieve_scripts() { assert_eq!(messages.len(), 2); let mut messages = messages.into_iter(); let notification = messages.next().unwrap(); - assert_eq!(notification.message.return_path, ""); + assert_eq!(notification.message.return_path.as_ref(), ""); assert_eq!(notification.message.recipients.len(), 2); assert_eq!( notification.message.recipients.first().unwrap().address(), @@ -323,7 +323,7 @@ async fn sieve_scripts() { .await; let redirect = qr.expect_message().await; - assert_eq!(redirect.message.return_path, ""); + assert_eq!(redirect.message.return_path.as_ref(), ""); assert_eq!(redirect.message.recipients.len(), 1); assert_eq!( redirect.message.recipients.first().unwrap().address(), @@ -351,7 +351,7 @@ async fn sieve_scripts() { .await; let redirect = qr.expect_message().await; - assert_eq!(redirect.message.return_path, ""); + assert_eq!(redirect.message.return_path.as_ref(), ""); assert_eq!(redirect.message.recipients.len(), 1); assert_eq!( redirect.message.recipients.first().unwrap().address(), diff --git a/tests/src/smtp/lookup/utils.rs b/tests/src/smtp/lookup/utils.rs index cdfa1aa0..a38286a5 100644 --- a/tests/src/smtp/lookup/utils.rs +++ b/tests/src/smtp/lookup/utils.rs @@ -146,16 +146,16 @@ async fn strategies() { blob_hash: Default::default(), received_from_ip: "1.2.3.4".parse().unwrap(), received_via_port: 7911, - return_path: "test@example.com".to_string(), + return_path: "test@example.com".into(), recipients: vec![Recipient { - address: "recipient@foobar.com".to_string(), + address: "recipient@foobar.com".into(), retry: Schedule::now(), notify: Schedule::now(), expires: QueueExpiry::Ttl(3600), queue: QueueName::new("test").unwrap(), status: Status::TemporaryFailure(ErrorDetails { - entity: "test.example.com".to_string(), - details: Error::TlsError("TLS handshake failed".to_string()), + entity: "test.example.com".into(), + details: Error::TlsError("TLS handshake failed".into()), }), flags: 0, orcpt: None, diff --git a/tests/src/smtp/outbound/dane.rs b/tests/src/smtp/outbound/dane.rs index cded5ba3..369fbcbc 100644 --- a/tests/src/smtp/outbound/dane.rs +++ b/tests/src/smtp/outbound/dane.rs @@ -344,7 +344,7 @@ async fn dane_test() { assert_eq!( tlsa.verify(0, &host, Some(&certs)), Err(Status::PermanentFailure(ErrorDetails { - entity: host, + entity: host.into(), details: Error::DaneError("No matching certificates found in TLSA records".into()) })) ); diff --git a/tests/src/smtp/queue/mod.rs b/tests/src/smtp/queue/mod.rs index 41c3d869..c562faa7 100644 --- a/tests/src/smtp/queue/mod.rs +++ b/tests/src/smtp/queue/mod.rs @@ -23,7 +23,7 @@ pub mod virtualq; pub fn build_rcpt(address: &str, retry: u64, notify: u64, expires: u64) -> Recipient { Recipient { - address: address.to_string(), + address: address.into(), retry: Schedule::later(retry), notify: Schedule::later(notify), expires: QueueExpiry::Ttl(expires), diff --git a/tests/src/smtp/queue/retry.rs b/tests/src/smtp/queue/retry.rs index 68ca626c..786c8817 100644 --- a/tests/src/smtp/queue/retry.rs +++ b/tests/src/smtp/queue/retry.rs @@ -73,7 +73,7 @@ async fn queue_retry() { // Expect a failed DSN attempt.try_deliver(core.clone()); let message = qr.expect_message().await; - assert_eq!(message.message.return_path, ""); + assert_eq!(message.message.return_path.as_ref(), ""); assert_eq!( message.message.recipients.first().unwrap().address(), "john@test.org" diff --git a/tests/src/smtp/reporting/dmarc.rs b/tests/src/smtp/reporting/dmarc.rs index 565ed7e3..3bc9f4be 100644 --- a/tests/src/smtp/reporting/dmarc.rs +++ b/tests/src/smtp/reporting/dmarc.rs @@ -116,7 +116,7 @@ async fn report_dmarc() { message.message.recipients.last().unwrap().address(), "reports@foobar.net" ); - assert_eq!(message.message.return_path, "reports@example.org"); + assert_eq!(message.message.return_path.as_ref(), "reports@example.org"); message .read_lines(qr) .await diff --git a/tests/src/smtp/reporting/tls.rs b/tests/src/smtp/reporting/tls.rs index 57f19f61..7dba1f78 100644 --- a/tests/src/smtp/reporting/tls.rs +++ b/tests/src/smtp/reporting/tls.rs @@ -115,7 +115,7 @@ async fn report_tls() { message.message.recipients.last().unwrap().address(), "reports@foobar.org" ); - assert_eq!(message.message.return_path, "reports@example.org"); + assert_eq!(message.message.return_path.as_ref(), "reports@example.org"); message .read_lines(qr) .await diff --git a/tests/src/webdav/mod.rs b/tests/src/webdav/mod.rs index d191ea9c..279601e4 100644 --- a/tests/src/webdav/mod.rs +++ b/tests/src/webdav/mod.rs @@ -46,7 +46,11 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use store::rand::{Rng, distr::Alphanumeric, rng}; +use store::{ + ValueKey, + rand::{Rng, distr::Alphanumeric, rng}, + write::{AlignedBytes, Archive}, +}; use tokio::sync::watch; use types::{collection::Collection, field::EmailField}; use utils::config::Config; @@ -1050,12 +1054,13 @@ impl WebDavTest { pub async fn fetch_email(&self, account_id: u32, document_id: u32) -> Vec { let metadata_ = self .server - .archive_by_property( + .store() + .get_value::>(ValueKey::property( account_id, Collection::Email, document_id, - EmailField::Metadata.into(), - ) + EmailField::Metadata, + )) .await .unwrap() .unwrap();