class ActiveStorage::Download

Constants

BINARY_CONTENT_TYPE
CONTENT_TYPES_TO_RENDER_AS_BINARY

Sending .ai files as application/postscript to Safari opens them in a blank, grey screen. Downloading .ai as application/postscript files in Safari appends .ps to the extension. Sending HTML, SVG, XML and SWF files as binary closes XSS vulnerabilities. Sending JS files as binary avoids InvalidCrossOriginRequest without compromising security.

RFC5987_PARAMETER_ESCAPED_CHAR
TRADITIONAL_PARAMETER_ESCAPED_CHAR

Public Class Methods

new(stored_file) click to toggle source
# File lib/active_storage/download.rb, line 19
def initialize(stored_file)
  @stored_file = stored_file
end

Public Instance Methods

headers(force_attachment: false) click to toggle source
# File lib/active_storage/download.rb, line 23
def headers(force_attachment: false)
  {
    x_accel_redirect:    '/reproxy',
    x_reproxy_url:       reproxy_url,
    content_type:        content_type,
    content_disposition: content_disposition(force_attachment),
    x_frame_options:     'SAMEORIGIN'
  }
end

Private Instance Methods

content_disposition(force_attachment = false) click to toggle source
# File lib/active_storage/download.rb, line 46
def content_disposition(force_attachment = false)
  if force_attachment || content_type == BINARY_CONTENT_TYPE
    "attachment; #{escaped_filename}"
  else
    "inline; #{escaped_filename}"
  end
end
content_type() click to toggle source
# File lib/active_storage/download.rb, line 38
def content_type
  if @stored_file.content_type.in? CONTENT_TYPES_TO_RENDER_AS_BINARY
    BINARY_CONTENT_TYPE
  else
    @stored_file.content_type
  end
end
encode_ascii_filename(filename) click to toggle source
# File lib/active_storage/download.rb, line 66
def encode_ascii_filename(filename)
  # There is no reliable way to escape special or non-Latin characters
  # in a traditionally quoted Content-Disposition filename parameter.
  # Settle for transliterating to ASCII, then percent-escaping special
  # characters, excluding spaces.
  filename = I18n.transliterate(filename)
  filename = percent_escape(filename, TRADITIONAL_PARAMETER_ESCAPED_CHAR)
  %(filename="#{filename}")
end
encode_utf8_filename(filename) click to toggle source
# File lib/active_storage/download.rb, line 78
def encode_utf8_filename(filename)
  # RFC2231 filename parameters can simply be percent-escaped according
  # to RFC5987.
  filename = percent_escape(filename, RFC5987_PARAMETER_ESCAPED_CHAR)
  %(filename*=UTF-8''#{filename})
end
escaped_filename() click to toggle source

RFC2231 encoding for UTF-8 filenames, with an ASCII fallback first for unsupported browsers (IE < 9, perhaps others?). greenbytes.de/tech/tc2231/#encoding-2231-fb

# File lib/active_storage/download.rb, line 57
def escaped_filename
  filename = @stored_file.filename.sanitized
  ascii_filename = encode_ascii_filename(filename)
  utf8_filename = encode_utf8_filename(filename)
  "#{ascii_filename}; #{utf8_filename}"
end
percent_escape(string, pattern) click to toggle source
# File lib/active_storage/download.rb, line 85
def percent_escape(string, pattern)
  string.gsub(pattern) do |char|
    char.bytes.map { |byte| "%%%02X" % byte }.join("")
  end
end
reproxy_url() click to toggle source
# File lib/active_storage/download.rb, line 34
def reproxy_url
  @stored_file.depot_location.paths.first
end