PHP���一些$_POST值缺失,但在php://input中存在

55

我有一个非常大的HTML表单(包含行的表格,其中包含多个输入),需要通过POST请求将其提交到PHP脚本。

问题是,一些值没有传递过来,在PHP的$_POST超全局变量中不存在。

我使用Firebug浏览器扩展程序检查发现这些值确实被发送到服务器。

$_POST得到填充,但一些值就是缺失了。

我使用以下命令检查原始请求:

$raw_post = file_get_contents('php://input');

返回的字符串具有值,但它们只是未解析为 $_POST 数组。我注意到的奇怪之处是,似乎 php://input 值在一定长度后被截断,剩余的字符串没有传递到 $_POST。

I thought about post_max_size and memory_limit and set them to large values:

memory_limit = 256M
post_max_size = 150M
but according to php documentation $_POST should not contain any values if request made is bigger than post_max_size.

由于表单和请求过大,我无法在此处发布它们,但是我可以发布我用来调试问题的PHP脚本:


var_dump($file = file_get_contents('php://input'));
var_dump($_POST);
//... then i parsed the $file

服务器版本:Apache/2.2.9(Debian)
PHP版本:PHP 5.3.2-0.dotdeb.2

有人能解释一下这种奇怪的PHP行为的原因吗?我应该怎么做(更改php设置、代码?)才能在处理表单时使用$ _POST数组?

编辑:明确一点:不仅值丢失了,$_POST也没有包含这些键。

例如原始邮寄片段:

t_dodparam%5B198%5D=&t_dodparam2%5B198%5D=&t_kolejnosc%5B198%5D=199&n_indeks=201&n_wartosc=testtesttest

在提交的数据中,键't_dodparam'存在且具有关键字198。其余参数缺失(例如t_dodparam2在post中存在,但没有关键字198,而$_POST中也不存在n_wartosc关键字)。


1
你能给我们举一个缺失参数值的例子吗? - Matt Gibson
这是原始帖子的片段:t_dodparam%5B198%5D =&t_dodparam2%5B198%5D =&t_kolejnosc%5B198%5D = 199&n_indeks = 201&n_wartosc = testtesttest。 t_dodparam [198]在帖子值中,但其余部分缺失。 - jankes
1
“&”符号不是表示参数的结束和新参数的开始吗? - Jean Hominal
你的原始数据中没有名为“198”的参数。 - zerkms
我的原始数据中有参数t_dodparam2[198]=''或者t_kolejnosc[198],但它们没有包含在$_POST中。正如Jean Hominal所指出的那样,&符号表示参数的结束,但这只是表明已经提交了空值(例如空文本字段)。 - jankes
14个回答

33

PHP会修改包含空格、点、开方括号等字符的字段以与弃用的register_globals兼容。

您可以在这里的评论中找到许多解决方法:PHP:来自外部来源的变量

例如(POSTer的评论):

<?php
//Function to fix up PHP's messing up POST input containing dots, etc.
function getRealPOST() {
    $pairs = explode("&", file_get_contents("php://input"));
    $vars = array();
    foreach ($pairs as $pair) {
        $nv = explode("=", $pair);
        $name = urldecode($nv[0]);
        $value = urldecode($nv[1]);
        $vars[$name] = $value;
    }
    return $vars;
}
?>

谢谢,这正是我所做的,它解决了问题(作为一种解决方法),但我仍然不知道原因。我认为方括号不应该是问题,因为许多HTML表单使用数组语法(例如<input name="user[1]" type="text" />)。在这种形式下它运行良好($_POST['t_dodparam'][198]存在且等于""),只是输入数据在此之后被截断。 - jankes
你说得对,这真是一个疯狂的行为! 你已经检查了max_input_time和max_execution_time吗? - felixsigl
1
这是一个 max_input_time 的问题。非常感谢! - jankes
另外,为了以后的参考,我建议查看一下suhosin补丁设置(如果已安装)。它有一些默认设置,来自像Ubuntu这样的系统上的软件包,限制超级全局变量的大小。 - jankes
1
我知道这是一个旧的帖子 - 只需要指出方括号不是问题,但是一个开放的方括号可能会成为一个问题。 - Stefan
正在修复一些遗留代码,问题出在 HTML 输入名称中的 '',例如 name="categories['xxx'][]"。应该改为 name="categories[current_categories][]"。 - Kipras Bielinskas

33

我通过在 PHP 配置文件中添加值来解决了这个问题,具体是将 max_input_vars 的值设定为更高的数值。根据 这里 的说明,该选项在 PHP 5.3.9 中引入,但在升级一些软件包后,我在 PHP 5.3.2 中也遇到了该问题。

max_input_vars 的默认值是 1000,对于我的表单来说太小了。


