diff --git a/Cargo.lock b/Cargo.lock index 699eea33..4abc7779 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -476,119 +476,17 @@ dependencies = [ "syn", ] -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - [[package]] name = "async-lock" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" version = "0.1.89" @@ -726,9 +624,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -760,19 +658,6 @@ dependencies = [ "objc2", ] -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel 2.5.0", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - [[package]] name = "brotli" version = "8.0.3" @@ -1447,12 +1332,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "event-listener" version = "5.4.1" @@ -1470,7 +1349,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener", "pin-project-lite", ] @@ -1637,19 +1516,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.32" @@ -1746,18 +1612,6 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" version = "0.3.27" @@ -1835,9 +1689,9 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ "hashbrown 0.16.1", ] @@ -1848,12 +1702,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" @@ -2289,15 +2137,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -2383,9 +2222,6 @@ name = "log" version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" -dependencies = [ - "value-bag", -] [[package]] name = "lru-slab" @@ -2907,23 +2743,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - [[package]] name = "pkcs1" version = "0.8.0-rc.4" @@ -2978,20 +2797,6 @@ dependencies = [ "plotters-backend", ] -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - [[package]] name = "potential_utf" version = "0.1.5" @@ -4091,7 +3896,7 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener 5.4.1", + "event-listener", "futures-core", "futures-intrusive", "futures-io", @@ -4554,14 +4359,13 @@ checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "torrust-actix" -version = "4.2.13" +version = "4.2.14" dependencies = [ "actix", "actix-cors", "actix-web", "actix-web-actors", "ahash", - "async-std", "async-trait", "base64", "binascii", @@ -4920,12 +4724,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "value-bag" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" - [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index 9099d05c..1d87a1e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "3" [package] name = "torrust-actix" -version = "4.2.13" +version = "4.2.14" edition = "2024" rust-version = "1.88.0" license = "MIT" @@ -42,7 +42,6 @@ tokio-tungstenite = { version = "0.29.0", features = ["rustls-tls-webpki-roots"] rmp-serde = "1.3.1" once_cell = "1.21.4" uuid = { version = "1.23.2", features = ["v4"] } -async-std = "1.13.2" binascii = "0.1.4" bip_bencode = "0.4.4" byteorder = "1.5.0" @@ -115,7 +114,7 @@ harness = false name = "Torrust Actix" identifier = "com.power2all.torrust-actix" icon = ["icon.ico"] -version = "4.2.13" +version = "4.2.14" copyright = "Copyright (c) 2024-2026 Power2All" category = "Public Utility" short_description = "BitTorrent Tracker" diff --git a/README.md b/README.md index d30febd3..254f23cf 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ This project originated from Torrust-Tracker code originally developed by Mick v * [BEP 48](https://www.bittorrent.org/beps/bep_0048.html): Tracker Protocol Extension: Scrape ## Getting Started -You can get the latest binaries from [releases](https://github.com/Power2All/torrust-actix/releases) or follow the install from scratch instructions below. +You can get the latest binaries from [releases](https://github.com/Power2All/torrust-actix/releases) or follow the installation from scratch instructions below. ### Install From Scratch 1. Clone the repository: @@ -85,7 +85,7 @@ cargo build --release --workspace ``` ### Usage -Run the code using `--help` argument for using in your enironment: +Run the code using `--help` argument for using in your environment: ```bash ./target/release/torrust-actix --help ``` @@ -173,6 +173,7 @@ DATABASE__UPDATE_PEERS DATABASE__PATH DATABASE__ENGINE DATABASE__PERSISTENT_INTERVAL +DATABASE__CHUNK_SIZE DATABASE_STRUCTURE__TORRENTS__BIN_TYPE_INFOHASH DATABASE_STRUCTURE__TORRENTS__TABLE_NAME @@ -250,6 +251,47 @@ UDP_0_SIMPLE_PROXY_PROTOCOL UDP_0_RECEIVE_METHOD ``` +#### Database commit chunk size + +`database.chunk_size` controls how many rows the tracker writes per SQL transaction when it flushes torrent/user updates to the database (once per `persistent_interval`). Instead of committing the whole sync batch in a single long-running transaction — which holds row/foreign-key locks for the entire batch and can block other applications writing to the same database (e.g. a website doing `DELETE`/`UPDATE`, leading to lock-wait timeouts) — the tracker commits every `chunk_size` rows, releasing locks frequently. + +```toml +[database] +chunk_size = 1000 # rows per transaction (default: 1000; 0 = commit the whole batch in one transaction) +``` + +Lower the value (e.g. `500` or `250`) if another application sharing the database still experiences lock contention; raise it (or set `0`) for maximum write throughput when the tracker owns the database exclusively. Applies to the high-volume `torrents` and `users` sync tables on MySQL, PostgreSQL, and SQLite. + +--- + +## API Authentication + +Every `/api/...` endpoint (and `/stats`, `/metrics`) is protected by the API key configured in `tracker.api_key` (or the `TRACKER__API_KEY` environment variable). The key can be supplied in two ways: + +1. **`Authorization` header (recommended)** — keeps the secret out of URLs, access logs and browser history: + + ``` + Authorization: Bearer + ``` + + The `Bearer ` prefix is optional; a bare `Authorization: ` is also accepted. + +2. **`token` query-string parameter (legacy)** — kept for backwards compatibility: + + ``` + GET /api/torrents?token= + ``` + +If both are present, the `Authorization` header takes precedence. The key is compared in constant time. Example: + +```bash +# Preferred — token in the header +curl -H "Authorization: Bearer " http://127.0.0.1:8080/api/torrents + +# Legacy — token in the query string +curl "http://127.0.0.1:8080/api/torrents?token=" +``` + --- ## RtcTorrent — WebRTC BitTorrent in the Browser @@ -568,6 +610,12 @@ echo "WebRTC seeds: {$data['rtc_seeds']}"; ### ChangeLog +#### v4.2.14 +* Hotfix in regard to database handling +* A full audit and security scan of the code, shown some possible vulnerabilities +* Small little performance tweaks +* Adding a better token usage by recommending a header bearer, legacy still works + #### v4.2.13 * Applied a fix for Windows, implemented RIO for Windows * Further optimization and some refactor work to improve performance and efficiency @@ -615,8 +663,8 @@ echo "WebRTC seeds: {$data['rtc_seeds']}"; * Added `///` / `//!` doc comments throughout the codebase for docs.rs / crates.io publishing * Made a large set of `[tracker_config]`, `[sentry_config]`, and `[database_structure.*]` keys optional in `config.toml` — sensible defaults are applied automatically when the keys are absent * Optional tracker fields: `whitelist_enabled`, `blacklist_enabled`, `keys_enabled`, `keys_cleanup_interval`, `users_enabled`, `swagger`, `prometheus_id`, all `cluster_*` fields, and all `rtc_*` fields - * The entire `[sentry_config]` section can now be omitted (defaults to disabled) - * All `[database_structure.*]` sub-sections can be omitted (default table/column names are used) + * The entire `[sentry_config]` section can now be omitted (defaults to disable) + * All `[database_structure.*]` subsections can be omitted (default table/column names are used) * `--create-config` now annotates every optional key with an inline `# Optional: defaults to …` comment so new users can see at a glance what can be left out * Refactored `impl RtcData` out of the struct file into `src/tracker/impls/rtc_data.rs`, consistent with the rest of the project structure diff --git a/docker/Dockerfile b/docker/Dockerfile index e408407b..e1a5e65f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,7 +3,7 @@ FROM rust:alpine RUN apk update --no-interactive RUN apk add git musl-dev curl pkgconfig openssl-dev openssl-libs-static --no-interactive RUN git clone https://github.com/Power2All/torrust-actix.git /app/torrust-actix -RUN cd /app/torrust-actix && git checkout tags/v4.2.13 +RUN cd /app/torrust-actix && git checkout tags/v4.2.14 WORKDIR /app/torrust-actix RUN cd /app/torrust-actix RUN cargo build --release && rm -Rf target/release/.fingerprint target/release/build target/release/deps target/release/examples target/release/incremental diff --git a/docker/build.bat b/docker/build.bat index c168e17d..87876940 100644 --- a/docker/build.bat +++ b/docker/build.bat @@ -1,5 +1,5 @@ @echo off -docker build --no-cache -t power2all/torrust-actix:v4.2.13 -t power2all/torrust-actix:latest . -docker push power2all/torrust-actix:v4.2.13 +docker build --no-cache -t power2all/torrust-actix:v4.2.14 -t power2all/torrust-actix:latest . +docker push power2all/torrust-actix:v4.2.14 docker push power2all/torrust-actix:latest \ No newline at end of file diff --git a/src/api/api.rs b/src/api/api.rs index 72c174ba..22ed237b 100644 --- a/src/api/api.rs +++ b/src/api/api.rs @@ -51,6 +51,7 @@ use crate::api::api_whitelists::{ api_service_whitelists_post }; use crate::api::structs::api_service_data::ApiServiceData; +use crate::api::structs::query_token::QueryToken; use crate::common::common::hex2bin; use crate::common::structs::custom_error::CustomError; use crate::config::structs::api_trackers_config::ApiTrackersConfig; @@ -107,6 +108,7 @@ pub fn api_service_cors() -> Cors .allowed_methods(vec!["GET", "POST", "PUT", "DELETE"]) .allowed_headers(vec![http::header::X_FORWARDED_FOR, http::header::ACCEPT]) .allowed_header(http::header::CONTENT_TYPE) + .allowed_header(http::header::AUTHORIZATION) .max_age(1) } @@ -288,9 +290,26 @@ pub async fn api_service_stats_log(ip: IpAddr, tracker: Arc) tracker.update_stats(event, 1); } -pub async fn api_service_token(token: Option, config: Arc) -> Option +pub fn api_extract_token(request: &HttpRequest) -> Option { - let token_code = match token { + if let Some(value) = request.headers().get(http::header::AUTHORIZATION).and_then(|h| h.to_str().ok()) { + let token = value + .strip_prefix("Bearer ") + .or_else(|| value.strip_prefix("bearer ")) + .unwrap_or(value) + .trim(); + if !token.is_empty() { + return Some(token.to_string()); + } + } + web::Query::::from_query(request.query_string()) + .ok() + .and_then(|params| params.token.clone()) +} + +pub async fn api_service_token(request: &HttpRequest, config: Arc) -> Option +{ + let token_code = match api_extract_token(request) { Some(token) => token, None => { return Some(HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({ diff --git a/src/api/api_blacklists.rs b/src/api/api_blacklists.rs index 4621b4df..09bd948c 100644 --- a/src/api/api_blacklists.rs +++ b/src/api/api_blacklists.rs @@ -4,7 +4,6 @@ use crate::api::api::{ api_validation }; use crate::api::structs::api_service_data::ApiServiceData; -use crate::api::structs::query_token::QueryToken; use crate::common::common::hex2bin; use crate::tracker::enums::updates_action::UpdatesAction; use crate::tracker::structs::info_hash::InfoHash; @@ -22,8 +21,7 @@ use std::sync::Arc; pub async fn api_service_blacklist_get(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let info = path.into_inner(); if info.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad info_hash"})); @@ -42,8 +40,7 @@ pub async fn api_service_blacklist_get(request: HttpRequest, path: web::Path>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -75,8 +72,7 @@ pub async fn api_service_blacklists_get(request: HttpRequest, payload: web::Payl pub async fn api_service_blacklist_post(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let info = path.into_inner(); if info.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad info_hash"})); @@ -98,8 +94,7 @@ pub async fn api_service_blacklist_post(request: HttpRequest, path: web::Path>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -136,8 +131,7 @@ pub async fn api_service_blacklists_post(request: HttpRequest, payload: web::Pay pub async fn api_service_blacklist_delete(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let info = path.into_inner(); if info.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad info_hash"})); @@ -159,8 +153,7 @@ pub async fn api_service_blacklist_delete(request: HttpRequest, path: web::Path< pub async fn api_service_blacklists_delete(request: HttpRequest, payload: web::Payload, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -199,8 +192,7 @@ pub async fn api_service_blacklists_delete(request: HttpRequest, payload: web::P pub async fn api_service_blacklist_clear(request: HttpRequest, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } if !data.torrent_tracker.config.tracker_config.blacklist_enabled { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "blacklist not enabled"})); } diff --git a/src/api/api_certificate.rs b/src/api/api_certificate.rs index cfc0b42c..3925ba29 100644 --- a/src/api/api_certificate.rs +++ b/src/api/api_certificate.rs @@ -7,7 +7,6 @@ use crate::api::structs::certificate_reload_request::CertificateReloadRequest; use crate::api::structs::certificate_reload_result::CertificateReloadResult; use crate::api::structs::certificate_status_item::CertificateStatusItem; use crate::api::structs::api_service_data::ApiServiceData; -use crate::api::structs::query_token::QueryToken; use crate::ssl::enums::server_identifier::ServerIdentifier; use actix_web::http::header::ContentType; use actix_web::web::Data; @@ -27,8 +26,7 @@ pub async fn api_service_certificate_reload( if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let certificate_store = &data.torrent_tracker.certificate_store; @@ -105,8 +103,7 @@ pub async fn api_service_certificate_status( if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let certificate_store = &data.torrent_tracker.certificate_store; diff --git a/src/api/api_keys.rs b/src/api/api_keys.rs index 05fe2287..df7a8fd2 100644 --- a/src/api/api_keys.rs +++ b/src/api/api_keys.rs @@ -4,7 +4,6 @@ use crate::api::api::{ api_validation }; use crate::api::structs::api_service_data::ApiServiceData; -use crate::api::structs::query_token::QueryToken; use crate::common::common::hex2bin; use crate::tracker::enums::updates_action::UpdatesAction; use crate::tracker::structs::info_hash::InfoHash; @@ -22,8 +21,7 @@ use std::sync::Arc; pub async fn api_service_key_get(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let key = path.into_inner(); if key.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad key_hash"})); @@ -44,8 +42,7 @@ pub async fn api_service_key_get(request: HttpRequest, path: web::Path, pub async fn api_service_keys_get(request: HttpRequest, payload: web::Payload, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -79,8 +76,7 @@ pub async fn api_service_keys_get(request: HttpRequest, payload: web::Payload, d pub async fn api_service_key_post(request: HttpRequest, path: web::Path<(String, u64)>, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let (key, timeout) = path.into_inner(); if key.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad key_hash"})); @@ -102,8 +98,7 @@ pub async fn api_service_key_post(request: HttpRequest, path: web::Path<(String, pub async fn api_service_keys_post(request: HttpRequest, payload: web::Payload, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -143,8 +138,7 @@ pub async fn api_service_keys_post(request: HttpRequest, payload: web::Payload, pub async fn api_service_key_delete(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let key = path.into_inner(); if key.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad key_hash"})); @@ -166,8 +160,7 @@ pub async fn api_service_key_delete(request: HttpRequest, path: web::Path>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -206,8 +199,7 @@ pub async fn api_service_keys_delete(request: HttpRequest, payload: web::Payload pub async fn api_service_keys_clear(request: HttpRequest, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } if !data.torrent_tracker.config.tracker_config.keys_enabled { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "keys not enabled"})); } diff --git a/src/api/api_stats.rs b/src/api/api_stats.rs index 0c61e94b..a7a958ce 100644 --- a/src/api/api_stats.rs +++ b/src/api/api_stats.rs @@ -3,11 +3,9 @@ use crate::api::api::{ api_validation }; use crate::api::structs::api_service_data::ApiServiceData; -use crate::api::structs::query_token::QueryToken; use actix_web::http::header::ContentType; use actix_web::web::Data; use actix_web::{ - web, HttpRequest, HttpResponse }; @@ -16,16 +14,14 @@ use std::sync::Arc; pub async fn api_service_stats_get(request: HttpRequest, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } HttpResponse::Ok().content_type(ContentType::json()).json(data.torrent_tracker.get_stats()) } pub async fn api_service_prom_get(request: HttpRequest, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let stats = data.torrent_tracker.get_stats(); let prometheus_id = &data.torrent_tracker.config.tracker_config.prometheus_id; let mut string_output = String::with_capacity(4096); diff --git a/src/api/api_torrents.rs b/src/api/api_torrents.rs index 43efc2ee..8dbb2208 100644 --- a/src/api/api_torrents.rs +++ b/src/api/api_torrents.rs @@ -5,7 +5,6 @@ use crate::api::api::{ parse_info_hash }; use crate::api::structs::api_service_data::ApiServiceData; -use crate::api::structs::query_token::QueryToken; use crate::tracker::enums::updates_action::UpdatesAction; use crate::tracker::structs::peer_id::PeerId; use crate::tracker::structs::torrent_entry::TorrentEntry; @@ -32,8 +31,7 @@ use std::time::{ pub async fn api_service_torrent_get(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let info = path.into_inner(); let info_hash = match parse_info_hash(&info) { Ok(h) => h, @@ -48,8 +46,7 @@ pub async fn api_service_torrent_get(request: HttpRequest, path: web::Path>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -82,8 +79,7 @@ pub async fn api_service_torrents_get(request: HttpRequest, payload: web::Payloa pub async fn api_service_torrent_post(request: HttpRequest, path: web::Path<(String, u64)>, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let (info, completed) = path.into_inner(); let info_hash = match parse_info_hash(&info) { Ok(h) => h, @@ -111,8 +107,7 @@ pub async fn api_service_torrent_post(request: HttpRequest, path: web::Path<(Str pub async fn api_service_torrents_post(request: HttpRequest, payload: web::Payload, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -160,8 +155,7 @@ pub async fn api_service_torrents_post(request: HttpRequest, payload: web::Paylo pub async fn api_service_torrent_delete(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let info = path.into_inner(); let info_hash = match parse_info_hash(&info) { Ok(h) => h, @@ -179,8 +173,7 @@ pub async fn api_service_torrent_delete(request: HttpRequest, path: web::Path>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), diff --git a/src/api/api_users.rs b/src/api/api_users.rs index 77a490f3..411a669d 100644 --- a/src/api/api_users.rs +++ b/src/api/api_users.rs @@ -4,7 +4,6 @@ use crate::api::api::{ api_validation }; use crate::api::structs::api_service_data::ApiServiceData; -use crate::api::structs::query_token::QueryToken; use crate::common::common::{ hash_id, hex2bin @@ -38,8 +37,7 @@ lazy_static::lazy_static! { pub async fn api_service_user_get(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let id = path.into_inner(); let (status_code, data) = api_service_users_return_json(id, data); match status_code { @@ -51,8 +49,7 @@ pub async fn api_service_user_get(request: HttpRequest, path: web::Path, pub async fn api_service_users_get(request: HttpRequest, payload: web::Payload, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -77,8 +74,7 @@ pub async fn api_service_users_get(request: HttpRequest, payload: web::Payload, pub async fn api_service_user_post(request: HttpRequest, path: web::Path<(String, String, u64, u64, u64, u64, u8)>, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let (id, key, uploaded, downloaded, completed, updated, active) = path.into_inner(); if key.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad key_hash"})); @@ -126,8 +122,7 @@ pub async fn api_service_user_post(request: HttpRequest, path: web::Path<(String pub async fn api_service_users_post(request: HttpRequest, payload: web::Payload, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -189,8 +184,7 @@ pub async fn api_service_users_post(request: HttpRequest, payload: web::Payload, pub async fn api_service_user_delete(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let id = path.into_inner(); if id.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad user_hash"})); @@ -222,8 +216,7 @@ pub async fn api_service_user_delete(request: HttpRequest, path: web::Path>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -305,8 +298,7 @@ pub fn api_service_users_return_json(id: String, data: Data> pub async fn api_service_users_clear(request: HttpRequest, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } if !data.torrent_tracker.config.tracker_config.users_enabled { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "users not enabled"})); } diff --git a/src/api/api_whitelists.rs b/src/api/api_whitelists.rs index 4abc5229..2a280aaf 100644 --- a/src/api/api_whitelists.rs +++ b/src/api/api_whitelists.rs @@ -4,7 +4,6 @@ use crate::api::api::{ api_validation }; use crate::api::structs::api_service_data::ApiServiceData; -use crate::api::structs::query_token::QueryToken; use crate::common::common::hex2bin; use crate::tracker::enums::updates_action::UpdatesAction; use crate::tracker::structs::info_hash::InfoHash; @@ -22,8 +21,7 @@ use std::sync::Arc; pub async fn api_service_whitelist_get(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let info = path.into_inner(); if info.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad info_hash"})); @@ -42,8 +40,7 @@ pub async fn api_service_whitelist_get(request: HttpRequest, path: web::Path>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -75,8 +72,7 @@ pub async fn api_service_whitelists_get(request: HttpRequest, payload: web::Payl pub async fn api_service_whitelist_post(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let info = path.into_inner(); if info.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad info_hash"})); @@ -98,8 +94,7 @@ pub async fn api_service_whitelist_post(request: HttpRequest, path: web::Path>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -139,8 +134,7 @@ pub async fn api_service_whitelists_post(request: HttpRequest, payload: web::Pay pub async fn api_service_whitelist_delete(request: HttpRequest, path: web::Path, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let info = path.into_inner(); if info.len() != 40 { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "bad info_hash"})); @@ -162,8 +156,7 @@ pub async fn api_service_whitelist_delete(request: HttpRequest, path: web::Path< pub async fn api_service_whitelists_delete(request: HttpRequest, payload: web::Payload, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } let body = match api_parse_body(payload).await { Ok(data) => data, Err(error) => return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": error.to_string()})), @@ -203,8 +196,7 @@ pub async fn api_service_whitelists_delete(request: HttpRequest, payload: web::P pub async fn api_service_whitelist_clear(request: HttpRequest, data: Data>) -> HttpResponse { if let Some(error_return) = api_validation(&request, &data).await { return error_return; } - let params = web::Query::::from_query(request.query_string()).unwrap(); - if let Some(response) = api_service_token(params.token.clone(), Arc::clone(&data.torrent_tracker.config)).await { return response; } + if let Some(response) = api_service_token(&request, Arc::clone(&data.torrent_tracker.config)).await { return response; } if !data.torrent_tracker.config.tracker_config.whitelist_enabled { return HttpResponse::BadRequest().content_type(ContentType::json()).json(json!({"status": "whitelist not enabled"})); } diff --git a/src/common/common.rs b/src/common/common.rs index a4e3166d..8e4728e6 100644 --- a/src/common/common.rs +++ b/src/common/common.rs @@ -5,7 +5,6 @@ use crate::common::types::QueryValues; use crate::config::enums::compression_algorithm::CompressionAlgorithm; use crate::config::structs::configuration::Configuration; use crate::security::security::MAX_PERCENT_DECODED_SIZE; -use async_std::future; use fern::colors::{ Color, ColoredLevelConfig @@ -186,7 +185,7 @@ pub fn convert_bytes_to_int(array: &[u8]) -> u64 { } pub async fn shutdown_waiting(timeout: Duration, shutdown_handler: Shutdown) -> bool { - future::timeout(timeout, shutdown_handler.handle()) + tokio::time::timeout(timeout, shutdown_handler.handle()) .await .is_ok() } diff --git a/src/config/config.rs b/src/config/config.rs index 73a8ce89..2c8df4ec 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -19,6 +19,8 @@ pub(crate) fn default_cluster_max_connections() -> u64 { 25000 } pub(crate) fn default_cluster_threads() -> u64 { available_parallelism().map(|n| n.get() as u64).unwrap_or(4) } pub(crate) fn default_cluster_tls_connection_rate() -> u64 { 256 } +pub(crate) fn default_chunk_size() -> u64 { 1000 } + pub(crate) fn default_sample_rate() -> f32 { 1.0 } pub(crate) fn default_traces_sample_rate() -> f32 { 1.0 } pub(crate) fn default_max_breadcrumbs() -> usize { 100 } diff --git a/src/config/impls/configuration.rs b/src/config/impls/configuration.rs index 70c2d029..8dc46227 100644 --- a/src/config/impls/configuration.rs +++ b/src/config/impls/configuration.rs @@ -91,6 +91,7 @@ impl Configuration { remove_action: false, update_completed: true, update_peers: false, + chunk_size: 1000, }, cache: None, database_structure: DatabaseStructureConfig { @@ -359,6 +360,9 @@ impl Configuration { if let Ok(value) = env::var("DATABASE__PERSISTENT_INTERVAL") { config.database.persistent_interval = parse_env_num::("DATABASE__PERSISTENT_INTERVAL", &value, 60); } + if let Ok(value) = env::var("DATABASE__CHUNK_SIZE") { + config.database.chunk_size = parse_env_num::("DATABASE__CHUNK_SIZE", &value, 1000); + } if let Ok(value) = env::var("CACHE__ENABLED") { let enabled = parse_env_bool("CACHE__ENABLED", &value, false); if let Some(ref mut cache) = config.cache { diff --git a/src/config/structs/database_config.rs b/src/config/structs/database_config.rs index 22770066..582162a7 100644 --- a/src/config/structs/database_config.rs +++ b/src/config/structs/database_config.rs @@ -13,5 +13,7 @@ pub struct DatabaseConfig { pub insert_vacant: bool, pub remove_action: bool, pub update_completed: bool, - pub update_peers: bool + pub update_peers: bool, + #[serde(default = "crate::config::config::default_chunk_size")] + pub chunk_size: u64 } \ No newline at end of file diff --git a/src/database/impls/database_connector_mysql.rs b/src/database/impls/database_connector_mysql.rs index fe63148f..e8ed39ea 100644 --- a/src/database/impls/database_connector_mysql.rs +++ b/src/database/impls/database_connector_mysql.rs @@ -221,6 +221,8 @@ impl DatabaseConnectorMySQL { let structure = &tracker.config.database_structure.torrents; let db_config = &tracker.config.database; let is_binary = structure.bin_type_infohash; + let chunk_size = db_config.chunk_size; + let mut in_chunk = 0u64; for (info_hash, (counts, updates_action)) in &torrents { handled += 1; let hash_str = info_hash.to_string(); @@ -313,6 +315,7 @@ impl DatabaseConnectorMySQL { if (handled as f64 / 1000f64).fract() == 0.0 || torrents.len() as u64 == handled { info!("{LOG_PREFIX} Handled {handled} torrents"); } + self.commit_chunk(&mut transaction, &mut in_chunk, chunk_size).await?; } info!("{LOG_PREFIX} Handled {handled} torrents"); self.commit(transaction).await @@ -664,6 +667,8 @@ impl DatabaseConnectorMySQL { let db_config = &tracker.config.database; let is_uuid = structure.id_uuid; let is_binary_key = structure.bin_type_key; + let chunk_size = db_config.chunk_size; + let mut in_chunk = 0u64; for (user_entry_item, updates_action) in users.values() { handled += 1; match updates_action { @@ -779,6 +784,7 @@ impl DatabaseConnectorMySQL { if (handled as f64 / 1000f64).fract() == 0.0 || users.len() as u64 == handled { info!("{LOG_PREFIX} Handled {handled} users"); } + self.commit_chunk(&mut transaction, &mut in_chunk, chunk_size).await?; } info!("{LOG_PREFIX} Handled {handled} users"); self.commit(transaction).await @@ -799,6 +805,20 @@ impl DatabaseConnectorMySQL { Ok(()) } + async fn commit_chunk(&self, transaction: &mut Transaction<'_, MySql>, in_chunk: &mut u64, chunk_size: u64) -> Result<(), Error> { + *in_chunk += 1; + if chunk_size != 0 && *in_chunk >= chunk_size { + let new_tx = self.pool.begin().await?; + let old_tx = std::mem::replace(transaction, new_tx); + if let Err(e) = old_tx.commit().await { + error!("{LOG_PREFIX} Error: {e}"); + return Err(e); + } + *in_chunk = 0; + } + Ok(()) + } + pub async fn commit(&self, transaction: Transaction<'_, MySql>) -> Result<(), Error> { match transaction.commit().await { Ok(()) => Ok(()), diff --git a/src/database/impls/database_connector_pgsql.rs b/src/database/impls/database_connector_pgsql.rs index a8725010..c13ffac3 100644 --- a/src/database/impls/database_connector_pgsql.rs +++ b/src/database/impls/database_connector_pgsql.rs @@ -219,6 +219,8 @@ impl DatabaseConnectorPgSQL { let structure = &tracker.config.database_structure.torrents; let db_config = &tracker.config.database; let is_binary = structure.bin_type_infohash; + let chunk_size = db_config.chunk_size; + let mut in_chunk = 0u64; for (info_hash, (counts, updates_action)) in &torrents { handled += 1; let hash_str = info_hash.to_string(); @@ -311,6 +313,7 @@ impl DatabaseConnectorPgSQL { if (handled as f64 / 1000f64).fract() == 0.0 || torrents.len() as u64 == handled { info!("{LOG_PREFIX} Handled {handled} torrents"); } + self.commit_chunk(&mut transaction, &mut in_chunk, chunk_size).await?; } info!("{LOG_PREFIX} Handled {handled} torrents"); self.commit(transaction).await @@ -672,6 +675,8 @@ impl DatabaseConnectorPgSQL { let db_config = &tracker.config.database; let is_uuid = structure.id_uuid; let is_binary_key = structure.bin_type_key; + let chunk_size = db_config.chunk_size; + let mut in_chunk = 0u64; for (user_entry_item, updates_action) in users.values() { handled += 1; match updates_action { @@ -787,6 +792,7 @@ impl DatabaseConnectorPgSQL { if (handled as f64 / 1000f64).fract() == 0.0 || users.len() as u64 == handled { info!("{LOG_PREFIX} Handled {handled} users"); } + self.commit_chunk(&mut transaction, &mut in_chunk, chunk_size).await?; } info!("{LOG_PREFIX} Handled {handled} users"); self.commit(transaction).await @@ -807,6 +813,20 @@ impl DatabaseConnectorPgSQL { Ok(()) } + async fn commit_chunk(&self, transaction: &mut Transaction<'_, Postgres>, in_chunk: &mut u64, chunk_size: u64) -> Result<(), Error> { + *in_chunk += 1; + if chunk_size != 0 && *in_chunk >= chunk_size { + let new_tx = self.pool.begin().await?; + let old_tx = std::mem::replace(transaction, new_tx); + if let Err(e) = old_tx.commit().await { + error!("{LOG_PREFIX} Error: {e}"); + return Err(e); + } + *in_chunk = 0; + } + Ok(()) + } + pub async fn commit(&self, transaction: Transaction<'_, Postgres>) -> Result<(), Error> { match transaction.commit().await { Ok(()) => Ok(()), diff --git a/src/database/impls/database_connector_sqlite.rs b/src/database/impls/database_connector_sqlite.rs index adee3ee9..bea7355a 100644 --- a/src/database/impls/database_connector_sqlite.rs +++ b/src/database/impls/database_connector_sqlite.rs @@ -242,6 +242,8 @@ impl DatabaseConnectorSQLite { let structure = &tracker.config.database_structure.torrents; let db_config = &tracker.config.database; let is_binary = structure.bin_type_infohash; + let chunk_size = db_config.chunk_size; + let mut in_chunk = 0u64; for (info_hash, (counts, updates_action)) in &torrents { handled += 1; let hash_str = info_hash.to_string(); @@ -334,6 +336,7 @@ impl DatabaseConnectorSQLite { if (handled as f64 / 1000f64).fract() == 0.0 || torrents.len() as u64 == handled { info!("{LOG_PREFIX} Handled {handled} torrents"); } + self.commit_chunk(&mut transaction_db, &mut in_chunk, chunk_size).await?; } info!("{LOG_PREFIX} Handled {handled} torrents"); if let Some(txn) = transaction { @@ -688,6 +691,8 @@ impl DatabaseConnectorSQLite { let db_config = &tracker.config.database; let is_uuid = structure.id_uuid; let is_binary_key = structure.bin_type_key; + let chunk_size = db_config.chunk_size; + let mut in_chunk = 0u64; for (user_entry_item, updates_action) in users.values() { handled += 1; match updates_action { @@ -804,6 +809,7 @@ impl DatabaseConnectorSQLite { if (handled as f64 / 1000f64).fract() == 0.0 || users.len() as u64 == handled { info!("{LOG_PREFIX} Handled {handled} users"); } + self.commit_chunk(&mut transaction, &mut in_chunk, chunk_size).await?; } info!("{LOG_PREFIX} Handled {handled} users"); self.commit(transaction).await @@ -824,6 +830,20 @@ impl DatabaseConnectorSQLite { Ok(()) } + async fn commit_chunk(&self, transaction: &mut Transaction<'_, Sqlite>, in_chunk: &mut u64, chunk_size: u64) -> Result<(), Error> { + *in_chunk += 1; + if chunk_size != 0 && *in_chunk >= chunk_size { + let new_tx = self.pool.begin().await?; + let old_tx = std::mem::replace(transaction, new_tx); + if let Err(e) = old_tx.commit().await { + error!("{LOG_PREFIX} Error: {e}"); + return Err(e); + } + *in_chunk = 0; + } + Ok(()) + } + pub async fn commit(&self, transaction: Transaction<'_, Sqlite>) -> Result<(), Error> { match transaction.commit().await { Ok(()) => Ok(()), diff --git a/src/http/http.rs b/src/http/http.rs index e2c97095..45487bd2 100644 --- a/src/http/http.rs +++ b/src/http/http.rs @@ -253,9 +253,14 @@ pub async fn http_service_announce_key(request: HttpRequest, path: web::Path { + return http_service_announce_handler(request, ip, data.torrent_tracker.clone(), Some(user_id), data.http_trackers_config.rtctorrent).await; + } + Err(response) => { + http_stat_update(ip, &data.torrent_tracker, StatsEvent::Tcp4Failure, StatsEvent::Tcp6Failure, 1); + return response; + } } } http_service_announce_handler(request, ip, data.torrent_tracker.clone(), None, data.http_trackers_config.rtctorrent).await @@ -281,9 +286,14 @@ pub async fn http_service_announce_userkey(request: HttpRequest, path: web::Path } if tracker_config.users_enabled { let user_key = path.clone().1; - let user_key_check = http_service_check_user_key_validation(data.torrent_tracker.clone(), user_key.clone()).await; - if user_key_check.is_none() { - return http_service_announce_handler(request, ip, data.torrent_tracker.clone(), Some(http_service_decode_hex_user_id(user_key.clone()).await.unwrap()), data.http_trackers_config.rtctorrent).await; + match http_service_check_user_key_validation(data.torrent_tracker.clone(), user_key).await { + Ok(user_id) => { + return http_service_announce_handler(request, ip, data.torrent_tracker.clone(), Some(user_id), data.http_trackers_config.rtctorrent).await; + } + Err(response) => { + http_stat_update(ip, &data.torrent_tracker, StatsEvent::Tcp4Failure, StatsEvent::Tcp6Failure, 1); + return response; + } } } http_service_announce_handler(request, ip, data.torrent_tracker.clone(), None, data.http_trackers_config.rtctorrent).await @@ -696,7 +706,7 @@ pub async fn http_service_scrape_handler(request: HttpRequest, ip: IpAddr, data: let request_interval_minimum = tracker_config.request_interval_minimum as i64; match scrape.as_ref() { Ok(e) => { - let data_scrape = data.handle_scrape(data.clone(), e.clone()).await; + let data_scrape = data.handle_scrape(data.clone(), e).await; let mut scrape_list = ben_map!(); let scrape_list_mut = scrape_list.dict_mut().unwrap(); for (info_hash, counts) in &data_scrape { @@ -845,19 +855,16 @@ pub async fn http_service_check_key_validation(data: Arc, key: S None } -pub async fn http_service_check_user_key_validation(data: Arc, user_key: String) -> Option +pub async fn http_service_check_user_key_validation(data: Arc, user_key: String) -> Result { if user_key.len() != 40 { - return Some(HttpResponse::Ok().content_type(ContentType::plaintext()).body(ERR_INVALID_USER_KEY.clone())); + return Err(HttpResponse::Ok().content_type(ContentType::plaintext()).body(ERR_INVALID_USER_KEY.clone())); } - let user_key_decoded: UserId = match http_service_decode_hex_user_id(user_key).await { - Ok(result) => { result } - Err(error) => { return Some(error) } - }; + let user_key_decoded: UserId = http_service_decode_hex_user_id(user_key).await?; if data.check_user_key(user_key_decoded).is_none() { - return Some(HttpResponse::Ok().content_type(ContentType::plaintext()).body(ERR_UNKNOWN_USER_KEY.clone())); + return Err(HttpResponse::Ok().content_type(ContentType::plaintext()).body(ERR_UNKNOWN_USER_KEY.clone())); } - None + Ok(user_key_decoded) } pub fn http_check_host_and_port_used(bind_address: &str) -> std::io::Result<()> { diff --git a/src/main.rs b/src/main.rs index 71289de4..2f4fb8ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use async_std::task; use clap::Parser; use futures_util::future::try_join_all; use log::{ @@ -78,7 +77,7 @@ fn main() -> std::io::Result<()> let tracker = Arc::new(TorrentTracker::new(config.clone(), args.create_database).await); if args.create_database { - task::sleep(Duration::from_secs(1)).await; + tokio::time::sleep(Duration::from_secs(1)).await; exit(0); } @@ -536,7 +535,7 @@ fn main() -> std::io::Result<()> } tokio_shutdown.handle().await; - task::sleep(Duration::from_secs(1)).await; + tokio::time::sleep(Duration::from_secs(1)).await; tracker.set_stats(StatsEvent::Completed, config.tracker_config.total_downloads.cast_signed()); Configuration::save_from_config(tracker.config.clone(), "config.toml"); @@ -596,7 +595,7 @@ fn main() -> std::io::Result<()> info!("Saving completed data to config..."); } - task::sleep(Duration::from_secs(1)).await; + tokio::time::sleep(Duration::from_secs(1)).await; info!("Server shutting down completed"); mem::forget(tokio_core); diff --git a/src/tracker/impls/torrent_tracker_handlers.rs b/src/tracker/impls/torrent_tracker_handlers.rs index e048feec..ec7d0fb9 100644 --- a/src/tracker/impls/torrent_tracker_handlers.rs +++ b/src/tracker/impls/torrent_tracker_handlers.rs @@ -1,6 +1,11 @@ use crate::common::structs::custom_error::CustomError; use crate::common::structs::number_of_bytes::NumberOfBytes; use crate::common::types::QueryValues; +use crate::security::security::{ + MAX_OFFER_ID_LENGTH, + MAX_PEER_MESSAGE_SIZE, + MAX_SCRAPE_TORRENTS +}; use crate::tracker::enums::announce_event::AnnounceEvent; use crate::tracker::enums::updates_action::UpdatesAction; use crate::tracker::structs::announce_query_request::AnnounceQueryRequest; @@ -106,6 +111,15 @@ impl TorrentTracker { .and_then(|v| v.first()) .and_then(|bytes| std::str::from_utf8(bytes).ok()) .map(std::string::ToString::to_string); + if let Some(ref offer) = rtcoffer_string && offer.len() > MAX_PEER_MESSAGE_SIZE { + return Err(CustomError::new("rtcoffer exceeds maximum size")); + } + if let Some(ref answer) = rtcanswer_string && answer.len() > MAX_PEER_MESSAGE_SIZE { + return Err(CustomError::new("rtcanswer exceeds maximum size")); + } + if let Some(ref answer_for) = rtcanswerfor_string && answer_for.len() > MAX_OFFER_ID_LENGTH { + return Err(CustomError::new("rtcanswerfor exceeds maximum size")); + } let elapsed = now.elapsed(); debug!("[PERF] Announce validation took: {elapsed:?}"); @@ -349,8 +363,9 @@ impl TorrentTracker { if result.is_empty() { return Err(CustomError::new("no info_hash given")); } - let mut info_hash_vec = Vec::with_capacity(result.len()); - for hash in result { + let cap = result.len().min(MAX_SCRAPE_TORRENTS); + let mut info_hash_vec = Vec::with_capacity(cap); + for hash in result.iter().take(MAX_SCRAPE_TORRENTS) { if hash.len() != 20 { return Err(CustomError::new("an invalid info_hash was given")); } @@ -363,7 +378,7 @@ impl TorrentTracker { } } - pub async fn handle_scrape(&self, data: Arc, scrape_query: ScrapeQueryRequest) -> BTreeMap + pub async fn handle_scrape(&self, data: Arc, scrape_query: &ScrapeQueryRequest) -> BTreeMap { let transaction = crate::utils::sentry_tracing::start_trace_transaction("handle_scrape", "tracker"); diff --git a/src/websocket/websocket.rs b/src/websocket/websocket.rs index 2edafd55..4634b50b 100644 --- a/src/websocket/websocket.rs +++ b/src/websocket/websocket.rs @@ -452,7 +452,7 @@ pub async fn process_scrape(tracker: &Arc, request: &ClusterRequ } }; let tracker_config = &tracker.config.tracker_config; - let data_scrape = tracker.handle_scrape(tracker.clone(), scrape.clone()).await; + let data_scrape = tracker.handle_scrape(tracker.clone(), &scrape).await; let mut files_map = ben_map!(); let files_map_mut = files_map.dict_mut().unwrap(); for (info_hash, counts) in &data_scrape { diff --git a/torrust-actix.desktop b/torrust-actix.desktop index 2739cd43..236f57a7 100644 --- a/torrust-actix.desktop +++ b/torrust-actix.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=4.2.13 +Version=4.2.14 Type=Application Name=Torrust Actix Comment=A rich, fast and efficient Bittorrent Tracker