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