use std::path::PathBuf;
use super::{CodeScanningHandler, models::ListCodeQLDatabase};
use crate::{
GHASError, Repository,
codeql::CodeQLLanguage,
codescanning::models::{CodeScanningAlert, CodeScanningAnalysis},
};
use log::debug;
use octocrab::{Octocrab, Page, Result as OctoResult};
impl<'octo> CodeScanningHandler<'octo> {
pub(crate) fn new(crab: &'octo Octocrab, repository: &'octo Repository) -> Self {
Self { crab, repository }
}
pub async fn is_enabled(&self) -> bool {
match self.analyses().per_page(1).send().await {
Ok(_) => true,
Err(_) => {
debug!("Code scanning is not enabled for this repository");
false
}
}
}
pub fn list(&self) -> ListCodeScanningAlerts {
ListCodeScanningAlerts::new(self)
}
pub async fn get(&self, number: u64) -> OctoResult<CodeScanningAlert> {
let route = format!(
"/repos/{owner}/{repo}/code-scanning/alerts/{number}",
owner = self.repository.owner(),
repo = self.repository.name(),
number = number
);
self.crab.get(route, None::<&()>).await
}
pub fn analyses(&self) -> ListCodeScanningAnalyses {
ListCodeScanningAnalyses::new(self)
}
pub async fn list_codeql_databases(&self) -> OctoResult<Vec<ListCodeQLDatabase>> {
let route = format!(
"/repos/{owner}/{repo}/code-scanning/codeql/databases",
owner = self.repository.owner(),
repo = self.repository.name()
);
self.crab.get(route, None::<&()>).await
}
pub async fn get_codeql_database(&self, language: String) -> OctoResult<ListCodeQLDatabase> {
let route = format!(
"/repos/{owner}/{repo}/code-scanning/codeql/databases/{lang}",
owner = self.repository.owner(),
repo = self.repository.name(),
lang = language
);
self.crab.get(route, None::<&()>).await
}
pub async fn download_codeql_database(
&self,
language: impl Into<CodeQLLanguage>,
output: impl Into<PathBuf>,
) -> Result<PathBuf, GHASError> {
let language = language.into();
let output = output.into();
let path = output
.join(self.repository.owner())
.join(self.repository.name())
.join(language.language());
let dbpath = path.join("codeql-database.zip");
if path.exists() {
std::fs::remove_dir_all(&path)?;
}
std::fs::create_dir_all(&path)?;
log::info!("Downloading CodeQL database to {}", path.display());
let route = format!(
"/repos/{owner}/{repo}/code-scanning/codeql/databases/{lang}",
owner = self.repository.owner(),
repo = self.repository.name(),
lang = language.language()
);
let client = reqwest::Client::new();
let data = client
.get(route)
.header(
http::header::ACCEPT,
http::header::HeaderValue::from_str("application/zip")?,
)
.header(
http::header::USER_AGENT,
http::header::HeaderValue::from_str("ghastoolkit")?,
)
.send()
.await?
.bytes()
.await?;
tokio::fs::write(&dbpath, data).await?;
self.unzip_codeql_database(&dbpath, &path)?;
Ok(path)
}
fn unzip_codeql_database(&self, zip: &PathBuf, output: &PathBuf) -> Result<(), GHASError> {
log::debug!("Unzipping CodeQL database to {}", output.display());
let file = std::fs::File::open(zip)?;
let mut archive = zip::ZipArchive::new(file)?;
archive.extract(output)?;
Ok(())
}
}
#[derive(Debug, serde::Serialize)]
pub struct ListCodeScanningAlerts<'octo, 'b> {
#[serde(skip)]
handler: &'b CodeScanningHandler<'octo>,
#[serde(skip_serializing_if = "Option::is_none")]
state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
tool_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
per_page: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
page: Option<u8>,
}
impl<'octo, 'b> ListCodeScanningAlerts<'octo, 'b> {
pub(crate) fn new(handler: &'b CodeScanningHandler<'octo>) -> Self {
Self {
handler,
state: Some(String::from("open")),
tool_name: None,
per_page: Some(100),
page: Some(1),
}
}
pub fn state(mut self, state: &str) -> Self {
self.state = Some(state.to_string());
self
}
pub fn tool_name(mut self, tool_name: &str) -> Self {
self.tool_name = Some(tool_name.to_string());
self
}
pub fn per_page(mut self, per_page: impl Into<u8>) -> Self {
self.per_page = Some(per_page.into());
self
}
pub fn page(mut self, page: impl Into<u8>) -> Self {
self.page = Some(page.into());
self
}
pub async fn send(self) -> OctoResult<Page<CodeScanningAlert>> {
let route = format!(
"/repos/{owner}/{repo}/code-scanning/alerts",
owner = self.handler.repository.owner(),
repo = self.handler.repository.name()
);
self.handler.crab.get(route, Some(&self)).await
}
}
#[derive(Debug, serde::Serialize)]
pub struct ListCodeScanningAnalyses<'octo, 'b> {
#[serde(skip)]
handler: &'b CodeScanningHandler<'octo>,
#[serde(skip_serializing_if = "Option::is_none")]
r#ref: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
tool_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
sarif_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
per_page: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
page: Option<u8>,
}
impl<'octo, 'b> ListCodeScanningAnalyses<'octo, 'b> {
pub(crate) fn new(handler: &'b CodeScanningHandler<'octo>) -> Self {
Self {
handler,
tool_name: None,
r#ref: None,
sarif_id: None,
per_page: Some(100),
page: Some(1),
}
}
pub fn r#ref(mut self, r#ref: &str) -> Self {
self.r#ref = Some(r#ref.to_string());
self
}
pub fn tool_name(mut self, tool_name: &str) -> Self {
self.tool_name = Some(tool_name.to_string());
self
}
pub fn sarif_id(mut self, sarif_id: &str) -> Self {
self.sarif_id = Some(sarif_id.to_string());
self
}
pub fn per_page(mut self, per_page: impl Into<u8>) -> Self {
self.per_page = Some(per_page.into());
self
}
pub fn page(mut self, page: impl Into<u8>) -> Self {
self.page = Some(page.into());
self
}
pub async fn send(self) -> OctoResult<Page<CodeScanningAnalysis>> {
let route = format!(
"/repos/{owner}/{repo}/code-scanning/analyses",
owner = self.handler.repository.owner(),
repo = self.handler.repository.name()
);
match self.handler.crab.get(route, Some(&self)).await {
Ok(response) => Ok(response),
Err(err) => Err(err),
}
}
}