rack 2.1.3 → 2.2.3

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +626 -1
  3. data/CONTRIBUTING.md +136 -0
  4. data/README.rdoc +83 -39
  5. data/Rakefile +14 -7
  6. data/{SPEC → SPEC.rdoc} +35 -6
  7. data/lib/rack.rb +7 -16
  8. data/lib/rack/auth/abstract/request.rb +0 -2
  9. data/lib/rack/auth/basic.rb +3 -3
  10. data/lib/rack/auth/digest/md5.rb +4 -4
  11. data/lib/rack/auth/digest/request.rb +3 -3
  12. data/lib/rack/body_proxy.rb +13 -9
  13. data/lib/rack/builder.rb +77 -8
  14. data/lib/rack/cascade.rb +23 -8
  15. data/lib/rack/chunked.rb +48 -23
  16. data/lib/rack/common_logger.rb +25 -18
  17. data/lib/rack/conditional_get.rb +18 -16
  18. data/lib/rack/content_length.rb +6 -7
  19. data/lib/rack/content_type.rb +3 -4
  20. data/lib/rack/deflater.rb +45 -35
  21. data/lib/rack/directory.rb +77 -59
  22. data/lib/rack/etag.rb +2 -3
  23. data/lib/rack/events.rb +15 -18
  24. data/lib/rack/file.rb +1 -1
  25. data/lib/rack/files.rb +96 -56
  26. data/lib/rack/handler/cgi.rb +1 -4
  27. data/lib/rack/handler/fastcgi.rb +1 -3
  28. data/lib/rack/handler/lsws.rb +1 -3
  29. data/lib/rack/handler/scgi.rb +1 -3
  30. data/lib/rack/handler/thin.rb +1 -3
  31. data/lib/rack/handler/webrick.rb +12 -5
  32. data/lib/rack/head.rb +0 -2
  33. data/lib/rack/lint.rb +57 -14
  34. data/lib/rack/lobster.rb +3 -5
  35. data/lib/rack/lock.rb +0 -1
  36. data/lib/rack/mock.rb +22 -4
  37. data/lib/rack/multipart.rb +1 -1
  38. data/lib/rack/multipart/generator.rb +11 -6
  39. data/lib/rack/multipart/parser.rb +7 -15
  40. data/lib/rack/multipart/uploaded_file.rb +13 -7
  41. data/lib/rack/query_parser.rb +7 -8
  42. data/lib/rack/recursive.rb +1 -1
  43. data/lib/rack/reloader.rb +1 -3
  44. data/lib/rack/request.rb +182 -76
  45. data/lib/rack/response.rb +62 -19
  46. data/lib/rack/rewindable_input.rb +0 -1
  47. data/lib/rack/runtime.rb +3 -3
  48. data/lib/rack/sendfile.rb +0 -3
  49. data/lib/rack/server.rb +9 -8
  50. data/lib/rack/session/abstract/id.rb +21 -18
  51. data/lib/rack/session/cookie.rb +1 -3
  52. data/lib/rack/session/pool.rb +1 -1
  53. data/lib/rack/show_exceptions.rb +6 -8
  54. data/lib/rack/show_status.rb +5 -7
  55. data/lib/rack/static.rb +13 -6
  56. data/lib/rack/tempfile_reaper.rb +0 -2
  57. data/lib/rack/urlmap.rb +1 -4
  58. data/lib/rack/utils.rb +63 -55
  59. data/lib/rack/version.rb +29 -0
  60. data/rack.gemspec +31 -29
  61. metadata +14 -15
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'tempfile'
5
- require 'rack/utils'
6
5
 
7
6
  module Rack
8
7
  # Class which can make any IO object rewindable, including non-rewindable ones. It does
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/utils'
4
-
5
3
  module Rack
6
4
  # Sets an "X-Runtime" response header, indicating the response
7
5
  # time of the request, in seconds
@@ -22,9 +20,11 @@ module Rack
22
20
  def call(env)
