Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rack::File -> Rack::Files #305

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.rdoc
Expand Up @@ -65,7 +65,7 @@ applications needs using middleware, for example:
* Rack::CommonLogger, for creating Apache-style logfiles.
* Rack::ShowException, for catching unhandled exceptions and
presenting them in a nice and helpful way with clickable backtrace.
* Rack::File, for serving static files.
* Rack::Files, for serving static files.
* ...many others!

All these components use the same interface, which is described in
Expand Down Expand Up @@ -194,7 +194,7 @@ run on port 11211) and memcache-client installed.
* Pool sessions, by blink.
* OpenID authentication, by blink.
* :Port and :File options for opening FastCGI sockets, by blink.
* Last-Modified HTTP header for Rack::File, by blink.
* Last-Modified HTTP header for Rack::Files, by blink.
* Rack::Builder#use now accepts blocks, by Corey Jewett.
(See example/protectedlobster.ru)
* HTTP status 201 can contain a Content-Type and a body now.
Expand Down Expand Up @@ -229,7 +229,7 @@ run on port 11211) and memcache-client installed.
* Many bugfixes and small improvements.

* January 9th, 2009: Sixth public release 0.9.1.
* Fix directory traversal exploits in Rack::File and Rack::Directory.
* Fix directory traversal exploits in Rack::Files and Rack::Directory.

* April 25th, 2009: Seventh public release 1.0.0.
* SPEC change: Rack::VERSION has been pushed to [1,0].
Expand Down Expand Up @@ -384,7 +384,7 @@ run on port 11211) and memcache-client installed.
* Various mime types added
* Rack::MockRequest now supports HEAD
* Rack::Directory now supports files that contain RFC3986 reserved chars
* Rack::File now only supports GET and HEAD requests
* Rack::Files now only supports GET and HEAD requests
* Rack::Server#start now passes the block to Rack::Handler::<h>#run
* Rack::Static now supports an index option
* Added the Teapot status code
Expand Down
5 changes: 3 additions & 2 deletions lib/rack/directory.rb
@@ -1,14 +1,15 @@
require 'time'
require 'rack/utils'
require 'rack/mime'
require 'rack/files'

module Rack
# Rack::Directory serves entries below the +root+ given, according to the
# path info of the Rack request. If a directory is found, the file's contents
# will be presented in an html based index. If a file is found, the env will
# be passed to the specified +app+.
#
# If +app+ is not specified, a Rack::File of the same +root+ will be used.
# If +app+ is not specified, a Rack::Files of the same +root+ will be used.

class Directory
DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>"
Expand Down Expand Up @@ -44,7 +45,7 @@ class Directory

def initialize(root, app=nil)
@root = F.expand_path(root)
@app = app || Rack::File.new(@root)
@app = app || Rack::Files.new(@root)
end

def call(env)
Expand Down
142 changes: 3 additions & 139 deletions lib/rack/file.rb
@@ -1,142 +1,6 @@
require 'time'
require 'rack/utils'
require 'rack/mime'
require 'rack/files'

module Rack
# Rack::File serves files below the +root+ directory given, according to the
# path info of the Rack request.
# e.g. when Rack::File.new("/etc") is used, you can access 'passwd' file
# as http://localhost:9292/passwd
#
# Handlers can detect if bodies are a Rack::File, and use mechanisms
# like sendfile on the +path+.

class File
SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
ALLOWED_VERBS = %w[GET HEAD]

attr_accessor :root
attr_accessor :path
attr_accessor :cache_control

alias :to_path :path

def initialize(root, cache_control = nil)
@root = root
@cache_control = cache_control
end

def call(env)
dup._call(env)
end

F = ::File

def _call(env)
unless ALLOWED_VERBS.include? env["REQUEST_METHOD"]
return fail(405, "Method Not Allowed")
end

@path_info = Utils.unescape(env["PATH_INFO"])
parts = @path_info.split SEPS

parts.inject(0) do |depth, part|
case part
when '', '.'
depth
when '..'
return fail(404, "Not Found") if depth - 1 < 0
depth - 1
else
depth + 1
end
end

@path = F.join(@root, *parts)

available = begin
F.file?(@path) && F.readable?(@path)
rescue SystemCallError
false
end

if available
serving(env)
else
fail(404, "File not found: #{@path_info}")
end
end

def serving(env)
last_modified = F.mtime(@path).httpdate
return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
response = [
200,
{
"Last-Modified" => last_modified,
"Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain')
},
env["REQUEST_METHOD"] == "HEAD" ? [] : self
]
response[1].merge! 'Cache-Control' => @cache_control if @cache_control

# NOTE:
# We check via File::size? whether this file provides size info
# via stat (e.g. /proc files often don't), otherwise we have to
# figure it out by reading the whole file into memory.
size = F.size?(@path) || Utils.bytesize(F.read(@path))

ranges = Rack::Utils.byte_ranges(env, size)
if ranges.nil? || ranges.length > 1
# No ranges, or multiple ranges (which we don't support):
# TODO: Support multiple byte-ranges
response[0] = 200
@range = 0..size-1
elsif ranges.empty?
# Unsatisfiable. Return error, and file size:
response = fail(416, "Byte range unsatisfiable")
response[1]["Content-Range"] = "bytes */#{size}"
return response
else
# Partial content:
@range = ranges[0]
response[0] = 206
response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
size = @range.end - @range.begin + 1
end

