Discourse ошибочно определяет загруженный файл как изображение

У меня есть несколько пользователей-инженеров, которые хотят прикреплять к своим сообщениям файлы данных с нестандартными расширениями. По сути, это обычные текстовые файлы, но содержащие расширенные символы ASCII.

Я попытался обновить конфигурацию NGINX в Discourse, чтобы указать типы MIME для этих файлов, но это не сработало. Две недели назад я создал тему (How to customize MIME media type emitted for certain attachments?) на эту тему, но пока не получил ответов. Даже если NGINX не обновлён, он всё равно будет обслуживать неизвестные типы файлов с использованием резервного типа MIME “application/octet-stream”. Пока что меня это устраивает.

Однако, когда пользователи пытаются загрузить эти файлы данных в сообщение (либо используя кнопку «Загрузить», либо перетаскиванием), появляется всплывающее окно с ошибкой от Discourse, подобное этому:

Похоже, что при загрузке файлов Discourse пытается «проявить смекалку» и определить, является ли файл изображением или чем-то другим. Более того, судя по всему, это определение происходит на основе содержимого файла (похоже на стандартную Unix-команду “file”). Я предполагаю, что это нужно, чтобы Discourse мог решить, следует ли встроить файл в содержимое сообщения или разместить его отдельно как вложение.

В случае с этими файлами данных такая проверка ошибочно определяет их как изображения. Просто ради интереса я поместил несколько таких файлов на машину с Ubuntu и проверил их с помощью команды “file”, и действительно, они были определены как “JPEG image data”.

Есть ли способ загрузить файлы без попытки Discourse определить, являются ли они изображениями? То есть: «Пожалуйста, загрузите это как вложение, независимо от содержания, не встраивайте»?

В качестве альтернативы я мог бы настроить Discourse на разрешение загрузки ZIP-файлов и попросить пользователей архивировать свои файлы перед загрузкой, но мне не хотелось бы открывать сайт для загрузки случайных ZIP-файлов. Это кажется проблемой безопасности.

Заранее спасибо за любую помощь!

Другой обходной путь — добавить поддержку расширения для таких странных файлов, как .bin, .data или вообще .любой_суффикс, что должно быть достаточно для Discourse, чтобы оставить эти файлы в покое.

Спасибо за быстрый ответ! Однако я попробовал это, и это не сработало. Discourse, безусловно, проверяет содержимое файла, а не его расширение, чтобы определить, является ли он изображением или нет.

У кого-нибудь есть мысли по этому поводу? Это выглядит как довольно серьёзный баг.

Я немного покопался в этой проблеме.

Коротко: ваш файл определяется как JPEG, потому что начинается с той же сигнатуры, что и файлы этого типа.
Исправить такое поведение в Discourse возможно, но для этого потребуется модификация (см. в конце).


Немного технической информации.
Проблема возникает здесь:

Библиотека FastImage открывает файл и определяет его тип и размер.
Как вы и ожидали, она возвращает тип JPEG.

Если посмотреть на сигнатуру JPEG, она выглядит так:

Маркеры JPEG

Дополнительная информация: List of file signatures - Wikipedia
JPEG - Wikipedia

Она всегда начинается со следующих байтов-маркеров: FF D8.

Если открыть образец вашего файла в шестнадцатеричном редакторе, вы увидите, что он начинается так же.

Теперь посмотрим, как FastImage определяет JPEG: это видно здесь:

Однако извлечь информацию об изображении нельзя, поскольку отсутствуют все необходимые байты.

Как исправить эту проблему в Discourse?
Изучив код FastImage, можно заметить полезный параметр, который можно передать.

Используя этот параметр, любая ошибка (SizeNotFound, ImageFetchFailure, CannotParseImage, UnknownImageType, BadImageURI) приведёт к отсутствию информации об изображении; и ваш файл не будет распознан как изображение.

@image_info =
begin
   FastImage.new(@file, :raise_on_failure=>true)
rescue StandardError
   nil
end
...
is_image ||= @image_info && FileHelper.is_supported_image?("test.#{@image_info.type}")

Теперь это может работать:

Я могу позже создать PR. Использование этого параметра здесь имеет смысл. :+1:

Вау! Это феноменальный анализ! Спасибо!

Несколько быстрых вопросов:

  1. Так что с этими изменениями файл не будет обнаружен как изображение и будет загружен как не-изображение, отображаясь справа от поста?

  2. Если я правильно понял, вы предлагаете внести эти изменения в мой локальный экземпляр Discourse, чтобы попробовать это и/или использовать до тех пор, пока это не будет включено в будущий релиз Discourse. Но как это сделать? (Я опытный разработчик программного обеспечения, но имею ограниченный опыт работы с Docker и никакого — с Ruby.)

  3. Вызов FastImage, который нужно будет изменить, находится в models/upload.rb, верно?

  1. Да, всё верно — как на моем скриншоте выше.

  2. Я не предлагаю вам вносить это изменение. Однако, если вы не можете ждать, вы, безусловно, можете протестировать такую правку.

  • Для временного изменения (исчезнет после пересборки):
cd /var/discourse
./launcher enter app
sed -i "s/FastImage.new(@file)/FastImage.new(@file, :raise_on_failure=>true)/" lib/upload_creator.rb
sed -i "s/FastImage.new(original_path)/FastImage.new(original_path, :raise_on_failure=>true)/" app/models/upload.rb
exit
  • Для постоянного изменения (сохранится после пересборки):
cd /var/discourse
nano containers/app.yml  (используйте ваш любимый редактор)

Добавьте следующие пользовательские команды в конец (секция run):

  - replace:
      filename: "/var/www/discourse/lib/upload_creator.rb"
      from: "FastImage.new(@file)"
      to: "FastImage.new(@file, :raise_on_failure=>true)"
  - replace:
      filename: "/var/www/discourse/app/models/upload.rb"
      from: "FastImage.new(original_path)"
      to: "FastImage.new(original_path, :raise_on_failure=>true)"

Затем выполните пересборку:

./launcher rebuild app
  1. Я полагаю, да, если вы планируете загружать файлы без расширений. Я не проверял, требуют ли другие случаи такого же изменения.

@Arkshine — Большое спасибо за эти детали. Я смог протестировать оба исправления отдельно (каждое на свежевосстановленной виртуальной машине), и оба сработали!

Заметки:

  1. Для временного исправления мне нужно было выполнить команду “./launcher restart app”, чтобы изменения вступили в силу.

  2. Похоже, что в файле “spec/models/optimized_image_spec.rb” также есть ссылка на FastImage.new(). Нужно ли обновить и этот файл, как и остальные?

Ещё раз спасибо за вашу помощь!

Рад, что всё работает. :slight_smile:

  1. Это только для тестирования, так что вам не о чем беспокоиться.

Отлично! Спасибо! Теперь, когда я протестировал это в своей dev-среде, я разверну это в test и prod средах.

Кстати, если у вас есть время, я был бы рад узнать ваше мнение по смежной проблеме (How to customize MIME media type emitted for certain attachments?).