23
21
  start_time = Utils.clock_time
24
22
  status, headers, body = @app.call(env)
23
+ headers = Utils::HeaderHash[headers]
24
+
25
25
  request_time = Utils.clock_time - start_time
26
26
 
27
- unless headers.has_key?(@header_name)
27
+ unless headers.key?(@header_name)
28
28
  headers[@header_name] = FORMAT_STRING % request_time
29
29
  end
30
30
 
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/files'
4
- require 'rack/body_proxy'
5
-
6
3
  module Rack
7
4
 
8
5
  # = Sendfile
@@ -3,12 +3,10 @@
3
3
  require 'optparse'
4
4
  require 'fileutils'
5
5
 
6
- require_relative 'core_ext/regexp'
7
-
8
6
  module Rack
9
7
 
10
8
  class Server
11
- using ::Rack::RegexpExtensions
9
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
12
10
 
13
11
  class Options
14
12
  def parse!(args)
@@ -42,7 +40,7 @@ module Rack
42
40
 
43
41
  opts.on("-r", "--require LIBRARY",
44
42
  "require the library, before executing your script") { |library|
45
- options[:require] = library
43
+ (options[:require] ||= []) << library
46
44
  }
47
45
 
48
46
  opts.separator ""
@@ -143,7 +141,7 @@ module Rack
143
141
  return "" if !has_options
144
142
  end
145
143
  info.join("\n")
146
- rescue NameError
144
+ rescue NameError, LoadError
147
145
  return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
148
146
  end
149
147
  end
@@ -285,7 +283,7 @@ module Rack
285
283
  self.class.middleware
286
284
  end
287
285
 
288
- def start &blk
286
+ def start(&block)
289
287
  if options[:warn]
290
288
  $-w = true
291
289
  end
@@ -294,7 +292,7 @@ module Rack
294
292
  $LOAD_PATH.unshift(*includes)
295
293
  end
296
294
 
297
- if library = options[:require]
295
+ Array(options[:require]).each do |library|
298
296
  require library
299
297
  end
300
298
 
@@ -326,7 +324,7 @@ module Rack
326
324
  end
327
325
  end
328
326
 
329
- server.run wrapped_app, options, &blk
327
+ server.run(wrapped_app, **options, &block)
330
328
  end
331
329
 
332
330
  def server
@@ -425,7 +423,10 @@ module Rack
425
423
  end
426
424
 
427
425
  def daemonize_app
426
+ # Cannot be covered as it forks
427
+ # :nocov:
428
428
  Process.daemon
429
+ # :nocov:
429
430
  end
430
431
 
431
432
  def write_pid
@@ -3,10 +3,8 @@
3
3
  # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
4
4
  # bugrep: Andreas Zehnder
5
5
 
6
- require 'rack'
6
+ require_relative '../../../rack'
7
7
  require 'time'
8
- require 'rack/request'
9
- require 'rack/response'
10
8
  require 'securerandom'
11
9
  require 'digest/sha2'
12
10
 
@@ -44,18 +42,6 @@ module Rack
44
42
  # SessionHash is responsible to lazily load the session from store.
45
43
 
46
44
  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
45
  include Enumerable
60
46
  attr_writer :id
61
47
 
@@ -98,6 +84,11 @@ module Rack
98
84
  @data[key.to_s]
99
85
  end
100
86
 
87
+ def dig(key, *keys)
88
+ load_for_read!
89
+ @data.dig(key.to_s, *keys)
90
+ end
91
+
101
92
  def fetch(key, default = Unspecified, &block)
102
93
  load_for_read!
103
94
  if default == Unspecified
@@ -201,14 +192,19 @@ module Rack
201
192
  end
202
193
 
203
194
  def stringify_keys(other)
204
- other.to_hash.transform_keys(&:to_s)
195
+ # Use transform_keys after dropping Ruby 2.4 support
196
+ hash = {}
197
+ other.to_hash.each do |key, value|
198
+ hash[key.to_s] = value
199
+ end
200
+ hash
205
201
  end
