8000 Malformed data while Content Type is not specified in multipart forms · Issue #2934 · rwf2/Rocket · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
Malformed data while Content Type is not specified in multipart forms #2934
Open
@Usioumeo

Description

@Usioumeo

Rocket Version

0.5.1

Operating System

Arch Linux

Rust Toolchain Version

rustup 1.28.1 (2025-03-05)

What happened?

We recently migrated our association code from rocket 0.4 to rocket 0.5.1. First there was no support for multipart data, and so we relied on rocket_multipart_form_data - crate, then we decided to use the TempFile data guard provided by rocket. We have a generic form upload for image, and for many format we support there is no valid ContentType, and because of that we decided to not specify it (given that it should be optional by our knowledge, and should not modify the parsing in any way).

But then we started getting malformed data out of our system.
I recreated a minimal reproducible to illustrate the issue.

Test Case

use reqwest::multipart;
use rocket::{FromForm, form::Form, fs::TempFile, launch, post, routes};

#[derive(FromForm)]
struct DummyForm<'a> {
    image: TempFile<'a>,
}

#[post("/", data = "<d
8000
ata>")]
async fn small_post(mut data: Form<DummyForm<'_>>) -> &'static str {
    //save file to a known location in order to read its content
    data.image.persist_to("/tmp/recv.png").await.unwrap();
    let recv = std::fs::read("/tmp/recv.png").unwrap();
    //generate same data
    let bytes: Vec<u8> = (0..140).map(|x| (x % 256) as u8).collect();
    //check if the data are the same
    assert_eq!(bytes, recv);
    "ok"
}

#[launch]
async fn rocket() -> _ {
    // spawn a task to send a request
    rocket::tokio::spawn(async {
        // wait for rocket startup
        rocket::tokio::time::sleep(rocket::tokio::time::Duration::from_secs(1)).await;

        //generate bytes to send
        let bytes: Vec<u8> = (0..140).map(|x| (x % 256) as u8).collect();
        //build form ()
        let image = multipart::Part::bytes(bytes)
            //UNCOMMENT IF WANT TO SET CONTENT TYPE
            //.headers(HeaderMap::from_iter(vec![(CONTENT_TYPE, HeaderValue::from_static("image/png")),]))
            ;
        let form = multipart::Form::new().part("image", image);

        let client = reqwest::Client::builder().build().unwrap();
        let resp = client
            .post("http://localhost:8000/")
            .multipart(form)
            .send()
            .await
            .unwrap();
        println!("{:?}", resp);
    });

    rocket::build().mount("/", routes![small_post])
}

Log Output

ROCKET_LOG_LEVEL=debug cargo run                                                        ✔  7s  
   Compiling stupid v0.1.0 (/home/alessio/Documents/Mindshub/insignio/insigno_backend/stupid)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.28s
     Running `target/debug/stupid`
-- configuration trace information --
   >> "address" parameter source: rocket::Config::default()
   >> "port" parameter source: rocket::Config::default()
   >> "workers" parameter source: rocket::Config::default()
   >> "max_blocking" parameter source: rocket::Config::default()
   >> "keep_alive" parameter source: rocket::Config::default()
   >> "ident" parameter source: rocket::Config::default()
   >> "ip_header" parameter source: rocket::Config::default()
   >> "limits" parameter source: TOML file (../Rocket.toml)
   >> "temp_dir" parameter source: rocket::Config::default()
   >> "log_level" parameter source: `ROCKET_` environment variable(s)
   >> "shutdown" parameter source: rocket::Config::default()
   >> "cli_colors" parameter source: rocket::Config::default()
🔧 Configured for debug.
   >> address: 127.0.0.1
   >> port: 8000
   >> workers: 16
   >> max blocking threads: 512
   >> ident: Rocket
   >> IP header: X-Real-IP
   >> limits: bytes = 8KiB, data-form = 64MB, file = 64MB, form = 128kB, json = 1MiB, msgpack = 1MiB, string = 8KiB
   >> temp dir: /tmp
   >> http/2: true
   >> keep-alive: 5s
   >> tls: disabled
   >> shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
   >> log level: debug
   >> cli colors: true
📬 Routes:
   >> (small_post) POST /
📡 Fairings:
   >> Shield (liftoff, response, singleton)
🛡️ Shield:
   >> X-Frame-Options: SAMEORIGIN
   >> Permissions-Policy: interest-cohort=()
   >> X-Content-Type-Options: nosniff
🚀 Rocket has launched from http://127.0.0.1:8000

--> /home/alessio/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/reqwest-0.12.15/src/connect.rs:625
        starting new connection: http://localhost:8000/

--> /home/alessio/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rocket-0.5.1/src/server.rs:77
        received request: Request {
    method: POST,
    uri: /,
    version: HTTP/1.1,
    headers: {
        "content-type": "multipart/form-data; boundary=d21ec4330d930b93-195c0b0274147312-0682dac8e0cc00cd-74d04b62a85a3e59",
        "content-length": "334",
        "accept": "*/*",
        "host": "localhost:8000",
    },
    body: Body(
        Streaming,
    ),
}
POST / multipart/form-data:
   >> Matched: (small_post) POST /
   >> multipart field: Field { state: Mutex { data: MultipartState { buffer: StreamBuffer, boundary: "d21ec4330d930b93-195c0b0274147312-0682dac8e0cc00cd-74d04b62a85a3e59", stage: ReadingFieldData, next_field_idx: 1, curr_field_name: Some("image"), curr_field_size_limit: 64000000, curr_field_size_counter: 0, constraints: Constraints { size_limit: SizeLimit { whole_stream: 64000000, per_field: 64000000, field_map: {} }, allowed_fields: None } }}, done: false, headers: {"content-disposition": "form-data; name=\"image\""}, content_disposition: ContentDisposition { field_name: Some("image"), file_name: None }, content_type: None, idx: 0 }

thread 'rocket-worker-thread' panicked at src/main.rs:17:5:
assertion `left == right` failed
  left: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139]
 right: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189, 239, 191, 189]
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
   >> Handler small_post panicked.
   >> This is an application bug.
   >> A panic in Rust must be treated as an exceptional event.
   >> Panicking is not a suitable error handling mechanism.
   >> Unwinding, the result of a panic, is an expensive operation.
   >> Panics will degrade application performance.
   >> Instead of panicking, return `Option` and/or `Result`.
   >> Values of either type can be returned directly from handlers.
   >> A panic is treated as an internal server error.
   >> Outcome: Error(500 Internal Server Error)
   >> No 500 catcher registered. Using Rocket default.

--> /home/alessio/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rocket-0.5.1/src/server.rs:166
        sending response: Response {
    status: 500,
    version: HTTP/1.1,
    headers: {
        "content-type": "text/html; charset=utf-8",
        "server": "Rocket",
        "x-frame-options": "SAMEORIGIN",
        "permissions-policy": "interest-cohort=()",
        "x-content-type-options": "nosniff",
        "content-length": "488",
    },
    body: Body(
        Streaming,
    ),
}
   >> Response succeeded.
Response { url: "http://localhost:8000/", status: 500, headers: {"content-type": "text/html; charset=utf-8", "server": "Rocket", "x-frame-options": "SAMEORIGIN", "permissions-policy": "interest-cohort=()", "x-content-type-options": "nosniff", "content-length": "488", "date": "Tue, 22 Apr 2025 14:04:40 GMT"} }

Additional Context

I haven't used the rocket local module because I do not know how to recreate the problem.
For the same reason I included the async module so that it is easily testable.

System Checks

  • My bug report relates to functionality.
  • I have tested against the latest Rocket release or a recent git commit.
  • I have tested against the latest stable rustc toolchain.
  • I was unable to find this issue previously reported.

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageA bug report being investigated

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0