Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

Open
wants to merge 3 commits into from

6 participants

@postmodern

Renamed Rack::File to Rack::Files, since it can serve arbitrary files from a root directory. Once we can remove Rack::File, we can no longer worry about name conflicts between ::File and Rack::File.

@postmodern

To be clear, I left a lib/rack/file.rb file and Rack::File constant, which can be removed on a major version bump. Although, I'm unsure if or when a deprecation warning message should be added to the lib/rack/file.rb file, or if a deprecation notice in the ChangeLog would suffice?

@krainboltgreene

This is an intelligent change, and makes literary and context sense.

@josevalim
Owner

Backwards incompatible. IIRC Rails relies on Rack::File (and potentially others tools besides Rails).

@krainboltgreene
@josevalim
Owner

It needs to be deprecated first.

@postmodern

@josevalim Like I said before, I left a Rack::File and rack/file.rb behind specifically for backwards compatibility. I'm leaving it up to the Rack team how they wish to deprecate the constant (I didn't get a clear answer as to how they normally handle deprecations).

@raggi
Owner

Needs rebasing now. I'm not ignoring this, I'm just trying to decide when I want to pull the trigger. I think my answer is "for rails 4". The main point being, I don't really want to maintain too much heavy deviance and do continual cross-patching. I'll keep you posted ofc.

@postmodern

Rebased! Tests pass.

Should we bring @rkh into this discussion, as changes to Rack can also impact Sinatra? Also, until Rack::File is completely removed, all Rack code will have to continue using the awkward ::File style. There should be a grace period where Rack::File and Rack::Files co-exist.

@raggi
Owner

The issues will be broad I believe. I'd expect this will also hit ramaze and maybe padrino separately.

Happy to add a deprecation notice in the next 1.4.x release. I'm not sure how long to give it, but I'd like to give it a while to avoid lots of merge issues. On that note, to reduce merge conflicts, I'd prefer to alias to Files first, and move it over when the deprecation is executed. This will save on maintenance time. (see multipart back ports :'( )

@raggi
Owner

Thanks for rebasing!!!

@raggi
Owner

Due to signature changes that have already occurred, I'm leaving this until 1.6 at the earliest.

@tenderlove
Owner

This seems fine to me. AFAIK, master is 1.6. Should we merge this? < @raggi

@spastorino spastorino modified the milestone: Rack 2.0, Rack 1.6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 23, 2012
  1. @postmodern

    Renamed Rack::File to Rack::Files, since it can serve multiple files …

    postmodern authored
    …from a root directory.
    
    * Left a Rack::File constant, for backwards compatibility.
  2. @postmodern
  3. @postmodern
