diff --git a/Gemfile b/Gemfile index 5bf31ec3329..53d34033ff6 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,7 @@ end gem 'mail' gem 'mime-types', require: 'mime/types/columnar' +gem 'mini_mime' gem 'hiredis' gem 'redis', require: ["redis", "redis/connection/hiredis"] diff --git a/Gemfile.lock b/Gemfile.lock index 1214cbfb3a7..721a2c2670c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -160,6 +160,7 @@ GEM metaclass (0.0.4) method_source (0.8.2) mime-types (2.99.3) + mini_mime (0.1.3) mini_portile2 (2.2.0) mini_racer (0.1.9) libv8 (~> 5.3) @@ -432,6 +433,7 @@ DEPENDENCIES memory_profiler message_bus mime-types + mini_mime mini_racer minitest mocha @@ -491,4 +493,4 @@ DEPENDENCIES webmock BUNDLED WITH - 1.14.6 + 1.15.1 diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 7313165d353..295671c8345 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -1,3 +1,4 @@ +require "mini_mime" require_dependency 'upload_creator' class UploadsController < ApplicationController @@ -42,7 +43,7 @@ class UploadsController < ApplicationController if upload = Upload.find_by(sha1: params[:sha]) || Upload.find_by(id: params[:id], url: request.env["PATH_INFO"]) opts = { filename: upload.original_filename, - content_type: Rack::Mime.mime_type(File.extname(upload.original_filename)), + content_type: MiniMime.lookup_by_filename(upload.original_filename)&.content_type, } opts[:disposition] = "inline" if params[:inline] opts[:disposition] ||= "attachment" unless FileHelper.is_image?(upload.original_filename) diff --git a/app/jobs/regular/pull_hotlinked_images.rb b/app/jobs/regular/pull_hotlinked_images.rb index 316dfda9fcb..10dffa6a0ee 100644 --- a/app/jobs/regular/pull_hotlinked_images.rb +++ b/app/jobs/regular/pull_hotlinked_images.rb @@ -47,8 +47,13 @@ module Jobs if hotlinked if File.size(hotlinked.path) <= @max_size filename = File.basename(URI.parse(src).path) + filename << File.extname(hotlinked.path) unless filename["."] upload = UploadCreator.new(hotlinked, filename, origin: src).create_for(post.user_id) - downloaded_urls[src] = upload.url + if upload.persisted? + downloaded_urls[src] = upload.url + else + Rails.logger.info("Failed to pull hotlinked image for post: #{post_id}: #{src} - #{upload.errors.join("\n")}") + end else Rails.logger.info("Failed to pull hotlinked image for post: #{post_id}: #{src} - Image is bigger than #{@max_size}") end @@ -81,8 +86,7 @@ module Jobs rescue => e Rails.logger.info("Failed to pull hotlinked image: #{src} post:#{post_id}\n" + e.message + "\n" + e.backtrace.join("\n")) ensure - # close & delete the temp file - hotlinked && hotlinked.close! + hotlinked&.close! rescue nil end end diff --git a/lib/file_helper.rb b/lib/file_helper.rb index d87db228bbd..da1962b1be0 100644 --- a/lib/file_helper.rb +++ b/lib/file_helper.rb @@ -1,5 +1,6 @@ -require "open-uri" require "final_destination" +require "mini_mime" +require "open-uri" class FileHelper @@ -24,19 +25,26 @@ class FileHelper ).resolve return unless uri.present? + downloaded = uri.open("rb", read_timeout: read_timeout) + extension = File.extname(uri.path) + + if extension.blank? && downloaded.content_type.present? + ext = MiniMime.lookup_by_content_type(downloaded.content_type)&.extension + extension = "." + ext if ext.present? + end + tmp = Tempfile.new([tmp_file_name, extension]) File.open(tmp.path, "wb") do |f| - downloaded = uri.open("rb", read_timeout: read_timeout) while f.size <= max_file_size && data = downloaded.read(512.kilobytes) f.write(data) end - # tiny files are StringIO, no close! on them - downloaded.try(:close!) rescue nil end tmp + ensure + downloaded&.close! rescue nil end private diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb index b19963bb65d..265c7d53a83 100644 --- a/lib/file_store/s3_store.rb +++ b/lib/file_store/s3_store.rb @@ -1,4 +1,5 @@ require "uri" +require "mini_mime" require_dependency "file_store/base_store" require_dependency "s3_helper" require_dependency "file_helper" @@ -33,7 +34,7 @@ module FileStore # stored uploaded are public by default options = { acl: "public-read", - content_type: opts[:content_type].presence || Rack::Mime.mime_type(File.extname(filename)) + content_type: opts[:content_type].presence || MiniMime.lookup_by_filename(filename)&.content_type } # add a "content disposition" header for "attachments" options[:content_disposition] = "attachment; filename=\"#{filename}\"" unless FileHelper.is_image?(filename) diff --git a/spec/jobs/pull_hotlinked_images_spec.rb b/spec/jobs/pull_hotlinked_images_spec.rb index 220ed4e79cf..e1f5cb16d3e 100644 --- a/spec/jobs/pull_hotlinked_images_spec.rb +++ b/spec/jobs/pull_hotlinked_images_spec.rb @@ -4,16 +4,16 @@ require 'jobs/regular/pull_hotlinked_images' describe Jobs::PullHotlinkedImages do let(:image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat1.png" } + let(:png) { Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==") } before do - png = Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==") - stub_request(:get, image_url).to_return(body: png) + stub_request(:get, image_url).to_return(body: png, headers: { "Content-Type" => "image/png" }) stub_request(:head, image_url) SiteSetting.download_remote_images_to_local = true FastImage.expects(:size).returns([100, 100]).at_least_once end - it 'replaces image src' do + it 'replaces images' do post = Fabricate(:post, raw: "") Jobs::PullHotlinkedImages.new.execute(post_id: post.id) @@ -22,7 +22,7 @@ describe Jobs::PullHotlinkedImages do expect(post.raw).to match(/^") Jobs::PullHotlinkedImages.new.execute(post_id: post.id) @@ -31,6 +31,18 @@ describe Jobs::PullHotlinkedImages do expect(post.raw).to match(/^ "image/png" }) + stub_request(:head, extensionless_url) + post = Fabricate(:post, raw: "") + + Jobs::PullHotlinkedImages.new.execute(post_id: post.id) + post.reload + + expect(post.raw).to match(/^