From 1142dc1735148fc1248a73e7ac30d6bec442ba77 Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Wed, 25 Oct 2023 00:55:04 +0100 Subject: [PATCH 1/7] parse: add feature to parse and upload msg files --- Cargo.lock | 23 +- cli/Cargo.toml | 2 + cli/src/args.rs | 9 +- cli/src/commands/mod.rs | 1 + cli/src/commands/parse/mod.rs | 21 ++ cli/src/commands/parse/msgs.rs | 415 ++++++++++++++++++++++++++++++ cli/src/main.rs | 3 +- cli/tests/samples/non-unicode.msg | Bin 0 -> 92672 bytes cli/tests/samples/unicode.msg | Bin 0 -> 111616 bytes 9 files changed, 470 insertions(+), 4 deletions(-) create mode 100644 cli/src/commands/parse/mod.rs create mode 100644 cli/src/commands/parse/msgs.rs create mode 100644 cli/tests/samples/non-unicode.msg create mode 100644 cli/tests/samples/unicode.msg diff --git a/Cargo.lock b/Cargo.lock index 64079111..12d9f412 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,12 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.4.0" @@ -119,6 +125,17 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +[[package]] +name = "cfb" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b390793e912300f1aa713429f7fd0c391024e6c18b988962558bc4f96a349b1f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -394,9 +411,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -1222,10 +1239,12 @@ version = "0.19.0" dependencies = [ "anyhow", "backoff", + "cfb", "chrono", "colored", "dialoguer", "dirs", + "encoding_rs", "env_logger", "indicatif", "log", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ec8768d8..4a10e6c5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -38,6 +38,8 @@ reinfer-client = { version = "0.19.0", path = "../api" } dialoguer = "0.10.4" scoped_threadpool = "0.1.9" backoff = "0.4.0" +cfb = "0.9.0" +encoding_rs = "0.8.33" [dev-dependencies] pretty_assertions = "1.3.0" diff --git a/cli/src/args.rs b/cli/src/args.rs index a3e8cabc..7c078aab 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -1,6 +1,6 @@ use crate::{ commands::{ - config::ConfigArgs, create::CreateArgs, delete::DeleteArgs, get::GetArgs, + config::ConfigArgs, create::CreateArgs, delete::DeleteArgs, get::GetArgs, parse::ParseArgs, update::UpdateArgs, }, printer::OutputFormat, @@ -104,6 +104,13 @@ pub enum Command { #[structopt(subcommand)] get_args: GetArgs, }, + + #[structopt(name = "parse")] + /// Upload data from various file types + Parse { + #[structopt(subcommand)] + parse_args: ParseArgs, + }, } #[derive(Debug)] diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index be824d0c..2d781a82 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -6,6 +6,7 @@ pub mod config; pub mod create; pub mod delete; pub mod get; +pub mod parse; pub mod update; pub fn ensure_uip_user_consents_to_ai_unit_charge(base_url: &Url) -> Result<()> { diff --git a/cli/src/commands/parse/mod.rs b/cli/src/commands/parse/mod.rs new file mode 100644 index 00000000..9523197d --- /dev/null +++ b/cli/src/commands/parse/mod.rs @@ -0,0 +1,21 @@ +mod msgs; + +use anyhow::Result; +use reinfer_client::Client; +use structopt::StructOpt; + +use self::msgs::ParseMsgArgs; + +#[derive(Debug, StructOpt)] +pub enum ParseArgs { + #[structopt(name = "msgs")] + /// Parse unicode msg files. Note: Currently the body is processed as plain text. + /// Html bodies are not supported. + Msgs(ParseMsgArgs), +} + +pub fn run(args: &ParseArgs, client: Client) -> Result<()> { + match args { + ParseArgs::Msgs(parse_msg_args) => msgs::parse(&client, parse_msg_args), + } +} diff --git a/cli/src/commands/parse/msgs.rs b/cli/src/commands/parse/msgs.rs new file mode 100644 index 00000000..8ebdf961 --- /dev/null +++ b/cli/src/commands/parse/msgs.rs @@ -0,0 +1,415 @@ +use anyhow::{anyhow, Context, Result}; +use cfb::CompoundFile; +use colored::Colorize; +use core::sync::atomic::Ordering; +use log::error; +use once_cell::sync::Lazy; +use regex::Regex; +use std::{ + fs::DirEntry, + io::Read, + sync::{atomic::AtomicUsize, Arc}, +}; + +use reinfer_client::{ + resources::{ + documents::{Document, RawEmail, RawEmailBody, RawEmailHeaders}, + email::AttachmentMetadata, + }, + Client, PropertyMap, Source, SourceIdentifier, TransformTag, +}; +use std::{fs::File, path::PathBuf}; +use structopt::StructOpt; + +use crate::{ + commands::ensure_uip_user_consents_to_ai_unit_charge, + progress::{Options as ProgressOptions, Progress}, +}; + +const UPLOAD_BATCH_SIZE: usize = 128; +const MSG_NAME_USER_PROPERTY_NAME: &str = "MSG NAME ID"; +const STREAM_PATH_ATTACHMENT_STORE_PREFIX: &str = "__attach_version1.0_#"; + +static CONTENT_TYPE_MIME_HEADER_RX: Lazy = + Lazy::new(|| Regex::new(r"Content-Type:((\s)+.+\n)*").unwrap()); +static STREAM_PATH_MESSAGE_BODY_PLAIN: Lazy = + Lazy::new(|| PathBuf::from("__substg1.0_1000001F")); +static STREAM_PATH_MESSAGE_HEADER: Lazy = + Lazy::new(|| PathBuf::from("__substg1.0_007d001F")); +static STREAM_PATH_ATTACHMENT_FILENAME: Lazy = + Lazy::new(|| PathBuf::from("__substg1.0_3707001F")); +static STREAM_PATH_ATTACHMENT_EXTENSION: Lazy = + Lazy::new(|| PathBuf::from("__substg1.0_3703001F")); +static STREAM_PATH_ATTACHMENT_DATA: Lazy = + Lazy::new(|| PathBuf::from("__substg1.0_37010102")); + +pub struct Statistics { + processed: AtomicUsize, + failed: AtomicUsize, + uploaded: AtomicUsize, +} + +impl Statistics { + fn new() -> Self { + Self { + processed: AtomicUsize::new(0), + failed: AtomicUsize::new(0), + uploaded: AtomicUsize::new(0), + } + } + + #[inline] + fn add_uploaded(&self, num_uploaded: usize) { + self.uploaded.fetch_add(num_uploaded, Ordering::SeqCst); + } + + #[inline] + fn increment_failed(&self) { + self.failed.fetch_add(1, Ordering::SeqCst); + } + + #[inline] + fn increment_processed(&self) { + self.processed.fetch_add(1, Ordering::SeqCst); + } + + #[inline] + fn num_uploaded(&self) -> usize { + self.uploaded.load(Ordering::SeqCst) + } + + #[inline] + fn num_failed(&self) -> usize { + self.failed.load(Ordering::SeqCst) + } + + #[inline] + fn num_processed(&self) -> usize { + self.processed.load(Ordering::SeqCst) + } +} + +#[derive(Debug, StructOpt)] +pub struct ParseMsgArgs { + #[structopt(short = "d", long = "dir", parse(from_os_str))] + /// Directory containing the msgs + directory: PathBuf, + + #[structopt(short = "s", long = "source")] + /// Source name or id + source: SourceIdentifier, + + #[structopt(long = "transform-tag")] + /// Transform tag to use. + transform_tag: TransformTag, + + #[structopt(short = "n", long = "no-charge")] + /// Whether to attempt to bypass billing (internal only) + no_charge: bool, + + #[structopt(short = "y", long = "yes")] + /// Consent to ai unit charge. Suppresses confirmation prompt. + yes: bool, +} + +fn read_stream(stream_path: PathBuf, compound_file: &mut CompoundFile) -> Result> { + let data = { + let mut stream = compound_file.open_stream(stream_path)?; + let mut buffer = Vec::new(); + stream.read_to_end(&mut buffer)?; + buffer + }; + + Ok(data) +} + +fn read_unicode_stream_to_string( + stream_path: PathBuf, + compound_file: &mut CompoundFile, +) -> Result { + if !compound_file.is_stream(stream_path.clone()) { + return Err(anyhow!( + "Could not find stream {}. Please check that you are using unicode msgs", + stream_path.to_string_lossy() + )); + } + + let data = read_stream(stream_path, compound_file)?; + + // Stream data is a UTF16 string encoded as Vec[u8] + let mut buffer: String = String::with_capacity(data.len() * 2); + let mut decoder = encoding_rs::UTF_16LE.new_decoder(); + let (coder_result, _, _) = decoder.decode_to_string(&data, &mut buffer, true); + if coder_result != encoding_rs::CoderResult::InputEmpty { + Err(anyhow!("Unexpected coder result")) + } else { + Ok(buffer) + } +} + +fn get_attachment_store_path(attachment_number: usize) -> PathBuf { + PathBuf::from(format!( + "{}{:08}", + STREAM_PATH_ATTACHMENT_STORE_PREFIX, attachment_number + )) +} + +fn read_attachment( + attachment_path: PathBuf, + compound_file: &mut CompoundFile, +) -> Result { + let mut attachment_name_path = attachment_path.clone(); + attachment_name_path.push(STREAM_PATH_ATTACHMENT_FILENAME.clone()); + + let mut content_type_path = attachment_path.clone(); + content_type_path.push(STREAM_PATH_ATTACHMENT_EXTENSION.clone()); + + let mut data_path = attachment_path.clone(); + data_path.push(STREAM_PATH_ATTACHMENT_DATA.clone()); + + let name = read_unicode_stream_to_string(attachment_name_path.clone(), compound_file)?; + let content_type = read_unicode_stream_to_string(content_type_path, compound_file)?; + let data = read_stream(data_path, compound_file)?; + + Ok(AttachmentMetadata { + name, + content_type, + size: data.len() as u64, + }) +} + +fn remove_content_type_header(headers_string: String) -> Result { + Ok(CONTENT_TYPE_MIME_HEADER_RX + .clone() + .replace(&headers_string, "") + .to_string()) +} + +fn read_msg_to_document(path: &PathBuf) -> Result { + if !path.is_file() { + return Err(anyhow!("No such file: {:?}", path)); + } + + let mut compound_file = cfb::open(path)?; + + // Headers + let headers_string = + read_unicode_stream_to_string(STREAM_PATH_MESSAGE_HEADER.clone(), &mut compound_file)?; + + // As the content type won't match the parsed value from the body in the msg + let headers_string_no_content_type = remove_content_type_header(headers_string)?; + + let plain_body_string = + read_unicode_stream_to_string(STREAM_PATH_MESSAGE_BODY_PLAIN.clone(), &mut compound_file)?; + + // Attachments + let mut attachment_number = 0; + let mut attachments = Vec::new(); + loop { + let attachment_path = get_attachment_store_path(attachment_number); + + if compound_file.is_storage(attachment_path.clone()) { + attachments.push(read_attachment(attachment_path, &mut compound_file)?); + } else { + break; + } + + attachment_number += 1; + } + + // User Properties + let mut user_properties = PropertyMap::new(); + user_properties.insert_string( + MSG_NAME_USER_PROPERTY_NAME.to_string(), + path.file_name() + .context("Could not get file name")? + .to_string_lossy() + .to_string(), + ); + + Ok(Document { + raw_email: RawEmail { + body: RawEmailBody::Plain(plain_body_string), + headers: RawEmailHeaders::Raw(headers_string_no_content_type), + attachments, + }, + user_properties, + comment_id: None, + }) +} + +fn upload_batch_of_documents( + client: &Client, + source: &Source, + documents: &Vec, + transform_tag: &TransformTag, + no_charge: bool, + statistics: &Arc, +) -> Result<()> { + client.sync_raw_emails( + &source.full_name(), + documents, + transform_tag, + false, + no_charge, + )?; + statistics.add_uploaded(documents.len()); + Ok(()) +} + +pub fn get_msgs_in_directory(directory: &PathBuf) -> Result> { + Ok(std::fs::read_dir(directory)? + .filter_map(|path| { + if let Ok(path) = path { + if !path.path().extension().is_some_and(|msg| msg == "msg") { + None + } else { + Some(path) + } + } else { + None + } + }) + .collect()) +} + +pub fn parse(client: &Client, args: &ParseMsgArgs) -> Result<()> { + let ParseMsgArgs { + directory, + source, + transform_tag, + no_charge, + yes, + } = args; + + if !no_charge && !yes { + ensure_uip_user_consents_to_ai_unit_charge(client.base_url())?; + } + + let msg_paths = get_msgs_in_directory(directory)?; + let statistics = Arc::new(Statistics::new()); + let _progress = get_progress_bar(msg_paths.len() as u64, &statistics); + let source = client.get_source(source.clone())?; + + let mut documents = Vec::new(); + let mut errors = Vec::new(); + for path in msg_paths { + match read_msg_to_document(&path.path()) { + Ok(document) => { + documents.push(document); + + if documents.len() >= UPLOAD_BATCH_SIZE { + upload_batch_of_documents( + client, + &source, + &documents, + transform_tag, + *no_charge, + &statistics, + )?; + documents.clear(); + } + statistics.increment_processed(); + } + Err(error) => { + errors.push(format!( + "Failed to process file {}: {}", + path.file_name().to_string_lossy(), + error + )); + statistics.increment_failed(); + statistics.increment_processed(); + } + } + } + + upload_batch_of_documents( + client, + &source, + &documents, + transform_tag, + *no_charge, + &statistics, + )?; + + for error in errors { + error!("{}", error); + } + + Ok(()) +} + +fn get_progress_bar(total_bytes: u64, statistics: &Arc) -> Progress { + Progress::new( + move |statistic| { + let num_processed = statistic.num_processed(); + let num_failed = statistic.num_failed(); + let num_uploaded = statistic.num_uploaded(); + ( + num_processed as u64, + format!( + "{} {} {} {} {} {}", + num_processed.to_string().bold(), + "processed".dimmed(), + num_failed.to_string().bold(), + "failed".dimmed(), + num_uploaded.to_string().bold(), + "uploaded".dimmed() + ), + ) + }, + statistics, + Some(total_bytes), + ProgressOptions { bytes_units: false }, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn test_read_msg_to_document_non_unicode() { + let result = read_msg_to_document(&PathBuf::from("tests/samples/non-unicode.msg")); + + assert_eq!(result.expect_err("Expected Error Result").to_string(), "Could not find stream __substg1.0_007d001F. Please check that you are using unicode msgs"); + } + + #[test] + fn test_read_msg_to_document_unicode() { + let mut expected_user_properties = PropertyMap::new(); + expected_user_properties + .insert_string("MSG NAME ID".to_string(), "unicode.msg".to_string()); + + let expected_headers = "Received: from DB8PR02MB5883.eurprd02.prod.outlook.com (2603:10a6:10:116::17)\r\n by AM6PR02MB4215.eurprd02.prod.outlook.com with HTTPS; Wed, 25 Oct 2023\r\n 17:03:35 +0000\r\nAuthentication-Results: dkim=none (message not signed)\r\n header.d=none;dmarc=none action=none header.from=uipath.com;\r\nReceived: from AM9PR02MB6642.eurprd02.prod.outlook.com (2603:10a6:20b:2d2::18)\r\n by DB8PR02MB5883.eurprd02.prod.outlook.com (2603:10a6:10:116::17) with\r\n Microsoft SMTP Server (version=TLS1_2,\r\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6907.33; Wed, 25 Oct\r\n 2023 17:03:23 +0000\r\nReceived: from AM9PR02MB6642.eurprd02.prod.outlook.com\r\n ([fe80::fb6e:4d16:d9ba:33ae]) by AM9PR02MB6642.eurprd02.prod.outlook.com\r\n ([fe80::fb6e:4d16:d9ba:33ae%6]) with mapi id 15.20.6933.019; Wed, 25 Oct 2023\r\n 17:03:23 +0000\r\nContent-Transfer-Encoding: binary\r\nFrom: Joe Prosser \r\nTo: Andra Buica \r\nSubject: Re: Testing the CLI!!\r\nThread-Topic: Testing the CLI!!\r\nThread-Index: AQHaB2N76HBc3H6u4Eeu9hAZAXjmE7BauHazgAAAIR2AAAJRsw==\r\nDate: Wed, 25 Oct 2023 17:03:22 +0000\r\nMessage-ID:\r\n \r\nReferences:\r\n \r\n \r\n \r\nIn-Reply-To:\r\n \r\nAccept-Language: en-GB, en-US\r\nContent-Language: en-GB\r\nX-MS-Has-Attach: yes\r\nX-MS-Exchange-Organization-SCL: 1\r\nX-MS-TNEF-Correlator:\r\n \r\nmsip_labels:\r\nMIME-Version: 1.0\r\nX-MS-Exchange-Organization-MessageDirectionality: Originating\r\nX-MS-Exchange-Organization-AuthSource: AM9PR02MB6642.eurprd02.prod.outlook.com\r\nX-MS-Exchange-Organization-AuthAs: Internal\r\nX-MS-Exchange-Organization-AuthMechanism: 04\r\nX-MS-Exchange-Organization-Network-Message-Id:\r\n 178907c2-2871-4221-a75a-08dbd57c4bf2\r\nX-MS-PublicTrafficType: Email\r\nX-MS-TrafficTypeDiagnostic:\r\n AM9PR02MB6642:EE_|DB8PR02MB5883:EE_|AM6PR02MB4215:EE_\r\nReturn-Path: joe.prosser@uipath.com\r\nX-MS-Exchange-Organization-ExpirationStartTime: 25 Oct 2023 17:03:23.2098\r\n (UTC)\r\nX-MS-Exchange-Organization-ExpirationStartTimeReason: OriginalSubmit\r\nX-MS-Exchange-Organization-ExpirationInterval: 1:00:00:00.0000000\r\nX-MS-Exchange-Organization-ExpirationIntervalReason: OriginalSubmit\r\nX-MS-Office365-Filtering-Correlation-Id: 178907c2-2871-4221-a75a-08dbd57c4bf2\r\nX-MS-Exchange-AtpMessageProperties: SA|SL\r\nX-Microsoft-Antispam: BCL:0;\r\nX-Forefront-Antispam-Report:\r\n CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:AM9PR02MB6642.eurprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:;DIR:INT;\r\nX-MS-Exchange-CrossTenant-OriginalArrivalTime: 25 Oct 2023 17:03:22.9433\r\n (UTC)\r\nX-MS-Exchange-CrossTenant-FromEntityHeader: Hosted\r\nX-MS-Exchange-CrossTenant-Id: d8353d2a-b153-4d17-8827-902c51f72357\r\nX-MS-Exchange-CrossTenant-AuthSource: AM9PR02MB6642.eurprd02.prod.outlook.com\r\nX-MS-Exchange-CrossTenant-AuthAs: Internal\r\nX-MS-Exchange-CrossTenant-Network-Message-Id: 178907c2-2871-4221-a75a-08dbd57c4bf2\r\nX-MS-Exchange-CrossTenant-MailboxType: HOSTED\r\nX-MS-Exchange-CrossTenant-UserPrincipalName: ++FDvUGfWQ/f3Ycjz3vJlna939o3V0zI8K8BPWCQ7aaQDhAJYe5XMf5Tr6B6e/5ucWri5/sUCnvrb1GDymXZmA==\r\nX-MS-Exchange-Transport-CrossTenantHeadersStamped: DB8PR02MB5883\r\nX-MS-Exchange-Transport-EndToEndLatency: 00:00:12.3444456\r\nX-MS-Exchange-Processed-By-BccFoldering: 15.20.6907.032\r\nX-Microsoft-Antispam-Mailbox-Delivery:\r\n\tucf:0;jmr:0;auth:0;dest:I;ENG:(910001)(944506478)(944626604)(920097)(425001)(930097)(140003)(1420103);\r\nX-Microsoft-Antispam-Message-Info:\r\n\thxMOcmClDYIwXiLHDHfW3mj4bIuPkLWLYD9z839jTLrsdaQbyDGWXui95ou9iKuUSUHzubxtiSwgj9OVTVInYaJ6SuN1imGY/n99Cn1vAx4VAh1xSYBUsm2bHu5atiwnMZTQaWg7BA03Yj6BSrOYqh49oLJrh9blZmtlkMs4uCi1Y5Y7YpEMpzLi7ye2fg+gPqELrNLHIKfziazCdrPNLKQ+9tlbb6UHbxeX1YVGY2ebnYXPwRilEP++cljm3hV8abvlgC1GF+nsuIS5XJwhkHmgTyetr7iZE3GWR9XC0NsB9w5bQgg82r75ozlGKWVKkuev1IlRjStpKaJbOoPBvo/6SCqSbWaQinyUEfPDXiJOYHW3D0xsf4uCpbFpb1D+TpQr+dFaCxBNdmFjBrd/SCHoyyl9QAO1nz0W5cUAwjSDKN7Pv7iKIUlx24nkDCeYeVqKhNcqulwIlEP6ewBBL2BTSplNPApIbliiHhh/Z6mes1xx3dfB5T4tIUv6wINSH3G2Ddec2fLzeTHknuyaOA9Wj8ks+JIjE+i5/CPidxfH4ACoggwbdnLwwnwniGcNoZKdyM+G4whogPe2oKXPggX9er/44bUOlKEcK5DsPFEZX2xKzsg7JwPPLcgO/lbP2iN/yFww6I6vri27P29a86np21iNSGOb51giFj5wgSq4iZLeRe64cEj4+i4K1KosNLBgDYTj/WGrioHl5Xe9ww==\r\n"; + + let expected_body = "Hey, \r\n\r\nWe should check that attachments work to ✅\r\n\r\nJoe \r\n________________________________\r\n\r\nFrom: Joe Prosser \r\nSent: 25 October 2023 17:53\r\nTo: Andra Buica \r\nSubject: Re: Testing the CLI!! \r\n \r\nHey,\r\n\r\nFingers cross 🤞 \r\n\r\nJoe\r\n________________________________\r\n\r\nFrom: Andra Buica \r\nSent: 25 October 2023 17:52\r\nTo: Joe Prosser \r\nSubject: Re: Testing the CLI!! \r\n \r\n\r\nHey, \r\n\r\n \r\n\r\nHopefully it will work! \r\n\r\n \r\n\r\nAndra\r\n\r\n \r\n\r\nFrom: Joe Prosser \r\nDate: Wednesday, 25 October 2023 at 17:52\r\nTo: Andra Buica \r\nSubject: Testing the CLI!!\r\n\r\nHey,\r\n\r\n \r\n\r\nDo you think it will work?\r\n\r\n \r\n\r\nJoe \r\n\r\n"; + + let expected_document = Document { + comment_id: None, + raw_email: RawEmail { + body: RawEmailBody::Plain(expected_body.to_string()), + headers: RawEmailHeaders::Raw(expected_headers.to_string()), + attachments: vec![ + AttachmentMetadata { + name: "hello.txt".to_string(), + size: 12, + content_type: ".txt".to_string(), + }, + AttachmentMetadata { + name: "world.pdf".to_string(), + size: 7302, + content_type: ".pdf".to_string(), + }, + ], + }, + user_properties: expected_user_properties, + }; + + let actual_document = read_msg_to_document(&PathBuf::from("tests/samples/unicode.msg")) + .expect("Failed to read msg"); + + assert_eq!(expected_document, actual_document); + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 13830be2..45585bf1 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -19,7 +19,7 @@ use structopt::{clap::Shell as ClapShell, StructOpt}; use crate::{ args::{Args, Command, Shell}, - commands::{config as config_command, create, delete, get, update}, + commands::{config as config_command, create, delete, get, parse, update}, config::ReinferConfig, printer::Printer, }; @@ -71,6 +71,7 @@ fn run(args: Args) -> Result<()> { Command::Update { update_args } => { update::run(update_args, client_from_args(&args, &config)?, &printer) } + Command::Parse { parse_args } => parse::run(parse_args, client_from_args(&args, &config)?), } } diff --git a/cli/tests/samples/non-unicode.msg b/cli/tests/samples/non-unicode.msg new file mode 100644 index 0000000000000000000000000000000000000000..782017a8e5b04cf2c5ef384025b35c9f30ea628d GIT binary patch literal 92672 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 Date: Wed, 25 Oct 2023 22:52:53 +0100 Subject: [PATCH 2/7] remove other problematic headers --- cli/src/commands/parse/msgs.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/cli/src/commands/parse/msgs.rs b/cli/src/commands/parse/msgs.rs index 8ebdf961..b8e02192 100644 --- a/cli/src/commands/parse/msgs.rs +++ b/cli/src/commands/parse/msgs.rs @@ -31,7 +31,9 @@ const MSG_NAME_USER_PROPERTY_NAME: &str = "MSG NAME ID"; const STREAM_PATH_ATTACHMENT_STORE_PREFIX: &str = "__attach_version1.0_#"; static CONTENT_TYPE_MIME_HEADER_RX: Lazy = - Lazy::new(|| Regex::new(r"Content-Type:((\s)+.+\n)*").unwrap()); + Lazy::new(|| Regex::new(r"Content-Type:((\s)+.+\n)+").unwrap()); +static CONTENT_TRANSFER_ENCODING_MIME_HEADER_RX: Lazy = + Lazy::new(|| Regex::new(r"Content-Transfer-Encoding:((\s)+.+\n)+").unwrap()); static STREAM_PATH_MESSAGE_BODY_PLAIN: Lazy = Lazy::new(|| PathBuf::from("__substg1.0_1000001F")); static STREAM_PATH_MESSAGE_HEADER: Lazy = @@ -178,11 +180,20 @@ fn read_attachment( }) } -fn remove_content_type_header(headers_string: String) -> Result { - Ok(CONTENT_TYPE_MIME_HEADER_RX +fn remove_content_headers(headers_string: String) -> Result { + let mut clean_headers_string: String; + + clean_headers_string = CONTENT_TYPE_MIME_HEADER_RX .clone() .replace(&headers_string, "") - .to_string()) + .to_string(); + + clean_headers_string = CONTENT_TRANSFER_ENCODING_MIME_HEADER_RX + .clone() + .replace(&clean_headers_string, "") + .to_string(); + + Ok(clean_headers_string) } fn read_msg_to_document(path: &PathBuf) -> Result { @@ -197,7 +208,7 @@ fn read_msg_to_document(path: &PathBuf) -> Result { read_unicode_stream_to_string(STREAM_PATH_MESSAGE_HEADER.clone(), &mut compound_file)?; // As the content type won't match the parsed value from the body in the msg - let headers_string_no_content_type = remove_content_type_header(headers_string)?; + let headers_string_no_content_headers = remove_content_headers(headers_string)?; let plain_body_string = read_unicode_stream_to_string(STREAM_PATH_MESSAGE_BODY_PLAIN.clone(), &mut compound_file)?; @@ -230,7 +241,7 @@ fn read_msg_to_document(path: &PathBuf) -> Result { Ok(Document { raw_email: RawEmail { body: RawEmailBody::Plain(plain_body_string), - headers: RawEmailHeaders::Raw(headers_string_no_content_type), + headers: RawEmailHeaders::Raw(headers_string_no_content_headers), attachments, }, user_properties, From b637501eb7580c982df8d62fba42f735fa2e465f Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Wed, 25 Oct 2023 23:08:49 +0100 Subject: [PATCH 3/7] fix tests --- cli/src/commands/parse/msgs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/commands/parse/msgs.rs b/cli/src/commands/parse/msgs.rs index b8e02192..e8995a6c 100644 --- a/cli/src/commands/parse/msgs.rs +++ b/cli/src/commands/parse/msgs.rs @@ -393,7 +393,7 @@ mod tests { expected_user_properties .insert_string("MSG NAME ID".to_string(), "unicode.msg".to_string()); - let expected_headers = "Received: from DB8PR02MB5883.eurprd02.prod.outlook.com (2603:10a6:10:116::17)\r\n by AM6PR02MB4215.eurprd02.prod.outlook.com with HTTPS; Wed, 25 Oct 2023\r\n 17:03:35 +0000\r\nAuthentication-Results: dkim=none (message not signed)\r\n header.d=none;dmarc=none action=none header.from=uipath.com;\r\nReceived: from AM9PR02MB6642.eurprd02.prod.outlook.com (2603:10a6:20b:2d2::18)\r\n by DB8PR02MB5883.eurprd02.prod.outlook.com (2603:10a6:10:116::17) with\r\n Microsoft SMTP Server (version=TLS1_2,\r\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6907.33; Wed, 25 Oct\r\n 2023 17:03:23 +0000\r\nReceived: from AM9PR02MB6642.eurprd02.prod.outlook.com\r\n ([fe80::fb6e:4d16:d9ba:33ae]) by AM9PR02MB6642.eurprd02.prod.outlook.com\r\n ([fe80::fb6e:4d16:d9ba:33ae%6]) with mapi id 15.20.6933.019; Wed, 25 Oct 2023\r\n 17:03:23 +0000\r\nContent-Transfer-Encoding: binary\r\nFrom: Joe Prosser \r\nTo: Andra Buica \r\nSubject: Re: Testing the CLI!!\r\nThread-Topic: Testing the CLI!!\r\nThread-Index: AQHaB2N76HBc3H6u4Eeu9hAZAXjmE7BauHazgAAAIR2AAAJRsw==\r\nDate: Wed, 25 Oct 2023 17:03:22 +0000\r\nMessage-ID:\r\n \r\nReferences:\r\n \r\n \r\n \r\nIn-Reply-To:\r\n \r\nAccept-Language: en-GB, en-US\r\nContent-Language: en-GB\r\nX-MS-Has-Attach: yes\r\nX-MS-Exchange-Organization-SCL: 1\r\nX-MS-TNEF-Correlator:\r\n \r\nmsip_labels:\r\nMIME-Version: 1.0\r\nX-MS-Exchange-Organization-MessageDirectionality: Originating\r\nX-MS-Exchange-Organization-AuthSource: AM9PR02MB6642.eurprd02.prod.outlook.com\r\nX-MS-Exchange-Organization-AuthAs: Internal\r\nX-MS-Exchange-Organization-AuthMechanism: 04\r\nX-MS-Exchange-Organization-Network-Message-Id:\r\n 178907c2-2871-4221-a75a-08dbd57c4bf2\r\nX-MS-PublicTrafficType: Email\r\nX-MS-TrafficTypeDiagnostic:\r\n AM9PR02MB6642:EE_|DB8PR02MB5883:EE_|AM6PR02MB4215:EE_\r\nReturn-Path: joe.prosser@uipath.com\r\nX-MS-Exchange-Organization-ExpirationStartTime: 25 Oct 2023 17:03:23.2098\r\n (UTC)\r\nX-MS-Exchange-Organization-ExpirationStartTimeReason: OriginalSubmit\r\nX-MS-Exchange-Organization-ExpirationInterval: 1:00:00:00.0000000\r\nX-MS-Exchange-Organization-ExpirationIntervalReason: OriginalSubmit\r\nX-MS-Office365-Filtering-Correlation-Id: 178907c2-2871-4221-a75a-08dbd57c4bf2\r\nX-MS-Exchange-AtpMessageProperties: SA|SL\r\nX-Microsoft-Antispam: BCL:0;\r\nX-Forefront-Antispam-Report:\r\n CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:AM9PR02MB6642.eurprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:;DIR:INT;\r\nX-MS-Exchange-CrossTenant-OriginalArrivalTime: 25 Oct 2023 17:03:22.9433\r\n (UTC)\r\nX-MS-Exchange-CrossTenant-FromEntityHeader: Hosted\r\nX-MS-Exchange-CrossTenant-Id: d8353d2a-b153-4d17-8827-902c51f72357\r\nX-MS-Exchange-CrossTenant-AuthSource: AM9PR02MB6642.eurprd02.prod.outlook.com\r\nX-MS-Exchange-CrossTenant-AuthAs: Internal\r\nX-MS-Exchange-CrossTenant-Network-Message-Id: 178907c2-2871-4221-a75a-08dbd57c4bf2\r\nX-MS-Exchange-CrossTenant-MailboxType: HOSTED\r\nX-MS-Exchange-CrossTenant-UserPrincipalName: ++FDvUGfWQ/f3Ycjz3vJlna939o3V0zI8K8BPWCQ7aaQDhAJYe5XMf5Tr6B6e/5ucWri5/sUCnvrb1GDymXZmA==\r\nX-MS-Exchange-Transport-CrossTenantHeadersStamped: DB8PR02MB5883\r\nX-MS-Exchange-Transport-EndToEndLatency: 00:00:12.3444456\r\nX-MS-Exchange-Processed-By-BccFoldering: 15.20.6907.032\r\nX-Microsoft-Antispam-Mailbox-Delivery:\r\n\tucf:0;jmr:0;auth:0;dest:I;ENG:(910001)(944506478)(944626604)(920097)(425001)(930097)(140003)(1420103);\r\nX-Microsoft-Antispam-Message-Info:\r\n\thxMOcmClDYIwXiLHDHfW3mj4bIuPkLWLYD9z839jTLrsdaQbyDGWXui95ou9iKuUSUHzubxtiSwgj9OVTVInYaJ6SuN1imGY/n99Cn1vAx4VAh1xSYBUsm2bHu5atiwnMZTQaWg7BA03Yj6BSrOYqh49oLJrh9blZmtlkMs4uCi1Y5Y7YpEMpzLi7ye2fg+gPqELrNLHIKfziazCdrPNLKQ+9tlbb6UHbxeX1YVGY2ebnYXPwRilEP++cljm3hV8abvlgC1GF+nsuIS5XJwhkHmgTyetr7iZE3GWR9XC0NsB9w5bQgg82r75ozlGKWVKkuev1IlRjStpKaJbOoPBvo/6SCqSbWaQinyUEfPDXiJOYHW3D0xsf4uCpbFpb1D+TpQr+dFaCxBNdmFjBrd/SCHoyyl9QAO1nz0W5cUAwjSDKN7Pv7iKIUlx24nkDCeYeVqKhNcqulwIlEP6ewBBL2BTSplNPApIbliiHhh/Z6mes1xx3dfB5T4tIUv6wINSH3G2Ddec2fLzeTHknuyaOA9Wj8ks+JIjE+i5/CPidxfH4ACoggwbdnLwwnwniGcNoZKdyM+G4whogPe2oKXPggX9er/44bUOlKEcK5DsPFEZX2xKzsg7JwPPLcgO/lbP2iN/yFww6I6vri27P29a86np21iNSGOb51giFj5wgSq4iZLeRe64cEj4+i4K1KosNLBgDYTj/WGrioHl5Xe9ww==\r\n"; + let expected_headers = "Received: from DB8PR02MB5883.eurprd02.prod.outlook.com (2603:10a6:10:116::17)\r\n by AM6PR02MB4215.eurprd02.prod.outlook.com with HTTPS; Wed, 25 Oct 2023\r\n 17:03:35 +0000\r\nAuthentication-Results: dkim=none (message not signed)\r\n header.d=none;dmarc=none action=none header.from=uipath.com;\r\nReceived: from AM9PR02MB6642.eurprd02.prod.outlook.com (2603:10a6:20b:2d2::18)\r\n by DB8PR02MB5883.eurprd02.prod.outlook.com (2603:10a6:10:116::17) with\r\n Microsoft SMTP Server (version=TLS1_2,\r\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6907.33; Wed, 25 Oct\r\n 2023 17:03:23 +0000\r\nReceived: from AM9PR02MB6642.eurprd02.prod.outlook.com\r\n ([fe80::fb6e:4d16:d9ba:33ae]) by AM9PR02MB6642.eurprd02.prod.outlook.com\r\n ([fe80::fb6e:4d16:d9ba:33ae%6]) with mapi id 15.20.6933.019; Wed, 25 Oct 2023\r\n 17:03:23 +0000\r\nFrom: Joe Prosser \r\nTo: Andra Buica \r\nSubject: Re: Testing the CLI!!\r\nThread-Topic: Testing the CLI!!\r\nThread-Index: AQHaB2N76HBc3H6u4Eeu9hAZAXjmE7BauHazgAAAIR2AAAJRsw==\r\nDate: Wed, 25 Oct 2023 17:03:22 +0000\r\nMessage-ID:\r\n \r\nReferences:\r\n \r\n \r\n \r\nIn-Reply-To:\r\n \r\nAccept-Language: en-GB, en-US\r\nContent-Language: en-GB\r\nX-MS-Has-Attach: yes\r\nX-MS-Exchange-Organization-SCL: 1\r\nX-MS-TNEF-Correlator:\r\n \r\nmsip_labels:\r\nMIME-Version: 1.0\r\nX-MS-Exchange-Organization-MessageDirectionality: Originating\r\nX-MS-Exchange-Organization-AuthSource: AM9PR02MB6642.eurprd02.prod.outlook.com\r\nX-MS-Exchange-Organization-AuthAs: Internal\r\nX-MS-Exchange-Organization-AuthMechanism: 04\r\nX-MS-Exchange-Organization-Network-Message-Id:\r\n 178907c2-2871-4221-a75a-08dbd57c4bf2\r\nX-MS-PublicTrafficType: Email\r\nX-MS-TrafficTypeDiagnostic:\r\n AM9PR02MB6642:EE_|DB8PR02MB5883:EE_|AM6PR02MB4215:EE_\r\nReturn-Path: joe.prosser@uipath.com\r\nX-MS-Exchange-Organization-ExpirationStartTime: 25 Oct 2023 17:03:23.2098\r\n (UTC)\r\nX-MS-Exchange-Organization-ExpirationStartTimeReason: OriginalSubmit\r\nX-MS-Exchange-Organization-ExpirationInterval: 1:00:00:00.0000000\r\nX-MS-Exchange-Organization-ExpirationIntervalReason: OriginalSubmit\r\nX-MS-Office365-Filtering-Correlation-Id: 178907c2-2871-4221-a75a-08dbd57c4bf2\r\nX-MS-Exchange-AtpMessageProperties: SA|SL\r\nX-Microsoft-Antispam: BCL:0;\r\nX-Forefront-Antispam-Report:\r\n CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:AM9PR02MB6642.eurprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:;DIR:INT;\r\nX-MS-Exchange-CrossTenant-OriginalArrivalTime: 25 Oct 2023 17:03:22.9433\r\n (UTC)\r\nX-MS-Exchange-CrossTenant-FromEntityHeader: Hosted\r\nX-MS-Exchange-CrossTenant-Id: d8353d2a-b153-4d17-8827-902c51f72357\r\nX-MS-Exchange-CrossTenant-AuthSource: AM9PR02MB6642.eurprd02.prod.outlook.com\r\nX-MS-Exchange-CrossTenant-AuthAs: Internal\r\nX-MS-Exchange-CrossTenant-Network-Message-Id: 178907c2-2871-4221-a75a-08dbd57c4bf2\r\nX-MS-Exchange-CrossTenant-MailboxType: HOSTED\r\nX-MS-Exchange-CrossTenant-UserPrincipalName: ++FDvUGfWQ/f3Ycjz3vJlna939o3V0zI8K8BPWCQ7aaQDhAJYe5XMf5Tr6B6e/5ucWri5/sUCnvrb1GDymXZmA==\r\nX-MS-Exchange-Transport-CrossTenantHeadersStamped: DB8PR02MB5883\r\nX-MS-Exchange-Transport-EndToEndLatency: 00:00:12.3444456\r\nX-MS-Exchange-Processed-By-BccFoldering: 15.20.6907.032\r\nX-Microsoft-Antispam-Mailbox-Delivery:\r\n\tucf:0;jmr:0;auth:0;dest:I;ENG:(910001)(944506478)(944626604)(920097)(425001)(930097)(140003)(1420103);\r\nX-Microsoft-Antispam-Message-Info:\r\n\thxMOcmClDYIwXiLHDHfW3mj4bIuPkLWLYD9z839jTLrsdaQbyDGWXui95ou9iKuUSUHzubxtiSwgj9OVTVInYaJ6SuN1imGY/n99Cn1vAx4VAh1xSYBUsm2bHu5atiwnMZTQaWg7BA03Yj6BSrOYqh49oLJrh9blZmtlkMs4uCi1Y5Y7YpEMpzLi7ye2fg+gPqELrNLHIKfziazCdrPNLKQ+9tlbb6UHbxeX1YVGY2ebnYXPwRilEP++cljm3hV8abvlgC1GF+nsuIS5XJwhkHmgTyetr7iZE3GWR9XC0NsB9w5bQgg82r75ozlGKWVKkuev1IlRjStpKaJbOoPBvo/6SCqSbWaQinyUEfPDXiJOYHW3D0xsf4uCpbFpb1D+TpQr+dFaCxBNdmFjBrd/SCHoyyl9QAO1nz0W5cUAwjSDKN7Pv7iKIUlx24nkDCeYeVqKhNcqulwIlEP6ewBBL2BTSplNPApIbliiHhh/Z6mes1xx3dfB5T4tIUv6wINSH3G2Ddec2fLzeTHknuyaOA9Wj8ks+JIjE+i5/CPidxfH4ACoggwbdnLwwnwniGcNoZKdyM+G4whogPe2oKXPggX9er/44bUOlKEcK5DsPFEZX2xKzsg7JwPPLcgO/lbP2iN/yFww6I6vri27P29a86np21iNSGOb51giFj5wgSq4iZLeRe64cEj4+i4K1KosNLBgDYTj/WGrioHl5Xe9ww==\r\n"; let expected_body = "Hey, \r\n\r\nWe should check that attachments work to ✅\r\n\r\nJoe \r\n________________________________\r\n\r\nFrom: Joe Prosser \r\nSent: 25 October 2023 17:53\r\nTo: Andra Buica \r\nSubject: Re: Testing the CLI!! \r\n \r\nHey,\r\n\r\nFingers cross 🤞 \r\n\r\nJoe\r\n________________________________\r\n\r\nFrom: Andra Buica \r\nSent: 25 October 2023 17:52\r\nTo: Joe Prosser \r\nSubject: Re: Testing the CLI!! \r\n \r\n\r\nHey, \r\n\r\n \r\n\r\nHopefully it will work! \r\n\r\n \r\n\r\nAndra\r\n\r\n \r\n\r\nFrom: Joe Prosser \r\nDate: Wednesday, 25 October 2023 at 17:52\r\nTo: Andra Buica \r\nSubject: Testing the CLI!!\r\n\r\nHey,\r\n\r\n \r\n\r\nDo you think it will work?\r\n\r\n \r\n\r\nJoe \r\n\r\n"; From 08cc1fd53e6b183eeee70685d85cf143c901adcc Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Wed, 25 Oct 2023 23:21:37 +0100 Subject: [PATCH 4/7] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fe0ae2..8a6ff9b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Show Global Permissions in `get users` - Upgrade `ordered-float` version, which is exposed in the public crate api. - Add ability to filter users by project and permission +- Add feature to parse unicode msg files ## v0.19.0 From 958dd6043a9966bc9f3d885ca2d5f89e49d251cd Mon Sep 17 00:00:00 2001 From: Tom Milligan Date: Thu, 26 Oct 2023 09:09:12 +0100 Subject: [PATCH 5/7] [review] feedback --- cli/src/commands/parse/msgs.rs | 78 ++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/cli/src/commands/parse/msgs.rs b/cli/src/commands/parse/msgs.rs index e8995a6c..fc36fc00 100644 --- a/cli/src/commands/parse/msgs.rs +++ b/cli/src/commands/parse/msgs.rs @@ -129,23 +129,35 @@ fn read_unicode_stream_to_string( stream_path: PathBuf, compound_file: &mut CompoundFile, ) -> Result { - if !compound_file.is_stream(stream_path.clone()) { + if !compound_file.is_stream(&stream_path) { return Err(anyhow!( "Could not find stream {}. Please check that you are using unicode msgs", stream_path.to_string_lossy() )); } + // Stream data is a UTF16 string encoded as Vec[u8] let data = read_stream(stream_path, compound_file)?; + Ok(utf16le_stream_to_string(&data)) +} - // Stream data is a UTF16 string encoded as Vec[u8] - let mut buffer: String = String::with_capacity(data.len() * 2); +// Decode a UTF-16LE data stream, given as raw bytes of Vec +fn utf16le_stream_to_string(data: &[u8]) -> String { let mut decoder = encoding_rs::UTF_16LE.new_decoder(); - let (coder_result, _, _) = decoder.decode_to_string(&data, &mut buffer, true); - if coder_result != encoding_rs::CoderResult::InputEmpty { - Err(anyhow!("Unexpected coder result")) - } else { - Ok(buffer) + + // The amount of memory to reserve for writing at a time + // We should only require one or two blocks for the vast majority of cases + let block_length = data.len(); + let mut buffer: String = String::with_capacity(block_length); + + loop { + let (coder_result, _, _) = decoder.decode_to_string(data, &mut buffer, true); + use encoding_rs::CoderResult; + match coder_result { + // The output buffer was not big enough - increase and retry + CoderResult::OutputFull => buffer.reserve(block_length), + CoderResult::InputEmpty => return buffer, + } } } @@ -161,13 +173,13 @@ fn read_attachment( compound_file: &mut CompoundFile, ) -> Result { let mut attachment_name_path = attachment_path.clone(); - attachment_name_path.push(STREAM_PATH_ATTACHMENT_FILENAME.clone()); + attachment_name_path.push(&*STREAM_PATH_ATTACHMENT_FILENAME); let mut content_type_path = attachment_path.clone(); - content_type_path.push(STREAM_PATH_ATTACHMENT_EXTENSION.clone()); + content_type_path.push(&*STREAM_PATH_ATTACHMENT_EXTENSION); let mut data_path = attachment_path.clone(); - data_path.push(STREAM_PATH_ATTACHMENT_DATA.clone()); + data_path.push(&*STREAM_PATH_ATTACHMENT_DATA); let name = read_unicode_stream_to_string(attachment_name_path.clone(), compound_file)?; let content_type = read_unicode_stream_to_string(content_type_path, compound_file)?; @@ -184,12 +196,10 @@ fn remove_content_headers(headers_string: String) -> Result { let mut clean_headers_string: String; clean_headers_string = CONTENT_TYPE_MIME_HEADER_RX - .clone() .replace(&headers_string, "") .to_string(); clean_headers_string = CONTENT_TRANSFER_ENCODING_MIME_HEADER_RX - .clone() .replace(&clean_headers_string, "") .to_string(); @@ -271,12 +281,9 @@ fn upload_batch_of_documents( pub fn get_msgs_in_directory(directory: &PathBuf) -> Result> { Ok(std::fs::read_dir(directory)? .filter_map(|path| { - if let Ok(path) = path { - if !path.path().extension().is_some_and(|msg| msg == "msg") { - None - } else { - Some(path) - } + let path = path.ok()?; + if path.path().extension().is_some_and(|msg| msg == "msg") { + Some(path) } else { None } @@ -304,21 +311,27 @@ pub fn parse(client: &Client, args: &ParseMsgArgs) -> Result<()> { let mut documents = Vec::new(); let mut errors = Vec::new(); + + let send = |documents: &mut Vec| -> Result<()> { + upload_batch_of_documents( + client, + &source, + documents, + transform_tag, + *no_charge, + &statistics, + )?; + documents.clear(); + Ok(()) + }; + for path in msg_paths { match read_msg_to_document(&path.path()) { Ok(document) => { documents.push(document); if documents.len() >= UPLOAD_BATCH_SIZE { - upload_batch_of_documents( - client, - &source, - &documents, - transform_tag, - *no_charge, - &statistics, - )?; - documents.clear(); + send(&mut documents)?; } statistics.increment_processed(); } @@ -334,14 +347,7 @@ pub fn parse(client: &Client, args: &ParseMsgArgs) -> Result<()> { } } - upload_batch_of_documents( - client, - &source, - &documents, - transform_tag, - *no_charge, - &statistics, - )?; + send(&mut documents)?; for error in errors { error!("{}", error); From 58f1621bc10d6bb9ae85c68d9d6195b7069357ee Mon Sep 17 00:00:00 2001 From: Tom Milligan Date: Thu, 26 Oct 2023 09:35:20 +0100 Subject: [PATCH 6/7] [review] more PathBuf -> Path --- cli/src/commands/parse/msgs.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/cli/src/commands/parse/msgs.rs b/cli/src/commands/parse/msgs.rs index fc36fc00..ebbe731c 100644 --- a/cli/src/commands/parse/msgs.rs +++ b/cli/src/commands/parse/msgs.rs @@ -18,7 +18,10 @@ use reinfer_client::{ }, Client, PropertyMap, Source, SourceIdentifier, TransformTag, }; -use std::{fs::File, path::PathBuf}; +use std::{ + fs::File, + path::{Path, PathBuf}, +}; use structopt::StructOpt; use crate::{ @@ -114,7 +117,7 @@ pub struct ParseMsgArgs { yes: bool, } -fn read_stream(stream_path: PathBuf, compound_file: &mut CompoundFile) -> Result> { +fn read_stream(stream_path: &Path, compound_file: &mut CompoundFile) -> Result> { let data = { let mut stream = compound_file.open_stream(stream_path)?; let mut buffer = Vec::new(); @@ -126,7 +129,7 @@ fn read_stream(stream_path: PathBuf, compound_file: &mut CompoundFile) -> } fn read_unicode_stream_to_string( - stream_path: PathBuf, + stream_path: &Path, compound_file: &mut CompoundFile, ) -> Result { if !compound_file.is_stream(&stream_path) { @@ -181,9 +184,9 @@ fn read_attachment( let mut data_path = attachment_path.clone(); data_path.push(&*STREAM_PATH_ATTACHMENT_DATA); - let name = read_unicode_stream_to_string(attachment_name_path.clone(), compound_file)?; - let content_type = read_unicode_stream_to_string(content_type_path, compound_file)?; - let data = read_stream(data_path, compound_file)?; + let name = read_unicode_stream_to_string(&attachment_name_path, compound_file)?; + let content_type = read_unicode_stream_to_string(&content_type_path, compound_file)?; + let data = read_stream(&data_path, compound_file)?; Ok(AttachmentMetadata { name, @@ -215,13 +218,13 @@ fn read_msg_to_document(path: &PathBuf) -> Result { // Headers let headers_string = - read_unicode_stream_to_string(STREAM_PATH_MESSAGE_HEADER.clone(), &mut compound_file)?; + read_unicode_stream_to_string(&*STREAM_PATH_MESSAGE_HEADER, &mut compound_file)?; // As the content type won't match the parsed value from the body in the msg let headers_string_no_content_headers = remove_content_headers(headers_string)?; let plain_body_string = - read_unicode_stream_to_string(STREAM_PATH_MESSAGE_BODY_PLAIN.clone(), &mut compound_file)?; + read_unicode_stream_to_string(&*STREAM_PATH_MESSAGE_BODY_PLAIN, &mut compound_file)?; // Attachments let mut attachment_number = 0; @@ -229,7 +232,7 @@ fn read_msg_to_document(path: &PathBuf) -> Result { loop { let attachment_path = get_attachment_store_path(attachment_number); - if compound_file.is_storage(attachment_path.clone()) { + if compound_file.is_storage(&attachment_path) { attachments.push(read_attachment(attachment_path, &mut compound_file)?); } else { break; From 944f509df5d47b32a5804fe09370d1d7c7aebfc1 Mon Sep 17 00:00:00 2001 From: Joe Prosser Date: Thu, 26 Oct 2023 10:23:51 +0100 Subject: [PATCH 7/7] fix clippy --- cli/src/commands/parse/msgs.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/commands/parse/msgs.rs b/cli/src/commands/parse/msgs.rs index ebbe731c..9202ac76 100644 --- a/cli/src/commands/parse/msgs.rs +++ b/cli/src/commands/parse/msgs.rs @@ -132,7 +132,7 @@ fn read_unicode_stream_to_string( stream_path: &Path, compound_file: &mut CompoundFile, ) -> Result { - if !compound_file.is_stream(&stream_path) { + if !compound_file.is_stream(stream_path) { return Err(anyhow!( "Could not find stream {}. Please check that you are using unicode msgs", stream_path.to_string_lossy() @@ -218,13 +218,13 @@ fn read_msg_to_document(path: &PathBuf) -> Result { // Headers let headers_string = - read_unicode_stream_to_string(&*STREAM_PATH_MESSAGE_HEADER, &mut compound_file)?; + read_unicode_stream_to_string(&STREAM_PATH_MESSAGE_HEADER, &mut compound_file)?; // As the content type won't match the parsed value from the body in the msg let headers_string_no_content_headers = remove_content_headers(headers_string)?; let plain_body_string = - read_unicode_stream_to_string(&*STREAM_PATH_MESSAGE_BODY_PLAIN, &mut compound_file)?; + read_unicode_stream_to_string(&STREAM_PATH_MESSAGE_BODY_PLAIN, &mut compound_file)?; // Attachments let mut attachment_number = 0;