rack 2.0.9 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +681 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +152 -148
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +29 -5
  8. data/bin/rackup +1 -0
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +3 -1
  11. data/example/protectedlobster.ru +2 -0
  12. data/lib/rack.rb +67 -73
  13. data/lib/rack/auth/abstract/handler.rb +3 -1
  14. data/lib/rack/auth/abstract/request.rb +1 -1
  15. data/lib/rack/auth/basic.rb +7 -4
  16. data/lib/rack/auth/digest/md5.rb +13 -11
  17. data/lib/rack/auth/digest/nonce.rb +6 -3
  18. data/lib/rack/auth/digest/params.rb +4 -2
  19. data/lib/rack/auth/digest/request.rb +5 -3
  20. data/lib/rack/body_proxy.rb +15 -14
  21. data/lib/rack/builder.rb +116 -23
  22. data/lib/rack/cascade.rb +28 -12
  23. data/lib/rack/chunked.rb +68 -20
  24. data/lib/rack/common_logger.rb +33 -25
  25. data/lib/rack/conditional_get.rb +20 -16
  26. data/lib/rack/config.rb +2 -0
  27. data/lib/rack/content_length.rb +8 -7
  28. data/lib/rack/content_type.rb +5 -4
  29. data/lib/rack/core_ext/regexp.rb +14 -0
  30. data/lib/rack/deflater.rb +59 -34
  31. data/lib/rack/directory.rb +84 -64
  32. data/lib/rack/etag.rb +5 -4
  33. data/lib/rack/events.rb +19 -20
  34. data/lib/rack/file.rb +4 -173
  35. data/lib/rack/files.rb +218 -0
  36. data/lib/rack/handler.rb +7 -2
  37. data/lib/rack/handler/cgi.rb +2 -3
  38. data/lib/rack/handler/fastcgi.rb +4 -4
  39. data/lib/rack/handler/lsws.rb +3 -3
  40. data/lib/rack/handler/scgi.rb +9 -8
  41. data/lib/rack/handler/thin.rb +17 -11
  42. data/lib/rack/handler/webrick.rb +15 -6
  43. data/lib/rack/head.rb +1 -1
  44. data/lib/rack/lint.rb +71 -25
  45. data/lib/rack/lobster.rb +10 -10
  46. data/lib/rack/lock.rb +2 -1
  47. data/lib/rack/logger.rb +2 -0
  48. data/lib/rack/media_type.rb +10 -5
  49. data/lib/rack/method_override.rb +4 -2
  50. data/lib/rack/mime.rb +9 -1
  51. data/lib/rack/mock.rb +97 -20
  52. data/lib/rack/multipart.rb +6 -4
  53. data/lib/rack/multipart/generator.rb +17 -13
  54. data/lib/rack/multipart/parser.rb +54 -56
  55. data/lib/rack/multipart/uploaded_file.rb +15 -7
  56. data/lib/rack/null_logger.rb +2 -0
  57. data/lib/rack/query_parser.rb +53 -28
  58. data/lib/rack/recursive.rb +7 -5
  59. data/lib/rack/reloader.rb +8 -4
  60. data/lib/rack/request.rb +220 -61
  61. data/lib/rack/response.rb +127 -44
  62. data/lib/rack/rewindable_input.rb +4 -3
  63. data/lib/rack/runtime.rb +6 -4
  64. data/lib/rack/sendfile.rb +13 -9
  65. data/lib/rack/server.rb +95 -24
  66. data/lib/rack/session/abstract/id.rb +33 -21
  67. data/lib/rack/session/cookie.rb +12 -12
  68. data/lib/rack/session/memcache.rb +4 -93
  69. data/lib/rack/session/pool.rb +5 -3
  70. data/lib/rack/show_exceptions.rb +17 -13
  71. data/lib/rack/show_status.rb +5 -5
  72. data/lib/rack/static.rb +23 -11
  73. data/lib/rack/tempfile_reaper.rb +1 -1
  74. data/lib/rack/urlmap.rb +12 -6
  75. data/lib/rack/utils.rb +97 -110
  76. data/lib/rack/version.rb +29 -0
  77. data/rack.gemspec +40 -28
  78. metadata +36 -179
  79. data/HISTORY.md +0 -505
  80. data/test/builder/an_underscore_app.rb +0 -5
  81. data/test/builder/anything.rb +0 -5
  82. data/test/builder/comment.ru +0 -4
  83. data/test/builder/end.ru +0 -5
  84. data/test/builder/line.ru +0 -1
  85. data/test/builder/options.ru +0 -2
  86. data/test/cgi/assets/folder/test.js +0 -1
  87. data/test/cgi/assets/fonts/font.eot +0 -1
  88. data/test/cgi/assets/images/image.png +0 -1
  89. data/test/cgi/assets/index.html +0 -1
  90. data/test/cgi/assets/javascripts/app.js +0 -1
  91. data/test/cgi/assets/stylesheets/app.css +0 -1
  92. data/test/cgi/lighttpd.conf +0 -26
  93. data/test/cgi/rackup_stub.rb +0 -6
  94. data/test/cgi/sample_rackup.ru +0 -5
  95. data/test/cgi/test +0 -9
  96. data/test/cgi/test+directory/test+file +0 -1
  97. data/test/cgi/test.fcgi +0 -9
  98. data/test/cgi/test.gz +0 -0
  99. data/test/cgi/test.ru +0 -5
  100. data/test/gemloader.rb +0 -10
  101. data/test/helper.rb +0 -34
  102. data/test/multipart/bad_robots +0 -259
  103. data/test/multipart/binary +0 -0
  104. data/test/multipart/content_type_and_no_filename +0 -6
  105. data/test/multipart/empty +0 -10
  106. data/test/multipart/fail_16384_nofile +0 -814
  107. data/test/multipart/file1.txt +0 -1
  108. data/test/multipart/filename_and_modification_param +0 -7
  109. data/test/multipart/filename_and_no_name +0 -6
  110. data/test/multipart/filename_with_encoded_words +0 -7
  111. data/test/multipart/filename_with_escaped_quotes +0 -6
  112. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  113. data/test/multipart/filename_with_null_byte +0 -7
  114. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  115. data/test/multipart/filename_with_single_quote +0 -7
  116. data/test/multipart/filename_with_unescaped_percentages +0 -6
  117. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  118. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  119. data/test/multipart/filename_with_unescaped_quotes +0 -6
  120. data/test/multipart/ie +0 -6
  121. data/test/multipart/invalid_character +0 -6
  122. data/test/multipart/mixed_files +0 -21
  123. data/test/multipart/nested +0 -10
  124. data/test/multipart/none +0 -9
  125. data/test/multipart/quoted +0 -15
  126. data/test/multipart/rack-logo.png +0 -0
  127. data/test/multipart/semicolon +0 -6
  128. data/test/multipart/text +0 -15
  129. data/test/multipart/three_files_three_fields +0 -31
  130. data/test/multipart/unity3d_wwwform +0 -11
  131. data/test/multipart/webkit +0 -32
  132. data/test/rackup/config.ru +0 -31
  133. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  134. data/test/spec_auth_basic.rb +0 -89
  135. data/test/spec_auth_digest.rb +0 -260
  136. data/test/spec_body_proxy.rb +0 -85
  137. data/test/spec_builder.rb +0 -233
  138. data/test/spec_cascade.rb +0 -63
  139. data/test/spec_cgi.rb +0 -84
  140. data/test/spec_chunked.rb +0 -103
  141. data/test/spec_common_logger.rb +0 -95
  142. data/test/spec_conditional_get.rb +0 -103
  143. data/test/spec_config.rb +0 -23
  144. data/test/spec_content_length.rb +0 -86
  145. data/test/spec_content_type.rb +0 -46
  146. data/test/spec_deflater.rb +0 -375
  147. data/test/spec_directory.rb +0 -148
  148. data/test/spec_etag.rb +0 -108
  149. data/test/spec_events.rb +0 -133
  150. data/test/spec_fastcgi.rb +0 -85
  151. data/test/spec_file.rb +0 -264
  152. data/test/spec_handler.rb +0 -57
  153. data/test/spec_head.rb +0 -46
  154. data/test/spec_lint.rb +0 -515
  155. data/test/spec_lobster.rb +0 -59
  156. data/test/spec_lock.rb +0 -204
  157. data/test/spec_logger.rb +0 -24
  158. data/test/spec_media_type.rb +0 -42
  159. data/test/spec_method_override.rb +0 -110
  160. data/test/spec_mime.rb +0 -51
  161. data/test/spec_mock.rb +0 -359
  162. data/test/spec_multipart.rb +0 -722
  163. data/test/spec_null_logger.rb +0 -21
  164. data/test/spec_recursive.rb +0 -75
  165. data/test/spec_request.rb +0 -1407
  166. data/test/spec_response.rb +0 -528
  167. data/test/spec_rewindable_input.rb +0 -128
  168. data/test/spec_runtime.rb +0 -50
  169. data/test/spec_sendfile.rb +0 -125
  170. data/test/spec_server.rb +0 -193
  171. data/test/spec_session_abstract_id.rb +0 -31
  172. data/test/spec_session_abstract_session_hash.rb +0 -45
  173. data/test/spec_session_cookie.rb +0 -442
  174. data/test/spec_session_memcache.rb +0 -357
  175. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  176. data/test/spec_session_pool.rb +0 -247
  177. data/test/spec_show_exceptions.rb +0 -93
  178. data/test/spec_show_status.rb +0 -104
  179. data/test/spec_static.rb +0 -184
  180. data/test/spec_tempfile_reaper.rb +0 -64
  181. data/test/spec_thin.rb +0 -96
  182. data/test/spec_urlmap.rb +0 -237
  183. data/test/spec_utils.rb +0 -742
  184. data/test/spec_version.rb +0 -11
  185. data/test/spec_webrick.rb +0 -206
  186. data/test/static/another/index.html +0 -1
  187. data/test/static/foo.html +0 -1
  188. data/test/static/index.html +0 -1
  189. data/test/testrequest.rb +0 -78
  190. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  191. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,4 +1,4 @@
