mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Download extract from BC3
This commit is contained in:
parent
d2ff19c39c
commit
dde68d4a8b
1 changed files with 90 additions and 0 deletions
90
lib/active_file/download.rb
Normal file
90
lib/active_file/download.rb
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
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
|
Loading…
Reference in a new issue