ПХП: красавица и чудовище

Одна из превосходнейших вещей в ПХП — обработчики протоколов. Они позволяют работать с разнообразными источниками данных как с файлами. Встроенные протоколы умеют понимать ftp, http, data URL, ssh2, rar, zlib и ещё несколько менее известных штук. Т. е. традиционный путь до файла в ПХП на самом деле является УРЛом, по виду которого интерпретатор решает какой обработчик за него ответственен. Если протокол (в терминах УРЛ) не указан, считается, что идёт обращение к локальному файлу. Язык так же позволяет создавать свои обработчики.

ПХП единственный из известных мне языков, где я могу с лёгкостью сделать такие вещи:

copy('ftp://user:pass@example.org/pub/somefile.txt', '/tmp/somefile.txt');
$var = file_get_contents('https://example.org/index.html');
# и при этом
copy('/tmp/somefile', '/tmp/anotherfile.txt');
$var = file_get_contents('/etc/passwd');

Немного более сложный синтаксис позволяет сделать и более сложные вещи:

$post = http_build_query([
    'login' => $login,
    'pass' => $password,
]);

$options = [
    'http' => [
        'method' => 'POST',
        'header' => 'Content-type: application/x-www-form-urlencoded\r\n'.
                           'Content-length: ' . strlen($post),
        'content' => $post,
        'proxy'=>"tcp://192.168.1.1:3128"
    ]
];

$result = file_get_contents('http://example.org/action.php', false, stream_context_create($options));

От полного изящества далеко, но по-прежнему проще, чем во многих других языках (особенно, в Пайтоне); можно даже с прокси работать!

Что сделано из рук вон плохо — это получение заголовков ответа для протоколов HTTP и HTTPS: ответ просто помещается в локальную переменную http_response_header, это печальное и достойное сожаления решение. Вообще-то в языке есть и альтернативы, но эта переменная всё равно будет создана.

Более неявное поведение трудно и выдумать. Оно настолько неявное, что многие о нём даже не подозревают. Ещё отвратительно, что эта переменная содержит заголовки не в виде ключ/значение, а просто набор строк:

file_get_contents('//bolknote.ru');
var_dump($http_response_header);

вернёт:

array(14) {
  [0]=>
  string(15) "HTTP/1.1 200 OK"
  [1]=>
  string(13) "Server: nginx"
  [2]=>
  string(35) "Date: Wed, 11 Jul 2012 19:50:10 GMT"
… и так далее

Как такой непродуманный функционал может являться продолжением такой прекрасной штуки мне непонятно.

Поделиться
Отправить
25 комментариев
Александр Бабаев (bealex.moikrug.ru) 2012

В Objective-C в Foundation можно делать похожие вещи. Что там с новыми протоколами — не знаю, но все базовые классы (строка, списки, ассоциативные массивы, просто массив данных) — можно грузить прямо из интернета, например. А можно из локального файла. Правда, это синхронный доступ, который мало где тут можно применить (так как UI и все дела). Но удобно, да.

Морозов (morozov.livejournal.com) 2012

Насчёт того, что переменная содержит заголовки не в виде ключ/значение, а просто набор строк, дело, видимо, в том, что некоторые заголовки, такие как «Set-Cookie», в ответе могут повторяться, поэтому, если их проиндексировать по имени, все значения кроме последнего будут потеряны.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для morozov.livejournal.com:

Решительно не вижу проблемы. Для этого придумали вложенные массивы.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для bealex.moikrug.ru:

Что там с новыми протоколами — не знаю, но все базовые классы (строка, списки, ассоциативные массивы, просто массив данных) — можно грузить прямо из интернета, например. А можно из локального файла.

А с методами POST,  PUT и другими как?

Андрей 2012

Мда уж, великий и могучий похапе )

На руби:

