5

I'm working with Rust and Rocket. I have an endpoint to upload one file at a time with form-data:

use rocket::form::{Form, FromForm};
use rocket::fs::TempFile;
use std::ffi::OsStr;
use std::path::{Path};
use uuid::Uuid;

#[post("/file_upload", format = "multipart/form-data", data = "<form>")]
pub async fn file_upload(mut form: Form<Upload<'_>>) -> std::io::Result<String> {
  // Get raw file 
  let file_name = form.file.raw_name().unwrap().dangerous_unsafe_unsanitized_raw().as_str();name
  // Get extension of file name
  let extension = Path::new(file_name).extension().and_then(OsStr::to_str).unwrap(); 
  // Generate new UUID
  let id: String = Uuid::new_v4().to_string(); 
  // Build path to save file
  let file_path = String::from("media/temp_files") + "/" + &id + "." + extension; 

  // Save file
  form.file.persist_to(file_path).await?; 

  Ok(String::from("Ok"))
}

This works, but I am mixing persistence, business logic and HTTP infrastructure in the same module. I want to rely on Rocket only to retrieve the file stream and metadata (file name, size and content type), and pass it to another function that would be in charge or validation, image processing, etc.

I have access to the metadata, but I don't know how to retrieve the Buffered content from the TempFile struct.

// rocket-0.5.0-rc.2/src/fs/temp_file.rs
[…]
pub enum TempFile<'v> {
    #[doc(hidden)]
    File {
        file_name: Option<&'v FileName>,
        content_type: Option<ContentType>,
        path: Either<TempPath, PathBuf>,
        len: u64,
    },
    #[doc(hidden)]
    Buffered {
        content: &'v str,
    }
}
[…]

I don't see any method returning it.Is there any method I'm missing to retrieve the raw file content? Or maybe there is a different struct/trait in Rocket to achieve this.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Emille C.
  • 315
  • 2
  • 15
  • Did you resolved this? If not have you tried to ask this question on https://users.rust-lang.org/? For instance there is this question which might help you https://users.rust-lang.org/t/uploading-files-to-a-rocket-server/76575 – Marek Fajkus Feb 26 '23 at 19:09
  • Did you solved this without reloading the file from disk? My point is: when uploading milions of small files (in a controlled environment), there is no point to save them to disk and reload them. Just having the `Vec` and metadata is enough. – Xvolks Apr 22 '23 at 08:50

1 Answers1

0

Just a followup of my comment that can answer your question.

It seems that @badoken submitted a PR here : https://github.com/SergioBenitez/Rocket/pull/2361

Basically this allows to get passed the TempFile struct like this:

#[derive(FromForm)]
struct MyForm<'r> {
    foo: &'r [u8],
}

I'm still unsure how to get the metadata from this. I have to test it further and improve this answer.

EDIT: after some digging, I've the following solution that suits my needs.

use rocket::form::{DataField, Form, FromFormField};
use rocket::http::{ContentType, CookieJar, Status};

use rocket::{FromForm, post};
use rocket::data::ToByteUnit;

use rocket::fs::{FileName};
use rocket::response::content::RawHtml;
use crate::utils::Md5;

pub struct File<'v> {
    file_name: Option<&'v FileName>,
    content_type: ContentType,
    data: Vec<u8>,
}

#[rocket::async_trait]
impl<'v> FromFormField<'v> for File<'v> {
    async fn from_data(field: DataField<'v, '_>) -> rocket::form::Result<'v, Self> {
        let stream = field.data.open(u32::MAX.bytes());
        let bytes = stream.into_bytes().await?;
        Ok(File {
            file_name: field.file_name,
            content_type: field.content_type,
            data: bytes.value,
        })

    }
}

#[derive(FromForm)]
pub struct UploadRequest<'r> {
    file: File<'r>,
}

#[post("/upload", data = "<req>")]
pub async fn upload(
    content_type: &ContentType,
    req: Form<UploadRequest<'_>>,
    cookies: &CookieJar<'_>,
) -> Result<RawHtml<String>, Status> {
    Ok(RawHtml(format!("file: {}, md5: {}, content-type: {} / {}",
                       req.file.file_name.unwrap().as_str().unwrap_or("Frack") ,
                       &req.file.data.md5(),
                       content_type,
                       req.file.content_type

    )))
}

When pushing a small video file in the /upload endpoint, I get the following result:

file: 1ae366a5-209e-4b5e-8d37-826db09090fb, md5: ae78f71db09cb7ae26607f0bd7e917a3, content-type: multipart/form-data;
boundary=--------------------------248453579191145706333860 / video/mp4

The extension of the filename is lost but it's type is in File's content_type field.

The md5 hash ensures that the file was not altered.

Xvolks
  • 2,065
  • 1
  • 21
  • 32