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

Одна из превосходнейших вещей в ПХП — обработчики протоколов. Они позволяют работать с разнообразными источниками данных как с файлами. Встроенные протоколы умеют понимать 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('http://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"
… и так далее
Как такой непродуманный функционал может являться продолжением такой прекрасной штуки мне непонятно.
11 июля 2012 22:18

Александр Бабаев (bealex.moikrug.ru)
11 июля 2012, 23:12

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

Морозов (morozov.livejournal.com)
11 июля 2012, 23:18

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

bolk (bolknote.ru)
12 июля 2012, 06:10, ответ предназначен Морозов (morozov.livejournal.com):

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

bolk (bolknote.ru)
12 июля 2012, 08:25, ответ предназначен Александр Бабаев (bealex.moikrug.ru):

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

Андрей (инкогнито)
12 июля 2012, 10:35

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

На руби:
require 'open-uri'
IO.copy_stream open('ftp://ftp.debian.org/debian/README.html'), '/tmp/somefile.txt'

aktuba (инкогнито)
12 июля 2012, 11:08

Андрей, тогда на php еще красивее:
copy('ftp://ftp.debian.org/debian/README.html', '/tmp/somefile.txt');
А как в ruby data: или ssh: обработать?

bolk (bolknote.ru)
12 июля 2012, 12:35, ответ предназначен Андрею

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

masterspammer.livejournal.com (инкогнито)
12 июля 2012, 12:42

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

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

bolk (bolknote.ru)
12 июля 2012, 12:53, ответ предназначен masterspammer.livejournal.com

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

hshhhhh (hshhhhh.name)
12 июля 2012, 13:28, ответ предназначен bolk (bolknote.ru):

Немного более сложный синтаксис позволяет сделать и более сложные вещи
Хм, а я через curl это делал потому что традиционно и работает везде примерно одинаково.

masterspammer.livejournal.com (инкогнито)
12 июля 2012, 13:29, ответ предназначен bolk (bolknote.ru):

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

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

hshhhhh (hshhhhh.name)
12 июля 2012, 13:33

То есть url_get_content было бы лучше.
rar_get_contents
ftp_get_contents
gzip_get_contents
https_get_contents

masterspammer.livejournal.com (инкогнито)
12 июля 2012, 13:48, ответ предназначен hshhhhh (hshhhhh.name):

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

Андрей (инкогнито)
12 июля 2012, 18:55, ответ предназначен bolk (bolknote.ru):

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

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

bolk (bolknote.ru)
12 июля 2012, 19:31, ответ предназначен masterspammer.livejournal.com

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

bolk (bolknote.ru)
12 июля 2012, 19:35, ответ предназначен Андрею

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

masterspammer (masterspammer.livejournal.com)
12 июля 2012, 20:20, ответ предназначен bolk (bolknote.ru):

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

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

bolk (bolknote.ru)
13 июля 2012, 07:55, ответ предназначен masterspammer.livejournal.com:

Сказано, но можно и забыть, если не доводилось использовать.
Язык знать надо, на мой взгляд.
И ситуацию, при которой функция имеет много больше эффектов, чем следует из её названия, считаю провоцирующей на ошибку.
Можно подумать, что это только в ПХП так. Что из «файлов» в каталоге /dev/ на самом деле файлы? Линукс считает, что «всё — файлы».

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

Кирилл Зорин (инкогнито)
13 июля 2012, 13:19, ответ предназначен bolk (bolknote.ru):

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

bolk (bolknote.ru)
13 июля 2012, 15:56, ответ предназначен Кириллу Зорину

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

http://habrahabr.ru/users/vrus/topics/ (инкогнито)
15 июля 2012, 15:01, ответ предназначен bolk (bolknote.ru):

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

bolk (bolknote.ru)
15 июля 2012, 16:58, ответ предназначен http://habrahabr.ru/users/vrus/topics/

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

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

http://habrahabr.ru/users/vrus/topics/ (инкогнито)
15 июля 2012, 18:10, ответ предназначен bolk (bolknote.ru):

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

bolk (bolknote.ru)
15 июля 2012, 18:42, ответ предназначен http://habrahabr.ru/users/vrus/topics/

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

http://habrahabr.ru/users/vrus/topics/ (инкогнито)
15 июля 2012, 19:27, ответ предназначен bolk (bolknote.ru):

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

Ваше имя или адрес блога (можно OpenID):

Текст вашего комментария, не HTML:

Кому бы вы хотели ответить (или кликните на его аватару)