ksud: config set support read from stdin, and less restriction
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use anyhow::{Ok, Result};
|
||||
use anyhow::{Context, Ok, Result};
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -322,8 +322,11 @@ enum ModuleConfigCmd {
|
||||
Set {
|
||||
/// config key
|
||||
key: String,
|
||||
/// config value
|
||||
value: String,
|
||||
/// config value (omit to read from stdin)
|
||||
value: Option<String>,
|
||||
/// read value from stdin (default if value not provided)
|
||||
#[arg(long)]
|
||||
stdin: bool,
|
||||
/// use temporary config (cleared on reboot)
|
||||
#[arg(short, long)]
|
||||
temp: bool,
|
||||
@@ -576,17 +579,32 @@ pub fn run() -> Result<()> {
|
||||
None => anyhow::bail!("Key '{key}' not found"),
|
||||
}
|
||||
}
|
||||
ModuleConfigCmd::Set { key, value, temp } => {
|
||||
// Validate input at CLI layer for better user experience
|
||||
ModuleConfigCmd::Set { key, value, stdin, temp } => {
|
||||
// Validate key at CLI layer for better user experience
|
||||
module_config::validate_config_key(&key)?;
|
||||
module_config::validate_config_value(&value)?;
|
||||
|
||||
// Read value from stdin or argument
|
||||
let value_str = match value {
|
||||
Some(v) if !stdin => v,
|
||||
_ => {
|
||||
// Read from stdin
|
||||
use std::io::Read;
|
||||
let mut buffer = String::new();
|
||||
std::io::stdin().read_to_string(&mut buffer)
|
||||
.context("Failed to read from stdin")?;
|
||||
buffer
|
||||
}
|
||||
};
|
||||
|
||||
// Validate value
|
||||
module_config::validate_config_value(&value_str)?;
|
||||
|
||||
let config_type = if temp {
|
||||
module_config::ConfigType::Temp
|
||||
} else {
|
||||
module_config::ConfigType::Persist
|
||||
};
|
||||
module_config::set_config_value(&module_id, &key, &value, config_type)
|
||||
module_config::set_config_value(&module_id, &key, &value_str, config_type)
|
||||
}
|
||||
ModuleConfigCmd::List => {
|
||||
let config = module_config::merge_configs(&module_id)?;
|
||||
|
||||
@@ -40,7 +40,10 @@ pub fn parse_bool_config(value: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Validate config key
|
||||
/// Rejects keys with control characters, newlines, or excessive length
|
||||
/// Uses the same validation rules as module_id: ^[a-zA-Z][a-zA-Z0-9._-]+$
|
||||
/// - Must start with a letter (a-zA-Z)
|
||||
/// - Followed by one or more alphanumeric, dot, underscore, or hyphen characters
|
||||
/// - Minimum length: 2 characters
|
||||
pub fn validate_config_key(key: &str) -> Result<()> {
|
||||
if key.is_empty() {
|
||||
bail!("Config key cannot be empty");
|
||||
@@ -54,27 +57,21 @@ pub fn validate_config_key(key: &str) -> Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
// Check for control characters and newlines
|
||||
for ch in key.chars() {
|
||||
if ch.is_control() {
|
||||
bail!(
|
||||
"Config key contains control character: {:?} (U+{:04X})",
|
||||
ch,
|
||||
ch as u32
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Reject keys with path separators to prevent path traversal
|
||||
if key.contains('/') || key.contains('\\') {
|
||||
bail!("Config key cannot contain path separators");
|
||||
// Use same pattern as module_id for consistency
|
||||
let re = regex_lite::Regex::new(r"^[a-zA-Z][a-zA-Z0-9._-]+$")?;
|
||||
if !re.is_match(key) {
|
||||
bail!(
|
||||
"Invalid config key: '{}'. Must match /^[a-zA-Z][a-zA-Z0-9._-]+$/",
|
||||
key
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate config value
|
||||
/// Rejects values with control characters (except tab), newlines, or excessive length
|
||||
/// Only enforces maximum length - no character restrictions
|
||||
/// Values are stored in binary format with length prefix, so any UTF-8 data is safe
|
||||
pub fn validate_config_value(value: &str) -> Result<()> {
|
||||
if value.len() > MAX_CONFIG_VALUE_LEN {
|
||||
bail!(
|
||||
@@ -84,17 +81,7 @@ pub fn validate_config_value(value: &str) -> Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
// Check for control characters (allow tab but reject newlines and others)
|
||||
for ch in value.chars() {
|
||||
if ch.is_control() && ch != '\t' {
|
||||
bail!(
|
||||
"Config value contains invalid control character: {:?} (U+{:04X})",
|
||||
ch,
|
||||
ch as u32
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// No character restrictions - binary storage format handles all UTF-8 safely
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user