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    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}