prefer to serve raw files over html files

This commit is contained in:
neri 2021-12-20 15:00:13 +01:00
parent 30d059b7af
commit c1c6adc576
1 changed files with 57 additions and 20 deletions

View File

@ -3,11 +3,15 @@ use std::str::FromStr;
use actix_files::NamedFile; use actix_files::NamedFile;
use actix_web::{ use actix_web::{
error, error,
http::header::{Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue}, http::header::{
web, Error, HttpRequest, HttpResponse, Accept, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
Header,
},
web::{self, delete},
Error, HttpRequest, HttpResponse,
}; };
use async_std::{fs, path::Path}; use async_std::{fs, path::Path};
use mime::Mime; use mime::{Mime, TEXT_HTML};
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
use url::Url; use url::Url;
@ -19,24 +23,29 @@ const URL_VIEW_HTML: &str = include_str!("../template/url-view.html");
const TEXT_VIEW_SIZE_LIMIT: u64 = 512 * 1024; // 512KiB const TEXT_VIEW_SIZE_LIMIT: u64 = 512 * 1024; // 512KiB
enum ViewType {
Raw,
Download,
Html,
}
pub async fn download( pub async fn download(
req: HttpRequest, req: HttpRequest,
db: web::Data<PgPool>, db: web::Data<PgPool>,
config: web::Data<Config>, config: web::Data<Config>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let id = req.match_info().query("id"); let id = req.match_info().query("id");
let (file_id, file_name, file_kind, delete_on_download) = load_file_info(id, &db).await?; let (file_id, file_name, file_kind, delete) = load_file_info(id, &db).await?;
let mut path = config.files_dir.clone(); let mut path = config.files_dir.clone();
path.push(&file_id); path.push(&file_id);
let download = delete_on_download || req.query_string().contains("dl"); let file_mime = get_content_type(&path);
let content_type = get_content_type(&path); let response = match get_view_type(&req, &file_kind, &file_mime, &path, delete).await {
let response = if use_text_view(&file_kind, &content_type, &path, download).await { ViewType::Raw => build_file_response(false, &file_name, path, file_mime, &req),
build_text_response(&path).await ViewType::Download => build_file_response(true, &file_name, path, file_mime, &req),
} else { ViewType::Html => build_text_response(&path).await,
build_file_response(download, &file_name, path, content_type, req)
}; };
if delete_on_download { if delete {
deleter::delete_by_id(&db, &file_id, &config.files_dir) deleter::delete_by_id(&db, &file_id, &config.files_dir)
.await .await
.map_err(|db_err| { .map_err(|db_err| {
@ -72,16 +81,44 @@ fn get_content_type(path: &Path) -> Mime {
.expect("tree_magic_mini should not produce invalid mime") .expect("tree_magic_mini should not produce invalid mime")
} }
async fn use_text_view( async fn get_view_type(
req: &HttpRequest,
file_kind: &str, file_kind: &str,
content_type: &Mime, file_mime: &Mime,
file_path: &Path, file_path: &Path,
download: bool, delete_on_download: bool,
) -> bool { ) -> ViewType {
if delete_on_download || req.query_string().contains("dl") {
return ViewType::Download;
}
let is_text = let is_text =
FileKind::from_str(file_kind) == Ok(FileKind::Text) || content_type.type_() == mime::TEXT; FileKind::from_str(file_kind) == Ok(FileKind::Text) || file_mime.type_() == mime::TEXT;
let is_not_large = get_file_size(file_path).await < TEXT_VIEW_SIZE_LIMIT; if !is_text {
is_text && is_not_large && !download return ViewType::Raw;
}
if get_file_size(file_path).await >= TEXT_VIEW_SIZE_LIMIT {
return ViewType::Raw;
}
if let Ok(accept) = Accept::parse(req) {
for accept_mime in accept.mime_precedence() {
if mime_matches(&accept_mime, file_mime) {
return ViewType::Raw;
}
if accept_mime == TEXT_HTML {
break;
}
}
} else {
return ViewType::Raw;
}
ViewType::Html
}
fn mime_matches(accept: &Mime, content: &Mime) -> bool {
let type_matches = accept.type_() == content.type_() || accept.type_() == mime::STAR;
let subtype_matches = accept.subtype() == content.subtype() || accept.subtype() == mime::STAR;
type_matches && subtype_matches
} }
async fn get_file_size(file_path: &Path) -> u64 { async fn get_file_size(file_path: &Path) -> u64 {
@ -114,7 +151,7 @@ fn build_file_response(
file_name: &str, file_name: &str,
path: async_std::path::PathBuf, path: async_std::path::PathBuf,
content_type: Mime, content_type: Mime,
req: HttpRequest, req: &HttpRequest,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let content_disposition = ContentDisposition { let content_disposition = ContentDisposition {
disposition: if download { disposition: if download {
@ -131,7 +168,7 @@ fn build_file_response(
})? })?
.set_content_type(content_type) .set_content_type(content_type)
.set_content_disposition(content_disposition); .set_content_disposition(content_disposition);
file.into_response(&req) file.into_response(req)
} }
fn get_disposition_params(filename: &str) -> Vec<DispositionParam> { fn get_disposition_params(filename: &str) -> Vec<DispositionParam> {