mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
send_file now copes with 'Range: bytes=100-400,500-600' headers
Signed-off-by: Konstantin Haase <konstantin.mailinglists@googlemail.com>
This commit is contained in:
parent
89af8c1167
commit
480b1e8ebe
2 changed files with 55 additions and 5 deletions
|
@ -161,8 +161,18 @@ module Sinatra
|
|||
elsif opts[:disposition] == 'inline'
|
||||
response['Content-Disposition'] = 'inline'
|
||||
end
|
||||
|
||||
halt StaticFile.open(path, 'rb')
|
||||
sf = StaticFile.open(path, 'rb')
|
||||
if (env['HTTP_RANGE'] =~ /^bytes=(\d+-\d+(?:,\d+-\d+)*)$/)
|
||||
sf.ranges = $1.split(',').collect{|range| range.split('-').collect{|n| n.to_i}}
|
||||
sf.ranges.each do |range|
|
||||
halt 416 if range[1] < range[0]
|
||||
end
|
||||
response['Content-Range'] = "bytes #{$1}/#{response['Content-Length']}"
|
||||
response['Content-Length'] = sf.ranges.dup.inject(0){|total,range| total + range[1] - range[0] + 1 }.to_s
|
||||
halt 206, sf
|
||||
else
|
||||
halt sf
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
not_found
|
||||
end
|
||||
|
@ -171,13 +181,28 @@ module Sinatra
|
|||
# generated iteratively in 8K chunks.
|
||||
class StaticFile < ::File #:nodoc:
|
||||
alias_method :to_path, :path
|
||||
|
||||
attr_accessor :ranges
|
||||
|
||||
def each
|
||||
if @ranges
|
||||
@ranges.each do |range|
|
||||
self.pos = range[0]
|
||||
length = range[1] - range[0] + 1
|
||||
while buf = read([8192,length.abs].min)
|
||||
yield buf
|
||||
length -= buf.length
|
||||
break if (length -= 8192) + 8192 <= 0
|
||||
end
|
||||
end
|
||||
else
|
||||
rewind
|
||||
while buf = read(8192)
|
||||
yield buf
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Specify response freshness policy for HTTP caches (Cache-Control header).
|
||||
# Any number of non-value directives (:public, :private, :no_cache,
|
||||
|
|
|
@ -90,4 +90,29 @@ class StaticTest < Test::Unit::TestCase
|
|||
get "/../#{File.basename(__FILE__)}"
|
||||
assert not_found?
|
||||
end
|
||||
|
||||
it 'deals correctly with incompletable range requests' do
|
||||
request = Rack::MockRequest.new(@app)
|
||||
response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => "bytes=45-40")
|
||||
|
||||
assert_equal 416,response.status, "Ranges with final position < initial position should give HTTP/1.1 416 Requested Range Not Satisfiable"
|
||||
end
|
||||
|
||||
it 'accepts and returns byte ranges correctly' do
|
||||
[[[42,88]],[[1,5],[6,85]]].each do |ranges|
|
||||
request = Rack::MockRequest.new(@app)
|
||||
response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => "bytes=#{ranges.map{|r| r.join('-')}.join(',')}")
|
||||
|
||||
file = File.read(__FILE__)
|
||||
should_be = ''
|
||||
ranges.each do |range|
|
||||
should_be += file[range[0]..range[1]]
|
||||
end
|
||||
|
||||
assert_equal 206,response.status, "Should be HTTP/1.1 206 Partial content"
|
||||
assert_equal should_be, response.body
|
||||
assert_equal should_be.length.to_s, response['Content-Length'], "Length given was not the same as Content-Length reported"
|
||||
assert_equal "bytes #{ranges.map{|r| r.join('-')}.join(',')}/#{File.size(__FILE__)}", response['Content-Range'],"Content-Range header was not correct"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue