1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use crate::auth;
use crate::config::Config;
use crate::file;
use crate::header::ContentDisposition;
use actix_files::NamedFile;
use actix_multipart::Multipart;
use actix_web::{error, get, post, web, Error, HttpRequest, HttpResponse, Responder};
use byte_unit::Byte;
use futures_util::stream::StreamExt;
use std::convert::TryFrom;
use std::env;
#[get("/")]
async fn index() -> impl Responder {
HttpResponse::Found()
.header("Location", env!("CARGO_PKG_HOMEPAGE"))
.finish()
}
#[get("/{file}")]
async fn serve(
request: HttpRequest,
path: web::Path<String>,
config: web::Data<Config>,
) -> Result<HttpResponse, Error> {
let path = config.server.upload_path.join(&*path);
let file = NamedFile::open(&path)?
.disable_content_disposition()
.prefer_utf8(true);
let response = file.into_response(&request)?;
Ok(response)
}
#[post("/")]
async fn upload(
request: HttpRequest,
mut payload: Multipart,
config: web::Data<Config>,
) -> Result<HttpResponse, Error> {
let connection = request.connection_info();
let host = connection.remote_addr().unwrap_or("unknown host");
auth::check(host, request.headers(), env::var("AUTH_TOKEN").ok())?;
let mut urls: Vec<String> = Vec::new();
while let Some(item) = payload.next().await {
let mut field = item?;
let content = ContentDisposition::try_from(field.content_disposition())?;
if content.has_form_field("file") {
let mut bytes = Vec::<u8>::new();
while let Some(chunk) = field.next().await {
bytes.append(&mut chunk?.to_vec());
}
let bytes_unit = Byte::from_bytes(bytes.len() as u128).get_appropriate_unit(false);
if bytes.len() as u128 > config.server.max_content_length.get_bytes() {
log::warn!("upload rejected for {} ({})", host, bytes_unit);
return Err(error::ErrorPayloadTooLarge("upload limit exceeded"));
}
let file_name = &file::save(content.get_file_name()?, &bytes, &config)?;
log::info!("{} ({}) is uploaded from {}", file_name, bytes_unit, host);
urls.push(format!(
"{}://{}/{}\n",
connection.scheme(),
connection.host(),
file_name
));
} else {
log::warn!("{} sent an invalid form field", host);
return Err(error::ErrorBadRequest("invalid form field"));
}
}
Ok(HttpResponse::Ok().body(urls.join("")))
}
pub fn configure_routes(cfg: &mut web::ServiceConfig) {
cfg.service(index)
.service(serve)
.service(upload)
.route("", web::head().to(HttpResponse::MethodNotAllowed));
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{http, test, App};
#[actix_rt::test]
async fn test_index() {
let mut app = test::init_service(App::new().service(index)).await;
let req = test::TestRequest::with_header("content-type", "text/plain").to_request();
let resp = test::call_service(&mut app, req).await;
assert!(resp.status().is_redirection());
assert_eq!(http::StatusCode::FOUND, resp.status());
}
#[actix_rt::test]
async fn test_serve() {
let mut app = test::init_service(App::new().service(serve)).await;
let req = test::TestRequest::default().to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(http::StatusCode::NOT_FOUND, resp.status());
}
}