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