Set up Go module and move Rust to legacy
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
use crate::domain::AllowedContext;
|
||||
|
||||
pub fn domain_allowed(context: &AllowedContext, candidate: &str) -> bool {
|
||||
let candidate = normalize_domain(candidate);
|
||||
if candidate.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
context.domains.iter().any(|allowed| {
|
||||
let allowed = normalize_domain(allowed);
|
||||
!allowed.is_empty()
|
||||
&& (candidate == allowed
|
||||
|| candidate
|
||||
.strip_suffix(&allowed)
|
||||
.is_some_and(|prefix| prefix.ends_with('.')))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn window_class_allowed(context: &AllowedContext, candidate: &str) -> bool {
|
||||
let candidate = normalize_casefolded(candidate);
|
||||
if candidate.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
context
|
||||
.window_classes
|
||||
.iter()
|
||||
.any(|allowed| normalize_casefolded(allowed) == candidate)
|
||||
}
|
||||
|
||||
pub fn window_title_allowed(context: &AllowedContext, candidate: &str) -> bool {
|
||||
let candidate = normalize_casefolded(candidate);
|
||||
if candidate.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
context.window_title_substrings.iter().any(|allowed| {
|
||||
let allowed = normalize_casefolded(allowed);
|
||||
!allowed.is_empty() && candidate.contains(&allowed)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn command_allowed(context: &AllowedContext, candidate: &str) -> bool {
|
||||
let candidate = executable_basename(candidate);
|
||||
if candidate.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
context
|
||||
.commands
|
||||
.iter()
|
||||
.any(|allowed| executable_basename(allowed) == candidate)
|
||||
}
|
||||
|
||||
fn normalize_domain(value: &str) -> String {
|
||||
value.trim().trim_end_matches('.').to_lowercase()
|
||||
}
|
||||
|
||||
fn normalize_casefolded(value: &str) -> String {
|
||||
value.trim().to_lowercase()
|
||||
}
|
||||
|
||||
fn executable_basename(command: &str) -> String {
|
||||
let executable = command.split_whitespace().next().unwrap_or_default();
|
||||
executable
|
||||
.trim()
|
||||
.rsplit(['/', '\\'])
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.to_lowercase()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::domain::AllowedContext;
|
||||
|
||||
#[test]
|
||||
fn domains_match_exact_or_subdomain() {
|
||||
let context = AllowedContext {
|
||||
domains: vec!["github.com".to_string()],
|
||||
..AllowedContext::default()
|
||||
};
|
||||
|
||||
assert!(domain_allowed(&context, "github.com"));
|
||||
assert!(domain_allowed(&context, "docs.github.com"));
|
||||
assert!(!domain_allowed(&context, "evilgithub.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_classes_match_case_insensitively_after_normalization() {
|
||||
let context = AllowedContext {
|
||||
window_classes: vec!["code".to_string()],
|
||||
..AllowedContext::default()
|
||||
};
|
||||
|
||||
assert!(window_class_allowed(&context, "Code"));
|
||||
assert!(!window_class_allowed(&context, "firefox"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn titles_match_by_substring() {
|
||||
let context = AllowedContext {
|
||||
window_title_substrings: vec!["antidrift".to_string()],
|
||||
..AllowedContext::default()
|
||||
};
|
||||
|
||||
assert!(window_title_allowed(&context, "Commitment OS - AntiDrift"));
|
||||
assert!(!window_title_allowed(&context, "random video"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn domains_ignore_whitespace_case_and_trailing_dots() {
|
||||
let context = AllowedContext {
|
||||
domains: vec![" GitHub.COM. ".to_string()],
|
||||
..AllowedContext::default()
|
||||
};
|
||||
|
||||
assert!(domain_allowed(&context, " DOCS.GITHUB.COM. "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_class_matching_trims_allowed_and_candidate_values() {
|
||||
let context = AllowedContext {
|
||||
window_classes: vec![" code ".to_string()],
|
||||
..AllowedContext::default()
|
||||
};
|
||||
|
||||
assert!(window_class_allowed(&context, " Code "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_matching_trims_allowed_substrings() {
|
||||
let context = AllowedContext {
|
||||
window_title_substrings: vec![" antidrift ".to_string()],
|
||||
..AllowedContext::default()
|
||||
};
|
||||
|
||||
assert!(window_title_allowed(&context, "Commitment OS - AntiDrift"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commands_match_executable_basename() {
|
||||
let context = AllowedContext {
|
||||
commands: vec!["cargo".to_string()],
|
||||
..AllowedContext::default()
|
||||
};
|
||||
|
||||
assert!(command_allowed(
|
||||
&context,
|
||||
"/home/felixm/.cargo/bin/cargo test"
|
||||
));
|
||||
assert!(command_allowed(&context, "cargo"));
|
||||
assert!(!command_allowed(&context, "/usr/bin/git status"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user