Skip to content

Commit

Permalink
Merge pull request #1841 from sinatra/filename-escaping
Browse files Browse the repository at this point in the history
escape filename in the Content-Disposition header
  • Loading branch information
jkowens committed Nov 25, 2022
2 parents c90f203 + ea8fc94 commit bbc1d47
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 1 deletion.
9 changes: 8 additions & 1 deletion lib/sinatra/base.rb
Expand Up @@ -396,13 +396,20 @@ def content_type(type = nil, params = {})
response['Content-Type'] = mime_type
end

# https://html.spec.whatwg.org/#multipart-form-data
MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
'"' => '%22',
"\r" => '%0D',
"\n" => '%0A'
}.freeze

# Set the Content-Disposition to "attachment" with the specified filename,
# instructing the user agents to prompt to save.
def attachment(filename = nil, disposition = :attachment)
response['Content-Disposition'] = disposition.to_s.dup
return unless filename

params = format('; filename="%s"', File.basename(filename))
params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE))
response['Content-Disposition'] << params
ext = File.extname(filename)
content_type(ext) unless response['Content-Type'] || ext.empty?
Expand Down
12 changes: 12 additions & 0 deletions test/helpers_test.rb
Expand Up @@ -781,6 +781,18 @@ def attachment_app(filename=nil)
assert_equal '<sinatra></sinatra>', body
end

it 'escapes filename in the Content-Disposition header according to the multipart form data spec in WHATWG living standard' do
mock_app do
get('/attachment') do
attachment "test.xml\";\r\next=.txt"
response.write("<sinatra></sinatra>")
end
end

get '/attachment'
assert_equal 'attachment; filename="test.xml%22;%0D%0Aext=.txt"', response['Content-Disposition']
assert_equal '<sinatra></sinatra>', body
end
end

describe 'send_file' do
Expand Down

0 comments on commit bbc1d47

Please sign in to comment.