rack 2.0.6 → 2.1.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/{HISTORY.md → CHANGELOG.md} +220 -155
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +77 -117
  5. data/Rakefile +25 -18
  6. data/SPEC +3 -4
  7. data/bin/rackup +1 -0
  8. data/example/lobster.ru +2 -0
  9. data/example/protectedlobster.rb +3 -1
  10. data/example/protectedlobster.ru +2 -0
  11. data/lib/rack/auth/abstract/handler.rb +3 -1
  12. data/lib/rack/auth/abstract/request.rb +2 -0
  13. data/lib/rack/auth/basic.rb +4 -1
  14. data/lib/rack/auth/digest/md5.rb +9 -7
  15. data/lib/rack/auth/digest/nonce.rb +6 -3
  16. data/lib/rack/auth/digest/params.rb +4 -2
  17. data/lib/rack/auth/digest/request.rb +2 -0
  18. data/lib/rack/body_proxy.rb +3 -6
  19. data/lib/rack/builder.rb +38 -15
  20. data/lib/rack/cascade.rb +6 -5
  21. data/lib/rack/chunked.rb +29 -6
  22. data/lib/rack/common_logger.rb +9 -8
  23. data/lib/rack/conditional_get.rb +3 -1
  24. data/lib/rack/config.rb +2 -0
  25. data/lib/rack/content_length.rb +3 -1
  26. data/lib/rack/content_type.rb +3 -1
  27. data/lib/rack/core_ext/regexp.rb +14 -0
  28. data/lib/rack/deflater.rb +28 -17
  29. data/lib/rack/directory.rb +17 -14
  30. data/lib/rack/etag.rb +3 -1
  31. data/lib/rack/events.rb +5 -3
  32. data/lib/rack/file.rb +5 -173
  33. data/lib/rack/files.rb +178 -0
  34. data/lib/rack/handler/cgi.rb +3 -1
  35. data/lib/rack/handler/fastcgi.rb +4 -2
  36. data/lib/rack/handler/lsws.rb +3 -1
  37. data/lib/rack/handler/scgi.rb +9 -6
  38. data/lib/rack/handler/thin.rb +3 -1
  39. data/lib/rack/handler/webrick.rb +4 -2
  40. data/lib/rack/handler.rb +7 -2
  41. data/lib/rack/head.rb +2 -0
  42. data/lib/rack/lint.rb +14 -11
  43. data/lib/rack/lobster.rb +7 -5
  44. data/lib/rack/lock.rb +2 -0
  45. data/lib/rack/logger.rb +2 -0
  46. data/lib/rack/media_type.rb +10 -5
  47. data/lib/rack/method_override.rb +4 -2
  48. data/lib/rack/mime.rb +9 -1
  49. data/lib/rack/mock.rb +74 -15
  50. data/lib/rack/multipart/generator.rb +6 -7
  51. data/lib/rack/multipart/parser.rb +55 -52
  52. data/lib/rack/multipart/uploaded_file.rb +2 -0
  53. data/lib/rack/multipart.rb +5 -3
  54. data/lib/rack/null_logger.rb +2 -0
  55. data/lib/rack/query_parser.rb +51 -25
  56. data/lib/rack/recursive.rb +7 -5
  57. data/lib/rack/reloader.rb +10 -4
  58. data/lib/rack/request.rb +80 -27
  59. data/lib/rack/response.rb +71 -31
  60. data/lib/rack/rewindable_input.rb +4 -2
  61. data/lib/rack/runtime.rb +4 -2
  62. data/lib/rack/sendfile.rb +15 -8
  63. data/lib/rack/server.rb +88 -16
  64. data/lib/rack/session/abstract/id.rb +104 -21
  65. data/lib/rack/session/cookie.rb +21 -11
  66. data/lib/rack/session/memcache.rb +4 -87
  67. data/lib/rack/session/pool.rb +17 -8
  68. data/lib/rack/show_exceptions.rb +15 -9
  69. data/lib/rack/show_status.rb +4 -2
  70. data/lib/rack/static.rb +15 -10
  71. data/lib/rack/tempfile_reaper.rb +2 -0
  72. data/lib/rack/urlmap.rb +11 -2
  73. data/lib/rack/utils.rb +55 -70
  74. data/lib/rack.rb +63 -60
  75. data/rack.gemspec +17 -7
  76. metadata +30 -171
  77. data/test/builder/an_underscore_app.rb +0 -5
  78. data/test/builder/anything.rb +0 -5
  79. data/test/builder/comment.ru +0 -4
  80. data/test/builder/end.ru +0 -5
  81. data/test/builder/line.ru +0 -1
  82. data/test/builder/options.ru +0 -2
  83. data/test/cgi/assets/folder/test.js +0 -1
  84. data/test/cgi/assets/fonts/font.eot +0 -1
  85. data/test/cgi/assets/images/image.png +0 -1
  86. data/test/cgi/assets/index.html +0 -1
  87. data/test/cgi/assets/javascripts/app.js +0 -1
  88. data/test/cgi/assets/stylesheets/app.css +0 -1
  89. data/test/cgi/lighttpd.conf +0 -26
  90. data/test/cgi/rackup_stub.rb +0 -6
  91. data/test/cgi/sample_rackup.ru +0 -5
  92. data/test/cgi/test +0 -9
  93. data/test/cgi/test+directory/test+file +0 -1
  94. data/test/cgi/test.fcgi +0 -9
  95. data/test/cgi/test.gz +0 -0
  96. data/test/cgi/test.ru +0 -5
  97. data/test/gemloader.rb +0 -10
  98. data/test/helper.rb +0 -34
  99. data/test/multipart/bad_robots +0 -259
  100. data/test/multipart/binary +0 -0
  101. data/test/multipart/content_type_and_no_filename +0 -6
  102. data/test/multipart/empty +0 -10
  103. data/test/multipart/fail_16384_nofile +0 -814
  104. data/test/multipart/file1.txt +0 -1
  105. data/test/multipart/filename_and_modification_param +0 -7
  106. data/test/multipart/filename_and_no_name +0 -6
  107. data/test/multipart/filename_with_encoded_words +0 -7
  108. data/test/multipart/filename_with_escaped_quotes +0 -6
  109. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  110. data/test/multipart/filename_with_null_byte +0 -7
  111. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_single_quote +0 -7
  113. data/test/multipart/filename_with_unescaped_percentages +0 -6
  114. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  115. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  116. data/test/multipart/filename_with_unescaped_quotes +0 -6
  117. data/test/multipart/ie +0 -6
  118. data/test/multipart/invalid_character +0 -6
  119. data/test/multipart/mixed_files +0 -21
  120. data/test/multipart/nested +0 -10
  121. data/test/multipart/none +0 -9
  122. data/test/multipart/quoted +0 -15
  123. data/test/multipart/rack-logo.png +0 -0
  124. data/test/multipart/semicolon +0 -6
  125. data/test/multipart/text +0 -15
  126. data/test/multipart/three_files_three_fields +0 -31
  127. data/test/multipart/unity3d_wwwform +0 -11
  128. data/test/multipart/webkit +0 -32
  129. data/test/rackup/config.ru +0 -31
  130. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  131. data/test/spec_auth_basic.rb +0 -89
  132. data/test/spec_auth_digest.rb +0 -260
  133. data/test/spec_body_proxy.rb +0 -85
  134. data/test/spec_builder.rb +0 -233
  135. data/test/spec_cascade.rb +0 -63
  136. data/test/spec_cgi.rb +0 -84
  137. data/test/spec_chunked.rb +0 -103
  138. data/test/spec_common_logger.rb +0 -95
  139. data/test/spec_conditional_get.rb +0 -103
  140. data/test/spec_config.rb +0 -23
  141. data/test/spec_content_length.rb +0 -86
  142. data/test/spec_content_type.rb +0 -46
  143. data/test/spec_deflater.rb +0 -375
  144. data/test/spec_directory.rb +0 -148
  145. data/test/spec_etag.rb +0 -108
  146. data/test/spec_events.rb +0 -133
  147. data/test/spec_fastcgi.rb +0 -85
  148. data/test/spec_file.rb +0 -264
  149. data/test/spec_handler.rb +0 -57
  150. data/test/spec_head.rb +0 -46
  151. data/test/spec_lint.rb +0 -515
  152. data/test/spec_lobster.rb +0 -59
  153. data/test/spec_lock.rb +0 -204
  154. data/test/spec_logger.rb +0 -24
  155. data/test/spec_media_type.rb +0 -42
  156. data/test/spec_method_override.rb +0 -110
  157. data/test/spec_mime.rb +0 -51
  158. data/test/spec_mock.rb +0 -359
  159. data/test/spec_multipart.rb +0 -722
  160. data/test/spec_null_logger.rb +0 -21
  161. data/test/spec_recursive.rb +0 -75
  162. data/test/spec_request.rb +0 -1398
  163. data/test/spec_response.rb +0 -510
  164. data/test/spec_rewindable_input.rb +0 -128
  165. data/test/spec_runtime.rb +0 -50
  166. data/test/spec_sendfile.rb +0 -125
  167. data/test/spec_server.rb +0 -193
  168. data/test/spec_session_abstract_id.rb +0 -31
  169. data/test/spec_session_abstract_session_hash.rb +0 -45
  170. data/test/spec_session_cookie.rb +0 -442
  171. data/test/spec_session_memcache.rb +0 -320
  172. data/test/spec_session_pool.rb +0 -210
  173. data/test/spec_show_exceptions.rb +0 -93
  174. data/test/spec_show_status.rb +0 -104
  175. data/test/spec_static.rb +0 -184
  176. data/test/spec_tempfile_reaper.rb +0 -64
  177. data/test/spec_thin.rb +0 -96
  178. data/test/spec_urlmap.rb +0 -237
  179. data/test/spec_utils.rb +0 -742
  180. data/test/spec_version.rb +0 -11
  181. data/test/spec_webrick.rb +0 -206
  182. data/test/static/another/index.html +0 -1
  183. data/test/static/foo.html +0 -1
  184. data/test/static/index.html +0 -1
  185. data/test/testrequest.rb +0 -78
  186. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  187. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/sendfile.rb CHANGED