require ’open-uri’
IO.copy_stream open(’ http://%27ftp://ftp.debian.org/debian/README.html%27 ’), ’/tmp/somefile.txt’

aktuba 2012

Андрей, тогда на php еще красивее:

copy(’ http://%27ftp://ftp.debian.org/debian/README.html%27 ’, ’/tmp/somefile.txt’);

А как в ruby data: или ssh: обработать?

Евгений Степанищев (bolknote.ru) 2012

Комментарий для Андрей:

Андрей, вы считаете, что на Руби это лучше сделано?

masterspammer.livejournal.com 2012

А потом приходи червь, находит на странице что-то типа content=dir/file.inc и делает запрос с content= http://wormed.host/worm_body.php и всем сразу становится нехорошо.

Конечно язык вроде бы и не виноват, но то, что открытие файла неявно (хотя и не настолько, насколько http_response_header) является и скачиванием файла по сети, может увеличить (и увеличивает) диаметр дыры с именем файла из параметра.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для masterspammer.livejournal.com:

Ну, то есть чтение в таком случае файла с компьютера — это ерунда, а чтение из сети — дырища? И то и другое — дыра. Причём тут ПХП?

hshhhhh (hshhhhh.name) 2012

Комментарий для Евгения Степанищева:

Немного более сложный синтаксис позволяет сделать и более сложные вещи

Хм, а я через curl это делал потому что традиционно и работает везде примерно одинаково.

masterspammer.livejournal.com 2012

Комментарий для Евгения Степанищева:

Не ерунда, а просто дыра; из сети — действительно дырища: при возможности локального чтение (в данном случае — include) чтобы выполнить свой код, его как-то нужно туда положить, что сама по себе та ещё задача. При возможности указать http:// задача подсунуть свой код на выполнение существенно упрощается и даже автоматизируется (потому про червя и писал, что видел такое в живой природе).

А php тут ровно при том, что в нём (насколько я помню) открыть файл откуда угодно гораздо проще, чем открыть именно локальный файл; причём про то, что file_get_contents умеет ходить по сети, из его имени не следует. Программист, не знакомый с такой особенностью, может и не заподозрить что такая есть. То есть url_get_content было бы лучше.

hshhhhh (hshhhhh.name) 2012

То есть url_get_content было бы лучше.

rar_get_contents
ftp_get_contents
gzip_get_contents
https_get_contents

masterspammer.livejournal.com 2012

Комментарий для hshhhhh.name:

не, http: rar: ftp: gzip: https:// — входят в url как протокол

Андрей 2012

Комментарий для Евгения Степанищева:

bolk, я считаю, что наличие такой «красоты» в стдлибе — это маловолнующая штука. Ну т. е. я могу порадоваться ее наличию раз в пять лет при написании какого-нибудь тупого одноразового скриптика, но если ее не будет, то напишу сам, это switch простейший. При условии, что в зоне досягаемости инфраструктуры языка есть нормальные либы для работы с http / ftp / ssh etc. А вот стдлибовские библиотеки для этого дела, что в руби, что в питоне (про пхп, простите, молчу) дизайнят и реализуют какие-то извращенцы :)

Что касается «лучше сделано». Ну в open-uri нет ни строчки на сях, есть выделеный модуль для этого и небольшой монкей-патч метода open у Kernel, т. е. это все расширяемо. IO.copy_stream принимает любой IO-объект или путь до файла. Что мудачество, на самом деле, т.к, никакого дак-тайпинга нет, мог бы принимать и любые обекты с .read и .write. Иначе говоря, я думаю, что в руби это сделано гибче и лучше, но все равно хреново, но по причине описаной выше никаких особых эмоций это не вызывает :)

Евгений Степанищев (bolknote.ru) 2012

Комментарий для masterspammer.livejournal.com:

Программист, не знакомый с такой особенностью, может и не заподозрить что такая есть.

Это в документации к этой функции сказано. Если он ни разу не читал документации к этой функции, то пусть идёт мусор развозить.

при возможности локального чтение (в данном случае — include) чтобы выполнить свой код, его как-то нужно туда положить, что сама по себе та ещё задача

Тоже мне задача. Если сайт позволяет хоть что-то закачивать, то можно закачать файл разрешённого типа, с ПХП-кодом внутри. Если нет — можно попытаться подключить что-то локальное с дырой, если этого нельзя сделать, можно попробовать положить что-то в сессию (на некоторых сайтах есть визарды, которые хранят промежуточные данные в сессии) и цепануть сессию из /tmp. Короче, не учите хакера сайты ломать.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для Андрей:

