rack 1.4.0 → 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 (88) hide show
  1. data/COPYING +1 -1
  2. data/KNOWN-ISSUES +9 -0
  3. data/README.rdoc +118 -4
  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 +15 -2
  16. data/lib/rack/builder.rb +1 -1
  17. data/lib/rack/cascade.rb +12 -1
  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 +20 -16
  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 +22 -20
  29. data/lib/rack/multipart.rb +2 -2
  30. data/lib/rack/reloader.rb +1 -1
  31. data/lib/rack/request.rb +4 -6
  32. data/lib/rack/response.rb +19 -17
  33. data/lib/rack/server.rb +28 -2
  34. data/lib/rack/session/abstract/id.rb +8 -3
  35. data/lib/rack/session/cookie.rb +20 -7
  36. data/lib/rack/showstatus.rb +2 -2
  37. data/lib/rack/static.rb +91 -9
  38. data/lib/rack/utils.rb +74 -36
  39. data/lib/rack.rb +13 -1
  40. data/rack.gemspec +3 -3
  41. data/test/builder/line.ru +1 -0
  42. data/test/cgi/assets/folder/test.js +1 -0
  43. data/test/cgi/assets/fonts/font.eot +1 -0
  44. data/test/cgi/assets/images/image.png +1 -0
  45. data/test/cgi/assets/index.html +1 -0
  46. data/test/cgi/assets/javascripts/app.js +1 -0
  47. data/test/cgi/assets/stylesheets/app.css +1 -0
  48. data/test/multipart/filename_with_unescaped_percentages +6 -0
  49. data/test/multipart/filename_with_unescaped_percentages2 +6 -0
  50. data/test/multipart/filename_with_unescaped_percentages3 +6 -0
  51. data/test/spec_auth.rb +57 -0
  52. data/test/spec_auth_basic.rb +8 -0
  53. data/test/spec_auth_digest.rb +14 -0
  54. data/test/spec_body_proxy.rb +21 -0
  55. data/test/spec_builder.rb +7 -1
  56. data/test/spec_cascade.rb +14 -3
  57. data/test/spec_chunked.rb +6 -6
  58. data/test/spec_config.rb +0 -1
  59. data/test/spec_content_length.rb +26 -13
  60. data/test/spec_content_type.rb +15 -5
  61. data/test/spec_deflater.rb +35 -17
  62. data/test/spec_directory.rb +20 -1
  63. data/test/spec_etag.rb +29 -13
  64. data/test/spec_file.rb +58 -30
  65. data/test/spec_head.rb +25 -7
  66. data/test/spec_lobster.rb +20 -5
  67. data/test/spec_lock.rb +46 -21
  68. data/test/spec_logger.rb +2 -7
  69. data/test/spec_methodoverride.rb +21 -22
  70. data/test/spec_mock.rb +12 -7
  71. data/test/spec_multipart.rb +129 -2
  72. data/test/spec_nulllogger.rb +13 -2
  73. data/test/spec_recursive.rb +12 -9
  74. data/test/spec_request.rb +15 -2
  75. data/test/spec_response.rb +41 -3
  76. data/test/spec_runtime.rb +15 -5
  77. data/test/spec_sendfile.rb +13 -9
  78. data/test/spec_server.rb +47 -0
  79. data/test/spec_session_cookie.rb +93 -3
  80. data/test/spec_session_memcache.rb +10 -8
  81. data/test/spec_session_pool.rb +13 -10
  82. data/test/spec_showexceptions.rb +9 -4
  83. data/test/spec_showstatus.rb +10 -5
  84. data/test/spec_static.rb +89 -8
  85. data/test/spec_urlmap.rb +10 -10
  86. data/test/spec_utils.rb +34 -7
  87. data/test/static/another/index.html +1 -0
  88. metadata +26 -8
data/lib/rack/deflater.rb CHANGED
@@ -45,6 +45,7 @@ module Rack
45
45
  when "identity"
46
46
  [status, headers, body]
47
47
  when nil
48
+ body.close if body.respond_to?(:close)
48
49
  message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
49
50
  [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
50
51
  end
@@ -64,6 +65,7 @@ module Rack
64
65
  gzip.write(part)
65
66
  gzip.flush
66
67
  }
68
+ ensure
67
69
  @body.close if @body.respond_to?(:close)
68
70
  gzip.close
69
71
  @writer = nil
@@ -90,9 +92,11 @@ module Rack
90
92
  def each
91
93
  deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
92
94
  @body.each { |part| yield deflater.deflate(part, Zlib::SYNC_FLUSH) }
93
- @body.close if @body.respond_to?(:close)
94
95
  yield deflater.finish
95
96
  nil
97
+ ensure
98
+ @body.close if @body.respond_to?(:close)
99
+ deflater.close
96
100
  end
97
101
  end
98
102
  end
@@ -80,7 +80,7 @@ table { width:100%%; }
80
80
  @files = [['../','Parent Directory','','','']]
