Can force file download
This commit is contained in:
		@@ -7,7 +7,7 @@ use crate::models::files::File;
 | 
			
		||||
use crate::services::files_service;
 | 
			
		||||
use crate::utils::time_utils;
 | 
			
		||||
use actix_web::http::header;
 | 
			
		||||
use actix_web::{HttpRequest, HttpResponse};
 | 
			
		||||
use actix_web::{HttpRequest, HttpResponse, web};
 | 
			
		||||
use std::ops::Add;
 | 
			
		||||
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()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize)]
 | 
			
		||||
pub struct DownloadQuery {
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    download: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Download an uploaded file
 | 
			
		||||
pub async fn download(req: HttpRequest, file_extractor: FileIdExtractor) -> HttpResult {
 | 
			
		||||
    serve_file(req, file_extractor.as_ref()).await
 | 
			
		||||
pub async fn download(
 | 
			
		||||
    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
 | 
			
		||||
pub async fn serve_file(req: HttpRequest, file: &File) -> HttpResult {
 | 
			
		||||
    // Check if the browser already knows the etag
 | 
			
		||||
    if let Some(c) = req.headers().get(header::IF_NONE_MATCH) {
 | 
			
		||||
        if c.to_str().unwrap_or("") == file.sha512.as_str() {
 | 
			
		||||
            return Ok(HttpResponse::NotModified().finish());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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)
 | 
			
		||||
            {
 | 
			
		||||
pub async fn serve_file(req: HttpRequest, file: &File, download_file: bool) -> HttpResult {
 | 
			
		||||
    if !download_file {
 | 
			
		||||
        // Check if the browser already knows the etag
 | 
			
		||||
        if let Some(c) = req.headers().get(header::IF_NONE_MATCH) {
 | 
			
		||||
            if c.to_str().unwrap_or("") == file.sha512.as_str() {
 | 
			
		||||
                return Ok(HttpResponse::NotModified().finish());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Ok()
 | 
			
		||||
        .content_type(file.mime_type.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());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    let mut res = HttpResponse::Ok();
 | 
			
		||||
    res.content_type(file.mime_type.as_str())
 | 
			
		||||
        .insert_header(("etag", file.sha512.as_str()))
 | 
			
		||||
        .insert_header((
 | 
			
		||||
            "last-modified",
 | 
			
		||||
            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
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,10 @@ export class FileApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a file download URL
 | 
			
		||||
   */
 | 
			
		||||
  static DownloadURL(file: UploadedFile): string {
 | 
			
		||||
    return APIClient.backendURL() + `/file/${file.id}/download`;
 | 
			
		||||
  static DownloadURL(file: UploadedFile, forceDownload = false): string {
 | 
			
		||||
    return (
 | 
			
		||||
      APIClient.backendURL() +
 | 
			
		||||
      `/file/${file.id}/download${forceDownload ? "?download=true" : ""}`
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ export function FileViewerDialog(p: {
 | 
			
		||||
        fileName={p.file.file_name}
 | 
			
		||||
        fileSize={p.file.file_size}
 | 
			
		||||
        url={FileApi.DownloadURL(p.file)}
 | 
			
		||||
        downloadUrl={FileApi.DownloadURL(p.file, true)}
 | 
			
		||||
        mimetype={p.file.mime_type}
 | 
			
		||||
      />
 | 
			
		||||
      <DialogActions>
 | 
			
		||||
@@ -38,6 +39,7 @@ interface ViewerProps {
 | 
			
		||||
  fileName: string;
 | 
			
		||||
  fileSize: number;
 | 
			
		||||
  url: string;
 | 
			
		||||
  downloadUrl: string;
 | 
			
		||||
  mimetype: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -65,7 +67,7 @@ function DefaultViewer(p: ViewerProps): React.ReactElement {
 | 
			
		||||
      <Typography variant="caption" gutterBottom>
 | 
			
		||||
        {filesize(p.fileSize)}
 | 
			
		||||
      </Typography>
 | 
			
		||||
      <a href={p.url} target="_blank" referrerPolicy="no-referrer">
 | 
			
		||||
      <a href={p.downloadUrl} target="_blank" referrerPolicy="no-referrer">
 | 
			
		||||
        <Button variant="outlined">Download</Button>
 | 
			
		||||
      </a>
 | 
			
		||||
    </Paper>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user