response[1]["Content-Length"] = size.to_s
response
end

def each
F.open(@path, "rb") do |file|
file.seek(@range.begin)
remaining_len = @range.end-@range.begin+1
while remaining_len > 0
part = file.read([8192, remaining_len].min)
break unless part
remaining_len -= part.length

yield part
end
end
end

private

def fail(status, body)
body += "\n"
[
status,
{
"Content-Type" => "text/plain",
"Content-Length" => body.size.to_s,
"X-Cascade" => "pass"
},
[body]
]
end

end
# Rack::File is deprecated, please use Rack::Files instead
File = Files
end
142 changes: 142 additions & 0 deletions lib/rack/files.rb
@@ -0,0 +1,142 @@
require 'time'
require 'rack/utils'
require 'rack/mime'

module Rack
# Rack::Files serves files below the +root+ directory given, according to the
# path info of the Rack request.
# e.g. when Rack::Files.new("/etc") is used, you can access 'passwd' file
# as http://localhost:9292/passwd
#
# Handlers can detect if bodies are a Rack::Files, and use mechanisms
# like sendfile on the +path+.

class Files
SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
ALLOWED_VERBS = %w[GET HEAD]

attr_accessor :root
attr_accessor :path
attr_accessor :cache_control

alias :to_path :path

def initialize(root, cache_control = nil)
@root = root
@cache_control = cache_control
end

def call(env)
dup._call(env)
end

F = ::File

def _call(env)
unless ALLOWED_VERBS.include? env["REQUEST_METHOD"]
return fail(405, "Method Not Allowed")
end

@path_info = Utils.unescape(env["PATH_INFO"])
parts = @path_info.split SEPS

parts.inject(0) do |depth, part|
case part
when '', '.'
depth
when '..'
return fail(404, "Not Found") if depth - 1 < 0
depth - 1
else
depth + 1
end
end

@path = F.join(@root, *parts)

available = begin
F.file?(@path) && F.readable?(@path)
rescue SystemCallError
false
end

if available
serving(env)
else
fail(404, "File not found: #{@path_info}")
end
end

def serving(env)
last_modified = F.mtime(@path).httpdate
return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
response = [
200,
{
"Last-Modified" => last_modified,
"Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain')
},
env["REQUEST_METHOD"] == "HEAD" ? [] : self
]
response[1].merge! 'Cache-Control' => @cache_control if @cache_control

# NOTE:
# We check via File::size? whether this file provides size info
# via stat (e.g. /proc files often don't), otherwise we have to
# figure it out by reading the whole file into memory.
size = F.size?(@path) || Utils.bytesize(F.read(@path))

ranges = Rack::Utils.byte_ranges(env, size)
if ranges.nil? || ranges.length > 1
# No ranges, or multiple ranges (which we don't support):
# TODO: Support multiple byte-ranges
response[0] = 200
@range = 0..size-1
elsif ranges.empty?
# Unsatisfiable. Return error, and file size:
response = fail(416, "Byte range unsatisfiable")
response[1]["Content-Range"] = "bytes */#{size}"
return response
else
# Partial content:
@range = ranges[0]
response[0] = 206
response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
size = @range.end - @range.begin + 1
end

response[1]["Content-Length"] = size.to_s
response
end

def each
F.open(@path, "rb") do |file|
file.seek(@range.begin)
remaining_len = @range.end-@range.begin+1
while remaining_len > 0
part = file.read([8192, remaining_len].min)
break unless part
remaining_len -= part.length

yield part
end
end
end

private

def fail(status, body)
body += "\n"
[
status,
{
"Content-Type" => "text/plain",
"Content-Length" => body.size.to_s,
"X-Cascade" => "pass"
},
[body]
]
end

end
end
4 changes: 2 additions & 2 deletions lib/rack/sendfile.rb
@@ -1,4 +1,4 @@
require 'rack/file'
require 'rack/files'

module Rack

Expand All @@ -13,7 +13,7 @@ module Rack
#
# In order to take advantage of this middleware, the response body must
# respond to +to_path+ and the request must include an X-Sendfile-Type
# header. Rack::File and other components implement +to_path+ so there's
# header. Rack::Files and other components implement +to_path+ so there's
# rarely anything you need to do in your application. The X-Sendfile-Type
# header is typically set in your web servers configuration. The following
# sections attempt to document
Expand Down
6 changes: 4 additions & 2 deletions lib/rack/static.rb
@@ -1,8 +1,10 @@
require 'rack/files'

module Rack

# The Rack::Static middleware intercepts requests for static files
# (javascript files, images, stylesheets, etc) based on the url prefixes or
# route mappings passed in the options, and serves them using a Rack::File
# route mappings passed in the options, and serves them using a Rack::Files
# object. This allows a Rack stack to serve both static and dynamic content.
#
# Examples:
Expand Down Expand Up @@ -41,7 +43,7 @@ def initialize(app, options={})
@index = options[:index]
root = options[:root] || Dir.pwd
cache_control = options[:cache_control]
@file_server = Rack::File.new(root, cache_control)
@file_server = Rack::Files.new(root, cache_control)
end

def overwrite_file_path(path)
Expand Down