81
81
  glob = F.join(@path, '*')
82
82
 
83
- url_head = ([@script_name] + @path_info.split('/')).map do |part|
83
+ url_head = (@script_name.split('/') + @path_info.split('/')).map do |part|
84
84
  Rack::Utils.escape part
85
85
  end
86
86
 
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)
@@ -34,25 +41,20 @@ module Rack
34
41
 
35
42
  def _call(env)
36
43
  unless ALLOWED_VERBS.include? env["REQUEST_METHOD"]
37
- return fail(403, "Forbidden")
44
+ return fail(405, "Method Not Allowed")
38
45
  end
39
46
 
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(403, "Forbidden") 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
 
@@ -14,9 +14,6 @@ module Rack
14
14
 
15
15
  fast_forward_to_first_boundary
16
16
 
17
- max_key_space = Utils.key_space_limit
18
- bytes = 0
19
-
20
17
  loop do
21
18
  head, filename, content_type, name, body =
22
19
  get_current_head_and_filename_and_content_type_and_name_and_body
@@ -31,13 +28,6 @@ module Rack
31
28
 
32
29
  filename, data = get_data(filename, body, content_type, name, head)
33
30
 
34
- if name
35
- bytes += name.size
36
- if bytes > max_key_space
37
- raise RangeError, "exceeded available parameter key space"
38
- end
39
- end
40
-
41
31
  Utils.normalize_params(@params, name, data) unless data.nil?
42
32
 
43
33
  # break if we're at the end of a buffer, but not if it is the end of a field
@@ -46,7 +36,7 @@ module Rack
46
36
 
47
37
  @io.rewind
48
38
 
49
- @params
39
+ @params.to_params_hash
50
40
  end
51
41
 
52
42
  private
@@ -56,15 +46,17 @@ module Rack
56
46
  @boundary = "--#{$1}"
57
47
 
58
48
  @buf = ""
59
- @params = {}
49
+ @params = Utils::KeySpaceConstrainedParams.new
60
50
 
61
- @content_length = @env['CONTENT_LENGTH'].to_i
62
51
  @io = @env['rack.input']
63
52
  @io.rewind
64
53
 
65
54
  @boundary_size = Utils.bytesize(@boundary) + EOL.size
66
55
 
67
- @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
68
60
  true
69
61
  end
70
62
 
@@ -78,9 +70,16 @@ module Rack
78
70
 
79
71
  def fast_forward_to_first_boundary
80
72
  loop do
81
- read_buffer = @io.gets
82
- break if read_buffer == full_boundary
83
- 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
84
83
  end
85
84
  end
86
85
 
@@ -114,11 +113,11 @@ module Rack
114
113
  body << @buf.slice!(0, @buf.size - (@boundary_size+4))
115
114
  end
116
115
 
117
- content = @io.read(BUFSIZE < @content_length ? BUFSIZE : @content_length)
116
+ content = @io.read(@content_length && BUFSIZE >= @content_length ? @content_length : BUFSIZE)
118
117
  raise EOFError, "bad content body" if content.nil? || content.empty?
119
118
 
120
119
  @buf << content
121
- @content_length -= content.size
120
+ @content_length -= content.size if @content_length
122
121
  end
123
122
 
124
123
  [head, filename, content_type, name, body]
@@ -135,8 +134,11 @@ module Rack
135
134
  filename = $1
136
135
  end
137
136
 
