DEV: Add both safe and unsafe Discourse.store.download methods (#21498)

* DEV: Add both safe and unsafe Discourse.store.download methods

* DEV: Update call sites that can use the safe store download method
This commit is contained in:
Ted Johansson 2023-05-11 17:27:27 +08:00 committed by GitHub
parent 4e846b69c4
commit b837459e1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 41 deletions

View File

@ -55,13 +55,8 @@ class OptimizedImage < ActiveRecord::Base
if original_path.blank? if original_path.blank?
# download is protected with a DistributedMutex # download is protected with a DistributedMutex
external_copy = external_copy = Discourse.store.download_safe(upload)
begin original_path = external_copy&.path
Discourse.store.download(upload)
rescue StandardError
nil
end
original_path = external_copy.try(:path)
end end
lock(upload.id, width, height) do lock(upload.id, width, height) do

View File

@ -189,13 +189,8 @@ class ThemeField < ActiveRecord::Base
end end
if Discourse.store.external? if Discourse.store.external?
external_copy = external_copy = Discourse.store.download_safe(upload)
begin path = external_copy&.path
Discourse.store.download(upload)
rescue StandardError
nil
end
path = external_copy.try(:path)
else else
path = Discourse.store.path_for(upload) path = Discourse.store.path_for(upload)
end end

View File

@ -160,13 +160,8 @@ class Upload < ActiveRecord::Base
# this is relatively cheap once cached # this is relatively cheap once cached
original_path = Discourse.store.path_for(self) original_path = Discourse.store.path_for(self)
if original_path.blank? if original_path.blank?
external_copy = external_copy = Discourse.store.download_safe(self)
begin original_path = external_copy&.path
Discourse.store.download(self)
rescue StandardError
nil
end
original_path = external_copy.try(:path)
end end
image_info = image_info =
@ -361,13 +356,7 @@ class Upload < ActiveRecord::Base
if local? if local?
Discourse.store.path_for(self) Discourse.store.path_for(self)
else else
begin Discourse.store.download_safe(self)&.path
Discourse.store.download(self)&.path
rescue OpenURI::HTTPError => e
# Some issue with downloading the image from a remote store.
# Assume the upload is broken and save an empty string to prevent re-evaluation
nil
end
end end
if local_path.nil? if local_path.nil?

View File

@ -1,6 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
module FileStore module FileStore
class DownloadError < StandardError
end
class BaseStore class BaseStore
UPLOAD_PATH_REGEX ||= %r{/(original/\d+X/.*)} UPLOAD_PATH_REGEX ||= %r{/(original/\d+X/.*)}
OPTIMIZED_IMAGE_PATH_REGEX ||= %r{/(optimized/\d+X/.*)} OPTIMIZED_IMAGE_PATH_REGEX ||= %r{/(optimized/\d+X/.*)}
@ -88,7 +91,26 @@ module FileStore
not_implemented not_implemented
end end
def download(object, max_file_size_kb: nil) # TODO: Remove when #download becomes the canonical safe version.
def download_safe(*, **)
download(*, **, print_deprecation: false)
rescue StandardError
nil
end
def download!(*, **)
download(*, **, print_deprecation: false)
rescue StandardError
raise DownloadError
end
def download(object, max_file_size_kb: nil, print_deprecation: true)
Discourse.deprecate(<<~MESSAGE, output_in_test: true) if print_deprecation
In a future version `FileStore#download` will no longer raise an error when the
download fails, and will instead return `nil`. If you need a method that raises
an error, use `FileStore#download!`, which raises a `FileStore::DownloadError`.
MESSAGE
DistributedMutex.synchronize("download_#{object.sha1}", validity: 3.minutes) do DistributedMutex.synchronize("download_#{object.sha1}", validity: 3.minutes) do
extension = extension =
File.extname( File.extname(

View File

@ -1200,13 +1200,7 @@ task "uploads:downsize" => :environment do
if upload.local? if upload.local?
Discourse.store.path_for(upload) Discourse.store.path_for(upload)
else else
( Discourse.store.download_safe(upload, max_file_size_kb: 100.megabytes)&.path
begin
Discourse.store.download(upload, max_file_size_kb: 100.megabytes)
rescue StandardError
nil
end
)&.path
end end
unless path unless path

View File

@ -189,16 +189,16 @@ RSpec.describe FileStore::BaseStore do
# Net::HTTP always returns binary ASCII-8BIT encoding. File.read auto-detects the encoding # Net::HTTP always returns binary ASCII-8BIT encoding. File.read auto-detects the encoding
# Make sure we File.read after downloading a file for consistency # Make sure we File.read after downloading a file for consistency
first_encoding = store.download(upload_s3).read.encoding first_encoding = store.download(upload_s3, print_deprecation: false).read.encoding
second_encoding = store.download(upload_s3).read.encoding second_encoding = store.download(upload_s3, print_deprecation: false).read.encoding
expect(first_encoding).to eq(Encoding::UTF_8) expect(first_encoding).to eq(Encoding::UTF_8)
expect(second_encoding).to eq(Encoding::UTF_8) expect(second_encoding).to eq(Encoding::UTF_8)
end end
it "should return the file" do it "should return the file" do
file = store.download(upload_s3) file = store.download(upload_s3, print_deprecation: false)
expect(file.class).to eq(File) expect(file.class).to eq(File)
end end
@ -210,7 +210,7 @@ RSpec.describe FileStore::BaseStore do
body: "Hello world", body: "Hello world",
) )
file = store.download(upload_s3) file = store.download(upload_s3, print_deprecation: true)
expect(file.class).to eq(File) expect(file.class).to eq(File)
end end
@ -223,9 +223,41 @@ RSpec.describe FileStore::BaseStore do
signed_url = Discourse.store.signed_url_for_path(upload_s3.url) signed_url = Discourse.store.signed_url_for_path(upload_s3.url)
stub_request(:get, signed_url).to_return(status: 200, body: "Hello world") stub_request(:get, signed_url).to_return(status: 200, body: "Hello world")
file = store.download(upload_s3) file = store.download(upload_s3, print_deprecation: false)
expect(file.class).to eq(File) expect(file.class).to eq(File)
end end
end end
describe "#download!" do
before do
setup_s3
stub_request(:get, upload_s3.url).to_return(status: 200, body: "Hello world")
end
let(:upload_s3) { Fabricate(:upload_s3) }
let(:store) { FileStore::BaseStore.new }
it "does not raise an error when download fails" do
FileHelper.stubs(:download).raises(OpenURI::HTTPError.new("400 error", anything))
expect { store.download!(upload_s3) }.to raise_error(FileStore::DownloadError)
end
end
describe "#download_safe" do
before do
setup_s3
stub_request(:get, upload_s3.url).to_return(status: 200, body: "Hello world")
end
let(:upload_s3) { Fabricate(:upload_s3) }
let(:store) { FileStore::BaseStore.new }
it "does not raise an error when download fails" do
FileHelper.stubs(:download).raises(OpenURI::HTTPError.new("400 error", anything))
expect(store.download_safe(upload_s3)).to eq(nil)
end
end
end end