prefer to serve raw files over html files
This commit is contained in:
parent
30d059b7af
commit
48574b1ec3
src
|
@ -3,11 +3,14 @@ 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::{
|
||||||
|
Accept, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
|
||||||
|
Header,
|
||||||
|
},
|
||||||
web, Error, HttpRequest, HttpResponse,
|
web, 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 +22,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 +80,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 +150,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 +167,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> {
|
||||||
|
|
Loading…
Reference in New Issue