6

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
rofrol
  • 14,438
  • 7
  • 79
  • 77
  • 2
    Producing a [MCVE] would go a long way towards getting a good answer. As it is right now, this question feels a lot like like "please write my code for me". It's expected to [show a large amount of effort](https://meta.stackoverflow.com/q/261592/155423) when asking a question. Right now, any answerer would have to create a basic Rocket application from scratch as well as a applicable client to test it, in addition to actually solving the problem. There's a vast difference in the amount of effort shown and effort requested. – Shepmaster Apr 15 '17 at 13:49
  • I was planning to expand my question as I learn more, but your answer was so quick :) thanks – rofrol Apr 15 '17 at 20:10

2 Answers2

10

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
1

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

Danilo Bargen
  • 18,626
  • 15
  • 91
  • 127
  • 1
    Mods: It's usually bad practice to link to external code instead of integrating it into the answer, but in this case the code may change together with the rocket and multipart libraries, so it does not make sense to copy-paste the almost 100 lines of code into my answer. The answer to "how do I use this library with rocket" is "take a look at this official up-to-date example". – Danilo Bargen Sep 07 '19 at 20:37
  • 2
    @Dharman thanks for the note :) I updated the answer, do you think it is better now? – Danilo Bargen Sep 08 '19 at 01:09