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.
Files changed (84) hide show
  1. data/COPYING +1 -1
  2. data/KNOWN-ISSUES +9 -0
  3. data/README.rdoc +105 -7
  4. data/Rakefile +18 -11
  5. data/SPEC +3 -1
  6. data/contrib/rack.png +0 -0
  7. data/contrib/rack.svg +150 -0
  8. data/contrib/rdoc.css +412 -0
  9. data/lib/rack/auth/abstract/request.rb +5 -1
  10. data/lib/rack/auth/basic.rb +1 -1
  11. data/lib/rack/auth/digest/nonce.rb +1 -1
  12. data/lib/rack/backports/uri/common_18.rb +14 -28
  13. data/lib/rack/backports/uri/common_192.rb +14 -17
  14. data/lib/rack/backports/uri/common_193.rb +29 -0
  15. data/lib/rack/body_proxy.rb +10 -0
  16. data/lib/rack/builder.rb +1 -1
  17. data/lib/rack/cascade.rb +11 -0
  18. data/lib/rack/commonlogger.rb +18 -5
  19. data/lib/rack/deflater.rb +5 -1
  20. data/lib/rack/directory.rb +1 -1
  21. data/lib/rack/etag.rb +6 -3
  22. data/lib/rack/file.rb +19 -15
  23. data/lib/rack/head.rb +1 -0
  24. data/lib/rack/lint.rb +3 -1
  25. data/lib/rack/lock.rb +3 -4
  26. data/lib/rack/mime.rb +1 -1
  27. data/lib/rack/mock.rb +3 -2
  28. data/lib/rack/multipart/parser.rb +16 -7
  29. data/lib/rack/multipart.rb +2 -2
  30. data/lib/rack/reloader.rb +1 -1
  31. data/lib/rack/request.rb +2 -4
  32. data/lib/rack/response.rb +2 -1
  33. data/lib/rack/server.rb +28 -2
  34. data/lib/rack/session/abstract/id.rb +5 -0
  35. data/lib/rack/session/cookie.rb +10 -1
  36. data/lib/rack/static.rb +90 -8
  37. data/lib/rack/utils.rb +28 -10
  38. data/lib/rack.rb +12 -0
  39. data/rack.gemspec +3 -3
  40. data/test/builder/line.ru +1 -0
  41. data/test/cgi/assets/folder/test.js +1 -0
  42. data/test/cgi/assets/fonts/font.eot +1 -0
  43. data/test/cgi/assets/images/image.png +1 -0
  44. data/test/cgi/assets/index.html +1 -0
  45. data/test/cgi/assets/javascripts/app.js +1 -0
  46. data/test/cgi/assets/stylesheets/app.css +1 -0
  47. data/test/spec_auth.rb +57 -0
  48. data/test/spec_auth_basic.rb +8 -0
  49. data/test/spec_auth_digest.rb +14 -0
  50. data/test/spec_body_proxy.rb +4 -0
  51. data/test/spec_builder.rb +7 -1
  52. data/test/spec_cascade.rb +8 -0
  53. data/test/spec_chunked.rb +6 -6
  54. data/test/spec_config.rb +0 -1
  55. data/test/spec_content_length.rb +26 -13
  56. data/test/spec_content_type.rb +15 -5
  57. data/test/spec_deflater.rb +35 -17
  58. data/test/spec_directory.rb +20 -1
  59. data/test/spec_etag.rb +29 -13
  60. data/test/spec_file.rb +42 -25
  61. data/test/spec_head.rb +25 -7
  62. data/test/spec_lobster.rb +20 -5
  63. data/test/spec_lock.rb +46 -21
  64. data/test/spec_logger.rb +2 -7
  65. data/test/spec_methodoverride.rb +21 -22
  66. data/test/spec_mock.rb +12 -7
  67. data/test/spec_multipart.rb +82 -0
  68. data/test/spec_nulllogger.rb +13 -2
  69. data/test/spec_recursive.rb +12 -9
  70. data/test/spec_request.rb +2 -2
  71. data/test/spec_response.rb +30 -0
  72. data/test/spec_runtime.rb +15 -5
  73. data/test/spec_sendfile.rb +13 -9
  74. data/test/spec_server.rb +47 -0
  75. data/test/spec_session_cookie.rb +68 -1
  76. data/test/spec_session_memcache.rb +10 -8
  77. data/test/spec_session_pool.rb +13 -10
  78. data/test/spec_showexceptions.rb +9 -4
  79. data/test/spec_showstatus.rb +10 -5
  80. data/test/spec_static.rb +85 -9
  81. data/test/spec_urlmap.rb +10 -10
  82. data/test/spec_utils.rb +19 -1
  83. data/test/static/another/index.html +1 -0
  84. 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
- headers['Cache-Control'] =
32
- (digest ? @cache_control : @no_cache_control) || []
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'] == 'no-cache' ||
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, cache_control = nil)
24
+ def initialize(root, headers={})
25
25
  @root = root
26
- @cache_control = cache_control
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
- parts.inject(0) do |depth, part|
44
- case part
45
- when '', '.'
46
- depth
47
- when '..'
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, *parts)
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
- response[1].merge! 'Cache-Control' => @cache_control if @cache_control
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"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
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
@@ -9,6 +9,7 @@ class Head
9
9
  status, headers, body = @app.call(env)
10
10
 
11
11
  if env["REQUEST_METHOD"] == "HEAD"
12
+ body.close if body.respond_to? :close
12
13
  [status, headers, []]
13
14
  else
14
15
  [status, headers, body]
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
- response[2] = BodyProxy.new(response[2]) { @mutex.unlock }
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/octet-stream",
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 -= @boundary_size
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
- read_buffer = @io.gets
72
- break if read_buffer == full_boundary
73
- raise EOFError, "bad content body" if read_buffer.nil?
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 < @content_length ? BUFSIZE : @content_length)
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]
@@ -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
@@ -101,7 +101,7 @@ module Rack
101
101
  return unless file
102
102
  stat = ::File.stat(file)
103
103
  return file, stat if stat.file?
104
- rescue Errno::ENOENT, Errno::ENOTDIR
104
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ESRCH
105
105
  @cache.delete(file) and false
106
106
  end
107
107
  end
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, ';,').each { |k,v| hash[k] = Array === v ? v.first : v }
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] = path.split(":")
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://hoohoo.ncsa.uiuc.edu/cgi/cl.html
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
@@ -116,6 +116,11 @@ module Rack
116
116
  super
117
117
  end
118
118
 
119
+ def merge!(hash)
120
+ load_for_write!
121
+ super
122
+ end
123
+
119
124
  private
120
125
 
121
126
  def load_for_read!
@@ -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 == generate_hmac(session_data, secret)
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
- # 'public/index.html'
29
+ # 'index.html'
30
30
  #
31
- # Set a fixed Cache-Control header for all served files:
31
+ # Set custom HTTP Headers for based on rules:
32
32
  #
33
- # use Rack::Static, :root => 'public', :cache_control => '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
- cache_control = options[:cache_control]
44
- @file_server = Rack::File.new(root, cache_control)
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 == '/' ? @index : @urls[path]) if overwrite_file_path(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 < 3
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
- k, v = p.split('=', 2).map { |x| unescape(x) }
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
- http_range.split(/,\s*/).each do |range_spec|
315
- matches = range_spec.match(/bytes=(\d*)-(\d*)/)
316
- return nil unless matches
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 = [size - r1.to_i, 0].max
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 unless @params.key?(key)
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.1"
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.rubyforge.org.
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.rubyforge.org'
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 ###