#
pacman_key/
validation.rs

    1use crate::error::{Error, Result};
    2
    3/// Validates a keyring name before passing to subprocess.
    4///
    5/// Accepted formats:
    6/// - Alphanumeric characters, hyphens, and underscores only
    7/// - Must not be empty
    8/// - Must not contain shell metacharacters
    9///
    10/// Returns the keyring name on success.
    11pub fn validate_keyring_name(name: &str) -> Result<&str> {
    12    if name.is_empty() {
    13        return Err(Error::InvalidKeyringName {
    14            name: name.to_string(),
    15            reason: "keyring name cannot be empty".to_string(),
    16        });
    17    }
    18
    19    if !name
    20        .chars()
    21        .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
    22    {
    23        return Err(Error::InvalidKeyringName {
    24            name: name.to_string(),
    25            reason:
    26                "keyring name must contain only alphanumeric characters, hyphens, or underscores"
    27                    .to_string(),
    28        });
    29    }
    30
    31    Ok(name)
    32}
    33
    34/// Validates a key ID format before passing to subprocess.
    35///
    36/// Accepted formats:
    37/// - 8 hex characters (short key ID, discouraged due to collisions)
    38/// - 16 hex characters (long key ID)
    39/// - 40 hex characters (full fingerprint, recommended)
    40/// - Any of the above with "0x" prefix
    41///
    42/// Returns the normalized keyid (uppercase, no prefix) on success.
    43pub fn validate_keyid(keyid: &str) -> Result<String> {
    44    if keyid.is_empty() {
    45        return Err(Error::InvalidKeyId {
    46            keyid: keyid.to_string(),
    47            reason: "key ID cannot be empty".to_string(),
    48        });
    49    }
    50
    51    let normalized = keyid
    52        .strip_prefix("0x")
    53        .or_else(|| keyid.strip_prefix("0X"))
    54        .unwrap_or(keyid)
    55        .to_uppercase();
    56
    57    if !normalized.chars().all(|c| c.is_ascii_hexdigit()) {
    58        return Err(Error::InvalidKeyId {
    59            keyid: keyid.to_string(),
    60            reason: "key ID must contain only hexadecimal characters".to_string(),
    61        });
    62    }
    63
    64    match normalized.len() {
    65        8 | 16 | 40 => Ok(normalized),
    66        len => Err(Error::InvalidKeyId {
    67            keyid: keyid.to_string(),
    68            reason: format!("key ID must be 8, 16, or 40 hex characters (got {})", len),
    69        }),
    70    }
    71}
    72
    73#[cfg(test)]
    74mod tests {
    75    use super::*;
    76
    77    #[test]
    78    fn test_valid_keyring_names() {
    79        assert_eq!(validate_keyring_name("archlinux").unwrap(), "archlinux");
    80        assert_eq!(
    81            validate_keyring_name("archlinuxarm").unwrap(),
    82            "archlinuxarm"
    83        );
    84        assert_eq!(validate_keyring_name("arch-linux").unwrap(), "arch-linux");
    85        assert_eq!(validate_keyring_name("arch_linux").unwrap(), "arch_linux");
    86        assert_eq!(validate_keyring_name("manjaro").unwrap(), "manjaro");
    87        assert_eq!(validate_keyring_name("test123").unwrap(), "test123");
    88    }
    89
    90    #[test]
    91    fn test_invalid_keyring_name_empty() {
    92        let err = validate_keyring_name("").unwrap_err();
    93        assert!(matches!(err, Error::InvalidKeyringName { .. }));
    94    }
    95
    96    #[test]
    97    fn test_invalid_keyring_name_special_chars() {
    98        let err = validate_keyring_name("$(whoami)").unwrap_err();
    99        assert!(matches!(err, Error::InvalidKeyringName { .. }));
    100
    101        let err = validate_keyring_name("arch;linux").unwrap_err();
    102        assert!(matches!(err, Error::InvalidKeyringName { .. }));
    103
    104        let err = validate_keyring_name("arch&linux").unwrap_err();
    105        assert!(matches!(err, Error::InvalidKeyringName { .. }));
    106
    107        let err = validate_keyring_name("arch|linux").unwrap_err();
    108        assert!(matches!(err, Error::InvalidKeyringName { .. }));
    109
    110        let err = validate_keyring_name("arch`whoami`").unwrap_err();
    111        assert!(matches!(err, Error::InvalidKeyringName { .. }));
    112
    113        let err = validate_keyring_name("arch linux").unwrap_err();
    114        assert!(matches!(err, Error::InvalidKeyringName { .. }));
    115
    116        let err = validate_keyring_name("arch\nlinux").unwrap_err();
    117        assert!(matches!(err, Error::InvalidKeyringName { .. }));
    118    }
    119
    120    #[test]
    121    fn test_valid_short_keyid() {
    122        assert_eq!(validate_keyid("DEADBEEF").unwrap(), "DEADBEEF");
    123        assert_eq!(validate_keyid("deadbeef").unwrap(), "DEADBEEF");
    124    }
    125
    126    #[test]
    127    fn test_valid_long_keyid() {
    128        assert_eq!(
    129            validate_keyid("786C63F330D7CB92").unwrap(),
    130            "786C63F330D7CB92"
    131        );
    132    }
    133
    134    #[test]
    135    fn test_valid_fingerprint() {
    136        assert_eq!(
    137            validate_keyid("ABAF11C65A2970B130ABE3C479BE3E4300411886").unwrap(),
    138            "ABAF11C65A2970B130ABE3C479BE3E4300411886"
    139        );
    140    }
    141
    142    #[test]
    143    fn test_valid_with_0x_prefix() {
    144        assert_eq!(validate_keyid("0xDEADBEEF").unwrap(), "DEADBEEF");
    145        assert_eq!(validate_keyid("0XDEADBEEF").unwrap(), "DEADBEEF");
    146    }
    147
    148    #[test]
    149    fn test_invalid_empty() {
    150        let err = validate_keyid("").unwrap_err();
    151        assert!(matches!(err, Error::InvalidKeyId { .. }));
    152    }
    153
    154    #[test]
    155    fn test_invalid_non_hex() {
    156        let err = validate_keyid("DEADBEEG").unwrap_err();
    157        assert!(matches!(err, Error::InvalidKeyId { .. }));
    158    }
    159
    160    #[test]
    161    fn test_invalid_wrong_length() {
    162        let err = validate_keyid("DEADBE").unwrap_err();
    163        assert!(matches!(err, Error::InvalidKeyId { .. }));
    164    }
    165
    166    #[test]
    167    fn test_invalid_contains_spaces() {
    168        let err = validate_keyid("DEAD BEEF").unwrap_err();
    169        assert!(matches!(err, Error::InvalidKeyId { .. }));
    170    }
    171
    172}