@@ -1,4 +1,6 @@
1
- require 'rack/file'
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/files'
2
4
  require 'rack/body_proxy'
3
5
 
4
6
  module Rack
@@ -14,7 +16,7 @@ module Rack
14
16
  #
15
17
  # In order to take advantage of this middleware, the response body must
16
18
  # respond to +to_path+ and the request must include an X-Sendfile-Type
17
- # header. Rack::File and other components implement +to_path+ so there's
19
+ # header. Rack::Files and other components implement +to_path+ so there's
18
20
  # rarely anything you need to do in your application. The X-Sendfile-Type
19
21
  # header is typically set in your web servers configuration. The following
20
22
  # sections attempt to document
@@ -53,7 +55,7 @@ module Rack
53
55
  # that it maps to. The middleware performs a simple substitution on the
54
56
  # resulting path.
55
57
  #
56
- # See Also: http://wiki.codemongers.com/NginxXSendfile
58
+ # See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
57
59
  #
58
60
  # === lighttpd
59
61
  #
@@ -99,7 +101,7 @@ module Rack
99
101
  # will be matched with case indifference.
100
102
 
101
103
  class Sendfile
102
- def initialize(app, variation=nil, mappings=[])
104
+ def initialize(app, variation = nil, mappings = [])
103
105
  @app = app
