Если скачанный файл получил имя «download», набор процентов или нечитаемую кириллицу, сначала проверьте Content-Disposition в конечном ответе сервера. Для русского имени обычно нужны два параметра: простой ASCII-вариант в filename и имя в UTF-8 с процентным кодированием в filename*.
Если заголовок правильный, а имя всё равно другое, сравните прямое скачивание с кнопкой на странице: она может создавать файл через JavaScript и назначать имя самостоятельно. Переименование файла вручную помогает получателю, но не исправляет выдачу для следующих посетителей.
1. Найдите ответ, который действительно отдаёт файл
Откройте инструменты разработчика браузера, вкладку Network, включите сохранение журнала и повторите скачивание тестового документа без персональных данных. Выберите запрос файла и посмотрите конечный URL, код ответа, Content-Type и Content-Disposition.
Проверяйте заголовки ответа, а не параметры отправленной формы. Если запрос перенаправили на хранилище, нужен ответ хранилища: заголовок на промежуточном редиректе не описывает скачиваемый документ.
Убедитесь, что внутри действительно ожидаемый PDF, архив или таблица. Сохранённая страница входа с расширением .pdf — отдельная ошибка доступа; исправление имени её не устранит.
Для небольшого публичного тестового файла можно повторить GET:
curl -sS -L --max-time 30 -D - -o /dev/null 'https://example.com/test.pdf'
Команда получает тело, но не сохраняет его, и показывает заголовки всех переходов. Найдите последний ответ; не подставляйте сюда закрытые ссылки с токенами и не передавайте полный вывод посторонним. Проверка через HEAD может идти по другой ветке сервера и не воспроизвести скачивание.
2. Сравните filename и filename*
Для условного документа «отчет.pdf» корректный вариант выглядит так:
Content-Disposition: attachment; filename="otchet.pdf"; filename*=UTF-8''%D0%BE%D1%82%D1%87%D0%B5%D1%82.pdf
Здесь attachment обозначает скачивание, filename даёт запасное имя, а filename* передаёт кириллицу. После UTF-8 стоят два апострофа; расширенное значение не заключено в двойные кавычки.
Клиент, понимающий оба параметра, должен предпочесть filename*. Повтор одного и того же параметра делает значение некорректным — например, два разных filename в одном заголовке. Эти правила описаны в RFC 6266.
Разберите наблюдаемый симптом:
- В имени остались %D0…: проверьте, не записано ли процентное кодирование в обычный
filename. Браузеры обрабатывают его неодинаково. - Появились %25D0…: проверьте повторное кодирование уже подготовленного значения.
- Сохранилась латиница: проверьте синтаксис
filename*и поддержку конкретного клиента. - Кириллица уже испорчена до кодирования: проследите исходную строку имени в приложении. Добавление
charsetк Content-Type не починит неверные исходные символы.
Совместимое сочетание ASCII-имени и расширенного параметра разобрано в документации MDN. Для формирования заголовка используйте штатный helper вашего фреймворка, передавая ему исходное имя в ожидаемой кодировке.
3. Определите, на каком участке меняется имя
Сервер, прокси или хранилище
Сопоставьте ответ приложения на тестовом стенде с ответом через обычный публичный маршрут. Если значения различаются, проверьте правила добавления заголовков в прокси/CDN и metadata объекта в хранилище.
Назначьте один слой ответственным за Content-Disposition: приложение и прокси не должны независимо добавлять конфликтующие значения. После исправления проверьте и свежий, и кешированный ответ; очистка браузерного кеша сама по себе не обновляет объект на CDN.
Кнопка скачивания и JavaScript
Если прямая ссылка работает, а кнопка сохраняет «download», найдите в коде создание Blob и значение атрибута download. Имя может быть задано константой или потеряться при разборе заголовка.
Когда JavaScript запрашивает файл с другого origin, Content-Disposition может быть виден в Network, но недоступен через response.headers.get(). При уже корректно настроенном CORS сервер должен явно открыть этот заголовок:
Access-Control-Expose-Headers: Content-Disposition
Это разрешает скрипту прочитать заголовок, но само по себе не разрешает cross-origin запрос и не заменяет авторизацию. Механизм описан в MDN: Access-Control-Expose-Headers.
4. Проверьте исправление по небольшому набору примеров
Повторите скачивание имён с латиницей, кириллицей и пробелом через прямую ссылку и штатную кнопку в браузерах ваших пользователей. Зафиксируйте ожидаемое имя, фактическое имя, конечный URL и результат открытия документа.
Не вставляйте пользовательское имя в заголовок без проверки: исключайте управляющие символы, пути и опасные расширения. Браузер также может менять символы под требования файловой системы; добавленный суффикс при совпадении с существующим файлом ещё не доказывает ошибку сервера.
Если имя исправилось, но файл обрывается, продолжите по отдельной инструкции о докачке через Range и If-Range.
Что контролировать после исправления
Web-Puls помогает следить за доступностью сайта, но успешный HTTP-ответ не доказывает правильность имени скачанного документа. Этот сценарий стоит включить в проверку после релиза: небольшой тестовый файл, ожидаемый заголовок и реальное сохранение через интерфейс.
Если имя меняется между приложением, CDN и кнопкой и причину не удаётся локализовать, можно отправить заявку на диагностику. Укажите браузер, способ скачивания, ожидаемое и фактическое имя тестового файла; формат и стоимость работ согласовываются до начала.