Почему в 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
НО! echo
или же var_dump()
Например, и некоторые другие функции будут выводить строку "как есть", просто \0 байтов скрыты браузерами.
$string = "index-of\0-an-array";
$strgin2 = "Y\0/m/d";
echo $string;
echo $string2;
var_dump($string);
var_dump($string2);
Выходы:
Индекс-оф-ан-массива
"Y/ м / д"
строка (18) "индекс-массива"
Строка (6) "Г / м / д"
Заметить, что $string
длина 18, но показаны 17 символов.
РЕДАКТИРОВАТЬ
Из возможного дубликата и руководства php:
Ключ может быть целым числом или строкой. Значение может быть любого типа. Строки, содержащие действительные целые числа, будут преобразованы в целочисленный тип. Например, ключ "8" будет фактически сохранен в 8. С другой стороны, "08" не будет разыгрываться, поскольку он не является действительным десятичным целым числом. Короче говоря, любая строка может быть ключом. И строка может содержать любые двоичные данные (до 2 ГБ). Следовательно, ключом могут быть любые двоичные данные (поскольку строка может быть любыми двоичными данными).
Нет ограничений на значения, из которых может состоять строка; в частности, байты со значением 0 ("NUL bytes") разрешены в любом месте строки (однако, некоторые функции, которые в этом руководстве не являются "бинарно-безопасными"), могут передавать строки библиотекам, которые игнорируют данные после NUL байт.)
Но я до сих пор не понимаю, почему язык разработан таким образом? Есть ли причины для такого поведения / реализации? Почему PHP не обрабатывает ввод как бинарный безопасный везде, но только в некоторых функциях?
Из комментария:
Причина в том, что многие функции PHP, такие как
printf
использовать реализацию библиотеки C за кулисами, потому что разработчики PHP были ленивы.
Такие, как echo
, var_dump
, print_r
? Другими словами, функции, которые что-то выводят. На самом деле они бинарно безопасны, если мы посмотрим на мой первый пример. Для меня нет смысла реализовывать некоторые бинарно-безопасные и бинарно-небезопасные функции для вывода. Или просто используйте некоторые из них, как они есть в std lib в C, и напишите некоторые совершенно новые функции.
3 ответа
Короткий ответ на вопрос "почему" - просто история.
Изначально PHP был написан как способ написания скриптов на C-функциях, чтобы их можно было легко вызывать при генерации HTML. Поэтому строки PHP были просто строками C, которые представляют собой набор любых байтов. Таким образом, в современных терминах PHP мы бы сказали, что ничто не является бинарно-безопасным просто потому, что ничего другого не планировалось.
Ранний PHP не был задуман как новый язык программирования, и он рос органично, и Лердорф ретроспективно заметил: "Я не знаю, как это остановить, никогда не было никакого намерения написать язык программирования […] У меня нет абсолютно никакого Идея, как написать язык программирования, я просто продолжал добавлять следующий логический шаг на пути ".
Со временем язык стал поддерживать более сложные функции обработки строк, многие из которых учитывают специфические байты строки и становятся "бинарно-безопасными". Согласно недавно написанной формальной спецификации PHP:
Что касается того, как байты в строке переводятся в символы, не определено. Хотя пользователь строки может решить приписать особую семантику байтам, имеющим значение
\0
с точки зрения PHP, такие нулевые байты не имеют особого значения. PHP не предполагает, что строки содержат какие-либо конкретные данные или назначают специальные значения для любых байтов или последовательностей.
Как язык, который вырос органически, не было никакого движения к универсальной обработке строк способом, отличным от C. Поэтому функции и библиотеки являются бинарно-безопасными в каждом конкретном случае.
Пример кулака из вопроса
Ваш первый пример сбивает с толку, потому что сообщение об ошибке - это та часть, которая оканчивается нулевым символом, а не потому, что строка неправильно обрабатывается массивом. Ниже приведен исходный код, который вы разместили с сообщением об ошибке:
$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
Есть ли причина для такого поведения
/ реализации?
Да. В любой системе, с которой вам нужно взаимодействовать, вам нужно совершать системные вызовы, если вы хотите указать текущее время или преобразовать дату и т. Д., Вам нужно поговорить с операционной системой, а это означает вызов API OS, в случае Linux это API находится в C
,
PHP изначально разрабатывался как тонкая оболочка C
многие языки начинаются таким образом и развиваются, PHP не является исключением.
Есть ли причина для такого
поведения /реализации?
В отсутствие каких-либо проблем с обратной совместимостью я бы сказал, что некоторые варианты выбора не являются оптимальными, но я подозреваю, что обратная совместимость является важным фактором.
Но я до сих пор не понимаю, почему язык разработан таким образом?
Обратная совместимость почти всегда является причиной того, что функции, которые не нравятся людям, остаются в языке. Со временем языки развиваются и удаляются, но это постепенно и приоритетно. Если бы вы спросили всех разработчиков PHP, хотят ли они лучше обрабатывать двоичные строки для некоторых функций или JIT-компилятор, я думаю, что JIT может выиграть, как в PHP 7. Обратите внимание, что люди, выполняющие реальную работу, в конечном итоге решают, над чем они работают, и работать над JIT-компилятором гораздо веселее, чем исправлять библиотеки, которые делают что-то странное.
Я не знаю ни одного языкового разработчика, который не хотел бы, чтобы они делали некоторые вещи иначе, чем с самого начала. Любой, кто внедряет компилятор до того, как язык станет популярным, находится под большим давлением, чтобы получить что-то, что работает для него, и это означает, что нужно срезать углы, не все существующие на сегодняшний день языки поддерживали их, чаще всего это была небольшая специальная команда и они допустили ошибки, некоторым посчастливилось получить за это деньги. Называть их ленивыми немного несправедливо.
У всех языков есть темные углы бородавок и кипит и особенности, которые вы в конечном итоге будете ненавидеть У некоторых больше, чем у других, и у PHP плохая репутация, потому что у него было / было намного больше, чем у большинства. Обратите внимание, что PHP 5 - это огромный шаг вперед по сравнению с PHP 4. Я предполагаю, что PHP 7 улучшит ситуацию еще больше.
Любой, кто думает, что его любимый язык свободен от проблем, бредит и почти наверняка не изучил глубины инструмента, который они используют, до какой-то большой глубины.
Функции в PHP, которые внутренне работают со строками C, "не бинарно безопасны" в терминологии PHP. Строка C - это массив байтов, заканчивающийся байтом 0. Когда функция PHP внутренне использует строки C, она читает один за другим символ, а когда она встречает байт 0, она считает ее концом строки. Байт 0 сообщает строковым функциям C, где находится конец строки, поскольку строка C не содержит никакой информации о длине строки.
"Не бинарный" означает, что если функции, которая работает со строкой C, каким-то образом передается строка C, не завершенная байтом 0, поведение непредсказуемо, поскольку функция будет читать / записывать байты за пределами конца строки, добавляя мусор в строку и / или потенциально сбой PHP.
Например, в C++ у нас есть строковый объект. Этот объект также содержит массив символов, но он также имеет поле длины, которое он обновляет при любом изменении длины. Таким образом, он не требует байта 0, чтобы сказать ему, где конец. Вот почему строковый объект может содержать любое количество 0 байтов, хотя обычно это недопустимо, поскольку он должен содержать только допустимые символы.
Чтобы это исправить, нужно переписать все ядро PHP, включая все модули, работающие со строками Си, чтобы отправлять "недвоичные безопасные" функции в историю. Объем работы, необходимый для этого, огромен, и создатели всех модулей должны создать новый код для своих модулей. Это может внести новые ошибки и нестабильности во всю историю.
Проблема с байтом 0 и "небинарно-безопасными" функциями не так уж критична, чтобы оправдать переписывание кода модулей PHP и PHP. Возможно, в более новой версии PHP, где некоторые вещи нужно кодировать с нуля, имеет смысл исправить это.
До этого вам просто нужно знать, что любые произвольные двоичные данные, помещенные в некоторую строку с помощью бинарно-безопасных функций, должны иметь добавленный байт 0 в конце. Обычно вы замечаете это, когда в конце строки возникает неожиданный мусор или происходит сбой PHP.