1
- require 'rack/body_proxy'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Rack
4
4
 
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
1
5
  module Rack
2
6
  # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
7
  # dispatches accordingly. Support for HTTP/1.1 host names exists if
@@ -12,17 +16,16 @@ module Rack
12
16
  # first, since they are most specific.
13
17
 
14
18
  class URLMap
15
- NEGATIVE_INFINITY = -1.0 / 0.0
16
- INFINITY = 1.0 / 0.0
17
-
18
19
  def initialize(map = {})
19
20
  remap(map)
20
21
  end
21
22
 
22
23
  def remap(map)
24
+ @known_hosts = Set[]
23
25
  @mapping = map.map { |location, app|
24
26
  if location =~ %r{\Ahttps?://(.*?)(/.*)}
25
27
  host, location = $1, $2
28
+ @known_hosts << host
26
29
  else
27
30
  host = nil
28
31
  end
@@ -36,7 +39,7 @@ module Rack
36
39
 
37
40
  [host, location, match, app]
38
41
  }.sort_by do |(host, location, _, _)|
39
- [host ? -host.size : INFINITY, -location.size]
42
+ [host ? -host.size : Float::INFINITY, -location.size]
40
43
  end
41
44
  end
42
45
 
@@ -50,10 +53,13 @@ module Rack
50
53
  is_same_server = casecmp?(http_host, server_name) ||
51
54
  casecmp?(http_host, "#{server_name}:#{server_port}")
52
55
 
56
+ is_host_known = @known_hosts.include? http_host
57
+
53
58
  @mapping.each do |host, location, match, app|
54
59
  unless casecmp?(http_host, host) \
55
60
  || casecmp?(server_name, host) \
56
- || (!host && is_same_server)
61
+ || (!host && is_same_server) \
62
+ || (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host
57
63
  next
58
64
  end
59
65
 
@@ -68,7 +74,7 @@ module Rack
68
74
  return app.call(env)
69
75
  end
70
76
 
71
- [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
77
+ [404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]]
72
78
 
73
79
  ensure
74
80
  env[PATH_INFO] = path
@@ -1,16 +1,21 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
3
+
2
4
  require 'uri'
3
5
  require 'fileutils'
4
6
  require 'set'
5
7
  require 'tempfile'
6
- require 'rack/query_parser'
7
8
  require 'time'
8
9
 
10
+ require_relative 'query_parser'
11
+
9
12
  module Rack
10
13
  # Rack::Utils contains a grab-bag of useful methods for writing web
11
14
  # applications adopted from all kinds of Ruby libraries.
12
15
 
13
16
  module Utils
17
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
18
+
14
19
  ParameterTypeError = QueryParser::ParameterTypeError
15
20
  InvalidParameterError = QueryParser::InvalidParameterError
16
21
  DEFAULT_SEP = QueryParser::DEFAULT_SEP
@@ -24,33 +29,30 @@ module Rack
24
29
  # This helps prevent a rogue client from flooding a Request.
25
30
  self.default_query_parser = QueryParser.make_default(65536, 100)
26
31
 
32
+ module_function
33
+
27
34
  # URI escapes. (CGI style space to +)
28
35
  def escape(s)
29
36
  URI.encode_www_form_component(s)
30
37
  end
31
- module_function :escape
32
38
 
33
39
  # Like URI escaping, but with %20 instead of +. Strictly speaking this is
34
40
  # true URI escaping.
35
41
  def escape_path(s)
36
42
  ::URI::DEFAULT_PARSER.escape s
37
43
  end
38
- module_function :escape_path
39
44
 
40
45
  # Unescapes the **path** component of a URI. See Rack::Utils.unescape for
41
46
  # unescaping query parameters or form components.
42
47
  def unescape_path(s)
43
48
  ::URI::DEFAULT_PARSER.unescape s
44
49
  end
45
- module_function :unescape_path
46
-
47
50
 
48
51
  # Unescapes a URI escaped string with +encoding+. +encoding+ will be the
49
52
  # target encoding of the string returned, and it defaults to UTF-8
50
53
  def unescape(s, encoding = Encoding::UTF_8)
51
54
  URI.decode_www_form_component(s, encoding)
52
55
  end
53
- module_function :unescape
54
56
 
55
57
  class << self
56
58
  attr_accessor :multipart_part_limit
@@ -82,21 +84,20 @@ module Rack
82
84
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
83
85
  end
84
86
  else
87
+ # :nocov:
85
88
  def clock_time
86
89
  Time.now.to_f
87
90
  end
91
+ # :nocov:
88
92
  end
89
- module_function :clock_time
90
93
 
91
94
  def parse_query(qs, d = nil, &unescaper)
92
95
  Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
93
96
  end
94
- module_function :parse_query
95
97
 
96
98
  def parse_nested_query(qs, d = nil)
97
99
  Rack::Utils.default_query_parser.parse_nested_query(qs, d)
98
100
  end
99
- module_function :parse_nested_query
100
101
 
101
102
  def build_query(params)
102
103
  params.map { |k, v|
@@ -107,7 +108,6 @@ module Rack
107
108
  end
108
109
  }.join("&")
109
110
  end
110
- module_function :build_query
111
111
 
112
112
  def build_nested_query(value, prefix = nil)
113
113
  case value
@@ -118,7 +118,7 @@ module Rack
118
118
  when Hash
119
119
  value.map { |k, v|
120
120
  build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
121
- }.reject(&:empty?).join('&')
121
+ }.delete_if(&:empty?).join('&')
122
122
  when nil
123
123
  prefix
124
124
  else
@@ -126,20 +126,22 @@ module Rack
126
126
  "#{prefix}=#{escape(value)}"
127
127
  end
128
128
  end
129
- module_function :build_nested_query
130
129
 
131
130
  def q_values(q_value_header)
132
131
  q_value_header.to_s.split(/\s*,\s*/).map do |part|
133
132
  value, parameters = part.split(/\s*;\s*/, 2)
134
133
  quality = 1.0
135
- if md = /\Aq=([\d.]+)/.match(parameters)
134
+ if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
136
135
  quality = md[1].to_f
137
136
  end
138
137
  [value, quality]
139
138
  end
140
139
  end
141
- module_function :q_values
142
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.
143
145
  def best_q_match(q_value_header, available_mimes)
144
146
  values = q_values(q_value_header)
145
147
 
@@ -152,7 +154,6 @@ module Rack
152
154
  end.last
153
155
  matches && matches.first
154
156
  end
155
- module_function :best_q_match
156
157
 
157
158
  ESCAPE_HTML = {
158
159
  "&" => "&amp;",
@@ -169,51 +170,51 @@ module Rack
169
170
  def escape_html(string)
170
171
  string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
171
172
  end
172
- module_function :escape_html
173
173
 
174
174
  def select_best_encoding(available_encodings, accept_encoding)
175
175
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
176
176
 
177
- expanded_accept_encoding =
178
- accept_encoding.map { |m, q|
179
- if m == "*"
180
- (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
181
- else
182
- [[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]
183
185
  end
184
- }.inject([]) { |mem, list|
185
- mem + list
186
- }
186
+ else
187
+ expanded_accept_encoding << [m, q, preference]
188
+ end
189
+ end
187
190
 
188
- encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
191
+ encoding_candidates = expanded_accept_encoding
192
+ .sort_by { |_, q, p| [-q, p] }
193
+ .map!(&:first)
189
194
 
190
195
  unless encoding_candidates.include?("identity")
191
196
  encoding_candidates.push("identity")
192
197
  end
193
198
 
194
- expanded_accept_encoding.each { |m, q|
199
+ expanded_accept_encoding.each do |m, q|
195
200
  encoding_candidates.delete(m) if q == 0.0
196
- }
201
+ end
197
202
 
198
- return (encoding_candidates & available_encodings)[0]
203
+ (encoding_candidates & available_encodings)[0]
199
204
  end
200
- module_function :select_best_encoding
201
205
 
202
206
  def parse_cookies(env)
203
207
  parse_cookies_header env[HTTP_COOKIE]
204
208
  end
205
- module_function :parse_cookies
206
209
 
207
210
  def parse_cookies_header(header)
208
- # According to RFC 2109:
209
- # If multiple cookies satisfy the criteria above, they are ordered in
210
- # the Cookie header such that those with more specific Path attributes
211
- # precede those with less specific. Ordering with respect to other
212
- # attributes (e.g., Domain) is unspecified.
213
- cookies = parse_query(header, ';,') { |s| unescape(s) rescue s }
214
- 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
+ cookies = parse_query(header, ';') { |s| unescape(s) rescue s }
216
+ cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v }
215
217
  end
216
- module_function :parse_cookies_header
217
218
 
218
219
  def add_cookie_to_header(header, key, value)
219
220
  case value
@@ -221,31 +222,7 @@ module Rack
221
222
  domain = "; domain=#{value[:domain]}" if value[:domain]
222
223
  path = "; path=#{value[:path]}" if value[:path]
223
224
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
224
- # There is an RFC mess in the area of date formatting for Cookies. Not
225
- # only are there contradicting RFCs and examples within RFC text, but
226
- # there are also numerous conflicting names of fields and partially
227
- # cross-applicable specifications.
228
- #
229
- # These are best described in RFC 2616 3.3.1. This RFC text also
230
- # specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
231
- # fixed length format with space-date delimited fields.
232
- #
233
- # See also RFC 1123 section 5.2.14.
234
- #
235
- # RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
236
- # in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
237
- # the space delimited format. These formats are compliant with RFC 2822.
238
- #
239
- # For reference, all involved RFCs are:
240
- # RFC 822
241
- # RFC 1123
242
- # RFC 2109
243
- # RFC 2616
244
- # RFC 2822
245
- # RFC 2965
246
- # RFC 6265
247
- expires = "; expires=" +
248
- rfc2822(value[:expires].clone.gmtime) if value[:expires]
225
+ expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
249
226
  secure = "; secure" if value[:secure]
250
227
  httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
251
228
  same_site =
@@ -253,11 +230,11 @@ module Rack
253
230
  when false, nil
254
231
  nil
255
232
  when :none, 'None', :None
256
- '; SameSite=None'.freeze
233
+ '; SameSite=None'
257
234
  when :lax, 'Lax', :Lax
258
- '; SameSite=Lax'.freeze
235
+ '; SameSite=Lax'
259
236
  when true, :strict, 'Strict', :Strict
260
- '; SameSite=Strict'.freeze
237
+ '; SameSite=Strict'
261
238
  else
262
239
  raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
263
240
  end
@@ -279,13 +256,11 @@ module Rack
279
256
  raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
280
257
  end
281
258
  end
282
- module_function :add_cookie_to_header
283
259
 
284
260
  def set_cookie_header!(header, key, value)
285
261
  header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
286
262
  nil
287
263
  end
288
- module_function :set_cookie_header!
289
264
 
290
265
  def make_delete_cookie_header(header, key, value)
291
266
  case header
@@ -297,25 +272,30 @@ module Rack
297
272
  cookies = header
298
273
  end
299
274
 
300
- cookies.reject! { |cookie|
301
- if value[:domain]
302
- cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
303
- elsif value[:path]
304
- cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
305
- else
306
- cookie =~ /\A#{escape(key)}=/
307
- end
308
- }
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}(?:;|$)/
286
+ else
287
+ /\A#{key}=/
288
+ end
289
+
290
+ cookies.reject! { |cookie| regexp.match? cookie }
309
291
 
310
292
  cookies.join("\n")
311
293
  end
312
- module_function :make_delete_cookie_header
313
294
 
314
295
  def delete_cookie_header!(header, key, value = {})
315
296
  header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
316
297
  nil
317
298
  end
318
- module_function :delete_cookie_header!
319
299
 
320
300
  # Adds a cookie that will *remove* a cookie from the client. Hence the
321
301
  # strange method name.
@@ -323,17 +303,15 @@ module Rack
323
303
  new_header = make_delete_cookie_header(header, key, value)
324
304
 
325
305
  add_cookie_to_header(new_header, key,
326
- {:value => '', :path => nil, :domain => nil,
327
- :max_age => '0',
328
- :expires => Time.at(0) }.merge(value))
306
+ { value: '', path: nil, domain: nil,
307
+ max_age: '0',
308
+ expires: Time.at(0) }.merge(value))
329
309
 
330
310
  end
331
- module_function :add_remove_cookie_to_header
332
311
 
333
312
  def rfc2822(time)
334
313
  time.rfc2822
335
314
  end
336
- module_function :rfc2822
337
315
 
338
316
  # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
339
317
  # of '% %b %Y'.
@@ -349,7 +327,6 @@ module Rack
349
327
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
350
328
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
351
329
  end
352
- module_function :rfc2109
353
330
 
354
331
  # Parses the "Range:" header, if present, into an array of Range objects.
355
332
  # Returns nil if the header is missing or syntactically invalid.
@@ -358,7 +335,6 @@ module Rack
358
335
  warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
359
336
  get_byte_ranges env['HTTP_RANGE'], size
360
337
  end
361
- module_function :byte_ranges
362
338
 
363
339
  def get_byte_ranges(http_range, size)
364
340
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
@@ -366,7 +342,7 @@ module Rack
366
342
  ranges = []
367
343
  $1.split(/,\s*/).each do |range_spec|
368
344
  return nil unless range_spec =~ /(\d*)-(\d*)/
369
- r0,r1 = $1, $2
345
+ r0, r1 = $1, $2
370
346
  if r0.empty?
371
347
  return nil if r1.empty?
372
348
  # suffix-byte-range-spec, represents trailing suffix of file
@@ -380,14 +356,13 @@ module Rack
380
356
  else
381
357
  r1 = r1.to_i
382
358
  return nil if r1 < r0 # backwards range is syntactically invalid
383
- r1 = size-1 if r1 >= size
359
+ r1 = size - 1 if r1 >= size
384
360
  end
385
361
  end
386
362
  ranges << (r0..r1) if r0 <= r1
387
363
  end
388
364
  ranges
389
365
  end
390
- module_function :get_byte_ranges
391
366
 
392
367
  # Constant time string comparison.
393
368
  #
@@ -401,10 +376,9 @@ module Rack
401
376
  l = a.unpack("C*")
402
377
 
403
378
  r, i = 0, -1
404
- b.each_byte { |v| r |= v ^ l[i+=1] }
379
+ b.each_byte { |v| r |= v ^ l[i += 1] }
405
380
  r == 0
406
381
  end
407
- module_function :secure_compare
408
382
 
409
383
  # Context allows the use of a compatible middleware at different points
410
384
  # in a request handling stack. A compatible middleware must define
@@ -427,19 +401,25 @@ module Rack
427
401
  self.class.new(@for, app)
428
402
  end
429
403
 
430
- def context(env, app=@app)
404
+ def context(env, app = @app)
431
405
  recontext(app).call(env)
432
406
  end
433
407
  end
434
408
 
435
409
  # A case-insensitive Hash that preserves the original case of a
436
410
  # header when set.
437
- class HeaderHash < Hash
438
- def self.new(hash={})
439
- HeaderHash === hash ? hash : super(hash)
411
+ #
412
+ # @api private
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
440
420
  end
441
421
 
442
- def initialize(hash={})
422
+ def initialize(hash = {})
443
423
  super()
444
424
  @names = {}
445
425
  hash.each { |k, v| self[k] = v }
@@ -451,6 +431,12 @@ module Rack
451
431
  @names = other.names.dup
452
432
  end
453
433
 
434
+ # on clear, we need to clear @names hash
435
+ def clear
436
+ super
437
+ @names.clear
438
+ end
439
+
454
440
  def each
455
441
  super do |k, v|
456
442
  yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
@@ -459,7 +445,7 @@ module Rack
459
445
 
460
446
  def to_hash
461
447
  hash = {}
462
- each { |k,v| hash[k] = v }
448
+ each { |k, v| hash[k] = v }
463
449
  hash
464
450
  end
465
451
 
@@ -512,13 +498,14 @@ module Rack
512
498
 
513
499
  # Every standard HTTP code mapped to the appropriate message.
514
500
  # Generated with:
515
- # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
516
- # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
517
- # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
501
+ # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
502
+ # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
503
+ # puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
518
504
  HTTP_STATUS_CODES = {
519
505
  100 => 'Continue',
520
506
  101 => 'Switching Protocols',
521
507
  102 => 'Processing',
508
+ 103 => 'Early Hints',
522
509
  200 => 'OK',
523
510
  201 => 'Created',
524
511
  202 => 'Accepted',
@@ -535,6 +522,7 @@ module Rack
535
522
  303 => 'See Other',
536
523
  304 => 'Not Modified',
537
524
  305 => 'Use Proxy',
525
+ 306 => '(Unused)',
538
526
  307 => 'Temporary Redirect',
539
527
  308 => 'Permanent Redirect',
540
528
  400 => 'Bad Request',
@@ -559,6 +547,7 @@ module Rack
559
547
  422 => 'Unprocessable Entity',
560
548
  423 => 'Locked',
561
549
  424 => 'Failed Dependency',
550
+ 425 => 'Too Early',
562
551
  426 => 'Upgrade Required',
563
552
  428 => 'Precondition Required',
564
553
  429 => 'Too Many Requests',
@@ -573,12 +562,13 @@ module Rack
573
562
  506 => 'Variant Also Negotiates',
574
563
  507 => 'Insufficient Storage',
575
564
  508 => 'Loop Detected',
565
+ 509 => 'Bandwidth Limit Exceeded',
576
566
  510 => 'Not Extended',
577
567
  511 => 'Network Authentication Required'
578
568
  }
579
569
 
580
570
  # Responses with HTTP status codes that should not have an entity body
581
- STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
571
+ STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
582
572
 
583
573
  SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
584
574
  [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
@@ -586,12 +576,11 @@ module Rack
586
576
 
587
577
  def status_code(status)
588
578
  if status.is_a?(Symbol)
589
- SYMBOL_TO_STATUS_CODE[status] || 500
579
+ SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
590
580
  else
591
581
  status.to_i
592
582
  end
593
583
  end
594
- module_function :status_code
595
584
 
596
585
  PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
597
586
 
@@ -605,18 +594,16 @@ module Rack
605
594
  part == '..' ? clean.pop : clean << part
606
595
  end
607
596
 
608
- clean.unshift '/' if parts.empty? || parts.first.empty?
609
-
610
- ::File.join(*clean)
597
+ clean_path = clean.join(::File::SEPARATOR)
598
+ clean_path.prepend("/") if parts.empty? || parts.first.empty?
599
+ clean_path
611
600
  end
612
- module_function :clean_path_info
613
601
 
614
- NULL_BYTE = "\0".freeze
602
+ NULL_BYTE = "\0"
615
603
 
616
604
  def valid_path?(path)
617
605
  path.valid_encoding? && !path.include?(NULL_BYTE)
618
606
  end
619
- module_function :valid_path?
620
607
 
621
608
  end
622
609
  end