use std::path::PathBuf;
use serde_json::{json, Value};
pub struct InterceptRule {
pub id: usize,
pub url_pattern: String,
pub capture_path: Option<PathBuf>,
pub respond_path: Option<PathBuf>,
pub respond_status: u16,
}
#[derive(Clone)]
pub struct InterceptLogEntry {
pub rule_id: usize,
pub url: String,
pub method: String,
pub has_body: bool,
pub action: String,
}
pub struct InterceptState {
rules: Vec<InterceptRule>,
log: Vec<InterceptLogEntry>,
next_id: usize,
pub enabled: bool,
}
impl InterceptState {
pub fn new() -> Self {
Self {
rules: Vec::new(),
log: Vec::new(),
next_id: 1,
enabled: false,
}
}
pub fn add_rule(
&mut self,
url_pattern: String,
capture_path: Option<PathBuf>,
respond_path: Option<PathBuf>,
respond_status: u16,
) -> usize {
let id = self.next_id;
self.next_id += 1;
self.rules.push(InterceptRule {
id,
url_pattern,
capture_path,
respond_path,
respond_status,
});
id
}
pub fn remove_rule(&mut self, id: usize) -> bool {
let len = self.rules.len();
self.rules.retain(|r| r.id != id);
self.rules.len() < len
}
pub fn remove_all(&mut self) {
self.rules.clear();
}
pub fn match_url(&self, url: &str) -> Option<&InterceptRule> {
self.rules.iter().find(|r| url_matches(&r.url_pattern, url))
}
pub fn add_log(&mut self, entry: InterceptLogEntry) {
if self.log.len() > 1000 {
self.log.drain(0..500);
}
self.log.push(entry);
}
pub fn clear_log(&mut self) {
self.log.clear();
}
pub fn list_json(&self) -> Value {
let rules: Vec<Value> = self
.rules
.iter()
.map(|r| {
json!({
"id": r.id,
"pattern": r.url_pattern,
"capture": r.capture_path.as_ref().map(|p| p.to_string_lossy().to_string()),
"respond": r.respond_path.as_ref().map(|p| p.to_string_lossy().to_string()),
"status": r.respond_status,
})
})
.collect();
Value::Array(rules)
}
pub fn log_json(&self) -> Value {
let entries: Vec<Value> = self
.log
.iter()
.map(|e| {
json!({
"rule_id": e.rule_id,
"url": e.url,
"method": e.method,
"has_body": e.has_body,
"action": e.action,
})
})
.collect();
Value::Array(entries)
}
}
fn url_matches(pattern: &str, url: &str) -> bool {
let parts: Vec<&str> = pattern.split('*').collect();
if parts.len() == 1 {
return url.contains(pattern);
}
let mut pos = 0;
for (i, part) in parts.iter().enumerate() {
if part.is_empty() {
continue;
}
match url[pos..].find(part) {
Some(found) => {
if i == 0 && found != 0 {
return false; }
pos += found + part.len();
}
None => return false,
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn glob_matching() {
assert!(url_matches(
"*/api/data*",
"https://example.com/api/data?foo=1"
));
assert!(url_matches(
"*/biometrics/*",
"https://api.facetec.com/biometrics/process-request"
));
assert!(!url_matches("*/api/data*", "https://example.com/other"));
assert!(url_matches("example.com", "https://example.com/foo"));
}
}