use crate::util::unwrap_try_into;
use failure::_core::fmt::{Error, Formatter};
use std::collections::HashSet;
use std::convert::{TryFrom, TryInto};
use std::str::FromStr;
type GenericResult<T> = std::result::Result<T, failure::Error>;
pub mod alienvault;
pub mod contenthash;
pub mod datetime;
pub mod hasher;
pub mod hashstr;
pub mod prelude;
pub mod scraper;
mod util;
pub mod virusbay;
pub mod virustotal;
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum SampleHash {
Sha1(String),
Sha256(String),
Md5(String),
}
impl std::fmt::Display for SampleHash {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "{}", self.as_ref())
}
}
impl Into<String> for SampleHash {
fn into(self) -> String {
match self {
SampleHash::Md5(x) => x,
SampleHash::Sha1(x) => x,
SampleHash::Sha256(x) => x,
}
}
}
impl AsRef<str> for SampleHash {
fn as_ref(&self) -> &str {
match self {
SampleHash::Md5(x) => x.as_str(),
SampleHash::Sha1(x) => x.as_str(),
SampleHash::Sha256(x) => x.as_str(),
}
}
}
fn to_sample(value: impl AsRef<str>) -> Result<SampleHash, std::io::Error> {
let s = value.as_ref().to_lowercase();
match hashstr::detect(&s) {
hashstr::HashType::MD5 => Ok(SampleHash::Md5(s)),
hashstr::HashType::SHA1 => Ok(SampleHash::Sha1(s)),
hashstr::HashType::SHA256 => Ok(SampleHash::Sha256(s)),
_ => Err(std::io::Error::from(std::io::ErrorKind::InvalidInput)),
}
}
impl TryFrom<&str> for SampleHash {
type Error = failure::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(to_sample(value)?)
}
}
impl TryFrom<&&str> for SampleHash {
type Error = failure::Error;
fn try_from(value: &&str) -> Result<Self, Self::Error> {
Ok(to_sample(value)?)
}
}
impl TryFrom<String> for SampleHash {
type Error = failure::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(to_sample(value)?)
}
}
impl TryFrom<&String> for SampleHash {
type Error = failure::Error;
fn try_from(value: &String) -> Result<Self, Self::Error> {
Ok(to_sample(value)?)
}
}
impl FromStr for SampleHash {
type Err = failure::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(to_sample(s)?)
}
}
impl SampleHash {
pub fn new(hash: impl AsRef<str>) -> GenericResult<Self> {
hash.as_ref().to_lowercase().parse()
}
pub fn map<T>(hashes: impl IntoIterator<Item = impl TryInto<SampleHash>>) -> T
where
T: std::iter::FromIterator<GenericResult<SampleHash>>,
{
hashes
.into_iter()
.map(|x| unwrap_try_into(x).map_err(|e| e.into()))
.collect()
}
pub fn try_map<T>(
hashes: impl IntoIterator<Item = impl TryInto<SampleHash>>,
) -> GenericResult<T>
where
T: std::iter::FromIterator<SampleHash>,
{
hashes
.into_iter()
.map(|x| unwrap_try_into(x).map_err(|e| e.into()))
.collect()
}
pub fn uniquify<T>(hashes: impl IntoIterator<Item = impl TryInto<SampleHash>>) -> T
where
T: std::iter::FromIterator<SampleHash>,
{
hashes
.into_iter()
.map(unwrap_try_into)
.flat_map(|x| x) .collect::<HashSet<SampleHash>>()
.into_iter()
.collect()
}
pub fn find<T>(text: impl AsRef<str>) -> T
where
T: std::iter::FromIterator<SampleHash>,
{
let v: GenericResult<HashSet<_>> = SampleHash::map(hashstr::find(&text));
v.unwrap().into_iter().collect()
}
pub fn scrape<T>(url: impl AsRef<str>) -> GenericResult<T>
where
T: std::iter::FromIterator<SampleHash>,
{
Ok(SampleHash::find(scraper::get_article(url)?))
}
}
#[macro_export]
macro_rules! sample {
($hash:literal) => {
$crate::SampleHash::new($hash).unwrap()
};
($hash:literal => sha256) => {
$crate::sample_sha256($hash).unwrap()
};
($hash:literal => sha1) => {
$crate::sample_sha1($hash).unwrap()
};
($hash:literal => md5) => {
$crate::sample_md5($hash).unwrap()
};
}
pub fn sample_md5(hash: impl TryInto<SampleHash>) -> GenericResult<SampleHash> {
let hash = unwrap_try_into(hash)?;
if let SampleHash::Md5(_) = hash {
Ok(hash)
} else {
Err(std::io::Error::from(std::io::ErrorKind::InvalidInput).into())
}
}
pub fn sample_sha1(hash: impl TryInto<SampleHash>) -> GenericResult<SampleHash> {
let hash = unwrap_try_into(hash)?;
if let SampleHash::Sha1(_) = hash {
Ok(hash)
} else {
Err(std::io::Error::from(std::io::ErrorKind::InvalidInput).into())
}
}
pub fn sample_sha256(hash: impl TryInto<SampleHash>) -> GenericResult<SampleHash> {
let hash = unwrap_try_into(hash)?;
if let SampleHash::Sha256(_) = hash {
Ok(hash)
} else {
Err(std::io::Error::from(std::io::ErrorKind::InvalidInput).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_from_str_works() {
let a1 = SampleHash::try_from("d41d8cd98f00b204e9800998ecf8427e").expect("failed to parse");
let a2 = SampleHash::try_from("D41D8CD98F00B204E9800998ECF8427E").expect("failed to parse");
assert_eq!(a1, a2);
let b1 = SampleHash::try_from("da39a3ee5e6b4b0d3255bfef95601890afd80709")
.expect("failed to parse");
let b2 = SampleHash::try_from("DA39A3EE5E6B4B0D3255BFEF95601890AFD80709")
.expect("failed to parse");
assert_eq!(b1, b2);
let c1 = SampleHash::try_from(
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
)
.expect("failed to parse");
let c2 = SampleHash::try_from(
"E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
)
.expect("failed to parse");
assert_eq!(c1, c2);
assert!(SampleHash::try_from("invalid_hash").is_err());
}
#[test]
fn new_works() {
let a1 = SampleHash::new("d41d8cd98f00b204e9800998ecf8427e").expect("parse error..");
let a2 = SampleHash::new("D41D8CD98F00B204E9800998ECF8427E").expect("parse error..");
let b1 =
SampleHash::new("d41d8cd98f00b204e9800998ecf8427e".to_string()).expect("parse error..");
let b2 =
SampleHash::new("D41D8CD98F00B204E9800998ECF8427E".to_string()).expect("parse error..");
assert_eq!(a1, a2);
assert_eq!(b1, b2);
assert_eq!(a1, b2);
}
#[test]
fn from_hashes_works() {
let v = vec![
"d41d8cd98f00b204e9800998ecf8427e",
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
];
let s: GenericResult<Vec<_>> = SampleHash::map(v);
assert_eq!(s.unwrap().len(), 3);
let v = vec![
"d41d8cd98f00b204e9800998ecf8427e".to_string(),
"da39a3ee5e6b4b0d3255bfef95601890afd80709".to_string(),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(),
];
let s: GenericResult<Vec<_>> = SampleHash::map(v);
assert_eq!(s.unwrap().len(), 3);
let v = &[
"d41d8cd98f00b204e9800998ecf8427e",
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
];
let s: GenericResult<Vec<_>> = SampleHash::map(v);
assert_eq!(s.unwrap().len(), 3);
let v = &[
"d41d8cd98f00b204e9800998ecf8427e".to_string(),
"da39a3ee5e6b4b0d3255bfef95601890afd80709".to_string(),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(),
];
let s: GenericResult<Vec<_>> = SampleHash::map(v);
assert_eq!(s.unwrap().len(), 3);
}
#[test]
fn try_from_string_works() {
let a1 = SampleHash::try_from("d41d8cd98f00b204e9800998ecf8427e".to_string())
.expect("failed to parse");
let a2 = SampleHash::try_from("D41D8CD98F00B204E9800998ECF8427E".to_string())
.expect("failed to parse");
assert_eq!(a1, a2);
let b1 = SampleHash::try_from("da39a3ee5e6b4b0d3255bfef95601890afd80709".to_string())
.expect("failed to parse");
let b2 = SampleHash::try_from("DA39A3EE5E6B4B0D3255BFEF95601890AFD80709".to_string())
.expect("failed to parse");
assert_eq!(b1, b2);
let c1 = SampleHash::try_from(
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".to_string(),
)
.expect("failed to parse");
let c2 = SampleHash::try_from(
"E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string(),
)
.expect("failed to parse");
assert_eq!(c1, c2);
assert!(SampleHash::try_from("invalid_hash".to_string()).is_err());
}
#[test]
fn try_parse_works() {
let _: SampleHash = "d41d8cd98f00b204e9800998ecf8427e"
.parse()
.expect("failed to parse");
let _: SampleHash = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
.parse()
.expect("failed to parse");
let _: SampleHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
.parse()
.expect("failed to parse");
let x: Result<SampleHash, _> = "invalid_hash".parse();
assert!(x.is_err());
}
}