Skip to content

Commit

Permalink
add old csv format
Browse files Browse the repository at this point in the history
  • Loading branch information
janstarke committed Jun 12, 2024
1 parent ff0a5cf commit c286fbd
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 1 deletion.
9 changes: 8 additions & 1 deletion src/bin/mactime2/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use clap::ValueEnum;
use clio::Input;
use strum_macros::Display;

use crate::output::OldCsvOutput;

use super::bodyfile::{BodyfileDecoder, BodyfileReader, BodyfileSorter};
use super::cli::Cli;
use super::error::MactimeError;
Expand Down Expand Up @@ -38,6 +40,7 @@ pub (crate) enum OutputFormat {

pub struct Mactime2Application {
format: OutputFormat,
old_csv: bool,
bodyfile: Input,
dst_zone: Tz,
strict_mode: bool,
Expand All @@ -59,7 +62,10 @@ impl Mactime2Application {
BodyfileSorter::default().with_receiver(decoder.get_receiver(), options);

sorter = sorter.with_output(match self.format {
OutputFormat::Csv => Box::new(CsvOutput::new(std::io::stdout(), self.dst_zone)),
OutputFormat::Csv => if self.old_csv {
Box::new(OldCsvOutput::new(std::io::stdout(), self.dst_zone))
} else {
Box::new(CsvOutput::new(std::io::stdout(), self.dst_zone))}
OutputFormat::Txt => Box::new(TxtOutput::new(std::io::stdout(), self.dst_zone)),
_ => panic!("invalid execution path"),
});
Expand Down Expand Up @@ -101,6 +107,7 @@ impl From<Cli> for Mactime2Application {

Self {
format,
old_csv: cli.old_csv,
bodyfile: cli.input_file,
dst_zone: cli.dst_zone.into_tz().unwrap(),
strict_mode: cli.strict_mode,
Expand Down
10 changes: 10 additions & 0 deletions src/bin/mactime2/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,18 @@ pub struct Cli {
/// output as CSV instead of TXT. This is a convenience option, which is identical to `--format=csv`
/// and will be removed in a future release. If you specified `--format` and `-d`, the latter will be ignored.
#[clap(short('d'), display_order(610))]
#[arg(group="csv")]
pub(crate) csv_format: bool,

/// use the old CSV format that was used by legacy mactime.
///
/// Keep in mind that in this format, fields which contain commas will
/// not be wrapped by quotes, as RFC4180 requires it. So, this format
/// is not RFC4180-compliant, which means that you might not be able
/// to use the output together with csv processing tools.
#[clap(long("old-csv"), display_order(615))]
pub(crate) old_csv: bool,

/// output as JSON instead of TXT. This is a convenience option, which is identical to `--format=json`
/// and will be removed in a future release. If you specified `--format` and `-j`, the latter will be ignored.
#[clap(short('j'), display_order(620))]
Expand Down
2 changes: 2 additions & 0 deletions src/bin/mactime2/output/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod csv_output;
mod old_csv_output;
mod txt_output;
mod json_sorter;

pub (crate) use csv_output::*;
pub (crate) use old_csv_output::*;
pub (crate) use txt_output::*;
pub (crate) use json_sorter::*;
129 changes: 129 additions & 0 deletions src/bin/mactime2/output/old_csv_output.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use std::io::Write;

use chrono_tz::Tz;
use dfir_toolkit::common::ForensicsTimestamp;

use crate::bodyfile::{ListEntry, Mactime2Writer};

pub(crate) struct OldCsvOutput<W>
where
W: Write + Send,
{
dst_zone: Tz,
writer: W,
}

impl<W> OldCsvOutput<W>
where
W: Write + Send,
{
pub fn new(writer: W, dst_zone: Tz) -> Self {
Self { dst_zone, writer }
}
}

impl<W> Mactime2Writer<W> for OldCsvOutput<W>
where
W: Write + Send,
{
fn write_line(&mut self, timestamp: &i64, entry: &ListEntry) -> std::io::Result<()> {
let timestamp = ForensicsTimestamp::from(*timestamp).with_timezone(self.dst_zone);
write!(
self.writer,
"{},{},{},{},{},{},{},\"{}\"",
timestamp,
entry.line.get_size(),
entry.flags,
entry.line.get_mode_as_string(),
entry.line.get_uid(),
entry.line.get_gid(),
entry.line.get_inode(),
entry.line.get_name()
)
}

fn into_writer(self) -> W {
self.writer
}
}

#[cfg(test)]
mod tests {
use crate::bodyfile::ListEntry;
use crate::bodyfile::MACBFlags;
use crate::bodyfile::Mactime2Writer;

use super::OldCsvOutput;
use chrono::DateTime;
use chrono_tz::Tz;
use chrono_tz::TZ_VARIANTS;
use dfir_toolkit::common::bodyfile::Bodyfile3Line;
use std::io::Cursor;
use std::io::{BufRead, BufReader};
use std::sync::Arc;

fn random_tz() -> Tz {
let index = rand::random::<usize>() % TZ_VARIANTS.len();
TZ_VARIANTS[index]
}

#[allow(non_snake_case)]
#[test]
fn test_correct_ts_UTC() {
for _ in 1..10 {
let unix_ts = rand::random::<u32>() as i64;
let bf_line = Bodyfile3Line::new().with_crtime(unix_ts.into());
let entry = ListEntry {
flags: MACBFlags::B,
line: Arc::new(bf_line),
};

let mut output = OldCsvOutput::new(Cursor::new(vec![]), Tz::UTC);

output.write_line(&unix_ts, &entry).unwrap();
let mut output = BufReader::new(Cursor::new(output.into_writer().into_inner())).lines();
let out_line = output.next().unwrap().unwrap();

let out_ts = out_line.split(',').next().unwrap();
let rfc3339 = DateTime::parse_from_rfc3339(out_ts)
.expect(out_ts)
.timestamp();
assert_eq!(
unix_ts, rfc3339,
"Timestamp {unix_ts} converted to '{out_ts}' and back to {rfc3339}",
);
}
}

#[allow(non_snake_case)]
#[test]
fn test_correct_ts_random_tz() -> Result<(), String> {
for _ in 1..100 {
let tz = random_tz();
let mut output = OldCsvOutput::new(Cursor::new(vec![]), tz);

let unix_ts = rand::random::<u32>() as i64;
let bf_line = Bodyfile3Line::new().with_crtime(unix_ts.into());
let entry = ListEntry {
flags: MACBFlags::B,
line: Arc::new(bf_line),
};

output.write_line(&unix_ts, &entry).unwrap();
let mut output = BufReader::new(Cursor::new(output.into_writer().into_inner())).lines();
let out_line = output.next().unwrap().unwrap();

let out_ts = out_line.split(',').next().unwrap();
let rfc3339 = match DateTime::parse_from_rfc3339(out_ts) {
Ok(ts) => ts,
Err(e) => return Err(format!("error while parsing '{}': {}", out_ts, e)),
};
let calculated_ts = rfc3339.timestamp();
assert_eq!(
unix_ts, calculated_ts,
"Timestamp {unix_ts} converted to '{out_ts}' and back to {calculated_ts}",
);
}
Ok(())
}
}

0 comments on commit c286fbd

Please sign in to comment.