use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue}, Method, }; use serde::{Deserialize, Serialize}; use std::time::Duration; #[derive(Clone, Deserialize, Serialize)] pub struct ProxyHttpHeader { name: String, value: String, } #[derive(Deserialize)] pub struct ProxyHttpRequest { url: String, method: Option, headers: Option>, body: Option>, max_response_bytes: Option, follow_redirects: Option, timeout_ms: Option, } #[derive(Serialize)] pub struct ProxyHttpResponse { status: u16, headers: Vec, body: Vec, url: String, } fn request_headers(headers: Option>) -> HeaderMap { let mut mapped = HeaderMap::new(); for header in headers.unwrap_or_default() { let Ok(header_name) = HeaderName::from_bytes(header.name.as_bytes()) else { continue; }; let Ok(header_value) = HeaderValue::from_str(&header.value) else { continue; }; mapped.insert(header_name, header_value); } mapped } fn request_method(method: Option) -> Result { let raw = method.unwrap_or_else(|| "GET".into()); Method::from_bytes(raw.as_bytes()).map_err(|e| e.to_string()) } #[tauri::command] pub async fn proxy_http_request(request: ProxyHttpRequest) -> Result { let parsed = reqwest::Url::parse(&request.url).map_err(|e| e.to_string())?; if !matches!(parsed.scheme(), "http" | "https") { return Err("Only HTTP(S) requests are supported".into()); } let redirect_policy = if request.follow_redirects.unwrap_or(true) { reqwest::redirect::Policy::limited(5) } else { reqwest::redirect::Policy::none() }; let client = reqwest::Client::builder() .redirect(redirect_policy) .timeout(Duration::from_secs(30)) .build() .map_err(|e| e.to_string())?; let mut builder = client .request(request_method(request.method)?, parsed) .headers(request_headers(request.headers)); if let Some(body) = request.body { builder = builder.body(body); } if let Some(timeout_ms) = request.timeout_ms { if timeout_ms == 0 { return Err("timeoutMs must be greater than 0".into()); } builder = builder.timeout(Duration::from_millis(timeout_ms)); } let mut response = builder.send().await.map_err(|e| e.to_string())?; let status = response.status().as_u16(); let url = response.url().to_string(); let headers = response .headers() .iter() .filter_map(|(name, value)| { Some(ProxyHttpHeader { name: name.as_str().to_owned(), value: value.to_str().ok()?.to_owned(), }) }) .collect(); if let (Some(limit), Some(content_length)) = (request.max_response_bytes, response.content_length()) { if content_length > limit as u64 { return Err("HTTP response exceeds the configured size limit".into()); } } let mut body = Vec::new(); while let Some(chunk) = response.chunk().await.map_err(|e| e.to_string())? { if request .max_response_bytes .is_some_and(|limit| body.len() + chunk.len() > limit) { return Err("HTTP response exceeds the configured size limit".into()); } body.extend_from_slice(&chunk); } Ok(ProxyHttpResponse { status, headers, body, url, }) }