使用PHP手动解析原始多部分/表单数据

使用PHP手动解析原始多部分/表单数据,php,http,parsing,curl,Php,Http,Parsing,Curl,我似乎找不到这个问题的真正答案,因此我决定: 如何在PHP中解析multipart/formdata格式的原始HTTP请求数据?我知道如果格式正确,原始帖子会自动解析,但我所指的数据来自PUT请求,PHP不会自动解析。数据是多部分的,看起来像: ------------------------------b2449e94a11c Content-Disposition: form-data; name="user_id" 3 ------------------------------b244

我似乎找不到这个问题的真正答案,因此我决定:

如何在PHP中解析
multipart/formdata
格式的原始HTTP请求数据?我知道如果格式正确,原始帖子会自动解析,但我所指的数据来自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发送数据,如下所示(伪代码):

如果我删除CURLOPT_CUSTOMREQUEST位,请求将作为POST在服务器上处理,所有内容都会被解析

有没有一种方法可以手动调用PHPs HTTP数据解析器,或者其他一些很好的方法?
是的,我必须以PUT的形式发送请求:)

我没有太多地处理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\u parse\u头($header)
{
$retVal=array();
$fields=explode(“\r\n”,preg_replace('/\x0D\x0A[\x09\x20]+/','','',$header));
foreach($fields作为$field){
if(preg_match('/([^::]+):(.+)/m',$field,$match)){

$match[1]=preg_replace('/(?你看过
fopen('php://input“,“r”)
用于解析内容


标题也可以作为
$\u SERVER['HTTP.*']
找到,名称总是大写,破折号变成下划线,例如
$\u SERVER['HTTP.'u ACCEPT.'u LANGUAGE']

我认为最好的方法是“自己动手”,尽管你可能会从使用类似方法的多部分电子邮件解析器中找到灵感(如果不完全相同)格式

从内容类型HTTP头中获取边界,并使用该边界分解请求的各个部分。如果请求非常大,请记住,您可能会将整个请求存储在内存中,甚至多次


相关的RFC是,幸运的是,它很短。

编辑-请先阅读:这个答案在7年后仍然经常被点击。从那时起,我从未使用过这个代码,也不知道这些天是否有更好的方法。请查看下面的评论,并知道有很多情况下这个code不起作用。使用风险自负

--

好的,根据Dave和Everts的建议,我决定手动解析原始请求数据。在搜索了大约一天之后,我没有找到任何其他方法

我从中得到了一些帮助。我没有像在引用线程中那样篡改原始数据,因为这会破坏上传的文件。所以都是正则表达式。这没有经过很好的测试,但似乎适用于我的工作案例。没有进一步的麻烦,希望有一天这可以帮助其他人:

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];
  }        
}
按引用使用(为了不在数据周围复制太多):

我使用了的示例函数并添加了一些必要的功能,例如需要数组的$u文件。希望它能帮助一些人

下面是一个示例&示例


我很惊讶没有人提到
parse_str
mb_parse_str

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

看看这个问题的python版本,了解一些想法:。基本上,您只需要拆分二进制数据,重新组合它们并重建原始文件。要解析简单的PDF表单,请尝试。我今天早些时候看到了该函数,但结果没有多大用处。您成功地使用了该函数吗?fopen()php://input')将只读取内容,而不解析它?我希望解析的值不在$u服务器变量中。使用mod_rewrite将其重定向为POSTnevermind如何,与只执行代码的R标志混淆了。但是您可以通过重建HTTP请求用PHP重定向它,但将其修改为POST请求并调用另一个script解析请求。如何将请求重写为POST?这必须在服务器上进行。您可以在端口80上打开服务器的套接字并向其提供请求。响应可以通过readfile发送回客户端。添加连接:close header以在处理完请求后关闭连接。嗯,这也是Dave Kok写的。我想我必须检查一下。问题是,我的请求内容与我预期的内容类型边界不太一样。我在最初的问题中粘贴了一点。你知道为什么它看起来是这样吗?实际的边界不是列在每个部分的标题中,而是列在顶部标题中。因此无法通过访问php://input,但正如dave提到的,它应该位于$\u服务器['HTTP\u CONTENT\u TYPE']或$\u服务器['CONTENT\u TYPE']属性中。如果post变量包含数组,此函数将不起作用。例如,“value[id]”的名称将无法正确解析。内容处置:表单数据;name=“elements[\u itemname][value]”内容配置:表单数据;name=“数组[值]”--这两种方法都不适用。没错。我不需要嵌套数组。谢谢。这对我帮助很大。只是修改为通过这些部分之间的两个换行来分隔标题/内容,而不是内容类型。我认为这涵盖了标准better@Chris我做了一个修改版本来覆盖嵌套数组,下面是代码解析遗憾的是,HTTP数据比此代码复杂得多。它可能在某些情况下有效,但在许多其他情况下无效。例如,在实际内容之前可能有多行(例如“content Length:XXX”,此代码不处理。边界的破折号数量可能因内容类型和输入中的内容而异。此外,代码也不处理存在但没有值的键。我想这对我不起作用,因为我使用的是带有
multipart/form data
CONTENT TYPE的表单中的二进制文件。FWMC问题是特别是关于MIME类型为
multipart/form data
,而不是
application/x-www-form-urlencoded
,这就是
parse_str()
$a_data = array();
parse_raw_http_request($a_data);
var_dump($a_data);
<?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']);
        }
    }
}
$result = [];
$rawPost = file_get_contents('php://input');
mb_parse_str($rawPost, $result);
var_dump($result);