Discourse가 업로드된 파일을 이미지로 잘못 감지하는 문제

엔지니어링 사용자 중 일부가 이색적인 확장자를 가진 데이터 파일을 게시글에 첨부하길 원합니다. 해당 파일은 기본적으로 일반 텍스트 파일이지만 확장 ASCII 문자를 포함하고 있습니다.

Discourse의 NGINX 설정을 업데이트하여 이러한 파일에 대한 MIME 미디어 유형을 지정하도록 해보았지만 작동하지 않았습니다. 2주 전 이 주제에 대해 메타 포럼(How to customize MIME media type emitted for certain attachments?)에 질문을 게시했지만 아직 답변을 받지 못했습니다. NGINX를 업데이트하지 않아도 Discourse는 "application/octet-stream"이라는 폴백 MIME 유형을 사용하여 알려지지 않은 파일 유형을 서빙합니다. 당장은 그 정도는 감당할 수 있습니다.

그러나 사용자가 이러한 데이터 파일을 게시글에 업로드할 때(“업로드” 버튼을 사용하거나 드래그 앤 드롭을 하는 경우), Discourse에서 다음과 같은 오류 팝업이 표시됩니다:

사용자가 파일을 업로드할 때 Discourse가 지능적으로 작동하여 해당 파일이 이미지인지 다른 파일 유형인지 감지하려는 것 같습니다. 또한, 이 판정은 파일 내용(표준 Unix 명령인 "file"이 하는 방식과 유사하게)을 확인하여 내리는 것으로 보입니다. 이는 Discourse가 파일을 게시글 내용과 인라인으로 표시할지, 아니면 첨부파일로 옆에 둘지 결정하기 위한 것으로 추정됩니다.

이러한 데이터 파일의 경우, 이 체크가 파일을 이미지로 잘못 식별하고 있습니다. 재미로 몇몇 데이터 파일을 Ubuntu 시스템에 올려 “file” 명령으로 확인해 보니, 과연 "JPEG image data"로 식별되었습니다.

Discourse가 이미지가 아닌지 감지하려는 시도를 하지 않고 파일을 업로드할 수 있는 방법이 있을까요? 즉, "무조건 첨부파일로 업로드하고, 인라인으로 표시하지 말 것"과 같은 기능 말입니다.

대안으로, Discourse를 zip 파일 허용으로 설정하고 사용자가 업로드 전에 파일을 압축하도록 안내할 수 있지만, 사이트가 임의의 zip 파일 업로드에 열려 있게 되는 것은 원하지 않습니다. 이는 보안 문제로 보입니다.

도움 주시면 감사하겠습니다!

또 다른 우회 방법은 .bin, .data 또는 실제로는 .anythinggoes와 같은 확장자를 사용하는 것입니다. 이렇게 하면 Discourse가 해당 파일들을 그대로 두도록 하는 데 충분할 것입니다.

빠른 답변 감사합니다! 하지만 시도해 봤는데 작동하지 않습니다. Discourse는 확실히 확장자가 아니라 파일 내용을 기준으로 이미지인지를 판단하고 있습니다.

이 문제에 대해 의견이 있으신 분 계신가요? 꽤 심각한 버그인 것 같습니다.

이 문제에 대해 좀 더 파고들어 보았습니다.

간단히 말하면, 해당 파일이 이 유형의 파일과 동일한 시그니처로 시작하기 때문에 JPEG로 인식되고 있습니다.
Discourse에서 이 동작을 수정하는 것은 가능하지만, 수정 작업이 필요합니다. (끝에 참고)


아래는 기술적인 내용입니다.
이 문제는 여기서 시작됩니다:

FastImage 라이브러리가 파일을 열고 유형과 크기를 결정합니다.
예상하셨듯이, JPEG 유형을 반환합니다.

JPEG 시그니처가 어떤 모습인지 살펴보면 다음과 같습니다:

JPEG 마커

더 많은 정보: List of file signatures - Wikipedia
JPEG - Wikipedia

항상 다음 마커 바이트로 시작합니다: FF D8

Hex 편집기로 파일 샘플을 살펴보면, 동일한 방식으로 시작하는 것을 볼 수 있습니다.

이제 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 릴리스에 포함될 때까지 이 방법을 사용하라는 말씀이시죠. 그런데 어떻게 하면 될까요? (저는 숙련된 소프트웨어 개발자이지만 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 – 이렇게 자세한 정보를 주셔서 정말 감사합니다. 두 가지 수정 사항을 각각 테스트해 보았습니다(각각 새로 복원된 VM에서)가 모두 성공적으로 작동했습니다!

참고:

  1. 임시 수정의 경우, 변경 사항이 적용되려면 "./launcher restart app"을 실행해야 했습니다.

  2. “spec/models/optimized_image_spec.rb” 파일에도 FastImage.new()에 대한 참조가 있는 것 같습니다. 이 부분도 나머지와 마찬가지로 업데이트해야 하나요?

도움 주셔서 다시 한번 감사합니다!

동작하니까 다행이에요. :slight_smile:

  1. 이건 테스트 용도만이라 걱정하지 않으셔도 돼요.

좋아요! 감사합니다! 이제 개발 환경에서 테스트를 마쳤으니, 테스트 및 프로덕션 환경에 배포하겠습니다.

참고로, 시간이 허락하신다면 관련 문제에 대해 의견을 듣고 싶습니다 (How to customize MIME media type emitted for certain attachments?).