Вопрос или проблема
Я пытаюсь создать плагин для безопасной загрузки.
Я нашел API REST как лучший вариант для этой задачи, но не могу изменить заголовки, такие как Content-Type:
. В коллбэке register_rest_route
эти заголовки уже установлены.
Если я установлю Content-disposition: attachment, filename=asd.txt
, я получаю ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION
в Chrome.
Я знаю, что REST не предназначен для выполнения этих задач, но я не смог найти альтернатив. Если они есть, пожалуйста, сообщите мне об этом.
Похоже, данные загружаются в формате JSON.
Я устал от “магии” WordPress. Эти задачи были бы такими простыми, если бы использовался чистый PHP, но WordPress только усложняет всё.
Пожалуйста, кто-нибудь скажите мне, как отключить эти магические заголовки *** или порекомендуйте способ для безопасных (PHP) загрузок в моем плагине.
(Нет, я не хочу использовать сторонние плагины. Я пробовал многие из них. Ни один из них пока не сработал… 😀 Весь этот WordPress — это огромный шаг назад после Symfony…)
Вот мой экспериментальный код:
add_action('rest_api_init', function() {
register_rest_route('secure-downloads/v1', '/download/(?P<filename>.*)',
array(
'methods' => 'GET',
'callback' => 'secure_downloads_handle_download'
));
});
function secure_downloads_handle_download(WP_REST_Request $request)
{
$filename = urldecode($request->offsetGet('filename'));
if(strpos($filename, '/..'))
{
return new WP_REST_Response('', 403);
}
$path = SECURE_DOWNLOADS_UPLOAD_DIR . $filename;
return new WP_REST_Response(file_get_contents($path), 200, array(
'Content-Type' => 'application/octet-stream',
'Content-Transfer-Encoding' => 'Binary',
'Content-disposition' => 'attachment, filename=\\' . $filename . '\\'
));
}
Должно быть достаточно добавить поле Content-Disposition.
Но конкретно это Content-Disposition, а не Content-disposition
Я также добавил бы некоторую проверку вашего параметра filename
, который передается в file_get_contents
, чтобы убедиться, что он существует и является допустимым. В противном случае вы можете быть уязвимы для атак обхода каталогов или удаленных запросов URL.
Вот что я выяснил:
Ошибка ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION
была вызвана неправильным составлением заголовка Content-Disposition
. Вот правильный:
array(
'Content-Disposition' => 'attachment; filename="' . $filename . '"'
)
Обратите внимание на точку с запятой после attachment, двойные кавычки вокруг имени файла и заглавную D в слове disposition, как указал @Tom J Nowell.
Это решило проблему с заголовками, но WP_REST_Response
все равно выводит файлы, как если бы они были закодированы в формате JSON. Поэтому я пошел традиционным путем:
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Type: application/octet-stream');
readfile($path);
exit;
Существует хук именно для этой ситуации, который позволяет избежать преждевременного завершения выполнения скрипта и отражает обработку WP-запросов методом HEAD
.
Разместите что-то подобное этому в конце вашей функции обработчика REST-ответа:
...
add_filter(
'rest_pre_serve_request',
function($served, $result, $request, $rest_server) use ($path) {
if (!$served) { # убедитесь, что запрос еще не был обработан
$served = true;
if ('HEAD' !== $request->get_method()) {
# выводите содержимое вашего ответа здесь, но не возвращайте или не завершайте
readfile($path);
}
}
return $served;
},
1, # измените приоритет по необходимости
4
);
$result = new WP_REST_Response();
$result->set_headers([
'Content-Type' => mime_content_type($path),
'Content-Length' => filesize($path),
...
]);
return $result;
.
Ответ или решение
Для решения проблемы с настройкой заголовков в REST API в контексте создания плагина для безопасной загрузки файлов в WordPress, необходимо глубже разобраться в механике работы WordPress и его REST API. Давайте пошагово разберем, что происходит и как можно обойти возникшие сложности.
Теория
1. Проблема заголовков в REST API: Когда вы регистрируете маршрут через register_rest_route
и предоставляете колбэк, WordPress обрабатывает возвратное значение вашей функции, рассматривая его как JSON-ответ по умолчанию. Это объясняет, почему ваши файлы загружаются как JSON, даже когда вы пытаетесь установить другие заголовки.
2. Конфликт с Content-Disposition
: Ошибка ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION
вызвана некорректной компоновкой заголовка Content-Disposition
. Эта ошибка особенно актуальна для браузеров, которые строго проверяют заголовки.
3. Безопасность и валидация данных: Необходимо тщательно проверять входные данные, особенно если это параметр filename
, чтобы предотвратить атаки типа обхода каталогов, которые могут привести к несанкционированному доступу к файлам за пределами ожидаемой директории.
Пример и Практическое Применение
Для решения задачи с правильной отправкой заголовков и обеспечения безопасности можно применить следующий подход:
add_action('rest_api_init', function() {
register_rest_route('secure-downloads/v1', '/download/(?P<filename>.+)', array(
'methods' => 'GET',
'callback' => 'secure_downloads_handle_download'
));
});
function secure_downloads_handle_download(WP_REST_Request $request)
{
$filename = urldecode($request->get_param('filename'));
// Проверка на попытки обхода каталога
if (strpos($filename, '../') !== false) {
return new WP_REST_Response('', 403);
}
$path = SECURE_DOWNLOADS_UPLOAD_DIR . $filename;
// Дополнительная проверка существования файла
if (!file_exists($path)) {
return new WP_REST_Response('File not found', 404);
}
// Установка заголовков перед выключением внешней обработки
add_filter('rest_pre_serve_request', function($served, $result, $request, $rest_server) use ($path) {
if (!$served) {
$served = true;
// Установка корректных заголовков для скачивания файла
header('Content-Disposition: attachment; filename="' . basename($path) . '"');
header('Content-Type: ' . mime_content_type($path));
header('Content-Length: ' . filesize($path));
// Вывод файлового содержимого
readfile($path);
}
return $served;
}, 10, 4);
// Возвращение пустого ответа, так как обслуживание будет обработано по фильтру
return new WP_REST_Response();
}
Применение
-
Управление заголовками: Использование фильтра
rest_pre_serve_request
позволяет переопределить стандартное поведение Rest API WordPress для прямой отправки файла, сохраняя при этом возможность изначальной конфигурации плагина. -
Защита от уязвимостей: Внедрение проверки параметров, таких как
filename
, предотвращает потенциальные угрозы, связанные с несанкционированным доступом к файлам. -
Гибкость и расширяемость: Такой подход обеспечивает больше контроля и гибкости, что позволяет проще адаптировать решение под будущие потребности, включая, например, изменение типа контента в зависимости от расширения файла.
-
Удобство интеграции: Этот подход сохраняет интеграцию с остальной системой WordPress, позволяет легко добавлять новые функциональные особенности и поддерживать их обновления.
Таким образом, предлагаемый способ решения проблемы включает в себя не только решение непосредственной задачи корректного формирования заголовков для скачивания файлов, но и реализацию механизмов безопасности, обработки ошибок, что позволяет обеспечить надежность и устойчивость разработки.