summaryrefslogtreecommitdiffstats
path: root/src/util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/util.rs')
-rw-r--r--src/util.rs334
1 files changed, 0 insertions, 334 deletions
diff --git a/src/util.rs b/src/util.rs
deleted file mode 100644
index 7510a33..0000000
--- a/src/util.rs
+++ /dev/null
@@ -1,334 +0,0 @@
-mod progress;
-
-use std::error::Error;
-use std::fmt::{Display, Formatter};
-use std::io::ErrorKind;
-use std::path::{Component, Path, PathBuf};
-use const_format::formatcp;
-use log::{debug, info, warn};
-use sha1_smol::{Digest, Sha1};
-use tokio::fs::File;
-use tokio::{fs, io};
-use tokio::io::{AsyncReadExt, AsyncWriteExt};
-
-const PKG_NAME: &str = env!("CARGO_PKG_NAME");
-const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
-const CRATE_NAME: &str = env!("CARGO_CRATE_NAME");
-
-pub const USER_AGENT: &str = formatcp!("{PKG_NAME}/{PKG_VERSION} (in {CRATE_NAME})");
-
-#[derive(Debug)]
-pub enum IntegrityError {
- SizeMismatch{ expect: usize, actual: usize },
- Sha1Mismatch{ expect: Digest, actual: Digest }
-}
-
-impl Display for IntegrityError {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- match self {
- IntegrityError::SizeMismatch{ expect, actual } =>
- write!(f, "size mismatch (expect {expect} bytes, got {actual} bytes)"),
- IntegrityError::Sha1Mismatch {expect, actual} =>
- write!(f, "sha1 mismatch (expect {expect}, got {actual})")
- }
- }
-}
-
-impl Error for IntegrityError {}
-
-pub fn verify_sha1(expect: Digest, s: &str) -> Result<(), Digest> {
- let dig = Sha1::from(s).digest();
-
- if dig == expect {
- return Ok(());
- }
-
- Err(dig)
-}
-
-#[derive(Debug)]
-pub enum FileVerifyError {
- Integrity(PathBuf, IntegrityError),
- Open(PathBuf, tokio::io::Error),
- Read(PathBuf, tokio::io::Error),
-}
-
-impl Display for FileVerifyError {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- match self {
- FileVerifyError::Integrity(path, e) => write!(f, "file integrity error {}: {}", path.display(), e),
- FileVerifyError::Open(path, e) => write!(f, "error opening file {}: {}", path.display(), e),
- FileVerifyError::Read(path, e) => write!(f, "error reading file {}: {}", path.display(), e)
- }
- }
-}
-
-impl Error for FileVerifyError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- FileVerifyError::Integrity(_, e) => Some(e),
- FileVerifyError::Open(_, e) => Some(e),
- FileVerifyError::Read(_, e) => Some(e)
- }
- }
-}
-
-pub async fn verify_file(path: impl AsRef<Path>, expect_size: Option<usize>, expect_sha1: Option<Digest>) -> Result<(), FileVerifyError> {
- let path = path.as_ref();
-
- if expect_size.is_none() && expect_sha1.is_none() {
- return match fs::metadata(path).await {
- Ok(_) => {
- debug!("No size or sha1 for {}, have to assume it's good.", path.display());
- Ok(())
- },
- Err(e) => {
- Err(FileVerifyError::Open(path.to_path_buf(), e))
- }
- }
- }
-
- let mut file = File::open(path).await.map_err(|e| FileVerifyError::Open(path.to_owned(), e))?;
-
- let mut tally = 0usize;
- let mut st = Sha1::new();
- let mut buf = [0u8; 4096];
-
- loop {
- let n = match file.read(&mut buf).await {
- Ok(n) => n,
- Err(e) => match e.kind() {
- ErrorKind::Interrupted => continue,
- _ => return Err(FileVerifyError::Read(path.to_owned(), e))
- }
- };
-
- if n == 0 {
- break;
- }
-
- st.update(&buf[..n]);
- tally += n;
- }
-
- let dig = st.digest();
-
- if expect_size.is_some_and(|sz| sz != tally) {
- return Err(FileVerifyError::Integrity(path.to_owned(), IntegrityError::SizeMismatch {
- expect: expect_size.unwrap(),
- actual: tally
- }));
- } else if expect_sha1.is_some_and(|exp_dig| exp_dig != dig) {
- return Err(FileVerifyError::Integrity(path.to_owned(), IntegrityError::Sha1Mismatch {
- expect: expect_sha1.unwrap(),
- actual: dig
- }));
- }
-
- Ok(())
-}
-
-#[derive(Debug)]
-pub enum EnsureFileError {
- IO { what: &'static str, error: io::Error },
- Download { url: String, error: reqwest::Error },
- Integrity(IntegrityError),
- Offline,
- MissingURL
-}
-
-impl Display for EnsureFileError {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- match self {
- EnsureFileError::IO { what, error } => write!(f, "i/o error ensuring file ({what}): {error}"),
- EnsureFileError::Download { url, error } => write!(f, "error downloading file ({url}): {error}"),
- EnsureFileError::Integrity(e) => write!(f, "integrity error for downloaded file: {e}"),
- EnsureFileError::Offline => f.write_str("unable to download file while offline"),
- EnsureFileError::MissingURL => f.write_str("missing url"),
- }
- }
-}
-
-impl Error for EnsureFileError {
- fn source(&self) -> Option<&(dyn Error + 'static)> {
- match self {
- EnsureFileError::IO { error, .. } => Some(error),
- EnsureFileError::Download { error, .. } => Some(error),
- EnsureFileError::Integrity(error) => Some(error),
- _ => None
- }
- }
-}
-
-pub async fn should_download(path: impl AsRef<Path>, expect_size: Option<usize>, expect_sha1: Option<Digest>) -> Result<bool, io::Error> {
- let path = path.as_ref();
-
- match verify_file(path, expect_size, expect_sha1).await {
- Ok(()) => {
- debug!("Skipping download for file {}, integrity matches.", path.display());
- Ok(false)
- },
- Err(FileVerifyError::Open(_, e)) if e.kind() == ErrorKind::NotFound => {
- debug!("File {} is missing, downloading it.", path.display());
- Ok(true)
- },
- Err(FileVerifyError::Integrity(p, e)) => {
- warn!("Integrity error on file {}: {}", p.display(), e);
-
- // try to delete the file since it's bad
- let _ = fs::remove_file(path).await
- .map_err(|e| warn!("Error deleting corrupted/modified file {} (ignoring): {}", path.display(), e));
- Ok(true)
- }
- Err(FileVerifyError::Open(_, e) | FileVerifyError::Read(_, e)) => {
- warn!("Error verifying file {} on disk: {}", path.display(), e);
- Err(e)
- }
- }
-}
-
-pub async fn ensure_file(path: impl AsRef<Path>, url: Option<&str>, expect_size: Option<usize>, expect_sha1: Option<Digest>, online: bool, force_download: bool) -> Result<bool, EnsureFileError> {
- let path = path.as_ref();
-
- if !force_download && !should_download(path, expect_size, expect_sha1).await
- .map_err(|e| EnsureFileError::IO { what: "verifying file on disk", error: e })? {
-
- return Ok(false);
- }
-
- if !online {
- warn!("Cannot download {} to {} while offline!", url.unwrap_or("(no url)"), path.display());
- return Err(EnsureFileError::Offline);
- }
-
- // download the file
- let Some(url) = url else {
- return Err(EnsureFileError::MissingURL);
- };
-
- let mut file = File::create(path).await.map_err(|e| EnsureFileError::IO {
- what: "save downloaded file (open)",
- error: e
- })?;
-
- debug!("File {} must be downloaded ({}).", path.display(), url);
-
- let mut response = reqwest::get(url).await.map_err(|e| EnsureFileError::Download { url: url.to_owned(), error: e })?;
- let mut tally = 0usize;
- let mut sha1 = Sha1::new();
-
- while let Some(chunk) = response.chunk().await.map_err(|e| EnsureFileError::Download { url: url.to_owned(), error: e })? {
- let slice = chunk.as_ref();
-
- file.write_all(slice).await.map_err(|e| EnsureFileError::IO {
- what: "save downloaded file (write)",
- error: e
- })?;
-
- tally += slice.len();
- sha1.update(slice);
- }
-
- drop(file); // manually close file
-
- let del_file_silent = || async {
- debug!("Deleting downloaded file {} since its integrity doesn't match :(", path.display());
- let _ = fs::remove_file(path).await.map_err(|e| warn!("failed to delete invalid downloaded file: {}", e));
- ()
- };
-
- if expect_size.is_some_and(|s| s != tally) {
- del_file_silent().await;
-
- return Err(EnsureFileError::Integrity(IntegrityError::SizeMismatch {
- expect: expect_size.unwrap(),
- actual: tally
- }));
- }
-
- let digest = sha1.digest();
-
- if expect_sha1.is_some_and(|exp_dig| exp_dig != digest) {
- del_file_silent().await;
-
- return Err(EnsureFileError::Integrity(IntegrityError::Sha1Mismatch {
- expect: expect_sha1.unwrap(),
- actual: digest
- }));
- }
-
- info!("File {} downloaded successfully.", path.display());
- Ok(true)
-}
-
-pub fn check_path(name: &str) -> Result<&Path, &'static str> {
- let entry_path: &Path = Path::new(name);
-
- let mut depth = 0usize;
- for component in entry_path.components() {
- depth = match component {
- Component::Prefix(_) | Component::RootDir =>
- return Err("root path component in entry"),
- Component::ParentDir => depth.checked_sub(1)
- .map_or_else(|| Err("entry path escapes"), |s| Ok(s))?,
- Component::Normal(_) => depth + 1,
- _ => depth
- }
- }
-
- Ok(entry_path)
-}
-
-#[cfg(windows)]
-pub fn strip_verbatim(path: &Path) -> &Path {
- let Some(Component::Prefix(p)) = path.components().next() else {
- return path;
- };
-
- use std::path::Prefix;
- use std::ffi::OsStr;
-
- match p.kind() {
- Prefix::VerbatimDisk(_) =>
- Path::new(unsafe { OsStr::from_encoded_bytes_unchecked(&path.as_os_str().as_encoded_bytes()[4..]) }),
- _ => path
- }
-}
-
-#[cfg(not(windows))]
-pub fn strip_verbatim(path: &Path) -> &Path {
- path
-}
-
-pub trait AsJavaPath {
- fn as_java_path(&self) -> &Path;
-}
-
-impl AsJavaPath for Path {
- fn as_java_path(&self) -> &Path {
- strip_verbatim(self)
- }
-}
-
-#[cfg(test)]
-mod tests {
- #[allow(unused_imports)]
- use super::*;
- use std::path::Prefix;
-
- #[test]
- #[cfg(windows)]
- fn test_strip_verbatim() {
- let path = Path::new(r"\\?\C:\Some\Verbatim\Path");
- match path.components().next().unwrap() {
- Component::Prefix(p) => assert!(matches!(p.kind(), Prefix::VerbatimDisk(_)), "(TEST BUG) path does not start with verbatim disk"),
- _ => panic!("(TEST BUG) path does not start with prefix")
- }
-
- let path2 = path.as_java_path();
- match path2.components().next().unwrap() {
- Component::Prefix(p) => assert!(matches!(p.kind(), Prefix::Disk(_))),
- _ => panic!("path does not begin with prefix")
- }
- }
-}