How to parse multipart forms using abonander/multi

2019-02-14 06:40发布

问题:

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

回答1:

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: Sized {
    type Error;
    fn from_data(request: &Request, data: Data) -> Outcome<Self, Self::Error>;
}

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

#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;
extern crate multipart;

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

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

use std::io::{Cursor, Read};
use rocket::{Request, Data, Outcome};
use rocket::data::{self, FromData};
use multipart::server::Multipart;

impl FromData for DummyMultipart {
    type Error = ();

    fn from_data(request: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
        // All of these errors should be reported
        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 d = Vec::new();
        data.stream_to(&mut d).expect("Unable to read");

        let mut mp = Multipart::with_body(Cursor::new(d), boundary);

        // Custom implementation parts

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

        mp.foreach_entry(|mut entry| {
            match entry.name.as_str() {
                "alpha" => {
                    let t = entry.data.as_text().expect("not text");
                    alpha = Some(t.into());
                },
                "one" => {
                    let t = entry.data.as_text().expect("not text");
                    let n = t.parse().expect("not number");
                    one = Some(n);
                },
                "file" => {
                    let mut d = Vec::new();
                    let f = entry.data.as_file().expect("not file");
                    f.read_to_end(&mut d).expect("cant read");
                    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 before about an hour ago, 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.