1
0
mirror of https://github.com/meineerde/redmine.git synced 2026-02-01 03:57:15 +00:00

Avoid generating PDF thumbnails for PostScript files (#43451).

Patch by Go MAEDA (user:maeda).


git-svn-id: https://svn.redmine.org/redmine/trunk@24267 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Marius Balteanu 2026-01-05 08:32:06 +00:00
parent 23a93e0e06
commit d3aa4e1458
5 changed files with 138 additions and 1 deletions

View File

@ -41,13 +41,15 @@ module Redmine
# Make sure we only invoke Imagemagick if the file type is allowed
mime_type = File.open(source) {|f| Marcel::MimeType.for(f)}
return nil unless ALLOWED_TYPES.include? mime_type
return nil if mime_type == 'application/pdf' && !gs_available?
directory = File.dirname(target)
FileUtils.mkdir_p directory
size_option = "#{size}x#{size}>"
if mime_type == 'application/pdf'
return nil unless gs_available?
return nil unless valid_pdf_magic?(source)
cmd = "#{shell_quote CONVERT_BIN} #{shell_quote "#{source}[0]"} -thumbnail #{shell_quote size_option} #{shell_quote "png:#{target}"}"
else
cmd = "#{shell_quote CONVERT_BIN} #{shell_quote source} -auto-orient -thumbnail #{shell_quote size_option} #{shell_quote target}"
@ -98,6 +100,21 @@ module Redmine
@gs_available
end
# Check PDF magic bytes to make sure the file looks like a PDF, not
# PostScript.
#
# This method treats the file as PostScript instead of PDF and returns
# false if PostScript magic bytes appear before the PDF magic bytes.
# This behavior is based on the detection logic used by Ghostscript in
# the redefined `run` operator in pdf_main.ps.
def self.valid_pdf_magic?(filename)
head_data = File.binread(filename, 1024)
pdf_magic_pos = head_data.index('%PDF-')
ps_magic_pos = head_data.index('%!PS')
!pdf_magic_pos.nil? && (ps_magic_pos.nil? || pdf_magic_pos < ps_magic_pos)
end
def self.logger
Rails.logger
end

32
test/fixtures/files/hello.pdf vendored Normal file
View File

@ -0,0 +1,32 @@
%PDF-2.0
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>
endobj
4 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj
5 0 obj
<< /Length 43 >>
stream
BT /F1 24 Tf 100 700 Td (Hello World) Tj ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000241 00000 n
0000000311 00000 n
trailer
<< /Size 6 /Root 1 0 R >>
startxref
404
%%EOF

7
test/fixtures/files/hello.ps vendored Normal file
View File

@ -0,0 +1,7 @@
%!PS
/Helvetica findfont
24 scalefont
setfont
100 700 moveto
(Hello World) show
showpage

10
test/fixtures/files/with_pdf_magic.ps vendored Normal file
View File

@ -0,0 +1,10 @@
%!PS
%PDF-2.0
% ***** The above line looks like PDF magic bytes, but just a comment *****
/Helvetica findfont
24 scalefont
setfont
100 700 moveto
(Hello World) show
showpage

View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
# Redmine - project management software
# Copyright (C) 2006- Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
require_relative '../../../test_helper'
class Redmine::ThumbnailTest < ActiveSupport::TestCase
def test_valid_pdf_magic_returns_false_when_postscript_magic_comes_first
# This PostScript file has a string '%PDF-' after '%!PS'.
# Marcel currently misclassifies it as application/pdf, so we use
# valid_pdf_magic? to avoid treating it as PDF.
# TODO:
# Consider removing `valid_pdf_magic?` once Marcel correctly
# returns application/postscript.
file = file_fixture('with_pdf_magic.ps')
assert_equal 'application/pdf', file.open {|f| Marcel::MimeType.for(f)}
assert_equal false, Redmine::Thumbnail.valid_pdf_magic?(file.to_s)
end
def test_valid_pdf_magic_returns_false_for_postscript
file = file_fixture('hello.ps')
assert_equal 'application/postscript', file.open {|f| Marcel::MimeType.for(f)}
assert_equal false, Redmine::Thumbnail.valid_pdf_magic?(file.to_s)
end
def test_valid_pdf_magic_returns_true_for_pdf
file = file_fixture('hello.pdf')
assert_equal 'application/pdf', file.open {|f| Marcel::MimeType.for(f)}
assert_equal true, Redmine::Thumbnail.valid_pdf_magic?(file.to_s)
end
def test_thumbnail_returns_nil_for_postscript
skip unless Redmine::Thumbnail.convert_available? && Redmine::Thumbnail.gs_available?
set_tmp_attachments_directory
file = file_fixture('with_pdf_magic.ps')
target = File.join(Attachment.storage_path, "#{SecureRandom.hex(8)}.thumb.png")
assert_nil Redmine::Thumbnail.generate(file.to_s, target, 100)
assert_not File.exist?(target)
ensure
FileUtils.rm_f(target) if target
end
def test_thumbnail_returns_thumbnail_filename_for_pdf
skip unless Redmine::Thumbnail.convert_available? && Redmine::Thumbnail.gs_available?
set_tmp_attachments_directory
file = file_fixture('hello.pdf')
target = File.join(Attachment.storage_path, "#{SecureRandom.hex(8)}.thumb.png")
result = Redmine::Thumbnail.generate(file.to_s, target, 100)
assert_equal target, result
assert File.exist?(target)
ensure
FileUtils.rm_f(target) if target
end
end