I'm looking for a drop-in include script / class that dissects multipart/form-data
and fills up $_POST
(+raw) and $_FILES
from it. Usually PHP does that itself. But because the automatic handling is insufficient for me and makes php://input
inaccesible[1] I'll probably be using something like this to prevent that:
RewriteRule .* - [E=CONTENT_TYPE:noparsing/for-you-php]
Does not work. Actual solution requires mod_headers andRequestHeader set
...
The extracting procedure might not be that complex. But I'd rather use a well-tested solution. And foremost I would prefer an implementation that uses fgets
for splitting, and mimics the $_FILES
handling closely and efficiently. Finding the end of binary payloads would seem rather tricky to me, in particular when you have to strip off \r\n
but might encounter clients that only send \n
(not allowed, but possible).
I'm certain something like this exists. But I'm having a hard time googling it. Does anyone know an implementation? (PEAR::mimeDecode can be hacked to get sort of working for form-data, but is a memory hog.)
The use case in short: need to preserve the raw field names (including whitespace and special characters), for logging, but can't avoid file uploads always.
For decorative purposes, that's how a POST request looks:
POST / HTTP/1.1
Host: localhost:8000
Content-Length: 17717
Content-Type: multipart/form-data; boundary=----------3wCuBwquE9P7A4OEylndVx
And after a \r\n\r\n
sequence the multipart/ payload follows like this:
------------3wCuBwquE9P7A4OEylndVx
Content-Disposition: form-data; name="_charset_"
windows-1252
------------3wCuBwquE9P7A4OEylndVx
Content-Disposition: form-data; name=" text field \\ 1 \";inject=1"
text1 te twj sakfkl
------------3wCuBwquE9P7A4OEylndVx
Content-Disposition: form-data; name="file"; filename="dial.png"
Content-Type: image/png
IPNG Z @@@MIHDR@@B`@@B;HF@@@-'.e@@@[email protected]\i@@@FbKGD@?@?@? ='S@@@
@@@GtIMEGYAAU,#}BRU@@@YtEXtComment@Created with GIMPWANW@@ @IDATxZl]w|
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>';
Maybe a new php.ini directive enable_post_data_reading could help, but it seems it was added in PHP 5.4, I still have the earlier version so could not test it :(
From PHP Manual:
enable_post_data_reading boolean
Disabling this option causes $_POST and $_FILES not to be populated. The only way to read postdata will then be through the php://input stream wrapper. This can be useful to proxy requests or to process the POST data in a memory efficient fashion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With