1use super::{ProxyContext, ProxyFilter, StringFilter};
2use rama_core::extensions::Extension;
3use rama_net::{address::ProxyAddress, asn::Asn, transport::TransportProtocol};
4use rama_utils::str::NonEmptyStr;
5use serde::{Deserialize, Serialize};
6
7#[cfg(feature = "memory-db")]
8use venndb::VennDB;
9
10#[derive(Debug, Clone, Serialize, Deserialize, Extension)]
11#[extension(tags(proxy))]
12#[cfg_attr(feature = "memory-db", derive(VennDB))]
13#[cfg_attr(feature = "memory-db", venndb(validator = proxydb_insert_validator))]
14pub struct Proxy {
16 #[cfg_attr(feature = "memory-db", venndb(key))]
17 pub id: NonEmptyStr,
19
20 pub address: ProxyAddress,
22
23 pub tcp: bool,
25
26 pub udp: bool,
28
29 pub http: bool,
31
32 pub https: bool,
34
35 pub socks5: bool,
37
38 pub socks5h: bool,
40
41 pub datacenter: bool,
43
44 pub residential: bool,
46
47 pub mobile: bool,
49
50 #[cfg_attr(feature = "memory-db", venndb(filter, any))]
51 pub pool_id: Option<StringFilter>,
53
54 #[cfg_attr(feature = "memory-db", venndb(filter, any))]
55 pub continent: Option<StringFilter>,
57
58 #[cfg_attr(feature = "memory-db", venndb(filter, any))]
59 pub country: Option<StringFilter>,
61
62 #[cfg_attr(feature = "memory-db", venndb(filter, any))]
63 pub state: Option<StringFilter>,
65
66 #[cfg_attr(feature = "memory-db", venndb(filter, any))]
67 pub city: Option<StringFilter>,
69
70 #[cfg_attr(feature = "memory-db", venndb(filter, any))]
71 pub carrier: Option<StringFilter>,
73
74 #[cfg_attr(feature = "memory-db", venndb(filter, any))]
75 pub asn: Option<Asn>,
77}
78
79#[cfg(feature = "memory-db")]
80fn proxydb_insert_validator(proxy: &Proxy) -> bool {
82 (proxy.datacenter || proxy.residential || proxy.mobile)
83 && (((proxy.http || proxy.https) && proxy.tcp)
84 || ((proxy.socks5 || proxy.socks5h) && (proxy.tcp || proxy.udp)))
85}
86
87impl Proxy {
88 #[must_use]
90 pub fn is_match(&self, ctx: &ProxyContext, filter: &ProxyFilter) -> bool {
91 if let Some(id) = &filter.id
92 && id != &self.id
93 {
94 return false;
95 }
96
97 match ctx.protocol {
98 TransportProtocol::Udp => {
99 if !(self.socks5 || self.socks5h) || !self.udp {
100 return false;
101 }
102 }
103 TransportProtocol::Tcp => {
104 if !self.tcp || !(self.http || self.https || self.socks5 || self.socks5h) {
105 return false;
106 }
107 }
108 }
109
110 filter
111 .continent
112 .as_ref()
113 .map(|c| {
114 let continent = self.continent.as_ref();
115 c.iter().any(|c| Some(c) == continent)
116 })
117 .unwrap_or(true)
118 && filter
119 .country
120 .as_ref()
121 .map(|c| {
122 let country = self.country.as_ref();
123 c.iter().any(|c| Some(c) == country)
124 })
125 .unwrap_or(true)
126 && filter
127 .state
128 .as_ref()
129 .map(|s| {
130 let state = self.state.as_ref();
131 s.iter().any(|s| Some(s) == state)
132 })
133 .unwrap_or(true)
134 && filter
135 .city
136 .as_ref()
137 .map(|c| {
138 let city = self.city.as_ref();
139 c.iter().any(|c| Some(c) == city)
140 })
141 .unwrap_or(true)
142 && filter
143 .pool_id
144 .as_ref()
145 .map(|p| {
146 let pool_id = self.pool_id.as_ref();
147 p.iter().any(|p| Some(p) == pool_id)
148 })
149 .unwrap_or(true)
150 && filter
151 .carrier
152 .as_ref()
153 .map(|c| {
154 let carrier = self.carrier.as_ref();
155 c.iter().any(|c| Some(c) == carrier)
156 })
157 .unwrap_or(true)
158 && filter
159 .asn
160 .as_ref()
161 .map(|a| {
162 let asn = self.asn.as_ref();
163 a.iter().any(|a| Some(a) == asn)
164 })
165 .unwrap_or(true)
166 && filter
167 .datacenter
168 .map(|d| d == self.datacenter)
169 .unwrap_or(true)
170 && filter
171 .residential
172 .map(|r| r == self.residential)
173 .unwrap_or(true)
174 && filter.mobile.map(|m| m == self.mobile).unwrap_or(true)
175 }
176}
177
178#[cfg(all(feature = "csv", feature = "memory-db"))]
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::proxydb::csv::{ProxyCsvRowReader, parse_csv_row};
183 use crate::proxydb::internal::{ProxyDB, ProxyDBErrorKind};
184 use itertools::Itertools;
185
186 #[test]
187 fn test_proxy_db_happy_path_basic() {
188 let mut db = ProxyDB::new();
189 let proxy = parse_csv_row("id,1,,1,,,,1,,,authority:80,,,,,,,,").unwrap();
190 db.append(proxy).unwrap();
191
192 let mut query = db.query();
193 query.tcp(true).http(true);
194
195 let proxy = query.execute().unwrap().any();
196 assert_eq!(proxy.id, "id");
197 }
198
199 #[tokio::test]
200 async fn test_proxy_db_happy_path_any_country() {
201 let mut db = ProxyDB::new();
202 let mut reader = ProxyCsvRowReader::raw(
203 "1,1,,1,,,,1,,,authority:80,,,US,,,,,\n2,1,,1,,,,1,,,authority:80,,,*,,,,,",
204 );
205 while let Some(proxy) = reader.next().await.unwrap() {
206 db.append(proxy).unwrap();
207 }
208
209 let mut query = db.query();
210 query.tcp(true).http(true).country("US");
211
212 let proxies: Vec<_> = query
213 .execute()
214 .unwrap()
215 .iter()
216 .sorted_by(|a, b| a.id.cmp(&b.id))
217 .collect();
218 assert_eq!(proxies.len(), 2);
219 assert_eq!(proxies[0].id, "1");
220 assert_eq!(proxies[1].id, "2");
221
222 query.reset().country("BE");
223 let proxies: Vec<_> = query
224 .execute()
225 .unwrap()
226 .iter()
227 .sorted_by(|a, b| a.id.cmp(&b.id))
228 .collect();
229 assert_eq!(proxies.len(), 1);
230 assert_eq!(proxies[0].id, "2");
231 }
232
233 #[tokio::test]
234 async fn test_proxy_db_happy_path_any_country_city() {
235 let mut db = ProxyDB::new();
236 let mut reader = ProxyCsvRowReader::raw(
237 "1,1,,1,,,,1,,,authority:80,,,US,,New York,,,\n2,1,,1,,,,1,,,authority:80,,,*,,*,,,",
238 );
239 while let Some(proxy) = reader.next().await.unwrap() {
240 db.append(proxy).unwrap();
241 }
242
243 let mut query = db.query();
244 query.tcp(true).http(true).country("US").city("new york");
245
246 let proxies: Vec<_> = query
247 .execute()
248 .unwrap()
249 .iter()
250 .sorted_by(|a, b| a.id.cmp(&b.id))
251 .collect();
252 assert_eq!(proxies.len(), 2);
253 assert_eq!(proxies[0].id, "1");
254 assert_eq!(proxies[1].id, "2");
255
256 query.reset().country("US").city("Los Angeles");
257 let proxies: Vec<_> = query
258 .execute()
259 .unwrap()
260 .iter()
261 .sorted_by(|a, b| a.id.cmp(&b.id))
262 .collect();
263 assert_eq!(proxies.len(), 1);
264 assert_eq!(proxies[0].id, "2");
265
266 query.reset().city("Ghent");
267 let proxies: Vec<_> = query
268 .execute()
269 .unwrap()
270 .iter()
271 .sorted_by(|a, b| a.id.cmp(&b.id))
272 .collect();
273 assert_eq!(proxies.len(), 1);
274 assert_eq!(proxies[0].id, "2");
275 }
276
277 #[tokio::test]
278 async fn test_proxy_db_happy_path_specific_asn_within_continents() {
279 let mut db = ProxyDB::new();
280 let mut reader = ProxyCsvRowReader::raw(
281 "1,1,,1,,,,1,,,authority:80,,europe,BE,,Brussels,,1348,\n2,1,,1,,,,1,,,authority:80,,asia,CN,,Shenzen,,1348,\n3,1,,1,,,,1,,,authority:80,,asia,CN,,Peking,,42,",
282 );
283 while let Some(proxy) = reader.next().await.unwrap() {
284 db.append(proxy).unwrap();
285 }
286
287 let mut query = db.query();
288 query
289 .tcp(true)
290 .http(true)
291 .continent("europe")
292 .continent("asia")
293 .asn(Asn::from_static(1348));
294
295 let proxies: Vec<_> = query
296 .execute()
297 .unwrap()
298 .iter()
299 .sorted_by(|a, b| a.id.cmp(&b.id))
300 .collect();
301 assert_eq!(proxies.len(), 2);
302 assert_eq!(proxies[0].id, "1");
303 assert_eq!(proxies[1].id, "2");
304
305 query.reset().asn(Asn::from_static(42));
306 let proxies: Vec<_> = query
307 .execute()
308 .unwrap()
309 .iter()
310 .sorted_by(|a, b| a.id.cmp(&b.id))
311 .collect();
312 assert_eq!(proxies.len(), 1);
313 assert_eq!(proxies[0].id, "3");
314 }
315
316 #[tokio::test]
317 async fn test_proxy_db_happy_path_states() {
318 let mut db = ProxyDB::new();
319 let mut reader = ProxyCsvRowReader::raw(
320 "1,1,,1,,,,1,,,authority:80,,,US,Texas,,,,\n2,1,,1,,,,1,,,authority:80,,,US,New York,,,,\n3,1,,1,,,,1,,,authority:80,,,US,California,,,,",
321 );
322 while let Some(proxy) = reader.next().await.unwrap() {
323 db.append(proxy).unwrap();
324 }
325
326 let mut query = db.query();
327 query.tcp(true).http(true).state("texas").state("new york");
328
329 let proxies: Vec<_> = query
330 .execute()
331 .unwrap()
332 .iter()
333 .sorted_by(|a, b| a.id.cmp(&b.id))
334 .collect();
335 assert_eq!(proxies.len(), 2);
336 assert_eq!(proxies[0].id, "1");
337 assert_eq!(proxies[1].id, "2");
338
339 query.reset().state("california");
340 let proxies: Vec<_> = query
341 .execute()
342 .unwrap()
343 .iter()
344 .sorted_by(|a, b| a.id.cmp(&b.id))
345 .collect();
346 assert_eq!(proxies.len(), 1);
347 assert_eq!(proxies[0].id, "3");
348 }
349
350 #[tokio::test]
351 async fn test_proxy_db_invalid_row_cases() {
352 let mut db = ProxyDB::new();
353 let mut reader = ProxyCsvRowReader::raw(
354 "id1,1,,,,,,,,,authority:80,,,,,,,\nid2,,1,,,,,,,,authority:80,,,,,,,\nid3,,1,1,,,,,,,authority:80,,,,,,,\nid4,,1,1,,,,,1,,authority:80,,,,,,,\nid5,,1,1,,,,,1,,authority:80,,,,,,,",
355 );
356 while let Some(proxy) = reader.next().await.unwrap() {
357 assert_eq!(
358 ProxyDBErrorKind::InvalidRow,
359 db.append(proxy).unwrap_err().kind
360 );
361 }
362 }
363}