137
+ if filename && filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
138
+ filename = Utils.unescape(filename)
139
+ end
138
140
  if filename && filename !~ /\\[^\\"]/
139
- filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
141
+ filename = filename.gsub(/\\(.)/, '\1')
140
142
  end
141
143
  filename
142
144
  end
@@ -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
@@ -177,7 +177,7 @@ module Rack
177
177
  PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
178
178
  end
179
179
 
180
- # Returns the data recieved in the query string.
180
+ # Returns the data received in the query string.
181
181
  def GET
182
182
  if @env["rack.request.query_string"] == query_string
183
183
  @env["rack.request.query_hash"]
@@ -187,7 +187,7 @@ module Rack
187
187
  end
188
188
  end
189
189
 
190
- # Returns the data recieved in the request body.
190
+ # Returns the data received in the request body.
191
191
  #
192
192
  # This method support both application/x-www-form-urlencoded and
193
193
  # multipart/form-data.
@@ -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
@@ -12,7 +12,7 @@ module Rack
12
12
  # You can use Response#write to iteratively generate your response,
13
13
  # but note that this is buffered by Rack::Response until you call
14
14
  # +finish+. +finish+ however can take a block inside which calls to
15
- # +write+ are syncronous with the Rack response.
15
+ # +write+ are synchronous with the Rack response.
16
16
  #
17
17
  # Your application's +call+ should end returning Response#finish.
18
18
 
@@ -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
@@ -112,21 +113,22 @@ module Rack
112
113
  alias headers header
113
114
 
114
115
  module Helpers
115
- def invalid?; status < 100 || status >= 600; end
116
-
117
- def informational?; status >= 100 && status < 200; end
118
- def successful?; status >= 200 && status < 300; end
119
- def redirection?; status >= 300 && status < 400; end
120
- def client_error?; status >= 400 && status < 500; end
121
- def server_error?; status >= 500 && status < 600; end
122
-
123
- def ok?; status == 200; end
124
- def bad_request?; status == 400; end
125
- def forbidden?; status == 403; end
126
- def not_found?; status == 404; end
127
- def unprocessable?; status == 422; end
128
-
129
- def redirect?; [301, 302, 303, 307].include? status; end
116
+ def invalid?; status < 100 || status >= 600; end
117
+
118
+ def informational?; status >= 100 && status < 200; end
119
+ def successful?; status >= 200 && status < 300; end
120
+ def redirection?; status >= 300 && status < 400; end
121
+ def client_error?; status >= 400 && status < 500; end
122
+ def server_error?; status >= 500 && status < 600; end
123
+
124
+ def ok?; status == 200; end
125
+ def bad_request?; status == 400; end
126
+ def forbidden?; status == 403; end
127
+ def not_found?; status == 404; end
128
+ def method_not_allowed?; status == 405; end
129
+ def unprocessable?; status == 422; end
130
+
131
+ def redirect?; [301, 302, 303, 307].include? status; end
130
132
 
131
133
  # Headers
132
134
  attr_reader :headers, :original_headers
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
@@ -36,7 +36,7 @@ module Rack
36
36
  private
37
37
 
38
38
  def session_id_not_loaded?
39
- !key?(:id) && !@session_id_loaded
39
+ !(@session_id_loaded || key?(:id))
40
40
  end
41
41
 
42
42
  def load_session_id!
@@ -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!
@@ -183,7 +188,7 @@ module Rack
183
188
  :renew => false,
184
189
  :sidbits => 128,
185
190
  :cookie_only => true,
186
- :secure_random => begin ::SecureRandom rescue false end
191
+ :secure_random => (::SecureRandom rescue false)
187
192
  }
188
193
 
189
194
  attr_reader :key, :default_options
@@ -191,7 +196,7 @@ module Rack
191
196
  def initialize(app, options={})
192
197
  @app = app
193
198
  @default_options = self.class::DEFAULT_OPTIONS.merge(options)
194
- @key = options[:key] || "rack.session"
199
+ @key = @default_options.delete(:key)
195
200
  @cookie_only = @default_options.delete(:cookie_only)
196
201
  initialize_sid
197
202
  end
@@ -81,8 +81,16 @@ module Rack
81
81
  attr_reader :coder
82
82
 
83
83
  def initialize(app, options={})
84
- @secret = options[:secret]
85
- @old_secret = options[:old_secret]
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
86
94
  @coder = options[:coder] ||= Base64::Marshal.new
87
95
  super(app, options.merge!(:cookie_only => true))
88
96
  end
@@ -104,11 +112,16 @@ module Rack
104
112
  request = Rack::Request.new(env)
105
113
  session_data = request.cookies[@key]
106
114
 
107
- if (@secret || @old_secret) && session_data
115
+ if @secrets.size > 0 && session_data
108
116
  session_data, digest = session_data.split("--")
109
- if (digest != generate_hmac(session_data, @secret)) && (digest != generate_hmac(session_data, @old_secret))
110
- session_data = nil
117
+
118
+ if session_data && digest
119
+ ok = @secrets.any? do |secret|
120
+ secret && Rack::Utils.secure_compare(digest, generate_hmac(session_data, secret))
121
+ end
111
122
  end
123
+
124
+ session_data = nil unless ok
112
125
  end
113
126
 
114
127
  coder.decode(session_data) || {}
@@ -131,8 +144,8 @@ module Rack
131
144
  session = session.merge("session_id" => session_id)
132
145
  session_data = coder.encode(session)
133
146
 
134
- if @secret
135
- session_data = "#{session_data}--#{generate_hmac(session_data, @secret)}"
147
+ if @secrets.first
148
+ session_data = "#{session_data}--#{generate_hmac(session_data, @secrets.first)}"
136
149
  end
137
150
 
138
151
  if session_data.size > (4096 - @key.size)
@@ -3,8 +3,8 @@ require 'rack/request'
3
3
  require 'rack/utils'
4
4
 
5
5
  module Rack
6
- # Rack::ShowStatus catches all empty responses the app it wraps and
7
- # replaces them with a site explaining the error.
6
+ # Rack::ShowStatus catches all empty responses and replaces them
7
+ # with a site explaining the error.
8
8
  #
9
9
  # Additional details can be put into <tt>rack.showstatus.detail</tt>
10
10
  # and will be shown as HTML. If such details exist, the error page