feat: normalize emails
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
use clap::Parser;
|
||||
use rs_pop_imap_importer::{config::Settings, imap_client::ImapClient};
|
||||
use std::fs;
|
||||
|
||||
/// Fetch a specific email from IMAP server by UID
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to the .env file containing server configurations
|
||||
#[clap(short, long, default_value = ".env")]
|
||||
env_file: String,
|
||||
|
||||
/// UID of the email to fetch
|
||||
#[clap(short, long)]
|
||||
uid: u32,
|
||||
|
||||
/// Output file path (optional, otherwise prints to stdout)
|
||||
#[clap(short, long)]
|
||||
output: Option<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
println!("Connecting to IMAP server...");
|
||||
let settings = Settings::from_env_file(&args.env_file)?;
|
||||
let mut imap_client = ImapClient::new(&settings.imap)?;
|
||||
imap_client.login(&settings.imap)?;
|
||||
imap_client.select_inbox()?;
|
||||
|
||||
println!("Fetching all messages to find UID {}...", args.uid);
|
||||
let messages = imap_client.fetch_all_messages()?;
|
||||
|
||||
let mut found = false;
|
||||
for (msg_id, content) in messages {
|
||||
if msg_id == args.uid {
|
||||
found = true;
|
||||
println!("Found message with UID {}", args.uid);
|
||||
println!("Size: {} bytes", content.len());
|
||||
|
||||
if let Some(output_path) = args.output {
|
||||
fs::write(&output_path, &content)?;
|
||||
println!("Saved to: {}", output_path);
|
||||
} else {
|
||||
println!("\n--- Email Content ---\n");
|
||||
print!("{}", content);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
eprintln!("Error: Email with UID {} not found", args.uid);
|
||||
}
|
||||
|
||||
imap_client.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use clap::Parser;
|
||||
use rs_pop_imap_importer::{config::Settings, imap_client::ImapClient};
|
||||
|
||||
/// List all email UIDs in IMAP inbox
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to the .env file containing server configurations
|
||||
#[clap(short, long, default_value = ".env")]
|
||||
env_file: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
let settings = Settings::from_env_file(&args.env_file)?;
|
||||
let mut imap_client = ImapClient::new(&settings.imap)?;
|
||||
imap_client.login(&settings.imap)?;
|
||||
imap_client.select_inbox()?;
|
||||
|
||||
println!("Fetching all messages...");
|
||||
let messages = imap_client.fetch_all_messages()?;
|
||||
|
||||
println!("\nFound {} messages:", messages.len());
|
||||
println!("UIDs:");
|
||||
for (uid, _) in messages {
|
||||
println!(" {}", uid);
|
||||
}
|
||||
|
||||
imap_client.logout()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
use clap::Parser;
|
||||
use rs_pop_imap_importer::{config::Settings, imap_client::ImapClient, normalize_headers};
|
||||
|
||||
/// IMAP Email Normalizer
|
||||
///
|
||||
/// This utility fetches emails from an IMAP server, normalizes their headers
|
||||
/// to ensure RFC 5322 compliance, and re-imports them.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to the .env file containing server configurations
|
||||
#[clap(short, long, default_value = ".env")]
|
||||
env_file: String,
|
||||
|
||||
/// Perform a dry run without making changes
|
||||
#[clap(short, long)]
|
||||
dry_run: bool,
|
||||
|
||||
/// Skip confirmation prompt
|
||||
#[clap(short, long)]
|
||||
yes: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
println!("Starting IMAP email header normalization tool...");
|
||||
|
||||
// Load configuration from specified .env file
|
||||
let settings = Settings::from_env_file(&args.env_file)?;
|
||||
|
||||
// Connect to IMAP server
|
||||
println!("Connecting to IMAP server at {}:{}...", settings.imap.host, settings.imap.port);
|
||||
let mut imap_client = ImapClient::new(&settings.imap)?;
|
||||
imap_client.login(&settings.imap)?;
|
||||
imap_client.select_inbox()?;
|
||||
println!("Successfully connected to IMAP server");
|
||||
|
||||
// Fetch all messages
|
||||
println!("Fetching all messages from INBOX...");
|
||||
let messages = imap_client.fetch_all_messages()?;
|
||||
println!("Found {} messages in INBOX", messages.len());
|
||||
|
||||
if messages.is_empty() {
|
||||
println!("No messages to process. Exiting.");
|
||||
imap_client.logout()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Analyze messages and count how many need normalization
|
||||
let mut needs_normalization = 0;
|
||||
let mut normalized_messages = Vec::new();
|
||||
|
||||
for (msg_id, content) in &messages {
|
||||
match normalize_headers(content) {
|
||||
Ok(normalized) => {
|
||||
if &normalized != content {
|
||||
needs_normalization += 1;
|
||||
|
||||
// Debug output to show what changed
|
||||
if args.dry_run {
|
||||
println!("\nMessage {} needs normalization:", msg_id);
|
||||
|
||||
// Show first difference
|
||||
let orig_lines: Vec<&str> = content.lines().collect();
|
||||
let norm_lines: Vec<&str> = normalized.lines().collect();
|
||||
for (i, (o, n)) in orig_lines.iter().zip(norm_lines.iter()).enumerate() {
|
||||
if o != n {
|
||||
println!(" Line {}: Missing whitespace on header continuation", i + 1);
|
||||
println!(" Before: {:?}", o);
|
||||
println!(" After: {:?}", n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
normalized_messages.push((*msg_id, normalized));
|
||||
} else {
|
||||
normalized_messages.push((*msg_id, content.clone()));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Failed to normalize message {}: {}", msg_id, e);
|
||||
normalized_messages.push((*msg_id, content.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nAnalysis complete:");
|
||||
println!(" Total messages: {}", messages.len());
|
||||
println!(" Messages needing normalization: {}", needs_normalization);
|
||||
println!(" Messages already compliant: {}", messages.len() - needs_normalization);
|
||||
|
||||
if needs_normalization == 0 {
|
||||
println!("\nAll messages are already RFC 5322 compliant. No changes needed.");
|
||||
imap_client.logout()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if args.dry_run {
|
||||
println!("\nDry run mode - no changes will be made.");
|
||||
println!("\nTo normalize these messages, run without --dry-run flag.");
|
||||
imap_client.logout()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Confirmation prompt
|
||||
if !args.yes {
|
||||
println!("\nWARNING: This operation will:");
|
||||
println!(" 1. Delete {} messages with malformed headers from INBOX", needs_normalization);
|
||||
println!(" 2. Re-import them with normalized headers");
|
||||
println!("\nThis operation cannot be undone!");
|
||||
println!("\nDo you want to proceed? (yes/no)");
|
||||
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input)?;
|
||||
let input = input.trim().to_lowercase();
|
||||
|
||||
if input != "yes" && input != "y" {
|
||||
println!("Operation cancelled.");
|
||||
imap_client.logout()?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
println!("\nStarting normalization process...");
|
||||
|
||||
// Process messages that need normalization
|
||||
let mut processed = 0;
|
||||
let mut errors = 0;
|
||||
|
||||
for (i, (msg_id, content)) in messages.iter().enumerate() {
|
||||
let normalized = &normalized_messages[i].1;
|
||||
|
||||
// Only process if normalization changed something
|
||||
if normalized != content {
|
||||
print!("Processing message {} ({}/{})... ", msg_id, processed + 1, needs_normalization);
|
||||
|
||||
// Delete the original message
|
||||
match imap_client.delete_message(*msg_id) {
|
||||
Ok(_) => {
|
||||
// Re-import with normalized headers
|
||||
match imap_client.append_message(normalized) {
|
||||
Ok(_) => {
|
||||
println!("✓ normalized");
|
||||
processed += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("✗ failed to re-import: {}", e);
|
||||
errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("✗ failed to delete: {}", e);
|
||||
errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expunge deleted messages
|
||||
println!("\nExpunging deleted messages...");
|
||||
imap_client.expunge()?;
|
||||
|
||||
// Summary
|
||||
println!("\n=== Normalization Summary ===");
|
||||
println!("Successfully processed: {}", processed);
|
||||
if errors > 0 {
|
||||
println!("Errors encountered: {}", errors);
|
||||
}
|
||||
println!("Operation completed!");
|
||||
|
||||
// Clean up
|
||||
imap_client.logout()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user