revault_lockbox_api 0.0.1

reVault lockbox API to create and manage lockboxes
Documentation
use crate::constants::{MAX_VARIABLE_NAME_BYTES, MAX_VARIABLE_VALUE_BYTES};
use crate::{Error, Result};

pub(crate) fn validate_permissions(permissions: u32) -> Result<u32> {
    if permissions <= 0o777 {
        Ok(permissions)
    } else {
        Err(Error::InvalidInput(format!(
            "permissions {permissions:o} include unsupported bits"
        )))
    }
}

pub(crate) fn validate_variable_name(name: &str) -> Result<String> {
    if name.is_empty() || name.len() > MAX_VARIABLE_NAME_BYTES {
        return Err(Error::InvalidPath(name.to_string()));
    }
    let canonical = if name.starts_with('/') {
        validate_variable_path(name)?;
        name.to_string()
    } else {
        validate_variable_component(name, name)?;
        format!("/{name}")
    };
    if canonical.len() > MAX_VARIABLE_NAME_BYTES {
        return Err(Error::InvalidPath(name.to_string()));
    }
    Ok(canonical)
}

fn validate_variable_path(path: &str) -> Result<()> {
    if path.len() == 1
        || path.ends_with('/')
        || path.starts_with("//")
        || path.contains('\\')
        || path.contains('\0')
        || path.contains(':')
    {
        return Err(Error::InvalidPath(path.to_string()));
    }
    for component in path.split('/').skip(1) {
        validate_variable_component(component, path)?;
    }
    Ok(())
}

fn validate_variable_component(component: &str, original: &str) -> Result<()> {
    if component.is_empty()
        || component == "."
        || component == ".."
        || !component
            .chars()
            .next()
            .is_some_and(|ch| ch == '_' || ch.is_ascii_alphabetic())
        || !component
            .chars()
            .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
    {
        return Err(Error::InvalidPath(original.to_string()));
    }
    Ok(())
}

pub(crate) fn validate_variable_value(value: &str) -> Result<String> {
    validate_variable_value_ref(value)?;
    Ok(value.to_string())
}

pub(crate) fn validate_variable_value_ref(value: &str) -> Result<()> {
    if value.len() > MAX_VARIABLE_VALUE_BYTES {
        return Err(Error::SecurityLimitExceeded(format!(
            "variable value exceeds {MAX_VARIABLE_VALUE_BYTES} bytes"
        )));
    }
    if value.contains('\0')
        || value.chars().any(|ch| {
            matches!(ch, '\u{0001}'..='\u{0008}' | '\u{000b}' | '\u{000c}' | '\u{000e}'..='\u{001f}' | '\u{007f}'..='\u{009f}')
        })
    {
        return Err(Error::InvalidInput(
            "variable value contains unsupported control characters".to_string(),
        ));
    }
    Ok(())
}