104
106
  @variation = variation
105
107
  @mappings = mappings.map do |internal, external|
@@ -115,7 +117,8 @@ module Rack
115
117
  path = ::File.expand_path(body.to_path)
116
118
  if url = map_accel_path(env, path)
117
119
  headers[CONTENT_LENGTH] = '0'
118
- headers[type] = url
120
+ # '?' must be percent-encoded because it is not query string but a part of path
121
+ headers[type] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
119
122
  obody = body
120
123
  body = Rack::BodyProxy.new([]) do
121
124
  obody.close if obody.respond_to?(:close)
@@ -147,11 +150,15 @@ module Rack
147
150
  end
148
151
 
149
152
  def map_accel_path(env, path)
150
- if mapping = @mappings.find { |internal,_| internal =~ path }
153
+ if mapping = @mappings.find { |internal, _| internal =~ path }
151
154
  path.sub(*mapping)
152
155
  elsif mapping = env['HTTP_X_ACCEL_MAPPING']
153
- internal, external = mapping.split('=', 2).map(&:strip)
154
- path.sub(/^#{internal}/i, external)
156
+ mapping.split(',').map(&:strip).each do |m|
157
+ internal, external = m.split('=', 2).map(&:strip)
158
+ new_path = path.sub(/^#{internal}/i, external)
159
+ return new_path unless path == new_path
160
+ end
161
+ path
155
162
  end
156
163
  end
157
164
  end
data/lib/rack/server.rb CHANGED
@@ -1,10 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
  require 'fileutils'
3
5
 
6
+ require_relative 'core_ext/regexp'
4
7
 
5
8
  module Rack
6
9
 
7
10
  class Server
11
+ using ::Rack::RegexpExtensions
8
12
 
9
13
  class Options
10
14
  def parse!(args)
@@ -21,10 +25,6 @@ module Rack
21
25
  lineno += 1
22
26
  }
23
27
 
24
- opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
25
- options[:builder] = line
26
- }
27
-
28
28
  opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