206
202
  end
207
203
 
208
204
  # ID sets up a basic framework for implementing an id based sessioning
209
205
  # service. Cookies sent to the client for maintaining sessions will only
210
- # contain an id reference. Only #find_session and #write_session are
211
- # required to be overwritten.
206
+ # contain an id reference. Only #find_session, #write_session and
207
+ # #delete_session are required to be overwritten.
212
208
  #
213
209
  # All parameters are optional.
214
210
  # * :key determines the name of the cookie, by default it is
@@ -256,6 +252,7 @@ module Rack
256
252
  @default_options = self.class::DEFAULT_OPTIONS.merge(options)
257
253
  @key = @default_options.delete(:key)
258
254
  @cookie_only = @default_options.delete(:cookie_only)
255
+ @same_site = @default_options.delete(:same_site)
259
256
  initialize_sid
260
257
  end
261
258
 
@@ -397,6 +394,12 @@ module Rack
397
394
  cookie[:value] = cookie_value(data)
398
395
  cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
399
396
  cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
397
+
398
+ if @same_site.respond_to? :call
399
+ cookie[:same_site] = @same_site.call(req, res)
400
+ else
401
+ cookie[:same_site] = @same_site
402
+ end
400
403
  set_cookie(req, res, cookie.merge!(options))
401
404
  end
402
405
  end
@@ -2,9 +2,7 @@
2
2
 
3
3
  require 'openssl'
4
4
  require 'zlib'
5
- require 'rack/request'
6
- require 'rack/response'
7
- require 'rack/session/abstract/id'
5
+ require_relative 'abstract/id'
8
6
  require 'json'
9
7
  require 'base64'
10
8
 
@@ -5,7 +5,7 @@
5
5
  # apeiros, for session id generation, expiry setup, and threadiness
6
6
  # sergio, threadiness and bugreps
7
7
 
8
- require 'rack/session/abstract/id'
8
+ require_relative 'abstract/id'
9
9
  require 'thread'
10
10
 
11
11
  module Rack
@@ -2,8 +2,6 @@
2
2
 
3
3
  require 'ostruct'
4
4
  require 'erb'
5
- require 'rack/request'
6
- require 'rack/utils'
7
5
 
8
6
  module Rack
9
7
  # Rack::ShowExceptions catches all exceptions raised from the app it
@@ -65,12 +63,12 @@ module Rack
65
63
  def pretty(env, exception)
66
64
  req = Rack::Request.new(env)
67
65
 
68
- # This double assignment is to prevent an "unused variable" warning on
69
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
66
+ # This double assignment is to prevent an "unused variable" warning.
67
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
70
68
  path = path = (req.script_name + req.path_info).squeeze("/")
71
69
 
