use std::fmt::{Debug, Display, Formatter};
use std::ops::{Add, BitOr};
#[cfg(feature = "chrono")]
use std::ops::{Shl, Shr};
#[cfg(feature = "chrono")]
use chrono::{DateTime, Days, Utc};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum FileType {
AnyExecutable,
Apk,
Android,
Coff,
Com,
Word,
Doc,
Docx,
Dos,
Elf,
Excel,
Xls,
Xlsx,
Linux,
Deb,
Rpm,
Ne,
NeExe,
NeDll,
Installer,
MachO,
MacDmg,
MsOffice,
Pdf,
Rtf,
PE32,
PeDll,
PeExe,
Perl,
Php,
PowerPoint,
Ppt,
Pptx,
Python,
Ps1,
Script,
Shell,
Source,
Symbian,
Vba,
Vbs,
Win16,
Win32,
Windows,
WindowsCE,
WindowsMSI,
}
impl Display for FileType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FileType::AnyExecutable => write!(f, "type:executable"),
FileType::Apk => write!(f, "type:apk"),
FileType::Android => write!(f, "type:android"),
FileType::Coff => write!(f, "type:coff"),
FileType::Com => write!(f, "type:com"),
FileType::Word => write!(f, "type:word"),
FileType::Doc => write!(f, "type:doc"),
FileType::Docx => write!(f, "type:docx"),
FileType::Dos => write!(f, "type:dos"),
FileType::Elf => write!(f, "type:elf"),
FileType::Deb => write!(f, "type:deb"),
FileType::Rpm => write!(f, "type:rpm"),
FileType::Excel => write!(f, "type:excel"),
FileType::Xls => write!(f, "type:xls"),
FileType::Xlsx => write!(f, "type:xlsx"),
FileType::Linux => write!(f, "type:linux"),
FileType::Ne => write!(f, "type:ne"),
FileType::Installer => write!(f, "type:installer"),
FileType::NeExe => write!(f, "type:neexe"),
FileType::NeDll => write!(f, "type:nedll"),
FileType::MachO => write!(f, "type:macho"),
FileType::MacDmg => write!(f, "type:dmg"),
FileType::MsOffice => write!(f, "type:msoffice"),
FileType::Pdf => write!(f, "type:pdf"),
FileType::Rtf => write!(f, "type:rtf"),
FileType::PE32 => write!(f, "type:peexe OR type:pedll"),
FileType::PeDll => write!(f, "type:pedll"),
FileType::PeExe => write!(f, "type:peexe"),
FileType::Perl => write!(f, "type:perl"),
FileType::Php => write!(f, "type:php"),
FileType::Ps1 => write!(f, "type:ps1"),
FileType::PowerPoint => write!(f, "type:powerpoint"),
FileType::Ppt => write!(f, "type:ppt"),
FileType::Pptx => write!(f, "type:pptx"),
FileType::Python => write!(f, "type:python"),
FileType::Source => write!(f, "type:source"),
FileType::Script => write!(f, "type:script"),
FileType::Shell => write!(f, "type:shell"),
FileType::Symbian => write!(f, "type:symbian"),
FileType::Vba => write!(f, "type:vba"),
FileType::Vbs => write!(f, "type:vbs"),
FileType::Win16 => write!(f, "type:win16"),
FileType::Win32 => write!(f, "type:win32"),
FileType::Windows => write!(f, "type:windows"),
FileType::WindowsCE => write!(f, "type:windowsce"),
FileType::WindowsMSI => write!(f, "type:msi"),
}
}
}
impl BitOr for FileType {
type Output = String;
fn bitor(self, rhs: Self) -> Self::Output {
format!("{self} OR {rhs}")
}
}
#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
pub struct FileTypes(pub Vec<FileType>);
impl Display for FileTypes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let combined = self
.0
.iter()
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(" OR ");
write!(f, "{combined}")
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum Tag {
DotNetAssembly,
Executable64bit,
ExecutableEFI,
ExecutableSigned,
Exploit,
Honeypot,
MSOfficeMacro,
PdfAutoAction,
PdfEmbeddedFile,
PdfForm,
PdfJs,
PdfLaunchAction,
}
impl Display for Tag {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Tag::DotNetAssembly => write!(f, "tag:assembly"),
Tag::Executable64bit => write!(f, "tag:64bits"),
Tag::ExecutableEFI => write!(f, "tag:efi"),
Tag::ExecutableSigned => write!(f, "tag:signed"),
Tag::Exploit => write!(f, "tag:exploit"),
Tag::Honeypot => write!(f, "tag:honeypot"),
Tag::MSOfficeMacro => write!(f, "tag:macros"),
Tag::PdfAutoAction => write!(f, "tag:autoaction"),
Tag::PdfEmbeddedFile => write!(f, "tag:file-embedded"),
Tag::PdfForm => write!(f, "tag:acroform"),
Tag::PdfJs => write!(f, "tag:js-embedded"),
Tag::PdfLaunchAction => write!(f, "tag:launch-action"),
}
}
}
impl BitOr for Tag {
type Output = String;
fn bitor(self, rhs: Self) -> Self::Output {
format!("{self} {rhs}")
}
}
#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
pub struct Tags(pub Vec<Tag>);
impl Display for Tags {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let combined = self
.0
.iter()
.map(std::string::ToString::to_string)
.collect::<Vec<String>>()
.join(" ");
write!(f, "{combined}")
}
}
#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize)]
pub struct Positives {
pub min: u32,
pub max: Option<u32>,
pub exact: bool,
}
impl Positives {
#[must_use]
pub const fn min(min: u32) -> Self {
Positives {
min,
max: None,
exact: false,
}
}
#[must_use]
pub const fn min_max(min: u32, max: u32) -> Self {
Positives {
min,
max: Some(max),
exact: false,
}
}
}
pub const BENIGN: Positives = Positives {
min: 0,
max: Some(0),
exact: true,
};
impl Default for Positives {
fn default() -> Self {
Positives {
min: 5,
max: None,
exact: false,
}
}
}
impl Display for Positives {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.exact {
return write!(f, "positives:{}", self.min);
}
write!(f, "positives:{}+", self.min)?;
if let Some(max) = self.max {
write!(f, " positives:{max}-")
} else {
write!(f, "")
}
}
}
#[cfg(feature = "chrono")]
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct FirstSubmission {
pub first: DateTime<Utc>,
pub second: Option<DateTime<Utc>>,
pub exact: bool,
pub format: &'static str,
}
#[cfg(feature = "chrono")]
impl FirstSubmission {
pub const FORMAT_DATE: &'static str = "%Y-%m-%d";
pub const FORMAT_DATETIME: &'static str = "%Y-%m-%dT%H:%M:%S";
#[must_use]
pub fn at_date(start: DateTime<Utc>) -> Self {
Self {
first: start,
second: None,
exact: true,
format: Self::FORMAT_DATE,
}
}
#[must_use]
pub fn at_datetime(start: DateTime<Utc>) -> Self {
Self {
first: start,
second: None,
exact: true,
format: Self::FORMAT_DATETIME,
}
}
#[must_use]
pub fn from_date(start: DateTime<Utc>) -> Self {
Self {
first: start,
second: None,
exact: false,
format: Self::FORMAT_DATE,
}
}
#[must_use]
pub fn from_datetime(start: DateTime<Utc>) -> Self {
Self {
first: start,
second: None,
exact: false,
format: Self::FORMAT_DATETIME,
}
}
#[must_use]
pub fn until_date(self, end: DateTime<Utc>) -> Self {
Self {
first: self.first,
second: Some(end),
exact: false,
format: self.format,
}
}
#[must_use]
pub fn days(days: u32) -> Self {
let start = Utc::now() - Days::new(u64::from(days));
Self {
first: start,
second: None,
exact: false,
format: Self::FORMAT_DATE,
}
}
}
#[cfg(feature = "chrono")]
impl Display for FirstSubmission {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.exact {
return write!(f, "fs:{}", self.first.format(self.format));
}
write!(f, "fs:{}+", self.first.format(self.format))?;
if let Some(second) = self.second {
write!(f, " fs:{}-", second.format(self.format))
} else {
write!(f, "")
}
}
}
#[cfg(feature = "chrono")]
impl Add<Days> for FirstSubmission {
type Output = FirstSubmission;
fn add(self, rhs: Days) -> Self::Output {
let end = if let Some(end) = self.second {
end + rhs
} else {
self.first + rhs
};
Self {
first: self.first,
second: Some(end),
exact: self.exact,
format: self.format,
}
}
}
#[cfg(feature = "chrono")]
impl Shl<Days> for FirstSubmission {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
fn shl(self, rhs: Days) -> Self::Output {
let start = self.first + rhs;
let end = self.second.map(|second| second + rhs);
Self {
first: start,
second: end,
exact: self.exact,
format: self.format,
}
}
}
#[cfg(feature = "chrono")]
impl Shr<Days> for FirstSubmission {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
fn shr(self, rhs: Days) -> Self::Output {
let start = self.first - rhs;
let end = self.second.map(|second| second - rhs);
Self {
first: start,
second: end,
exact: self.exact,
format: self.format,
}
}
}
impl Add<FileType> for Positives {
type Output = String;
fn add(self, rhs: FileType) -> Self::Output {
format!("{rhs} {self}")
}
}
impl Add<Positives> for FileType {
type Output = String;
fn add(self, rhs: Positives) -> Self::Output {
format!("{self} {rhs}")
}
}
impl Add<Tag> for FileType {
type Output = String;
fn add(self, rhs: Tag) -> Self::Output {
format!("{self} {rhs}")
}
}
impl Add<Tags> for FileType {
type Output = String;
fn add(self, rhs: Tags) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(feature = "chrono")]
impl Add<FirstSubmission> for FileType {
type Output = String;
fn add(self, rhs: FirstSubmission) -> Self::Output {
format!("{self} {rhs}")
}
}
impl Add<Positives> for Tag {
type Output = String;
fn add(self, rhs: Positives) -> Self::Output {
format!("{self} {rhs}")
}
}
impl Add<Positives> for Tags {
type Output = String;
fn add(self, rhs: Positives) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(feature = "chrono")]
impl Add<FirstSubmission> for Tag {
type Output = String;
fn add(self, rhs: FirstSubmission) -> Self::Output {
format!("{self} {rhs}")
}
}
impl Add<FileTypes> for Positives {
type Output = String;
fn add(self, rhs: FileTypes) -> Self::Output {
format!("{rhs} {self}")
}
}
impl Add<Positives> for FileTypes {
type Output = String;
fn add(self, rhs: Positives) -> Self::Output {
format!("{self} {rhs}")
}
}
impl Add<String> for FileTypes {
type Output = String;
fn add(self, rhs: String) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(feature = "chrono")]
impl Add<FirstSubmission> for FileTypes {
type Output = String;
fn add(self, rhs: FirstSubmission) -> Self::Output {
format!("{self} {rhs}")
}
}
impl Add<String> for Tags {
type Output = String;
fn add(self, rhs: String) -> Self::Output {
format!("{rhs} {self}")
}
}
impl Add<String> for Tag {
type Output = String;
fn add(self, rhs: String) -> Self::Output {
format!("{rhs} {self}")
}
}
#[cfg(feature = "chrono")]
impl Add<FirstSubmission> for Tags {
type Output = String;
fn add(self, rhs: FirstSubmission) -> Self::Output {
format!("{rhs} {self}")
}
}
impl Add<Tag> for String {
type Output = String;
fn add(self, rhs: Tag) -> Self::Output {
format!("{self} {rhs}")
}
}
impl Add<Tags> for String {
type Output = String;
fn add(self, rhs: Tags) -> Self::Output {
format!("{self} {rhs}")
}
}
impl Add<FileType> for String {
type Output = String;
fn add(self, rhs: FileType) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(feature = "chrono")]
impl Add<FirstSubmission> for String {
type Output = String;
fn add(self, rhs: FirstSubmission) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(feature = "chrono")]
impl Add<FileType> for FirstSubmission {
type Output = String;
fn add(self, rhs: FileType) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(feature = "chrono")]
impl Add<Positives> for FirstSubmission {
type Output = String;
fn add(self, rhs: Positives) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(feature = "chrono")]
impl Add<String> for FirstSubmission {
type Output = String;
fn add(self, rhs: String) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(feature = "chrono")]
impl Add<Tag> for FirstSubmission {
type Output = String;
fn add(self, rhs: Tag) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(feature = "chrono")]
impl Add<Tags> for FirstSubmission {
type Output = String;
fn add(self, rhs: Tags) -> Self::Output {
format!("{self} {rhs}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn benign() {
assert_eq!(BENIGN.to_string(), "positives:0");
}
#[test]
fn types_positives() {
let types: String = FileTypes(vec![FileType::MachO, FileType::Dos, FileType::WindowsMSI])
+ Positives::default()
+ Tag::ExecutableSigned;
assert_eq!(
types,
"type:macho OR type:dos OR type:msi positives:5+ tag:signed"
);
}
#[cfg(feature = "chrono")]
#[test]
fn first_submission() {
use chrono::TimeZone;
let first =
FirstSubmission::at_datetime(Utc.with_ymd_and_hms(2015, 5, 15, 10, 20, 30).unwrap());
assert_eq!(first.to_string(), "fs:2015-05-15T10:20:30");
let first =
FirstSubmission::at_datetime(Utc.with_ymd_and_hms(2015, 5, 15, 10, 20, 30).unwrap());
let first = first.until_date(Utc.with_ymd_and_hms(2015, 12, 30, 23, 59, 59).unwrap());
assert_eq!(
first.to_string(),
"fs:2015-05-15T10:20:30+ fs:2015-12-30T23:59:59-"
);
let shifted_forward = first.clone() << Days::new(1);
assert_eq!(
shifted_forward.to_string(),
"fs:2015-05-16T10:20:30+ fs:2015-12-31T23:59:59-"
);
let shifted_back = shifted_forward >> Days::new(1);
assert_eq!(shifted_back, first);
}
}