use std::{
future::Future,
path::PathBuf,
sync::{Arc, Mutex},
};
use eyre::{eyre, ContextCompat};
use reqwest::{
header::{self, HeaderValue},
Client,
};
use serde::ser::{Serialize, Serializer};
use serde_json::Value;
use tempfile::NamedTempFile;
use w3s::helper;
use crate::{
constants::{
CRATES_API_RELEASE_ENDPOINT, GITHUB_LATEST_RELEASE, PDF_GENERATE_ENDPOINT,
TRUSTBLOCK_API_KEY_HEADER, WEB3_STORAGE_API_ENDPOINT, WEB3_STORAGE_ENDPOINT,
},
types::{Issue, IssueCount, Severity, Status},
utils::{apply_dotenv, validate_pdf},
};
pub trait Cmd: clap::Parser + Sized {
fn run(self) -> eyre::Result<()>;
}
pub fn block_on<F: Future>(future: F) -> F::Output {
let rt = tokio::runtime::Runtime::new().expect("could not start tokio rt");
rt.block_on(future)
}
#[allow(clippy::future_not_send)]
pub async fn upload_ipfs(
report_file_path: PathBuf,
api_key: &str,
) -> eyre::Result<(String, String)> {
apply_dotenv()?;
let client = Client::new();
let web3_storage_endpoint = std::env::var("WEB3_STORAGE_API_ENDPOINT")
.unwrap_or_else(|_| WEB3_STORAGE_API_ENDPOINT.to_string());
let response = client
.post(web3_storage_endpoint)
.header(TRUSTBLOCK_API_KEY_HEADER, api_key)
.send()
.await?;
if !response.status().is_success() {
return Err(eyre!("Could not upload to report. Try again"));
}
let api_response_data = response.json::<Value>().await?;
let api_key = &api_response_data["apiKey"]
.to_string()
.trim()
.replace('\"', "");
if api_key.is_empty() {
return Err(eyre!("Could not upload to report. Try again"));
}
let results = helper::upload(
report_file_path.to_str().wrap_err("Invalid File Path")?,
api_key,
2,
Some(Arc::new(Mutex::new(|name, _, pos, total| {
if pos != 0 {
if pos == total {
println!("[+] Uploading is done\n");
} else {
let percentage = (pos * 100) / total;
println!("[+] Uploading {name}. Finished %{percentage:}");
}
}
}))),
None,
None,
None,
)
.await?;
if report_file_path.starts_with(std::env::temp_dir()) {
std::fs::remove_file(report_file_path)?;
}
let cid = results[0].to_string();
let report_url = format!("https://{cid}{WEB3_STORAGE_ENDPOINT}");
Ok((cid, report_url))
}
pub async fn generate_pdf_from_url(url: String, api_key: &str) -> eyre::Result<PathBuf> {
apply_dotenv()?;
let client = Client::new();
let temp_pdf_file = NamedTempFile::new()?;
let pdf_generate_endpoint = std::env::var("PDF_GENERATE_ENDPOINT")
.unwrap_or_else(|_| PDF_GENERATE_ENDPOINT.to_string());
let response = client
.get(pdf_generate_endpoint)
.header(TRUSTBLOCK_API_KEY_HEADER, api_key)
.query(&[("url", url)])
.send()
.await?;
if !response.status().is_success() {
return Err(eyre!(
"Could not fetch web based audit report: {:#?}",
response.json::<Value>().await?
));
}
let response_bytes = response.bytes().await?;
let temp_pdf_path = temp_pdf_file.path();
std::fs::write(temp_pdf_path, response_bytes)?;
validate_pdf(temp_pdf_path.to_str().expect("should not fail"))?;
Ok(temp_pdf_file.into_temp_path().keep()?)
}
pub fn serialize_issues<S>(issues: &IssueCount, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let transformed_issues = get_transformed_issues(issues);
transformed_issues.serialize(serializer)
}
#[must_use]
pub fn get_transformed_issues(issues: &IssueCount) -> Vec<Issue> {
let mut result: Vec<Issue> = Vec::new();
for (status, severity_count) in [
(Status::Fixed, issues.fixed),
(Status::RiskAccepted, issues.risk_accepted),
] {
for (severity, count) in [
(Severity::Low, severity_count.low),
(Severity::Medium, severity_count.medium),
(Severity::High, severity_count.high),
(Severity::Critical, severity_count.critical),
] {
for _ in 0..count {
result.push(Issue { status, severity });
}
}
}
result
}
pub async fn check_update() -> eyre::Result<()> {
let client = Client::new();
let local_version = clap::crate_version!();
let response = client
.get(CRATES_API_RELEASE_ENDPOINT)
.header(
header::USER_AGENT,
HeaderValue::from_str(
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1",
)?,
)
.send()
.await?;
if response.status().is_success() {
let json_response = response.json::<Value>().await?;
let version = &json_response["crate"]["newest_version"]
.as_str()
.unwrap_or_default();
if version != &local_version {
println!(
"New version of Trustblock CLI is available: {version}.\nPlease download a new version from: {GITHUB_LATEST_RELEASE}\n\n"
);
}
}
Ok(())
}