Ну т. е. я могу порадоваться ее наличию раз в пять лет при написании какого-нибудь тупого одноразового скриптика, но если ее не будет, то напишу сам, это switch простейший

Я же не поддержку кучи протоколов, а про простоту работы с ними. И я не понимаю почему это только для одноразовых скриптов. Неужели вы так редко пишите код, который в сеть лезет?

masterspammer (masterspammer.livejournal.com) 2012

Комментарий для Евгения Степанищева:

Сказано, но можно и забыть, если не доводилось использовать. И про уровень программистов на PHP — вообще-то таких много, кому бы мусор развозить, многие вообще для себя пишут и некому их отправить мусор развозить. И ситуацию, при которой функция имеет много больше эффектов, чем следует из её названия, считаю провоцирующей на ошибку.

Ага, я тоже знаю, как что-то залить на сайт. Тут один способ сработает, там — другой, а где-то — ни одного — то есть нужна ещё другая дырка, чтоб эксплуатировать данную. Вот поэтому я и говорю про дыры разного диаметра.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для masterspammer.livejournal.com:

Сказано, но можно и забыть, если не доводилось использовать.

Язык знать надо, на мой взгляд.

И ситуацию, при которой функция имеет много больше эффектов, чем следует из её названия, считаю провоцирующей на ошибку.

Можно подумать, что это только в ПХП так. Что из «файлов» в каталоге /dev/ на самом деле файлы? Линукс считает, что «всё — файлы».

И такое поведение файловых функций можно отключить, есть опция. Но такая универсальность мне всё равно симпатична.

Кирилл Зорин 2012

Комментарий для Евгения Степанищева:

Евгений, у вас опечатка: «...функционал может являЕться...».
Заодно, зная ваше щепетильное отношение к русскому языку, хочу спросить: чем обосновано использование жаргонизма «функционал»?

Евгений Степанищев (bolknote.ru) 2012

Комментарий для Кирилл Зорин:

За опечатку спасибо! Я не вижу ничего страшного в применении жаргона в профессиональном тексте.

habrahabr.ru/users/vrus/topics/ 2012

Комментарий для Евгения Степанищева:

Извините за офтоп. Ежели допустим язык с нуля изучить по-быстрому, то считаете РНР не так плох? А то на Хабре читал про него ужасы. И вообще непонятно что нужно кроме языка знать. В смысле чтобы веб-сайт сделать. HTML, CSS, что-то про базы данных? Конкретный веб-сайт имеется в виду, не абстрактно выучить непонятно для чего.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для http://habrahabr.ru/users/vrus/topics/:

Если у вас это первый язык программирования, то смотря что вы собираетесь делать дальше. Если хотите, чтобы программирование стало вашей профессией, ни в коем случае не учите этот язык первым, если собираетесь один раз сляпать сайт и время от времени его поддерживать, выбирайте ПХП, конечно.

Если у вас простенький веб-сайт, посмотрите на Wordpress, можете сделать сайт на его основе. Если у вас какая-то более сложная задумка, то вам понадобится знание HTML, CSS и, например, MySQL.

habrahabr.ru/users/vrus/topics/ 2012

Комментарий для Евгения Степанищева:

Опа. Спасиб, но вы меня озадачили. Язык первый, да, если Фортран немного не считать. Еще однажды изучил одну книжку про С++ и было интересно. Но не хочу чтоб программирование было моей профессией (да и поздно уже :) Однако фиг же его знает, что там ждет впереди. Вдруг стартапы не пойдут, неохота подрабатывать чернорабочим :) ОК, в таком случае какой посоветуете язык на случай более серьезного погружения в программирование, но таки оставаясь в рамках цели сделать сайт? Сайт не суперсложный, но и не совсем простой. Вордпрессы изучать неохота. Мне кажется это какие-то полумеры.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для http://habrahabr.ru/users/vrus/topics/:

Если хотите учить хороший язык, но с целью сделать сайт, изучите Пайтон ( http://ru.wikipedia.org/wiki/Python )

habrahabr.ru/users/vrus/topics/ 2012

Комментарий для Евгения Степанищева:

Благодарю! Наверно так и сделаю. Несмотря что он в последнем рейтинге языков почему-то падает.

Популярное