datatrash/src/upload.rs

166 lines
5.3 KiB
Rust
Raw Normal View History

use crate::config::Config;
use crate::file_kind::FileKind;
use crate::multipart;
use crate::multipart::UploadConfig;
use actix_multipart::Multipart;
use actix_web::{error, web, Error, HttpResponse};
use async_std::{channel::Sender, fs};
use rand::prelude::SliceRandom;
use sqlx::postgres::PgPool;
const INDEX_HTML: &str = include_str!("../template/index.html");
const INDEX_AUTH_HTML: &str = include_str!("../template/index-auth.html");
const AUTH_HIDE_JS: &str = include_str!("../template/auth-hide.js");
const UPLOAD_HTML: &str = include_str!("../template/upload.html");
const ID_CHARS: &[char] = &[
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9',
];
pub async fn index(
req: web::HttpRequest,
config: web::Data<Config>,
) -> Result<HttpResponse, Error> {
let upload_url = format!("{}/upload", get_host_url(&req));
let index_html = if config.no_auth_limits.is_some() {
INDEX_AUTH_HTML
} else {
INDEX_HTML
};
let filled_index_html = index_html.replace("{upload_url}", upload_url.as_str());
Ok(HttpResponse::Ok()
.content_type("text/html")
.body(filled_index_html))
}
pub async fn auth_hide(config: web::Data<Config>) -> Result<HttpResponse, Error> {
if let Some(no_auth_limits) = &config.no_auth_limits {
let auth_hide_js = AUTH_HIDE_JS
.replace("{no_auth_max_time}", &no_auth_limits.max_time.to_string())
.replace(
"{no_auth_large_file_max_time}",
&no_auth_limits.large_file_max_time.to_string(),
)
.replace(
"{no_auth_large_file_size}",
&no_auth_limits.large_file_size.to_string(),
);
Ok(HttpResponse::Ok()
.content_type("application/javascript")
.body(auth_hide_js))
} else {
Err(error::ErrorNotFound("file not found"))
}
}
pub async fn upload(
req: web::HttpRequest,
payload: Multipart,
db: web::Data<PgPool>,
expiry_watch_sender: web::Data<Sender<()>>,
config: web::Data<Config>,
) -> Result<HttpResponse, Error> {
let file_id = gen_file_id();
let mut filename = config.files_dir.clone();
filename.push(&file_id);
let parsed_multipart = multipart::parse_multipart(payload, &file_id, &filename, &config).await;
let UploadConfig {
original_name,
valid_till,
kind,
delete_on_download,
} = match parsed_multipart {
Ok(data) => data,
Err(err) => {
if filename.exists().await {
fs::remove_file(filename).await.map_err(|_| {
error::ErrorInternalServerError(
"could not parse multipart; could not remove file",
)
})?;
}
return Err(err);
}
};
let db_insert = sqlx::query(
"INSERT INTO Files (file_id, file_name, valid_till, kind, delete_on_download) \
VALUES ($1, $2, $3, $4, $5)",
)
.bind(&file_id)
.bind(&original_name)
.bind(valid_till.naive_local())
.bind(kind.to_string())
.bind(delete_on_download)
.execute(db.as_ref())
.await;
if db_insert.is_err() {
fs::remove_file(filename).await.map_err(|_| {
error::ErrorInternalServerError(
"could not insert file into database; could not remove file",
)
})?;
return Err(error::ErrorInternalServerError(
"could not insert file into database",
));
}
log::info!(
"{} create new file {} (valid_till: {}, kind: {}, delete_on_download: {})",
req.connection_info().realip_remote_addr().unwrap_or("-"),
file_id,
valid_till,
kind,
delete_on_download
);
expiry_watch_sender.send(()).await.unwrap();
let redirect = if kind == FileKind::Binary {
let encoded_name = urlencoding::encode(&original_name);
format!("/upload/{}/{}", file_id, encoded_name)
} else {
format!("/upload/{}", file_id)
};
let url = get_file_url(&req, &file_id, Some(&original_name));
Ok(HttpResponse::SeeOther()
.header("location", redirect)
.body(format!("{}\n", url)))
}
fn gen_file_id() -> String {
let mut rng = rand::thread_rng();
let mut id = String::with_capacity(5);
for _ in 0..5 {
id.push(*ID_CHARS.choose(&mut rng).expect("ID_CHARS is not empty"));
}
id
}
fn get_host_url(req: &web::HttpRequest) -> String {
let conn = req.connection_info();
format!("{}://{}", conn.scheme(), conn.host())
}
fn get_file_url(req: &web::HttpRequest, id: &str, name: Option<&str>) -> String {
if let Some(name) = name {
let encoded_name = urlencoding::encode(name);
format!("{}/{}/{}", get_host_url(req), id, encoded_name)
} else {
format!("{}/{}", get_host_url(req), id)
}
}
pub async fn uploaded(req: web::HttpRequest) -> Result<HttpResponse, Error> {
let id = req.match_info().query("id");
let name = req.match_info().get("name");
let url = get_file_url(&req, id, name);
let upload_html = UPLOAD_HTML.replace("{url}", url.as_str());
Ok(HttpResponse::Ok()
.content_type("text/html")
.body(upload_html))
}