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

时间:2016-04-29 08:53:33

标签: php string io binary-data

这种行为/实施是否有任何原因?例如:

$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"
  字符串(18)"数组索引"
  string(6)" Y / m / d"

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

修改

来自possible duplicatephp manual

  

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

来自php string details

  

字符串可以组成的值没有限制;   特别是,任何地方都允许使用值为0的字节(“NUL字节”)   在字符串中(但是,一些功能,在本手册中说不是   “二进制安全”,可以将字符串移交给忽略数据的库   在NUL字节之后。)

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

来自comment

  

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

不是echovar_dumpprint_r等?换句话说,输出某些东西的函数。如果我们看看我的第一个例子,它们实际上是二进制安全的。对输出实现一些二进制安全和二进制不安全函数对我没有任何意义。或者只是在C中的std lib中使用一些,并编写一些全新的函数。

3 个答案:

答案 0 :(得分:8)

简短回答"为什么"只是历史

PHP最初是作为编写C函数脚本的一种方式编写的,因此可以在生成HTML时轻松调用它们。因此, PHP字符串只是C字符串,它们是任何字节的集合。因此,在现代PHP术语中,我们会说没有什么是二元安全的,只是因为it wasn't planned to be anything else

  

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

随着时间的推移,语言越来越多,以支持更精细的字符串处理功能,许多人将字符串的特定字节考虑在内并成为二进制安全"。根据到最近写的formal PHP specification

  

关于如何将字符串中的字节转换为字符是未指定的。尽管字符串的用户可能选择将特殊语义归因于具有值\0的字节,但从PHP的角度来看,这样的空字节没有特殊含义。 PHP不假定字符串包含任何特定数据或为任何字节或序列分配特殊值。

作为一种有机增长的语言,并没有采用与C不同的方式普遍对待字符串。因此,函数和库在具体情况下是二元安全的。

答案 1 :(得分:1)

问题的拳头示例

您的第一个示例令人困惑,因为错误消息是在空字符上终止的部分,不是因为数组错误地处理了字符串。您使用错误消息发布的原始代码如下:

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

问题是错误消息打印出来的所有内容都是空字符。如果是这种情况,则可能会被某些人视为错误。

第二个例子开始探测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
  

这种行为有什么理由 / implementation

是。在您需要与之交互的任何系统上,您需要制作系统 电话,如果您想要当前时间或转换日期等,您需要说话 对于操作系统而言,这意味着在Linux的情况下调用OS API 此API位于C

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

  

这个行为/ 实施有什么理由吗?

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

  

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

向后兼容性几乎总是人们不喜欢的功能保留在语言中的原因。随着时间的推移,语言逐渐发展并被移除,但它是渐进的并且是优先的。如果你曾经问过所有的PHP开发人员他们想要更好的二进制字符串处理某些函数或JIT编译器我认为JIT可能会赢得它在PHP 7中所做的。注意,做实际工作的人最终决定他们的工作和使用JIT编译器比修复以看似奇怪的方式执行操作的库更有趣。

我不知道任何语言实现者不希望他们从一开始就做出不同的事情。任何人在a之前实现编译器 语言流行是一个很大的压力,以获得有用的东西 他们这意味着偷工减料,而不是现在存在的所有语言都有 支持他们的大公司,通常是一个小型的专业团队和他们 犯了错误,有些人很幸运能够得到报酬。称他们懒惰 有点不公平。

所有语言都有黑暗的角落疣和沸腾以及你最终会讨厌的功能。比其他人更多,PHP有一个糟糕的代表,因为它比大多数人有更多。注意,PHP 5是PHP 4的巨大飞跃。我认为PHP 7将进一步改进。

任何认为自己喜欢的语言没有问题的人都是妄想,并且几乎肯定没有将他们使用的工具的深度探测到任何深度。

答案 2 :(得分:0)

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崩溃时出现意外垃圾时,您会注意到这一点。