use crate::common::{AnalysisResult, LastAnalysisStats, Votes};
use crate::VirusTotalError;
use std::collections::HashMap;
#[cfg(feature = "chrono")]
use chrono::serde::{ts_seconds, ts_seconds_option};
#[cfg(feature = "chrono")]
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DomainReportRequestResponse {
#[serde(rename = "data")]
Data(DomainReportData),
#[serde(rename = "error")]
Error(VirusTotalError),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DomainReportData {
#[serde(default)]
pub links: HashMap<String, String>,
#[serde(rename = "type")]
pub record_type: String,
pub id: String,
pub attributes: DomainAttributes,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DomainAttributes {
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds")]
pub last_modification_date: DateTime<Utc>,
#[cfg(not(feature = "chrono"))]
pub last_modification_date: u64,
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds_option", default)]
pub last_https_certificate_date: Option<DateTime<Utc>>,
#[cfg(not(feature = "chrono"))]
#[serde(default)]
pub last_https_certificate_date: Option<u64>,
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds")]
pub creation_date: DateTime<Utc>,
#[cfg(not(feature = "chrono"))]
pub creation_date: u64,
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds")]
pub whois_date: DateTime<Utc>,
#[cfg(not(feature = "chrono"))]
pub whois_date: u64,
pub whois: String,
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds_option", default)]
pub last_dns_records_date: Option<DateTime<Utc>>,
#[cfg(not(feature = "chrono"))]
#[serde(default)]
pub last_dns_records_date: Option<u64>,
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds_option", default)]
pub last_update_date: Option<DateTime<Utc>>,
#[cfg(not(feature = "chrono"))]
#[serde(default)]
pub last_update_date: Option<u64>,
pub jarm: String,
pub tld: String,
pub registrar: String,
#[serde(default)]
pub last_dns_records: Vec<DnsRecord>,
pub total_votes: Votes,
pub last_analysis_stats: LastAnalysisStats,
#[serde(default)]
pub last_analysis_results: HashMap<String, AnalysisResult>,
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds")]
pub last_analysis_date: DateTime<Utc>,
#[cfg(not(feature = "chrono"))]
pub last_analysis_date: u64,
pub reputation: u64,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub popularity_ranks: HashMap<String, PopularityRankEntry>,
#[serde(default)]
pub last_https_certificate: HashMap<String, serde_json::Value>,
#[serde(default)]
pub categories: HashMap<String, serde_json::Value>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DnsRecord {
#[serde(default)]
pub expire: Option<u64>,
#[serde(default)]
pub minimum: Option<u64>,
#[serde(rename = "type")]
pub record_type: DnsRecordType,
#[serde(default)]
pub refresh: Option<u64>,
#[serde(default)]
pub retry: Option<u64>,
#[serde(default)]
pub serial: Option<u64>,
#[serde(default)]
pub tag: Option<String>,
pub ttl: u64,
pub value: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename(serialize = "UPPERCASE"))]
pub enum DnsRecordType {
A,
AAAA,
AFSDB,
APL,
CAA,
CDNSKEY,
CDS,
CERT,
CNAME,
CSYNC,
DHCID,
DLV,
DNAME,
DNSKey,
DS,
EUI48,
EUI64,
HInfo,
HIP,
HTTPS,
IPSecKey,
Key,
KX,
LOC,
MX,
NAPTR,
NS,
NSEC,
NSEC3PARAM,
OpenPGPKey,
PTR,
RP,
RRSIG,
SIG,
SMIMEA,
SOA,
SRV,
SSHFP,
SVCB,
TA,
TKEY,
TLSA,
TSIG,
TXT,
URI,
ZoneMD,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PopularityRankEntry {
#[cfg(feature = "chrono")]
#[serde(with = "ts_seconds")]
pub timestamp: DateTime<Utc>,
#[cfg(not(feature = "chrono"))]
pub timestamp: u64,
pub rank: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn haiku_org() {
const DOMAIN_REPORT: &str = include_str!("../../testdata/haikuorg.json");
let report: DomainReportRequestResponse =
serde_json::from_str(DOMAIN_REPORT).expect("failed to deserialize VT report");
let report = if let DomainReportRequestResponse::Data(data) = report {
data
} else {
panic!("expected data");
};
eprintln!("Remaining fields: {}", report.attributes.extra.len());
eprintln!("{:?}", report.attributes.extra);
assert!(report.attributes.extra.is_empty());
}
}