reqwest icon indicating copy to clipboard operation
reqwest copied to clipboard

Slash in nested multipart form silently fails file upload

Open korewaChino opened this issue 2 years ago • 3 comments

Take this production code:

let files: Vec<(String, PathBuf)> = self.files.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
        let mut form = multipart::Form::new()
            .text("build_id", build_id);


        for file in &files {
            // add to array of form data
            let (path, aa) = file;

            let mut openfile = File::open(&aa).await?;

            let mut buf = Vec::new();
            openfile.read(&mut buf).await?;

            debug!("adding file: {}", aa.display());
            let filename = aa.file_name().unwrap().to_str().unwrap();
            debug!("filename: {}", filename);
            let filename = format!("{}", filename);

            // add part to form
            let file_part = multipart::Part::bytes(buf)
                .file_name(filename)
                .mime_str("application/octet-stream")?;

            // Get a position of the hashmap by matching the key to the path
            //let pos = files.clone().iter().position(|(k, _)| &k == &path);

            //form = form.part(format!("files[{}]", pos.unwrap()), file_part);
            form = form.part(format!("files[{}]", path), file_part);

        }

When a part called foo is passed in the form, this code will work properly. However, when a file with path foo/bar is passed in, the part with said nested form fails.

If the form part name does not contain any slashes, it will work properly.

EDIT: This issue seems to be also discussed in #419 #1218 #467

korewaChino avatar Jul 19 '22 07:07 korewaChino

How does it fail? Do you see an error? Or does the server say something?

seanmonstar avatar Jul 19 '22 13:07 seanmonstar

How does it fail? Do you see an error? Or does the server say something?

reqwest fails to parse the nested form properly. There are no errors (it fails silently), the server acts as if there is no form part with the specified name.

Here's the output of nc when listening to the request:

POST /artifacts HTTP/1.1
content-type: multipart/form-data; boundary=d2485401f3723dc8-9744e4b61b5d11e3-bdab875e6c11d363-f5397025e1d06c23
content-length: 735
accept: */*
host: localhost:8080

--d2485401f3723dc8-9744e4b61b5d11e3-bdab875e6c11d363-f5397025e1d06c23
Content-Disposition: form-data; name="build_id"

563d25a5-ad8f-4718-86eb-646bf8ccee26
--d2485401f3723dc8-9744e4b61b5d11e3-bdab875e6c11d363-f5397025e1d06c23
Content-Disposition: form-data; name*=utf-8''files[x86_64%2Fanda-0.1.0-1.um36.x86_64.rpm]; filename="anda-build/x86_64/anda-0.1.0-1.um36.x86_64.rpm"
Content-Type: application/octet-stream


--d2485401f3723dc8-9744e4b61b5d11e3-bdab875e6c11d363-f5397025e1d06c23
Content-Disposition: form-data; name="files[anda-0.1.0-1.um36.src.rpm]"; filename="anda-build/anda-0.1.0-1.um36.src.rpm"
Content-Type: application/octet-stream


--d2485401f3723dc8-9744e4b61b5d11e3-bdab875e6c11d363-f5397025e1d06c23--

Note that the first file's form data has name*=utf-8'' before the actual form data, instead of the usual name="". This is probably the issue. The name is also percent-coded, which is not other HTTP libraries do.

cURL does not percent-code form names, it seems.

curl --request POST \
  --url 'http://localhost:8080/artifacts?=' \
  --header 'Content-Type: multipart/form-data' \
  --form build_id=563d25a5-ad8f-4718-86eb-646bf8ccee26 \
  --form 'files[a/a/credentials.json]=@/home/cappy/Downloads/credentials.json'
POST /artifacts?= HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.82.0
Accept: */*
Content-Length: 501
Content-Type: multipart/form-data; boundary=------------------------155eb790cd4dd4ce

--------------------------155eb790cd4dd4ce
Content-Disposition: form-data; name="build_id"

563d25a5-ad8f-4718-86eb-646bf8ccee26
--------------------------155eb790cd4dd4ce
Content-Disposition: form-data; name="files[a/a/credentials.json]"; filename="credentials.json"
Content-Type: application/octet-stream

{"url":"http://172.17.0.2:9000","accessKey":"XDxwT19aj8hpIJoB","secretKey":"ALe67utbNnuYTVcHaQbPdgXTUKQb7cPL","api":"s3v4","path":"auto"}
--------------------------155eb790cd4dd4ce--

korewaChino avatar Jul 19 '22 13:07 korewaChino

You can disable percent-encoding of form values with Form::percent_encode_noop(). Some clients do it (since the spec said to), and some server frameworks expect it, others don't. That's why the options on Form exist :)

seanmonstar avatar Jul 19 '22 14:07 seanmonstar