Я использую следующий код для загрузки файлов, которые хранятся вне общей папки.
$mime_type = mime_content_type("{$_GET['file']}");
define("IMG_LOC","/var/www/domain.com/upload/");
$filename = $_GET['file'];
header('Content-Description: File Transfer');
header('Content-Type: '.$mime_type);
header('Content-Disposition: attachment; filename='.basename(IMG_LOC.$filename));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filename));
readfile($filename);
exit;
Проблема в том, что файл, загруженный с помощью этого сценария, не может быть использован. Excel открывается пустым, PowerPoint сообщает «есть ошибка чтения», а Word говорит, что пропал конвертер. Принимая во внимание, что если я загружаю те же файлы с помощью ftp и открываю их вручную, файлы открываются правильно, показывая, что файлы не повреждены.
Для информации, это вызывается с другой страницы как: file.php?file='. $filename
Любая помощь будет приветствоваться. Спасибо за ваше время.
Вы, похоже, не указали путь к вашему файлу:
header('Content-Length: ' . filesize(IMG_LOC . $filename));
readfile(IMG_LOC . $filename);
Вы также должны добавить проверку имени файла, чтобы избежать проблем с безопасностью.
Если у вас все еще есть проблема, вы также должны проверить точный вывод скрипта, возможно, перед вашим файлом есть php-предупреждения или сообщения.
Я делаю вывод что $filename
не абсолютный путь к файлу, который вы ищете, и, следовательно, почему вы определяете IMG_LOC
постоянная с путем. Отсюда ясно, что filesize($filename)
а также readfile($filename)
вряд ли даст вам то, что вы хотите.
Попробуйте объединить константу перед $filename
переменная вроде так …
header('Content-Length: ' . filesize(IMG_LOC . $filename));
readfile(IMG_LOC . $filename);
Также учтите, что этот код подвержен атакам с внедрением заголовка, а также другим проблемам безопасности, таким как пользователь, предоставляющий вам имя файла на вашем сервере, который вы, возможно, не захотите видеть. Например, если я вызову ваш скрипт со строкой запроса ?file=yourscript.php
Я смогу загрузить ваш реальный код PHP и, возможно, увидеть любую конфиденциальную информацию, которую вы, возможно, не захотите раскрыть, например пароль вашей базы данных или что-то еще хуже.
Также, mime_content_type
это осуждается функция и должна быть заменена на Fileinfo расширение вместо.
У вашего скрипта есть различные проблемы, которые в целом будут мешать ему должным образом за работой. Я грубо прохожу строки и оставляю некоторые комментарии, затем пишу небольшое резюме и предлагаю другой пример кода с включенными комментариями:
$mime_type = mime_content_type("{$_GET['file']}");
Вам не нужно оборачивать $_GET
суперглобальный в фигурных скобках, а затем в двойных кавычках. Это просто не нужно для этого параметра. Вы, кажется, отвлекаетесь в этой точке.
Во всяком случае, эта вещь MIME-типа не является необходимой, так как MIME-тип не интересен, если вы хотите предложить скачать. Ты взял application/octet-stream
вместо этого вы можете позаботиться позже о более конкретном типе пантомимы:
$mime_type = "application/octet-stream";
Тогда в неправильном положении вы определяете IMG_LOC
постоянная:
define("IMG_LOC", "/var/www/domain.com/upload/");
Это относится к самой верхней части скрипта, так как вы определяете конфигурацию этим.
В соответствии:
$filename = $_GET['file'];
вы больше не проверяете ошибки, это открывает ваш сценарий для обхода каталогов и атак путем внедрения пути, что на самом деле превращает скрипт, если он у вас есть, в черный ход. Любой файл, к которому скрипт имеет доступ на этом сервере, может быть загружен.
Следующие две строки более или менее правильны:
header('Content-Description: File Transfer');
header('Content-Type: '.$mime_type);
Для следующего заголовка:
header('Content-Disposition: attachment; filename='.basename(IMG_LOC.$filename));
Я бы извлек базовое имя ранее и просто передал переменную здесь. То же самое для заголовка content-length позже:
header('Content-Length: ' . filesize($filename));
Затем у вас есть этот блок кэширующих заголовков, так как вы отправляете файл с диска, я не думаю, что это действительно необходимо, поэтому я бы удалил их:
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
Строка readfile выглядит нормально, однако вы можете выполнить некоторую проверку ошибок:
readfile($filename);
И последняя строка, которую я не понимаю, так как скрипт в конце концов, зачем выходить?
exit;
Мои предложения после этого небольшого обзора:
Соберите информацию, какие файлы должны быть обслужены и как они должны быть названы. Сбор такой информации позволит вам закрыть проблему обхода каталога, которую вы должны закрыть в первую очередь.
Во-вторых, размещение логической части над выводом (и конфигурации над логикой) должно позволить вам упорядочить сценарий более полезным способом, что позволит вам легче обрабатывать проблемы с типом mime, например, когда вы поддерживаете сценарий (или кэшируете). если это действительно проблема).
<?php
/**
* download a file
*
* parameter:
*
* file - name of the relative to upload folder
*/
const IMG_LOC = "/var/www/domain.com/upload";
// validate filename input
if (!isset($_GET['file'])) {
return;
}
$filename = $_GET['file'];
$path = realpath(IMG_LOC . '/' . $filename);
if (0 !== strpos($path, IMG_LOC)) {
return;
}
if (!is_readable($filename)) {
return;
}
// obtain data
$basename = basename($filename);
$mime_type = "application/octet-stream"; # can be improved later
$size = filesize($path);
// output
header('Content-Description: File Transfer');
header('Content-Type: ' . $mime_type);
header('Content-Disposition: attachment; filename=' . $basename);
header('Content-Length: ' . $size);
readfile($filename);