blob: 74f69a9dfc8974cd85f78b3010bdd6a6b107758f (
plain) (
tree)
|
|
class ActiveFile::Download
# 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.
CONTENT_TYPES_TO_RENDER_AS_BINARY = %w(
text/html
text/javascript
image/svg+xml
application/postscript
application/x-shockwave-flash
text/xml
application/xml
application/xhtml+xml
)
BINARY_CONTENT_TYPE = 'application/octet-stream'
def initialize(stored_file)
@stored_file = stored_file
end
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
def reproxy_url
@stored_file.depot_location.paths.first
end
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
def content_disposition(force_attachment = false)
if force_attachment || content_type == BINARY_CONTENT_TYPE
"attachment; #{escaped_filename}"
else
"inline; #{escaped_filename}"
end
end
# RFC2231 encoding for UTF-8 filenames, with an ASCII fallback
# first for unsupported browsers (IE < 9, perhaps others?).
# http://greenbytes.de/tech/tc2231/#encoding-2231-fb
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
TRADITIONAL_PARAMETER_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/
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
RFC5987_PARAMETER_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/
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
def percent_escape(string, pattern)
string.gsub(pattern) do |char|
char.bytes.map { |byte| "%%%02X" % byte }.join("")
end
end
end
|