Чтобы браузер скачал файл вместо его отображения, необходимо установить заголовок ответа Content-Disposition со значением attachment. Если этого не сделать или указать inline, браузер попытается открыть файл в новой вкладке или на странице.
Корректная настройка заголовка ответа (Response Header)
Заголовок Content-Disposition должен быть включен в HTTP-ответ сервера перед отправкой тела запроса. Формат строки: Content-Disposition: attachment; filename="имя_файла".
Имя файла должно быть закодировано, если содержит пробелы или специальные символы. Используйте функцию encodeURIComponent для JavaScript или экранирование в коде на сервере (например, PHP header() или Python response.headers).
Пример корректного заголовка: Content-Disposition: attachment; filename="отчет_2024.pdf".
Если имя файла содержит кириллицу, браузеры могут отображать его некорректно. Рекомендуется использовать ASCII-совместимые имена или передавать оригинальное имя в дополнительном поле disposition-filename* (RFC 5987), хотя поддержка этого поля варьируется.
Влияние параметра Content-Disposition на поведение клиента
Значение attachment: браузер показывает диалоговое окно «Сохранить как» или автоматически сохраняет файл в папку по умолчанию, если пользователь не отключил этот механизм.
Значение inline: браузер пытается открыть файл с помощью встроенного просмотрщика (например, PDF-вьюера) или предлагает скачать, если встроенный просмотрщик отсутствует. Для изображений это стандартное поведение без заголовка.
Отсутствие заголовка Content-Disposition: браузер решает самостоятельно, как поступить с файлом. Обычно он проверяет расширение файла и тип контента. Если тип указывает на текст (text/plain) или изображение (image/png), файл откроется в новой вкладке. Для бинарных данных (application/octet-stream) часто происходит скачивание.
Важно: даже при наличии заголовка attachment, браузер может игнорировать его, если тип контента явно указывает на отображаемый формат.
Типы значений для параметра filename и их совместимость
ASCII-имена: работают во всех браузерах. Пример: file.txt, data.csv.
UTF-8 с экранированием: современные браузеры (Chrome, Firefox, Safari) поддерживают имена в UTF-8, но для максимальной совместимости используйте ASCII или RFC 5987.
Проблемные символы: пробелы, скобки, двоеточия требуют кодирования. Например, «Мой файл.txt» лучше передать как «My%20file.txt» или использовать экранирование в зависимости от языка программирования.
Расширения файлов: браузер часто игнорирует расширение в заголовке и использует своё собственное на основе типа контента. Если вы хотите принудительно задать расширение, используйте тип контента application/octet-stream.
Обработка случаев, когда браузер игнорирует заголовок
Сценарий 1: Тип контента image/png. Браузер всегда попытается отобразить изображение, игнорируя attachment. Решение: использовать тип application/octet-stream или application/pdf для принудительного скачивания.
Сценарий 2: Пользователь ранее выбрал «Всегда открывать файлы этого типа». В этом случае браузер не показывает диалог сохранения даже при наличии заголовка attachment.
Сценарий 3: Кэширование. Если файл уже кэширован, браузер может использовать сохраненную версию без проверки заголовков. Очистка кэша или использование уникальных имен файлов решает проблему.
Сценарий 4: Мобильные устройства. На iOS и Android поведение может отличаться. Safari на iOS часто игнорирует attachment для некоторых типов контента.
Сценарии использования: прямая ссылка vs форма загрузки
Прямая ссылка (GET): при запросе к URL с файлом браузер проверяет заголовок. Если он отсутствует или указан inline, файл откроется. Для скачивания обязательно нужен attachment.
Форма загрузки (POST): если форма отправляет файл на сервер, сервер должен вернуть заголовок Content-Disposition: attachment в ответе 200 OK или 201 Created.
Множественные файлы: при загрузке нескольких файлов через multipart/form-data каждый файл обрабатывается отдельно. Заголовок устанавливается для каждого chunk ответа.
Динамическая генерация: если файл создается на лету (например, отчет по запросу), установите заголовок перед отправкой тела ответа.
Проверка корректности работы в разных браузерах
Тестирование: используйте инструменты разработчика (F12) для проверки заголовков ответа. Отправьте запрос и посмотрите, какой статус и заголовки возвращает сервер.
Браузеры Chrome и Firefox: корректно обрабатывают attachment с ASCII-именами. UTF-8 работает в последних версиях.
Safari на macOS/iOS: может игнорировать attachment для изображений и PDF. Используйте тип application/octet-stream для принудительного скачивания.
Edge и Opera: поведение аналогично Chrome, но с некоторыми нюансами при работе с кириллицей.
Ограничения безопасности при передаче имен файлов
Не передавайте имена файлов напрямую из запроса пользователя без проверки. Это может привести к атакам типа path traversal или выполнению кода.
Валидация: проверяйте расширение файла на сервере, а не только по имени. Используйте whitelist разрешенных расширений.
Экранирование: используйте функции кодирования для имен файлов перед установкой заголовка. В PHP — header('Content-Disposition: attachment; filename="' . escapeshellarg($filename) . '"');
Хранение: не сохраняйте файлы с именами, которые могут содержать вредоносные символы. Генерируйте уникальные имена на сервере.
Вопросы и ответы
Можно ли использовать Content-Disposition для скачивания видео?
Да, но только если тип контента не указывает на воспроизведение. Используйте application/octet-stream и заголовок attachment. Браузеры могут игнорировать это для популярных форматов (mp4, webm), поэтому лучше проверять поведение в каждом браузере.
Как заставить браузер скачать файл без диалога «Сохранить как»?
Это невозможно гарантировать во всех браузерах. Пользователь может настроить автоматическое сохранение, но это не является функцией сервера. Единственный способ — использовать attachment и надеяться на настройки пользователя.
Что делать, если браузер открывает файл вместо скачивания?
Проверьте тип контента. Если он image/png или application/pdf, браузер попытается отобразить файл. Измените тип на application/octet-stream или добавьте заголовок Content-Disposition: attachment.
<?php
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="report.pdf"');
echo file_get_contents('report.pdf');
?>
from flask import Response
@app.route('/download')
def download_file():
return Response(
open('file.pdf', 'rb').read(),
mimetype='application/pdf',
headers={'Content-Disposition': 'attachment; filename=file.pdf'}
)