This page is out of date. Refresh to see the latest.
View
8 README.rdoc
@@ -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
@@ -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.
@@ -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].
@@ -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
View
5 lib/rack/directory.rb
@@ -1,6 +1,7 @@
require 'time'
require 'rack/utils'
require 'rack/mime'
+require 'rack/files'
module Rack
# Rack::Directory serves entries below the +root+ given, according to the
@@ -8,7 +9,7 @@ module Rack
# 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>"
@@ -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)
View
142 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
View
142 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
View
4 lib/rack/sendfile.rb
@@ -1,4 +1,4 @@
-require 'rack/file'
+require 'rack/files'
module Rack
@@ -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
View
6 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:
@@ -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)
View
4 test/spec_cascade.rb
@@ -1,5 +1,5 @@
require 'rack/cascade'
-require 'rack/file'
+require 'rack/files'
require 'rack/lint'
require 'rack/urlmap'
require 'rack/mock'
@@ -10,7 +10,7 @@ def cascade(*args)
end
docroot = File.expand_path(File.dirname(__FILE__))
- app1 = Rack::File.new(docroot)
+ app1 = Rack::Files.new(docroot)
app2 = Rack::URLMap.new("/crash" => lambda { |env| raise "boom" })
View
38 test/spec_file.rb → test/spec_files.rb
@@ -1,11 +1,11 @@
-require 'rack/file'
+require 'rack/files'
require 'rack/mock'
-describe Rack::File do
+describe Rack::Files do
DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
should "serve files" do
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))).
get("/cgi/test")
res.should.be.ok
@@ -13,7 +13,7 @@
end
should "set Last-Modified header" do
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))).
get("/cgi/test")
path = File.join(DOCROOT, "/cgi/test")
@@ -24,7 +24,7 @@
should "return 304 if file isn't modified since last serve" do
path = File.join(DOCROOT, "/cgi/test")
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))).
get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path).httpdate)
res.status.should.equal 304
@@ -33,14 +33,14 @@
should "return the file if it's modified since last serve" do
path = File.join(DOCROOT, "/cgi/test")
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))).
get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => (File.mtime(path) - 100).httpdate)
res.should.be.ok
end
should "serve files with URL encoded filenames" do
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))).
get("/cgi/%74%65%73%74") # "/cgi/test"
res.should.be.ok
@@ -48,7 +48,7 @@
end
should "allow safe directory traversal" do
- req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
+ req = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT)))
res = req.get('/cgi/../cgi/test')
res.should.be.successful
@@ -61,7 +61,7 @@
end
should "not allow unsafe directory traversal" do
- req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
+ req = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT)))
res = req.get("/../README")
res.should.be.client_error
@@ -76,7 +76,7 @@
end
should "allow files with .. in their name" do
- req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
+ req = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT)))
res = req.get("/cgi/..test")
res.should.be.not_found
@@ -88,7 +88,7 @@
end
should "not allow unsafe directory traversal with encoded periods" do
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))).
get("/%2E%2E/README")
res.should.be.client_error?
@@ -96,21 +96,21 @@
end
should "allow safe directory traversal with encoded periods" do
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))).
get("/cgi/%2E%2E/cgi/test")
res.should.be.successful
end
should "404 if it can't find the file" do
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))).
get("/cgi/blubb")
res.should.be.not_found
end
should "detect SystemCallErrors" do
- res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT))).
get("/cgi")
res.should.be.not_found
@@ -118,7 +118,7 @@
should "return bodies that respond to #to_path" do
env = Rack::MockRequest.env_for("/cgi/test")
- status, _, body = Rack::File.new(DOCROOT).call(env)
+ status, _, body = Rack::Files.new(DOCROOT).call(env)
path = File.join(DOCROOT, "/cgi/test")
@@ -130,7 +130,7 @@
should "return correct byte range in body" do
env = Rack::MockRequest.env_for("/cgi/test")
env["HTTP_RANGE"] = "bytes=22-33"
- res = Rack::MockResponse.new(*Rack::File.new(DOCROOT).call(env))
+ res = Rack::MockResponse.new(*Rack::Files.new(DOCROOT).call(env))
res.status.should.equal 206
res["Content-Length"].should.equal "12"
@@ -141,7 +141,7 @@
should "return error for unsatisfiable byte range" do
env = Rack::MockRequest.env_for("/cgi/test")
env["HTTP_RANGE"] = "bytes=1234-5678"
- res = Rack::MockResponse.new(*Rack::File.new(DOCROOT).call(env))
+ res = Rack::MockResponse.new(*Rack::Files.new(DOCROOT).call(env))
res.status.should.equal 416
res["Content-Range"].should.equal "bytes */193"
@@ -149,14 +149,14 @@
should "support cache control options" do
env = Rack::MockRequest.env_for("/cgi/test")
- status, heads, _ = Rack::File.new(DOCROOT, 'public, max-age=38').call(env)
+ status, heads, _ = Rack::Files.new(DOCROOT, 'public, max-age=38').call(env)
status.should.equal 200
heads['Cache-Control'].should.equal 'public, max-age=38'
end
should "only support GET and HEAD requests" do
- req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
+ req = Rack::MockRequest.new(Rack::Lint.new(Rack::Files.new(DOCROOT)))
forbidden = %w[post put delete]
forbidden.each do |method|
Something went wrong with that request. Please try again.