Can force file download

This commit is contained in:
Pierre HUBERT 2025-05-01 19:09:43 +02:00
parent 40cf0c16df
commit cfbc003737
3 changed files with 51 additions and 26 deletions

View File

@ -7,7 +7,7 @@ use crate::models::files::File;
use crate::services::files_service; use crate::services::files_service;
use crate::utils::time_utils; use crate::utils::time_utils;
use actix_web::http::header; use actix_web::http::header;
use actix_web::{HttpRequest, HttpResponse}; use actix_web::{HttpRequest, HttpResponse, web};
use std::ops::Add; use std::ops::Add;
use std::time::Duration; use std::time::Duration;
@ -44,40 +44,60 @@ pub async fn get_info(file_extractor: FileIdExtractor) -> HttpResult {
Ok(HttpResponse::Ok().json(file_extractor.as_ref())) Ok(HttpResponse::Ok().json(file_extractor.as_ref()))
} }
#[derive(serde::Deserialize)]
pub struct DownloadQuery {
#[serde(default)]
download: bool,
}
/// Download an uploaded file /// Download an uploaded file
pub async fn download(req: HttpRequest, file_extractor: FileIdExtractor) -> HttpResult { pub async fn download(
serve_file(req, file_extractor.as_ref()).await req: HttpRequest,
file_extractor: FileIdExtractor,
query: web::Query<DownloadQuery>,
) -> HttpResult {
serve_file(req, file_extractor.as_ref(), query.download).await
} }
/// Serve a file, returning 304 status code if the requested file already exists /// Serve a file, returning 304 status code if the requested file already exists
pub async fn serve_file(req: HttpRequest, file: &File) -> HttpResult { pub async fn serve_file(req: HttpRequest, file: &File, download_file: bool) -> HttpResult {
// Check if the browser already knows the etag if !download_file {
if let Some(c) = req.headers().get(header::IF_NONE_MATCH) { // Check if the browser already knows the etag
if c.to_str().unwrap_or("") == file.sha512.as_str() { if let Some(c) = req.headers().get(header::IF_NONE_MATCH) {
return Ok(HttpResponse::NotModified().finish()); if c.to_str().unwrap_or("") == file.sha512.as_str() {
}
}
// Check if the browser already knows the file by date
if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) {
let date_str = c.to_str().unwrap_or("");
if let Ok(date) = httpdate::parse_http_date(date_str) {
if date.add(Duration::from_secs(1))
>= time_utils::unix_to_system_time(file.time_create as u64)
{
return Ok(HttpResponse::NotModified().finish()); return Ok(HttpResponse::NotModified().finish());
} }
} }
}
Ok(HttpResponse::Ok() // Check if the browser already knows the file by date
.content_type(file.mime_type.as_str()) if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) {
let date_str = c.to_str().unwrap_or("");
if let Ok(date) = httpdate::parse_http_date(date_str) {
if date.add(Duration::from_secs(1))
>= time_utils::unix_to_system_time(file.time_create as u64)
{
return Ok(HttpResponse::NotModified().finish());
}
}
}
}
let mut res = HttpResponse::Ok();
res.content_type(file.mime_type.as_str())
.insert_header(("etag", file.sha512.as_str())) .insert_header(("etag", file.sha512.as_str()))
.insert_header(( .insert_header((
"last-modified", "last-modified",
time_utils::unix_to_http_date(file.time_create as u64), time_utils::unix_to_http_date(file.time_create as u64),
)) ));
.body(files_service::get_file_content(file).await?))
// Add filename to response headers if requested
if download_file {
res.insert_header((
"content-disposition",
format!("attachment; filename={}", file.file_name),
));
}
Ok(res.body(files_service::get_file_content(file).await?))
} }
/// Delete an uploaded file /// Delete an uploaded file

View File

@ -41,7 +41,10 @@ export class FileApi {
/** /**
* Get a file download URL * Get a file download URL
*/ */
static DownloadURL(file: UploadedFile): string { static DownloadURL(file: UploadedFile, forceDownload = false): string {
return APIClient.backendURL() + `/file/${file.id}/download`; return (
APIClient.backendURL() +
`/file/${file.id}/download${forceDownload ? "?download=true" : ""}`
);
} }
} }

View File

@ -25,6 +25,7 @@ export function FileViewerDialog(p: {
fileName={p.file.file_name} fileName={p.file.file_name}
fileSize={p.file.file_size} fileSize={p.file.file_size}
url={FileApi.DownloadURL(p.file)} url={FileApi.DownloadURL(p.file)}
downloadUrl={FileApi.DownloadURL(p.file, true)}
mimetype={p.file.mime_type} mimetype={p.file.mime_type}
/> />
<DialogActions> <DialogActions>
@ -38,6 +39,7 @@ interface ViewerProps {
fileName: string; fileName: string;
fileSize: number; fileSize: number;
url: string; url: string;
downloadUrl: string;
mimetype: string; mimetype: string;
} }
@ -65,7 +67,7 @@ function DefaultViewer(p: ViewerProps): React.ReactElement {
<Typography variant="caption" gutterBottom> <Typography variant="caption" gutterBottom>
{filesize(p.fileSize)} {filesize(p.fileSize)}
</Typography> </Typography>
<a href={p.url} target="_blank" referrerPolicy="no-referrer"> <a href={p.downloadUrl} target="_blank" referrerPolicy="no-referrer">
<Button variant="outlined">Download</Button> <Button variant="outlined">Download</Button>
</a> </a>
</Paper> </Paper>