2011-04-06 53 views
9

我正在寻找一个包含脚本/类的解决方案,该脚本/类解析了multipart/form-data并填充了$_POST(+ raw)和$_FILES。通常PHP本身就是这样做的。但由于自动处理是不够的,我和让php://input inaccesible [1]我可能会使用这样的东西,以防止:userland multipart/form-data handler

RewriteRule .* - [E=CONTENT_TYPE:noparsing/for-you-php]
Does not work. Actual solution requires mod_headers and RequestHeader set...

的提取过程可能不会是那么复杂。但我宁愿使用经过良好测试的解决方案。最重要的是我更喜欢使用fgets进行分割的实现,并且模仿$_FILES密切和高效地处理。查找二进制有效负载的结束对我来说似乎相当棘手,特别是当您不得不取消\r\n但可能遇到仅发送\n(不允许,但可能)的客户端时。

我确定存在这样的事情。但是我很难用Google搜索它。有谁知道一个实现? (PEAR :: mimeDecode可以被破解以获得某种形式数据的工作,但它是一种内存管理器。)

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


用于装饰目的,这是一个POST请求的外观:

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

而且多/有效载荷的\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 @@@[email protected]@B`@@B;[email protected]@@-'[email protected]@@[email protected]\[email protected]@@[email protected][email protected][email protected]? ='[email protected]@@  
@@@GtIMEGYAAU,#}[email protected]@@[email protected] with [email protected]@ @IDATxZl]w| 
+0

这可能会成为一个赏金问题。 – mario 2011-04-06 04:43:49

+0

是否保证multipart中的每个MIME部分都有一个Content-Length?我不记得规范是否需要这个。我会想象会的。 – Charles 2011-04-07 01:43:40

+0

糟糕的是规范[RFC2388](http://www.faqs.org/rfcs/rfc2388.html)完全没有提及“Content-Length”。虽然我会假设大多数当前的浏览器都这样做(至少使用base64编码),但我实际上正在尝试支持更古怪的客户端。 (编辑:不,即使Opera没有。 – mario 2011-04-07 01:52:38

回答

4

这晚,我可以目前暂时不测试,但以下应该做你想做的:

//$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

无法对其进行测试。看起来可行但是。 - 我忘记了这个任务的一些问题,nameley重复了'name []','name []','name []'request vars。我的POST描述是误导性的,“边界=”从来不是身体的一部分。但至少'fgets'方法看起来可以这样做! – mario 2011-04-09 17:11:24

+0

@mario:修复了正则表达式中的拼写错误,并为$ POST请求变量添加了重复的键支持。你能否澄清一下当你说边界从来不是身体的一部分时,你的意思是什么? – 2011-04-09 17:47:19

+0

谢谢!我解决了我的问题示例。 '; boundary ='仅出现在$ _SERVER [“CONTENT_TYPE”]中。 ://输入主体确实从第一个“------ whatever”开始。但是我已经事先调整了preg_match()。我认为这很有效,因为你的代码预见了使用isset()测试$边界。我花了一些时间来进行测试。但是,我认为这看起来没问题,我可以适应其他奇怪的需求。 – mario 2011-04-09 18:32:19

0

阅读评论,如何在数据进行POST之前对数据进行编码?获取客户端以UTF8甚至URL编码发送POST数据,然后丢失的ASCII字符将被传输,无需编写自己的POST处理程序,这可能会引入自己的错误...

+0

不,那不行。这是我奇怪的要求。我需要**拦截一个普通的POST请求。我对客户没有影响力,需要支持标准表格。我可以通过使用'application/x-www-urlencoded'而不是'multipart/form-data'来解决整个问题。但是这样做打破了目的,使文件上传变得不可能。我将不得不采取解决方法和所有潜在问题。 – mario 2011-04-13 08:56:25

3

也许一个新的PHP。 INI指令enable_post_data_reading能帮上忙,但现在看来,这是PHP 5.4中添加,我仍然有版本较低,因此无法测试它:(

PHP Manual

enable_post_data_reading布尔

禁用此选项会导致$ _POST 和$ _FILES不被填充。读取postdata的唯一方法是 然后通过php://输入流包装器。这对 代理请求或以有效的内存方式处理POST数据很有用。

+0

似乎在这里工作,可能需要与嗅探器验证,看看它是否真的**生**。 – Pacerier 2015-02-05 12:09:42