From 63395aba5a96cf7f915ac97c2ac1c42f58a9a850 Mon Sep 17 00:00:00 2001 From: George Claghorn Date: Mon, 21 Aug 2017 16:34:18 -0400 Subject: Encode Content-Disposition filenames according to RFC 2231 Closes #30134. --- activestorage/app/models/active_storage/blob.rb | 2 +- .../app/models/active_storage/filename.rb | 4 +++ .../models/active_storage/filename/parameters.rb | 34 ++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 activestorage/app/models/active_storage/filename/parameters.rb (limited to 'activestorage/app') 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 -- cgit v1.2.3