aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage/app
diff options
context:
space:
mode:
Diffstat (limited to 'activestorage/app')
-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
8 files changed, 76 insertions, 28 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: