userland multipart/form-data 处理程序

userland multipart/form-data handler

提问人:mario 提问时间:4/6/2011 最后编辑:Communitymario 更新时间:10/8/2012 访问量:9233

问:

我正在寻找一个插入式包含脚本/类,该脚本/类可以剖析和填充(+原始)并从中解剖和填充。通常PHP自己做。但是由于自动处理对我来说是不够的,并且使 php://input 无法访问[1],我可能会使用这样的东西来防止这种情况:multipart/form-data$_POST$_FILES

RewriteRule .* - [E=CONTENT_TYPE:noparsing/for-you-php]
不起作用。实际解决方案需要设置mod_headers和 RequestHeader...

提取过程可能没有那么复杂。但我宁愿使用经过充分测试的解决方案。最重要的是,我更喜欢用于拆分的实现,并密切有效地模拟处理。找到二进制有效负载的末尾对我来说似乎相当棘手,特别是当您必须剥离但可能会遇到仅发送(不允许,但可能)的客户端时。fgets$_FILES\r\n\n

我敢肯定这样的事情存在。但我很难在谷歌上搜索它。有谁知道实现?(PEAR::mimeDecode 可以被黑客入侵以处理表单数据,但这是一个内存消耗。

简而言之,用例:需要保留原始字段名称(包括空格和特殊字符)用于日志记录,但无法始终避免文件上传。


出于装饰目的,POST 请求如下所示:

POST / HTTP/1.1
Host: localhost:8000
Content-Length: 17717
Content-Type: multipart/form-data; boundary=----------3wCuBwquE9P7A4OEylndVx

在一个序列之后,multipart/payload 如下所示:\r\n\r\n

------------3wCuBwquE9P7A4OEylndVx
Content-Disposition: form-data; name="_charset_"

windows-1252
------------3wCuBwquE9P7A4OEylndVx
Content-Disposition: form-data; name=" text field \\ 1 \";inject=1"

text1 te twj sakfkl
------------3wCuBwquE9P7A4OEylndVx
Content-Disposition: form-data; name="file"; filename="dial.png"
Content-Type: image/png

IPNG Z @@@MIHDR@@B`@@B;HF@@@-'.e@@@[email protected]\i@@@FbKGD@?@?@? ='S@@@     
@@@GtIMEGYAAU,#}BRU@@@YtEXtComment@Created with GIMPWANW@@ @IDATxZl]w|
php multipartform-data

评论

0赞 mario 4/6/2011
这可能会成为一个赏金问题。
0赞 Charles 4/7/2011
是否可以保证 multipart 中的每个 MIME 部分都有一个 Content-Length?我不记得规范是否需要这样做。我想会的。
0赞 mario 4/7/2011
令人遗憾的是,RFC2388根本没有提到规格。虽然我假设大多数当前的浏览器都这样做(并且至少使用 base64 编码),但我实际上是在尝试支持更古怪的客户端。(编辑:不,甚至Opera也没有这样做。Content-Length
0赞 David d C e Freitas 4/13/2011
“$argc”变量不包含原始帖子数据吗?还是空字符串 $_POST['']?为什么说自动处理不够?php://stdio 如何为您服务?
0赞 mario 4/13/2011
@David:POST 请求既不存在,也不存在。并且 和 是空的。PHP 吸收了完整的 POST 正文。这就是为什么它无法访问的原因。我的问题是前导空格被剥离,许多 ASCII 字符转换为下划线。argcargvphp://stdinphp://inputmain/rfc1867.c_

答:

4赞 Alix Axel 4/9/2011 #1

已经晚了,我目前无法测试,但以下内容应该可以做你想要的:

//$boundary = null;

if (is_resource($input = fopen('php://input', 'rb')) === true)
{

    while ((feof($input) !== true) && (($line = fgets($input)) !== false))
    {
        if (isset($boundary) === true)
        {
            $content = null;

            while ((feof($input) !== true) && (($line = fgets($input)) !== false))
            {
                $line = trim($line);

                if (strlen($line) > 0)
                {
                    $content .= $line . ' ';
                }

                else if (empty($line) === true)
                {
                    if (stripos($content, 'name=') !== false)
                    {
                        $name = trim(stripcslashes(preg_replace('~.*name="?(.+)"?.*~i', '$1', $content)));

                        if (stripos($content, 'Content-Type:') !== false)
                        {
                            $tmpname = tempnam(sys_get_temp_dir(), '');

                            if (is_resource($temp = fopen($tmpname, 'wb')) === true)
                            {
                                while ((feof($input) !== true) && (($line = fgets($input)) !== false) && (strpos($line, $boundary) !== 0))
                                {
                                    fwrite($temp, preg_replace('~(?:\r\n|\n)$~', '', $line));
                                }

                                fclose($temp);
                            }

                            $FILES[$name] = array
                            (
                                'name' => trim(stripcslashes(preg_replace('~.*filename="?(.+)"?.*~i', '$1', $content))),
                                'type' => trim(preg_replace('~.*Content-Type: ([^\s]*).*~i', '$1', $content)),
                                'size' => sprintf('%u', filesize($tmpname)),
                                'tmp_name' => $tmpname,
                                'error' => UPLOAD_ERR_OK,
                            );
                        }

                        else
                        {
                            $result = null;

                            while ((feof($input) !== true) && (($line = fgets($input)) !== false) && (strpos($line, $boundary) !== 0))
                            {
                                $result .= preg_replace('~(?:\r\n|\n)$~', '', $line);
                            }

                            if (array_key_exists($name, $POST) === true)
                            {
                                if (is_array($POST[$name]) === true)
                                {
                                    $POST[$name][] = $result;
                                }

                                else
                                {
                                    $POST[$name] = array($POST[$name], $result);
                                }
                            }

                            else
                            {
                                $POST[$name] = $result;
                            }
                        }
                    }

                    if (strpos($line, $boundary) === 0)
                    {
                        //break;
                    }
                }
            }
        }

        else if ((is_null($boundary) === true) && (strpos($line, 'boundary=') !== false))
        {
            $boundary = "--" . trim(preg_replace('~.*boundary="?(.+)"?.*~i', '$1', $line));
        }
    }

    fclose($input);
}

echo '<pre>';
print_r($POST);
echo '</pre>';

echo '<hr />';

echo '<pre>';
print_r($FILES);
echo '</pre>';

评论

0赞 mario 4/10/2011
还不能测试它。不过看起来可行。-- 我忘记了任务的几个问题,nameley duplicate , , request vars。我的 POST 描述具有误导性,它从来都不是身体的一部分。但至少这种方法看起来是可行的!name[]name[]name[]boundary=fgets
0赞 Alix Axel 4/10/2011
@mario:修复了正则表达式中的拼写错误,并添加了对$POST请求变量的重复键支持。你能澄清一下,当你说边界从来都不是身体的一部分时,你是什么意思吗?
0赞 mario 4/10/2011
谢谢!我修复了我的问题示例。仅出现在 中。://input body 实际上从第一个开始。但是我已经事先用一个小的 preg_match() 对其进行了调整;我认为这很有效,因为您的代码预见测试$boundary使用 isset()。我正在花一点时间进行一次粗略的测试。但同样,我认为这看起来还不错,我可以根据我的其他奇怪需求进行调整。;boundary=$_SERVER["CONTENT_TYPE"]------whatever
1赞 Alix Axel 4/10/2011
@mario:哦,我明白了。我忘了在之前的编辑中删除一个,现在修复了它。至于重复的键,我每次都会假设,而不仅仅是.实际上,我发现以这种方式穿越 _FILES 美元的超全球更容易(见 github.com/alixaxel/phunction/blob/......while$FILES$FILES[$name][] = array()$FILES[$name] = array()
1赞 mario 4/13/2011
还有一些错误。但我只接受其中之一的责备!:} $boundary实际上在实际正文中使用了另外两个破折号。剥离应该只发生在每个可变部分/文件的末尾。还必须禁用跳过部分的 as,不确定它的目的是什么。但无论如何,fgets 方法似乎毕竟是有效和可行的。--\r\nbreak;
0赞 boisvert 4/13/2011 #2

阅读评论,如何在 POST 之前对数据进行编码?让客户端以 UTF8 甚至 URL 编码发送 POST 数据,那么丢失的 ASCII 字符将在不编写您自己的 POST 处理程序的情况下传输,这很可能会引入自己的错误......

评论

1赞 mario 4/13/2011
不,不能那样做。这是我在这里的奇怪要求。我需要拦截一个普通的 POST 请求。我对客户没有影响,需要支持标准表格。我可以通过使用 POST 而不是 .但这违背了目的,使文件上传变得不可能。我将不得不使用解决方法及其所有潜在问题。application/x-www-urlencodedmultipart/form-data
3赞 Snifff 10/8/2012 #3

也许一个新的 php.ini 指令enable_post_data_reading会有所帮助,但似乎它是在 PHP 5.4 中添加的,我仍然有早期版本,所以无法测试它:(

来自PHP手册

enable_post_data_reading布尔值

禁用此选项会导致 $_POST 和 $_FILES 不填充。读取 postdata 的唯一方法是 然后通过 php://input 流包装器。这可能很有用 代理请求或在内存中高效处理 POST 数据 时尚。

评论

0赞 Pacerier 2/5/2015
似乎在这里工作,可能需要用嗅探器验证它是否真的是原始的。