29
29
  options[:debug] = true
30
30
  }
@@ -47,7 +47,11 @@ module Rack
47
47
 
48
48
  opts.separator ""
49
49
  opts.separator "Rack options:"
50
- opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
50
+ opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
51
+ options[:builder] = line
52
+ }
53
+
54
+ opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick)") { |s|
51
55
  options[:server] = s
52
56
  }
53
57
 
@@ -77,6 +81,24 @@ module Rack
77
81
  options[:pid] = ::File.expand_path(f)
78
82
  }
79
83
 
84
+ opts.separator ""
85
+ opts.separator "Profiling options:"
86
+
87
+ opts.on("--heap HEAPFILE", "Build the application, then dump the heap to HEAPFILE") do |e|
88
+ options[:heapfile] = e
89
+ end
90
+
91
+ opts.on("--profile PROFILE", "Dump CPU or Memory profile to PROFILE (defaults to a tempfile)") do |e|
92
+ options[:profile_file] = e
93
+ end
94
+
95
+ opts.on("--profile-mode MODE", "Profile mode (cpu|wall|object)") do |e|
96
+ { cpu: true, wall: true, object: true }.fetch(e.to_sym) do
97
+ raise OptionParser::InvalidOption, "unknown profile mode: #{e}"
98
+ end
99
+ options[:profile_mode] = e.to_sym
100
+ end
101
+
80
102
  opts.separator ""
81
103
  opts.separator "Common options:"
82
104
 
@@ -114,7 +136,7 @@ module Rack
114
136
 
115
137
  has_options = false
116
138
  server.valid_options.each do |name, description|
117
- next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
139
+ next if /^(Host|Port)[^a-zA-Z]/.match?(name.to_s) # ignore handler's host and port options, we do our own.
118
140
  info << " -O %-21s %s" % [name, description]
119
141
  has_options = true
120
142
  end
@@ -152,7 +174,9 @@ module Rack
152
174
 
153
175
  # Options may include:
154
176
  # * :app
155
- # a rack application to run (overrides :config)
177
+ # a rack application to run (overrides :config and :builder)
178
+ # * :builder
179
+ # a string to evaluate a Rack::Builder from
156
180
  # * :config
157
181
  # a rackup configuration file path to load (.ru)
