From f04c9611b670f57c42c609f840687c7b307858d2 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Thu, 3 Sep 2026 17:21:04 +0200 Subject: [PATCH] **Feat: Verbessert Token-Handling und String-Sanitization** MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Leere/Whitespace-Tokens werden konsistent als `None` behandelt - Sanitization für Anführungszeichen (`"` und `'`) und Umgebungsvariablen - Getter-Methode für GitHub-Token hinzugefügt --- src/config.rs | 34 +++++++++++++++++++++++----------- src/github.rs | 16 +++++++++------- src/utils.rs | 8 ++++---- tests/config_tests.rs | 18 +++++++++++++++++- tests/github_tests.rs | 11 ++++++----- 5 files changed, 59 insertions(+), 28 deletions(-) diff --git a/src/config.rs b/src/config.rs index f10b64f..6ccbec9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ +use crate::utils::sanitize_string; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use crate::utils::sanitize_env_vars; fn default_true() -> bool { true @@ -110,20 +110,32 @@ pub fn get_config_file_path() -> PathBuf { /// Lädt die Anwendungskonfiguration. Verwendet Standardwerte, falls keine Datei vorhanden ist. pub fn load_config() -> AppConfig { let mut config = config_ctdra::load_config::(); - let mut env_vars = std::env::vars().collect(); - sanitize_env_vars(&mut env_vars); - if let Some(gitea_url) = env_vars.get("GITEA_URL") { - config.gitea_url = Some(gitea_url.clone()); + + if let Ok(val) = std::env::var("GITEA_URL") { + let val = sanitize_string(&val); + if !val.is_empty() { + config.gitea_url = Some(val); + } } - if let Some(gitea_token) = env_vars.get("GITEA_TOKEN") { - config.gitea_token = Some(gitea_token.clone()); + if let Ok(val) = std::env::var("GITEA_TOKEN") { + let val = sanitize_string(&val); + if !val.is_empty() { + config.gitea_token = Some(val); + } } - if let Some(registry_owner) = env_vars.get("REGISTRY_OWNER") { - config.registry_owner = Some(registry_owner.clone()); + if let Ok(val) = std::env::var("REGISTRY_OWNER") { + let val = sanitize_string(&val); + if !val.is_empty() { + config.registry_owner = Some(val); + } } - if let Some(github_token) = env_vars.get("GITHUB_TOKEN") { - config.github_token = Some(github_token.clone()); + if let Ok(val) = std::env::var("GITHUB_TOKEN") { + let val = sanitize_string(&val); + if !val.is_empty() { + config.github_token = Some(val); + } } + config } diff --git a/src/github.rs b/src/github.rs index a034f37..bb6d138 100644 --- a/src/github.rs +++ b/src/github.rs @@ -81,12 +81,13 @@ struct GhApiAsset { #[derive(Clone, Debug)] pub struct GitHubClient { client: reqwest::Client, - pub token: Option, + token: Option, } impl GitHubClient { /// Erstellt einen neuen GitHub-API-Client mit optionalem Authentifizierungstoken. pub fn new(token: Option) -> Result { + let token = token.filter(|t| !t.trim().is_empty()); let mut headers = reqwest::header::HeaderMap::new(); headers.insert( reqwest::header::USER_AGENT, @@ -105,6 +106,11 @@ impl GitHubClient { Ok(Self { client, token }) } + /// Gibt das optionale Authentifizierungstoken zurück (falls gesetzt). + pub fn token(&self) -> Option<&str> { + self.token.as_deref() + } + /// Ruft Releases für ein angegebenes Repository ab (Format "owner/repo"). /// @@ -130,9 +136,7 @@ impl GitHubClient { let mut req = self.client.get(&url); if let Some(token) = &self.token { - if !token.is_empty() { - req = req.bearer_auth(token); - } + req = req.bearer_auth(token); } let resp = req.send().await.with_context(|| format!("Failed to send request to {}", url))?; @@ -220,9 +224,7 @@ impl GitHubClient { pub async fn download_asset_stream(&self, download_url: &str) -> Result { let mut req = self.client.get(download_url); if let Some(token) = &self.token { - if !token.is_empty() { - req = req.bearer_auth(token); - } + req = req.bearer_auth(token); } let resp = req diff --git a/src/utils.rs b/src/utils.rs index 02a2138..7d55e90 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,10 +10,10 @@ /// The sanitized string with surrounding quotes removed. pub fn sanitize_string(input: &str) -> String { let trimmed = input.trim(); - if trimmed.starts_with('"') && trimmed.ends_with('"') { - trimmed[1..trimmed.len() - 1].to_string() - } else if trimmed.starts_with('') && trimmed.ends_with('') { - trimmed[1..trimmed.len() - 1].to_string() + if let Some(stripped) = trimmed.strip_prefix('"').and_then(|s| s.strip_suffix('"')) { + stripped.to_string() + } else if let Some(stripped) = trimmed.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { + stripped.to_string() } else { trimmed.to_string() } diff --git a/tests/config_tests.rs b/tests/config_tests.rs index 0fbb851..768082b 100644 --- a/tests/config_tests.rs +++ b/tests/config_tests.rs @@ -1,4 +1,4 @@ -use mirror_package::config::{load_config, AppConfig, RepoConfig}; +use mirror_package::config::{AppConfig, RepoConfig, load_config}; use mirror_package::utils::{sanitize_env_vars, sanitize_string}; use std::collections::HashMap; @@ -41,12 +41,28 @@ fn test_load_config_with_env_vars() { #[test] fn test_sanitize_string() { assert_eq!(sanitize_string("\"https://gitea.example.com\""), "https://gitea.example.com"); + assert_eq!(sanitize_string("'https://gitea.example.com'"), "https://gitea.example.com"); assert_eq!(sanitize_string("\"token123\""), "token123"); + assert_eq!(sanitize_string("'token123'"), "token123"); assert_eq!(sanitize_string("\"owner\""), "owner"); + assert_eq!(sanitize_string("'owner'"), "owner"); assert_eq!(sanitize_string("\"github_token123\""), "github_token123"); + assert_eq!(sanitize_string("'github_token123'"), "github_token123"); assert_eq!(sanitize_string("no_quotes"), "no_quotes"); assert_eq!(sanitize_string("\"single_quote\""), "single_quote"); + assert_eq!(sanitize_string("'single_quote'"), "single_quote"); assert_eq!(sanitize_string("\"escaped_quote\""), "escaped_quote"); + // Edge cases: single character inputs + assert_eq!(sanitize_string("\""), "\""); + assert_eq!(sanitize_string("'"), "'"); + assert_eq!(sanitize_string("a"), "a"); + // Edge cases: empty quotes and empty strings + assert_eq!(sanitize_string("\"\""), ""); + assert_eq!(sanitize_string("''"), ""); + assert_eq!(sanitize_string(""), ""); + assert_eq!(sanitize_string(" "), ""); + assert_eq!(sanitize_string(" 'hello' "), "hello"); + assert_eq!(sanitize_string(" \"world\" "), "world"); } #[test] diff --git a/tests/github_tests.rs b/tests/github_tests.rs index a64af71..7cdadec 100644 --- a/tests/github_tests.rs +++ b/tests/github_tests.rs @@ -73,17 +73,18 @@ fn test_parse_repo_owner_name() { async fn test_github_client_empty_token() { use mirror_package::github::GitHubClient; - // Test that a client with an empty token can be created + // Test that a client with an empty or whitespace token sanitizes it to None let client = GitHubClient::new(Some("".to_string())).expect("Failed to create client with empty token"); + assert_eq!(client.token(), None); - // The client should be created successfully but has_auth() should return false - assert!(client.token.is_none() || client.token.as_ref().map_or(true, |t| t.is_empty())); + let client_whitespace = GitHubClient::new(Some(" ".to_string())).expect("Failed to create client with whitespace token"); + assert_eq!(client_whitespace.token(), None); // Test with a valid token let client_with_token = GitHubClient::new(Some("valid_token".to_string())).expect("Failed to create client with valid token"); - assert!(client_with_token.token.as_ref().map_or(false, |t| !t.is_empty())); + assert_eq!(client_with_token.token(), Some("valid_token")); // Test with no token let client_no_token = GitHubClient::new(None).expect("Failed to create client with no token"); - assert!(client_no_token.token.is_none()); + assert_eq!(client_no_token.token(), None); }