diff options
Diffstat (limited to 'activestorage')
29 files changed, 211 insertions, 65 deletions
diff --git a/activestorage/app/controllers/active_storage/disk_controller.rb b/activestorage/app/controllers/active_storage/disk_controller.rb index b10d4e2cac..41e6d61bff 100644 --- a/activestorage/app/controllers/active_storage/disk_controller.rb +++ b/activestorage/app/controllers/active_storage/disk_controller.rb @@ -8,7 +8,7 @@ class ActiveStorage::DiskController < ActionController::Base def show if key = decode_verified_key send_data disk_service.download(key), - filename: params[:filename], disposition: disposition_param, content_type: params[:content_type] + disposition: disposition_param, content_type: params[:content_type] else head :not_found end @@ -39,7 +39,7 @@ class ActiveStorage::DiskController < ActionController::Base end def disposition_param - params[:disposition].presence_in(%w( inline attachment )) || "inline" + params[:disposition].presence || "inline" end diff --git a/activestorage/app/jobs/active_storage/purge_job.rb b/activestorage/app/jobs/active_storage/purge_job.rb index 369c07929d..990ab27c9f 100644 --- a/activestorage/app/jobs/active_storage/purge_job.rb +++ b/activestorage/app/jobs/active_storage/purge_job.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -# Provides delayed purging of attachments or blobs using their +purge_later+ method. +# Provides delayed purging of ActiveStorage::Blob records via ActiveStorage::Blob#purge_later. class ActiveStorage::PurgeJob < ActiveJob::Base # FIXME: Limit this to a custom ActiveStorage error retry_on StandardError - def perform(attachment_or_blob) - attachment_or_blob.purge + def perform(blob) + blob.purge end end diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb index ad43845e4e..2a1c20b7db 100644 --- a/activestorage/app/models/active_storage/attachment.rb +++ b/activestorage/app/models/active_storage/attachment.rb @@ -14,17 +14,15 @@ class ActiveStorage::Attachment < ActiveRecord::Base delegate_missing_to :blob - # Purging an attachment will purge the blob (delete the file on the service, then destroy the record) - # and then destroy the attachment itself. + # Synchronously purges the blob (deletes it from the configured service) and destroys the attachment. def purge blob.purge destroy end - # Purging an attachment means purging the blob, which means talking to the service, which means - # talking over the Internet. Whenever you're doing that, it's a good idea to put that work in a job, - # so it doesn't hold up other operations. That's what +purge_later+ provides. + # Destroys the attachment and asynchronously purges the blob (deletes it from the configured service). def purge_later - ActiveStorage::PurgeJob.perform_later(self) + blob.purge_later + destroy end end diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb index 9f2ed1e5ac..e6cf08ce83 100644 --- a/activestorage/app/models/active_storage/blob.rb +++ b/activestorage/app/models/active_storage/blob.rb @@ -3,8 +3,8 @@ # A blob is a record that contains the metadata about a file and a key for where that file resides on the service. # Blobs can be created in two ways: # -# 1) Subsequent to the file being uploaded server-side to the service via <tt>create_after_upload!</tt>. -# 2) Ahead of the file being directly uploaded client-side to the service via <tt>create_before_direct_upload!</tt>. +# 1. Subsequent to the file being uploaded server-side to the service via <tt>create_after_upload!</tt>. +# 2. Ahead of the file being directly uploaded client-side to the service via <tt>create_before_direct_upload!</tt>. # # The first option doesn't require any client-side JavaScript integration, and can be used by any other back-end # service that deals with files. The second option is faster, since you're not using your own server as a staging @@ -127,7 +127,7 @@ class ActiveStorage::Blob < ActiveRecord::Base # Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And # it allows permanent URLs that redirect to the +service_url+ to be cached in the view. def service_url(expires_in: 5.minutes, disposition: :inline) - service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type + service.url key, expires_in: expires_in, disposition: "#{disposition}; #{filename.parameters}", filename: filename, content_type: content_type end # Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be diff --git a/activestorage/app/models/active_storage/filename.rb b/activestorage/app/models/active_storage/filename.rb index 6a9889addf..dead6b6d33 100644 --- a/activestorage/app/models/active_storage/filename.rb +++ b/activestorage/app/models/active_storage/filename.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Encapsulates a string representing a filename to provide convenience access to parts of it and a sanitized version. -# This is what's returned by `ActiveStorage::Blob#filename`. A Filename instance is comparable so it can be used for sorting. +# This is what's returned by ActiveStorage::Blob#filename. A Filename instance is comparable so it can be used for sorting. class ActiveStorage::Filename include Comparable @@ -9,29 +9,43 @@ class ActiveStorage::Filename @filename = filename end - # Filename.new("racecar.jpg").extname # => ".jpg" - def extname - File.extname(@filename) + # Returns the basename of the filename. + # + # ActiveStorage::Filename.new("racecar.jpg").base # => "racecar" + def base + File.basename @filename, extension_with_delimiter end - # Filename.new("racecar.jpg").extension # => "jpg" - def extension - extname.from(1) + # Returns the extension with delimiter of the filename. + # + # ActiveStorage::Filename.new("racecar.jpg").extension_with_delimiter # => ".jpg" + def extension_with_delimiter + File.extname @filename end - # Filename.new("racecar.jpg").base # => "racecar" - def base - File.basename(@filename, extname) + # Returns the extension without delimiter of the filename. + # + # ActiveStorage::Filename.new("racecar.jpg").extension_without_delimiter # => "jpg" + def extension_without_delimiter + extension_with_delimiter.from(1).to_s end - # Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg" - # Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg" + alias_method :extension, :extension_without_delimiter + + # Returns the sanitized filename. + # + # ActiveStorage::Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg" + # ActiveStorage::Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg" # # ...and any other character unsafe for URLs or storage is converted or stripped. def sanitized @filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-") end + def parameters + Parameters.new self + end + # Returns the sanitized version of the filename. def to_s sanitized.to_s diff --git a/activestorage/app/models/active_storage/filename/parameters.rb b/activestorage/app/models/active_storage/filename/parameters.rb new file mode 100644 index 0000000000..58ce198d38 --- /dev/null +++ b/activestorage/app/models/active_storage/filename/parameters.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class ActiveStorage::Filename::Parameters + attr_reader :filename + + def initialize(filename) + @filename = filename + end + + def combined + "#{ascii}; #{utf8}" + end + + TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/ + + def ascii + 'filename="' + percent_escape(I18n.transliterate(filename.sanitized), TRADITIONAL_ESCAPED_CHAR) + '"' + end + + RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/ + + def utf8 + "filename*=UTF-8''" + percent_escape(filename.sanitized, RFC_5987_ESCAPED_CHAR) + end + + def to_s + combined + end + + private + def percent_escape(string, pattern) + string.gsub(pattern) do |char| + char.bytes.map { |byte| "%%%02X" % byte }.join + end + end +end diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb index 40648a27f7..02bf32b352 100644 --- a/activestorage/app/models/active_storage/variant.rb +++ b/activestorage/app/models/active_storage/variant.rb @@ -4,7 +4,7 @@ # These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the # original. # -# Variants rely on {MiniMagick}(https://github.com/minimagick/minimagick) for the actual transformations +# Variants rely on {MiniMagick}[https://github.com/minimagick/minimagick] for the actual transformations # of the file, so you must add <tt>gem "mini_magick"</tt> to your Gemfile if you wish to use variants. # # Note that to create a variant it's necessary to download the entire blob file from the service and load it diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb index f657d90db4..bf269e2a8f 100644 --- a/activestorage/app/models/active_storage/variation.rb +++ b/activestorage/app/models/active_storage/variation.rb @@ -3,7 +3,7 @@ require "active_support/core_ext/object/inclusion" # A set of transformations that can be applied to a blob to create a variant. This class is exposed via -# the `ActiveStorage::Blob#variant` method and should rarely be used directly. +# the ActiveStorage::Blob#variant method and should rarely be used directly. # # In case you do need to use this directly, it's instantiated using a hash of transformations where # the key is the command and the value is the arguments. Example: diff --git a/activestorage/bin/test b/activestorage/bin/test new file mode 100755 index 0000000000..c53377cc97 --- /dev/null +++ b/activestorage/bin/test @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +COMPONENT_ROOT = File.expand_path("..", __dir__) +require_relative "../../tools/test" diff --git a/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb index d333b6bf9c..9e31e3966a 100644 --- a/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb +++ b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb @@ -1,13 +1,11 @@ -# frozen_string_literal: true - -class CreateActiveStorageTables < ActiveRecord::Migration[5.1] +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] def change create_table :active_storage_blobs do |t| t.string :key, null: false t.string :filename, null: false t.string :content_type t.text :metadata - t.integer :byte_size, null: false + t.bigint :byte_size, null: false t.string :checksum, null: false t.datetime :created_at, null: false diff --git a/activestorage/lib/active_storage/attached.rb b/activestorage/lib/active_storage/attached.rb index 9c4b6d5d1d..c08fd56652 100644 --- a/activestorage/lib/active_storage/attached.rb +++ b/activestorage/lib/active_storage/attached.rb @@ -8,10 +8,10 @@ module ActiveStorage # Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many # classes that both provide proxy access to the blob association for a record. class Attached - attr_reader :name, :record + attr_reader :name, :record, :dependent - def initialize(name, record) - @name, @record = name, record + def initialize(name, record, dependent:) + @name, @record, @dependent = name, record, dependent end private diff --git a/activestorage/lib/active_storage/attached/macros.rb b/activestorage/lib/active_storage/attached/macros.rb index f3879ee2e3..35a081adc4 100644 --- a/activestorage/lib/active_storage/attached/macros.rb +++ b/activestorage/lib/active_storage/attached/macros.rb @@ -26,7 +26,7 @@ module ActiveStorage def has_one_attached(name, dependent: :purge_later) class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name} - @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self) + @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"}) end CODE @@ -65,7 +65,7 @@ module ActiveStorage def has_many_attached(name, dependent: :purge_later) class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name} - @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self) + @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"}) end CODE diff --git a/activestorage/lib/active_storage/attached/one.rb b/activestorage/lib/active_storage/attached/one.rb index 2e5831348e..ac90f32d95 100644 --- a/activestorage/lib/active_storage/attached/one.rb +++ b/activestorage/lib/active_storage/attached/one.rb @@ -21,11 +21,14 @@ module ActiveStorage # person.avatar.attach(io: File.open("~/face.jpg"), filename: "face.jpg", content_type: "image/jpg") # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object def attach(attachable) - write_attachment \ - ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) + if attached? && dependent == :purge_later + replace attachable + else + write_attachment create_attachment_from(attachable) + end end - # Returns true if an attachment has been made. + # Returns +true+ if an attachment has been made. # # class User < ActiveRecord::Base # has_one_attached :avatar @@ -53,6 +56,19 @@ module ActiveStorage end private + def replace(attachable) + blob.tap do + transaction do + destroy + write_attachment create_attachment_from(attachable) + end + end.purge_later + end + + def create_attachment_from(attachable) + ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable)) + end + def write_attachment(attachment) record.public_send("#{name}_attachment=", attachment) end diff --git a/activestorage/lib/active_storage/service.rb b/activestorage/lib/active_storage/service.rb index ce736b8728..b80fdea1ab 100644 --- a/activestorage/lib/active_storage/service.rb +++ b/activestorage/lib/active_storage/service.rb @@ -77,7 +77,7 @@ module ActiveStorage raise NotImplementedError end - # Return true if a file exists at the +key+. + # Return +true+ if a file exists at the +key+. def exist?(key) raise NotImplementedError end diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb index d0ee2273c5..895cc9c2f1 100644 --- a/activestorage/lib/active_storage/service/azure_storage_service.rb +++ b/activestorage/lib/active_storage/service/azure_storage_service.rb @@ -59,12 +59,16 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:, content_type:) + def url(key, expires_in:, filename:, disposition:, content_type:) instrument :url, key do |payload| base_url = url_for(key) - generated_url = signer.signed_uri(URI(base_url), false, permissions: "r", - expiry: format_expiry(expires_in), content_type: content_type, - content_disposition: "#{disposition}; filename=\"#{filename}\"").to_s + generated_url = signer.signed_uri( + URI(base_url), false, + permissions: "r", + expiry: format_expiry(expires_in), + content_disposition: disposition, + content_type: content_type + ).to_s payload[:url] = generated_url diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb index 1d121dcb6c..f600753a08 100644 --- a/activestorage/lib/active_storage/service/disk_service.rb +++ b/activestorage/lib/active_storage/service/disk_service.rb @@ -56,7 +56,7 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:, content_type:) + def url(key, expires_in:, filename:, disposition:, content_type:) instrument :url, key do |payload| verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key) @@ -64,9 +64,9 @@ module ActiveStorage if defined?(Rails.application) Rails.application.routes.url_helpers.rails_disk_service_path \ verified_key_with_expiration, - disposition: disposition, filename: filename, content_type: content_type + filename: filename, disposition: disposition, content_type: content_type else - "/rails/active_storage/disk/#{verified_key_with_expiration}/#{filename}?disposition=#{disposition}&content_type=#{content_type}" + "/rails/active_storage/disk/#{verified_key_with_expiration}/#{filename}?content_type=#{content_type}&disposition=#{disposition}" end payload[:url] = generated_url diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb index e656f2c69f..ebf24a36d7 100644 --- a/activestorage/lib/active_storage/service/gcs_service.rb +++ b/activestorage/lib/active_storage/service/gcs_service.rb @@ -47,10 +47,10 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:, content_type:) + def url(key, expires_in:, filename:, content_type:, disposition:) instrument :url, key do |payload| generated_url = file_for(key).signed_url expires: expires_in, query: { - "response-content-disposition" => "#{disposition}; filename=\"#{filename}\"", + "response-content-disposition" => disposition, "response-content-type" => content_type } diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb index bef2ecbea9..e5d1e56e05 100644 --- a/activestorage/lib/active_storage/service/s3_service.rb +++ b/activestorage/lib/active_storage/service/s3_service.rb @@ -52,10 +52,10 @@ module ActiveStorage end end - def url(key, expires_in:, disposition:, filename:, content_type:) + def url(key, expires_in:, filename:, disposition:, content_type:) instrument :url, key do |payload| generated_url = object_for(key).presigned_url :get, expires_in: expires_in, - response_content_disposition: "#{disposition}; filename=\"#{filename}\"", + response_content_disposition: disposition, response_content_type: content_type payload[:url] = generated_url diff --git a/activestorage/test/controllers/disk_controller_test.rb b/activestorage/test/controllers/disk_controller_test.rb index 53a086f214..940dbf5918 100644 --- a/activestorage/test/controllers/disk_controller_test.rb +++ b/activestorage/test/controllers/disk_controller_test.rb @@ -8,7 +8,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest blob = create_blob get blob.service_url - assert_equal "inline; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] + assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end @@ -16,7 +16,7 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest blob = create_blob get blob.service_url(disposition: :attachment) - assert_equal "attachment; filename=\"#{blob.filename.base}\"", @response.headers["Content-Disposition"] + assert_equal "attachment; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", @response.headers["Content-Disposition"] assert_equal "text/plain", @response.headers["Content-Type"] end diff --git a/activestorage/test/database/create_users_migration.rb b/activestorage/test/database/create_users_migration.rb index 317daa87b5..fdba87cacf 100644 --- a/activestorage/test/database/create_users_migration.rb +++ b/activestorage/test/database/create_users_migration.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActiveStorageCreateUsers < ActiveRecord::Migration[5.1] +class ActiveStorageCreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| t.string :name diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb index 7cfd8683db..ac346c0087 100644 --- a/activestorage/test/models/attachments_test.rb +++ b/activestorage/test/models/attachments_test.rb @@ -31,6 +31,31 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase assert_equal "racecar.jpg", @user.avatar.filename.to_s end + test "replace attached blob" do + @user.avatar.attach create_blob(filename: "funky.jpg") + + perform_enqueued_jobs do + assert_no_difference -> { ActiveStorage::Blob.count } do + @user.avatar.attach create_blob(filename: "town.jpg") + end + end + + assert_equal "town.jpg", @user.avatar.filename.to_s + end + + test "replace attached blob unsuccessfully" do + @user.avatar.attach create_blob(filename: "funky.jpg") + + perform_enqueued_jobs do + assert_raises do + @user.avatar.attach nil + end + end + + assert_equal "funky.jpg", @user.reload.avatar.filename.to_s + assert ActiveStorage::Blob.service.exist?(@user.avatar.key) + end + test "access underlying associations of new blob" do @user.avatar.attach create_blob(filename: "funky.jpg") assert_equal @user, @user.avatar_attachment.record diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb index 99f2fb20b0..6e815997ba 100644 --- a/activestorage/test/models/blob_test.rb +++ b/activestorage/test/models/blob_test.rb @@ -50,7 +50,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase private def expected_url_for(blob, disposition: :inline) - query_string = { content_type: blob.content_type, disposition: disposition }.to_param + query_string = { content_type: blob.content_type, disposition: "#{disposition}; #{blob.filename.parameters}" }.to_param "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{blob.filename}?#{query_string}" end end diff --git a/activestorage/test/models/filename/parameters_test.rb b/activestorage/test/models/filename/parameters_test.rb new file mode 100644 index 0000000000..431be00639 --- /dev/null +++ b/activestorage/test/models/filename/parameters_test.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "test_helper" + +class ActiveStorage::Filename::ParametersTest < ActiveSupport::TestCase + test "parameterizing a Latin filename" do + filename = ActiveStorage::Filename.new("racecar.jpg") + + assert_equal %(filename="racecar.jpg"), filename.parameters.ascii + assert_equal "filename*=UTF-8''racecar.jpg", filename.parameters.utf8 + assert_equal "#{filename.parameters.ascii}; #{filename.parameters.utf8}", filename.parameters.combined + assert_equal filename.parameters.combined, filename.parameters.to_s + end + + test "parameterizing a Latin filename with accented characters" do + filename = ActiveStorage::Filename.new("råcëçâr.jpg") + + assert_equal %(filename="racecar.jpg"), filename.parameters.ascii + assert_equal "filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg", filename.parameters.utf8 + assert_equal "#{filename.parameters.ascii}; #{filename.parameters.utf8}", filename.parameters.combined + assert_equal filename.parameters.combined, filename.parameters.to_s + end + + test "parameterizing a non-Latin filename" do + filename = ActiveStorage::Filename.new("автомобиль.jpg") + + assert_equal %(filename="%3F%3F%3F%3F%3F%3F%3F%3F%3F%3F.jpg"), filename.parameters.ascii + assert_equal "filename*=UTF-8''%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg", filename.parameters.utf8 + assert_equal "#{filename.parameters.ascii}; #{filename.parameters.utf8}", filename.parameters.combined + assert_equal filename.parameters.combined, filename.parameters.to_s + end +end diff --git a/activestorage/test/models/filename_test.rb b/activestorage/test/models/filename_test.rb index f1e4a467ba..88405e41c0 100644 --- a/activestorage/test/models/filename_test.rb +++ b/activestorage/test/models/filename_test.rb @@ -3,6 +3,24 @@ require "test_helper" class ActiveStorage::FilenameTest < ActiveSupport::TestCase + test "base" do + assert_equal "racecar", ActiveStorage::Filename.new("racecar.jpg").base + assert_equal "race.car", ActiveStorage::Filename.new("race.car.jpg").base + assert_equal "racecar", ActiveStorage::Filename.new("racecar").base + end + + test "extension with delimiter" do + assert_equal ".jpg", ActiveStorage::Filename.new("racecar.jpg").extension_with_delimiter + assert_equal ".jpg", ActiveStorage::Filename.new("race.car.jpg").extension_with_delimiter + assert_equal "", ActiveStorage::Filename.new("racecar").extension_with_delimiter + end + + test "extension without delimiter" do + assert_equal "jpg", ActiveStorage::Filename.new("racecar.jpg").extension_without_delimiter + assert_equal "jpg", ActiveStorage::Filename.new("race.car.jpg").extension_without_delimiter + assert_equal "", ActiveStorage::Filename.new("racecar").extension_without_delimiter + end + test "sanitize" do "%$|:;/\t\r\n\\".each_char do |character| filename = ActiveStorage::Filename.new("foo#{character}bar.pdf") diff --git a/activestorage/test/service/azure_storage_service_test.rb b/activestorage/test/service/azure_storage_service_test.rb index dac154cf11..4729bdfbc5 100644 --- a/activestorage/test/service/azure_storage_service_test.rb +++ b/activestorage/test/service/azure_storage_service_test.rb @@ -11,9 +11,9 @@ if SERVICE_CONFIGURATIONS[:azure] test "signed URL generation" do url = @service.url(FIXTURE_KEY, expires_in: 5.minutes, - disposition: :inline, filename: "avatar.png", content_type: "image/png") + disposition: "inline; filename=\"avatar.png\"", filename: "avatar.png", content_type: "image/png") - assert_match(/(\S+)&rsct=image%2Fpng&rscd=inline%3B\+filename%3D%22avatar.png/, url) + assert_match(/(\S+)&rscd=inline%3B\+filename%3D%22avatar.png%22&rsct=image%2Fpng/, url) assert_match SERVICE_CONFIGURATIONS[:azure][:container], url end end diff --git a/activestorage/test/service/disk_service_test.rb b/activestorage/test/service/disk_service_test.rb index 835a3a2971..e07d1d88bc 100644 --- a/activestorage/test/service/disk_service_test.rb +++ b/activestorage/test/service/disk_service_test.rb @@ -8,7 +8,7 @@ class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase include ActiveStorage::Service::SharedServiceTests test "url generation" do - assert_match(/rails\/active_storage\/disk\/.*\/avatar\.png\?.+disposition=inline/, - @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: "avatar.png", content_type: "image/png")) + assert_match(/rails\/active_storage\/disk\/.*\/avatar\.png\?content_type=image%2Fpng&disposition=inline/, + @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: "inline; filename=\"avatar.png\"", filename: "avatar.png", content_type: "image/png")) end end diff --git a/activestorage/test/service/gcs_service_test.rb b/activestorage/test/service/gcs_service_test.rb index 5f53f61baf..f664cee90b 100644 --- a/activestorage/test/service/gcs_service_test.rb +++ b/activestorage/test/service/gcs_service_test.rb @@ -37,7 +37,7 @@ if SERVICE_CONFIGURATIONS[:gcs] "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" + "&response-content-type=text%2Fplain" - assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt", content_type: "text/plain") + assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: "inline; filename=\"test.txt\"", filename: "test.txt", content_type: "text/plain") end end end diff --git a/activestorage/test/service/s3_service_test.rb b/activestorage/test/service/s3_service_test.rb index 98f520741d..c07d6396b1 100644 --- a/activestorage/test/service/s3_service_test.rb +++ b/activestorage/test/service/s3_service_test.rb @@ -33,7 +33,7 @@ if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].pr test "signed URL generation" do url = @service.url(FIXTURE_KEY, expires_in: 5.minutes, - disposition: :inline, filename: "avatar.png", content_type: "image/png") + disposition: "inline; filename=\"avatar.png\"", filename: "avatar.png", content_type: "image/png") assert_match(/s3\.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png.*response-content-type=image%2Fpng/, url) assert_match SERVICE_CONFIGURATIONS[:s3][:bucket], url diff --git a/activestorage/test/template/image_tag_test.rb b/activestorage/test/template/image_tag_test.rb index 46dd97b3e9..dedc58452e 100644 --- a/activestorage/test/template/image_tag_test.rb +++ b/activestorage/test/template/image_tag_test.rb @@ -11,17 +11,17 @@ class ActiveStorage::ImageTagTest < ActionView::TestCase end test "blob" do - assert_dom_equal %(<img alt="Racecar" src="#{polymorphic_url @blob}" />), image_tag(@blob) + assert_dom_equal %(<img src="#{polymorphic_url @blob}" />), image_tag(@blob) end test "variant" do variant = @blob.variant(resize: "100x100") - assert_dom_equal %(<img alt="Racecar" src="#{polymorphic_url variant}" />), image_tag(variant) + assert_dom_equal %(<img src="#{polymorphic_url variant}" />), image_tag(variant) end test "attachment" do attachment = ActiveStorage::Attachment.new(blob: @blob) - assert_dom_equal %(<img alt="Racecar" src="#{polymorphic_url attachment}" />), image_tag(attachment) + assert_dom_equal %(<img src="#{polymorphic_url attachment}" />), image_tag(attachment) end test "error when attachment's empty" do |