Manually parse raw multipart/form-data data with PHP
Edit - please read first: this answer is still getting regular hits 7 years later. I have never used this code since then and do not know if there is a better way to do it these days. Please view the comments below and know that there are many scenarios where this code will not work. Use at your own risk.
--
Ok, so with Dave and Everts suggestions I decided to parse the raw request data manually. I didn't find any other way to do this after searching around for about a day.
I got some help from this thread. I didn't have any luck tampering with the raw data like they do in the referenced thread, as that will break the files being uploaded. So it's all regex. This wasnt't tested very well, but seems to be working for my work case. Without further ado and in the hope that this may help someone else someday:
function parse_raw_http_request(array &$a_data){ // read incoming data $input = file_get_contents('php://input'); // grab multipart boundary from content type header preg_match('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'], $matches); $boundary = $matches[1]; // split content by boundary and get rid of last -- element $a_blocks = preg_split("/-+$boundary/", $input); array_pop($a_blocks); // loop data blocks foreach ($a_blocks as $id => $block) { if (empty($block)) continue; // you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char // parse uploaded files if (strpos($block, 'application/octet-stream') !== FALSE) { // match "name", then everything after "stream" (optional) except for prepending newlines preg_match("/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s", $block, $matches); } // parse all other fields else { // match "name" and optional value in between newline sequences preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $block, $matches); } $a_data[$matches[1]] = $matches[2]; } }
Usage by reference (in order not to copy around the data too much):
$a_data = array();parse_raw_http_request($a_data);var_dump($a_data);
I used Chris's example function and added some needed functionality, such as R Porter's need for array's of $_FILES. Hope it helps some people.
Here is the class & example usage
<?phpinclude_once('class.stream.php');$data = array();new stream($data);$_PUT = $data['post'];$_FILES = $data['file'];/* Handle moving the file(s) */if (count($_FILES) > 0) { foreach($_FILES as $key => $value) { if (!is_uploaded_file($value['tmp_name'])) { /* Use getimagesize() or fileinfo() to validate file prior to moving here */ rename($value['tmp_name'], '/path/to/uploads/'.$value['name']); } else { move_uploaded_file($value['tmp_name'], '/path/to/uploads/'.$value['name']); } }}
I would suspect the best way to go about it is 'doing it yourself', although you might find inspiration in multipart email parsers that use a similar (if not the exact same) format.
Grab the boundary from the Content-Type HTTP header, and use that to explode the various parts of the request. If the request is very large, keep in mind that you might store the entire request in memory, possibly even multiple times.
The related RFC is RFC2388, which fortunately is pretty short.