pub mod utils; use curl::easy::{Easy, List}; use std::env::var; use std::io::Read; use std::str::from_utf8; use std::{error::Error, net::SocketAddr}; use unicase::Ascii; use warp::hyper::HeaderMap; use warp::{http::Method, http::Response, hyper::body::Bytes, path::FullPath, Filter}; const FOR_COPY: [&'static str; 2] = ["Content-Type", "Cookie"]; const BASE: &str = "https://www.bankofmaldives.com.mv"; const ERR: &str = "{ \"code\": 407, \"message\": \"Proxy failed\" }"; fn get_base_url() -> String { match var("BML_PROXY_BASE") { Ok(base) => base, Err(_) => BASE.to_string(), } } fn get_proxied_body( uri: &String, method: &Method, headers: &HeaderMap, body: &Bytes, base: &String, ) -> Result<(String, Vec), Box> { let mut handle = Easy::new(); handle.url(format!("{}{}", base, uri).as_str())?; let mut custom_headers = List::new(); for (name, value) in headers.iter() { if FOR_COPY.iter().any(|v| Ascii::new(v) == &name) { match value.to_str() { Ok(val) => custom_headers.append(format!("{}: {}", name.as_str(), val).as_str())?, Err(_) => (), }; } } handle.http_headers(custom_headers)?; let body = std::str::from_utf8(body)?; let mut body_bytes = body.as_bytes(); if method == Method::POST { handle.post_field_size(body_bytes.len() as u64)?; } let mut body_buffer = Vec::new(); let mut headers = Vec::new(); { let mut transfer = handle.transfer(); if method == Method::POST { transfer.read_function(|into| { let read = body_bytes.read(into).unwrap_or(0); Ok(read) })?; } transfer.write_function(|data| { body_buffer.extend_from_slice(data); Ok(data.len()) })?; transfer.header_function(|header| { match from_utf8(header) { Ok(v) => headers.push(v.to_string()), Err(_) => (), } true })?; transfer.perform()?; } let cloned_buf = body_buffer.clone(); let s = from_utf8(&cloned_buf)?; Ok((s.to_string(), headers)) } fn handler( uri: FullPath, headers: HeaderMap, method: Method, body: Bytes, ip: Option, base: String, ) -> Result, warp::http::Error> { let uri = uri.as_str().clone().to_string(); let ip_str = match ip { Some(v) => format!("{}", v), None => "unknown".to_string(), }; println!("({}) {}", ip_str, uri); let proxy_response = get_proxied_body(&uri, &method, &headers, &body, &base); let (res, headers) = match proxy_response { Ok(v) => v, Err(e) => { println!("error: {}", e); (ERR.to_string(), vec![]) } }; let mut builder = Response::builder(); for header in headers.iter() { let split: Vec<&str> = header.split(": ").collect(); if split.len() == 2 { builder = builder.header(split[0], split[1].replace("\r\n", "")); } } builder.body(res) } #[tokio::main] async fn main() { let proxy_handler = warp::any() .and(utils::extract_request_data_filter()) .and(warp::filters::addr::remote()) .and(warp::any().map(move || get_base_url())) .map(handler); let log_warning = || { println!("This binary is meant to be used with LD_PRELOAD=\"/path/to/libcurl-impersonate-chrome.so\""); println!("If you're on Arch Linux, this library can be obtained via the AUR package 'libcurl-impersonate-bin'"); println!( "A common location for this library is '/usr/local/lib/libcurl-impersonate-chrome.so'" ); println!("\n[WARN] proceeding without libcurl-impersonate, expect cloudflare blockages\n"); }; match var("LD_PRELOAD") { Ok(v) => { if !v.contains("libcurl-impersonate-chrome.so") { log_warning(); } } Err(_) => log_warning(), } let port = var("BML_PROXY_PORT") .unwrap_or_default() .parse::() .unwrap_or(3030); println!("Proxy running on port: {}", port); println!("Proxy base URL: {}", get_base_url()); println!("Available environment variables for modification: BML_PROXY_PORT, BML_PROXY_BASE\n"); warp::serve(proxy_handler).run(([0, 0, 0, 0], port)).await; }