Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to parse multipart forms using abonander/multipart with Rocket?

This might be useful for me:

I have no idea how you're meant to go about parsing a multipart form besides doing it manually using just the raw post-data string as input

I will try to adjust the Hyper example but any help will be much appreciated.

Relevant issues:

  • Support Multipart Forms.
  • support rocket
like image 546
rofrol Avatar asked Apr 15 '17 10:04

rofrol


2 Answers

Rocket's primary abstraction for data is the FromData trait. Given the POST data and the request, you can construct a given type:

pub trait FromData<'a>: Sized {
    type Error;
    type Owned: Borrow<Self::Borrowed>;
    type Borrowed: ?Sized;
    fn transform(
        request: &Request, 
        data: Data
    ) -> Transform<Outcome<Self::Owned, Self::Error>>;
    fn from_data(
        request: &Request, 
        outcome: Transformed<'a, Self>
    ) -> Outcome<Self, Self::Error>;
}

Then, it's just a matter of reading the API for multipart and inserting tab A into slot B:

#![feature(proc_macro_hygiene, decl_macro)]

use multipart::server::Multipart; // 0.16.1, default-features = false, features = ["server"]
use rocket::{
    data::{Data, FromData, Outcome, Transform, Transformed},
    post, routes, Request,
}; // 0.4.2
use std::io::Read;

#[post("/", data = "<upload>")]
fn index(upload: DummyMultipart) -> String {
    format!("I read this: {:?}", upload)
}

#[derive(Debug)]
struct DummyMultipart {
    alpha: String,
    one: i32,
    file: Vec<u8>,
}

// All of the errors in these functions should be reported
impl<'a> FromData<'a> for DummyMultipart {
    type Owned = Vec<u8>;
    type Borrowed = [u8];
    type Error = ();

    fn transform(_request: &Request, data: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
        let mut d = Vec::new();
        data.stream_to(&mut d).expect("Unable to read");

        Transform::Owned(Outcome::Success(d))
    }

    fn from_data(request: &Request, outcome: Transformed<'a, Self>) -> Outcome<Self, Self::Error> {
        let d = outcome.owned()?;

        let ct = request
            .headers()
            .get_one("Content-Type")
            .expect("no content-type");
        let idx = ct.find("boundary=").expect("no boundary");
        let boundary = &ct[(idx + "boundary=".len())..];

        let mut mp = Multipart::with_body(&d[..], boundary);

        // Custom implementation parts

        let mut alpha = None;
        let mut one = None;
        let mut file = None;

        mp.foreach_entry(|mut entry| match &*entry.headers.name {
            "alpha" => {
                let mut t = String::new();
                entry.data.read_to_string(&mut t).expect("not text");
                alpha = Some(t);
            }
            "one" => {
                let mut t = String::new();
                entry.data.read_to_string(&mut t).expect("not text");
                let n = t.parse().expect("not number");
                one = Some(n);
            }
            "file" => {
                let mut d = Vec::new();
                entry.data.read_to_end(&mut d).expect("not file");
                file = Some(d);
            }
            other => panic!("No known key {}", other),
        })
        .expect("Unable to iterate");

        let v = DummyMultipart {
            alpha: alpha.expect("alpha not set"),
            one: one.expect("one not set"),
            file: file.expect("file not set"),
        };

        // End custom

        Outcome::Success(v)
    }
}

fn main() {
    rocket::ignite().mount("/", routes![index]).launch();
}

I've never used either of these APIs for real, so there's no guarantee that this is a good implementation. In fact, all the panicking on error definitely means it's suboptimal. A production usage would handle all of those cleanly.

However, it does work:

%curl -X POST -F alpha=omega -F one=2 -F file=@hello http://localhost:8000/
I read this: DummyMultipart { alpha: "omega", one: 2, file: [104, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 10] }

An advanced implementation would allow for some abstraction between the user-specific data and the generic multipart aspects. Something like Multipart<MyForm> would be nice.

The author of Rocket points out that this solution allows a malicious end user to POST an infinitely sized file, which would cause the machine to run out of memory. Depending on the intended use, you may wish to establish some kind of cap on the number of bytes read, potentially writing to the filesystem at some breakpoint.

like image 125
Shepmaster Avatar answered Nov 11 '22 16:11

Shepmaster


Official support for multipart form parsing in Rocket is still being discussed. Until then, take a look at the official example on how to integrate the multipart crate with Rocket: https://github.com/abonander/multipart/blob/master/examples/rocket.rs

like image 32
Danilo Bargen Avatar answered Nov 11 '22 17:11

Danilo Bargen