aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage/app
diff options
context:
space:
mode:
authorGeorge Claghorn <george@basecamp.com>2017-08-21 16:34:18 -0400
committerGeorge Claghorn <george@basecamp.com>2017-08-21 18:23:15 -0400
commit63395aba5a96cf7f915ac97c2ac1c42f58a9a850 (patch)
tree1bfa3b8e10b09b846bc82535e1d4080a6d4270a9 /activestorage/app
parent2fb658d1e39364926aebd09f264d395bd1863e56 (diff)
downloadrails-63395aba5a96cf7f915ac97c2ac1c42f58a9a850.tar.gz
rails-63395aba5a96cf7f915ac97c2ac1c42f58a9a850.tar.bz2
rails-63395aba5a96cf7f915ac97c2ac1c42f58a9a850.zip
Encode Content-Disposition filenames according to RFC 2231
Closes #30134.
Diffstat (limited to 'activestorage/app')
-rw-r--r--activestorage/app/models/active_storage/blob.rb2
-rw-r--r--activestorage/app/models/active_storage/filename.rb4
-rw-r--r--activestorage/app/models/active_storage/filename/parameters.rb34
3 files changed, 39 insertions, 1 deletions
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index 2e627ef118..664a53a778 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -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}\"", 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 df21078718..c2ad5c844c 100644
--- a/activestorage/app/models/active_storage/filename.rb
+++ b/activestorage/app/models/active_storage/filename.rb
@@ -34,6 +34,10 @@ class ActiveStorage::Filename
@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..f8f54ba8eb
--- /dev/null
+++ b/activestorage/app/models/active_storage/filename/parameters.rb
@@ -0,0 +1,34 @@
+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