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