请注意,此变量不能在运行时设置。 :( - mrbellek
2
如果您在POST中只是“缺少”变量,并且您有一个非常大的表单,那么这可能是答案... - ftrotter
1
您可以在.htaccess文件中添加以下行来设置:php_value max_input_vars 10000 - Gurnzbot
“php_value max_input_vars 100000” 是否存在漏洞? - Hebe

12

有许多不同的原因可能会导致这种情况发生。最好检查一下错误日志。许多导致此症状的问题都会在错误日志中显示消息,但不会在您的PHP应用程序中显示错误。

可能的原因之一:

Suhosin

Suhosin是一个为PHP设计的扩展,旨在保护服务器和用户免受PHP应用程序和PHP核心中已知和未知的缺陷。其中之一就是限制 $_POST、$_GET、$_REQUEST 和 $_COOKIE 的大小。如果你的问题是Suhosin,你将在日志中获得类似于以下这样的错误:

ALERT - configured POST variable limit exceeded - dropped variable 'foo'

解决方法很简单,只需在php.ini中增加允许的最大变量数。如果您没有 suhosin 部分,请创建一个。像这样:

[suhosin]
suhosin.request.max_vars = 1000 # Default is 200
suhosin.post.max_vars = 1000 # Default is 200

还有其他Suhosin设置可能会导致此问题,但这些是最可能的候选项。

无效的表单字段名称

在旧版本的PHP中,有一个名为register_globals(现在已弃用)的设置,它自动将GET和POST变量转换为PHP变量。 因此,如果您的表单有字段“foo”和“bar”,当该表单被提交时,变量$foo和$bar将自动创建。 然而,有几个字符在PHP变量名中是无效的(例如空格,点,左方括号等)。 根据使用的字符,变量的值可能会被删除无效字符、缺失或未设置。 尽管register_globals不再使用,但PHP仍会在构建$_POST,$_GET,$_REQUEST和$_COOKIE时删除这些字符。 如果您无法修复表单字段的值,您可以尝试类似以下方法:

<?php
/**
 * Converts raw POST data into an array.
 * 
 * Does not work for hierarchical POST data.
 *
 * @return array
 */
function real_post() {
  static $post;
  if (!isset($post)) {
    $pairs = explode("&", file_get_contents("php://input"));
    $post = array();
    foreach ($pairs as $pair) {
      $x = explode("=", $pair);
      $post[rawurldecode($x[0])] = rawurldecode($x[1]);
    }
  }
  return $post;
}
?>

实际上,名称中的一个点导致了我的 $ _POST 变量出现了问题。谢谢 @Dalin - Giorgio Barchiesi

10

使用"parse_str"函数将查询字符串转换为php数据结构如何?这个函数是http_build_query的反函数。

    $b = array();
    parse_str(file_get_contents("php://input"), $b);

1
这对我帮助很大。 - Vojtěch

2
使用此函数获取真实数据。
function get_real_post() {

    function set_nested_value(&$arr, &$keys, &$value) {
        $key = array_shift($keys);
        if (count($keys)) {
            // Got deeper to go
            if (!array_key_exists($key, $arr)) {
                // Make sure we can get deeper if we've not hit this key before
                $arr[$key] = array();
            } elseif (!is_array($arr[$key])) {
                // This should never be relevant for well formed input data
                throw new Exception("Setting a value and an array with the same key: $key");
            }
            set_nested_value($arr[$key], $keys, $value);
        } elseif (empty($key)) {
            // Setting an Array
            $arr[] = $value;
        } else {
            // Setting an Object
            $arr[$key] = $value;
        }
    }

    $input = array();
    $parts = array();

    $rawdata=file_get_contents("php://input");
    $rawdata=urldecode($rawdata);
    $pairs = explode("&", $rawdata);
    foreach ($pairs as $pair) {
        $key_value = explode("=", $pair, 2);
        preg_match_all("/([a-zA-Z0-9_]*)(?:\[([^\[\]]*(?:(?R)[^\[\]]*)*)\])?/", urldecode($key_value[0]), $parts);
        $keys = array($parts[1][0]);
        if (isset($parts[2][0])&&$parts[2][0]!="") {
            array_pop($parts[2]); // Remove the blank one on the end
            $keys = array_merge($keys, $parts[2]);
        }
        $value = urldecode($key_value[1]);
        if ($value == "true") {
            $value = true;
        } else if ($value == "false") {
            $value = false;
        } else if (is_numeric($value)) {
            if (strpos($value, ".") !== false) {
                $num = floatval($value);
            } else {
                $num = intval($value);
            }
            if (strval($num) === $value) {
                $value = $num;
            }
        }
        set_nested_value($input, $keys, $value);
    }
    return $input;
}

2

我通过搜索找到了这个答案,但我认为我应该提供另一种解决方案。在我的情况下,我的帖子并不是太大,但我提交的值在$_POST数组中没有显示出来。后来发现,我在表单中意外地有另一个同名字段。所以:

<form>
   <input type="text" name="field1">
   <input type="text" name="field2">
   <input type="text" name="field3">
   <input type="text" name="field1">
   <input type="submit">
</form>

$_POST变量用来存储表单数据时,与第一个字段同名的最后一个字段的值将覆盖第一个字段的值。如果第一个字段是必填项并且您填写了一个值,但最后一个字段不是必填项并且被提交为空,则会出现类似于此问题的症状,因为$_POST['field1']的值将显示您表单中的最后一个空元素的值。

简而言之:

确保检查表单中是否存在重复的字段名称!


我遇到了一个问题,但是与数组有关。几个字段填充了一个数组,然后将其值设置为字符串,之后又添加了更多的数组值。结果导致我得到了一个比预期短的数组。 - Wouter
我遇到了一个问题,但是是关于数组的。有几个字段填充了一个数组,然后将其值设置为字符串,然后又添加了更多的数组值。结果导致我得到了一个比预期更短的数组。 - undefined

2

我在发布Jquery的.ajax()函数。我试图从$_POST中检索我的数据,但是它不完整。然后我找到了这篇文章,让我走上了正确的轨道。然而,上面描述的getRealPOST()方法对我来说不起作用 - 它无法很好地处理多维和嵌套数组。相反,我使用了PHP的parse_str()方法,这样就解决了问题,并且更加简洁:

$rawdata = file_get_contents('php://input');
$urldecoded = urldecode($rawdata);
parse_str($urldecoded, $parsed);

$data = $parsed['data'];

在我的JS中,我发布一个对象,其中有效载荷位于data属性中。你的可能不同。

我还进入了我的php.ini文件,并将max_memorymax_input_vars增加了,但似乎没有解决我的问题。我也花了很长时间追踪一个错综复杂的线索,因为我使用错误日志打印原始数据,而我忘记了error_log除非记得增加它,否则只会打印有限数量的字符。

总之,希望这可以帮助那些遇到这个问题的人们。


1

在寻找解决Javascript对象键包含方括号的名称而导致PHP混淆并像从未听说过嵌套一样行事的错误时,我遇到了这个(诚然有些陈旧)帖子。@Dalin给出了一个很好的基本响应,但它不会创建嵌套数组,也不会将值类型转换为布尔值/数字 - 因此我的版本(get_real_post()也在GitHub上)。

尽管名称如此,但对于_GET(即任何php://input抓取的内容),这应该完全相同。

/**
 * Gets the _POST data with correct handling of nested brackets:
 * "path[to][data[nested]]=value"
 * "path"
 *    -> "to"
 *       -> "data[nested]" = value
 * @return array
 */
function get_real_post() {

    function set_nested_value(&$arr, &$keys, &$value) {
        $key = array_shift($keys);
        if (count($keys)) {
            // Got deeper to go
            if (!array_key_exists($key, $arr)) {
                // Make sure we can get deeper if we've not hit this key before
                $arr[$key] = array();
            } elseif (!is_array($arr[$key])) {
                // This should never be relevant for well formed input data
                throw new Exception("Setting a value and an array with the same key: $key");
            }
            set_nested_value($arr[$key], $keys, $value);
        } elseif (empty($key)) {
            // Setting an Array
            $arr[] = $value;
        } else {
            // Setting an Object
            $arr[$key] = $value;
        }
    }

    $input = array();
    $parts = array();
    $pairs = explode("&", file_get_contents("php://input"));
    foreach ($pairs as $pair) {
        $key_value = explode("=", $pair, 2);
        preg_match_all("/([a-zA-Z0-9]*)(?:\[([^\[\]]*(?:(?R)[^\[\]]*)*)\])?/", urldecode($key_value[0]), $parts);
        $keys = array($parts[1][0]);
        if (!empty($parts[2][0])) {
            array_pop($parts[2]); // Remove the blank one on the end
            $keys = array_merge($keys, $parts[2]);
        }
        $value = urldecode($key_value[1]);
        if ($value == "true") {
            $value = true;
        } else if ($value == "false") {
            $value = false;
        } else if (is_numeric($value)) {
            if (strpos($value, ".") !== false) {
                $num = floatval($value);
            } else {
                $num = intval($value);
            }
            if (strval($num) === $value) {
                $value = $num;
            }
        }
        set_nested_value($input, $keys, $value);
    }
    return $input;
}

1

我曾遇到同样的问题,后来发现我使用了AJAX根据表单中的其他输入动态修改输入字段。ajax函数重新创建了输入框,但未包含输入名称。


1

供将来参考: 在Ubuntu系统上,我曾经长时间遇到这个问题,采用了类似上面描述的解决方法来解决它。 现在我在我的php.ini文件中找到了这个问题,并最终在以下行中找到了它: max_input_nesting_level = 0 我注释了上面的行,重新启动了apache,问题得到了解决。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接