PHP输入流php://input
对于php://input介绍,PHP官方手册文档有一段话对它进行了很明确地概述:
“php://input allows you to read raw POST data. It is a less memory intensive alternative to $HTTP_RAW_POST_DATA and does not need any special php.ini directives. php://input is not available with enctype=”multipart/form-data”.
翻译过来,是这样:
“php://input可以读取没有处理过的POST数据。相较于$HTTP_RAW_POST_DATA而言,它给内存带来的压力较小,并且不需要特殊的php.ini设置。php://input不能用于enctype=multipart/form-data”
我把它划分为三部分,逐步去理解:
- 读取POST数据
- 不能用于multipart/form-data类型
- php://input VS $HTTP_RAW_POST_DATA
1 读取POST数据
我写了几个脚本来帮助我们测试。
@file 192.168.0.6:/phpinput_server.php 打印出接收到的数据 @file 192.168.0.8:/phpinput_post.php 模拟以POST方法提交表单数据 @file 192.168.0.8:/phpinput_xmlrpc.php 模拟以POST方法发出xmlrpc请求. @file 192.168.0.8:/phpinput_get.php 模拟以GET方法提交表单表数
phpinput_server.php与phpinput_post.php
//@file phpinput_server.php $raw_post_data = file_get_contents('php://input', 'r'); echo "-------\$_POST------------------\n"; echo var_dump($_POST) . "\n"; echo "-------php://input-------------\n"; echo $raw_post_data . "\n";
//@file phpinput_post.php $http_entity_body = 'n=' . urldecode('perfgeeks') . '&p=' . urldecode('7788'); $http_entity_type = 'application/x-www-form-urlencoded'; $http_entity_length = strlen($http_entity_body); $host = '192.168.0.6'; $port = 80; $path = '/phpinput_server.php'; $fp = fsockopen($host, $port, $error_no, $error_desc, 30); if ($fp) { fputs($fp, "POST {$path} HTTP/1.1\r\n"); fputs($fp, "Host: {$host}\r\n"); fputs($fp, "Content-Type: {$http_entity_type}\r\n"); fputs($fp, "Content-Length: {$http_entity_length}\r\n"); fputs($fp, "Connection: close\r\n\r\n"); fputs($fp, $http_entity_body . "\r\n\r\n"); while (!feof($fp)) { $d .= fgets($fp, 4096); } fclose($fp); echo $d; }
我们可以通过使用工具ngrep抓取http请求包(因为我们需要探知的是php://input,所以我们这里只抓取http Request数据包)。我们来执行测试脚本phpinput_post.php
@php /phpinput_post.php HTTP/1.1 200 OK Date: Thu, 08 Apr 2010 03:23:36 GMT Server: Apache/2.2.3 (CentOS) X-Powered-By: PHP/5.1.6 Content-Length: 160 Connection: close Content-Type: text/html; charset=UTF-8 -------$_POST------------------ array(2) { ["n"]=> string(9) "perfgeeks" ["p"]=> string(4) "7788" } -------php://input------------- n=perfgeeks&p=7788
通过ngrep抓到的http请求包如下:
T 192.168.0.8:57846 -> 192.168.0.6:80 [AP] POST /phpinput_server.php HTTP/1.1.. Host: 192.168.0.6..Content-Type: application/x-www-form-urlencoded..Co ntent-Length: 18..Connection: close....n=perfgeeks&p=7788....
仔细观察,我们不难发现:
- $_POST数据,php://input 数据与httpd entity body数据是“一致”的。
- http请求中的Content-Type是application/x-www-form-urlencoded ,它表示http请求body中的数据是使用http的post方法提交的表单数据,并且进行了urlencode()处理。
我们再来看看脚本phpinput_xmlrpc.php的原文件内容,它模拟了一个POST方法提交的xml-rpc请求。
//@file phpinput_xmlrpc.php $http_entity_body = "\n\n jt_userinfo\n"; $http_entity_type = 'text/html'; $http_entity_length = strlen($http_entity_body); $host = '192.168.0.6'; $port = 80; $path = '/phpinput_server.php'; $fp = fsockopen($host, $port, $error_no, $error_desc, 30); if ($fp) { fputs($fp, "POST {$path} HTTP/1.1\r\n"); fputs($fp, "Host: {$host}\r\n"); fputs($fp, "Content-Type: {$http_entity_type}\r\n"); fputs($fp, "Content-Length: {$http_entity_length}\r\n"); fputs($fp, "Connection: close\r\n\r\n"); fputs($fp, $http_entity_body . "\r\n\r\n"); while (!feof($fp)) { $d .= fgets($fp, 4096); } fclose($fp); echo $d; }
同样地,让我们来执行这个测试脚本:
@php /phpinput_xmlrcp.php HTTP/1.1 200 OK Date: Thu, 08 Apr 2010 03:47:18 GMT Server: Apache/2.2.3 (CentOS) X-Powered-By: PHP/5.1.6 Content-Length: 154 Connection: close Content-Type: text/html; charset=UTF-8 -------$_POST------------------ array(0) { } -------php://input------------- <?xml version="1.0"> <methodcall> <name>jt_userinfo</name> </methodcall>
执行这个脚本的时候,我们通过ngrep抓取的http请求数据包如下:
T 192.168.0.8:45570 -> 192.168.0.6:80 [AP] POST /phpinput_server.php HTTP/1.1.. Host: 192.168.0.6..Content-Type: text/html..Content-Length: 75..Connec tion: close....<?xml version="1.0">.<methodcall>. <name>jt_userinfo< /name>.</methodcall>....
同样,我样也可以很容易地发现:
- http请求中的Content-Type是text/xml。它表示http请求中的body数据是xml数据格式。
- 服务端$_POST打印出来的是一个空数组, 而php://input数据还是跟http entity body数据一致。也就是php://input数据和$_POST数据不一致了。
- 说明仅当Content-Type为application/x-www-form-urlencoded且提交方法是POST方法时,$_POST数据与php://input数据才是”一致”(打上引号,表示它们格式不一致,内容一致)的。其它情况,它们都不一致。
这也帮助我们理解了,为什么xml_rpc服务端读取数据都是通过file_get_contents(‘php://input’, ‘r’)。而不是从$_POST中读取,正是因为xml_rpc数据规格是xml,它的Content-Type是text/xml。
当Content-Type为application/x-www-form-urlencoded时,php://input和$_POST数据是“一致”的,为其它Content-Type的时候,php://input和$_POST数据数据是不一致的。因为只有在Content-Type为application/x-www-form-urlencoded或者为multipart/form-data的时候,PHP才会将http请求数据包中的body相应部分数据填入$_POST全局变量中,其它情况PHP都忽略。而php://input除了在数据类型为multipart/form-data之外为空外,其它情况都可能不为空。