Похоже, что при поиске проблем с xsendfile в Google выдается ряд противоречивых/устаревших обращений.
В интересах полного раскрытия информации я рассказываю о бета-версии xsendfile 1.0, задокументированной по адресу https://tn123.org/mod_xsendfile/beta/ (этот сайт не часто появляется в результатах поиска, только тот, у которого нет /beta). Я использую его с apache 2.4 и php 5.4.34 как в Linux, так и в Windows. Помимо того, что это последняя версия, мне нужно использовать бета-версию, потому что только на бета-сайте есть двоичные файлы Windows, созданные с помощью VC9 для apache 2.4.
Я сделал ошибку, прочитав документацию, где в описании значения имени файла в шапке написано:
Предполагается, что значение (имя файла), указанное в заголовке, закодировано в URL-адресе, т. е. будет выполнено неэкранирование/декодирование URL-адреса. См. XSendFileUnescape. Если вам случится хранить файлы, используя имена файлов, уже закодированные в URL-адресе, вы должны «двойно» кодировать имена... %20 -> %2520
И описание XSendFIleUnescape говорит:
Отключение XSendFileUnescape восстановит поведение до 1.0 с использованием необработанного значения заголовка, вместо того, чтобы сначала пытаться unescape/url-decode.
В документации об относительных путях ясно указано, что имя файла в заголовке X-SendFile
должно быть полным путем. Поэтому я тщательно прогнал свои пути через функцию urlencode
php.
Конечным результатом для меня неизменно была внутренняя ошибка сервера (код состояния 500) как в Linux, так и в Windows. Когда у меня была директива XSendFilePath в контексте server config
, что, согласно документации, разрешено, я не получил ничего более конкретного в своем журнале ошибок. Но когда я (в конце концов) переместил эту директиву в контекст Directory
, я получил это в своем журнале ошибок:
(404)Unknown error: [client 127.0.0.1:20742] xsendfile: bad file name encoding
В конце концов, в отчаянии, я сказал «к черту документацию» и удалил urlencode
в имени пути. И вдруг он заработал отлично (и Windows, и Linux)!!!
У меня нет путей с символами, отличными от ASCII, так что все готово. Но мне интересно, какая кодировка должна применяться, чтобы разрешить работу символов, отличных от ASCII. Если вы погуглите xsendfile: bad file name encoding
, вы найдете следующий исходный код по адресу https://github.com/nmaier/mod_xsendfile/blob/master/mod_xsendfile.c, где эта строка сообщения создается путем выбора истинной ветви:
rv = ap_unescape_url(file);
if (rv != OK) {
Но я не могу найти хорошее описание или исходный код для ap_unescape_url()
. Если исходный код на github не устарел, эта функция возражает против простого %-кодирования, которое выполняет функция PHP urlencode()
. В качестве дикой догадки я попытался вызвать ap_escape_url()
, но он не определен в PHP. Таким образом, остается вопрос о том, какая кодировка предполагается применяться к параметру пути в заголовке X-SendFile
??
Еще одно наблюдение/вопрос Описание XSendFile
, использующего «внутренние компоненты apache» для отправки файла, может натолкнуть вас на мысль, что он создаст заголовок Content-Type
из расширения имени файла, используя mod_mime
. Но на самом деле это не так, и примеры показывают явный вызов header()
для Content-Type
. Итак, мое продолжение: каков «правильный» способ построить этот заголовок из имени пути, передаваемого в X-SendFile
, чтобы он гарантированно соответствовал тому, что сделал бы mod_mime
, если бы мы не использовали X-SendFile
? Лучшее, что я мог придумать, это следующий код, использующий расширение PHP fileinfo
, но, насколько я знаю, нет особых причин ожидать, что он действительно будет соответствовать тому, что делает apache, когда ему дается URL-адрес для имени файла.
$finfo = new finfo(FILEINFO_MIME);
$mime_info = $finfo->file($pathname);
if (! strlen($mime_info)) {
$mime_info = 'application/octet-stream; charset=binary';
}
$basename = basename($pathname);
$encoded = "$pathname";
header("Content-Type: $mime_info");
header("Content-Disposition: attachment; filename=\"$basename\"");
header("X-SendFile: $encoded");