diff options
| author | ache <ache@ache.one> | 2026-03-07 20:17:21 +0100 |
|---|---|---|
| committer | ache <ache@ache.one> | 2026-03-07 20:17:21 +0100 |
| commit | 216b92f67d9f81cf083b3abd6cde14bf1d311653 (patch) | |
| tree | 87bdbb18a7685425918659e1f60ddb1c28dc5671 | |
| parent | Fix error message, wait second order (diff) | |
| -rw-r--r-- | src/main.rs | 200 |
1 files changed, 125 insertions, 75 deletions
diff --git a/src/main.rs b/src/main.rs index 657c512..d5bfd09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,33 +1,31 @@ // Init translations for current crate. rust_i18n::i18n!("src/locales"); -use std::convert::Infallible; -use std::net::{IpAddr, SocketAddr, Ipv4Addr}; use std::collections::HashMap; +use std::convert::Infallible; +use std::fs; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::str::FromStr; -use std::sync::mpsc::{Sender, channel}; -use std::{thread, time::Duration}; +use std::sync::mpsc::{channel, Sender}; use std::sync::Arc; -use std::fs; +use std::{thread, time::Duration}; -use serde_derive::{Serialize, Deserialize}; -use toml; +use serde_derive::{Deserialize, Serialize}; use hyper::server::conn::AddrStream; use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Request, Response, Server, StatusCode, Method, header::HeaderValue}; +use hyper::{header::HeaderValue, Body, Method, Request, Response, Server, StatusCode}; use rusqlite::{Connection, Transaction}; -use blake3; use rust_i18n::t; - enum VustQuery { Get, Like, Commit, } + struct VustMessage { req_type: VustQuery, ip: SocketAddr, @@ -35,38 +33,51 @@ struct VustMessage { res: Sender<Response<Body>>, } -fn respond(sc :StatusCode, mes : String) -> Response<Body> { +fn respond(sc: StatusCode, mes: String) -> Response<Body> { Response::builder() .status(sc) .body(Body::from(mes)) .unwrap() } -fn wrap_cors(mut res: Response<Body>, config :Arc<Config>) -> Response<Body> { - res.headers_mut().insert("Access-Control-Allow-Origin", HeaderValue::from_str(&config.cors_hosts).unwrap() ); - res.headers_mut().insert("Access-Control-Allow-Methods", HeaderValue::from_static("*")); - res.headers_mut().insert("Access-Control-Allow-Headers", HeaderValue::from_static("*")); +fn wrap_cors(mut res: Response<Body>, config: Arc<Config>) -> Response<Body> { + res.headers_mut().insert( + "Access-Control-Allow-Origin", + HeaderValue::from_str(&config.cors_hosts).unwrap(), + ); + res.headers_mut().insert( + "Access-Control-Allow-Methods", + HeaderValue::from_static("*"), + ); + res.headers_mut().insert( + "Access-Control-Allow-Headers", + HeaderValue::from_static("*"), + ); res } -fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config: Arc<Config>) -> Response<Body> { - const PREFIX_PATH :&str = "/like/"; - const DEFAULT_LOCALE :&str = "fr"; +fn handle( + req: Request<Body>, + addr: SocketAddr, + tx: Sender<VustMessage>, + config: Arc<Config>, +) -> Response<Body> { + const PREFIX_PATH: &str = "/like/"; + const DEFAULT_LOCALE: &str = "fr"; let path: String = req.uri().path().to_string(); rust_i18n::set_locale(match req.headers().contains_key("lang") { - true => match req.headers().get("lang") { + true => match req.headers().get("lang") { Some(h) => match h.to_str() { - Ok(l) => l, + Ok(l) => l, Err(_) => DEFAULT_LOCALE, }, - None => DEFAULT_LOCALE, + None => DEFAULT_LOCALE, }, false => DEFAULT_LOCALE, }); - let addr = match req.headers().contains_key("real-ip") { false => addr, true => { @@ -74,18 +85,24 @@ fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config: Some(ip) => ip.to_str(), None => { return respond(StatusCode::SERVICE_UNAVAILABLE, t!("hidden_ip").to_string()); - }, + } }; let real_ip_str = match real_ip_str { Ok(addr) => addr, Err(s) => { - return respond(StatusCode::SERVICE_UNAVAILABLE, format!("{}: {}.", t!("invalid_ip").to_string(), s.to_string())); + return respond( + StatusCode::SERVICE_UNAVAILABLE, + format!("{}: {}.", t!("invalid_ip"), s), + ); } }; - let ip :IpAddr = match real_ip_str.parse() { + let ip: IpAddr = match real_ip_str.parse() { Ok(addr) => addr, Err(_) => { - return respond(StatusCode::SERVICE_UNAVAILABLE, format!("{} {}.", t!("invalid_ip").to_string(), real_ip_str)); + return respond( + StatusCode::SERVICE_UNAVAILABLE, + format!("{} {}.", t!("invalid_ip"), real_ip_str), + ); } }; SocketAddr::new(ip, 0) @@ -98,19 +115,29 @@ fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config: Method::GET => VustQuery::Get, Method::OPTIONS => { return respond(StatusCode::OK, "WHAT ?".to_string()); - }, + } _ => { - return respond(StatusCode::METHOD_NOT_ALLOWED, t!("method_not_allowed").to_string()); - }, + return respond( + StatusCode::METHOD_NOT_ALLOWED, + t!("method_not_allowed").to_string(), + ); + } }; if !path.starts_with(PREFIX_PATH) { - return respond(StatusCode::BAD_REQUEST, format!("{} {}.", t!("bad_endpoint"), PREFIX_PATH)); + return respond( + StatusCode::BAD_REQUEST, + format!("{} {}.", t!("bad_endpoint"), PREFIX_PATH), + ); } - let path : String = path.chars().skip(PREFIX_PATH.len()).collect(); + let path: String = path.chars().skip(PREFIX_PATH.len()).collect(); - if path.contains("/") || path.len() > 128 || path.len() == 0 || !config.list_articles.contains(&path) { + if path.contains("/") + || path.is_empty() + || path.len() > 128 + || !config.list_articles.contains(&path) + { return respond(StatusCode::BAD_REQUEST, t!("bad_request").to_string()); } @@ -120,8 +147,8 @@ fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config: path }; - let (ttx , rx) = channel(); - let message = VustMessage{ + let (ttx, rx) = channel(); + let message = VustMessage { req_type: query_type, ip: addr, path: article_key_db, @@ -136,11 +163,14 @@ fn handle(req: Request<Body>, addr: SocketAddr, tx: Sender<VustMessage>, config: } } -fn do_get(tr: &Transaction, path : String) -> Response<Body> { - - for _ in [1..3] { - let mut req_prepared = tr.prepare("SELECT cast(SUM(number) as text) FROM likes WHERE path = ?").unwrap(); - let mut rows = req_prepared.query(rusqlite::params![path.as_str()]).unwrap(); +fn do_get(tr: &Transaction, path: String) -> Response<Body> { + for _ in 1..3 { + let mut req_prepared = tr + .prepare("SELECT cast(SUM(number) as text) FROM likes WHERE path = ?") + .unwrap(); + let mut rows = req_prepared + .query(rusqlite::params![path.as_str()]) + .unwrap(); let first_row = rows.next(); @@ -151,54 +181,60 @@ fn do_get(tr: &Transaction, path : String) -> Response<Body> { } let first_row = first_row.unwrap(); - // Empty row ! Nobody like what I do. π + // Empty row! Nobody like what I do. π if first_row.is_none() { - return respond(StatusCode::OK, "0".to_string()) + return respond(StatusCode::OK, "0".to_string()); } let first_row = first_row.unwrap(); match first_row.get(0) { Ok(nb_likes) => return respond(StatusCode::OK, nb_likes), - // In case of NULL or not a Integer value: - Err(_) => { - return respond(StatusCode::OK, "β
".to_string()) - } + // In case of NULL or not an Integer value: + Err(_) => return respond(StatusCode::OK, "β
".to_string()), } } respond(StatusCode::OK, "β".to_string()) } -fn do_like(tr: &Transaction, ip : SocketAddr, path : String) -> Response<Body> { +fn do_like(tr: &Transaction, ip: SocketAddr, path: String) -> Response<Body> { let hash_ip = match ip.ip() { IpAddr::V4(ip) => blake3::hash(&ip.octets()).to_hex(), IpAddr::V6(ip) => blake3::hash(&ip.octets()).to_hex(), }; - for _ in [1..7] { + for _ in 1..7 { let mut req_prepared = tr.prepare("SELECT number, cast(lastMod as UNSIGNED INT), cast(unixepoch() as UNSIGNED INT) FROM likes WHERE ip_hash = ? and path = ?").unwrap(); // , path.as_str()]).unwrap(); - let mut rows = req_prepared.query(rusqlite::params![hash_ip.as_str(), path.as_str()]).unwrap(); + let mut rows = req_prepared + .query(rusqlite::params![hash_ip.as_str(), path.as_str()]) + .unwrap(); let first_row = rows.next(); match first_row { Ok(None) => { - let res = tr.execute("INSERT OR IGNORE INTO likes VALUES (?, ?, unixepoch(), 1)", [hash_ip.as_str(), path.as_str()]); + let res = tr.execute( + "INSERT OR IGNORE INTO likes VALUES (?, ?, unixepoch(), 1)", + [hash_ip.as_str(), path.as_str()], + ); if res.is_err() { eprintln!("Error doing the request {:?}", res.err()); continue; } - return respond(StatusCode::OK, t!("thanks").to_string()) - }, + return respond(StatusCode::OK, t!("thanks").to_string()); + } Ok(Some(t)) => { - let number : u64 = t.get(0).unwrap(); - let time : u64 = t.get(1).unwrap(); - let now : u64= t.get(2).unwrap(); + let number: u64 = t.get(0).unwrap(); + let time: u64 = t.get(1).unwrap(); + let now: u64 = t.get(2).unwrap(); if number > 31 { - return respond(StatusCode::RANGE_NOT_SATISFIABLE, format!("{} π x ({})", t!("too_many_hears"), number).to_string()); + return respond( + StatusCode::RANGE_NOT_SATISFIABLE, + format!("{} π x ({})", t!("too_many_hears"), number).to_string(), + ); } let limite = (1 << number) / 10; // 2^likes / Cst @@ -206,7 +242,15 @@ fn do_like(tr: &Transaction, ip : SocketAddr, path : String) -> Response<Body> { if dtime < limite { let time_remaining = limite - dtime; - return respond(StatusCode::TOO_MANY_REQUESTS, format!("{} {}s {}", t!("wait_prefix"), time_remaining, t!("wait_message_suffix"))); + return respond( + StatusCode::TOO_MANY_REQUESTS, + format!( + "{} {}s {}", + t!("wait_prefix"), + time_remaining, + t!("wait_message_suffix") + ), + ); } let res = tr.execute("UPDATE likes SET number = number + 1, lastMod = unixepoch() WHERE ip_hash = ? and path = ?", [hash_ip.as_str(), path.as_str()]); @@ -214,17 +258,21 @@ fn do_like(tr: &Transaction, ip : SocketAddr, path : String) -> Response<Body> { if res.is_err() { eprintln!("Error doing the request {:?}", res.err()); - continue + continue; } - return respond(StatusCode::OK, format!("{} π x {}", t!("thanks") , number + 1).to_string()) - }, - Err(_) => { - continue - }, + return respond( + StatusCode::OK, + format!("{} π x {}", t!("thanks"), number + 1).to_string(), + ); + } + Err(_) => continue, }; } - respond(StatusCode::INTERNAL_SERVER_ERROR, t!("unknow_error").to_string()) + respond( + StatusCode::INTERNAL_SERVER_ERROR, + t!("unknow_error").to_string(), + ) } #[derive(Serialize, Deserialize)] @@ -263,7 +311,8 @@ fn get_config() -> String { aliases.c-language-quirks='bizarreries-du-langage-c' aliases.web-image-formats='formats-images-web' aliases.rail-and-advertising='les-trains-et-la-publicit%C3%A9' - "#.to_string(); + "# + .to_string(); }; fs::read_to_string(path).expect("Unable to read config file") @@ -276,15 +325,14 @@ async fn main() { let addr = SocketAddr::new(ip, config.port); eprintln!("Listening on {}", addr); - let (tx , rx) = channel(); - + let (tx, rx) = channel(); let ttx = tx.clone(); thread::spawn(move || { let tx = ttx.clone(); loop { - let (txx , _) = channel(); - let res = tx.send(VustMessage{ + let (txx, _) = channel(); + let res = tx.send(VustMessage { req_type: VustQuery::Commit, ip: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080), path: "".to_string(), @@ -301,12 +349,11 @@ async fn main() { thread::spawn(move || { let mut conn = Connection::open("likes.db").unwrap(); loop { - let tr = conn.transaction().unwrap(); let mut should_commit = false; loop { - let recv : VustMessage = rx.recv().unwrap(); + let recv: VustMessage = rx.recv().unwrap(); match recv.req_type { VustQuery::Like => { let res = do_like(&tr, recv.ip, recv.path); @@ -316,13 +363,13 @@ async fn main() { } continue; - }, + } VustQuery::Get => { let res = recv.res.send(do_get(&tr, recv.path)); if res.is_ok() || res.is_err() { continue; } - }, + } VustQuery::Commit => { if should_commit { break; @@ -337,7 +384,7 @@ async fn main() { // The closure passed to `make_service_fn` is executed each time a new // connection is established and returns a future that resolves to a // service. - let make_service = make_service_fn(|conn: &AddrStream| { + let make_service = make_service_fn(|conn: &AddrStream| { // The closure passed to `service_fn` is executed each time a request // arrives on the connection and returns a future that resolves // to a response. @@ -346,11 +393,14 @@ async fn main() { let config = config.clone(); async move { - Ok::<_, Infallible>(service_fn( move |req| { + Ok::<_, Infallible>(service_fn(move |req| { let tx = tx.clone(); let config = config.clone(); async move { - Ok::<_, Infallible>(wrap_cors(handle(req, remote_addr, tx, config.clone()), config.clone())) + Ok::<_, Infallible>(wrap_cors( + handle(req, remote_addr, tx, config.clone()), + config.clone(), + )) } })) } |