158
182
  # * :environment
@@ -182,6 +206,14 @@ module Rack
182
206
  # add given paths to $LOAD_PATH
183
207
  # * :require
184
208
  # require the given libraries
209
+ #
210
+ # Additional options for profiling app initialization include:
211
+ # * :heapfile
212
+ # location for ObjectSpace.dump_all to write the output to
213
+ # * :profile_file
214
+ # location for CPU/Memory (StackProf) profile output (defaults to a tempfile)
215
+ # * :profile_mode
216
+ # StackProf profile mode (cpu|wall|object)
185
217
  def initialize(options = nil)
186
218
  @ignore_options = []
187
219
 
@@ -206,12 +238,12 @@ module Rack
206
238
  default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
207
239
 
208
240
  {
209
- :environment => environment,
210
- :pid => nil,
211
- :Port => 9292,
212
- :Host => default_host,
213
- :AccessLog => [],
214
- :config => "config.ru"
241
+ environment: environment,
242
+ pid: nil,
243
+ Port: 9292,
244
+ Host: default_host,
245
+ AccessLog: [],
246
+ config: "config.ru"
215
247
  }
216
248
  end
217
249
 
@@ -222,12 +254,12 @@ module Rack
222
254
  class << self
223
255
  def logging_middleware
224
256
  lambda { |server|
225
- server.server.name =~ /CGI/ || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
257
+ /CGI/.match?(server.server.name) || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
226
258
  }
227
259
  end
228
260
 
229
261
  def default_middleware_by_environment
