Skip to main content

capmonstercloud_client/requests/
imperva.rs

1use serde::Serialize;
2
3use super::{require_non_empty, CaptchaRequest};
4use crate::error::Result;
5use crate::proxy::ProxyInfo;
6use crate::timeouts::GetResultTimeouts;
7
8/// Imperva (Incapsula) challenge data. Values are dynamic: extract them right before
9/// creating the task.
10#[derive(Debug, Clone, Serialize)]
11pub struct ImpervaMetadata {
12    /// Name of the Incapsula JS file, e.g. `_Incapsula_Resource?SWJIYLWA=719d34d3...`.
13    #[serde(rename = "incapsulaScriptUrl")]
14    pub incapsula_script_url: String,
15    /// Incapsula cookies, e.g. `"incap_sess_*=...; visid_incap_*=..."`.
16    #[serde(rename = "incapsulaCookies")]
17    pub incapsula_cookies: String,
18    /// Name of the endpoint the reese84 fingerprint is sent to (ends with `?d=site.com`).
19    #[serde(rename = "reese84UrlEndpoint", skip_serializing_if = "Option::is_none")]
20    pub reese84_url_endpoint: Option<String>,
21}
22
23impl ImpervaMetadata {
24    pub fn new(
25        incapsula_script_url: impl Into<String>,
26        incapsula_cookies: impl Into<String>,
27    ) -> Self {
28        Self {
29            incapsula_script_url: incapsula_script_url.into(),
30            incapsula_cookies: incapsula_cookies.into(),
31            reese84_url_endpoint: None,
32        }
33    }
34
35    pub fn with_reese84_url_endpoint(mut self, value: impl Into<String>) -> Self {
36        self.reese84_url_endpoint = Some(value.into());
37        self
38    }
39}
40
41/// Imperva / Incapsula (`CustomTask`, class `Imperva`). Your own proxy is required.
42#[derive(Debug, Clone, Serialize)]
43pub struct ImpervaCustomTaskRequest {
44    /// Address of the main page where Incapsula is located.
45    #[serde(rename = "websiteURL")]
46    pub website_url: String,
47    pub metadata: ImpervaMetadata,
48    /// Required proxy (non-transparent, not local).
49    #[serde(flatten)]
50    pub proxy: ProxyInfo,
51    /// Browser User-Agent (current Windows UA supported by CapMonster Cloud).
52    #[serde(rename = "userAgent", skip_serializing_if = "Option::is_none")]
53    pub user_agent: Option<String>,
54}
55
56impl ImpervaCustomTaskRequest {
57    pub fn new(
58        website_url: impl Into<String>,
59        metadata: ImpervaMetadata,
60        proxy: ProxyInfo,
61    ) -> Self {
62        Self {
63            website_url: website_url.into(),
64            metadata,
65            proxy,
66            user_agent: None,
67        }
68    }
69
70    pub fn with_user_agent(mut self, value: impl Into<String>) -> Self {
71        self.user_agent = Some(value.into());
72        self
73    }
74}
75
76impl CaptchaRequest for ImpervaCustomTaskRequest {
77    const TASK_TYPE: &'static str = "CustomTask";
78    const CLASS: Option<&'static str> = Some("Imperva");
79
80    fn timeouts(&self) -> GetResultTimeouts {
81        GetResultTimeouts::secs(1.0, 0.0, 1.0, 15.0)
82    }
83
84    fn validate(&self) -> Result<()> {
85        require_non_empty("websiteURL", &self.website_url)?;
86        require_non_empty(
87            "metadata.incapsulaScriptUrl",
88            &self.metadata.incapsula_script_url,
89        )?;
90        require_non_empty(
91            "metadata.incapsulaCookies",
92            &self.metadata.incapsula_cookies,
93        )?;
94        Ok(())
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::proxy::ProxyType;
102    use serde_json::json;
103
104    fn proxy() -> ProxyInfo {
105        ProxyInfo::new(ProxyType::Socks5, "1.2.3.4", 1080).with_auth("u", "p")
106    }
107
108    #[test]
109    fn minimal_task() {
110        let task = ImpervaCustomTaskRequest::new(
111            "https://example.com",
112            ImpervaMetadata::new("_Incapsula_Resource?x=1", "incap_sess_1=a"),
113            proxy(),
114        )
115        .to_task()
116        .unwrap();
117        assert_eq!(
118            task,
119            json!({
120                "type": "CustomTask", "class": "Imperva", "websiteURL": "https://example.com",
121                "metadata": {"incapsulaScriptUrl": "_Incapsula_Resource?x=1", "incapsulaCookies": "incap_sess_1=a"},
122                "proxyType": "socks5", "proxyAddress": "1.2.3.4", "proxyPort": 1080,
123                "proxyLogin": "u", "proxyPassword": "p"
124            })
125        );
126    }
127
128    #[test]
129    fn full_task() {
130        let task = ImpervaCustomTaskRequest::new(
131            "https://example.com",
132            ImpervaMetadata::new("S", "C").with_reese84_url_endpoint("E?d=example.com"),
133            proxy(),
134        )
135        .with_user_agent("UA")
136        .to_task()
137        .unwrap();
138        assert_eq!(
139            task,
140            json!({
141                "type": "CustomTask", "class": "Imperva", "websiteURL": "https://example.com",
142                "metadata": {"incapsulaScriptUrl": "S", "incapsulaCookies": "C", "reese84UrlEndpoint": "E?d=example.com"},
143                "proxyType": "socks5", "proxyAddress": "1.2.3.4", "proxyPort": 1080,
144                "proxyLogin": "u", "proxyPassword": "p", "userAgent": "UA"
145            })
146        );
147    }
148
149    #[test]
150    fn empty_required_strings_are_rejected() {
151        let err = ImpervaCustomTaskRequest::new("", ImpervaMetadata::new("S", "C"), proxy())
152            .to_task()
153            .unwrap_err();
154        assert!(matches!(err, crate::error::Error::Validation(ref m) if m.contains("websiteURL")));
155        let err = ImpervaCustomTaskRequest::new(
156            "https://example.com",
157            ImpervaMetadata::new("", "C"),
158            proxy(),
159        )
160        .to_task()
161        .unwrap_err();
162        assert!(
163            matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.incapsulaScriptUrl"))
164        );
165        let err = ImpervaCustomTaskRequest::new(
166            "https://example.com",
167            ImpervaMetadata::new("S", ""),
168            proxy(),
169        )
170        .to_task()
171        .unwrap_err();
172        assert!(
173            matches!(err, crate::error::Error::Validation(ref m) if m.contains("metadata.incapsulaCookies"))
174        );
175    }
176}