Posting multipart form data using curl in PHP.

In this article I will encode post fields using multipart form data encoding.

First of all, I should say that when posting, curl does the multipart form data encoding by default if you pass an array as the CURLOPT_POSTFIELDS option.

For example, this code…

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://localhost:8888");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POSTFIELDS, array('a' => 'b', 'c' => 'd'));
curl_exec($ch);

will produce this request:

POST / HTTP/1.1
Host: localhost:8888
Accept: */*
Content-Length: 228
Expect: 100-continue
Content-Type: multipart/form-data; boundary=------------------------f287dd3807057a2c

--------------------------f287dd3807057a2c
Content-Disposition: form-data; name="a"
Content-Length: 1

b
--------------------------f287dd3807057a2c
Content-Disposition: form-data; name="c"
Content-Length: 1

d
--------------------------f287dd3807057a2c--

What motivated me to build the request body was the fact that when I was trying to use a particular API, I had to send some parameters that had the same name. It is not possible in curl PHP to pass an array as CURLOPT_POSTFIELDS option because it is not possible to have two values with the same key in a PHP array.

So the only solution I found was to build the request body by myself.

If you see, these are the rules to build the body:

  • Each post field should be in its part.
  • Each part is separated by a boundary, that consists in a random string that begins with “–”, with the condition that this string is not found in any part.
  • The first line in each part says the content disposition and the name of the parameter, the second one the content length, then it comes an empty line, and by last the most important thing, the data.
  • The body closes with the boundary string ended with “–” (two dashes).

So I coded two functions in order to build the body: getPart and getBody.

The first one builds a part from a “name-value” pair and a boundary…

function getPart($name, $value, $boundary)
{
  $eol = "\r\n";

  $part = '--'. $boundary . $eol;
  $part .= 'Content-Disposition: form-data; name="' . $name . '"' . $eol;
  $part .= 'Content-Length: ' . strlen($value) . $eol . $eol;
  $part .= $value . $eol;

  return $part;
}

The second one, getBody, concatenates the different parts in order to build the body. It receives the boundary as a parameter.

function getBody($boundary)
{
  $eol = "\r\n";

  $body = getPart('a', 'b', $boundary);
  $body .= getPart('c', 'd', $boundary);
  $body .= '--'. $boundary . '--' . $eol;

  return $body;
}

Using it…

// random boundary
$boundary = 'TITO-' . md5(time());

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://localhost:8888");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER,
  array('Content-Type: multipart/form-data; boundary=' . $boundary));
curl_setopt($ch, CURLOPT_POSTFIELDS, getBody($boundary));
curl_exec($ch);

It seems to work!