230
- m = Hash.new {|h,k| h[k] = []}
262
+ m = Hash.new {|h, k| h[k] = []}
231
263
  m["deployment"] = [
232
264
  [Rack::ContentLength],
233
265
  [Rack::Chunked],
@@ -280,7 +312,9 @@ module Rack
280
312
 
281
313
  # Touch the wrapped app, so that the config.ru is loaded before
282
314
  # daemonization (i.e. before chdir, etc).
283
- wrapped_app
315
+ handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do
316
+ wrapped_app
317
+ end
284
318
 
285
319
  daemonize_app if options[:daemonize]
286
320
 
@@ -321,6 +355,44 @@ module Rack
321
355
  app
322
356
  end
323
357
 
358
+ def handle_profiling(heapfile, profile_mode, filename)
359
+ if heapfile
360
+ require "objspace"
361
+ ObjectSpace.trace_object_allocations_start
362
+ yield
363
+ GC.start
364
+ ::File.open(heapfile, "w") { |f| ObjectSpace.dump_all(output: f) }
365
+ exit
366
+ end
367
+
368
+ if profile_mode
369
+ require "stackprof"
370
+ require "tempfile"
371
+
372
+ make_profile_name(filename) do |filename|
373
+ ::File.open(filename, "w") do |f|
374
+ StackProf.run(mode: profile_mode, out: f) do
375
+ yield
376
+ end
377
+ puts "Profile written to: #{filename}"
378
+ end
379
+ end
380
+ exit
381
+ end
382
+
383
+ yield
384
+ end
385
+
386
+ def make_profile_name(filename)
387
+ if filename
388
+ yield filename
389
+ else
390
+ ::Dir::Tmpname.create("profile.dump") do |tmpname, _, _|
391
+ yield tmpname
392
+ end
393
+ end
394
+ end
395
+
324
396
  def build_app_from_string
325
397
  Rack::Builder.new_from_string(self.options[:builder])
326
398
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
4
  # bugrep: Andreas Zehnder
3
5
 
@@ -6,15 +8,62 @@ require 'time'
6
8
  require 'rack/request'
7
9
  require 'rack/response'
8
10
  require 'securerandom'
11
+ require 'digest/sha2'
9
12
 
10
13
  module Rack
11
14
 
12
15
  module Session
13
16
 
17
+ class SessionId
18
+ ID_VERSION = 2
19
+
20
+ attr_reader :public_id
21
+
22
+ def initialize(public_id)
23
+ @public_id = public_id
24
+ end
25
+
26
+ def private_id
27
+ "#{ID_VERSION}::#{hash_sid(public_id)}"
28
+ end
29
+
30
+ alias :cookie_value :public_id
31
+
32
+ def empty?; false; end
33
+ def to_s; raise; end
34
+ def inspect; public_id.inspect; end
35
+
36
+ private
37
+
38
+ def hash_sid(sid)
39
+ Digest::SHA256.hexdigest(sid)
40
+ end
41
+ end
42
+
14
43
  module Abstract
15
44
  # SessionHash is responsible to lazily load the session from store.
16
45
 
17
46
  class SessionHash
47
+ using Module.new {
48
+ refine Hash do
49
+ def transform_keys(&block)
50
+ hash = {}
51
+ each do |key, value|
52
+ hash[block.call(key)] = value
53
+ end
54
+ hash
55
+ end
56
+ end
57
+ } unless {}.respond_to?(:transform_keys)
58
+
59
+ def transform_keys(&block)
60
+ hash = dup
61
+ each do |key, value|
62
+ hash[block.call(key)] = value
63
+ end
64
+ hash
65
+ end
66
+
18
67
  include Enumerable
19
68
  attr_writer :id
20
69
 
@@ -57,7 +106,7 @@ module Rack
57
106
  @data[key.to_s]
58
107
  end
59
108
 
60
- def fetch(key, default=Unspecified, &block)
109
+ def fetch(key, default = Unspecified, &block)
61
110
  load_for_read!
62
111
  if default == Unspecified
63
112
  @data.fetch(key.to_s, &block)
@@ -160,11 +209,7 @@ module Rack
160
209
  end
161
210
 
162
211
  def stringify_keys(other)
163
- hash = {}
164
- other.each do |key, value|
165
- hash[key.to_s] = value
166
- end
167
- hash
212
+ other.transform_keys(&:to_s)
168
213
  end
169
214
  end
170
215
 
@@ -199,22 +244,22 @@ module Rack
199
244
 
200
245
  class Persisted
201
246
  DEFAULT_OPTIONS = {
202
- :key => RACK_SESSION,
203
- :path => '/',
204
- :domain => nil,
205
- :expire_after => nil,
206
- :secure => false,
207
- :httponly => true,
208
- :defer => false,
209
- :renew => false,
210
- :sidbits => 128,
211
- :cookie_only => true,
212
- :secure_random => ::SecureRandom
247
+ key: RACK_SESSION,
248
+ path: '/',
249
+ domain: nil,
250
+ expire_after: nil,
251
+ secure: false,
252
+ httponly: true,
253
+ defer: false,
254
+ renew: false,
255
+ sidbits: 128,
256
+ cookie_only: true,
257
+ secure_random: ::SecureRandom
213
258
  }.freeze
214
259
 
215
260
  attr_reader :key, :default_options, :sid_secure
216
261
 
217
- def initialize(app, options={})
262
+ def initialize(app, options = {})
218
263
  @app = app
219
264
  @default_options = self.class::DEFAULT_OPTIONS.merge(options)
220
265
  @key = @default_options.delete(:key)
@@ -226,7 +271,7 @@ module Rack
226
271
  context(env)
227
272
  end
228
273
 
229
- def context(env, app=@app)
274
+ def context(env, app = @app)
230
275
  req = make_request env
231
276
  prepare_session(req)
232
277
  status, headers, body = app.call(req.env)
@@ -349,7 +394,7 @@ module Rack
349
394
 
350
395
  session.send(:load!) unless loaded_session?(session)
351
396
  session_id ||= session.id
352
- session_data = session.to_hash.delete_if { |k,v| v.nil? }
397
+ session_data = session.to_hash.delete_if { |k, v| v.nil? }
353
398
 
354
399
  if not data = write_session(req, session_id, session_data, options)
355
400
  req.get_header(RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.")
@@ -357,7 +402,7 @@ module Rack
357
402
  req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
358
403
  else
359
404
  cookie = Hash.new
360
- cookie[:value] = data
405
+ cookie[:value] = cookie_value(data)
361
406
  cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
362
407
  cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
363
408
  set_cookie(req, res, cookie.merge!(options))
@@ -365,6 +410,10 @@ module Rack
365
410
  end
366
411
  public :commit_session
367
412
 
413
+ def cookie_value(data)
414
+ data
415
+ end
416
+
368
417
  # Sets the cookie back to the client with session id. We skip the cookie
369
418
  # setting if the value didn't change (sid is the same) or expires was given.
370
419
 
@@ -406,6 +455,40 @@ module Rack
406
455
  end
407
456
  end
408
457
 
458
+ class PersistedSecure < Persisted
459
+ class SecureSessionHash < SessionHash
460
+ def [](key)
461
+ if key == "session_id"
462
+ load_for_read!
463
+ id.public_id
464
+ else
465
+ super
466
+ end
467
+ end
468
+ end
469
+
470
+ def generate_sid(*)
471
+ public_id = super
472
+
473
+ SessionId.new(public_id)
474
+ end
475
+
476
+ def extract_session_id(*)
477
+ public_id = super
478
+ public_id && SessionId.new(public_id)
479
+ end
480
+
481
+ private
482
+
483
+ def session_class
484
+ SecureSessionHash
485
+ end
486
+
487
+ def cookie_value(data)
488
+ data.cookie_value
489
+ end
490
+ end
491
+
409
492
  class ID < Persisted
410
493
  def self.inherited(klass)
411
494
  k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
  require 'zlib'
3
5
  require 'rack/request'
4
6
  require 'rack/response'
5
7
  require 'rack/session/abstract/id'
6
8
  require 'json'
9
+ require 'base64'
7
10
 
8
11
  module Rack
9
12
 
@@ -45,15 +48,15 @@ module Rack
45
48
  # })
46
49
  #
47
50
 
48
- class Cookie < Abstract::Persisted
51
+ class Cookie < Abstract::PersistedSecure
49
52
  # Encode session cookies as Base64
50
53
  class Base64
51
54
  def encode(str)
52
- [str].pack('m')
55
+ ::Base64.strict_encode64(str)
53
56
  end
54
57
 
55
58
  def decode(str)
56
- str.unpack('m').first
59
+ ::Base64.decode64(str)
57
60
  end
58
61
 
59
62
  # Encode session cookies as Marshaled Base64 data
@@ -103,7 +106,7 @@ module Rack
103
106
 
104
107
  attr_reader :coder
105
108
 
106
- def initialize(app, options={})
109
+ def initialize(app, options = {})
107
110
  @secrets = options.values_at(:secret, :old_secret).compact
108
111
  @hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
109
112
 
@@ -116,8 +119,8 @@ module Rack
116
119
 
117
120
  Called from: #{caller[0]}.
118
121
  MSG
119
- @coder = options[:coder] ||= Base64::Marshal.new
120
- super(app, options.merge!(:cookie_only => true))
122
+ @coder = options[:coder] ||= Base64::Marshal.new
123
+ super(app, options.merge!(cookie_only: true))
121
124
  end
122
125
 
123
126
  private
@@ -137,9 +140,7 @@ module Rack
137
140
  session_data = request.cookies[@key]
138
141
 
139
142
  if @secrets.size > 0 && session_data
140
- digest, session_data = session_data.reverse.split("--", 2)
141
- digest.reverse! if digest
142
- session_data.reverse! if session_data
143
+ session_data, _, digest = session_data.rpartition('--')
143
144
  session_data = nil unless digest_match?(session_data, digest)
144
145
  end
145
146
 
@@ -147,12 +148,21 @@ module Rack
147
148
  end
148
149
  end
149
150
 
150
- def persistent_session_id!(data, sid=nil)
151
+ def persistent_session_id!(data, sid = nil)
151
152
  data ||= {}
152
153
  data["session_id"] ||= sid || generate_sid
153
154
  data
154
155
  end
155
156
 
157
+ class SessionId < DelegateClass(Session::SessionId)
158
+ attr_reader :cookie_value
159
+
160
+ def initialize(session_id, cookie_value)
161
+ super(session_id)
162
+ @cookie_value = cookie_value
163
+ end
164
+ end
165
+
156
166
  def write_session(req, session_id, session, options)
157
167
  session = session.merge("session_id" => session_id)
158
168
  session_data = coder.encode(session)
@@ -165,7 +175,7 @@ module Rack
165
175
  req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
166
176
  nil
167
177
  else
168
- session_data
178
+ SessionId.new(session_id, session_data)
169
179
  end
170
180
  end
171
181
 
@@ -1,93 +1,10 @@
1
- # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
1
+ # frozen_string_literal: true
2
2
 
3
- require 'rack/session/abstract/id'
4
- require 'memcache'
3
+ require 'rack/session/dalli'
5
4
 
6
5
  module Rack
7
6
  module Session
8
- # Rack::Session::Memcache provides simple cookie based session management.
9
- # Session data is stored in memcached. The corresponding session key is
10
- # maintained in the cookie.
11
- # You may treat Session::Memcache as you would Session::Pool with the
12
- # following caveats.
13
- #
14
- # * Setting :expire_after to 0 would note to the Memcache server to hang
15
- # onto the session data until it would drop it according to it's own
16
- # specifications. However, the cookie sent to the client would expire
17
- # immediately.
18
- #
19
- # Note that memcache does drop data before it may be listed to expire. For
20
- # a full description of behaviour, please see memcache's documentation.
21
-
22
- class Memcache < Abstract::ID
23
- attr_reader :mutex, :pool
24
-
25
- DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
26
- :namespace => 'rack:session',
27
- :memcache_server => 'localhost:11211'
28
-
29
- def initialize(app, options={})
30
- super
31
-
32
- @mutex = Mutex.new
33
- mserv = @default_options[:memcache_server]
34
- mopts = @default_options.reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
35
-
36
- @pool = options[:cache] || MemCache.new(mserv, mopts)
37
- unless @pool.active? and @pool.servers.any?(&:alive?)
38
- raise 'No memcache servers'
39
- end
40
- end
41
-
42
- def generate_sid
43
- loop do
44
- sid = super
45
- break sid unless @pool.get(sid, true)
46
- end
47
- end
48
-
49
- def get_session(env, sid)
50
- with_lock(env) do
51
- unless sid and session = @pool.get(sid)
52
- sid, session = generate_sid, {}
53
- unless /^STORED/ =~ @pool.add(sid, session)
54
- raise "Session collision on '#{sid.inspect}'"
55
- end
56
- end
57
- [sid, session]
58
- end
59
- end
60
-
61
- def set_session(env, session_id, new_session, options)
62
- expiry = options[:expire_after]
63
- expiry = expiry.nil? ? 0 : expiry + 1
64
-
65
- with_lock(env) do
66
- @pool.set session_id, new_session, expiry
67
- session_id
68
- end
69
- end
70
-
71
- def destroy_session(env, session_id, options)
72
- with_lock(env) do
73
- @pool.delete(session_id)
74
- generate_sid unless options[:drop]
75
- end
76
- end
77
-
78
- def with_lock(env)
79
- @mutex.lock if env[RACK_MULTITHREAD]
80
- yield
81
- rescue MemCache::MemCacheError, Errno::ECONNREFUSED
82
- if $VERBOSE
83
- warn "#{self} is unable to find memcached server."
84
- warn $!.inspect
85
- end
86
- raise
87
- ensure
88
- @mutex.unlock if @mutex.locked?
89
- end
90
-
91
- end
7
+ warn "Rack::Session::Memcache is deprecated, please use Rack::Session::Dalli from 'dalli' gem instead."
8
+ Memcache = Dalli
92
9
  end
93
10
  end