2022-01-22 16:41:24 +00:00
|
|
|
// SPDX-FileCopyrightText: 2021-2022 Lynnesbian
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
2021-10-05 14:24:08 +00:00
|
|
|
|
2021-10-04 14:18:42 +00:00
|
|
|
use std::collections::{BTreeMap, HashMap};
|
2021-09-24 08:11:25 +00:00
|
|
|
use std::ffi::OsStr;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
2022-01-01 03:28:45 +00:00
|
|
|
use clap::Parser;
|
2021-09-24 14:53:35 +00:00
|
|
|
use fif::files::{mime_extension_lookup, scan_directory, scan_from_walkdir, BUF_SIZE};
|
2021-08-28 07:54:01 +00:00
|
|
|
use fif::findings::Findings;
|
|
|
|
use fif::formats::{Format, PowerShell, Shell};
|
|
|
|
use fif::mime_db::MimeDb;
|
2021-10-05 16:36:09 +00:00
|
|
|
use fif::utils::APPLICATION_ZIP;
|
2021-09-24 14:53:35 +00:00
|
|
|
use fif::{String, MIMEDB};
|
2021-10-04 18:45:05 +00:00
|
|
|
use itertools::Itertools;
|
2021-10-04 14:18:42 +00:00
|
|
|
use maplit::{btreeset, hashmap};
|
2021-08-06 13:33:42 +00:00
|
|
|
use mime::{Mime, APPLICATION_OCTET_STREAM, APPLICATION_PDF, IMAGE_JPEG, IMAGE_PNG};
|
2021-04-02 17:51:49 +00:00
|
|
|
|
2021-04-28 09:33:42 +00:00
|
|
|
use crate::parameters::ExtensionSet;
|
2021-09-24 08:11:25 +00:00
|
|
|
use crate::parameters::Parameters;
|
2021-02-28 09:47:18 +00:00
|
|
|
|
|
|
|
const JPEG_BYTES: &[u8] = b"\xFF\xD8\xFF";
|
|
|
|
const PNG_BYTES: &[u8] = b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
|
|
|
|
const PDF_BYTES: &[u8] = b"%PDF-";
|
|
|
|
const ZIP_BYTES: &[u8] = b"PK\x03\x04";
|
|
|
|
|
|
|
|
#[test]
|
2021-04-14 08:25:46 +00:00
|
|
|
/// Ensure that `extension_from_path` successfully returns the extension from a set of paths.
|
2021-02-28 09:47:18 +00:00
|
|
|
fn get_ext() {
|
2021-10-04 14:18:42 +00:00
|
|
|
let ext_checks: HashMap<_, Option<&OsStr>> = hashmap![
|
2021-11-24 20:40:34 +00:00
|
|
|
Path::new("test.txt") => Some(OsStr::new("txt")),
|
|
|
|
Path::new("test.zip") => Some(OsStr::new("zip")),
|
|
|
|
Path::new("test.tar.gz") => Some(OsStr::new("gz")),
|
|
|
|
Path::new("test.") => Some(OsStr::new("")),
|
|
|
|
Path::new("test") => None,
|
|
|
|
Path::new(".hidden") => None,
|
|
|
|
];
|
2021-02-28 09:47:18 +00:00
|
|
|
|
|
|
|
for (path, ext) in ext_checks {
|
2021-07-31 13:33:18 +00:00
|
|
|
assert_eq!(path.extension(), ext);
|
2021-02-28 09:47:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-11-24 20:29:27 +00:00
|
|
|
/// Ensure that the MIME types for JPEG, PNG, PDF, and ZIP are detected from their magic numbers.
|
2021-02-28 09:47:18 +00:00
|
|
|
fn detect_type() {
|
2021-09-24 14:53:35 +00:00
|
|
|
assert_eq!(MIMEDB.get_type(JPEG_BYTES), Some(IMAGE_JPEG));
|
|
|
|
assert_eq!(MIMEDB.get_type(PNG_BYTES), Some(IMAGE_PNG));
|
|
|
|
assert_eq!(MIMEDB.get_type(PDF_BYTES), Some(APPLICATION_PDF));
|
2021-10-05 16:36:09 +00:00
|
|
|
assert_eq!(MIMEDB.get_type(ZIP_BYTES), Some(APPLICATION_ZIP.clone()));
|
2021-02-28 09:47:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-04-14 08:25:46 +00:00
|
|
|
/// Ensure that `mime_extension_lookup` works as expected, and that the set of extensions for JPEG, PNG, PDF, and ZIP
|
|
|
|
/// contain "jpg", "png", "pdf", and "zip", respectively.
|
2021-02-28 09:47:18 +00:00
|
|
|
fn recommend_ext() {
|
2021-11-24 20:40:34 +00:00
|
|
|
let tests = hashmap![
|
|
|
|
&IMAGE_JPEG => "jpg",
|
|
|
|
&IMAGE_PNG => "png",
|
|
|
|
&APPLICATION_PDF => "pdf",
|
2021-11-24 22:45:20 +00:00
|
|
|
&*APPLICATION_ZIP => "zip",
|
2021-11-24 20:40:34 +00:00
|
|
|
];
|
|
|
|
|
2021-11-24 22:45:20 +00:00
|
|
|
for (mime, ext) in tests {
|
2021-11-24 20:40:34 +00:00
|
|
|
assert!(
|
|
|
|
mime_extension_lookup(mime.essence_str().into())
|
|
|
|
.unwrap()
|
|
|
|
.contains(&String::from(ext)),
|
|
|
|
"mime_extension_lookup for {} didn't contain {}!",
|
|
|
|
mime.essence_str(),
|
|
|
|
ext
|
|
|
|
);
|
|
|
|
}
|
2021-02-28 09:47:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-04-14 08:25:46 +00:00
|
|
|
/// Create a simple directory with some files, run `scan_directory` on it, and ensure that the files have their
|
2021-11-24 20:29:27 +00:00
|
|
|
/// associated MIME types correctly deduced.
|
2021-02-28 09:47:18 +00:00
|
|
|
fn simple_directory() {
|
2021-04-14 08:25:46 +00:00
|
|
|
use std::borrow::Borrow;
|
2021-06-18 05:17:30 +00:00
|
|
|
use std::env::set_current_dir;
|
2021-06-18 05:36:05 +00:00
|
|
|
use std::fs::{canonicalize, File};
|
|
|
|
use std::io::Write;
|
2021-09-24 08:11:25 +00:00
|
|
|
|
2021-02-28 09:47:18 +00:00
|
|
|
use tempfile::tempdir;
|
|
|
|
|
2021-09-24 08:11:25 +00:00
|
|
|
use crate::parameters::ScanOpts;
|
|
|
|
|
2021-04-14 08:25:46 +00:00
|
|
|
// set of files to scan. all but the last files have magic numbers corresponding to their extension, except for
|
|
|
|
// "wrong.jpg", which is actually a png.
|
2021-10-04 14:18:42 +00:00
|
|
|
let files = hashmap![
|
|
|
|
"test.jpg" => JPEG_BYTES,
|
|
|
|
"test.jpeg" => JPEG_BYTES,
|
|
|
|
"test.png" => PNG_BYTES,
|
|
|
|
"test.pdf" => PDF_BYTES,
|
|
|
|
"test.zip" => ZIP_BYTES,
|
|
|
|
"wrong.jpg" => PNG_BYTES,
|
|
|
|
"ignore.fake_ext" => ZIP_BYTES,
|
|
|
|
];
|
2021-02-28 09:47:18 +00:00
|
|
|
|
|
|
|
let dir = tempdir().expect("Failed to create temporary directory.");
|
2021-06-18 05:17:30 +00:00
|
|
|
set_current_dir(dir.path()).expect("Failed to change directory.");
|
2021-02-28 09:47:18 +00:00
|
|
|
|
|
|
|
for (name, bytes) in &files {
|
|
|
|
let mut file = File::create(dir.path().join(name)).expect(&*format!("Failed to create file: {}", name));
|
|
|
|
|
|
|
|
file
|
|
|
|
.write_all(bytes)
|
|
|
|
.expect(&*format!("Failed to write to file: {}", name));
|
|
|
|
drop(file);
|
|
|
|
}
|
|
|
|
|
2021-03-11 17:44:31 +00:00
|
|
|
let scan_opts = ScanOpts {
|
2021-03-02 15:12:29 +00:00
|
|
|
hidden: true,
|
2021-03-11 17:44:31 +00:00
|
|
|
extensionless: false,
|
2021-04-04 13:52:16 +00:00
|
|
|
follow_symlinks: false,
|
2021-08-06 14:12:26 +00:00
|
|
|
ignore_unknown_exts: true,
|
2021-03-02 15:12:29 +00:00
|
|
|
};
|
|
|
|
|
2021-05-08 00:10:51 +00:00
|
|
|
let entries = scan_directory(dir.path(), None, None, &scan_opts).expect("Directory scan failed.");
|
2021-02-28 09:47:18 +00:00
|
|
|
|
2021-08-06 14:12:26 +00:00
|
|
|
// there should be one file missing: "ignore.fake_ext"
|
|
|
|
assert_eq!(entries.len(), files.len() - 1);
|
2021-02-28 09:47:18 +00:00
|
|
|
|
2021-09-25 08:55:50 +00:00
|
|
|
let use_threads = cfg!(feature = "multi-threaded");
|
|
|
|
|
2021-10-04 10:22:15 +00:00
|
|
|
let results = scan_from_walkdir(&entries, false, use_threads).0;
|
|
|
|
let canonical_results = scan_from_walkdir(&entries, true, use_threads).0;
|
2021-06-18 05:17:30 +00:00
|
|
|
assert_eq!(results.len(), canonical_results.len());
|
|
|
|
|
|
|
|
for (result, canonical_result) in results.iter().zip(canonical_results.iter()) {
|
2021-04-14 08:25:46 +00:00
|
|
|
// there should be no IO errors during this test. any IO errors encountered are outside the scope of this test.
|
2021-06-18 05:17:30 +00:00
|
|
|
|
|
|
|
// paths should be canonical
|
|
|
|
assert_eq!(canonicalize(&result.file).unwrap(), canonical_result.file);
|
2021-02-28 12:16:54 +00:00
|
|
|
|
2021-02-28 09:47:18 +00:00
|
|
|
if !result.valid {
|
2021-04-14 08:25:46 +00:00
|
|
|
// the only invalid file detected should be "wrong.jpg", which is a misnamed png file
|
|
|
|
// 1. ensure detected extension is "jpg"
|
2021-07-31 13:33:18 +00:00
|
|
|
assert_eq!(result.file.as_path().extension().unwrap(), OsStr::new("jpg"));
|
2021-11-24 20:29:27 +00:00
|
|
|
// 2. ensure detected MIME type is IMAGE_PNG
|
2021-02-28 09:47:18 +00:00
|
|
|
assert_eq!(result.mime, IMAGE_PNG);
|
2021-04-14 08:25:46 +00:00
|
|
|
// 3. ensure the recommended extension for "wrong.jpg" is "png"
|
|
|
|
assert_eq!(&result.recommended_extension().unwrap(), &String::from("png"));
|
2021-10-04 13:33:48 +00:00
|
|
|
// 4. ensure the recommended filename for "wrong.jpg" is "wrong.png"
|
2021-11-05 15:30:34 +00:00
|
|
|
assert_eq!(result.recommended_path().unwrap().file_name(), Some(OsStr::new("wrong.png")));
|
2021-02-28 09:47:18 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-11-24 20:29:27 +00:00
|
|
|
// check if the recommended extension for this file is in the list of known extensions for its MIME type - for
|
2021-04-14 08:25:46 +00:00
|
|
|
// example, if the file is determined to be an IMAGE_PNG, its recommended extension should be one of the extensions
|
|
|
|
// returned by `mime_extension_lookup(IMAGE_PNG)`.
|
2021-05-07 14:32:44 +00:00
|
|
|
assert!(mime_extension_lookup(result.mime.essence_str().into())
|
2021-02-28 14:06:05 +00:00
|
|
|
.unwrap()
|
2021-03-25 18:46:07 +00:00
|
|
|
.contains(&result.recommended_extension().unwrap()));
|
2021-02-28 12:16:54 +00:00
|
|
|
|
2021-10-04 13:33:48 +00:00
|
|
|
// ensure that the recommended_name function outputs something beginning with "test"
|
|
|
|
assert!(result
|
|
|
|
.recommended_path()
|
|
|
|
.unwrap()
|
|
|
|
.file_name()
|
|
|
|
.unwrap()
|
|
|
|
.to_string_lossy()
|
|
|
|
.starts_with("test"));
|
|
|
|
|
2021-11-24 20:29:27 +00:00
|
|
|
// make sure the guessed MIME type is correct based on the extension of the scanned file
|
|
|
|
// because we already know that the extensions match the MIME type (as we created these files ourselves earlier in
|
2021-04-14 08:25:46 +00:00
|
|
|
// the test), all files with the "jpg" extension should be IMAGE_JPEGs, etc.
|
2021-07-31 13:33:18 +00:00
|
|
|
let ext = result.file.as_path().extension().unwrap();
|
2021-02-28 09:47:18 +00:00
|
|
|
assert_eq!(
|
|
|
|
result.mime,
|
2021-07-31 13:33:18 +00:00
|
|
|
match ext.to_string_lossy().borrow() {
|
2021-03-25 18:46:07 +00:00
|
|
|
"jpg" | "jpeg" => IMAGE_JPEG,
|
|
|
|
"png" => IMAGE_PNG,
|
|
|
|
"pdf" => APPLICATION_PDF,
|
2021-10-05 16:36:09 +00:00
|
|
|
"zip" => APPLICATION_ZIP.clone(),
|
2021-03-25 18:46:07 +00:00
|
|
|
_ => APPLICATION_OCTET_STREAM, // general "fallback" type
|
2021-04-08 13:33:33 +00:00
|
|
|
},
|
|
|
|
"Incorrect MIME type detected - got {:?} for a {:?} file",
|
|
|
|
result.mime,
|
2021-07-31 13:33:18 +00:00
|
|
|
ext
|
2021-02-28 09:47:18 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2021-02-28 12:16:54 +00:00
|
|
|
|
|
|
|
#[test]
|
2021-04-14 08:25:46 +00:00
|
|
|
/// Ensure that command line argument parsing works correctly - flags are interpreted, booleans are set, and so on.
|
2021-02-28 12:16:54 +00:00
|
|
|
fn argument_parsing() {
|
2021-04-28 06:16:32 +00:00
|
|
|
use crate::parameters::ScanOpts;
|
2021-02-28 12:16:54 +00:00
|
|
|
|
2021-04-04 13:52:16 +00:00
|
|
|
// pass `-f`, which enables following symlinks, and `-E images`, which scans files with image extensions
|
|
|
|
let args: Parameters = Parameters::parse_from(vec!["fif", "-f", "-E", "images"]);
|
|
|
|
|
|
|
|
// check if "jpg" is in the list of extensions to be scanned
|
2021-04-27 10:26:01 +00:00
|
|
|
assert!(
|
|
|
|
args
|
|
|
|
.extensions()
|
|
|
|
.expect("args.extensions() should be Some(_)!")
|
|
|
|
.contains(&"jpg"),
|
2021-04-27 10:25:41 +00:00
|
|
|
"args.extensions() should contain the `images` set!"
|
|
|
|
);
|
2021-02-28 12:16:54 +00:00
|
|
|
|
|
|
|
// make sure "scan_hidden" is false
|
|
|
|
assert!(!args.scan_hidden);
|
|
|
|
|
|
|
|
// exts should be none
|
|
|
|
assert!(args.exts.is_none());
|
2021-04-04 13:52:16 +00:00
|
|
|
|
2021-04-27 10:25:41 +00:00
|
|
|
// there shouldn't be any excluded extensions
|
|
|
|
assert!(args.excluded_extensions().is_none());
|
|
|
|
|
2021-04-04 13:52:16 +00:00
|
|
|
// get the ScanOpts, and make sure they match expectations
|
|
|
|
assert_eq!(
|
|
|
|
args.get_scan_opts(),
|
|
|
|
ScanOpts {
|
|
|
|
hidden: false,
|
|
|
|
extensionless: false,
|
2021-04-20 05:20:10 +00:00
|
|
|
follow_symlinks: true,
|
2021-08-06 14:12:26 +00:00
|
|
|
ignore_unknown_exts: false,
|
2021-04-08 13:33:33 +00:00
|
|
|
},
|
|
|
|
"ScanOpts are incorrect"
|
2021-06-14 06:53:41 +00:00
|
|
|
);
|
2021-02-28 14:06:05 +00:00
|
|
|
}
|
2021-03-25 18:46:07 +00:00
|
|
|
|
2021-04-28 08:09:44 +00:00
|
|
|
#[test]
|
|
|
|
/// Ensure that `fif -e jpg dir` is interpreted as "scan for jpg files in dir" and not "scan for jpg and dir files"
|
|
|
|
fn positional_args() {
|
|
|
|
for flag in &["-x", "-e", "-X", "-E"] {
|
|
|
|
assert_eq!(
|
2021-05-08 00:10:51 +00:00
|
|
|
Parameters::parse_from(vec!["fif", flag, "images", "directory"]).dir,
|
2021-04-28 08:09:44 +00:00
|
|
|
PathBuf::from("directory")
|
2021-06-14 06:53:41 +00:00
|
|
|
);
|
2021-04-28 08:09:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-27 10:25:41 +00:00
|
|
|
#[test]
|
2021-04-28 06:16:32 +00:00
|
|
|
/// Ensure the `exclude` flag (`-x`) overrides `-e` and `-E`.
|
2021-04-27 10:25:41 +00:00
|
|
|
fn exclude_overrides() {
|
|
|
|
// pass `-E images`, which includes many image extensions, and `-x jpg,png`, which should remove "jpg" and "png" from
|
|
|
|
// the extensions list
|
|
|
|
let args: Parameters = Parameters::parse_from(vec!["fif", "-x", "jpg,png", "-E", "images"]);
|
|
|
|
let extensions = args.extensions();
|
|
|
|
assert!(extensions.is_some(), "Extensions should contain the `images` set!");
|
|
|
|
let extensions = extensions.unwrap();
|
|
|
|
|
|
|
|
assert!(!extensions.contains(&"jpg"), "\"jpg\" should be excluded!");
|
|
|
|
assert!(!extensions.contains(&"png"), "\"png\" should be excluded!");
|
|
|
|
assert!(extensions.contains(&"jpeg"), "\"jpeg\" should be included!");
|
|
|
|
|
|
|
|
// pass `-e abc,def,ghi,jkl` and `-x abc,def` -- extensions() should only contain "ghi" and "jkl"
|
|
|
|
let args: Parameters = Parameters::parse_from(vec!["fif", "-e", "abc,def,ghi,jkl", "-x", "abc,def"]);
|
|
|
|
let extensions = args.extensions();
|
|
|
|
assert!(extensions.is_some(), "Extensions should be set!");
|
2021-10-04 14:18:42 +00:00
|
|
|
assert_eq!(extensions, Some(btreeset!["ghi", "jkl"]));
|
2021-04-27 10:25:41 +00:00
|
|
|
}
|
|
|
|
|
2021-04-28 06:16:32 +00:00
|
|
|
#[test]
|
|
|
|
/// Ensure the `exclude_set` flag (`-X`) overrides `-e`.
|
|
|
|
fn exclude_set_overrides_includes() {
|
|
|
|
// pass `-e jpg,flac` and `-X images` -- which should produce the equivalent of `-e flag`
|
|
|
|
let args: Parameters = Parameters::parse_from(vec!["fif", "-e", "jpg,flac", "-X", "images"]);
|
|
|
|
let extensions = args.extensions();
|
|
|
|
assert!(extensions.is_some(), "Extensions should be set!");
|
2021-10-04 14:18:42 +00:00
|
|
|
assert_eq!(extensions, Some(btreeset!["flac"]));
|
2021-04-28 06:16:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
/// Ensure the `exclude_set` flag (`-X`) overrides `-E`.
|
|
|
|
fn exclude_set_overrides_include_set() {
|
2021-04-28 06:44:29 +00:00
|
|
|
// pass `-E media` and `-X images` -- which should produce the equivalent of `-E audio,video`
|
2021-04-28 06:16:32 +00:00
|
|
|
let args: Parameters = Parameters::parse_from(vec!["fif", "-E", "media", "-X", "images"]);
|
|
|
|
let extensions = args.extensions();
|
|
|
|
assert!(extensions.is_some(), "Extensions should be set!");
|
|
|
|
let extensions = extensions.unwrap();
|
|
|
|
|
|
|
|
// ensure all of audio and video's extensions are here
|
2021-04-28 09:33:42 +00:00
|
|
|
for &ext in ExtensionSet::Audio
|
|
|
|
.extensions()
|
|
|
|
.iter()
|
|
|
|
.chain(ExtensionSet::Video.extensions().iter())
|
|
|
|
{
|
2021-06-14 06:53:41 +00:00
|
|
|
assert!(extensions.contains(&ext), "Extensions should contain {}!", ext);
|
2021-04-28 06:16:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ensure all of images' extensions are excluded
|
|
|
|
for ext in ExtensionSet::Images.extensions() {
|
2021-06-14 06:53:41 +00:00
|
|
|
assert!(!extensions.contains(&ext), "Extensions should not contain {}!", ext);
|
2021-04-28 06:16:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-25 18:46:07 +00:00
|
|
|
#[test]
|
2021-04-14 08:25:46 +00:00
|
|
|
/// Ensure that badly formed command line arguments are rejected.
|
2021-03-25 18:46:07 +00:00
|
|
|
fn rejects_bad_args() {
|
2022-01-01 03:02:27 +00:00
|
|
|
use assert_cmd::Command;
|
2021-04-04 13:52:16 +00:00
|
|
|
let tests = [
|
|
|
|
// Non-existent flags:
|
|
|
|
vec!["fif", "-abcdefghijklmnopqrstuvwxyz"],
|
|
|
|
// `-E` without specifying a set:
|
|
|
|
vec!["fif", "-E"],
|
|
|
|
// `-E` with an invalid set:
|
|
|
|
vec!["fif", "-E", "pebis"],
|
2021-04-28 06:44:29 +00:00
|
|
|
// `-X` with an invalid set:
|
|
|
|
vec!["fif", "-X", "pebis"],
|
2021-04-04 13:52:16 +00:00
|
|
|
// `-e` with nothing but commas:
|
|
|
|
vec!["fif", "-e", ",,,,,"],
|
2022-01-01 03:28:45 +00:00
|
|
|
// `-x` with nothing but commas:
|
|
|
|
vec!["fif", "-x", ",,,,,"],
|
2021-09-25 08:55:50 +00:00
|
|
|
// `-j` with a negative value:
|
|
|
|
vec!["fif", "-j", "-1"],
|
2021-10-04 14:18:42 +00:00
|
|
|
// `--prompt` without `--fix`:
|
|
|
|
vec!["fif", "--prompt", "always"],
|
|
|
|
// `--overwrite` without `--fix`:
|
|
|
|
vec!["fif", "--overwrite"],
|
2021-04-04 13:52:16 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
for test in &tests {
|
2022-01-01 03:02:27 +00:00
|
|
|
// first, try testing the flags against the Parameters struct...
|
2021-04-04 13:52:16 +00:00
|
|
|
assert!(Parameters::try_parse_from(test).is_err(), "Failed to reject {:?}", test);
|
2022-01-01 03:02:27 +00:00
|
|
|
// ...then, make sure it actually works against the binary
|
|
|
|
let mut cmd = Command::cargo_bin("fif").unwrap();
|
|
|
|
cmd.args(test).assert().failure();
|
2021-04-04 13:52:16 +00:00
|
|
|
}
|
2021-03-25 18:46:07 +00:00
|
|
|
}
|
|
|
|
|
2022-01-01 03:02:27 +00:00
|
|
|
#[test]
|
|
|
|
/// Ensure that a few simple, well-formed command line argument cases pass
|
|
|
|
fn accepts_good_args() {
|
|
|
|
use assert_cmd::Command;
|
|
|
|
|
|
|
|
// all of these commands pass either the version or help flag, ensuring that they won't fail for reasons relating
|
|
|
|
// to filesystem access
|
|
|
|
let tests = [
|
|
|
|
vec!["-V"],
|
|
|
|
vec!["--version"],
|
|
|
|
vec!["-E", "images", "--version"],
|
|
|
|
vec!["-h"],
|
|
|
|
vec!["--help"],
|
|
|
|
vec!["dir_name", "--version"],
|
|
|
|
];
|
|
|
|
|
|
|
|
for test in &tests {
|
|
|
|
let mut cmd = Command::cargo_bin("fif").unwrap();
|
|
|
|
cmd.args(test).assert().success();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
/// Ensures that output from the `-V` and `--version` flags is formatted properly.
|
|
|
|
fn check_version_output() {
|
|
|
|
use std::string::String;
|
2022-01-18 08:44:46 +00:00
|
|
|
|
2022-01-01 03:02:27 +00:00
|
|
|
use assert_cmd::Command;
|
|
|
|
use regex::Regex;
|
|
|
|
|
|
|
|
// test `-V` matches the format of "fif x.y.z"
|
|
|
|
let mut cmd = Command::cargo_bin("fif").unwrap();
|
|
|
|
let output = cmd.arg("-V").ok().unwrap().stdout;
|
|
|
|
let output = String::from_utf8(output).unwrap();
|
|
|
|
assert!(
|
2022-01-01 03:28:45 +00:00
|
|
|
Regex::new(r#"fif v([0-9]\.){2}[0-9]"#).unwrap().is_match(output.trim()),
|
2022-01-01 03:02:27 +00:00
|
|
|
"\"{}\" does not match the expected `-v` format!",
|
|
|
|
output
|
|
|
|
);
|
|
|
|
|
|
|
|
// test `--version` matches the format of "fif x.y.z (OS, example backend, commit #1234abc)"
|
|
|
|
let mut cmd = Command::cargo_bin("fif").unwrap();
|
|
|
|
let output = cmd.arg("--version").ok().unwrap().stdout;
|
|
|
|
let output = String::from_utf8(output).unwrap();
|
|
|
|
assert!(
|
2022-01-01 03:28:45 +00:00
|
|
|
Regex::new(r#"fif v([0-9]\.){2}[0-9] \(.+, .+ backend, (unknown commit|commit #[[:xdigit:]]{7})\)"#)
|
2022-01-01 03:02:27 +00:00
|
|
|
.unwrap()
|
2022-01-01 03:04:07 +00:00
|
|
|
.is_match(output.trim()),
|
2022-01-01 03:02:27 +00:00
|
|
|
"\"{}\" does not match the expected `--version` format!",
|
|
|
|
output.trim()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-03-25 18:46:07 +00:00
|
|
|
#[test]
|
2021-04-14 08:25:46 +00:00
|
|
|
/// Generate random series of bytes and try to identify them. This test makes no assertions and can only fail if the
|
|
|
|
/// mime database somehow panics or hangs.
|
2021-03-25 18:46:07 +00:00
|
|
|
fn identify_random_bytes() {
|
2021-04-29 07:07:58 +00:00
|
|
|
use rand::RngCore;
|
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
let mut bytes: [u8; BUF_SIZE * 2] = [0; BUF_SIZE * 2];
|
2021-10-04 14:18:42 +00:00
|
|
|
let mut results: BTreeMap<Mime, i32> = BTreeMap::new();
|
2021-03-25 18:46:07 +00:00
|
|
|
|
2021-10-04 18:45:05 +00:00
|
|
|
for _ in 1..1000 {
|
2021-04-29 07:07:58 +00:00
|
|
|
rng.fill_bytes(&mut bytes);
|
2021-09-24 14:53:35 +00:00
|
|
|
if let Some(detected_type) = MIMEDB.get_type(&bytes) {
|
2021-03-25 18:46:07 +00:00
|
|
|
*results.entry(detected_type).or_insert(0) += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (mime, count) in &results {
|
|
|
|
println!("{}:\t{} counts", mime, count);
|
|
|
|
}
|
2021-08-28 07:59:04 +00:00
|
|
|
println!("No type found:\t{} counts", 1000 - results.values().sum::<i32>());
|
2021-03-25 18:46:07 +00:00
|
|
|
}
|
2021-04-08 13:33:33 +00:00
|
|
|
|
|
|
|
#[test]
|
2021-04-14 08:25:46 +00:00
|
|
|
/// Ensure that, for a given file "wrong.bad", which should have extension "good", the shell output contains something
|
|
|
|
/// like "mv wrong.bad wrong.good".
|
2021-04-08 13:33:33 +00:00
|
|
|
fn outputs_move_commands() {
|
2021-04-14 08:25:46 +00:00
|
|
|
use std::io::Read;
|
|
|
|
|
2021-04-08 13:33:33 +00:00
|
|
|
// create an example finding stating that "misnamed_file.png" has been identified as a jpeg file
|
2021-10-04 10:22:15 +00:00
|
|
|
let findings = vec![Findings {
|
2021-06-18 05:17:30 +00:00
|
|
|
file: Path::new("misnamed_file.png").to_path_buf(),
|
2021-04-08 13:33:33 +00:00
|
|
|
valid: false,
|
|
|
|
mime: IMAGE_JPEG,
|
2021-10-04 10:22:15 +00:00
|
|
|
}];
|
2021-05-05 23:06:05 +00:00
|
|
|
|
2021-05-05 22:57:42 +00:00
|
|
|
for format in &["Shell", "PowerShell"] {
|
2021-04-30 09:11:24 +00:00
|
|
|
let mut cursor = std::io::Cursor::new(Vec::new());
|
|
|
|
let mut contents = std::string::String::new();
|
|
|
|
|
|
|
|
match *format {
|
2021-10-04 10:22:15 +00:00
|
|
|
"Shell" => Shell.write_all(&mut cursor, &findings, &[]),
|
|
|
|
"PowerShell" => PowerShell.write_all(&mut cursor, &findings, &[]),
|
2021-05-05 23:06:05 +00:00
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
.expect("Failed to write to cursor");
|
2021-04-08 13:33:33 +00:00
|
|
|
|
2021-04-30 09:11:24 +00:00
|
|
|
cursor.set_position(0);
|
|
|
|
cursor
|
|
|
|
.read_to_string(&mut contents)
|
|
|
|
.expect("Failed to read from cursor to string");
|
|
|
|
|
|
|
|
// the output should contain a command like "mv -i misnamed_file.png misnamed_file.jpg"
|
|
|
|
assert!(
|
2021-08-06 14:12:26 +00:00
|
|
|
contents.contains("misnamed_file.jpg") && contents.contains("misnamed_file.png"),
|
2021-05-05 22:57:42 +00:00
|
|
|
"{} output doesn't contain move command!\n===\n{}",
|
|
|
|
format,
|
|
|
|
contents
|
2021-06-14 06:53:41 +00:00
|
|
|
);
|
2021-04-30 09:11:24 +00:00
|
|
|
}
|
2021-05-05 22:57:42 +00:00
|
|
|
}
|
2021-04-08 13:33:33 +00:00
|
|
|
|
2021-05-05 22:57:42 +00:00
|
|
|
#[test]
|
2021-05-09 23:44:03 +00:00
|
|
|
#[cfg(feature = "json")]
|
2021-05-05 22:57:42 +00:00
|
|
|
/// Ensure JSON output is valid.
|
|
|
|
fn test_json() {
|
2021-05-05 23:06:05 +00:00
|
|
|
use std::io::Read;
|
2021-09-24 08:11:25 +00:00
|
|
|
|
|
|
|
use crate::formats::Json;
|
2021-05-05 22:57:42 +00:00
|
|
|
// create an example finding stating that "misnamed_file.png" has been identified as a jpeg file
|
2021-10-04 10:22:15 +00:00
|
|
|
let findings = vec![Findings {
|
2021-06-18 05:17:30 +00:00
|
|
|
file: Path::new("misnamed_file.png").to_path_buf(),
|
2021-05-05 22:57:42 +00:00
|
|
|
valid: false,
|
|
|
|
mime: IMAGE_JPEG,
|
2021-10-04 10:22:15 +00:00
|
|
|
}];
|
2021-05-05 22:57:42 +00:00
|
|
|
|
|
|
|
let mut cursor = std::io::Cursor::new(Vec::new());
|
|
|
|
let mut contents = std::string::String::new();
|
|
|
|
|
2021-07-24 06:20:49 +00:00
|
|
|
Json
|
2021-10-04 10:22:15 +00:00
|
|
|
.write_all(&mut cursor, &findings, &[])
|
2021-05-05 23:06:05 +00:00
|
|
|
.expect("Failed to write to cursor");
|
2021-05-05 22:57:42 +00:00
|
|
|
|
|
|
|
cursor.set_position(0);
|
|
|
|
cursor
|
|
|
|
.read_to_string(&mut contents)
|
|
|
|
.expect("Failed to read from cursor to string");
|
|
|
|
|
2021-11-24 20:29:27 +00:00
|
|
|
// the output should contain the file's MIME type
|
2021-05-05 22:57:42 +00:00
|
|
|
assert!(
|
|
|
|
contents.contains(IMAGE_JPEG.essence_str()),
|
|
|
|
"JSON output doesn't contain move command!\n===\n{}",
|
|
|
|
contents
|
2021-06-14 06:53:41 +00:00
|
|
|
);
|
2021-04-08 13:33:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-04-26 10:19:58 +00:00
|
|
|
/// Ensure that the Media extension set contains all (is a superset) of Audio, Video, and Images.
|
2021-04-08 13:33:33 +00:00
|
|
|
fn media_contains_audio_video_images() {
|
2021-04-28 06:44:29 +00:00
|
|
|
use crate::parameters::ExtensionSet::{Audio, Images, Media, Video};
|
2021-04-08 13:33:33 +00:00
|
|
|
let media_exts = Media.extensions();
|
|
|
|
|
|
|
|
// assert every extension in the audio/video/image sets is contained in the media set
|
2021-04-28 06:44:29 +00:00
|
|
|
[Audio.extensions(), Video.extensions(), Images.extensions()]
|
2021-04-08 13:33:33 +00:00
|
|
|
.concat()
|
|
|
|
.into_iter()
|
|
|
|
.for_each(|ext| assert!(media_exts.contains(&ext)));
|
2021-04-28 08:09:44 +00:00
|
|
|
|
2021-04-28 09:33:42 +00:00
|
|
|
assert_eq!(
|
|
|
|
Parameters::parse_from(&["fif", "-E", "media"]).extensions(),
|
|
|
|
Parameters::parse_from(&["fif", "-E", "audio,video,images"]).extensions()
|
2021-06-14 06:53:41 +00:00
|
|
|
);
|
2021-04-08 13:33:33 +00:00
|
|
|
}
|
2021-04-26 10:19:58 +00:00
|
|
|
|
|
|
|
#[test]
|
2021-11-24 20:40:34 +00:00
|
|
|
/// Ensure that the `writables!` and `writablesln!` macros produce the output they should.
|
2021-04-26 10:19:58 +00:00
|
|
|
fn writables_is_correct() {
|
2021-08-28 07:54:01 +00:00
|
|
|
use fif::formats::Writable;
|
2021-11-24 20:40:34 +00:00
|
|
|
use fif::{writables, writablesln};
|
2021-04-26 10:19:58 +00:00
|
|
|
|
|
|
|
assert_eq!(
|
2021-08-06 14:36:19 +00:00
|
|
|
&["henlo".into(), Path::new("henlo").into(), Writable::Newline,],
|
2021-08-06 14:12:26 +00:00
|
|
|
writables!["henlo", (Path::new("henlo")), Newline]
|
2021-06-14 06:53:41 +00:00
|
|
|
);
|
2021-11-24 20:40:34 +00:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
&["henlo".into(), Path::new("henlo").into(), Writable::Newline, Writable::Newline],
|
|
|
|
writablesln!["henlo", (Path::new("henlo")), Newline]
|
2021-11-24 22:45:20 +00:00
|
|
|
);
|
2021-04-26 10:19:58 +00:00
|
|
|
}
|
2021-04-28 13:19:04 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
/// Test various combinations of verbosity flags.
|
|
|
|
fn verbosity() {
|
2021-07-01 08:52:53 +00:00
|
|
|
use log::LevelFilter;
|
2021-04-29 07:07:58 +00:00
|
|
|
assert!(
|
|
|
|
Parameters::try_parse_from(&["fif", "-q", "-v"]).is_err(),
|
|
|
|
"Failed to reject usage of both -q and -v!"
|
|
|
|
);
|
2021-04-28 13:19:04 +00:00
|
|
|
|
2021-10-04 14:18:42 +00:00
|
|
|
let expected_results = hashmap![
|
|
|
|
"-qqqqqqqq" => LevelFilter::Off,
|
|
|
|
"-qqq" => LevelFilter::Off,
|
|
|
|
"-qq" => LevelFilter::Error,
|
|
|
|
"-q" => LevelFilter::Warn,
|
|
|
|
"-s" => LevelFilter::Info,
|
|
|
|
"-v" => LevelFilter::Debug,
|
|
|
|
"-vv" => LevelFilter::Trace,
|
|
|
|
"-vvv" => LevelFilter::Trace,
|
|
|
|
"-vvvvvvvv" => LevelFilter::Trace,
|
|
|
|
];
|
2021-04-28 13:19:04 +00:00
|
|
|
|
|
|
|
for (flags, level) in expected_results {
|
2021-10-04 16:13:32 +00:00
|
|
|
assert_eq!(Parameters::parse_from(&["fif", flags]).get_verbosity(), level);
|
2021-04-28 13:19:04 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-28 08:21:53 +00:00
|
|
|
|
|
|
|
#[test]
|
2021-10-04 18:45:05 +00:00
|
|
|
/// Ensures `os_name()`'s output is the same as [`std::env::consts::OS`], capitalisation notwithstanding
|
|
|
|
fn validate_os_name() {
|
2021-11-05 15:30:34 +00:00
|
|
|
assert_eq!(fif::utils::os_name().to_lowercase(), std::env::consts::OS.to_lowercase());
|
2021-10-04 18:45:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
/// Ensures that [`Findings`] are sorted properly.
|
|
|
|
fn sort_findings() {
|
|
|
|
let findings = vec![
|
|
|
|
Findings {
|
|
|
|
file: Path::new("ccc").to_path_buf(),
|
|
|
|
valid: false,
|
|
|
|
mime: IMAGE_JPEG,
|
|
|
|
},
|
|
|
|
Findings {
|
|
|
|
file: Path::new("bbb.xyz").to_path_buf(),
|
|
|
|
valid: true,
|
|
|
|
mime: IMAGE_PNG,
|
|
|
|
},
|
|
|
|
Findings {
|
|
|
|
file: Path::new("aaa").to_path_buf(),
|
|
|
|
valid: true,
|
|
|
|
mime: APPLICATION_PDF,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
let mut findings = findings.iter().sorted_unstable();
|
|
|
|
|
|
|
|
assert_eq!(findings.next().unwrap().file, Path::new("aaa"));
|
|
|
|
assert_eq!(findings.next().unwrap().file, Path::new("bbb.xyz"));
|
|
|
|
assert_eq!(findings.next().unwrap().file, Path::new("ccc"));
|
|
|
|
assert_eq!(findings.next(), None);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2021-11-24 20:40:34 +00:00
|
|
|
#[cfg(not(all(target_endian = "big", target_pointer_width = "32")))]
|
2021-10-04 18:45:05 +00:00
|
|
|
/// Ensures that [`SmartString`]s don't deviate from std's Strings
|
2021-08-28 08:21:53 +00:00
|
|
|
fn validate_string_type() {
|
|
|
|
use std::string::String as StdString;
|
2021-09-24 08:11:25 +00:00
|
|
|
|
|
|
|
use fif::String as SmartString;
|
2021-08-28 08:21:53 +00:00
|
|
|
assert_eq!(SmartString::new(), StdString::new());
|
|
|
|
assert_eq!(SmartString::from("smol"), StdString::from("smol"));
|
|
|
|
assert_eq!(
|
|
|
|
SmartString::from("A long and therefore heap-allocated string"),
|
|
|
|
StdString::from("A long and therefore heap-allocated string")
|
|
|
|
);
|
2021-11-24 20:40:34 +00:00
|
|
|
|
2021-10-04 16:13:32 +00:00
|
|
|
smartstring::validate();
|
2021-08-28 08:21:53 +00:00
|
|
|
}
|