rack 1.3.10 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (83) hide show
  1. data/COPYING +1 -1
  2. data/KNOWN-ISSUES +0 -9
  3. data/README.rdoc +4 -118
  4. data/Rakefile +15 -0
  5. data/SPEC +3 -5
  6. data/lib/rack.rb +0 -12
  7. data/lib/rack/auth/abstract/request.rb +1 -5
  8. data/lib/rack/auth/basic.rb +1 -1
  9. data/lib/rack/auth/digest/nonce.rb +1 -1
  10. data/lib/rack/backports/uri/common_18.rb +28 -14
  11. data/lib/rack/backports/uri/common_192.rb +17 -14
  12. data/lib/rack/body_proxy.rb +0 -10
  13. data/lib/rack/builder.rb +26 -18
  14. data/lib/rack/cascade.rb +1 -12
  15. data/lib/rack/chunked.rb +2 -0
  16. data/lib/rack/content_type.rb +7 -1
  17. data/lib/rack/deflater.rb +1 -5
  18. data/lib/rack/directory.rb +5 -1
  19. data/lib/rack/file.rb +26 -9
  20. data/lib/rack/handler.rb +2 -2
  21. data/lib/rack/head.rb +0 -1
  22. data/lib/rack/lint.rb +3 -5
  23. data/lib/rack/methodoverride.rb +10 -4
  24. data/lib/rack/mime.rb +606 -171
  25. data/lib/rack/mock.rb +2 -1
  26. data/lib/rack/multipart.rb +2 -2
  27. data/lib/rack/multipart/parser.rb +3 -10
  28. data/lib/rack/reloader.rb +1 -1
  29. data/lib/rack/request.rb +45 -13
  30. data/lib/rack/response.rb +15 -14
  31. data/lib/rack/sendfile.rb +8 -6
  32. data/lib/rack/server.rb +4 -30
  33. data/lib/rack/session/abstract/id.rb +25 -6
  34. data/lib/rack/session/cookie.rb +12 -16
  35. data/lib/rack/static.rb +21 -8
  36. data/lib/rack/urlmap.rb +28 -13
  37. data/lib/rack/utils.rb +22 -28
  38. data/rack.gemspec +5 -5
  39. data/test/builder/end.ru +2 -0
  40. data/test/cgi/lighttpd.conf +1 -0
  41. data/test/cgi/sample_rackup.ru +1 -1
  42. data/test/cgi/test+directory/test+file +1 -0
  43. data/test/cgi/test.ru +1 -1
  44. data/test/gemloader.rb +6 -2
  45. data/test/spec_auth_basic.rb +4 -9
  46. data/test/spec_auth_digest.rb +3 -16
  47. data/test/spec_body_proxy.rb +0 -4
  48. data/test/spec_builder.rb +63 -20
  49. data/test/spec_cascade.rb +10 -13
  50. data/test/spec_cgi.rb +1 -1
  51. data/test/spec_chunked.rb +39 -12
  52. data/test/spec_commonlogger.rb +4 -3
  53. data/test/spec_conditionalget.rb +16 -12
  54. data/test/spec_content_length.rb +1 -1
  55. data/test/spec_content_type.rb +6 -0
  56. data/test/spec_deflater.rb +2 -2
  57. data/test/spec_directory.rb +12 -0
  58. data/test/spec_fastcgi.rb +1 -1
  59. data/test/spec_file.rb +58 -8
  60. data/test/spec_head.rb +6 -18
  61. data/test/spec_lint.rb +2 -2
  62. data/test/spec_methodoverride.rb +15 -0
  63. data/test/spec_mock.rb +6 -2
  64. data/test/spec_mongrel.rb +8 -8
  65. data/test/spec_multipart.rb +10 -63
  66. data/test/spec_request.rb +94 -21
  67. data/test/spec_response.rb +22 -24
  68. data/test/spec_sendfile.rb +3 -0
  69. data/test/spec_server.rb +2 -49
  70. data/test/spec_session_cookie.rb +58 -22
  71. data/test/spec_session_memcache.rb +31 -1
  72. data/test/spec_session_pool.rb +10 -4
  73. data/test/spec_static.rb +8 -0
  74. data/test/spec_thin.rb +2 -2
  75. data/test/spec_utils.rb +38 -35
  76. data/test/spec_webrick.rb +5 -3
  77. data/test/static/index.html +1 -0
  78. metadata +13 -18
  79. data/contrib/rack.png +0 -0
  80. data/contrib/rack.svg +0 -150
  81. data/lib/rack/backports/uri/common_193.rb +0 -29
  82. data/test/builder/line.ru +0 -1
  83. data/test/spec_auth.rb +0 -57
