This commit is contained in:
taqin
2026-04-19 21:10:40 +07:00
parent 5fdd214fdc
commit 27381d4e37
211 changed files with 53571 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
use std::sync::Arc;
use std::time::Duration;
use futures::stream::{self, StreamExt};
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Emitter};
use tokio::sync::Semaphore;
#[derive(Debug, Clone, Deserialize)]
pub struct HttpProbeRequest {
pub targets: Vec<String>,
pub ports: Option<Vec<u16>>,
pub concurrency: usize,
pub timeout_ms: u64,
pub follow_redirects: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct HttpProbeResult {
pub url: String,
pub status: u16,
pub title: Option<String>,
pub server: Option<String>,
pub content_length: Option<u64>,
pub tech: Vec<String>,
}
static TITLE_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?is)<title[^>]*>(.*?)</title>").unwrap());
fn detect_tech(headers: &reqwest::header::HeaderMap, body: &str) -> Vec<String> {
let mut tech = Vec::new();
let get = |k: &str| headers.get(k).and_then(|v| v.to_str().ok()).unwrap_or("");
let server = get("server").to_lowercase();
let powered = get("x-powered-by").to_lowercase();
if server.contains("nginx") { tech.push("nginx".into()); }
if server.contains("apache") { tech.push("apache".into()); }
if server.contains("cloudflare") { tech.push("cloudflare".into()); }
if server.contains("iis") { tech.push("iis".into()); }
if powered.contains("php") { tech.push("php".into()); }
if powered.contains("express") { tech.push("express".into()); }
if powered.contains("asp.net") { tech.push("asp.net".into()); }
let b = body.to_lowercase();
if b.contains("wp-content") || b.contains("wp-includes") { tech.push("wordpress".into()); }
if b.contains("drupal-settings-json") { tech.push("drupal".into()); }
if b.contains("joomla") { tech.push("joomla".into()); }
if b.contains("__next_data__") { tech.push("next.js".into()); }
if b.contains("data-reactroot") || b.contains("__reactcontainer") { tech.push("react".into()); }
if b.contains("ng-version") { tech.push("angular".into()); }
if b.contains("window.laravel") || b.contains("laravel_session") { tech.push("laravel".into()); }
tech
}
async fn probe_one(client: &reqwest::Client, url: String) -> Option<HttpProbeResult> {
let resp = client.get(&url).send().await.ok()?;
let status = resp.status().as_u16();
let headers = resp.headers().clone();
let content_length = resp.content_length();
let server = headers
.get("server")
.and_then(|v| v.to_str().ok())
.map(String::from);
let body = resp.text().await.unwrap_or_default();
let title = TITLE_RE
.captures(&body)
.and_then(|c| c.get(1))
.map(|m| m.as_str().trim().to_string())
.filter(|s| !s.is_empty());
let tech = detect_tech(&headers, &body);
Some(HttpProbeResult { url, status, title, server, content_length, tech })
}
#[tauri::command]
pub async fn http_probe(
app: AppHandle,
req: HttpProbeRequest,
) -> Result<Vec<HttpProbeResult>, String> {
let client = reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.timeout(Duration::from_millis(req.timeout_ms))
.redirect(if req.follow_redirects {
reqwest::redirect::Policy::limited(5)
} else {
reqwest::redirect::Policy::none()
})
.user_agent("Mozilla/5.0 (PocketPentester)")
.build()
.map_err(|e| e.to_string())?;
let ports = req.ports.unwrap_or_else(|| vec![80, 443, 8080, 8443]);
let mut urls: Vec<String> = Vec::new();
for t in &req.targets {
let t = t.trim();
if t.starts_with("http://") || t.starts_with("https://") {
urls.push(t.to_string());
continue;
}
for p in &ports {
let scheme = if matches!(p, 443 | 8443) { "https" } else { "http" };
if *p == 80 || *p == 443 {
urls.push(format!("{scheme}://{t}"));
} else {
urls.push(format!("{scheme}://{t}:{p}"));
}
}
}
let total = urls.len();
let sem = Arc::new(Semaphore::new(req.concurrency.max(1)));
let done = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let client = Arc::new(client);
let results: Vec<HttpProbeResult> = stream::iter(urls.into_iter())
.map(|url| {
let sem = sem.clone();
let done = done.clone();
let client = client.clone();
let app = app.clone();
async move {
let _permit = sem.acquire().await.unwrap();
let res = probe_one(&client, url).await;
let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
let _ = app.emit("httpx:progress", serde_json::json!({"done": n, "total": total}));
if let Some(ref r) = res {
let _ = app.emit("httpx:hit", r.clone());
}
res
}
})
.buffer_unordered(req.concurrency.max(1))
.filter_map(|x| async move { x })
.collect()
.await;
let _ = app.emit("httpx:done", results.len());
Ok(results)
}