reqwest
reqwest copied to clipboard
Slash in nested multipart form silently fails file upload
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
How does it fail? Do you see an error? Or does the server say something?
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--
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 :)