@@ -57,6 +57,7 @@ module Rack
57
57
  def post(uri, opts={}) request("POST", uri, opts) end
58
58
  def put(uri, opts={}) request("PUT", uri, opts) end
59
59
  def delete(uri, opts={}) request("DELETE", uri, opts) end
60
+ def head(uri, opts={}) request("HEAD", uri, opts) end
60
61
 
61
62
  def request(method="GET", uri="", opts={})
62
63
  env = self.class.env_for(uri, opts.merge(:method => method))
@@ -182,7 +183,7 @@ module Rack
182
183
  end
183
184
 
184
185
  def empty?
185
- [201, 204, 304].include? status
186
+ [201, 204, 205, 304].include? status
186
187
  end
187
188
  end
188
189
  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
@@ -78,16 +78,9 @@ module Rack
78
78
 
79
79
  def fast_forward_to_first_boundary
80
80
  loop do
81
- content = @io.read(BUFSIZE)
82
- raise EOFError, "bad content body" unless content
83
- @buf << content
84
-
85
- while @buf.gsub!(/\A([^\n]*\n)/, '')
86
- read_buffer = $1
87
- return if read_buffer == full_boundary
88
- end
89
-
90
- raise EOFError, "bad content body" if Utils.bytesize(@buf) >= BUFSIZE
81
+ read_buffer = @io.gets
82
+ break if read_buffer == full_boundary
83
+ raise EOFError, "bad content body" if read_buffer.nil?
91
84
  end
92
85
  end
93
86
 
@@ -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, Errno::ESRCH
104
+ rescue Errno::ENOENT, Errno::ENOTDIR
105
105
  @cache.delete(file) and false
106
106
  end
107
107
  end
@@ -72,6 +72,8 @@ module Rack
72
72
  'https'
73
73
  elsif @env['HTTP_X_FORWARDED_SSL'] == 'on'
74
74
  'https'
75
+ elsif @env['HTTP_X_FORWARDED_SCHEME']
76
+ @env['HTTP_X_FORWARDED_SCHEME']
75
77
  elsif @env['HTTP_X_FORWARDED_PROTO']
76
78
  @env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
77
79
  else
@@ -113,15 +115,32 @@ module Rack
113
115
  def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
114
116
  def path_info=(s); @env["PATH_INFO"] = s.to_s end
115
117
 
118
+
119
+ # Checks the HTTP request method (or verb) to see if it was of type DELETE
116
120
  def delete?; request_method == "DELETE" end
121
+
122
+ # Checks the HTTP request method (or verb) to see if it was of type GET
117
123
  def get?; request_method == "GET" end
124
+
125
+ # Checks the HTTP request method (or verb) to see if it was of type HEAD
118
126
  def head?; request_method == "HEAD" end
127
+
128
+ # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
119
129
  def options?; request_method == "OPTIONS" end
130
+
131
+ # Checks the HTTP request method (or verb) to see if it was of type PATCH
120
132
  def patch?; request_method == "PATCH" end
133
+
134
+ # Checks the HTTP request method (or verb) to see if it was of type POST
121
135
  def post?; request_method == "POST" end
136
+
137
+ # Checks the HTTP request method (or verb) to see if it was of type PUT
122
138
  def put?; request_method == "PUT" end
139
+
140
+ # Checks the HTTP request method (or verb) to see if it was of type TRACE
123
141
  def trace?; request_method == "TRACE" end
124
142
 
143
+
125
144
  # The set of form-data media-types. Requests that do not indicate
126
145
  # one of the media types presents in this list will not be eligible
127
146
  # for form-data / param parsing.
@@ -233,8 +252,8 @@ module Rack
233
252
  hash = @env["rack.request.cookie_hash"] ||= {}
234
253
  string = @env["HTTP_COOKIE"]
235
254
 
236
- hash.clear unless string
237
255
  return hash if string == @env["rack.request.cookie_string"]
256
+ hash.clear
238
257
 
239
258
  # According to RFC 2109:
240
259
  # If multiple cookies satisfy the criteria above, they are ordered in
