Adds safety check for valid package names

This commit is contained in:
howardwu 2021-02-24 19:25:41 -08:00
parent c61cd3459a
commit 73b550011e
14 changed files with 139 additions and 41 deletions

View File

@ -40,13 +40,20 @@ impl Command for Init {
} }
fn apply(self, _: Context, _: Self::Input) -> Result<Self::Output> { fn apply(self, _: Context, _: Self::Input) -> Result<Self::Output> {
let path = current_dir()?; // Derive the package directory path.
let mut path = current_dir()?;
// Check that the given package name is valid.
let package_name = path let package_name = path
.file_stem() .file_stem()
.ok_or_else(|| anyhow!("Project name invalid"))? .ok_or_else(|| anyhow!("Project name invalid"))?
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
if !LeoPackage::is_package_name_valid(&package_name) {
return Err(anyhow!("Invalid Leo project name"));
}
// Check that the current package directory path exists.
if !path.exists() { if !path.exists() {
return Err(anyhow!("Directory does not exist")); return Err(anyhow!("Directory does not exist"));
} }

View File

@ -43,13 +43,17 @@ impl Command for New {
} }
fn apply(self, _: Context, _: Self::Input) -> Result<Self::Output> { fn apply(self, _: Context, _: Self::Input) -> Result<Self::Output> {
let mut path = current_dir()?; // Check that the given package name is valid.
let package_name = self.name; let package_name = self.name;
if !LeoPackage::is_package_name_valid(&package_name) {
return Err(anyhow!("Invalid Leo project name"));
}
// Derive the package directory path // Derive the package directory path.
let mut path = current_dir()?;
path.push(&package_name); path.push(&package_name);
// Verify the package directory path does not exist yet // Verify the package directory path does not exist yet.
if path.exists() { if path.exists() {
return Err(anyhow!("Directory already exists {:?}", path)); return Err(anyhow!("Directory already exists {:?}", path));
} }

View File

@ -32,7 +32,7 @@ use tracing::span::Span;
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Prove { pub struct Prove {
#[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")] #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")]
pub(super) skip_key_check: bool, pub(crate) skip_key_check: bool,
} }
impl Command for Prove { impl Command for Prove {

View File

@ -30,7 +30,7 @@ use tracing::span::Span;
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Run { pub struct Run {
#[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")] #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")]
skip_key_check: bool, pub(crate) skip_key_check: bool,
} }
impl Command for Run { impl Command for Run {

View File

@ -35,7 +35,7 @@ use tracing::span::Span;
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Setup { pub struct Setup {
#[structopt(long = "skip-key-check", help = "Skip key verification")] #[structopt(long = "skip-key-check", help = "Skip key verification")]
pub(super) skip_key_check: bool, pub(crate) skip_key_check: bool,
} }
impl Command for Setup { impl Command for Setup {

View File

@ -36,7 +36,7 @@ use tracing::span::Span;
#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Test { pub struct Test {
#[structopt(short = "f", long = "file", name = "file")] #[structopt(short = "f", long = "file", name = "file")]
files: Vec<PathBuf>, pub(crate) files: Vec<PathBuf>,
} }
impl Command for Test { impl Command for Test {

View File

@ -35,15 +35,15 @@ pub enum Automatic {
pub struct Update { pub struct Update {
/// List all available versions of Leo /// List all available versions of Leo
#[structopt(short, long)] #[structopt(short, long)]
list: bool, pub(crate) list: bool,
/// For Aleo Studio only /// For Aleo Studio only
#[structopt(short, long)] #[structopt(short, long)]
studio: bool, pub(crate) studio: bool,
/// Setting for automatic updates of Leo /// Setting for automatic updates of Leo
#[structopt(subcommand)] #[structopt(subcommand)]
automatic: Option<Automatic>, pub(crate) automatic: Option<Automatic>,
} }
impl Command for Update { impl Command for Update {

View File

@ -39,34 +39,34 @@ const PEDERSEN_HASH_PATH: &str = "./examples/pedersen-hash/";
#[test] #[test]
pub fn build_pedersen_hash() -> Result<()> { pub fn build_pedersen_hash() -> Result<()> {
Build::new().apply(context()?, ())?; (Build {}).apply(context()?, ())?;
Ok(()) Ok(())
} }
#[test] #[test]
pub fn setup_pedersen_hash() -> Result<()> { pub fn setup_pedersen_hash() -> Result<()> {
let build = Build::new().apply(context()?, ())?; let build = (Build {}).apply(context()?, ())?;
Setup::new(false).apply(context()?, build.clone())?; (Setup { skip_key_check: false }).apply(context()?, build.clone())?;
Setup::new(true).apply(context()?, build)?; (Setup { skip_key_check: true }).apply(context()?, build)?;
Ok(()) Ok(())
} }
#[test] #[test]
pub fn prove_pedersen_hash() -> Result<()> { pub fn prove_pedersen_hash() -> Result<()> {
let build = Build::new().apply(context()?, ())?; let build = (Build {}).apply(context()?, ())?;
let setup = Setup::new(false).apply(context()?, build)?; let setup = (Setup { skip_key_check: false }).apply(context()?, build)?;
Prove::new(false).apply(context()?, setup.clone())?; (Prove { skip_key_check: false }).apply(context()?, setup.clone())?;
Prove::new(true).apply(context()?, setup)?; (Prove { skip_key_check: true }).apply(context()?, setup)?;
Ok(()) Ok(())
} }
#[test] #[test]
pub fn run_pedersen_hash() -> Result<()> { pub fn run_pedersen_hash() -> Result<()> {
let build = Build::new().apply(context()?, ())?; let build = (Build {}).apply(context()?, ())?;
let setup = Setup::new(false).apply(context()?, build)?; let setup = (Setup { skip_key_check: false }).apply(context()?, build)?;
let prove = Prove::new(false).apply(context()?, setup)?; let prove = (Prove { skip_key_check: false }).apply(context()?, setup)?;
Run::new(false).apply(context()?, prove.clone())?; (Run { skip_key_check: false }).apply(context()?, prove.clone())?;
Run::new(true).apply(context()?, prove)?; (Run { skip_key_check: true }).apply(context()?, prove)?;
Ok(()) Ok(())
} }
@ -75,14 +75,14 @@ pub fn test_pedersen_hash() -> Result<()> {
let mut main_file = PathBuf::from(PEDERSEN_HASH_PATH); let mut main_file = PathBuf::from(PEDERSEN_HASH_PATH);
main_file.push("src/main.leo"); main_file.push("src/main.leo");
Test::new(Vec::new()).apply(context()?, ())?; (Test { files: vec![] }).apply(context()?, ())?;
Test::new(vec![main_file]).apply(context()?, ())?; (Test { files: vec![main_file] }).apply(context()?, ())?;
Ok(()) Ok(())
} }
#[test] #[test]
pub fn test_logout() -> Result<()> { pub fn test_logout() -> Result<()> {
Logout::new().apply(context()?, ())?; (Logout {}).apply(context()?, ())?;
Ok(()) Ok(())
} }
@ -111,12 +111,40 @@ pub fn login_incorrect_credentials_or_token() -> Result<()> {
#[test] #[test]
pub fn leo_update_and_update_automatic() -> Result<()> { pub fn leo_update_and_update_automatic() -> Result<()> {
Update::new(true, true, None).apply(context()?, ())?; let update = Update {
Update::new(false, true, None).apply(context()?, ())?; list: true,
Update::new(false, false, None).apply(context()?, ())?; studio: true,
automatic: None,
};
update.apply(context()?, ())?;
Update::new(false, false, Some(UpdateAutomatic::Automatic { value: true })).apply(context()?, ())?; let update = Update {
Update::new(false, false, Some(UpdateAutomatic::Automatic { value: false })).apply(context()?, ())?; list: false,
studio: true,
automatic: None,
};
update.apply(context()?, ())?;
let update = Update {
list: false,
studio: false,
automatic: None,
};
update.apply(context()?, ())?;
let update = Update {
list: false,
studio: false,
automatic: Some(UpdateAutomatic::Automatic { value: true }),
};
update.apply(context()?, ())?;
let update = Update {
list: false,
studio: false,
automatic: Some(UpdateAutomatic::Automatic { value: false }),
};
update.apply(context()?, ())?;
Ok(()) Ok(())
} }

View File

@ -26,6 +26,9 @@ pub enum PackageError {
#[error("Failed to initialize package {:?} ({:?})", _0, _1)] #[error("Failed to initialize package {:?} ({:?})", _0, _1)]
FailedToInitialize(String, OsString), FailedToInitialize(String, OsString),
#[error("Invalid project name: {:?}", _0)]
InvalidPackageName(String),
#[error("`{}` metadata: {}", _0, _1)] #[error("`{}` metadata: {}", _0, _1)]
Removing(&'static str, io::Error), Removing(&'static str, io::Error),
} }

View File

@ -18,6 +18,9 @@ use std::io;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ManifestError { pub enum ManifestError {
#[error("{}: {}", _0, _1)]
Crate(&'static str, String),
#[error("`{}` creating: {}", _0, _1)] #[error("`{}` creating: {}", _0, _1)]
Creating(&'static str, io::Error), Creating(&'static str, io::Error),
@ -36,3 +39,9 @@ pub enum ManifestError {
#[error("`{}` writing: {}", _0, _1)] #[error("`{}` writing: {}", _0, _1)]
Writing(&'static str, io::Error), Writing(&'static str, io::Error),
} }
impl From<crate::errors::PackageError> for ManifestError {
fn from(error: crate::errors::PackageError) -> Self {
ManifestError::Crate("leo-package", error.to_string())
}
}

View File

@ -37,6 +37,11 @@ impl LeoPackage {
package::Package::initialize(package_name, is_lib, path) package::Package::initialize(package_name, is_lib, path)
} }
/// Returns `true` if the given Leo package name is valid.
pub fn is_package_name_valid(package_name: &str) -> bool {
package::Package::is_package_name_valid(package_name)
}
/// Removes an imported Leo package /// Removes an imported Leo package
pub fn remove_imported_package(package_name: &str, path: &Path) -> Result<(), PackageError> { pub fn remove_imported_package(package_name: &str, path: &Path) -> Result<(), PackageError> {
package::Package::remove_imported_package(package_name, path) package::Package::remove_imported_package(package_name, path)

View File

@ -34,17 +34,51 @@ pub struct Package {
} }
impl Package { impl Package {
pub fn new(package_name: &str) -> Self { pub fn new(package_name: &str) -> Result<Self, PackageError> {
Self { // Check that the package name is valid.
if !Self::is_package_name_valid(package_name) {
return Err(PackageError::InvalidPackageName(package_name.to_string()));
}
Ok(Self {
name: package_name.to_owned(), name: package_name.to_owned(),
version: "0.1.0".to_owned(), version: "0.1.0".to_owned(),
description: None, description: None,
license: None, license: None,
})
} }
/// Returns `true` if the package name is valid.
///
/// Package names must be lowercase and composed solely
/// of ASCII alphanumeric characters, and may be word-separated
/// by a single dash '-'.
pub fn is_package_name_valid(package_name: &str) -> bool {
// Check that the package name:
// 1. is lowercase,
// 2. is ASCII alphanumeric or a dash.
package_name.chars().all(|x| {
if !x.is_lowercase() {
tracing::error!("Project names must be all lowercase");
return false;
}
if x.is_ascii_alphanumeric() || x == '-' {
tracing::error!("Project names must be ASCII alphanumeric, and may be word-separated with a dash");
return false;
}
true
})
} }
/// Returns `true` if a package is can be initialized at a given path. /// Returns `true` if a package is can be initialized at a given path.
pub fn can_initialize(package_name: &str, is_lib: bool, path: &Path) -> bool { pub fn can_initialize(package_name: &str, is_lib: bool, path: &Path) -> bool {
// Check that the package name is valid.
if !Self::is_package_name_valid(package_name) {
return false;
}
let mut result = true; let mut result = true;
let mut existing_files = vec![]; let mut existing_files = vec![];
@ -91,6 +125,11 @@ impl Package {
/// Returns `true` if a package is initialized at the given path /// Returns `true` if a package is initialized at the given path
pub fn is_initialized(package_name: &str, is_lib: bool, path: &Path) -> bool { pub fn is_initialized(package_name: &str, is_lib: bool, path: &Path) -> bool {
// Check that the package name is valid.
if !Self::is_package_name_valid(package_name) {
return false;
}
// Check if the manifest file exists. // Check if the manifest file exists.
if !Manifest::exists_at(&path) { if !Manifest::exists_at(&path) {
return false; return false;
@ -137,7 +176,7 @@ impl Package {
// Next, initialize this directory as a Leo package. // Next, initialize this directory as a Leo package.
{ {
// Create the manifest file. // Create the manifest file.
Manifest::new(&package_name).write_to(&path)?; Manifest::new(&package_name)?.write_to(&path)?;
// Verify that the .gitignore file does not exist. // Verify that the .gitignore file does not exist.
if !Gitignore::exists_at(&path) { if !Gitignore::exists_at(&path) {

View File

@ -39,11 +39,11 @@ pub struct Manifest {
} }
impl Manifest { impl Manifest {
pub fn new(package_name: &str) -> Self { pub fn new(package_name: &str) -> Result<Self, ManifestError> {
Self { Ok(Self {
project: Package::new(package_name), project: Package::new(package_name)?,
remote: None, remote: None,
} })
} }
pub fn filename() -> String { pub fn filename() -> String {

View File

@ -52,7 +52,10 @@ fn initialize_fails_with_existing_manifest() {
assert!(Package::can_initialize(TEST_PACKAGE_NAME, false, &test_directory)); assert!(Package::can_initialize(TEST_PACKAGE_NAME, false, &test_directory));
// Manually add a manifest file to the `test_directory` // Manually add a manifest file to the `test_directory`
Manifest::new(TEST_PACKAGE_NAME).write_to(&test_directory).unwrap(); Manifest::new(TEST_PACKAGE_NAME)
.unwrap()
.write_to(&test_directory)
.unwrap();
// Attempt to initialize a package at the `test_directory` // Attempt to initialize a package at the `test_directory`
assert!(Package::initialize(TEST_PACKAGE_NAME, false, &test_directory).is_err()); assert!(Package::initialize(TEST_PACKAGE_NAME, false, &test_directory).is_err());