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_web::{
error,
http::header::{Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue},
web, Error, HttpRequest, HttpResponse,
http::header::{
Accept, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
Header,
},
web::{self, delete},
Error, HttpRequest, HttpResponse,
};
use async_std::{fs, path::Path};
use mime::Mime;
use mime::{Mime, TEXT_HTML};
use sqlx::postgres::PgPool;
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
enum ViewType {
Raw,
Download,
Html,
}
pub async fn download(
req: HttpRequest,
db: web::Data<PgPool>,
config: web::Data<Config>,
) -> Result<HttpResponse, Error> {
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();
path.push(&file_id);
let download = delete_on_download || req.query_string().contains("dl");
let content_type = get_content_type(&path);
let response = if use_text_view(&file_kind, &content_type, &path, download).await {
build_text_response(&path).await
} else {
build_file_response(download, &file_name, path, content_type, req)
let file_mime = get_content_type(&path);
let response = match get_view_type(&req, &file_kind, &file_mime, &path, delete).await {
ViewType::Raw => build_file_response(false, &file_name, path, file_mime, &req),
ViewType::Download => build_file_response(true, &file_name, path, file_mime, &req),
ViewType::Html => build_text_response(&path).await,
};
if delete_on_download {
if delete {
deleter::delete_by_id(&db, &file_id, &config.files_dir)
.await
.map_err(|db_err| {
@ -72,16 +81,44 @@ fn get_content_type(path: &Path) -> 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,
content_type: &Mime,
file_mime: &Mime,
file_path: &Path,
download: bool,
) -> bool {
delete_on_download: bool,
) -> ViewType {
if delete_on_download || req.query_string().contains("dl") {
return ViewType::Download;
}
let is_text =
FileKind::from_str(file_kind) == Ok(FileKind::Text) || content_type.type_() == mime::TEXT;
let is_not_large = get_file_size(file_path).await < TEXT_VIEW_SIZE_LIMIT;
is_text && is_not_large && !download
FileKind::from_str(file_kind) == Ok(FileKind::Text) || file_mime.type_() == mime::TEXT;
if !is_text {
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 {
@ -114,7 +151,7 @@ fn build_file_response(
file_name: &str,
path: async_std::path::PathBuf,
content_type: Mime,
req: HttpRequest,
req: &HttpRequest,
) -> Result<HttpResponse, Error> {
let content_disposition = ContentDisposition {
disposition: if download {
@ -131,7 +168,7 @@ fn build_file_response(
})?
.set_content_type(content_type)
.set_content_disposition(content_disposition);
file.into_response(&req)
file.into_response(req)
}
fn get_disposition_params(filename: &str) -> Vec<DispositionParam> {