72
- # This double assignment is to prevent an "unused variable" warning on
73
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
70
+ # This double assignment is to prevent an "unused variable" warning.
71
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
74
72
  frames = frames = exception.backtrace.map { |line|
75
73
  frame = OpenStruct.new
76
74
  if line =~ /(.*?):(\d+)(:in `(.*)')?/
@@ -313,7 +311,7 @@ module Rack
313
311
  <% end %>
314
312
 
315
313
  <h3 id="post-info">POST</h3>
316
- <% if req.POST and not req.POST.empty? %>
314
+ <% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %>
317
315
  <table class="req">
318
316
  <thead>
319
317
  <tr>
@@ -331,7 +329,7 @@ module Rack
331
329
  </tbody>
332
330
  </table>
333
331
  <% else %>
334
- <p>No POST data.</p>
332
+ <p><%= no_post_data || "No POST data" %>.</p>
335
333
  <% end %>
336
334
 
337
335
 
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
- require 'rack/request'
5
- require 'rack/utils'
6
4
 
7
5
  module Rack
8
6
  # Rack::ShowStatus catches all empty responses and replaces them
@@ -20,19 +18,19 @@ module Rack
20
18
 
21
19
  def call(env)
22
20
  status, headers, body = @app.call(env)
23
- headers = Utils::HeaderHash.new(headers)
21
+ headers = Utils::HeaderHash[headers]
24
22
  empty = headers[CONTENT_LENGTH].to_i <= 0
25
23
 
26
24
  # client or server error, or explicit message
27
25
  if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL]
28
- # This double assignment is to prevent an "unused variable" warning on
29
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
26
+ # This double assignment is to prevent an "unused variable" warning.
27
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
30
28
  req = req = Rack::Request.new(env)
31
29
 
32
30
  message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
33
31
 
34
- # This double assignment is to prevent an "unused variable" warning on
35
- # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
32
+ # This double assignment is to prevent an "unused variable" warning.
33
+ # Yes, it is dumb, but I don't like Ruby yelling at me.
36
34
  detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message
37
35
 
38
36
  body = @template.result(binding)
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rack/files"
4
- require "rack/utils"
5
-
6
- require_relative 'core_ext/regexp'
7
-
8
3
  module Rack
9
4
 
10
5
  # The Rack::Static middleware intercepts requests for static files
@@ -19,6 +14,11 @@ module Rack
19
14
  #
20
15
  # use Rack::Static, :urls => ["/media"]
21
16
  #
17
+ # Same as previous, but instead of returning 404 for missing files under
18
+ # /media, call the next middleware:
19
+ #
20
+ # use Rack::Static, :urls => ["/media"], :cascade => true
21
+ #
22
22
  # Serve all requests beginning with /css or /images from the folder "public"
23
23
  # in the current directory (ie public/css/* and public/images/*):
24
24
  #
@@ -86,13 +86,14 @@ module Rack
86
86
  # ]
87
87
  #
88
88
  class Static
89
- using ::Rack::RegexpExtensions
89
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
90
90
 
91
91
  def initialize(app, options = {})
92
92
  @app = app
93
93
  @urls = options[:urls] || ["/favicon.ico"]
94
94
  @index = options[:index]
95
95
  @gzip = options[:gzip]
96
+ @cascade = options[:cascade]
96
97
  root = options[:root] || Dir.pwd
97
98
 
98
99
  # HTTP Headers
@@ -133,6 +134,8 @@ module Rack
133
134
 
134
135
  if response[0] == 404
135
136
  response = nil
137
+ elsif response[0] == 304
138
+ # Do nothing, leave headers as is
136
139
  else
137
140
  if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
138
141
  response[1][CONTENT_TYPE] = mime_type
@@ -144,6 +147,10 @@ module Rack
144
147
  path = env[PATH_INFO]
145
148
  response ||= @file_server.call(env)
146
149
 
150
+ if @cascade && response[0] == 404
151
+ return @app.call(env)
152
+ end
153
+
147
154
  headers = response[1]
148
155
  applicable_rules(path).each do |rule, new_headers|
149
156
  new_headers.each { |field, content| headers[field] = content }
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/body_proxy'
4
-
5
3
  module Rack
6
4
 
7
5
  # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
@@ -16,9 +16,6 @@ module Rack
16
16
  # first, since they are most specific.
17
17
 
18
18
  class URLMap
19
- NEGATIVE_INFINITY = -1.0 / 0.0
20
- INFINITY = 1.0 / 0.0
21
-
22
19
  def initialize(map = {})
23
20
  remap(map)
24
21
  end
@@ -42,7 +39,7 @@ module Rack
42
39
 
43
40
  [host, location, match, app]
44
41
  }.sort_by do |(host, location, _, _)|
45
- [host ? -host.size : INFINITY, -location.size]
42
+ [host ? -host.size : Float::INFINITY, -location.size]
46
43
  end
47
44
  end
48
45
 
@@ -5,17 +5,16 @@ require 'uri'
5
5
  require 'fileutils'
6
6
  require 'set'
7
7
  require 'tempfile'
8
- require 'rack/query_parser'
9
8
  require 'time'
10
9
 
11
- require_relative 'core_ext/regexp'
10
+ require_relative 'query_parser'
12
11
 
13
12
  module Rack
14
13
  # Rack::Utils contains a grab-bag of useful methods for writing web
15
14
  # applications adopted from all kinds of Ruby libraries.
16
15
 
17
16
  module Utils
18
- using ::Rack::RegexpExtensions
17
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
19
18
 
20
19
  ParameterTypeError = QueryParser::ParameterTypeError
21
20
  InvalidParameterError = QueryParser::InvalidParameterError
@@ -30,33 +29,30 @@ module Rack
30
29
  # This helps prevent a rogue client from flooding a Request.
31
30
  self.default_query_parser = QueryParser.make_default(65536, 100)
32
31
 
32
+ module_function
33
+
33
34
  # URI escapes. (CGI style space to +)
34
35
  def escape(s)
35
36
  URI.encode_www_form_component(s)
36
37
  end
37
- module_function :escape
38
38
 
39
39
  # Like URI escaping, but with %20 instead of +. Strictly speaking this is
40
40
  # true URI escaping.
41
41
  def escape_path(s)
42
42
  ::URI::DEFAULT_PARSER.escape s
43
43
  end
44
- module_function :escape_path
45
44
 
46
45
  # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
47
46
  # unescaping query parameters or form components.
48
47
  def unescape_path(s)
49
48
  ::URI::DEFAULT_PARSER.unescape s
50
49
  end
51
- module_function :unescape_path
52
-
53
50
 
54
51
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
55
52
  # target encoding of the string returned, and it defaults to UTF-8
56
53
  def unescape(s, encoding = Encoding::UTF_8)
57
54
  URI.decode_www_form_component(s, encoding)
58
55
  end
59
- module_function :unescape
60
56
 
61
57
  class << self
62
58
  attr_accessor :multipart_part_limit
@@ -88,21 +84,20 @@ module Rack
88
84
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
89
85
  end
90
86
  else
87
+ # :nocov:
91
88
  def clock_time
92
89
  Time.now.to_f
93
90
  end
91
+ # :nocov:
94
92
  end
95
- module_function :clock_time
96
93
 
97
94
  def parse_query(qs, d = nil, &unescaper)
98
95
  Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
99
96
  end
100
- module_function :parse_query
101
97
 
102
98
  def parse_nested_query(qs, d = nil)
103
99
  Rack::Utils.default_query_parser.parse_nested_query(qs, d)
104
100
  end
105
- module_function :parse_nested_query
106
101
 
107
102
  def build_query(params)
108
103
  params.map { |k, v|
@@ -113,7 +108,6 @@ module Rack
113
108
  end
114
109
  }.join("&")
115
110
  end
116
- module_function :build_query
117
111
 
118
112
  def build_nested_query(value, prefix = nil)
119
113
  case value
@@ -132,7 +126,6 @@ module Rack
132
126
  "#{prefix}=#{escape(value)}"
133
127
  end
134
128
  end
135
- module_function :build_nested_query
136
129
 
137
130
  def q_values(q_value_header)
138
131
  q_value_header.to_s.split(/\s*,\s*/).map do |part|
@@ -144,8 +137,11 @@ module Rack
144
137
  [value, quality]
145
138
  end
146
139
  end
147
- module_function :q_values
148
140
 
141
+ # Return best accept value to use, based on the algorithm
142
+ # in RFC 2616 Section 14. If there are multiple best
143
+ # matches (same specificity and quality), the value returned
144
+ # is arbitrary.
149
145
  def best_q_match(q_value_header, available_mimes)
150
146
  values = q_values(q_value_header)
151
147
 
@@ -158,7 +154,6 @@ module Rack
158
154
  end.last
159
155
  matches && matches.first
160
156
  end
161
- module_function :best_q_match
162
157
 
163
158
  ESCAPE_HTML = {
164
159
  "&" => "&amp;",
@@ -175,22 +170,27 @@ module Rack
175
170
  def escape_html(string)
176
171
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
177
172
  end
178
- module_function :escape_html
179
173
 
180
174
  def select_best_encoding(available_encodings, accept_encoding)
181
175
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
182
176
 
183
- expanded_accept_encoding =
184
- accept_encoding.each_with_object([]) do |(m, q), list|
185
- if m == "*"
186
- (available_encodings - accept_encoding.map(&:first))
187
- .each { |m2| list << [m2, q] }
188
- else
189
- list << [m, q]
177
+ expanded_accept_encoding = []
178
+
179
+ accept_encoding.each do |m, q|
180
+ preference = available_encodings.index(m) || available_encodings.size
181
+
182
+ if m == "*"
183
+ (available_encodings - accept_encoding.map(&:first)).each do |m2|
184
+ expanded_accept_encoding << [m2, q, preference]
190
185
  end
186
+ else
187
+ expanded_accept_encoding << [m, q, preference]
191
188
  end
189
+ end
192
190
 
193
- encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map!(&:first)
191
+ encoding_candidates = expanded_accept_encoding
192
+ .sort_by { |_, q, p| [-q, p] }
193
+ .map!(&:first)
194
194
 
195
195
  unless encoding_candidates.include?("identity")
196
196
  encoding_candidates.push("identity")
@@ -202,23 +202,23 @@ module Rack
202
202
 
203
203
  (encoding_candidates & available_encodings)[0]
204
204
  end
205
- module_function :select_best_encoding
206
205
 
207
206
  def parse_cookies(env)
208
207
  parse_cookies_header env[HTTP_COOKIE]
209
208
  end
210
- module_function :parse_cookies
211
209
 
212
210
  def parse_cookies_header(header)
213
- # According to RFC 2109:
214
- # If multiple cookies satisfy the criteria above, they are ordered in
215
- # the Cookie header such that those with more specific Path attributes
216
- # precede those with less specific. Ordering with respect to other
217
- # attributes (e.g., Domain) is unspecified.
218
- cookies = parse_query(header, ';,') { |s| unescape(s) rescue s }
219
- cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v }
211
+ # According to RFC 6265:
212
+ # The syntax for cookie headers only supports semicolons
213
+ # User Agent -> Server ==
214
+ # Cookie: SID=31d4d96e407aad42; lang=en-US
215
+ return {} unless header
216
+ header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
217
+ next if cookie.empty?
218
+ key, value = cookie.split('=', 2)
219
+ cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
220
+ end
220
221
  end
221
- module_function :parse_cookies_header
222
222
 
223
223
  def add_cookie_to_header(header, key, value)
224
224
  case value
@@ -260,13 +260,11 @@ module Rack
260
260
  raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
261
261
  end
262
262
  end
263
- module_function :add_cookie_to_header
264
263
 
265
264
  def set_cookie_header!(header, key, value)
266
265
  header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
267
266
  nil
268
267
  end
269
- module_function :set_cookie_header!
270
268
 
271
269
  def make_delete_cookie_header(header, key, value)
272
270
  case header
@@ -278,25 +276,30 @@ module Rack
278
276
  cookies = header
279
277
  end
280
278
 
281
- regexp = if value[:domain]
282
- /\A#{escape(key)}=.*domain=#{value[:domain]}/
283
- elsif value[:path]
284
- /\A#{escape(key)}=.*path=#{value[:path]}/
279
+ key = escape(key)
280
+ domain = value[:domain]
281
+ path = value[:path]
282
+ regexp = if domain
283
+ if path
284
+ /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
285
+ else
286
+ /\A#{key}=.*domain=#{domain}(?:;|$)/
287
+ end
288
+ elsif path
289
+ /\A#{key}=.*path=#{path}(?:;|$)/
285
290
  else
286
- /\A#{escape(key)}=/
291
+ /\A#{key}=/
287
292
  end
288
293
 
289
294
  cookies.reject! { |cookie| regexp.match? cookie }
290
295
 
291
296
  cookies.join("\n")
292
297
  end
293
- module_function :make_delete_cookie_header
294
298
 
295
299
  def delete_cookie_header!(header, key, value = {})
296
300
  header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
297
301
  nil
298
302
  end
299
- module_function :delete_cookie_header!
300
303
 
301
304
  # Adds a cookie that will *remove* a cookie from the client. Hence the
302
305
  # strange method name.
@@ -309,12 +312,10 @@ module Rack
309
312
  expires: Time.at(0) }.merge(value))
310
313
 
311
314
  end
312
- module_function :add_remove_cookie_to_header
313
315
 
314
316
  def rfc2822(time)
315
317
  time.rfc2822
316
318
  end
317
- module_function :rfc2822
318
319
 
319
320
  # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
320
321
  # of '% %b %Y'.
@@ -330,7 +331,6 @@ module Rack
330
331
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
331
332
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
332
333
  end
333
- module_function :rfc2109
334
334
 
335
335
  # Parses the "Range:" header, if present, into an array of Range objects.
336
336
  # Returns nil if the header is missing or syntactically invalid.
@@ -339,7 +339,6 @@ module Rack
339
339
  warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
340
340
  get_byte_ranges env['HTTP_RANGE'], size
341
341
  end
342
- module_function :byte_ranges
343
342
 
344
343
  def get_byte_ranges(http_range, size)
345
344
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
@@ -368,7 +367,6 @@ module Rack
368
367
  end
369
368
  ranges
370
369
  end
371
- module_function :get_byte_ranges
372
370
 
373
371
  # Constant time string comparison.
374
372
  #
@@ -385,7 +383,6 @@ module Rack
385
383
  b.each_byte { |v| r |= v ^ l[i += 1] }
386
384
  r == 0
387
385
  end
388
- module_function :secure_compare
389
386
 
390
387
  # Context allows the use of a compatible middleware at different points
391
388
  # in a request handling stack. A compatible middleware must define
@@ -418,6 +415,14 @@ module Rack
418
415
  #
419
416
  # @api private
420
417
  class HeaderHash < Hash # :nodoc:
418
+ def self.[](headers)
419
+ if headers.is_a?(HeaderHash) && !headers.frozen?
420
+ return headers
421
+ else
422
+ return self.new(headers)
423
+ end
424
+ end
425
+
421
426
  def initialize(hash = {})
422
427
  super()
423
428
  @names = {}
@@ -430,6 +435,12 @@ module Rack
430
435
  @names = other.names.dup
431
436
  end
432
437
 
438
+ # on clear, we need to clear @names hash
439
+ def clear
440
+ super
441
+ @names.clear
442
+ end
443
+
433
444
  def each
434
445
  super do |k, v|
435
446
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -574,7 +585,6 @@ module Rack
574
585
  status.to_i
575
586
  end
576
587
  end
577
- module_function :status_code
578
588
 
579
589
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
580
590
 
@@ -588,18 +598,16 @@ module Rack
588
598
  part == '..' ? clean.pop : clean << part
589
599
  end
590
600
 
591
- clean.unshift '/' if parts.empty? || parts.first.empty?
592
-
593
- ::File.join clean
601
+ clean_path = clean.join(::File::SEPARATOR)
602
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
603
+ clean_path
594
604
  end
595
- module_function :clean_path_info
596
605
 
597
606
  NULL_BYTE = "\0"
598
607
 
599
608
  def valid_path?(path)
600
609
  path.valid_encoding? && !path.include?(NULL_BYTE)
601
610
  end
602
- module_function :valid_path?
603
611
 
604
612
  end
605
613
  end