为什么php中存在二进制安全AND二进制不安全函数


Why are there binary safe AND binary unsafe functions in php?

这种行为/实现有什么原因吗
示例:

$array = array("index_of_an_array" => "value");
class Foo {
    private $index_of_an_array;
    function __construct() {}   
}
$foo = new Foo();
$array = (array)$foo;
$key = str_replace("Foo", "", array_keys($array)[0]);
echo $array[$key];

给我们一个完整的错误:

注意未定义索引:第9行

示例2:

echo date("Y'0/m/d");

输出:

2016年

但是echovar_dump(),以及其他一些函数,会输出字符串"原样",浏览器只隐藏''0个字节。

$string = "index-of'0-an-array";
$strgin2 = "Y'0/m/d";
echo $string;
echo $string2;
var_dump($string);
var_dump($string2);

输出:

数组的索引
"Y/m/d"
string(18)"数组的索引"
string(6)"Y/m/d"

请注意,$string长度为18,但显示了17个字符。

编辑

来自可能的重复和php手册:

键可以是整数,也可以是字符串。该值可以是任何类型。包含有效整数的字符串将强制转换为整数类型。例如,密钥"8"实际上将存储在8下。另一方面,"08"不会被强制转换,因为它不是有效的十进制整数。简而言之,任何字符串都可以是一个键。字符串可以包含任何二进制数据(最高2GB)。因此,键可以是任何二进制数据(因为字符串可以是任何二元数据)。

来自php字符串详细信息:

字符串所包含的值没有任何限制;特别是,任何位置都允许值为0的字节("NUL字节")在字符串中(然而,一些函数,在本手册中说不要"二进制安全",可能会将字符串交给忽略数据的库在NUL字节之后。)

但我仍然不明白为什么语言是这样设计的?这种行为/实施有什么原因吗?为什么PHP不在任何地方都以二进制安全的方式处理输入,而只在某些函数中处理?

来自评论:

原因很简单,许多像printf这样的PHP函数在幕后使用C库的实现,因为PHP开发人员很懒惰。

echovar_dumpprint_r吗?换句话说,就是输出某种东西的函数。如果我们看一下我的第一个例子,它们实际上是二进制安全的。对我来说,实现一些二进制安全和二进制不安全的输出函数毫无意义。或者像在C中的stdlib中那样使用一些,并编写一些全新的函数。

"为什么"的简短答案只是历史

PHP最初是作为编写C函数脚本的一种方式编写的,因此在生成HTML时可以很容易地调用它们。因此PHP字符串只是C字符串,它是任何字节的集合。因此,用现代PHP术语来说,我们会说没有什么是二进制安全的,只是因为它没有计划成为其他任何东西。

早期的PHP并不是为了成为一种新的编程语言,而是有机地发展起来的,Lerdorf回忆道:"我不知道如何阻止它,从来没有写编程语言的意图[…]我完全不知道如何写编程语言,我只是不断地添加下一个逻辑步骤。"

随着时间的推移,该语言逐渐支持更精细的字符串处理函数,许多函数都考虑到了字符串的特定字节,并变得"二进制安全"根据最近编写的正式PHP规范:

至于字符串中的字节如何转换为字符,则没有具体说明。尽管字符串的用户可能会选择将特殊语义赋予值为'0的字节,但从PHP的角度来看,这种空字节没有特殊意义。PHP不会假定字符串包含任何特定数据,也不会为任何字节或序列分配特殊值。

作为一种有机发展的语言,还没有采取与C不同的方式来普遍处理字符串。因此,函数和库在具体情况下是二进制安全的。

问题的第一个例子

您的第一个示例令人困惑,因为错误消息是终止于null字符的部分,而不是因为数组对字符串的处理不正确。您发布的带有错误消息的原始代码如下:

$array = array("index-of-an-array" => "value");
$string = "index-of'0-an-array";
echo $array[$string];

注意:未定义的索引:中的索引

注意,由于空字符,上面的错误消息已被截断index-of,数组按预期工作,因为如果您这样尝试,它会正常工作:

$array = array("index-of'0-an-array" => "value");
$string = "index-of'0-an-array";
echo $array[$string];

错误消息正确地标识了两个键错误它们是

"index-of'0-an-array" != "index-of-an-array"

问题是错误消息打印出了所有内容,直到空字符。如果是这样的话,那么它可能会被一些人认为是一个bug。

第二个例子是从PHP的深度开始:)

我添加了一些代码,这样我们就可以看到发生了什么

