rack 1.4.1 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +1 -1
- data/KNOWN-ISSUES +9 -0
- data/README.rdoc +105 -7
- data/Rakefile +18 -11
- data/SPEC +3 -1
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +150 -0
- data/contrib/rdoc.css +412 -0
- data/lib/rack/auth/abstract/request.rb +5 -1
- data/lib/rack/auth/basic.rb +1 -1
- data/lib/rack/auth/digest/nonce.rb +1 -1
- data/lib/rack/backports/uri/common_18.rb +14 -28
- data/lib/rack/backports/uri/common_192.rb +14 -17
- data/lib/rack/backports/uri/common_193.rb +29 -0
- data/lib/rack/body_proxy.rb +10 -0
- data/lib/rack/builder.rb +1 -1
- data/lib/rack/cascade.rb +11 -0
- data/lib/rack/commonlogger.rb +18 -5
- data/lib/rack/deflater.rb +5 -1
- data/lib/rack/directory.rb +1 -1
- data/lib/rack/etag.rb +6 -3
- data/lib/rack/file.rb +19 -15
- data/lib/rack/head.rb +1 -0
- data/lib/rack/lint.rb +3 -1
- data/lib/rack/lock.rb +3 -4
- data/lib/rack/mime.rb +1 -1
- data/lib/rack/mock.rb +3 -2
- data/lib/rack/multipart/parser.rb +16 -7
- data/lib/rack/multipart.rb +2 -2
- data/lib/rack/reloader.rb +1 -1
- data/lib/rack/request.rb +2 -4
- data/lib/rack/response.rb +2 -1
- data/lib/rack/server.rb +28 -2
- data/lib/rack/session/abstract/id.rb +5 -0
- data/lib/rack/session/cookie.rb +10 -1
- data/lib/rack/static.rb +90 -8
- data/lib/rack/utils.rb +28 -10
- data/lib/rack.rb +12 -0
- data/rack.gemspec +3 -3
- data/test/builder/line.ru +1 -0
- data/test/cgi/assets/folder/test.js +1 -0
- data/test/cgi/assets/fonts/font.eot +1 -0
- data/test/cgi/assets/images/image.png +1 -0
- data/test/cgi/assets/index.html +1 -0
- data/test/cgi/assets/javascripts/app.js +1 -0
- data/test/cgi/assets/stylesheets/app.css +1 -0
- data/test/spec_auth.rb +57 -0
- data/test/spec_auth_basic.rb +8 -0
- data/test/spec_auth_digest.rb +14 -0
- data/test/spec_body_proxy.rb +4 -0
- data/test/spec_builder.rb +7 -1
- data/test/spec_cascade.rb +8 -0
- data/test/spec_chunked.rb +6 -6
- data/test/spec_config.rb +0 -1
- data/test/spec_content_length.rb +26 -13
- data/test/spec_content_type.rb +15 -5
- data/test/spec_deflater.rb +35 -17
- data/test/spec_directory.rb +20 -1
- data/test/spec_etag.rb +29 -13
- data/test/spec_file.rb +42 -25
- data/test/spec_head.rb +25 -7
- data/test/spec_lobster.rb +20 -5
- data/test/spec_lock.rb +46 -21
- data/test/spec_logger.rb +2 -7
- data/test/spec_methodoverride.rb +21 -22
- data/test/spec_mock.rb +12 -7
- data/test/spec_multipart.rb +82 -0
- data/test/spec_nulllogger.rb +13 -2
- data/test/spec_recursive.rb +12 -9
- data/test/spec_request.rb +2 -2
- data/test/spec_response.rb +30 -0
- data/test/spec_runtime.rb +15 -5
- data/test/spec_sendfile.rb +13 -9
- data/test/spec_server.rb +47 -0
- data/test/spec_session_cookie.rb +68 -1
- data/test/spec_session_memcache.rb +10 -8
- data/test/spec_session_pool.rb +13 -10
- data/test/spec_showexceptions.rb +9 -4
- data/test/spec_showstatus.rb +10 -5
- data/test/spec_static.rb +85 -9
- data/test/spec_urlmap.rb +10 -10
- data/test/spec_utils.rb +19 -1
- data/test/static/another/index.html +1 -0
- metadata +23 -8
data/lib/rack/etag.rb
CHANGED
|
@@ -28,8 +28,11 @@ module Rack
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
unless headers['Cache-Control']
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
if digest
|
|
32
|
+
headers['Cache-Control'] = @cache_control if @cache_control
|
|
33
|
+
else
|
|
34
|
+
headers['Cache-Control'] = @no_cache_control if @no_cache_control
|
|
35
|
+
end
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
[status, headers, body]
|
|
@@ -46,7 +49,7 @@ module Rack
|
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
def skip_caching?(headers)
|
|
49
|
-
headers['Cache-Control']
|
|
52
|
+
(headers['Cache-Control'] && headers['Cache-Control'].include?('no-cache')) ||
|
|
50
53
|
headers.key?('ETag') || headers.key?('Last-Modified')
|
|
51
54
|
end
|
|
52
55
|
|
data/lib/rack/file.rb
CHANGED
|
@@ -21,9 +21,16 @@ module Rack
|
|
|
21
21
|
|
|
22
22
|
alias :to_path :path
|
|
23
23
|
|
|
24
|
-
def initialize(root,
|
|
24
|
+
def initialize(root, headers={})
|
|
25
25
|
@root = root
|
|
26
|
-
|
|
26
|
+
# Allow a cache_control string for backwards compatibility
|
|
27
|
+
if headers.instance_of? String
|
|
28
|
+
warn \
|
|
29
|
+
"Rack::File headers parameter replaces cache_control after Rack 1.5."
|
|
30
|
+
@headers = { 'Cache-Control' => headers }
|
|
31
|
+
else
|
|
32
|
+
@headers = headers
|
|
33
|
+
end
|
|
27
34
|
end
|
|
28
35
|
|
|
29
36
|
def call(env)
|
|
@@ -40,19 +47,14 @@ module Rack
|
|
|
40
47
|
@path_info = Utils.unescape(env["PATH_INFO"])
|
|
41
48
|
parts = @path_info.split SEPS
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return fail(404, "Not Found") if depth - 1 < 0
|
|
49
|
-
depth - 1
|
|
50
|
-
else
|
|
51
|
-
depth + 1
|
|
52
|
-
end
|
|
50
|
+
clean = []
|
|
51
|
+
|
|
52
|
+
parts.each do |part|
|
|
53
|
+
next if part.empty? || part == '.'
|
|
54
|
+
part == '..' ? clean.pop : clean << part
|
|
53
55
|
end
|
|
54
56
|
|
|
55
|
-
@path = F.join(@root, *
|
|
57
|
+
@path = F.join(@root, *clean)
|
|
56
58
|
|
|
57
59
|
available = begin
|
|
58
60
|
F.file?(@path) && F.readable?(@path)
|
|
@@ -78,7 +80,9 @@ module Rack
|
|
|
78
80
|
},
|
|
79
81
|
env["REQUEST_METHOD"] == "HEAD" ? [] : self
|
|
80
82
|
]
|
|
81
|
-
|
|
83
|
+
|
|
84
|
+
# Set custom headers
|
|
85
|
+
@headers.each { |field, content| response[1][field] = content } if @headers
|
|
82
86
|
|
|
83
87
|
# NOTE:
|
|
84
88
|
# We check via File::size? whether this file provides size info
|
|
@@ -101,7 +105,7 @@ module Rack
|
|
|
101
105
|
# Partial content:
|
|
102
106
|
@range = ranges[0]
|
|
103
107
|
response[0] = 206
|
|
104
|
-
response[1]["Content-Range"]
|
|
108
|
+
response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
|
|
105
109
|
size = @range.end - @range.begin + 1
|
|
106
110
|
end
|
|
107
111
|
|
data/lib/rack/head.rb
CHANGED
data/lib/rack/lint.rb
CHANGED
|
@@ -528,7 +528,9 @@ module Rack
|
|
|
528
528
|
## The Body itself should not be an instance of String, as this will
|
|
529
529
|
## break in Ruby 1.9.
|
|
530
530
|
##
|
|
531
|
-
## If the Body responds to +close+, it will be called after iteration.
|
|
531
|
+
## If the Body responds to +close+, it will be called after iteration. If
|
|
532
|
+
## the body is replaced by a middleware after action, the original body
|
|
533
|
+
## must be closed first, if it repsonds to close.
|
|
532
534
|
# XXX howto: assert("Body has not been closed") { @closed }
|
|
533
535
|
|
|
534
536
|
|
data/lib/rack/lock.rb
CHANGED
|
@@ -13,12 +13,11 @@ module Rack
|
|
|
13
13
|
old, env[FLAG] = env[FLAG], false
|
|
14
14
|
@mutex.lock
|
|
15
15
|
response = @app.call(env)
|
|
16
|
-
|
|
16
|
+
body = BodyProxy.new(response[2]) { @mutex.unlock }
|
|
17
|
+
response[2] = body
|
|
17
18
|
response
|
|
18
|
-
rescue Exception
|
|
19
|
-
@mutex.unlock
|
|
20
|
-
raise
|
|
21
19
|
ensure
|
|
20
|
+
@mutex.unlock unless body
|
|
22
21
|
env[FLAG] = old
|
|
23
22
|
end
|
|
24
23
|
end
|
data/lib/rack/mime.rb
CHANGED
|
@@ -598,7 +598,7 @@ module Rack
|
|
|
598
598
|
".wmv" => "video/x-ms-wmv",
|
|
599
599
|
".wmx" => "video/x-ms-wmx",
|
|
600
600
|
".wmz" => "application/x-ms-wmz",
|
|
601
|
-
".woff" => "application/
|
|
601
|
+
".woff" => "application/font-woff",
|
|
602
602
|
".wpd" => "application/vnd.wordperfect",
|
|
603
603
|
".wpl" => "application/vnd.ms-wpl",
|
|
604
604
|
".wps" => "application/vnd.ms-works",
|
data/lib/rack/mock.rb
CHANGED
|
@@ -9,12 +9,12 @@ module Rack
|
|
|
9
9
|
# Rack::MockRequest helps testing your Rack application without
|
|
10
10
|
# actually using HTTP.
|
|
11
11
|
#
|
|
12
|
-
# After performing a request on a URL with get/post/put/delete, it
|
|
12
|
+
# After performing a request on a URL with get/post/put/patch/delete, it
|
|
13
13
|
# returns a MockResponse with useful helper methods for effective
|
|
14
14
|
# testing.
|
|
15
15
|
#
|
|
16
16
|
# You can pass a hash with additional configuration to the
|
|
17
|
-
# get/post/put/delete.
|
|
17
|
+
# get/post/put/patch/delete.
|
|
18
18
|
# <tt>:input</tt>:: A String or IO-like to be used as rack.input.
|
|
19
19
|
# <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
|
|
20
20
|
# <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
|
|
@@ -56,6 +56,7 @@ module Rack
|
|
|
56
56
|
def get(uri, opts={}) request("GET", uri, opts) end
|
|
57
57
|
def post(uri, opts={}) request("POST", uri, opts) end
|
|
58
58
|
def put(uri, opts={}) request("PUT", uri, opts) end
|
|
59
|
+
def patch(uri, opts={}) request("PATCH", uri, opts) end
|
|
59
60
|
def delete(uri, opts={}) request("DELETE", uri, opts) end
|
|
60
61
|
def head(uri, opts={}) request("HEAD", uri, opts) end
|
|
61
62
|
|
|
@@ -48,13 +48,15 @@ module Rack
|
|
|
48
48
|
@buf = ""
|
|
49
49
|
@params = Utils::KeySpaceConstrainedParams.new
|
|
50
50
|
|
|
51
|
-
@content_length = @env['CONTENT_LENGTH'].to_i
|
|
52
51
|
@io = @env['rack.input']
|
|
53
52
|
@io.rewind
|
|
54
53
|
|
|
55
54
|
@boundary_size = Utils.bytesize(@boundary) + EOL.size
|
|
56
55
|
|
|
57
|
-
@content_length
|
|
56
|
+
if @content_length = @env['CONTENT_LENGTH']
|
|
57
|
+
@content_length = @content_length.to_i
|
|
58
|
+
@content_length -= @boundary_size
|
|
59
|
+
end
|
|
58
60
|
true
|
|
59
61
|
end
|
|
60
62
|
|
|
@@ -68,9 +70,16 @@ module Rack
|
|
|
68
70
|
|
|
69
71
|
def fast_forward_to_first_boundary
|
|
70
72
|
loop do
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
content = @io.read(BUFSIZE)
|
|
74
|
+
raise EOFError, "bad content body" unless content
|
|
75
|
+
@buf << content
|
|
76
|
+
|
|
77
|
+
while @buf.gsub!(/\A([^\n]*\n)/, '')
|
|
78
|
+
read_buffer = $1
|
|
79
|
+
return if read_buffer == full_boundary
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
raise EOFError, "bad content body" if Utils.bytesize(@buf) >= BUFSIZE
|
|
74
83
|
end
|
|
75
84
|
end
|
|
76
85
|
|
|
@@ -104,11 +113,11 @@ module Rack
|
|
|
104
113
|
body << @buf.slice!(0, @buf.size - (@boundary_size+4))
|
|
105
114
|
end
|
|
106
115
|
|
|
107
|
-
content = @io.read(BUFSIZE
|
|
116
|
+
content = @io.read(@content_length && BUFSIZE >= @content_length ? @content_length : BUFSIZE)
|
|
108
117
|
raise EOFError, "bad content body" if content.nil? || content.empty?
|
|
109
118
|
|
|
110
119
|
@buf << content
|
|
111
|
-
@content_length -= content.size
|
|
120
|
+
@content_length -= content.size if @content_length
|
|
112
121
|
end
|
|
113
122
|
|
|
114
123
|
[head, filename, content_type, name, body]
|
data/lib/rack/multipart.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Rack
|
|
|
12
12
|
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
|
|
13
13
|
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
|
14
14
|
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
|
15
|
-
DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})
|
|
15
|
+
DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/
|
|
16
16
|
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
|
17
17
|
BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
|
18
18
|
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
|
|
@@ -31,4 +31,4 @@ module Rack
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
end
|
|
34
|
-
end
|
|
34
|
+
end
|
data/lib/rack/reloader.rb
CHANGED
data/lib/rack/request.rb
CHANGED
|
@@ -260,12 +260,10 @@ module Rack
|
|
|
260
260
|
# the Cookie header such that those with more specific Path attributes
|
|
261
261
|
# precede those with less specific. Ordering with respect to other
|
|
262
262
|
# attributes (e.g., Domain) is unspecified.
|
|
263
|
-
Utils.parse_query(string, ';,')
|
|
263
|
+
cookies = Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s }
|
|
264
|
+
cookies.each { |k,v| hash[k] = Array === v ? v.first : v }
|
|
264
265
|
@env["rack.request.cookie_string"] = string
|
|
265
266
|
hash
|
|
266
|
-
rescue => error
|
|
267
|
-
error.message.replace "cannot parse Cookie header: #{error.message}"
|
|
268
|
-
raise
|
|
269
267
|
end
|
|
270
268
|
|
|
271
269
|
def xhr?
|
data/lib/rack/response.rb
CHANGED
|
@@ -74,9 +74,10 @@ module Rack
|
|
|
74
74
|
if [204, 205, 304].include?(status.to_i)
|
|
75
75
|
header.delete "Content-Type"
|
|
76
76
|
header.delete "Content-Length"
|
|
77
|
+
close
|
|
77
78
|
[status.to_i, header, []]
|
|
78
79
|
else
|
|
79
|
-
[status.to_i, header, self]
|
|
80
|
+
[status.to_i, header, BodyProxy.new(self){}]
|
|
80
81
|
end
|
|
81
82
|
end
|
|
82
83
|
alias to_a finish # For *response
|
data/lib/rack/server.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Rack
|
|
|
26
26
|
|
|
27
27
|
opts.on("-I", "--include PATH",
|
|
28
28
|
"specify $LOAD_PATH (may be used more than once)") { |path|
|
|
29
|
-
options[:include]
|
|
29
|
+
(options[:include] ||= []).concat(path.split(":"))
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
opts.on("-r", "--require LIBRARY",
|
|
@@ -247,11 +247,14 @@ module Rack
|
|
|
247
247
|
pp app
|
|
248
248
|
end
|
|
249
249
|
|
|
250
|
+
check_pid! if options[:pid]
|
|
251
|
+
|
|
250
252
|
# Touch the wrapped app, so that the config.ru is loaded before
|
|
251
253
|
# daemonization (i.e. before chdir, etc).
|
|
252
254
|
wrapped_app
|
|
253
255
|
|
|
254
256
|
daemonize_app if options[:daemonize]
|
|
257
|
+
|
|
255
258
|
write_pid if options[:pid]
|
|
256
259
|
|
|
257
260
|
trap(:INT) do
|
|
@@ -274,7 +277,7 @@ module Rack
|
|
|
274
277
|
options = default_options
|
|
275
278
|
|
|
276
279
|
# Don't evaluate CGI ISINDEX parameters.
|
|
277
|
-
# http://
|
|
280
|
+
# http://www.meb.uni-bonn.de/docs/cgi/cl.html
|
|
278
281
|
args.clear if ENV.include?("REQUEST_METHOD")
|
|
279
282
|
|
|
280
283
|
options.merge! opt_parser.parse!(args)
|
|
@@ -319,5 +322,28 @@ module Rack
|
|
|
319
322
|
::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
|
|
320
323
|
at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
|
|
321
324
|
end
|
|
325
|
+
|
|
326
|
+
def check_pid!
|
|
327
|
+
case pidfile_process_status
|
|
328
|
+
when :running, :not_owned
|
|
329
|
+
$stderr.puts "A server is already running. Check #{options[:pid]}."
|
|
330
|
+
exit(1)
|
|
331
|
+
when :dead
|
|
332
|
+
::File.delete(options[:pid])
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def pidfile_process_status
|
|
337
|
+
return :exited unless ::File.exist?(options[:pid])
|
|
338
|
+
|
|
339
|
+
pid = ::File.read(options[:pid]).to_i
|
|
340
|
+
Process.kill(0, pid)
|
|
341
|
+
:running
|
|
342
|
+
rescue Errno::ESRCH
|
|
343
|
+
:dead
|
|
344
|
+
rescue Errno::EPERM
|
|
345
|
+
:not_owned
|
|
346
|
+
end
|
|
347
|
+
|
|
322
348
|
end
|
|
323
349
|
end
|
data/lib/rack/session/cookie.rb
CHANGED
|
@@ -82,6 +82,15 @@ module Rack
|
|
|
82
82
|
|
|
83
83
|
def initialize(app, options={})
|
|
84
84
|
@secrets = options.values_at(:secret, :old_secret).compact
|
|
85
|
+
warn <<-MSG unless @secrets.size >= 1
|
|
86
|
+
SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
|
|
87
|
+
This poses a security threat. It is strongly recommended that you
|
|
88
|
+
provide a secret to prevent exploits that may be possible from crafted
|
|
89
|
+
cookies. This will not be supported in future versions of Rack, and
|
|
90
|
+
future versions will even invalidate your existing user cookies.
|
|
91
|
+
|
|
92
|
+
Called from: #{caller[0]}.
|
|
93
|
+
MSG
|
|
85
94
|
@coder = options[:coder] ||= Base64::Marshal.new
|
|
86
95
|
super(app, options.merge!(:cookie_only => true))
|
|
87
96
|
end
|
|
@@ -108,7 +117,7 @@ module Rack
|
|
|
108
117
|
|
|
109
118
|
if session_data && digest
|
|
110
119
|
ok = @secrets.any? do |secret|
|
|
111
|
-
secret && digest
|
|
120
|
+
secret && Rack::Utils.secure_compare(digest, generate_hmac(session_data, secret))
|
|
112
121
|
end
|
|
113
122
|
end
|
|
114
123
|
|
data/lib/rack/static.rb
CHANGED
|
@@ -26,13 +26,58 @@ module Rack
|
|
|
26
26
|
# directory but uses index.html as default route for "/"
|
|
27
27
|
#
|
|
28
28
|
# use Rack::Static, :urls => [""], :root => 'public', :index =>
|
|
29
|
-
# '
|
|
29
|
+
# 'index.html'
|
|
30
30
|
#
|
|
31
|
-
# Set
|
|
31
|
+
# Set custom HTTP Headers for based on rules:
|
|
32
32
|
#
|
|
33
|
-
# use Rack::Static, :root => 'public',
|
|
33
|
+
# use Rack::Static, :root => 'public',
|
|
34
|
+
# :header_rules => [
|
|
35
|
+
# [rule, {header_field => content, header_field => content}],
|
|
36
|
+
# [rule, {header_field => content}]
|
|
37
|
+
# ]
|
|
38
|
+
#
|
|
39
|
+
# Rules for selecting files:
|
|
40
|
+
#
|
|
41
|
+
# 1) All files
|
|
42
|
+
# Provide the :all symbol
|
|
43
|
+
# :all => Matches every file
|
|
44
|
+
#
|
|
45
|
+
# 2) Folders
|
|
46
|
+
# Provide the folder path as a string
|
|
47
|
+
# '/folder' or '/folder/subfolder' => Matches files in a certain folder
|
|
48
|
+
#
|
|
49
|
+
# 3) File Extensions
|
|
50
|
+
# Provide the file extensions as an array
|
|
51
|
+
# ['css', 'js'] or %w(css js) => Matches files ending in .css or .js
|
|
52
|
+
#
|
|
53
|
+
# 4) Regular Expressions / Regexp
|
|
54
|
+
# Provide a regular expression
|
|
55
|
+
# %r{\.(?:css|js)\z} => Matches files ending in .css or .js
|
|
56
|
+
# /\.(?:eot|ttf|otf|woff|svg)\z/ => Matches files ending in
|
|
57
|
+
# the most common web font formats (.eot, .ttf, .otf, .woff, .svg)
|
|
58
|
+
# Note: This Regexp is available as a shortcut, using the :fonts rule
|
|
59
|
+
#
|
|
60
|
+
# 5) Font Shortcut
|
|
61
|
+
# Provide the :fonts symbol
|
|
62
|
+
# :fonts => Uses the Regexp rule stated right above to match all common web font endings
|
|
63
|
+
#
|
|
64
|
+
# Rule Ordering:
|
|
65
|
+
# Rules are applied in the order that they are provided.
|
|
66
|
+
# List rather general rules above special ones.
|
|
67
|
+
#
|
|
68
|
+
# Complete example use case including HTTP header rules:
|
|
69
|
+
#
|
|
70
|
+
# use Rack::Static, :root => 'public',
|
|
71
|
+
# :header_rules => [
|
|
72
|
+
# # Cache all static files in public caches (e.g. Rack::Cache)
|
|
73
|
+
# # as well as in the browser
|
|
74
|
+
# [:all, {'Cache-Control' => 'public, max-age=31536000'}],
|
|
75
|
+
#
|
|
76
|
+
# # Provide web fonts with cross-origin access-control-headers
|
|
77
|
+
# # Firefox requires this when serving assets using a Content Delivery Network
|
|
78
|
+
# [:fonts, {'Access-Control-Allow-Origin' => '*'}]
|
|
79
|
+
# ]
|
|
34
80
|
#
|
|
35
|
-
|
|
36
81
|
class Static
|
|
37
82
|
|
|
38
83
|
def initialize(app, options={})
|
|
@@ -40,12 +85,18 @@ module Rack
|
|
|
40
85
|
@urls = options[:urls] || ["/favicon.ico"]
|
|
41
86
|
@index = options[:index]
|
|
42
87
|
root = options[:root] || Dir.pwd
|
|
43
|
-
|
|
44
|
-
|
|
88
|
+
|
|
89
|
+
# HTTP Headers
|
|
90
|
+
@header_rules = options[:header_rules] || []
|
|
91
|
+
# Allow for legacy :cache_control option while prioritizing global header_rules setting
|
|
92
|
+
@header_rules.insert(0, [:all, {'Cache-Control' => options[:cache_control]}]) if options[:cache_control]
|
|
93
|
+
@headers = {}
|
|
94
|
+
|
|
95
|
+
@file_server = Rack::File.new(root, @headers)
|
|
45
96
|
end
|
|
46
97
|
|
|
47
98
|
def overwrite_file_path(path)
|
|
48
|
-
@urls.kind_of?(Hash) && @urls.key?(path) || @index && path
|
|
99
|
+
@urls.kind_of?(Hash) && @urls.key?(path) || @index && path =~ /\/$/
|
|
49
100
|
end
|
|
50
101
|
|
|
51
102
|
def route_file(path)
|
|
@@ -60,12 +111,43 @@ module Rack
|
|
|
60
111
|
path = env["PATH_INFO"]
|
|
61
112
|
|
|
62
113
|
if can_serve(path)
|
|
63
|
-
env["PATH_INFO"] = (path
|
|
114
|
+
env["PATH_INFO"] = (path =~ /\/$/ ? path + @index : @urls[path]) if overwrite_file_path(path)
|
|
115
|
+
@path = env["PATH_INFO"]
|
|
116
|
+
apply_header_rules
|
|
64
117
|
@file_server.call(env)
|
|
65
118
|
else
|
|
66
119
|
@app.call(env)
|
|
67
120
|
end
|
|
68
121
|
end
|
|
69
122
|
|
|
123
|
+
# Convert HTTP header rules to HTTP headers
|
|
124
|
+
def apply_header_rules
|
|
125
|
+
@header_rules.each do |rule, headers|
|
|
126
|
+
apply_rule(rule, headers)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def apply_rule(rule, headers)
|
|
131
|
+
case rule
|
|
132
|
+
when :all # All files
|
|
133
|
+
set_headers(headers)
|
|
134
|
+
when :fonts # Fonts Shortcut
|
|
135
|
+
set_headers(headers) if @path.match(/\.(?:ttf|otf|eot|woff|svg)\z/)
|
|
136
|
+
when String # Folder
|
|
137
|
+
path = ::Rack::Utils.unescape(@path)
|
|
138
|
+
set_headers(headers) if (path.start_with?(rule) || path.start_with?('/' + rule))
|
|
139
|
+
when Array # Extension/Extensions
|
|
140
|
+
extensions = rule.join('|')
|
|
141
|
+
set_headers(headers) if @path.match(/\.(#{extensions})\z/)
|
|
142
|
+
when Regexp # Flexible Regexp
|
|
143
|
+
set_headers(headers) if @path.match(rule)
|
|
144
|
+
else
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def set_headers(headers)
|
|
149
|
+
headers.each { |field, content| @headers[field] = content }
|
|
150
|
+
end
|
|
151
|
+
|
|
70
152
|
end
|
|
71
153
|
end
|
data/lib/rack/utils.rb
CHANGED
|
@@ -8,8 +8,10 @@ major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
|
|
|
8
8
|
|
|
9
9
|
if major == 1 && minor < 9
|
|
10
10
|
require 'rack/backports/uri/common_18'
|
|
11
|
-
elsif major == 1 && minor == 9 && patch
|
|
11
|
+
elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 320 && RUBY_ENGINE != 'jruby'
|
|
12
12
|
require 'rack/backports/uri/common_192'
|
|
13
|
+
elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
|
|
14
|
+
require 'rack/backports/uri/common_193'
|
|
13
15
|
else
|
|
14
16
|
require 'uri/common'
|
|
15
17
|
end
|
|
@@ -60,11 +62,15 @@ module Rack
|
|
|
60
62
|
# and ';' characters. You can also use this to parse
|
|
61
63
|
# cookies by changing the characters used in the second
|
|
62
64
|
# parameter (which defaults to '&;').
|
|
63
|
-
def parse_query(qs, d = nil)
|
|
65
|
+
def parse_query(qs, d = nil, &unescaper)
|
|
66
|
+
unescaper ||= method(:unescape)
|
|
67
|
+
|
|
64
68
|
params = KeySpaceConstrainedParams.new
|
|
65
69
|
|
|
66
70
|
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
|
67
|
-
|
|
71
|
+
next if p.empty?
|
|
72
|
+
k, v = p.split('=', 2).map(&unescaper)
|
|
73
|
+
next unless k || v
|
|
68
74
|
|
|
69
75
|
if cur = params[k]
|
|
70
76
|
if cur.class == Array
|
|
@@ -309,16 +315,16 @@ module Rack
|
|
|
309
315
|
def byte_ranges(env, size)
|
|
310
316
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
|
311
317
|
http_range = env['HTTP_RANGE']
|
|
312
|
-
return nil unless http_range
|
|
318
|
+
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
|
313
319
|
ranges = []
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
r0,r1 = matches[1], matches[2]
|
|
320
|
+
$1.split(/,\s*/).each do |range_spec|
|
|
321
|
+
return nil unless range_spec =~ /(\d*)-(\d*)/
|
|
322
|
+
r0,r1 = $1, $2
|
|
318
323
|
if r0.empty?
|
|
319
324
|
return nil if r1.empty?
|
|
320
325
|
# suffix-byte-range-spec, represents trailing suffix of file
|
|
321
|
-
r0 =
|
|
326
|
+
r0 = size - r1.to_i
|
|
327
|
+
r0 = 0 if r0 < 0
|
|
322
328
|
r1 = size - 1
|
|
323
329
|
else
|
|
324
330
|
r0 = r0.to_i
|
|
@@ -336,6 +342,18 @@ module Rack
|
|
|
336
342
|
end
|
|
337
343
|
module_function :byte_ranges
|
|
338
344
|
|
|
345
|
+
# Constant time string comparison.
|
|
346
|
+
def secure_compare(a, b)
|
|
347
|
+
return false unless bytesize(a) == bytesize(b)
|
|
348
|
+
|
|
349
|
+
l = a.unpack("C*")
|
|
350
|
+
|
|
351
|
+
r, i = 0, -1
|
|
352
|
+
b.each_byte { |v| r |= v ^ l[i+=1] }
|
|
353
|
+
r == 0
|
|
354
|
+
end
|
|
355
|
+
module_function :secure_compare
|
|
356
|
+
|
|
339
357
|
# Context allows the use of a compatible middleware at different points
|
|
340
358
|
# in a request handling stack. A compatible middleware must define
|
|
341
359
|
# #context which should take the arguments env and app. The first of which
|
|
@@ -442,7 +460,7 @@ module Rack
|
|
|
442
460
|
end
|
|
443
461
|
|
|
444
462
|
def []=(key, value)
|
|
445
|
-
@size += key.size
|
|
463
|
+
@size += key.size if key && !@params.key?(key)
|
|
446
464
|
raise RangeError, 'exceeded available parameter key space' if @size > @limit
|
|
447
465
|
@params[key] = value
|
|
448
466
|
end
|
data/lib/rack.rb
CHANGED
|
@@ -73,6 +73,18 @@ module Rack
|
|
|
73
73
|
autoload :Params, "rack/auth/digest/params"
|
|
74
74
|
autoload :Request, "rack/auth/digest/request"
|
|
75
75
|
end
|
|
76
|
+
|
|
77
|
+
# Not all of the following schemes are "standards", but they are used often.
|
|
78
|
+
@schemes = %w[basic digest bearer mac token oauth oauth2]
|
|
79
|
+
|
|
80
|
+
def self.add_scheme scheme
|
|
81
|
+
@schemes << scheme
|
|
82
|
+
@schemes.uniq!
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.schemes
|
|
86
|
+
@schemes.dup
|
|
87
|
+
end
|
|
76
88
|
end
|
|
77
89
|
|
|
78
90
|
module Session
|
data/rack.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |s|
|
|
2
2
|
s.name = "rack"
|
|
3
|
-
s.version = "1.4.
|
|
3
|
+
s.version = "1.4.5"
|
|
4
4
|
s.platform = Gem::Platform::RUBY
|
|
5
5
|
s.summary = "a modular Ruby webserver interface"
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ the simplest way possible, it unifies and distills the API for web
|
|
|
11
11
|
servers, web frameworks, and software in between (the so-called
|
|
12
12
|
middleware) into a single method call.
|
|
13
13
|
|
|
14
|
-
Also see http://rack.
|
|
14
|
+
Also see http://rack.github.com/.
|
|
15
15
|
EOF
|
|
16
16
|
|
|
17
17
|
s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*,test/**/*}'] +
|
|
@@ -24,7 +24,7 @@ EOF
|
|
|
24
24
|
|
|
25
25
|
s.author = 'Christian Neukirchen'
|
|
26
26
|
s.email = 'chneukirchen@gmail.com'
|
|
27
|
-
s.homepage = 'http://rack.
|
|
27
|
+
s.homepage = 'http://rack.github.com/'
|
|
28
28
|
s.rubyforge_project = 'rack'
|
|
29
29
|
|
|
30
30
|
s.add_development_dependency 'bacon'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
run lambda{ |env| [200, {'Content-Type' => 'text/plain'}, [__LINE__.to_s]] }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
### TestFile ###
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
### TestFile ###
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
### TestFile ###
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
### TestFile ###
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
### TestFile ###
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
### TestFile ###
|