Http Client mangles multiple-valued keys in parseMultipartBodyFormat
Laravel Version
12.x
PHP Version
8.4
Database Driver & Version
No response
Description
Premise: http post data often contains multiple values for the same key. This is needed to post the selection of checkbox widgets, select widgets with the multiple flag set, and others. Laravel's http client receives the post data and stores it internally as a keyed array, therefore the only way to post multiple values for a single key is to associate the key with an array of values:
Http::post('http://example.com/users', [
'name' => 'Steve',
'roles' => ['Network Administrator', 'Janitor'],
]);
This bug happens when the request includes both attached files, which forces it to be sent as multipart/form-data, and checkboxes or other input elements with multiple values. The source of the bug is PendingRequest::parseMultipartBodyFormat in the following code:
is_array($value) ? $value : ['name' => $key, 'contents' => $value]
This fails to handle multiple value arrays, such as ['Network Administrator', 'Janitor'].
Steps To Reproduce
Use the Laravel Http client to make a POST request that includes both an attached file and a multiple-valued key. For example:
Http::attach(
'attachment', file_get_contents('photo.jpg'), 'photo.jpg', ['Content-Type' => 'image/jpeg']
)->post('http://example.com/users', [
'name' => 'Steve',
'roles' => ['Network Administrator', 'Janitor'],
]);
This mangles the multiple-valued key and results in InvalidArgumentException: A 'contents' key is required.
Thank you for reporting this issue!
As Laravel is an open source project, we rely on the community to help us diagnose and fix issues as it is not possible to research and fix every issue reported to us via GitHub.
If possible, please make a pull request fixing the issue you have described, along with corresponding tests. All pull requests are promptly reviewed by the Laravel team.
Thank you!
I think accepting an array with a Multipart request might just be assuming you are sending your own like this:
Http::attach(
'attachment', file_get_contents('photo.jpg'), 'photo.jpg', ['Content-Type' => 'image/jpeg']
)->post('http://example.com/users', [
[
'name' => 'name',
'contents' => 'Steve',
],
[
'name' => 'roles[]',
'contents' => 'Network Administrator',
],
[
'name' => 'roles[]',
'contents' => 'Janitor',
],
]);
So it's probably not safe to try and do anything clever in parseMultipartBodyFormat() to handle it.
https://docs.guzzlephp.org/en/stable/request-options.html#multipart
But it also looks like parseHttpOptions supports Arrayable classes, so in theory you could create your own class to pass in there and supply the data in the right format depending on whether it's multipart or not.
@Muffinman It was not clear to me, by looking at the documentation, that I could pass a numeric array of associative arrays, each with name and contents. I thought the second argument to post() had to be an associative array of [$name => $contents] pairs.
Edit: I just checked, the numeric array of associative arrays only works when issuing a multipart request. In the other case asForm() I still need to send an "array value," as in: 'roles' => ['Network Administrator', 'Janitor'] So I still think that the "array value" should be better supported by the multipart implementation, as well as documenting that the user can pass a different format (numeric array of associative arrays) and what keys are supported in the latter case.
But at least now there is a clear workaround on how to issue multipart POST requests with array fields. Thanks.
I've reviewed the issue with Laravel's HTTP client mishandling multipart/form-data when sending files alongside multi-valued fields like checkboxes. I understand the root cause in parseMultipartBodyFormat() and can fix it by correctly formatting array values using GuzzleHttp\Psr7\MultipartStream.
I can implement a clean solution (via helper or macro) that maintains Laravel's syntax and resolves the error. I'm confident I can handle this quickly and effectively.
Looking forward to your response!
Hi! I’d like to work on this issue and submit a PR to address it. If anyone else is already working on it, please let me know. Otherwise, I’ll start working on a fix. Thanks!