<?php
class Foo {
  public    $index_public;
  protected $index_prot;
  private   $index_priv;
  function __construct() {
    $this->index_public = 0;
    $this->index_prot   = 1;
    $this->index_priv   = 2;
  }   
}
$foo = new Foo();
$array = (array)$foo;
print_r($foo);
print_r($array);
//echo $array["'0Foo'0index_of_an_array2"];//This prints 2
//echo $foo->{"'0Foo'0index_of_an_array2"};//This fails
var_dump($array);
echo array_keys($array)[0]       . "'n";
echo $array["'0Foo'0index_priv"] . "'n";
echo $array["'0*'0index_prot"]   . "'n";

以上代码输出为

Foo Object
(
    [index_public] => 0
    [index_prot:protected] => 1
    [index_priv:Foo:private] => 2
)
Array
(
    [index_public] => 0
    [*index_prot] => 1
    [Fooindex_priv] => 2
)
array(3) {
  'index_public' =>
  int(0)
  ''0*'0index_prot' =>
  int(1)
  ''0Foo'0index_priv' =>
  int(2)
}
index_public
2
1

PHP开发人员选择使用'0字符来拆分成员变量类型。注意,受保护字段使用*来指示成员变量实际上可能属于许多类。它还用于保护私人访问,即此代码不起作用。

echo $foo->{"'0Foo'0index_priv"}; //This fails

但一旦你把它投射到一个数组中,就没有这样的保护了,也就是说有效

echo $array["'0Foo'0index_priv"]; //This prints 2

这种行为/实现有什么原因吗?

是的。在任何需要与之接口的系统上,都需要制作系统电话,如果你想要当前时间或转换日期等,你需要谈谈在Linux的情况下,这意味着调用操作系统API此API位于CCD_ 12中。

PHP最初是作为C的瘦包装器开发的,相当多的语言从这种方式开始并不断发展,PHP也不例外。

这种行为/实现有什么原因吗?

在没有任何向后兼容性问题的情况下,我会说有些选择不是最佳的,但我怀疑向后兼容性是一个很大的因素。

但我仍然不明白为什么语言是这样设计的?

向后兼容性几乎总是人们不喜欢的功能保留在语言中的原因。随着时间的推移,语言会不断发展并去除一些东西,但它是递增的,并具有优先级。如果你问过所有的PHP开发人员,他们是希望对某些函数进行更好的二进制字符串处理,还是希望使用JIT编译器,我认为JIT可能会像在PHP7中那样获胜。请注意,做实际工作的人最终决定他们在做什么,使用JIT编译器比修复那些以看似奇怪的方式做事的库更有趣。

我不知道有哪个语言实现者不希望他们从一开始就做一些不同的事情。在语言很受欢迎,要想获得适合自己的东西,压力很大这意味着偷工减料,并不是今天存在的所有语言都有支持他们的大公司,通常是一个敬业的小团队,他们犯了错误,有些人幸运地得到了报酬。称他们懒惰有点不公平。

所有的语言都有阴暗的角落,有你最终会讨厌的缺点。有些人比其他人多,PHP的名声不好,因为它比大多数人都多。注意,PHP 5是从PHP 4向前迈出的一大步。我想PHP 7会对事情有更大的改进。

任何认为自己喜欢的语言没有问题的人都是妄想症,几乎可以肯定的是,他们并没有深入了解自己使用的工具的深度。

PHP中使用C字符串内部操作的函数在PHP术语中是"不二进制安全的"。C字符串是一个以字节0结尾的字节数组。当PHP函数在内部使用C字符串时,它会逐个读取字符,当遇到字节0时,它将其视为字符串的末尾。字节0告诉C字符串函数其中是字符串的结尾,因为C字符串不包含任何关于字符串长度的信息。

"非二进制安全"意味着,如果以C字符串操作的函数以某种方式被传递了一个未以字节0终止的C字符串,则行为是不可预测的,因为函数将读取/写入字符串末尾以外的字节,从而向字符串添加垃圾和/或可能导致PHP崩溃。

例如,在C++中,我们有字符串对象。该对象还包含一个字符数组,但它也有一个长度字段,在任何长度更改时都会更新该字段。因此,它不需要字节0来告诉它结尾在哪里。这就是为什么字符串对象可以包含任意数量的0字节,尽管这通常是无效的,因为它应该只包含有效字符。

为了纠正这种情况,整个PHP核心,包括任何使用C字符串操作的模块,都需要重写,以便将"非二进制安全"函数发送到历史记录中。这需要大量的工作,所有模块的创建者都需要为他们的模块生成新的代码。这可能会在整个故事中引入新的错误和不稳定性。

字节0和"非二进制安全"函数的问题对于重写PHP和PHP模块代码来说并不重要。也许在一些更新的PHP版本中,有些东西需要从头开始编码,纠正这一点是有意义的。

在此之前,您只需要知道,任何使用二进制安全函数放入某个字符串的任意二进制数据都需要在末尾添加字节0。通常,当字符串末尾出现意外垃圾或PHP崩溃时,您会注意到这一点。