From 4d7fe8e36410e589a233795b800744282fea8369 Mon Sep 17 00:00:00 2001 From: Marek Kaput Date: Fri, 22 Sep 2023 11:35:56 +0200 Subject: [PATCH] Check archive filenames commit-id:1b4d23da --- scarb/src/internal/restricted_names.rs | 12 ++++++- scarb/src/ops/package.rs | 22 +++++++++++-- scarb/tests/package.rs | 44 +++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/scarb/src/internal/restricted_names.rs b/scarb/src/internal/restricted_names.rs index d2b137c37..916b78890 100644 --- a/scarb/src/internal/restricted_names.rs +++ b/scarb/src/internal/restricted_names.rs @@ -1,5 +1,7 @@ //! Helpers for validating and checking names. +use std::path::Path; + use crate::DEFAULT_TESTS_PATH; use cairo_lang_filesystem::db::CORELIB_CRATE_NAME; @@ -56,7 +58,7 @@ pub fn is_keyword(name: &str) -> bool { .contains(&name) } -/// Checks if name is restricted on Windows platforms +/// Checks if name is restricted on Windows platforms. pub fn is_windows_restricted(name: &str) -> bool { [ "con", "prn", "aux", "nul", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", @@ -65,6 +67,14 @@ pub fn is_windows_restricted(name: &str) -> bool { .contains(&name) } +/// Checks the entire path for names restricted on Windows platforms. +pub fn is_windows_restricted_path(path: &Path) -> bool { + path.iter() + .filter_map(|c| c.to_str()) + .filter_map(|s| s.split('.').next()) + .any(is_windows_restricted) +} + /// Checks if name equals `core` or `starknet` pub fn is_internal(name: &str) -> bool { [ diff --git a/scarb/src/ops/package.rs b/scarb/src/ops/package.rs index 27e70a5ef..40c834f0a 100644 --- a/scarb/src/ops/package.rs +++ b/scarb/src/ops/package.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::fs::File; use std::io::{Seek, SeekFrom, Write}; -use anyhow::{ensure, Context, Result}; +use anyhow::{bail, ensure, Context, Result}; use camino::Utf8PathBuf; use indoc::writedoc; @@ -13,7 +13,7 @@ use crate::core::publishing::manifest_normalization::prepare_manifest_for_publis use crate::core::publishing::source::list_source_files; use crate::core::{Package, PackageId, PackageName, Workspace}; use crate::flock::FileLockGuard; -use crate::internal::fsx; +use crate::internal::{fsx, restricted_names}; use crate::{ops, MANIFEST_FILE_NAME}; const VERSION: u8 = 1; @@ -147,6 +147,7 @@ fn list_one_impl( fn prepare_archive_recipe(pkg: &Package) -> Result { let mut recipe = source_files(pkg)?; + check_filenames(&recipe)?; check_no_reserved_files(&recipe)?; // Add normalized manifest file. @@ -219,6 +220,23 @@ fn check_no_reserved_files(recipe: &ArchiveRecipe) -> Result<()> { Ok(()) } +fn check_filenames(recipe: &ArchiveRecipe) -> Result<()> { + for ArchiveFile { path, .. } in recipe { + const BAD_CHARS: &[char] = &['/', '\\', '<', '>', ':', '"', '|', '?', '*']; + for component in path.components() { + let name = component.as_str(); + if let Some(c) = BAD_CHARS.iter().find(|&&c| name.contains(c)) { + bail!("cannot package a filename with a special character `{c}`: {path}"); + } + } + + if restricted_names::is_windows_restricted_path(path.as_std_path()) { + bail!("cannot package file `{path}`, it is a Windows reserved filename"); + } + } + Ok(()) +} + fn normalize_manifest(pkg: Package) -> Result> { let mut buf = Vec::new(); diff --git a/scarb/tests/package.rs b/scarb/tests/package.rs index 636ac7f3f..662879ffa 100644 --- a/scarb/tests/package.rs +++ b/scarb/tests/package.rs @@ -542,7 +542,49 @@ fn list_ignore_nested() { } // TODO(mkaput): Invalid readme/license path -// TODO(mkaput): Restricted Windows files + +#[test] +#[cfg_attr( + target_family = "windows", + ignore = "Windows doesn't allow these characters in filenames." +)] +fn weird_characters_in_filenames() { + let t = TempDir::new().unwrap(); + ProjectBuilder::start().src("src/:foo", "").build(&t); + + Scarb::quick_snapbox() + .arg("package") + .current_dir(&t) + .assert() + .failure() + .stdout_matches(indoc! {r#" + [..] Packaging [..] + error: cannot package a filename with a special character `:`: src/:foo + "#}); +} + +#[test] +#[cfg_attr( + target_family = "windows", + ignore = "We do not want to create invalid files on Windows." +)] +fn windows_restricted_filenames() { + let t = TempDir::new().unwrap(); + ProjectBuilder::start() + .lib_cairo("mod aux;") + .src("src/aux.cairo", "") + .build(&t); + + Scarb::quick_snapbox() + .arg("package") + .current_dir(&t) + .assert() + .failure() + .stdout_matches(indoc! {r#" + [..] Packaging [..] + error: cannot package file `src/aux.cairo`, it is a Windows reserved filename + "#}); +} #[test] fn package_symlink() {