rack 2.1.2 → 2.2.2

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 +622 -1
  3. data/CONTRIBUTING.md +136 -0
  4. data/README.rdoc +83 -39
  5. data/Rakefile +14 -7
  6. data/{SPEC → SPEC.rdoc} +26 -1
  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 -60
  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 +58 -54
  59. data/lib/rack/version.rb +29 -0
  60. data/rack.gemspec +31 -29
  61. metadata +11 -12
@@ -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,19 @@ 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 }
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
+ cookies = parse_query(header, ';') { |s| unescape(s) rescue s }
219
216
  cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v }
220
217
  end
221
- module_function :parse_cookies_header
222
218
 
223
219
  def add_cookie_to_header(header, key, value)
224
220
  case value
@@ -260,13 +256,11 @@ module Rack
260
256
  raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
261
257
  end
262
258
  end
263
- module_function :add_cookie_to_header
264
259
 
265
260
  def set_cookie_header!(header, key, value)
266
261
  header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
267
262
  nil
268
263
  end
269
- module_function :set_cookie_header!
270
264
 
271
265
  def make_delete_cookie_header(header, key, value)
272
266
  case header
@@ -278,25 +272,30 @@ module Rack
278
272
  cookies = header
279
273
  end
280
274
 
281
- regexp = if value[:domain]
282
- /\A#{escape(key)}=.*domain=#{value[:domain]}/
283
- elsif value[:path]
284
- /\A#{escape(key)}=.*path=#{value[:path]}/
275
+ key = escape(key)
276
+ domain = value[:domain]
277
+ path = value[:path]
278
+ regexp = if domain
279
+ if path
280
+ /\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
281
+ else
282
+ /\A#{key}=.*domain=#{domain}(?:;|$)/
283
+ end
284
+ elsif path
285
+ /\A#{key}=.*path=#{path}(?:;|$)/
285
286
  else
286
- /\A#{escape(key)}=/
287
+ /\A#{key}=/
287
288
  end
288
289
 
289
290
  cookies.reject! { |cookie| regexp.match? cookie }
290
291
 
291
292
  cookies.join("\n")
292
293
  end
293
- module_function :make_delete_cookie_header
294
294
 
295
295
  def delete_cookie_header!(header, key, value = {})
296
296
  header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
297
297
  nil
298
298
  end
299
- module_function :delete_cookie_header!
300
299
 
301
300
  # Adds a cookie that will *remove* a cookie from the client. Hence the
302
301
  # strange method name.
@@ -309,12 +308,10 @@ module Rack
309
308
  expires: Time.at(0) }.merge(value))
310
309
 
311
310
  end
312
- module_function :add_remove_cookie_to_header
313
311
 
314
312
  def rfc2822(time)
315
313
  time.rfc2822
316
314
  end
317
- module_function :rfc2822
318
315
 
319
316
  # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
320
317
  # of '% %b %Y'.
@@ -330,7 +327,6 @@ module Rack
330
327
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
331
328
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
332
329
  end
333
- module_function :rfc2109
334
330
 
335
331
  # Parses the "Range:" header, if present, into an array of Range objects.
336
332
  # Returns nil if the header is missing or syntactically invalid.
@@ -339,7 +335,6 @@ module Rack
339
335
  warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
340
336
  get_byte_ranges env['HTTP_RANGE'], size
341
337
  end
342
- module_function :byte_ranges
343
338
 
344
339
  def get_byte_ranges(http_range, size)
345
340
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
@@ -368,7 +363,6 @@ module Rack
368
363
  end
369
364
  ranges
370
365
  end
371
- module_function :get_byte_ranges
372
366
 
373
367
  # Constant time string comparison.
374
368
  #
@@ -385,7 +379,6 @@ module Rack
385
379
  b.each_byte { |v| r |= v ^ l[i += 1] }
386
380
  r == 0
387
381
  end
388
- module_function :secure_compare
389
382
 
390
383
  # Context allows the use of a compatible middleware at different points
391
384
  # in a request handling stack. A compatible middleware must define
@@ -418,6 +411,14 @@ module Rack
418
411
  #
419
412
  # @api private
420
413
  class HeaderHash < Hash # :nodoc:
414
+ def self.[](headers)
415
+ if headers.is_a?(HeaderHash) && !headers.frozen?
416
+ return headers
417
+ else
418
+ return self.new(headers)
419
+ end
420
+ end
421
+
421
422
  def initialize(hash = {})
422
423
  super()
423
424
  @names = {}
@@ -430,6 +431,12 @@ module Rack
430
431
  @names = other.names.dup
431
432
  end
432
433
 
434
+ # on clear, we need to clear @names hash
435
+ def clear
436
+ super
437
+ @names.clear
438
+ end
439
+
433
440
  def each
434
441
  super do |k, v|
435
442
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -574,7 +581,6 @@ module Rack
574
581
  status.to_i
575
582
  end
576
583
  end
577
- module_function :status_code
578
584
 
579
585
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
580
586
 
@@ -588,18 +594,16 @@ module Rack
588
594
  part == '..' ? clean.pop : clean << part
589
595
  end
590
596
 
591
- clean.unshift '/' if parts.empty? || parts.first.empty?
592
-
593
- ::File.join clean
597
+ clean_path = clean.join(::File::SEPARATOR)
598
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
599
+ clean_path
594
600
  end
595
- module_function :clean_path_info
596
601
 
597
602
  NULL_BYTE = "\0"
598
603
 
599
604
  def valid_path?(path)
600
605
  path.valid_encoding? && !path.include?(NULL_BYTE)
601
606
  end
602
- module_function :valid_path?
603
607
 
604
608
  end
605
609
  end