use crate::tools::permissions::PermissionManager;
use crate::tools::utils::{is_absolute_path, normalize_command_whitespace};
pub(super) const BLANKET_BASH: &str = "Bash";
pub(super) const WEB_FETCH_SCOPE: &str = "WebFetch";
pub(super) const WEB_FETCH_DOMAIN_PREFIX: &str = "domain:";
pub(super) const MCP_SCOPE: &str = "Mcp";
impl PermissionManager {
pub(super) fn extract_bash_path_pattern(entry: &str) -> Option<&str> {
let trimmed = entry.trim();
if let Some(rest) = trimmed.strip_prefix("Bash(") {
if let Some(end) = rest.rfind(')') {
let content = &rest[..end];
let looks_like_path = content.starts_with('~') || is_absolute_path(content);
if looks_like_path && content.contains('*') {
return Some(content);
}
}
}
None
}
pub fn normalize_command(command: &str) -> String {
format!("Bash({})", command.trim())
}
pub fn normalize_command_key(command: &str) -> String {
let collapsed = normalize_command_whitespace(command);
format!("Bash({})", collapsed.trim())
}
pub(super) fn normalize_read(path: &str) -> String {
format!("Read({})", path.trim())
}
pub(super) fn normalize_write(path: &str) -> String {
format!("Write({})", path.trim())
}
pub(super) fn canonical_web_fetch_host(host: &str) -> String {
host.trim().trim_end_matches('.').to_ascii_lowercase()
}
pub(super) fn extract_web_fetch_domain(entry: &str) -> Option<String> {
let trimmed = entry.trim();
let inner = trimmed
.strip_prefix(WEB_FETCH_SCOPE)?
.strip_prefix('(')?
.strip_suffix(')')?
.trim();
let host = Self::canonical_web_fetch_host(
inner.strip_prefix(WEB_FETCH_DOMAIN_PREFIX).unwrap_or(inner),
);
(!host.is_empty()).then_some(host)
}
pub(super) fn normalize_web_fetch(host: &str) -> String {
format!(
"{WEB_FETCH_SCOPE}({WEB_FETCH_DOMAIN_PREFIX}{})",
Self::canonical_web_fetch_host(host)
)
}
pub(super) fn canonical_mcp_server(server: &str) -> String {
server.trim().to_ascii_lowercase()
}
pub(super) fn extract_mcp_server(entry: &str) -> Option<String> {
let inner = entry
.trim()
.strip_prefix(MCP_SCOPE)?
.strip_prefix('(')?
.strip_suffix(')')?;
let server = Self::canonical_mcp_server(inner);
(!server.is_empty()).then_some(server)
}
pub(super) fn normalize_mcp(server: &str) -> String {
format!("{MCP_SCOPE}({})", Self::canonical_mcp_server(server))
}
}