@@ -245,7 +264,8 @@ module Rack
245
264
  @env["rack.request.cookie_string"] = string
246
265
  hash
247
266
  rescue => error
248
- raise error.class, "cannot parse Cookie header: #{error.message}"
267
+ error.message.replace "cannot parse Cookie header: #{error.message}"
268
+ raise
249
269
  end
250
270
 
251
271
  def xhr?
@@ -278,23 +298,35 @@ module Rack
278
298
  end
279
299
 
280
300
  def accept_encoding
281
- @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part|
282
- m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick
283
-
284
- if m
285
- [m[1], (m[2] || 1.0).to_f]
286
- else
287
- raise "Invalid value for Accept-Encoding: #{part.inspect}"
301
+ @env["HTTP_ACCEPT_ENCODING"].to_s.split(/\s*,\s*/).map do |part|
302
+ encoding, parameters = part.split(/\s*;\s*/, 2)
303
+ quality = 1.0
304
+ if parameters and /\Aq=([\d.]+)/ =~ parameters
305
+ quality = $1.to_f
288
306
  end
307
+ [encoding, quality]
289
308
  end
290
309
  end
291
310
 
311
+ def trusted_proxy?(ip)
312
+ ip =~ /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|^::1$|^fd[0-9a-f]{2}:.+|^localhost$/i
313
+ end
314
+
292
315
  def ip
293
- if addr = @env['HTTP_X_FORWARDED_FOR']
294
- (addr.split(',').grep(/\d\./).first || @env['REMOTE_ADDR']).to_s.strip
295
- else
296
- @env['REMOTE_ADDR']
316
+ remote_addrs = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
317
+ remote_addrs.reject! { |addr| trusted_proxy?(addr) }
318
+
319
+ return remote_addrs.first if remote_addrs.any?
320
+
321
+ forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
322
+
323
+ if client_ip = @env['HTTP_CLIENT_IP']
324
+ # If forwarded_ips doesn't include the client_ip, it might be an
325
+ # ip spoofing attempt, so we ignore HTTP_CLIENT_IP
326
+ return client_ip if forwarded_ips.include?(client_ip)
297
327
  end
328
+
329
+ return forwarded_ips.reject { |ip| trusted_proxy?(ip) }.last || @env["REMOTE_ADDR"]
298
330
  end
299
331
 
300
332
  protected
@@ -19,7 +19,7 @@ module Rack
19
19
  class Response
20
20
  attr_accessor :length
21
21
 
22
- def initialize(body=[], status=200, header={}, &block)
22
+ def initialize(body=[], status=200, header={})
23
23
  @status = status.to_i
24
24
  @header = Utils::HeaderHash.new("Content-Type" => "text/html").
25
25
  merge(header)
@@ -71,13 +71,12 @@ module Rack
71
71
  def finish(&block)
72
72
  @block = block
73
73
 
74
- if [204, 304].include?(status.to_i)
74
+ if [204, 205, 304].include?(status.to_i)
75
75
  header.delete "Content-Type"
76
76
  header.delete "Content-Length"
77
- close
78
77
  [status.to_i, header, []]
79
78
  else
80
- [status.to_i, header, BodyProxy.new(self){}]
79
+ [status.to_i, header, self]
81
80
  end
82
81
  end
83
82
  alias to_a finish # For *response
@@ -113,19 +112,21 @@ module Rack
113
112
  alias headers header
114
113
 
115
114
  module Helpers
116
- def invalid?; @status < 100 || @status >= 600; end
115
+ def invalid?; status < 100 || status >= 600; end
117
116
 
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
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
123
122
 
124
- def ok?; @status == 200; end
125
- def forbidden?; @status == 403; end
126
- def not_found?; @status == 404; end
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
127
128
 
128
- def redirect?; [301, 302, 303, 307].include? @status; end
129
+ def redirect?; [301, 302, 303, 307].include? status; end
129
130
 
130
131
  # Headers
131
132
  attr_reader :headers, :original_headers
@@ -46,10 +46,11 @@ module Rack
46
46
  # proxy_pass http://127.0.0.1:8080/;
47
47
  # }
48
48
  #
