diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab012cc..98c74b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,9 +22,20 @@ jobs: cargo fmt --all -- --check cargo clippy -- -Dwarnings - build-windows: + tests: if: github.ref_type == 'tag' || startsWith(github.ref, 'refs/pull/') needs: lint + runs-on: ubuntu-latest + name: Tests + steps: + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@v3 + - run: cargo test + + build-windows: + if: github.ref_type == 'tag' || startsWith(github.ref, 'refs/pull/') + needs: [lint, tests] runs-on: windows-latest name: Release build for Windows steps: @@ -42,7 +53,7 @@ jobs: build-macos: if: github.ref_type == 'tag' || startsWith(github.ref, 'refs/pull/') - needs: lint + needs: [lint, tests] runs-on: macos-latest name: Release build for macOS steps: @@ -60,7 +71,7 @@ jobs: build-linux: if: github.ref_type == 'tag' || startsWith(github.ref, 'refs/pull/') - needs: lint + needs: [lint, tests] runs-on: ubuntu-latest name: Release build for linux x86_64 steps: @@ -82,7 +93,7 @@ jobs: build-arm: if: github.ref_type == 'tag' || startsWith(github.ref, 'refs/pull/') - needs: lint + needs: [lint, tests] name: Release builds for linux ARM runs-on: ubuntu-latest strategy: diff --git a/src/aws.rs b/src/aws.rs index 5535a62..a187947 100644 --- a/src/aws.rs +++ b/src/aws.rs @@ -12,6 +12,7 @@ use aws_sdk_s3::{ Client, }; use aws_smithy_http::byte_stream::Length; +use log::*; use temp_dir::TempDir; use crate::config::Params; @@ -129,6 +130,8 @@ pub async fn upload_file(archive_path: PathBuf, _temp_dir: TempDir, params: &Par .upload_id(upload_id) .send() .await?; + + info!("Archive uploaded as \"{}\"", &filename); Ok(()) } diff --git a/src/config.rs b/src/config.rs index 4defb9d..09689c3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,6 +8,9 @@ use log::*; use crate::prelude::*; +/// https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html +const VALID_FILENAME_CHARS: &str = "!-_.*'()/"; // plus alphanumeric + /// CLI Parser uses `clap`. /// /// command args take precedence over environment variables. @@ -109,13 +112,42 @@ pub async fn parse_config() -> Result { return Err(anyhow!("No AWS secret key was provided")); }; + // sanitize filename + let filename = params + .filename + .map(sanitize_filename) + .filter(|s| !s.is_empty()); // if only bad chars were provided, ignore and use default filename + Ok(Params { folder, interval: params.interval, - filename: params.filename, + filename, aws_region, aws_bucket, aws_key_id, aws_key, }) } + +/// Only keep recommended chars for S3 object keys +fn sanitize_filename(filename: impl Into) -> String { + let mut filename: String = filename.into(); + // remove invalid characters + filename.retain(|c| c.is_ascii_alphanumeric() || VALID_FILENAME_CHARS.contains(c)); + filename +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sanitize_filename() { + assert_eq!(&sanitize_filename("foo123"), "foo123"); + assert_eq!(&sanitize_filename("foo bar"), "foobar"); + assert_eq!(&sanitize_filename("foo/bar"), "foo/bar"); + assert_eq!(&sanitize_filename("foo.tar.gz"), "foo.tar.gz"); + assert_eq!(&sanitize_filename("٣৬¾①"), ""); + assert_eq!(&sanitize_filename("!-_.*'()/"), "!-_.*'()/"); + } +}