Userland Multipart/Form-Data Handler

userland multipart/form-data handler

It's late and I can't test this at the moment but the following should do what you want:

//$boundary = null;

if (is_resource($input = fopen('php://input', 'rb')) === true)
{

while ((feof($input) !== true) && (($line = fgets($input)) !== false))
{
if (isset($boundary) === true)
{
$content = null;

while ((feof($input) !== true) && (($line = fgets($input)) !== false))
{
$line = trim($line);

if (strlen($line) > 0)
{
$content .= $line . ' ';
}

else if (empty($line) === true)
{
if (stripos($content, 'name=') !== false)
{
$name = trim(stripcslashes(preg_replace('~.*name="?(.+)"?.*~i', '$1', $content)));

if (stripos($content, 'Content-Type:') !== false)
{
$tmpname = tempnam(sys_get_temp_dir(), '');

if (is_resource($temp = fopen($tmpname, 'wb')) === true)
{
while ((feof($input) !== true) && (($line = fgets($input)) !== false) && (strpos($line, $boundary) !== 0))
{
fwrite($temp, preg_replace('~(?:\r\n|\n)$~', '', $line));
}

fclose($temp);
}

$FILES[$name] = array
(
'name' => trim(stripcslashes(preg_replace('~.*filename="?(.+)"?.*~i', '$1', $content))),
'type' => trim(preg_replace('~.*Content-Type: ([^\s]*).*~i', '$1', $content)),
'size' => sprintf('%u', filesize($tmpname)),
'tmp_name' => $tmpname,
'error' => UPLOAD_ERR_OK,
);
}

else
{
$result = null;

while ((feof($input) !== true) && (($line = fgets($input)) !== false) && (strpos($line, $boundary) !== 0))
{
$result .= preg_replace('~(?:\r\n|\n)$~', '', $line);
}

if (array_key_exists($name, $POST) === true)
{
if (is_array($POST[$name]) === true)
{
$POST[$name][] = $result;
}

else
{
$POST[$name] = array($POST[$name], $result);
}
}

else
{
$POST[$name] = $result;
}
}
}

if (strpos($line, $boundary) === 0)
{
//break;
}
}
}
}

else if ((is_null($boundary) === true) && (strpos($line, 'boundary=') !== false))
{
$boundary = "--" . trim(preg_replace('~.*boundary="?(.+)"?.*~i', '$1', $line));
}
}

fclose($input);
}

echo '<pre>';
print_r($POST);
echo '</pre>';

echo '<hr />';

echo '<pre>';
print_r($FILES);
echo '</pre>';

Get raw post data

Direct answer: you can not do that. PHP insists on parsing it itself, whenever it sees the multipart/form-data Content-Type. The raw data will not be available to you. Sadly. But you can hack around it.

I hit a similar problem, a partner was sending incorrectly formatted data as multipart/form-data, PHP could not parse it and was not giving it out so I could parse it myself.

The solution? I added this to my apache conf:

<Location "/backend/XXX.php">
SetEnvIf Content-Type ^(multipart/form-data)(.*) NEW_CONTENT_TYPE=multipart/form-data-alternate$2 OLD_CONTENT_TYPE=$1$2
RequestHeader set Content-Type %{NEW_CONTENT_TYPE}e env=NEW_CONTENT_TYPE
</Location>

This will change the Content-Type of incoming request to XXX.php from multipart/form-data to multipart/form-data-alternate, which is enough to block PHP from trying to parse it

After this you can finally read the whole raw data from php://input and parse it yourself.

It is ugly, but I have not found a better or in fact any other solution - short of asking the partner to fix their side.

NB! When you do what I described here, $_FILES will be empty.

Posting an empty filefield with CURLOPT_POSTFIELDS to multipart form

Without refactoring your backend or emulating a multi-part form submission manually I'm pretty sure you won't be able to solve your problem with any CURLOPT_POSTFIELDS logic alone, also see:

  • http_request_body_encode
  • Generating multipart boundary
  • userland multipart/form-data handler

If you insist on trying to solve this using CURL alone, I suggest you inspect the ext/curl source code.

How to handle images sent by a mobile device?

Doesn't appear it's being sent via a form, i.e., <form enctype=multipart/form-data"> and <input type="file">, so the $_FILES array won't be populated.

You'll probably need to read:

$HTTP_RAW_POST_DATA

or do:

$rawPost = file_get_contents("php://input");

From the manual:

php://input allows you to read raw
data from the request body. In case of
POST requests, it preferrable to
$HTTP_RAW_POST_DATA as it does not
depend on special php.ini directives.
Moreover, for those cases where
$HTTP_RAW_POST_DATA is not populated
by default, it is a potentially less
memory intensive alternative to
activating
always_populate_raw_post_data.
php://input is not available with
enctype="multipart/form-data".

For more info, check out:

http://php.net/manual/en/wrappers.php.php

http://php.net/manual/en/reserved.variables.httprawpostdata.php

php://input is empty on POST request

Before PHP 5.4 $HTTP_RAW_POST_DATA is not available with enctype="multipart/form-data" (with the exception of some SAPI implementations), explanations here:

  • http://www.php.net/manual/en/wrappers.php.php
  • http://www.php.net/manual/en/ini.core.php#ini.always-populate-raw-post-data

I suggest you give a look to a couple of answers to existing questions:

  • php http request content raw data enctype=multipart/form-data
  • userland multipart/form-data handler

From PHP 5.4+ you can use the php.ini directive enable_post_data_reading to disable PHP consuming the raw data (hence process it), be aware that $_POST and $_FILES won't be populated though (refer to Vitaly Chirkov answer).

Empty $_POST without Content-type

You can override/inject the necessary header using Apache mod_headers if you have to. Or otherwise resort to manually reconstructing the $_POST array. (See also userland multipart/form-data handler)

If it's always urlencoded, simply read from php://input (Which contains the raw POST request body) and use parse_str:

parse_str(file_get_contents("php://input"), $_POST);

That should recreate the POST array, pretty much like PHP would do.



Related Topics



Leave a reply



Submit