diff --git a/Cargo.lock b/Cargo.lock index 75619522..d1ab10fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,23 +26,6 @@ dependencies = [ "tokio-util 0.6.9", ] -[[package]] -name = "actix-codec" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36c014a3e811624313b51a227b775ecba55d36ef9462bbaac7d4f13e54c9271" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-sink", - "log", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util 0.6.9", -] - [[package]] name = "actix-codec" version = "0.5.0" @@ -62,9 +45,9 @@ dependencies = [ [[package]] name = "actix-cors" -version = "0.6.0-beta.10" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8debd30414af03e9411186aac95e0230b0bb1e91146f48015dfab5c049940223" +checksum = "30dbd116ef7532f56e2f6d7c511736ea0b124d914ee8820a5271247bf89f06aa" dependencies = [ "actix-service", "actix-utils", @@ -78,11 +61,11 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.0.0-rc.3" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b5ba038f3bb4aa29ad9bdd7eba09955ff04503263c497fc61a389d6412f4e8" +checksum = "0f3fdd63b9cfeaf92eeeece719dabbddddb420a57d3fd171ce1490ecfb7086b1" dependencies = [ - "actix-codec 0.5.0", + "actix-codec", "actix-rt", "actix-service", "actix-utils", @@ -125,9 +108,9 @@ dependencies = [ [[package]] name = "actix-multipart" -version = "0.4.0-beta.13" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59b1f14a8b2bc14df9be544d173f5390da5b62d531e406fd0f0ce9b825fea5a" +checksum = "c9edfb0e7663d7fe18c8d5b668c9c1bcf79176b1dcc9d4da9592503209a6bfb0" dependencies = [ "actix-utils", "actix-web", @@ -143,9 +126,9 @@ dependencies = [ [[package]] name = "actix-router" -version = "0.5.0-rc.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6506dbef336634ff35d994d58daa0a412ea23751f15f9b4dcac4d594b1ed1f" +checksum = "eb60846b52c118f2f04a56cc90880a274271c489b2498623d58176f8ca21fa80" dependencies = [ "bytestring", "firestorm", @@ -207,11 +190,11 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.0.0-rc.3" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e3c85bc4116b69913b03f16cff8cade1212508fcd321847d9cfe3d3e41f991" +checksum = "f4e5ebffd51d50df56a3ae0de0e59487340ca456f05dd0b90c0a7a6dd6a74d31" dependencies = [ - "actix-codec 0.4.2", + "actix-codec", "actix-http", "actix-macros", "actix-router", @@ -222,6 +205,7 @@ dependencies = [ "actix-web-codegen", "ahash 0.7.6", "bytes", + "bytestring", "cfg-if", "cookie", "derive_more", @@ -246,9 +230,9 @@ dependencies = [ [[package]] name = "actix-web-codegen" -version = "0.5.0-rc.2" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d0976042e6ddc82c7d0dedd64d39959bc26d9bba098b2f6c32a73fbef784eaf" +checksum = "7525bedf54704abb1d469e88d7e7e9226df73778798a69cea5022d53b2ae91bc" dependencies = [ "actix-router", "proc-macro2", @@ -285,7 +269,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", "once_cell", "version_check", ] @@ -316,9 +300,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.54" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a99269dff3bc004caa411f38845c20303f1e393ca2bd6581576fa3a7f59577d" +checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" [[package]] name = "async-channel" @@ -1056,9 +1040,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if", "libc", @@ -1371,6 +1355,7 @@ dependencies = [ "futures-timer", "gumdrop", "hex", + "itertools", "lazy_static", "log", "meilisearch-sdk", @@ -1943,7 +1928,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", ] [[package]] @@ -1970,7 +1955,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", "redox_syscall", ] @@ -2178,9 +2163,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0486718e92ec9a68fbed73bb5ef687d71103b142595b406835649bebd33f72c7" +checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" [[package]] name = "serde" diff --git a/Cargo.toml b/Cargo.toml index 49fd74c5..465949ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,11 @@ path = "src/main.rs" [dependencies] actix = "0.12.0" -actix-web = "4.0.0-rc.2" +actix-web = "4.0.0" actix-rt = "2.6.0" tokio-stream = "0.1.8" -actix-multipart = "0.4.0-beta.13" -actix-cors = "0.6.0-beta.8" +actix-multipart = "0.4.0" +actix-cors = "0.6.0" meilisearch-sdk = "0.6.0" reqwest = { version = "0.11.9", features = ["json"] } @@ -36,6 +36,7 @@ sha1 = { version = "0.6.0", features = ["std"] } sha2 = "0.9.2" bitflags = "1.2.1" zip = "0.5.12" +itertools = "0.10.3" validator = { version = "0.13", features = ["derive"] } regex = "1.5.4" @@ -59,4 +60,4 @@ sqlx = { version = "0.5.10", features = ["runtime-actix-rustls", "postgres", "ch bytes = "1.1.0" dashmap = "4.0.2" -hex = "0.4.3" \ No newline at end of file +hex = "0.4.3" diff --git a/sqlx-data.json b/sqlx-data.json index 224960e3..a52de9cb 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -639,27 +639,6 @@ "nullable": [] } }, - "22f3f089050594199c3a3265da8ca68264a7457ae6ec4aef3644035a2022a830": { - "query": "\n SELECT version.id id FROM (\n SELECT DISTINCT ON(v.id) v.id, v.date_published FROM versions v\n INNER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id AND gvv.game_version_id IN (SELECT game_version_id FROM game_versions_versions WHERE joining_version_id = $2)\n INNER JOIN loaders_versions lv ON lv.version_id = v.id AND lv.loader_id IN (SELECT loader_id FROM loaders_versions WHERE version_id = $2)\n WHERE v.mod_id = $1\n ) AS version\n ORDER BY version.date_published DESC\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - }, - "nullable": [ - false - ] - } - }, "232d7d0319c20dd5fff29331b067d6c6373bcff761a77958a2bb5f59068a83a5": { "query": "\n UPDATE team_members\n SET permissions = $1\n WHERE (team_id = $2 AND user_id = $3)\n ", "describe": { @@ -1941,6 +1920,27 @@ "nullable": [] } }, + "62416ea5972daa46ff2d077f631a8a30d9ec922056cf7ff1b0d74d9511010c96": { + "query": "\n SELECT version.id id FROM (\n SELECT DISTINCT ON(v.id) v.id, v.date_published FROM versions v\n INNER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id AND gvv.game_version_id IN (SELECT game_version_id FROM game_versions_versions WHERE joining_version_id = $2)\n INNER JOIN loaders_versions lv ON lv.version_id = v.id AND lv.loader_id IN (SELECT loader_id FROM loaders_versions WHERE version_id = $2)\n WHERE v.mod_id = $1\n ) AS version\n ORDER BY version.date_published DESC\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [ + false + ] + } + }, "6326543f7cbd0ce03bbfe234ee82ca1b61d411589dc2d61753598679942cfdd8": { "query": "\n SELECT md.url url, dp.id platform_id, dp.name dp_name, dp.short short\n FROM mods_donations md\n INNER JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n WHERE md.joining_mod_id = $1\n ", "describe": { diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index f156f0d9..2a2f1b70 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -29,25 +29,30 @@ impl DependencyBuilder { version_id: VersionId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result<(), DatabaseError> { - let version_dependency_id = if let Some(project_id) = self.project_id { - sqlx::query!( - " - SELECT version.id id FROM ( - SELECT DISTINCT ON(v.id) v.id, v.date_published FROM versions v - INNER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id AND gvv.game_version_id IN (SELECT game_version_id FROM game_versions_versions WHERE joining_version_id = $2) - INNER JOIN loaders_versions lv ON lv.version_id = v.id AND lv.loader_id IN (SELECT loader_id FROM loaders_versions WHERE version_id = $2) - WHERE v.mod_id = $1 - ) AS version - ORDER BY version.date_published DESC - LIMIT 1 - ", - project_id as ProjectId, - version_id as VersionId, - ) - .fetch_optional(&mut *transaction).await?.map(|x| VersionId(x.id)) - } else { - self.version_id - }; + let (version_dependency_id, project_dependency_id): (Option, Option) = + if self.version_id.is_some() { + (self.version_id, None) + } else if let Some(project_id) = self.project_id { + let version_id = sqlx::query!( + " + SELECT version.id id FROM ( + SELECT DISTINCT ON(v.id) v.id, v.date_published FROM versions v + INNER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id AND gvv.game_version_id IN (SELECT game_version_id FROM game_versions_versions WHERE joining_version_id = $2) + INNER JOIN loaders_versions lv ON lv.version_id = v.id AND lv.loader_id IN (SELECT loader_id FROM loaders_versions WHERE version_id = $2) + WHERE v.mod_id = $1 + ) AS version + ORDER BY version.date_published DESC + LIMIT 1 + ", + project_id as ProjectId, + version_id as VersionId, + ) + .fetch_optional(&mut *transaction).await?.map(|x| VersionId(x.id)); + + (version_id, Some(project_id)) + } else { + (None, None) + }; sqlx::query!( " @@ -57,7 +62,7 @@ impl DependencyBuilder { version_id as VersionId, self.dependency_type, version_dependency_id.map(|x| x.0), - self.project_id.map(|x| x.0), + project_dependency_id.map(|x| x.0), ) .execute(&mut *transaction) .await?; diff --git a/src/ratelimit/errors.rs b/src/ratelimit/errors.rs index 8e425cbb..be8f87d4 100644 --- a/src/ratelimit/errors.rs +++ b/src/ratelimit/errors.rs @@ -26,20 +26,20 @@ pub enum ARError { } impl ResponseError for ARError { - fn error_response(&self) -> actix_web::web::HttpResponse { + fn error_response(&self) -> actix_web::HttpResponse { match self { Self::LimitedError { max_requests, remaining, reset, } => { - let mut response = actix_web::web::HttpResponse::TooManyRequests(); + let mut response = actix_web::HttpResponse::TooManyRequests(); response.insert_header(("x-ratelimit-limit", max_requests.to_string())); response.insert_header(("x-ratelimit-remaining", remaining.to_string())); response.insert_header(("x-ratelimit-reset", reset.to_string())); response.body(self.to_string()) } - _ => actix_web::web::HttpResponse::build(self.status_code()).body(self.to_string()), + _ => actix_web::HttpResponse::build(self.status_code()).body(self.to_string()), } } } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 67ac0c26..fd8feacc 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -202,25 +202,23 @@ impl actix_web::ResponseError for ApiError { } } - fn error_response(&self) -> actix_web::web::HttpResponse { - actix_web::web::HttpResponse::build(self.status_code()).json( - crate::models::error::ApiError { - error: match self { - ApiError::EnvError(..) => "environment_error", - ApiError::SqlxDatabaseError(..) => "database_error", - ApiError::DatabaseError(..) => "database_error", - ApiError::AuthenticationError(..) => "unauthorized", - ApiError::CustomAuthenticationError(..) => "unauthorized", - ApiError::XmlError(..) => "xml_error", - ApiError::JsonError(..) => "json_error", - ApiError::SearchError(..) => "search_error", - ApiError::IndexingError(..) => "indexing_error", - ApiError::FileHostingError(..) => "file_hosting_error", - ApiError::InvalidInputError(..) => "invalid_input", - ApiError::ValidationError(..) => "invalid_input", - }, - description: &self.to_string(), + fn error_response(&self) -> actix_web::HttpResponse { + actix_web::HttpResponse::build(self.status_code()).json(crate::models::error::ApiError { + error: match self { + ApiError::EnvError(..) => "environment_error", + ApiError::SqlxDatabaseError(..) => "database_error", + ApiError::DatabaseError(..) => "database_error", + ApiError::AuthenticationError(..) => "unauthorized", + ApiError::CustomAuthenticationError(..) => "unauthorized", + ApiError::XmlError(..) => "xml_error", + ApiError::JsonError(..) => "json_error", + ApiError::SearchError(..) => "search_error", + ApiError::IndexingError(..) => "indexing_error", + ApiError::FileHostingError(..) => "file_hosting_error", + ApiError::InvalidInputError(..) => "invalid_input", + ApiError::ValidationError(..) => "invalid_input", }, - ) + description: &self.to_string(), + }) } } diff --git a/src/routes/v1/mods.rs b/src/routes/v1/mods.rs index 947fb246..20e2ee9a 100644 --- a/src/routes/v1/mods.rs +++ b/src/routes/v1/mods.rs @@ -56,7 +56,7 @@ pub async fn mod_search( .hits .into_iter() .map(|x| ResultSearchMod { - mod_id: format!("local-{}", x.project_id.clone()), + mod_id: format!("local-{}", x.project_id), slug: x.slug, author: x.author.clone(), title: x.title, diff --git a/src/routes/version_creation.rs b/src/routes/version_creation.rs index f5329755..545d0514 100644 --- a/src/routes/version_creation.rs +++ b/src/routes/version_creation.rs @@ -34,7 +34,10 @@ pub struct InitialVersionData { pub version_title: String, #[validate(length(max = 65536))] pub version_body: Option, - #[validate(length(min = 0, max = 256))] + #[validate( + length(min = 0, max = 256), + custom(function = "crate::util::validate::validate_deps") + )] pub dependencies: Vec, #[validate(length(min = 1))] pub game_versions: Vec, @@ -630,22 +633,24 @@ pub async fn upload_file( ) .await?; + let file_name_encode = format!( + "data/{}/versions/{}/{}", + project_id, + version_number, + urlencoding::encode(file_name) + ); + let file_name = format!( + "data/{}/versions/{}/{}", + project_id, version_number, &file_name + ); + let upload_data = file_host - .upload_file( - content_type, - &format!( - "data/{}/versions/{}/{}", - project_id, - version_number, - urlencoding::encode(&file_name) - ), - data.freeze(), - ) + .upload_file(content_type, &file_name, data.freeze()) .await?; uploaded_files.push(UploadedFile { file_id: upload_data.file_id, - file_name: upload_data.file_name.clone(), + file_name: file_name_encode, }); // TODO: Malware scan + file validation diff --git a/src/routes/version_file.rs b/src/routes/version_file.rs index 9821290c..fe191152 100644 --- a/src/routes/version_file.rs +++ b/src/routes/version_file.rs @@ -1,11 +1,11 @@ use super::ApiError; use crate::database::models::version_item::QueryVersion; use crate::file_hosting::FileHost; -use crate::{models, database}; use crate::models::projects::{GameVersion, Loader, Version}; use crate::models::teams::Permissions; use crate::util::auth::get_user_from_headers; use crate::util::routes::ok_or_not_found; +use crate::{database, models}; use actix_web::{delete, get, post, web, HttpRequest, HttpResponse}; use serde::{Deserialize, Serialize}; use sqlx::PgPool; diff --git a/src/routes/versions.rs b/src/routes/versions.rs index 9a84209f..c3d8c837 100644 --- a/src/routes/versions.rs +++ b/src/routes/versions.rs @@ -171,6 +171,10 @@ pub struct EditVersion { #[validate(length(max = 65536))] pub changelog: Option, pub version_type: Option, + #[validate( + length(min = 0, max = 256), + custom(function = "crate::util::validate::validate_deps") + )] pub dependencies: Option>, pub game_versions: Option>, pub loaders: Option>, diff --git a/src/search/mod.rs b/src/search/mod.rs index ba6f08cb..20e7262d 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -1,7 +1,7 @@ use crate::models::error::ApiError; use crate::models::projects::SearchRequest; use actix_web::http::StatusCode; -use actix_web::web::HttpResponse; +use actix_web::HttpResponse; use chrono::{DateTime, Utc}; use meilisearch_sdk::client::Client; use meilisearch_sdk::document::Document; diff --git a/src/util/validate.rs b/src/util/validate.rs index 487e5f2c..d5dbd3e7 100644 --- a/src/util/validate.rs +++ b/src/util/validate.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use lazy_static::lazy_static; use regex::Regex; use validator::{ValidationErrors, ValidationErrorsKind}; @@ -53,3 +54,26 @@ pub fn validation_errors_to_string(errors: ValidationErrors, adder: Option Result<(), validator::ValidationError> { + if values + .iter() + .duplicates_by(|x| { + format!( + "{}-{}", + x.version_id + .unwrap_or(crate::models::projects::VersionId(0)), + x.project_id + .unwrap_or(crate::models::projects::ProjectId(0)) + ) + }) + .next() + .is_some() + { + return Err(validator::ValidationError::new("duplicate dependency")); + } + + Ok(()) +} diff --git a/src/validate/mod.rs b/src/validate/mod.rs index a545368b..279ae22f 100644 --- a/src/validate/mod.rs +++ b/src/validate/mod.rs @@ -37,7 +37,8 @@ pub enum SupportedGameVersions { All, PastDate(DateTime), Range(DateTime, DateTime), - #[allow(dead_code)] Custom(Vec), + #[allow(dead_code)] + Custom(Vec), } pub trait Validator: Sync { diff --git a/src/validate/pack.rs b/src/validate/pack.rs index 854cecee..e89714f0 100644 --- a/src/validate/pack.rs +++ b/src/validate/pack.rs @@ -33,7 +33,7 @@ pub struct PackFile<'a> { pub downloads: Vec<&'a str>, } -fn validate_download_url(values: &Vec<&str>) -> Result<(), validator::ValidationError> { +fn validate_download_url(values: &[&str]) -> Result<(), validator::ValidationError> { for value in values { let url = url::Url::parse(value) .ok()