49
- # Note that the X-Sendfile-Type header must be set exactly as shown above. The
50
- # X-Accel-Mapping header should specify the internal URI path, followed by an
51
- # equals sign (=), followed name of the location in the file system that it maps
52
- # to. The middleware performs a simple substitution on the resulting path.
49
+ # Note that the X-Sendfile-Type header must be set exactly as shown above.
50
+ # The X-Accel-Mapping header should specify the location on the file system,
51
+ # followed by an equals sign (=), followed name of the private URL pattern
52
+ # that it maps to. The middleware performs a simple substitution on the
53
+ # resulting path.
53
54
  #
54
55
  # See Also: http://wiki.codemongers.com/NginxXSendfile
55
56
  #
@@ -104,10 +105,11 @@ module Rack
104
105
  when 'X-Accel-Redirect'
105
106
  path = F.expand_path(body.to_path)
106
107
  if url = map_accel_path(env, path)
108
+ headers['Content-Length'] = '0'
107
109
  headers[type] = url
108
110
  body = []
109
111
  else
110
- env['rack.errors'] << "X-Accel-Mapping header missing"
112
+ env['rack.errors'].puts "X-Accel-Mapping header missing"
111
113
  end
112
114
  when 'X-Sendfile', 'X-Lighttpd-Send-File'
113
115
  path = F.expand_path(body.to_path)
@@ -116,7 +118,7 @@ module Rack
116
118
  body = []
117
119
  when '', nil
118
120
  else
119
- env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n"
121
+ env['rack.errors'].puts "Unknown x-sendfile variation: '#{variation}'.\n"
120
122
  end
121
123
  end
122
124
  [status, headers, body]
@@ -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] ||= []).concat(path.split(":"))
29
+ options[:include] = path.split(":")
30
30
  }
31
31
 
32
32
  opts.on("-r", "--require LIBRARY",
@@ -226,7 +226,7 @@ module Rack
226
226
  self.class.middleware
227
227
  end
228
228
 
229
- def start
229
+ def start &blk
230
230
  if options[:warn]
231
231
  $-w = true
232
232
  end
@@ -247,14 +247,11 @@ module Rack
247
247
  pp app
248
248
  end
249
249
 
250
- check_pid! if options[:pid]
251
-
252
250
  # Touch the wrapped app, so that the config.ru is loaded before
253
251
  # daemonization (i.e. before chdir, etc).
254
252
  wrapped_app
255
253
 
256
254
  daemonize_app if options[:daemonize]
257
-
258
255
  write_pid if options[:pid]
259
256
 
260
257
  trap(:INT) do
@@ -265,7 +262,7 @@ module Rack
265
262
  end
266
263
  end
267
264
 
268
- server.run wrapped_app, options
265
+ server.run wrapped_app, options, &blk
269
266
  end
270
267
 
271
268
  def server
@@ -277,7 +274,7 @@ module Rack
277
274
  options = default_options
278
275
 
279
276
  # Don't evaluate CGI ISINDEX parameters.
280
- # http://www.meb.uni-bonn.de/docs/cgi/cl.html
277
+ # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
281
278
  args.clear if ENV.include?("REQUEST_METHOD")
282
279
 
283
280
  options.merge! opt_parser.parse!(args)
@@ -322,28 +319,5 @@ module Rack
322
319
  ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") }
323
320
  at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) }
324
321
  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
-
348
322
  end
349
323
  end
@@ -95,8 +95,11 @@ module Rack
95
95
  end
96
96
 
97
97
  def inspect
98
- load_for_read!
99
- super
98
+ if loaded?
99
+ super
100
+ else
101
+ "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
102
+ end
100
103
  end
101
104
 
102
105
  def exists?
@@ -108,6 +111,11 @@ module Rack
108
111
  @loaded
109
112
  end
110
113
 
114
+ def empty?
115
+ load_for_read!
116
+ super
117
+ end
118
+
111
119
  private
112
120
 
113
121
  def load_for_read!
@@ -144,7 +152,9 @@ module Rack
144
152
  # 'rack.session'
145
153
  # * :path, :domain, :expire_after, :secure, and :httponly set the related
146
154
  # cookie options as by Rack::Response#add_cookie
147
- # * :defer will not set a cookie in the response.
155
+ # * :skip will not a set a cookie in the response nor update the session state
156
+ # * :defer will not set a cookie in the response but still update the session
157
+ # state if it is used with a backend
148
158
  # * :renew (implementation dependent) will prompt the generation of a new
149
159
  # session id, and migration of data to be referenced at the new id. If
150
160
  # :defer is set, it will be overridden and the cookie will be set.
