2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-05-23 13:31:20 -04:00
|
|
|
require "final_destination"
|
2017-06-13 07:27:05 -04:00
|
|
|
require "mini_mime"
|
|
|
|
require "open-uri"
|
2014-04-22 11:11:06 -04:00
|
|
|
|
2014-04-14 16:55:57 -04:00
|
|
|
class FileHelper
|
2017-09-27 19:00:13 -04:00
|
|
|
def self.log(log_level, message)
|
2017-09-27 19:07:43 -04:00
|
|
|
Rails.logger.public_send(
|
|
|
|
log_level,
|
|
|
|
"#{RailsMultisite::ConnectionManagement.current_db}: #{message}",
|
|
|
|
)
|
|
|
|
end
|
2017-09-27 19:00:13 -04:00
|
|
|
|
2018-09-09 22:22:45 -04:00
|
|
|
def self.is_supported_image?(filename)
|
2020-12-30 08:08:02 -05:00
|
|
|
filename.match?(supported_images_regexp)
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
2022-02-14 00:48:27 -05:00
|
|
|
def self.is_supported_video?(filename)
|
|
|
|
filename.match?(supported_video_regexp)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.is_supported_audio?(filename)
|
|
|
|
filename.match?(supported_audio_regexp)
|
|
|
|
end
|
|
|
|
|
2020-07-08 23:31:48 -04:00
|
|
|
def self.is_inline_image?(filename)
|
2020-12-30 08:08:02 -05:00
|
|
|
filename.match?(inline_images_regexp)
|
2020-07-08 23:31:48 -04:00
|
|
|
end
|
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
def self.is_supported_media?(filename)
|
2020-12-30 08:08:02 -05:00
|
|
|
filename.match?(supported_media_regexp)
|
2019-11-17 20:25:42 -05:00
|
|
|
end
|
|
|
|
|
2022-02-14 00:48:27 -05:00
|
|
|
def self.is_supported_playable_media?(filename)
|
|
|
|
filename.match?(supported_playable_media_regexp)
|
|
|
|
end
|
|
|
|
|
2017-09-28 02:35:27 -04:00
|
|
|
class FakeIO
|
|
|
|
attr_accessor :status
|
|
|
|
end
|
|
|
|
|
2017-05-24 13:46:57 -04:00
|
|
|
def self.download(
|
|
|
|
url,
|
|
|
|
max_file_size:,
|
|
|
|
tmp_file_name:,
|
|
|
|
follow_redirect: false,
|
|
|
|
read_timeout: 5,
|
2017-09-27 21:32:26 -04:00
|
|
|
skip_rate_limit: false,
|
2018-08-17 04:52:55 -04:00
|
|
|
verbose: false,
|
2019-05-27 20:28:57 -04:00
|
|
|
validate_uri: true,
|
2023-08-22 21:18:33 -04:00
|
|
|
retain_on_max_file_size_exceeded: false,
|
|
|
|
include_port_in_host_header: false
|
2018-08-17 04:52:55 -04:00
|
|
|
)
|
2017-05-15 15:32:55 -04:00
|
|
|
url = "https:" + url if url.start_with?("//")
|
2023-01-20 13:52:49 -05:00
|
|
|
raise Discourse::InvalidParameters.new(:url) unless url =~ %r{\Ahttps?://}
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2018-02-24 06:35:57 -05:00
|
|
|
tmp = nil
|
|
|
|
|
|
|
|
fd =
|
|
|
|
FinalDestination.new(
|
2017-05-24 13:46:57 -04:00
|
|
|
url,
|
2019-05-27 20:28:57 -04:00
|
|
|
max_redirects: follow_redirect ? 5 : 0,
|
2017-10-31 12:03:03 -04:00
|
|
|
skip_rate_limit: skip_rate_limit,
|
2019-05-27 20:28:57 -04:00
|
|
|
verbose: verbose,
|
2021-05-03 03:21:11 -04:00
|
|
|
validate_uri: validate_uri,
|
|
|
|
timeout: read_timeout,
|
2023-08-22 21:18:33 -04:00
|
|
|
include_port_in_host_header: include_port_in_host_header,
|
2017-09-28 02:35:27 -04:00
|
|
|
)
|
2017-06-13 07:27:05 -04:00
|
|
|
|
2018-02-24 06:35:57 -05:00
|
|
|
fd.get do |response, chunk, uri|
|
|
|
|
if tmp.nil?
|
|
|
|
# error handling
|
|
|
|
if uri.blank?
|
|
|
|
if response.code.to_i >= 400
|
|
|
|
# attempt error API compatibility
|
|
|
|
io = FakeIO.new
|
|
|
|
io.status = [response.code, ""]
|
2022-06-17 06:50:30 -04:00
|
|
|
raise OpenURI::HTTPError.new("#{response.code} Error", io)
|
2018-02-24 06:35:57 -05:00
|
|
|
else
|
|
|
|
log(:error, "FinalDestination did not work for: #{url}") if verbose
|
|
|
|
throw :done
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-07-29 22:48:44 -04:00
|
|
|
if response.content_type.present?
|
2018-02-24 06:35:57 -05:00
|
|
|
ext = MiniMime.lookup_by_content_type(response.content_type)&.extension
|
|
|
|
ext = "jpg" if ext == "jpe"
|
|
|
|
tmp_file_ext = "." + ext if ext.present?
|
|
|
|
end
|
|
|
|
|
2018-07-29 22:48:44 -04:00
|
|
|
tmp_file_ext ||= File.extname(uri.path)
|
2018-02-24 06:35:57 -05:00
|
|
|
tmp = Tempfile.new([tmp_file_name, tmp_file_ext])
|
|
|
|
tmp.binmode
|
|
|
|
end
|
2018-02-22 12:15:42 -05:00
|
|
|
|
2018-02-24 06:35:57 -05:00
|
|
|
tmp.write(chunk)
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2018-08-17 04:17:58 -04:00
|
|
|
if tmp.size > max_file_size
|
2018-08-17 04:52:55 -04:00
|
|
|
unless retain_on_max_file_size_exceeded
|
|
|
|
tmp.close
|
|
|
|
tmp = nil
|
|
|
|
end
|
|
|
|
|
2018-08-17 04:17:58 -04:00
|
|
|
throw :done
|
|
|
|
end
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|
|
|
|
|
2018-02-24 06:35:57 -05:00
|
|
|
tmp&.rewind
|
2014-04-14 16:55:57 -04:00
|
|
|
tmp
|
|
|
|
end
|
|
|
|
|
2019-01-02 01:19:52 -05:00
|
|
|
def self.optimize_image!(filename, allow_pngquant: false)
|
2019-01-08 20:28:06 -05:00
|
|
|
image_optim(
|
|
|
|
allow_pngquant: allow_pngquant,
|
|
|
|
strip_image_metadata: SiteSetting.strip_image_metadata,
|
|
|
|
).optimize_image!(filename)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.image_optim(allow_pngquant: false, strip_image_metadata: true)
|
|
|
|
# memoization is critical, initializing an ImageOptim object is very expensive
|
|
|
|
# sometimes up to 200ms searching for binaries and looking at versions
|
|
|
|
memoize("image_optim", allow_pngquant, strip_image_metadata) do
|
|
|
|
pngquant_options = false
|
|
|
|
pngquant_options = { allow_lossy: true } if allow_pngquant
|
|
|
|
|
|
|
|
ImageOptim.new(
|
|
|
|
# GLOBAL
|
|
|
|
timeout: 15,
|
|
|
|
skip_missing_workers: true,
|
|
|
|
# PNG
|
2021-11-22 12:16:35 -05:00
|
|
|
oxipng: {
|
|
|
|
level: 3,
|
|
|
|
strip: strip_image_metadata,
|
|
|
|
},
|
|
|
|
optipng: false,
|
2019-01-08 20:28:06 -05:00
|
|
|
advpng: false,
|
|
|
|
pngcrush: false,
|
|
|
|
pngout: false,
|
|
|
|
pngquant: pngquant_options,
|
|
|
|
# JPG
|
|
|
|
jpegoptim: {
|
|
|
|
strip: strip_image_metadata ? "all" : "none",
|
|
|
|
},
|
|
|
|
jpegtran: false,
|
|
|
|
jpegrecompress: false,
|
2021-04-09 08:52:06 -04:00
|
|
|
# Skip looking for gifsicle, svgo binaries
|
|
|
|
gifsicle: false,
|
2021-10-14 17:17:47 -04:00
|
|
|
svgo: false,
|
2019-01-08 20:28:06 -05:00
|
|
|
)
|
2019-01-02 01:19:52 -05:00
|
|
|
end
|
2019-01-08 20:28:06 -05:00
|
|
|
end
|
2019-01-02 01:19:52 -05:00
|
|
|
|
2019-01-08 20:28:06 -05:00
|
|
|
def self.memoize(*args)
|
|
|
|
(@memoized ||= {})[args] ||= yield
|
2017-07-25 05:48:39 -04:00
|
|
|
end
|
|
|
|
|
2019-07-30 23:16:03 -04:00
|
|
|
def self.supported_gravatar_extensions
|
|
|
|
@@supported_gravatar_images ||= Set.new(%w[jpg jpeg png gif])
|
|
|
|
end
|
|
|
|
|
2018-09-09 22:03:44 -04:00
|
|
|
def self.supported_images
|
2023-05-24 15:13:36 -04:00
|
|
|
@@supported_images ||= Set.new %w[jpg jpeg png gif svg ico webp avif]
|
2017-07-25 05:48:39 -04:00
|
|
|
end
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2020-07-08 23:31:48 -04:00
|
|
|
def self.inline_images
|
|
|
|
# SVG cannot safely be shown as a document
|
|
|
|
@@inline_images ||= supported_images - %w[svg]
|
|
|
|
end
|
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
def self.supported_audio
|
2022-02-14 00:48:27 -05:00
|
|
|
@@supported_audio ||= Set.new %w[mp3 ogg oga opus wav m4a m4b m4p m4r aac flac]
|
2019-11-17 20:25:42 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.supported_video
|
2022-02-14 00:48:27 -05:00
|
|
|
@@supported_video ||= Set.new %w[mov mp4 webm ogv m4v 3gp avi mpeg]
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.supported_video_regexp
|
2023-01-20 13:52:49 -05:00
|
|
|
@@supported_video_regexp ||= /\.(#{supported_video.to_a.join("|")})\z/i
|
2022-02-14 00:48:27 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.supported_audio_regexp
|
2023-01-20 13:52:49 -05:00
|
|
|
@@supported_audio_regexp ||= /\.(#{supported_audio.to_a.join("|")})\z/i
|
2019-11-17 20:25:42 -05:00
|
|
|
end
|
|
|
|
|
2018-09-09 22:22:45 -04:00
|
|
|
def self.supported_images_regexp
|
2023-01-20 13:52:49 -05:00
|
|
|
@@supported_images_regexp ||= /\.(#{supported_images.to_a.join("|")})\z/i
|
2017-07-25 05:48:39 -04:00
|
|
|
end
|
2014-04-14 16:55:57 -04:00
|
|
|
|
2020-07-08 23:31:48 -04:00
|
|
|
def self.inline_images_regexp
|
2023-01-20 13:52:49 -05:00
|
|
|
@@inline_images_regexp ||= /\.(#{inline_images.to_a.join("|")})\z/i
|
2020-07-08 23:31:48 -04:00
|
|
|
end
|
|
|
|
|
2019-11-17 20:25:42 -05:00
|
|
|
def self.supported_media_regexp
|
2020-12-30 08:08:02 -05:00
|
|
|
@@supported_media_regexp ||=
|
|
|
|
begin
|
|
|
|
media = supported_images | supported_audio | supported_video
|
2023-01-20 13:52:49 -05:00
|
|
|
/\.(#{media.to_a.join("|")})\z/i
|
2020-12-30 08:08:02 -05:00
|
|
|
end
|
2019-11-17 20:25:42 -05:00
|
|
|
end
|
2022-02-14 00:48:27 -05:00
|
|
|
|
|
|
|
def self.supported_playable_media_regexp
|
|
|
|
@@supported_playable_media_regexp ||=
|
|
|
|
begin
|
|
|
|
media = supported_audio | supported_video
|
2023-01-20 13:52:49 -05:00
|
|
|
/\.(#{media.to_a.join("|")})\z/i
|
2022-02-14 00:48:27 -05:00
|
|
|
end
|
|
|
|
end
|
2014-04-14 16:55:57 -04:00
|
|
|
end
|