1use std::sync::{Arc, LazyLock};
2
3use http::{HeaderName, StatusCode};
4use serde::Deserialize;
5
6use crate::core::error::{Error, Kind, Result};
7use crate::core::response::Response;
8
9const STATUS_VENDOR: &str = "status";
10const BUILTIN: &str = include_str!("../../profiles/blocks.toml");
11
12static BUILTIN_RULES: LazyLock<BlockRules> = LazyLock::new(|| {
13 BlockRules::from_toml(BUILTIN).expect("bundled block rules are statically valid")
14});
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
17#[serde(rename_all = "lowercase")]
18#[non_exhaustive]
19pub enum BlockKind {
20 Challenge,
21 Captcha,
22 Block,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26#[non_exhaustive]
27pub struct BlockSignal {
28 pub vendor: String,
29 pub kind: BlockKind,
30}
31
32#[derive(Debug, Clone, Default)]
33pub struct BlockRules {
34 rules: Arc<Vec<BlockRule>>,
35}
36
37#[derive(Debug, Clone)]
38struct BlockRule {
39 vendor: String,
40 kind: BlockKind,
41 status: Option<StatusCode>,
42 header: Option<HeaderName>,
43 value: Option<String>,
44}
45
46#[derive(Deserialize)]
47#[serde(deny_unknown_fields)]
48struct RulesFile {
49 #[serde(default)]
50 rule: Vec<RuleEntry>,
51}
52
53#[derive(Deserialize)]
54#[serde(deny_unknown_fields)]
55struct RuleEntry {
56 vendor: String,
57 kind: BlockKind,
58 status: Option<u16>,
59 header: Option<String>,
60 value: Option<String>,
61}
62
63impl BlockRules {
64 pub fn builtin() -> &'static BlockRules {
65 &BUILTIN_RULES
66 }
67
68 pub fn from_toml(source: &str) -> Result<Self> {
69 let file: RulesFile =
70 toml::from_str(source).map_err(|e| Error::new(Kind::Config).with_source(e))?;
71 let rules = file
72 .rule
73 .into_iter()
74 .map(BlockRule::try_from)
75 .collect::<Result<Vec<_>>>()?;
76 Ok(Self {
77 rules: Arc::new(rules),
78 })
79 }
80
81 pub fn statuses(statuses: impl IntoIterator<Item = u16>) -> Self {
82 let rules = statuses
83 .into_iter()
84 .filter_map(|code| StatusCode::from_u16(code).ok())
85 .map(|status| BlockRule {
86 vendor: STATUS_VENDOR.to_string(),
87 kind: BlockKind::Block,
88 status: Some(status),
89 header: None,
90 value: None,
91 })
92 .collect();
93 Self {
94 rules: Arc::new(rules),
95 }
96 }
97
98 pub fn extend(&mut self, other: BlockRules) {
99 Arc::make_mut(&mut self.rules).extend(other.rules.iter().cloned());
100 }
101
102 pub fn check(&self, response: &Response) -> Option<BlockSignal> {
103 self.rules
104 .iter()
105 .find(|rule| rule.matches(response))
106 .map(|rule| BlockSignal {
107 vendor: rule.vendor.clone(),
108 kind: rule.kind,
109 })
110 }
111}
112
113impl TryFrom<RuleEntry> for BlockRule {
114 type Error = Error;
115
116 fn try_from(entry: RuleEntry) -> Result<Self> {
117 let invalid = |message: String| Error::new(Kind::Config).with_message(message);
118 if entry.status.is_none() && entry.header.is_none() {
119 return Err(invalid(format!(
120 "block rule for {} needs a status or a header",
121 entry.vendor
122 )));
123 }
124 if entry.value.is_some() && entry.header.is_none() {
125 return Err(invalid(format!(
126 "block rule for {} has a value without a header",
127 entry.vendor
128 )));
129 }
130 let status = entry
131 .status
132 .map(StatusCode::from_u16)
133 .transpose()
134 .map_err(|e| Error::new(Kind::Config).with_source(e))?;
135 let header = entry
136 .header
137 .map(|name| HeaderName::from_bytes(name.as_bytes()))
138 .transpose()
139 .map_err(|e| Error::new(Kind::Config).with_source(e))?;
140 Ok(Self {
141 vendor: entry.vendor,
142 kind: entry.kind,
143 status,
144 header,
145 value: entry.value,
146 })
147 }
148}
149
150impl BlockRule {
151 fn matches(&self, response: &Response) -> bool {
152 if self
153 .status
154 .is_some_and(|status| status != response.status())
155 {
156 return false;
157 }
158 let Some(header) = &self.header else {
159 return true;
160 };
161 let mut values = response.headers().get_all(header).iter().peekable();
162 let Some(expected) = &self.value else {
163 return values.peek().is_some();
164 };
165 values
166 .filter_map(|value| value.to_str().ok())
167 .any(|value| value.trim().eq_ignore_ascii_case(expected))
168 }
169}