web speedtest fixes
This commit is contained in:
+14
-21
@@ -1,6 +1,7 @@
|
||||
use axum::{
|
||||
body::Bytes,
|
||||
http::{header, Method, StatusCode},
|
||||
extract::DefaultBodyLimit,
|
||||
http::{header, HeaderValue, Method, StatusCode},
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
@@ -10,7 +11,7 @@ use reqwest::Client;
|
||||
use rustrict::CensorStr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::LazyLock;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tower_http::cors::CorsLayer;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct CommentPayload {
|
||||
@@ -80,6 +81,7 @@ const SAFE_WORDS: &[&str] = &[
|
||||
"даунгрейд",
|
||||
"адаун",
|
||||
];
|
||||
|
||||
// Заменяет легальные слова-исключения на плейсхолдер перед проверкой
|
||||
fn apply_allowlist(text: &str) -> String {
|
||||
let mut safe_text = text.to_string();
|
||||
@@ -88,6 +90,7 @@ fn apply_allowlist(text: &str) -> String {
|
||||
}
|
||||
safe_text
|
||||
}
|
||||
|
||||
// Функция жесткой нормализации текста перед проверкой
|
||||
fn normalize_for_filter(input: &str) -> String {
|
||||
let mut normalized = String::new();
|
||||
@@ -96,19 +99,16 @@ fn normalize_for_filter(input: &str) -> String {
|
||||
let lower_input = input.to_lowercase();
|
||||
|
||||
for ch in lower_input.chars() {
|
||||
// СОХРАНЯЕМ ПРОБЕЛЫ, чтобы не склеивать соседние слова ("нас укачало" != "насукачало")
|
||||
if ch.is_whitespace() {
|
||||
normalized.push(' ');
|
||||
prev_char = Some(' ');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Вырезаем пунктуацию (чтобы ловить х.у.й), но пропускаем дальше только буквы/цифры
|
||||
if !ch.is_alphanumeric() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Замена "leetspeak" (англо-русская подмена и цифры)
|
||||
let mapped = match ch {
|
||||
'a' | '@' => 'а',
|
||||
'b' => 'в',
|
||||
@@ -126,8 +126,6 @@ fn normalize_for_filter(input: &str) -> String {
|
||||
_ => ch,
|
||||
};
|
||||
|
||||
// Сжатие повторяющихся символов (хххуууууууй -> хуй)
|
||||
// Не сжимаем пробелы, только буквы
|
||||
if let Some(p) = prev_char {
|
||||
if p == mapped && mapped != ' ' {
|
||||
continue;
|
||||
@@ -141,14 +139,12 @@ fn normalize_for_filter(input: &str) -> String {
|
||||
normalized
|
||||
}
|
||||
|
||||
// Функция проверки на спам (КАПС или слишком длинные бессмысленные слова)
|
||||
fn is_spam(input: &str) -> bool {
|
||||
let alpha_chars: String = input.chars().filter(|c| c.is_alphabetic()).collect();
|
||||
if alpha_chars.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Проверка на КАПС (> 70% заглавных при длине > 10)
|
||||
let uppercase_count = alpha_chars.chars().filter(|c| c.is_uppercase()).count();
|
||||
let uppercase_ratio = uppercase_count as f64 / alpha_chars.len() as f64;
|
||||
|
||||
@@ -156,9 +152,7 @@ fn is_spam(input: &str) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Проверка на зажатую клавиатуру (aaaaaaaaaaaa)
|
||||
for word in input.split_whitespace() {
|
||||
// Игнорируем длинные ссылки (http/https)
|
||||
if word.len() > 35 && !word.starts_with("http") {
|
||||
return true;
|
||||
}
|
||||
@@ -169,10 +163,12 @@ fn is_spam(input: &str) -> bool {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Настраиваем правила CORS
|
||||
// 🔥 Исправленный CORS: Явно указываем протоколы и разрешаем локалхост для тестов
|
||||
let cors = CorsLayer::new()
|
||||
// Разрешаем запросы только с твоего фронтенда
|
||||
.allow_origin("netrunner-vpn.com") // Для разработки можно Any, для прода лучше конкретный URL
|
||||
.allow_origin([
|
||||
"http://localhost:3000".parse::<HeaderValue>().unwrap(),
|
||||
"https://netrunner-vpn.com".parse::<HeaderValue>().unwrap(),
|
||||
])
|
||||
.allow_methods([Method::GET, Method::POST])
|
||||
.allow_headers([header::CONTENT_TYPE]);
|
||||
|
||||
@@ -181,7 +177,8 @@ async fn main() {
|
||||
.route("/api/speedtest/ping", get(|| async { "pong" }))
|
||||
.route("/api/speedtest/download", get(speedtest_download))
|
||||
.route("/api/speedtest/upload", post(speedtest_upload))
|
||||
// Применяем CORS ко всем роутам
|
||||
// 🔥 Увеличиваем лимит тела запроса до 50 Мегабайт для спидтеста (дефолт 2MB)
|
||||
.layer(DefaultBodyLimit::max(50 * 1024 * 1024))
|
||||
.layer(cors);
|
||||
|
||||
println!("🚀 Netrunner Gateway started on 0.0.0.0:3001");
|
||||
@@ -190,7 +187,6 @@ async fn main() {
|
||||
}
|
||||
|
||||
async fn create_comment(Json(payload): Json<CommentPayload>) -> impl IntoResponse {
|
||||
// --- 1. Проверка на спам ---
|
||||
if is_spam(&payload.message) || is_spam(&payload.nickname) {
|
||||
println!("🗑️ Blocked by SPAM filter: {}", payload.nickname);
|
||||
return (
|
||||
@@ -199,7 +195,6 @@ async fn create_comment(Json(payload): Json<CommentPayload>) -> impl IntoRespons
|
||||
);
|
||||
}
|
||||
|
||||
// --- 2. Английский фильтр (Crate rustrict) ---
|
||||
if payload.message.is_inappropriate() || payload.nickname.is_inappropriate() {
|
||||
println!("🛡️ Blocked by rustrict: {}", payload.nickname);
|
||||
return (
|
||||
@@ -208,11 +203,9 @@ async fn create_comment(Json(payload): Json<CommentPayload>) -> impl IntoRespons
|
||||
);
|
||||
}
|
||||
|
||||
// --- 3. Русский хардкорный фильтр с защитой от ложных срабатываний ---
|
||||
let normalized_msg = normalize_for_filter(&payload.message);
|
||||
let normalized_nick = normalize_for_filter(&payload.nickname);
|
||||
|
||||
// Сначала защищаем нормальные слова, затем проверяем регуляркой
|
||||
let safe_msg = apply_allowlist(&normalized_msg);
|
||||
let safe_nick = apply_allowlist(&normalized_nick);
|
||||
|
||||
@@ -227,7 +220,6 @@ async fn create_comment(Json(payload): Json<CommentPayload>) -> impl IntoRespons
|
||||
);
|
||||
}
|
||||
|
||||
// --- 4. ОТПРАВКА В POCKETBASE ---
|
||||
let pb_base_url =
|
||||
std::env::var("PB_INTERNAL_URL").unwrap_or_else(|_| "http://localhost:8090".to_string());
|
||||
let pb_url = format!("{}/api/collections/comments/records", pb_base_url);
|
||||
@@ -253,13 +245,14 @@ async fn create_comment(Json(payload): Json<CommentPayload>) -> impl IntoRespons
|
||||
async fn speedtest_download() -> impl IntoResponse {
|
||||
let payload_size = 25 * 1024 * 1024; // 25 MB
|
||||
let data = vec![0u8; payload_size];
|
||||
|
||||
(
|
||||
[(axum::http::header::CONTENT_TYPE, "application/octet-stream")],
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
// 🔥 Возвращаем `body: Bytes`. Благодаря `DefaultBodyLimit` в main(),
|
||||
// Axum 0.7 успешно примет в память файл до 50МБ без ручной работы со стримами.
|
||||
async fn speedtest_upload(body: Bytes) -> impl IntoResponse {
|
||||
(StatusCode::OK, format!("Received {} bytes", body.len()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user