How to properly store uploaded file from multipartform-data in Rust?

I'm trying to build a web server in Rust, and i'm having a few issues trying to upload file into the server. With text based files it uploads fine, but whenever i try to upload other type of media (images, videos, etc), if the file is small enough, it will save, but corrupted, as showned.

Original file raw data.

Raw file data after attempting to upload on the server.

async fn parse_body(content_type: Option<&String>, body: String) -> HashMap<String, String> {
    match content_type {
        Some(content_type) => {
            let ct = content_type.as_str();
            if ct.contains("application/x-www-form-urlencoded") {
                let buffer = body.replace("\r\n\r\n", "");
                let _body = from_bytes::<Vec<(String, String)>>(buffer.as_bytes()).unwrap();
                return _body.into_iter().collect();
            }
    
            if ct.contains("multipart/form-data") {
                let boundary = multer::parse_boundary(ct).unwrap();
                let data = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(body)) });
                let mut multipart = multer::Multipart::new(data, boundary);
                let mut _body: HashMap<String, String> = HashMap::new();
    
                // Iterate over the fields, use `next_field()` to get the next field.
                while let Some(mut field) = multipart.next_field().await.unwrap() {
                    // Get field name.
                    let name = field.name().unwrap().to_string();
                    // Get the field's filename if provided in "Content-Disposition" header.
                    //
                    // Process the field data chunks e.g. store them in a file.
                    while let Some(chunk) = field.chunk().await.unwrap() {
                        // Do something with field chunk.
                        if let Some(file_name) = field.file_name() {
                            let file_dir = format!("src\\static\\temp\\{}", file_name);
                            let current_dir: &Path = Path::new(&file_dir);
                            let path = env::current_dir().unwrap().join(current_dir);
                            if let Ok(mut file) = std::fs::File::create(path) {
                                file.write_all(&chunk).unwrap();
                            }
                        } else {
                            _body.insert(name.clone(), String::from_utf8(chunk.to_vec()).unwrap());
                        }
                    }
                }
    
                return _body;
            }
    
        },
        None => return HashMap::new()
    }

    HashMap::new()
}

You're opening the file for each chunk. Whenever you open an already-existing file the default behavior is to truncate the file (delete all existing contents), so successive chunks are overwriting previous ones instead of being appended. You can open the file for appending but a much more efficient approach is to open it only once.


Also, this is a security vulnerability:

let file_dir = format!("src\\static\\temp\\{}", file_name);

You must not use arbitrary text that came from an untrusted source as a pathname, without ensuring it doesn't contain, say, ..\..\directory_you_didn't_want_changed\.

Don't try to invent this from scratch; read up on what's necessary to prevent it. You can find it called a “directory traversal” or “path traversal” attack in the context of web servers; usually the concern is reading paths specified via the URL, but the same principles apply here. If you don't absolutely need the file names to be the preserve, it would be safe and simple to not use the specified filename for uploads, but create your own auto-generated file name that you can rely on the characteristics of.

5 Likes

Thanks for the tip, i optimized to open the file once and assign a specific path. But it seems the client application (Postman) is encoding the file, debugged with a small file (334 bytes long PNG) and all the content fits in one chunk. Unfortunately i don't have enough knowledge on file transfer encoding through HTTP.