Подтвердить что ты не робот

Ручной анализ сырых данных 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:)

4b9b3361

Ответ 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'].