@@ -260,21 +270,30 @@ module Rack
260
270
  end
261
271
 
262
272
  # Session should be commited if it was loaded, any of specific options like :renew, :drop
263
- # or :expire_after was given and the security permissions match.
273
+ # or :expire_after was given and the security permissions match. Skips if skip is given.
264
274
 
265
275
  def commit_session?(env, session, options)
266
- (loaded_session?(session) || force_options?(options)) && secure_session?(env, options)
276
+ if options[:skip]
277
+ false
278
+ else
279
+ has_session = loaded_session?(session) || forced_session_update?(session, options)
280
+ has_session && security_matches?(env, options)
281
+ end
267
282
  end
268
283
 
269
284
  def loaded_session?(session)
270
285
  !session.is_a?(SessionHash) || session.loaded?
271
286
  end
272
287
 
288
+ def forced_session_update?(session, options)
289
+ force_options?(options) && session && !session.empty?
290
+ end
291
+
273
292
  def force_options?(options)
274
293
  options.values_at(:renew, :drop, :defer, :expire_after).any?
275
294
  end
276
295
 
277
- def secure_session?(env, options)
296
+ def security_matches?(env, options)
278
297
  return true unless options[:secure]
279
298
  request = Rack::Request.new(env)
280
299
  request.ssl?
@@ -14,6 +14,7 @@ module Rack
14
14
  # Both methods must take a string and return a string.
15
15
  #
16
16
  # When the secret key is set, cookie data is checked for data integrity.
17
+ # The old secret key is also accepted and allows graceful secret rotation.
17
18
  #
18
19
  # Example:
19
20
  #
@@ -21,14 +22,15 @@ module Rack
21
22
  # :domain => 'foo.com',
22
23
  # :path => '/',
23
24
  # :expire_after => 2592000,
24
- # :secret => 'change_me'
25
+ # :secret => 'change_me',
26
+ # :old_secret => 'also_change_me'
25
27
  #
26
28
  # All parameters are optional.
27
29
  #
28
30
  # Example of a cookie with no encoding:
29
31
  #
30
32
  # Rack::Session::Cookie.new(application, {
31
- # :coder => Racke::Session::Cookie::Identity.new
33
+ # :coder => Rack::Session::Cookie::Identity.new
32
34
  # })
33
35
  #
34
36
  # Example of a cookie with custom encoding:
@@ -80,15 +82,7 @@ module Rack
80
82
 
81
83
  def initialize(app, options={})
82
84
  @secret = options[:secret]
83
- warn <<-MSG unless @secret
84
- SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
85
- This poses a security threat. It is strongly recommended that you
86
- provide a secret to prevent exploits that may be possible from crafted
87
- cookies. This will not be supported in future versions of Rack, and
88
- future versions will even invalidate your existing user cookies.
89
-
90
- Called from: #{caller[0]}.
91
- MSG
85
+ @old_secret = options[:old_secret]
92
86
  @coder = options[:coder] ||= Base64::Marshal.new
93
87
  super(app, options.merge!(:cookie_only => true))
94
88
  end
@@ -110,9 +104,11 @@ module Rack
110
104
  request = Rack::Request.new(env)
111
105
  session_data = request.cookies[@key]
112
106
 
113
- if @secret && session_data
107
+ if (@secret || @old_secret) && session_data
114
108
  session_data, digest = session_data.split("--")
115
- session_data = nil unless Rack::Utils.secure_compare(digest, generate_hmac(session_data))
109
+ if (digest != generate_hmac(session_data, @secret)) && (digest != generate_hmac(session_data, @old_secret))
110
+ session_data = nil
111
+ end
116
112
  end
117
113
 
118
114
  coder.decode(session_data) || {}
@@ -136,7 +132,7 @@ module Rack
136
132
  session_data = coder.encode(session)
137
133
 
138
134
  if @secret
139
- session_data = "#{session_data}--#{generate_hmac(session_data)}"
135
+ session_data = "#{session_data}--#{generate_hmac(session_data, @secret)}"
140
136
  end
141
137
 
142
138
  if session_data.size > (4096 - @key.size)
@@ -152,8 +148,8 @@ module Rack
152
148
  generate_sid unless options[:drop]
153
149
  end
154
150
 
155
- def generate_hmac(data)
156
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
151
+ def generate_hmac(data, secret)
152
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
157
153
  end
158
154
 
159
155
  end