Все статьи раздела «Программирование»

Защита от лишних символов во включаемом PHP-файле

Лишние символы в начале и в конце PHP-файла могут привести к неприятным последствиям, а то и к полной неработоспособности онлайн-приложения. Давайте рассмотрим, откуда они могут браться и как с ними бороться.

Текст программы на PHP помещается в теги, обычно <?php в начале и ?> в конце (возможны и другие варианты, см. руководство по языку PHP). Если весь файл состоит из такого кода, то первый тег стоит в самом начале файла, а второй в конце. Если вне тегов оказываются лишние символы, то они будут выведены в браузер в момент чтения PHP-интерпретатором соответствующего места файла, т.е. вначале выводятся символы от начала файла до открывающего тега, потом выполняется программа между тегами, потом (если до этого дойдет, а не встретится раньше, например, exit) символы после закрывающего тега до конца файла.

Такие лишние символы могут появиться несколькими путями. Самый простой — пробелы в конце файла. Согласно документации, закрывающий тег включает в себя следующий за ним перевод строки, поэтому один перевод строки выводится не будет, но если за ним идет, например, пробел, то этот пробел будет напечатан. Еще один источник я открыл недавно, когда отредактировал файл в кодировке UTF-8 с помощью Notepad. Этот редактор и некоторые другие "умные" программы автоматически добавляют в начало файла сигнатуру, которая позволяет определить тип кодировки (так называемый Byte-order mark). Такая сигнатура представляет собой символ U-FEFF (неразрывный пробел нулевой ширины), который кодируется в соответствии с кодировкой файла (для UTF-8 это будут три байта EF BB BF). В Notepad символ никак не отображается, но при чтении такого файла интерпретатором PHP три символа передаются в браузер. Третий возможный путь — вывод в браузер сообщений интерпретатора (например, уведомления E_NOTICE) — я не рассматриваю, потому что в действующей онлайн-системе таким сообщения место в протоколе сервера, а не в выводе.

Какие же последствия имеет несанкционированный вывод символов в браузер? Первое: перестают работать функции header и setcookie. Эти функции отправляют заголовки HTTP-ответа, которые накапливаются в буфере до тех пор, пока не начнется вывод собственно текста страницы (любого символа). Перед тем как отправить первый символ страницы, интерпретатор отправляет браузеру заголовки и больше заголовки добавлять нельзя. Симптомом такой ситуации является предупреждение вида "PHP Warning:  Cannot modify header information - headers already sent by (output started at", с указанием имени файла и строки, где был напечатан первый символ. Если в вашей программе не часто используется функция header, то можно долго быть в неведении о лишних символах, однако если не удается установить cookie или выполнить перенаправление на другую страницу через header('Location: page.html') то проблема легко обнаруживается при анализе протокола ошибок. Второе: как ни банально звучит, но вы получаете лишние символы в результате выполнения скрипта. И если в обычной HTML-странице пробел обычно не мешает, то при выводе скриптом изображения, результат, скорее всего, будет некорректным. То же может произойти при формировании каких-то специальным образом форматированных данных.

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

Для файлов, которые могут редактироваться другими людьми (не читавшими эту статью :), можно предложить следующую хитрость: заключите вставку такого файла в пару функций ob_start / ob_end_clean. При этом PHP-код отлично выполнится, но все выводимые символы будут буферизированы, а затем сброшены. Пример кода:

ob_start();
require('user_edited_file.php');
ob_end_clean();

Обратите внимание: буферизация, которая была использована, может повлиять только на символы, выводимые на верхнем уровне включаемого кода, но не на то, что будет выводиться внутри функций:

Hello!
<?php

function out1() {
  print "Bye\n";
}

?>

В приведенном файле, если он будет включен с помощью кода, описанного выше, текст "Hello!" не будет выведен. Однако функция out1 будет успешно зарегистрирована, и когда она будет вызвана (если это произойдет уже после ob_end_clean!), строка "Bye" будет успешно отправлена в браузер.