0

I have an API I am trying to use that accepts multiple files in a single HTTP POST via multipart form data.

The problem is, the files have the same key (images). In Postman, the sample request looks like so: Postman example

And in cURL this works too:

curl --location 'http://my-amazing-service.local' \
--form 'images=@"/Users/zach/Desktop/1.jpg"' \
--form 'images=@"/Users/zach/Desktop/2.jpg"' \
--form 'images=@"/Users/zach/Desktop/3.jpg"'

The problem is in PHP, CURLOPT_POSTFIELDS does not take multidimensional arrays. For whatever reason, it works on my Mac I do anyway, but not inside a Docker container running Alpine Linux.

When I run the code below on Alpine Linux (in a Docker container), I can see from the Content-Length in the header is only 146, meaning the binary contents of the image are not being included (my test image is about 50KB).

Is it possible to pass multiple files into CURLOPT_POSTFIELDS? I'd rather not have to write the raw multipart POST body manually if possible.

$curl = curl_init();
      
curl_setopt_array($curl, array(
    CURLOPT_URL => 'http://my-amazing-service.local',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 0,
    CURLOPT_POSTFIELDS => [
        'images' => [
            file_get_contents('1.jpg'),
            file_get_contents('2.jpg'),
            file_get_contents('3.jpg'),
        ],
    ]
));
$verbose = fopen('php://temp', 'w+');
curl_setopt($curl, CURLOPT_STDERR, $verbose);
curl_setopt($curl, CURLOPT_VERBOSE, true);
$response = curl_exec($curl);
if ($response === FALSE) {
    printf("cURL error: %s\n", curl_error($curl));
}
rewind($verbose);
$verboseLog = stream_get_contents($verbose);
echo "Verbose information:\n<pre>", $verboseLog, "</pre>\n";        
curl_close($curl);    
Zach Rattner
  • 20,745
  • 9
  • 59
  • 82

2 Answers2

0

You can use the function curl_file_create to attach multiple files on your request, e.g:

$file1 = curl_file_create('1.jpg');
$file2 = curl_file_create('2.jpg');

$data = array(
  'file1' => $file1,
  'file2' => $file2
);

$ch = curl_init('https://example.com/upload.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);

Using your approach:

$curl = curl_init();
      
curl_setopt_array($curl, array(
    CURLOPT_URL => 'http://my-amazing-service.local',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 0,
    CURLOPT_POSTFIELDS => [
        'file1'  => curl_file_create('1.jpg'),
        'file2' => curl_file_create('2.jpg'),
        'file3' => curl_file_create('3.jpg'),
    ]
));

$response = curl_exec($curl);

In order to use a single key on the CURLOPT_POSTFIELDS, you have to use the same syntax used to pass an array in a query string:

curl_setopt_array($curl, array(
    CURLOPT_URL => 'http://my-amazing-service.local',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 0,
    CURLOPT_POSTFIELDS => [
        'files[0]'  => curl_file_create('1.jpg'),
        'files[1]' => curl_file_create('2.jpg'),
        'files[2]' => curl_file_create('3.jpg'),
    ]
));

This is the same syntax that we use to pass an array of values on a query string:

http://example.com?name[0]=jon&name[1]=ari&name[2]=alex
Rafa Acioly
  • 530
  • 9
  • 34
  • Thanks, this will work if the API accepts `file1`, `file2`, and `file3` as the keys. But if the API accepts just `files`, it seems I can't pass an array of files into it? – Zach Rattner Jul 26 '23 at 00:37
  • @ZachRattner I've added another example – Rafa Acioly Jul 26 '23 at 00:45
  • Thanks, but this still doesn't work - the multipart POST data literally calls each key `files[0]` and as a result if the client is looking for multiple `files` parameters, the recipient doesn't get the data. Looks like the reason this is hard is the kets have to be unique, which the API isn't honoring right now. So I think there's no good solution apart from changing the API: https://www.ietf.org/rfc/rfc2388.txt – Zach Rattner Jul 27 '23 at 21:47
0

After some more checking, it looks like the reason it's so hard to send a bunch of files in the same field name is because it technically violates RFC 2388:

In forms, there are a series of fields to be supplied by the user who fills out the form. Each field has a name. Within a given form, the names are unique.

So this problem just went from a "Can I do it?" to a "Should I do it?"

I think the better solution is to reach out to the API maintainer and bring this up.

Zach Rattner
  • 20,745
  • 9
  • 59
  • 82