Compare commits

...

2 Commits

Author SHA1 Message Date
neri 6362f3bd0b chore: fix Dockerfile warning 2024-10-23 13:51:17 +02:00
neri 5d02c4beef chore: update dependencies 2024-10-23 13:46:54 +02:00
7 changed files with 316 additions and 472 deletions

727
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ actix-web = { version = "4.3.0", default-features = false, features = [
"compress-gzip", "compress-gzip",
"compress-zstd", "compress-zstd",
] } ] }
sqlx = { version = "0.7.1", default-features = false, features = [ sqlx = { version = "0.8.2", default-features = false, features = [
"runtime-tokio-rustls", "runtime-tokio-rustls",
"postgres", "postgres",
"time", "time",
@ -23,7 +23,7 @@ env_logger = { version = "0.11.3", default-features = false, features = [
log = "0.4.17" log = "0.4.17"
actix-files = "0.6.2" actix-files = "0.6.2"
tokio = { version = "1.25.0", features = ["rt-multi-thread", "macros", "sync"] } tokio = { version = "1.25.0", features = ["rt-multi-thread", "macros", "sync"] }
actix-multipart = "0.6.0" actix-multipart = "0.7.0"
futures-util = "0.3.26" futures-util = "0.3.26"
rand = "0.8.5" rand = "0.8.5"
time = "0.3.17" time = "0.3.17"
@ -33,10 +33,8 @@ tree_magic_mini = { version = "3.0.3", features = ["with-gpl-data"] }
tree_magic_db = "*" tree_magic_db = "*"
mime = "0.3.16" mime = "0.3.16"
url = "2.3.1" url = "2.3.1"
actix-governor = "0.5.0" actix-governor = "0.6.0"
governor = "0.6.3"
lazy_static = "1.4.0" lazy_static = "1.4.0"
actix-web-lab = "0.20.2"
[profile.release] [profile.release]
strip = "symbols" strip = "symbols"

View File

@ -1,4 +1,4 @@
FROM rust:alpine as builder FROM rust:alpine AS builder
WORKDIR /app WORKDIR /app
RUN apk add musl-dev RUN apk add musl-dev

View File

@ -22,7 +22,7 @@ use actix_web::{
web::{self, Data}, web::{self, Data},
App, Error, HttpResponse, HttpServer, App, Error, HttpResponse, HttpServer,
}; };
use actix_web_lab::middleware::from_fn; use actix_web::middleware::from_fn;
use env_logger::Env; use env_logger::Env;
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
use std::env; use std::env;
@ -67,7 +67,7 @@ async fn main() -> std::io::Result<()> {
let config = Data::new(config); let config = Data::new(config);
let governor_conf = GovernorConfigBuilder::default() let governor_conf = GovernorConfigBuilder::default()
.per_second(config.rate_limit_replenish_seconds) .seconds_per_request(config.rate_limit_replenish_seconds)
.burst_size(config.rate_limit_burst) .burst_size(config.rate_limit_burst)
.key_extractor(ForwardedPeerIpKeyExtractor { .key_extractor(ForwardedPeerIpKeyExtractor {
proxied: config.proxied, proxied: config.proxied,

View File

@ -1,6 +1,10 @@
use crate::{config, mime_relations}; use crate::{config, mime_relations};
use actix_multipart::{Field, Multipart}; use actix_multipart::{Field, Multipart};
use actix_web::{error, http::header::DispositionParam, Error}; use actix_web::{
error,
http::header::{ContentDisposition, DispositionParam},
Error,
};
use futures_util::{StreamExt, TryStreamExt}; use futures_util::{StreamExt, TryStreamExt};
use mime::{Mime, APPLICATION_OCTET_STREAM, TEXT_PLAIN}; use mime::{Mime, APPLICATION_OCTET_STREAM, TEXT_PLAIN};
use std::{cmp::min, io::ErrorKind, path::Path}; use std::{cmp::min, io::ErrorKind, path::Path};
@ -52,7 +56,9 @@ pub(crate) async fn parse_multipart_inner(
let mut size = 0; let mut size = 0;
while let Ok(Some(mut field)) = payload.try_next().await { while let Ok(Some(mut field)) = payload.try_next().await {
let name = get_field_name(&field)?.to_owned(); let name = get_field_name(&field)
.ok_or(error::ParseError::Incomplete)?
.to_owned();
match name.as_str() { match name.as_str() {
"keep_for" => { "keep_for" => {
keep_for_seconds = Some(parse_string(&name, &mut field).await?); keep_for_seconds = Some(parse_string(&name, &mut field).await?);
@ -145,11 +151,8 @@ fn check_requirements(
Ok(()) Ok(())
} }
fn get_field_name(field: &Field) -> Result<&str, error::Error> { fn get_field_name(field: &Field) -> Option<&str> {
Ok(field field.content_disposition()?.get_name()
.content_disposition()
.get_name()
.ok_or(error::ParseError::Incomplete)?)
} }
async fn parse_string( async fn parse_string(
@ -217,15 +220,18 @@ fn validate_max_size(written_bytes: u64, max_size: Option<u64>) -> Result<(), Er
fn get_file_metadata(field: &actix_multipart::Field) -> (Option<Mime>, Option<String>) { fn get_file_metadata(field: &actix_multipart::Field) -> (Option<Mime>, Option<String>) {
let mime = field.content_type().cloned(); let mime = field.content_type().cloned();
let filename = field let filename = field.content_disposition().and_then(get_filename);
.content_disposition() (mime, filename)
}
fn get_filename(content_disposition: &ContentDisposition) -> Option<String> {
content_disposition
.parameters .parameters
.iter() .iter()
.find_map(|param| match param { .find_map(|param| match param {
DispositionParam::Filename(filename) => Some(filename.clone()), DispositionParam::Filename(filename) => Some(filename.clone()),
_ => None, _ => None,
}); })
(mime, filename)
} }
fn get_content_type(bytes: &[u8]) -> Option<Mime> { fn get_content_type(bytes: &[u8]) -> Option<Mime> {

View File

@ -1,12 +1,12 @@
use actix_governor::governor::clock::{Clock, DefaultClock, QuantaInstant};
use actix_governor::governor::NotUntil;
use actix_governor::KeyExtractor; use actix_governor::KeyExtractor;
use actix_governor::PeerIpKeyExtractor; use actix_governor::PeerIpKeyExtractor;
use actix_governor::SimpleKeyExtractionError; use actix_governor::SimpleKeyExtractionError;
use actix_web::HttpResponse; use actix_web::HttpResponse;
use actix_web::HttpResponseBuilder; use actix_web::HttpResponseBuilder;
use actix_web::{dev::ServiceRequest, http::header::ContentType}; use actix_web::{dev::ServiceRequest, http::header::ContentType};
use governor::clock::{Clock, DefaultClock, QuantaInstant}; use std::net::{IpAddr, Ipv6Addr};
use governor::NotUntil;
use std::net::IpAddr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ForwardedPeerIpKeyExtractor { pub struct ForwardedPeerIpKeyExtractor {
@ -19,18 +19,15 @@ impl KeyExtractor for ForwardedPeerIpKeyExtractor {
fn extract(&self, req: &ServiceRequest) -> Result<Self::Key, Self::KeyExtractionError> { fn extract(&self, req: &ServiceRequest) -> Result<Self::Key, Self::KeyExtractionError> {
let forwarded_for = req.headers().get("x-forwarded-for"); let forwarded_for = req.headers().get("x-forwarded-for");
let mut ip = if self.proxied && forwarded_for.is_some() { let ip = if self.proxied && forwarded_for.is_some() {
read_forwareded_for(forwarded_for).map_err(SimpleKeyExtractionError::new)? read_forwareded_for(forwarded_for).map_err(SimpleKeyExtractionError::new)?
} else { } else {
PeerIpKeyExtractor.extract(req)? PeerIpKeyExtractor.extract(req)?
}; };
// only keep the first /56 for ipv6 addresses if let IpAddr::V6(mut ipv6) = ip {
// mask 0xffff_ffff_ffff_ff00_0000_0000_0000_0000 // only keep the first /56 for IPv6 addresses
if let IpAddr::V6(ipv6) = ip { ipv6 &= Ipv6Addr::from_bits(u128::MAX << (u128::BITS - 56));
let mut octets = ipv6.octets();
octets[7..16].fill(0);
ip = IpAddr::V6(octets.into());
} }
Ok(ip) Ok(ip)

View File

@ -4,7 +4,7 @@ use actix_web::{
http::header::{HeaderValue, CONTENT_SECURITY_POLICY}, http::header::{HeaderValue, CONTENT_SECURITY_POLICY},
Error, HttpMessage, Error, HttpMessage,
}; };
use actix_web_lab::middleware::Next; use actix_web::middleware::Next;
use rand::Rng; use rand::Rng;
use std::fmt::Display; use std::fmt::Display;