rama_proxy/proxydb/
internal.rs

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))]
12/// The selected proxy to use to connect to the proxy.
13pub struct Proxy {
14    #[cfg_attr(feature = "memory-db", venndb(key))]
15    /// Unique identifier of the proxy.
16    pub id: NonEmptyString,
17
18    /// The address to be used to connect to the proxy, including credentials if needed.
19    pub address: ProxyAddress,
20
21    /// True if the proxy supports TCP connections.
22    pub tcp: bool,
23
24    /// True if the proxy supports UDP connections.
25    pub udp: bool,
26
27    /// http-proxy enabled
28    pub http: bool,
29
30    /// https-proxy enabled
31    pub https: bool,
32
33    /// socks5-proxy enabled
34    pub socks5: bool,
35
36    /// socks5h-proxy enabled
37    pub socks5h: bool,
38
39    /// Proxy is located in a datacenter.
40    pub datacenter: bool,
41
42    /// Proxy's IP is labeled as residential.
43    pub residential: bool,
44
45    /// Proxy's IP originates from a mobile network.
46    pub mobile: bool,
47
48    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
49    /// Pool ID of the proxy.
50    pub pool_id: Option<StringFilter>,
51
52    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
53    /// Continent of the proxy.
54    pub continent: Option<StringFilter>,
55
56    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
57    /// Country of the proxy.
58    pub country: Option<StringFilter>,
59
60    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
61    /// State of the proxy.
62    pub state: Option<StringFilter>,
63
64    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
65    /// City of the proxy.
66    pub city: Option<StringFilter>,
67
68    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
69    /// Mobile carrier of the proxy.
70    pub carrier: Option<StringFilter>,
71
72    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
73    ///  Autonomous System Number (ASN).
74    pub asn: Option<Asn>,
75}
76
77#[cfg(feature = "memory-db")]
78/// Validate the proxy is valid according to rules that are not enforced by the type system.
79fn 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    /// Check if the proxy is a match for the given[`ProxyContext`] and [`ProxyFilter`].
87    #[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}