Ручной анализ сырых данных multipart / form-data с помощью PHP

Кажется, я не могу найти реального ответа на эту проблему, поэтому я иду:

Как вы анализируете необработанные данные запроса HTTP в формате multipart/form-data в PHP? Я знаю, что raw POST автоматически анализируется, если отформатирован правильно, но данные, которые я имею в виду, поступают из запроса PUT, который автоматически не обрабатывается PHP. Данные многочастны и выглядят примерно так:

 ------------------------------b2449e94a11c Content-Disposition: form-data; name="user_id" 3 ------------------------------b2449e94a11c Content-Disposition: form-data; name="post_id" 5 ------------------------------b2449e94a11c Content-Disposition: form-data; name="image"; filename="/tmp/current_file" Content-Type: application/octet-stream      JFIF         ... a bunch of binary data 

Я отправляю данные с libcurl, как это (псевдо-код):

 curl_setopt_array( CURLOPT_POSTFIELDS => array( 'user_id' => 3, 'post_id' => 5, 'image' => '@/tmp/current_file'), CURLOPT_CUSTOMREQUEST => 'PUT' ); 

Если я отбрасываю бит CURLOPT_CUSTOMREQUEST, запрос обрабатывается как POST на сервере, и все анализируется просто отлично.

Есть ли способ вручную вызывать парсер PHP-данных PHP или какой-либо другой хороший способ сделать это? И да, я должен отправить запрос как PUT 🙂

Хорошо, поэтому с предложениями Дейва и Эвертса я решил вручную разобрать необработанные данные запроса. Я не нашел другого способа сделать это после поиска около одного дня.

Я получил некоторую помощь из этой темы . У меня не было никакой удачи в подделке необработанных данных, подобных тем, которые есть в упомянутом потоке, так как это приведет к поломке загружаемых файлов. Так что это все регулярное выражение. Это не было проверено очень хорошо, но, похоже, работает для моего рабочего дела. Без дальнейших церемоний и в надежде, что это может когда-нибудь помочь кому-то другому:

 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]; } } 

Использование по ссылке (чтобы не копировать данные слишком много):

 $a_data = array(); parse_raw_http_request($a_data); var_dump($a_data); 

Изменить: см. Ответ Jas ниже, он добавил поддержку нескольких файлов и некоторые другие функции.

Я удивлен, что никто не упомянул parse_str или mb_parse_str :

 $result = []; $rawPost = file_get_contents('php://input'); mb_parse_str($rawPost, $result); var_dump($result); 

http://php.net/manual/en/function.mb-parse-str.php

Я подозреваю, что лучший способ сделать это – «делать это самостоятельно», хотя вы можете найти вдохновение в многопользовательских парсерах, которые используют аналогичный (если не тот же) формат.

Возьмите границу из HTTP-заголовка Content-Type и используйте это, чтобы взорвать различные части запроса. Если запрос очень большой, имейте в виду, что вы можете хранить весь запрос в памяти, возможно, даже несколько раз.

Связанный RFC – RFC2388 , который, к счастью, довольно короткий.

Я не очень разбирался в заголовках http, но нашел этот код, который может помочь

 function http_parse_headers( $header ) { $retVal = array(); $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header)); foreach( $fields as $field ) { if( preg_match('/([^:]+): (.+)/m', $field, $match) ) { $match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1]))); if( isset($retVal[$match[1]]) ) { $retVal[$match[1]] = array($retVal[$match[1]], $match[2]); } else { $retVal[$match[1]] = trim($match[2]); } } } return $retVal; } 

С http://php.net/manual/en/function.http-parse-headers.php

Вы просмотрели fopen("php://input") для синтаксического анализа содержимого?

Заголовки также могут быть найдены как $_SERVER['HTTP_*'] , имена всегда имеют верхний регистр, а штрихи становятся $_SERVER['HTTP_ACCEPT_LANGUAGE'] подчеркивания, например $_SERVER['HTTP_ACCEPT_LANGUAGE'] .

Я использовал примерную функцию Криса и добавил некоторые необходимые функции, такие как потребность R Porter для массива $ _FILES. Надеюсь, это поможет некоторым людям.

Вот пример использования класса и примера

 <?php include_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']); } } }