From 6095ead99248963ae70091c5bb3399eed49c0826 Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Wed, 24 Jan 2024 18:58:58 +0100 Subject: Remove Go and C source The Rust implementation now implements all features I need --- rs/git-lfs-authenticate/src/main.rs | 236 ------------------------------------ 1 file changed, 236 deletions(-) delete mode 100644 rs/git-lfs-authenticate/src/main.rs (limited to 'rs/git-lfs-authenticate/src/main.rs') diff --git a/rs/git-lfs-authenticate/src/main.rs b/rs/git-lfs-authenticate/src/main.rs deleted file mode 100644 index 36d7818..0000000 --- a/rs/git-lfs-authenticate/src/main.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::{fmt, process::ExitCode, time::Duration}; - -use chrono::Utc; -use common::{Operation, ParseOperationError}; - -fn help() { - eprintln!("Usage: git-lfs-authenticate upload/download"); -} - -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -enum RepoNameError { - TooLong, - UnresolvedPath, - AbsolutePath, - MissingGitSuffix, -} - -impl fmt::Display for RepoNameError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::TooLong => write!(f, "too long (more than 100 characters)"), - Self::UnresolvedPath => { - write!(f, "contains path one or more path elements '.' and '..'") - } - Self::AbsolutePath => { - write!(f, "starts with '/', which is not allowed") - } - Self::MissingGitSuffix => write!(f, "misses '.git' suffix"), - } - } -} - -// Using `Result<(), E>` here instead of `Option` because `None` typically signifies some error -// state with no further details provided. If we were to return an `Option` type, the user would -// have to first transform it into a `Result` type in order to use the `?` operator, meaning that -// they would have to the following operation to get the type into the right shape: -// `validate_repo_path(path).map_or(Ok(()), Err)`. That would not be very ergonomic. -fn validate_repo_path(path: &str) -> Result<(), RepoNameError> { - if path.len() > 100 { - return Err(RepoNameError::TooLong); - } - if path.contains("//") - || path.contains("/./") - || path.contains("/../") - || path.starts_with("./") - || path.starts_with("../") - { - return Err(RepoNameError::UnresolvedPath); - } - if path.starts_with('/') { - return Err(RepoNameError::AbsolutePath); - } - if !path.ends_with(".git") { - return Err(RepoNameError::MissingGitSuffix); - } - Ok(()) -} - -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -enum ParseCmdlineError { - UnknownOperation(ParseOperationError), - InvalidRepoName(RepoNameError), - UnexpectedArgCount(ArgCountError), -} - -impl From for ParseCmdlineError { - fn from(value: RepoNameError) -> Self { - Self::InvalidRepoName(value) - } -} - -impl From for ParseCmdlineError { - fn from(value: ParseOperationError) -> Self { - Self::UnknownOperation(value) - } -} - -impl From for ParseCmdlineError { - fn from(value: ArgCountError) -> Self { - Self::UnexpectedArgCount(value) - } -} - -impl fmt::Display for ParseCmdlineError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::UnknownOperation(e) => write!(f, "unknown operation: {e}"), - Self::InvalidRepoName(e) => write!(f, "invalid repository name: {e}"), - Self::UnexpectedArgCount(e) => e.fmt(f), - } - } -} - -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -struct ArgCountError { - provided: usize, - expected: usize, -} - -impl fmt::Display for ArgCountError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "got {} argument(s), expected {}", - self.provided, self.expected - ) - } -} - -fn get_cmdline_args() -> Result<[String; N], ArgCountError> { - let args = std::env::args(); - if args.len() - 1 != N { - return Err(ArgCountError { - provided: args.len() - 1, - expected: N, - }); - } - - // Does not allocate. - const EMPTY_STRING: String = String::new(); - let mut values = [EMPTY_STRING; N]; - - // Skip the first element; we do not care about the program name. - for (i, arg) in args.skip(1).enumerate() { - values[i] = arg - } - Ok(values) -} - -fn parse_cmdline() -> Result<(String, Operation), ParseCmdlineError> { - let [repo_path, op_str] = get_cmdline_args::<2>()?; - let op: Operation = op_str.parse()?; - validate_repo_path(&repo_path)?; - Ok((repo_path.to_string(), op)) -} - -fn repo_exists(name: &str) -> bool { - match std::fs::metadata(name) { - Ok(metadata) => metadata.is_dir(), - _ => false, - } -} - -struct Config { - href_base: String, - key_path: String, -} - -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -enum LoadConfigError { - BaseUrlNotProvided, - BaseUrlSlashSuffixMissing, - KeyPathNotProvided, -} - -impl fmt::Display for LoadConfigError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::BaseUrlNotProvided => write!(f, "base URL not provided"), - Self::BaseUrlSlashSuffixMissing => write!(f, "base URL does not end with slash"), - Self::KeyPathNotProvided => write!(f, "key path not provided"), - } - } -} - -fn load_config() -> Result { - let Ok(href_base) = std::env::var("GITOLFS3_HREF_BASE") else { - return Err(LoadConfigError::BaseUrlNotProvided); - }; - if !href_base.ends_with('/') { - return Err(LoadConfigError::BaseUrlSlashSuffixMissing); - } - let Ok(key_path) = std::env::var("GITOLFS3_KEY_PATH") else { - return Err(LoadConfigError::KeyPathNotProvided); - }; - Ok(Config { - href_base, - key_path, - }) -} - -fn main() -> ExitCode { - let config = match load_config() { - Ok(config) => config, - Err(e) => { - eprintln!("Failed to load config: {e}"); - return ExitCode::FAILURE; - } - }; - let key = match common::load_key(&config.key_path) { - Ok(key) => key, - Err(e) => { - eprintln!("Failed to load key: {e}"); - return ExitCode::FAILURE; - } - }; - - let (repo_name, operation) = match parse_cmdline() { - Ok(args) => args, - Err(e) => { - eprintln!("Error: {e}\n"); - help(); - // Exit code 2 signifies bad usage of CLI. - return ExitCode::from(2); - } - }; - - if !repo_exists(&repo_name) { - eprintln!("Error: repository does not exist"); - return ExitCode::FAILURE; - } - - let expires_at = Utc::now() + Duration::from_secs(5 * 60); - let Some(tag) = common::generate_tag( - common::Claims { - specific_claims: common::SpecificClaims::BatchApi(operation), - repo_path: &repo_name, - expires_at, - }, - key, - ) else { - eprintln!("Failed to generate validation tag"); - return ExitCode::FAILURE; - }; - - println!( - "{{\"header\":{{\"Authorization\":\"Gitolfs3-Hmac-Sha256 {tag} {}\"}},\ - \"expires_at\":\"{}\",\"href\":\"{}{}/info/lfs\"}}", - expires_at.timestamp(), - common::EscJsonFmt(&expires_at.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)), - common::EscJsonFmt(&config.href_base), - common::EscJsonFmt(&repo_name), - ); - - ExitCode::SUCCESS -} -- cgit v1.2.3