aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage
diff options
context:
space:
mode:
Diffstat (limited to 'activestorage')
-rw-r--r--activestorage/app/controllers/active_storage/disk_controller.rb4
-rw-r--r--activestorage/app/jobs/active_storage/purge_job.rb6
-rw-r--r--activestorage/app/models/active_storage/attachment.rb10
-rw-r--r--activestorage/app/models/active_storage/blob.rb6
-rw-r--r--activestorage/app/models/active_storage/filename.rb38
-rw-r--r--activestorage/app/models/active_storage/filename/parameters.rb36
-rw-r--r--activestorage/app/models/active_storage/variant.rb2
-rw-r--r--activestorage/app/models/active_storage/variation.rb2
-rwxr-xr-xactivestorage/bin/test5
-rw-r--r--activestorage/db/migrate/20170806125915_create_active_storage_tables.rb6
-rw-r--r--activestorage/lib/active_storage/attached.rb6
-rw-r--r--activestorage/lib/active_storage/attached/macros.rb4
-rw-r--r--activestorage/lib/active_storage/attached/one.rb22
-rw-r--r--activestorage/lib/active_storage/service.rb2
-rw-r--r--activestorage/lib/active_storage/service/azure_storage_service.rb12
-rw-r--r--activestorage/lib/active_storage/service/disk_service.rb6
-rw-r--r--activestorage/lib/active_storage/service/gcs_service.rb4
-rw-r--r--activestorage/lib/active_storage/service/s3_service.rb4
-rw-r--r--activestorage/test/controllers/disk_controller_test.rb4
-rw-r--r--activestorage/test/database/create_users_migration.rb2
-rw-r--r--activestorage/test/models/attachments_test.rb25
-rw-r--r--activestorage/test/models/blob_test.rb2
-rw-r--r--activestorage/test/models/filename/parameters_test.rb32
-rw-r--r--activestorage/test/models/filename_test.rb18
-rw-r--r--activestorage/test/service/azure_storage_service_test.rb4
-rw-r--r--activestorage/test/service/disk_service_test.rb4
-rw-r--r--activestorage/test/service/gcs_service_test.rb2
-rw-r--r--activestorage/test/service/s3_service_test.rb2
-rw-r--r--activestorage/test/template/image_tag_test.rb6
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