diff --git a/src/proxies/pimpbunny.rs b/src/proxies/pimpbunny.rs index 93a2011..2608495 100644 --- a/src/proxies/pimpbunny.rs +++ b/src/proxies/pimpbunny.rs @@ -152,17 +152,12 @@ impl PimpbunnyProxy { /// (including 403 "access denied"). /// /// The PHPSESSID cookie — acquired when we fetched the detail page — must be - /// forwarded; the server won't issue the 302 without it. - async fn follow_302(url: &str, requester: &Requester) -> Option { - let client = wreq::Client::builder() - .redirect(wreq::redirect::Policy::none()) - .build() - .ok()?; - let mut req = client.get(url); - if let Some(cookie) = requester.cookie_header_for_url(url) { - req = req.header("Cookie", cookie); - } - let resp = req.send().await.ok()?; + /// forwarded; the server won't issue the 302 without it. Uses the shared + /// Requester's Cloudflare-resilient client (Firefox emulation) rather than a bare + /// wreq::Client — an unfingerprinted client gets blocked by Cloudflare from + /// datacenter/production IPs even though it works from less-scrutinized networks. + async fn follow_302(url: &str, requester: &mut Requester) -> Option { + let resp = requester.get_no_redirect_raw(url).await.ok()?; if resp.status().as_u16() != 302 { return None; } @@ -188,7 +183,7 @@ impl PimpbunnyProxy { let Some(decoded) = Self::decode_encoded_url(encoded_url, &license_code) else { continue; }; - if let Some(location) = Self::follow_302(&decoded, requester).await { + if let Some(location) = Self::follow_302(&decoded, &mut *requester).await { return Some(location); } } diff --git a/src/proxy.rs b/src/proxy.rs index 76935b8..f0c3bb3 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -197,10 +197,10 @@ async fn proxy2redirect( ) -> Result { let proxy = get_proxy(req.uri().to_string().split("/").collect::>()[2]).unwrap(); let endpoint = req.match_info().query("endpoint").to_string(); - let video_url = match proxy.get_video_url(endpoint, requester).await { - url if url != "" => url, - _ => "Error".to_string(), - }; + let video_url = proxy.get_video_url(endpoint, requester).await; + if video_url.is_empty() { + return Ok(web::HttpResponse::BadGateway().finish()); + } Ok(web::HttpResponse::Found() .header("Location", video_url) .finish()) diff --git a/src/util/requester.rs b/src/util/requester.rs index 1bc0e36..e24649b 100644 --- a/src/util/requester.rs +++ b/src/util/requester.rs @@ -315,6 +315,54 @@ impl Requester { Ok(response) } + /// Like [`get_raw`], but disables redirect-following so a 3xx response's + /// `Location` header can be inspected directly. Uses the same + /// Cloudflare-resilient client (Firefox emulation, shared cookie jar, + /// FlareSolverr-derived user agent) as every other request path — unlike + /// a bare `wreq::Client`, which Cloudflare is far more likely to flag, + /// especially from datacenter/production IPs. + pub async fn get_no_redirect_raw(&mut self, url: &str) -> Result { + let cookie_preview = self + .cookie_header_for_url(url) + .map(|cookie| crate::util::flow_debug::preview(&cookie, 160)) + .unwrap_or_else(|| "none".to_string()); + #[cfg(not(feature = "debug"))] + let _ = &cookie_preview; + crate::flow_debug!( + "trace={} requester get_no_redirect_raw url={} cookies={} proxy={}", + self.debug_trace_id().unwrap_or("none"), + crate::util::flow_debug::preview(url, 120), + cookie_preview, + self.proxy + ); + let mut builder = Client::builder() + .cert_verification(false) + .emulation(Emulation::Firefox136) + .cookie_provider(self.cookie_jar.clone()) + .redirect(Policy::none()); + if let Some(user_agent) = self.user_agent.as_deref() { + let mut headers = HeaderMap::new(); + if let Ok(value) = HeaderValue::from_str(user_agent) { + headers.insert(USER_AGENT, value); + builder = builder.default_headers(headers); + } + } + let client = builder.build().expect("Failed to create HTTP client"); + + let mut request = client.get(url).version(Version::HTTP_11); + + if self.proxy { + if let Ok(proxy_url) = env::var("BURP_URL") { + let proxy = Proxy::all(&proxy_url).unwrap(); + request = request.proxy(proxy); + } + } + + let response = request.send().await?; + self.store_response_cookies(url, &response); + Ok(response) + } + pub async fn get_raw_with_headers( &mut self, url: &str,