0.34.6_repo_cleanup

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-22 09:31:15 +02:00
1 parent ce1a90397c
commit 133d2f0f91
273 files changed
+5969 -6926

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
use anyhow::{ensure, Result};
use http::{header::USER_AGENT, HeaderMap};
use serde::{Deserialize, Serialize};
const MAX_PATTERNS: usize = 128;
const MAX_PATTERN_BYTES: usize = 256;
const MAX_TOTAL_BYTES: usize = 16 * 1024;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct Policy {
pub enabled: bool,
pub patterns: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct CompiledPolicy {
enabled: bool,
patterns: Vec<String>,
}
impl Policy {
pub fn compile(&self) -> Result<CompiledPolicy> {
ensure!(
self.patterns.len() <= MAX_PATTERNS,
"User-Agent blocklist supports at most {MAX_PATTERNS} patterns"
);
let mut patterns = Vec::with_capacity(self.patterns.len());
let mut total = 0usize;
for raw in &self.patterns {
let pattern = raw.trim();
ensure!(
!pattern.is_empty()
&& pattern.len() <= MAX_PATTERN_BYTES
&& !pattern.chars().any(char::is_control),
"User-Agent block patterns must be 1..{MAX_PATTERN_BYTES} bytes without controls"
);
total = total.saturating_add(pattern.len());
ensure!(
total <= MAX_TOTAL_BYTES,
"User-Agent blocklist is too large"
);
let normalized = pattern.to_ascii_lowercase();
if !patterns.contains(&normalized) {
patterns.push(normalized);
}
}
Ok(CompiledPolicy {
enabled: self.enabled,
patterns,
})
}
}
pub(crate) fn validate_pattern(raw: &str) -> Result<String> {
let value = raw.trim();
ensure!(
!value.is_empty()
&& value.len() <= MAX_PATTERN_BYTES
&& !value.chars().any(char::is_control),
"User-Agent patterns must be 1..{MAX_PATTERN_BYTES} bytes without controls"
);
Ok(value.to_ascii_lowercase())
}
pub(crate) fn wildcard_match(candidate: &str, pattern: &str) -> bool {
if !pattern.contains('*') {
return candidate.contains(pattern);
}
if pattern == "*" {
return true;
}
let starts_wild = pattern.starts_with('*');
let ends_wild = pattern.ends_with('*');
let parts = pattern
.split('*')
.filter(|part| !part.is_empty())
.collect::<Vec<_>>();
if parts.is_empty() {
return true;
}
let mut cursor = 0usize;
for (index, part) in parts.iter().enumerate() {
let Some(found) = candidate[cursor..].find(part) else {
return false;
};
if index == 0 && !starts_wild && found != 0 {
return false;
}
cursor += found + part.len();
}
ends_wild || cursor == candidate.len()
}
impl CompiledPolicy {
pub fn blocks(&self, headers: &HeaderMap) -> bool {
if !self.enabled || self.patterns.is_empty() {
return false;
}
let Some(value) = headers.get(USER_AGENT) else {
return false;
};
let candidate = String::from_utf8_lossy(value.as_bytes()).to_ascii_lowercase();
self.patterns
.iter()
.any(|pattern| wildcard_match(&candidate, pattern))
}
}
#[cfg(test)]
mod tests;