mirror of
https://github.com/discourse/discourse.git
synced 2024-12-13 04:33:42 +08:00
FIX: automatically timeout long running image magick commands (#12670)
Previously certain images may lead to convert / identify to run for unreasonable amounts of time This adds a maximum amount of time these commands can run prior to forcing them to stop
This commit is contained in:
parent
3aa9ae08c2
commit
5b99cb9275
|
@ -306,9 +306,10 @@ class OptimizedImage < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
MAX_PNGQUANT_SIZE = 500_000
|
MAX_PNGQUANT_SIZE = 500_000
|
||||||
|
MAX_CONVERT_SECONDS = 20
|
||||||
|
|
||||||
def self.convert_with(instructions, to, opts = {})
|
def self.convert_with(instructions, to, opts = {})
|
||||||
Discourse::Utils.execute_command("nice", "-n", "10", *instructions)
|
Discourse::Utils.execute_command("nice", "-n", "10", *instructions, timeout: MAX_CONVERT_SECONDS)
|
||||||
|
|
||||||
allow_pngquant = to.downcase.ends_with?(".png") && File.size(to) < MAX_PNGQUANT_SIZE
|
allow_pngquant = to.downcase.ends_with?(".png") && File.size(to) < MAX_PNGQUANT_SIZE
|
||||||
FileHelper.optimize_image!(to, allow_pngquant: allow_pngquant)
|
FileHelper.optimize_image!(to, allow_pngquant: allow_pngquant)
|
||||||
|
|
|
@ -13,6 +13,7 @@ class Upload < ActiveRecord::Base
|
||||||
SHA1_LENGTH = 40
|
SHA1_LENGTH = 40
|
||||||
SEEDED_ID_THRESHOLD = 0
|
SEEDED_ID_THRESHOLD = 0
|
||||||
URL_REGEX ||= /(\/original\/\dX[\/\.\w]*\/(\h+)[\.\w]*)/
|
URL_REGEX ||= /(\/original\/\dX[\/\.\w]*\/(\h+)[\.\w]*)/
|
||||||
|
MAX_IDENTIFY_SECONDS = 5
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :access_control_post, class_name: 'Post'
|
belongs_to :access_control_post, class_name: 'Post'
|
||||||
|
@ -270,7 +271,7 @@ class Upload < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def target_image_quality(local_path, test_quality)
|
def target_image_quality(local_path, test_quality)
|
||||||
@file_quality ||= Discourse::Utils.execute_command("identify", "-format", "%Q", local_path).to_i rescue 0
|
@file_quality ||= Discourse::Utils.execute_command("identify", "-format", "%Q", local_path, timeout: MAX_IDENTIFY_SECONDS).to_i rescue 0
|
||||||
|
|
||||||
if @file_quality == 0 || @file_quality > test_quality
|
if @file_quality == 0 || @file_quality > test_quality
|
||||||
test_quality
|
test_quality
|
||||||
|
|
|
@ -94,7 +94,13 @@ module Discourse
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def execute_command(*command, failure_message: "", success_status_codes: [0], chdir: ".")
|
def execute_command(*command, timeout: nil, failure_message: "", success_status_codes: [0], chdir: ".")
|
||||||
|
if timeout
|
||||||
|
# will send a TERM after timeout
|
||||||
|
# will send a KILL after timeout * 2
|
||||||
|
command = ["timeout", "-k", "#{timeout.to_f * 2}", timeout.to_s] + command
|
||||||
|
end
|
||||||
|
|
||||||
stdout, stderr, status = Open3.capture3(*command, chdir: chdir)
|
stdout, stderr, status = Open3.capture3(*command, chdir: chdir)
|
||||||
|
|
||||||
if !status.exited? || !success_status_codes.include?(status.exitstatus)
|
if !status.exited? || !success_status_codes.include?(status.exitstatus)
|
||||||
|
|
|
@ -82,7 +82,7 @@ class ThemeStore::GitImporter
|
||||||
else
|
else
|
||||||
Discourse::Utils.execute_command("git", "clone", @url, @temp_folder)
|
Discourse::Utils.execute_command("git", "clone", @url, @temp_folder)
|
||||||
end
|
end
|
||||||
rescue RuntimeError => err
|
rescue RuntimeError
|
||||||
raise RemoteTheme::ImportError.new(I18n.t("themes.import_error.git"))
|
raise RemoteTheme::ImportError.new(I18n.t("themes.import_error.git"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -190,7 +190,7 @@ class UploadCreator
|
||||||
file.path
|
file.path
|
||||||
]
|
]
|
||||||
|
|
||||||
frames = Discourse::Utils.execute_command(*command).to_i rescue 1
|
frames = Discourse::Utils.execute_command(*command, timeout: Upload::MAX_IDENTIFY_SECONDS).to_i rescue 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@frame_count > 1
|
@frame_count > 1
|
||||||
|
@ -269,6 +269,7 @@ class UploadCreator
|
||||||
extract_image_info!
|
extract_image_info!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
MAX_CONVERT_FORMAT_SECONDS = 20
|
||||||
def execute_convert(from, to, opts = {})
|
def execute_convert(from, to, opts = {})
|
||||||
command = [
|
command = [
|
||||||
"convert",
|
"convert",
|
||||||
|
@ -282,7 +283,11 @@ class UploadCreator
|
||||||
command << "-quality" << opts[:quality].to_s if opts[:quality]
|
command << "-quality" << opts[:quality].to_s if opts[:quality]
|
||||||
command << to
|
command << to
|
||||||
|
|
||||||
Discourse::Utils.execute_command(*command, failure_message: I18n.t("upload.png_to_jpg_conversion_failure_message"))
|
Discourse::Utils.execute_command(
|
||||||
|
*command,
|
||||||
|
failure_message: I18n.t("upload.png_to_jpg_conversion_failure_message"),
|
||||||
|
timeout: MAX_CONVERT_FORMAT_SECONDS
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def should_alter_quality?
|
def should_alter_quality?
|
||||||
|
@ -359,13 +364,14 @@ class UploadCreator
|
||||||
@image_info.orientation.to_i > 1
|
@image_info.orientation.to_i > 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
MAX_FIX_ORIENTATION_TIME = 5
|
||||||
def fix_orientation!
|
def fix_orientation!
|
||||||
path = @file.path
|
path = @file.path
|
||||||
|
|
||||||
OptimizedImage.ensure_safe_paths!(path)
|
OptimizedImage.ensure_safe_paths!(path)
|
||||||
path = OptimizedImage.prepend_decoder!(path, nil, filename: "image.#{@image_info.type}")
|
path = OptimizedImage.prepend_decoder!(path, nil, filename: "image.#{@image_info.type}")
|
||||||
|
|
||||||
Discourse::Utils.execute_command('convert', path, '-auto-orient', path)
|
Discourse::Utils.execute_command('convert', path, '-auto-orient', path, timeout: MAX_FIX_ORIENTATION_TIME)
|
||||||
|
|
||||||
extract_image_info!
|
extract_image_info!
|
||||||
end
|
end
|
||||||
|
|
|
@ -392,6 +392,12 @@ describe Discourse do
|
||||||
expect(Discourse::Utils.execute_command("pwd", chdir: "plugins").strip).to eq("#{Rails.root.to_s}/plugins")
|
expect(Discourse::Utils.execute_command("pwd", chdir: "plugins").strip).to eq("#{Rails.root.to_s}/plugins")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "supports timeouts" do
|
||||||
|
expect do
|
||||||
|
Discourse::Utils.execute_command("sleep", "999999999999", timeout: 0.001)
|
||||||
|
end.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
|
||||||
it "works with a block" do
|
it "works with a block" do
|
||||||
Discourse::Utils.execute_command do |runner|
|
Discourse::Utils.execute_command do |runner|
|
||||||
expect(runner.exec("pwd").strip).to eq(Rails.root.to_s)
|
expect(runner.exec("pwd").strip).to eq(Rails.root.to_s)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user