Ручной анализ сырых данных 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:)
Ответы
Ответ 1
Итак, с предложениями Дэйва и Эверта я решил разобрать необработанные данные запроса вручную. Я не нашел другого способа сделать это после поисков около дня.
Я получил некоторую помощь от этой темы. Мне не повезло, вмешиваясь в необработанные данные, как они делают в ссылочной теме, так как это сломает загружаемые файлы. Так что все это регулярное выражение. Это не было проверено очень хорошо, но, похоже, работает для моего рабочего случая. Без лишних слов и в надежде, что это когда-нибудь поможет кому-то другому:
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);
Отредактируйте - пожалуйста, прочитайте: этот ответ все еще получает регулярные хиты 7 лет спустя. С тех пор я никогда не использовал этот код и не знаю, есть ли лучший способ сделать это в наши дни. Пожалуйста, просмотрите комментарии ниже и знайте, что есть много сценариев, где этот код не будет работать. Используйте на свой риск.
Ответ 2
Я удивлен, что никто не упомянул 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
Ответ 3
Я использовал Chris функцию примера и добавил некоторые необходимые функции, такие как R Porter для массив $_FILES. Надеюсь, это поможет некоторым людям.
Вот class и пример usage
<?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']);
}
}
}
Ответ 4
Я подозреваю, что лучший способ сделать это - "сделать это самостоятельно", хотя вы можете найти вдохновение в многопользовательских синтаксических анализаторах, которые используют похожий (если не тот же) формат.
Возьмите границу из HTTP-заголовка Content-Type и используйте это, чтобы взорвать различные части запроса. Если запрос очень большой, имейте в виду, что вы можете хранить весь запрос в памяти, возможно, даже несколько раз.
Связанный RFC RFC2388, который, к счастью, довольно короткий.
Ответ 5
Я не очень разбирался в заголовках 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
Ответ 6
Вы просмотрели fopen("php://input")
для анализа содержимого?
Заголовки также могут быть найдены как $_SERVER['HTTP_*']
, имена всегда верхние и нижние символы становятся символами подчеркивания, например $_SERVER['HTTP_ACCEPT_LANGUAGE']
.