rack 2.0.9.3 → 2.2.1

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 (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 -162
  6. data/Rakefile +37 -23
  7. data/{SPEC → SPEC.rdoc} +35 -10
  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/auth/abstract/handler.rb +3 -1
  13. data/lib/rack/auth/abstract/request.rb +1 -1
  14. data/lib/rack/auth/basic.rb +7 -4
  15. data/lib/rack/auth/digest/md5.rb +13 -11
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +4 -2
  18. data/lib/rack/auth/digest/request.rb +5 -3
  19. data/lib/rack/body_proxy.rb +15 -14
  20. data/lib/rack/builder.rb +116 -23
  21. data/lib/rack/cascade.rb +28 -12
  22. data/lib/rack/chunked.rb +68 -20
  23. data/lib/rack/common_logger.rb +33 -28
  24. data/lib/rack/conditional_get.rb +20 -16
  25. data/lib/rack/config.rb +2 -0
  26. data/lib/rack/content_length.rb +8 -7
  27. data/lib/rack/content_type.rb +5 -4
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +59 -34
  30. data/lib/rack/directory.rb +84 -64
  31. data/lib/rack/etag.rb +5 -4
  32. data/lib/rack/events.rb +19 -20
  33. data/lib/rack/file.rb +4 -173
  34. data/lib/rack/files.rb +218 -0
  35. data/lib/rack/handler/cgi.rb +2 -3
  36. data/lib/rack/handler/fastcgi.rb +4 -4
  37. data/lib/rack/handler/lsws.rb +3 -3
  38. data/lib/rack/handler/scgi.rb +9 -8
  39. data/lib/rack/handler/thin.rb +17 -11
  40. data/lib/rack/handler/webrick.rb +15 -6
  41. data/lib/rack/handler.rb +7 -2
  42. data/lib/rack/head.rb +1 -1
  43. data/lib/rack/lint.rb +72 -26
  44. data/lib/rack/lobster.rb +10 -10
  45. data/lib/rack/lock.rb +2 -1
  46. data/lib/rack/logger.rb +2 -0
  47. data/lib/rack/media_type.rb +10 -5
  48. data/lib/rack/method_override.rb +4 -2
  49. data/lib/rack/mime.rb +9 -1
  50. data/lib/rack/mock.rb +97 -20
  51. data/lib/rack/multipart/generator.rb +17 -13
  52. data/lib/rack/multipart/parser.rb +58 -73
  53. data/lib/rack/multipart/uploaded_file.rb +15 -7
  54. data/lib/rack/multipart.rb +7 -4
  55. data/lib/rack/null_logger.rb +2 -0
  56. data/lib/rack/query_parser.rb +53 -28
  57. data/lib/rack/recursive.rb +7 -5
  58. data/lib/rack/reloader.rb +8 -4
  59. data/lib/rack/request.rb +220 -61
  60. data/lib/rack/response.rb +127 -44
  61. data/lib/rack/rewindable_input.rb +4 -3
  62. data/lib/rack/runtime.rb +6 -4
  63. data/lib/rack/sendfile.rb +13 -9
  64. data/lib/rack/server.rb +95 -24
  65. data/lib/rack/session/abstract/id.rb +33 -21
  66. data/lib/rack/session/cookie.rb +12 -12
  67. data/lib/rack/session/memcache.rb +4 -93
  68. data/lib/rack/session/pool.rb +5 -3
  69. data/lib/rack/show_exceptions.rb +17 -13
  70. data/lib/rack/show_status.rb +5 -5
  71. data/lib/rack/static.rb +23 -11
  72. data/lib/rack/tempfile_reaper.rb +1 -1
  73. data/lib/rack/urlmap.rb +12 -6
  74. data/lib/rack/utils.rb +105 -130
  75. data/lib/rack/version.rb +29 -0
  76. data/lib/rack.rb +67 -73
  77. data/rack.gemspec +40 -28
  78. metadata +39 -182
  79. data/HISTORY.md +0 -520
  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 -107
  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 -520
  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 -721
  163. data/test/spec_null_logger.rb +0 -21
  164. data/test/spec_recursive.rb +0 -75
  165. data/test/spec_request.rb +0 -1423
  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
data/lib/rack/request.rb CHANGED
@@ -1,5 +1,4 @@
1
- require 'rack/utils'
2
- require 'rack/media_type'
1
+ # frozen_string_literal: true
3
2
 
4
3
  module Rack
5
4
  # Rack::Request provides a convenient interface to a Rack
@@ -11,7 +10,18 @@ module Rack
11
10
  # req.params["data"]
12
11
 
13
12
  class Request
14
- SCHEME_WHITELIST = %w(https http).freeze
13
+ (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
14
+
15
+ class << self
16
+ attr_accessor :ip_filter
17
+ end
18
+
19
+ self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) }
20
+ ALLOWED_SCHEMES = %w(https http).freeze
21
+ SCHEME_WHITELIST = ALLOWED_SCHEMES
22
+ if Object.respond_to?(:deprecate_constant)
23
+ deprecate_constant :SCHEME_WHITELIST
24
+ end
15
25
 
16
26
  def initialize(env)
17
27
  @params = nil
@@ -78,7 +88,7 @@ module Rack
78
88
  # assert_equal 'image/png,*/*', request.get_header('Accept')
79
89
  #
80
90
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
81
- def add_header key, v
91
+ def add_header(key, v)
82
92
  if v.nil?
83
93
  get_header key
84
94
  elsif has_header? key
@@ -100,7 +110,7 @@ module Rack
100
110
 
101
111
  module Helpers
102
112
  # The set of form-data media-types. Requests that do not indicate
103
- # one of the media types presents in this list will not be eligible
113
+ # one of the media types present in this list will not be eligible
104
114
  # for form-data / param parsing.
105
115
  FORM_DATA_MEDIA_TYPES = [
106
116
  'application/x-www-form-urlencoded',
@@ -108,7 +118,7 @@ module Rack
108
118
  ]
109
119
 
110
120
  # The set of media-types. Requests that do not indicate
111
- # one of the media types presents in this list will not be eligible
121
+ # one of the media types present in this list will not be eligible
112
122
  # for param parsing like soap attachments or generic multiparts
113
123
  PARSEABLE_DATA_MEDIA_TYPES = [
114
124
  'multipart/related',
@@ -119,11 +129,23 @@ module Rack
119
129
  # to include the port in a generated URI.
120
130
  DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
121
131
 
122
- HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'.freeze
123
- HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'.freeze
124
- HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'.freeze
125
- HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'.freeze
126
- HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'.freeze
132
+ # The address of the client which connected to the proxy.
133
+ HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'
134
+
135
+ # The contents of the host/:authority header sent to the proxy.
136
+ HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
137
+
138
+ # The value of the scheme sent to the proxy.
139
+ HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
140
+
141
+ # The protocol used to connect to the proxy.
142
+ HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
143
+
144
+ # The port used to connect to the proxy.
145
+ HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
146
+
147
+ # Another way for specifing https scheme was used.
148
+ HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
127
149
 
128
150
  def body; get_header(RACK_INPUT) end
129
151
  def script_name; get_header(SCRIPT_NAME).to_s end
@@ -159,10 +181,10 @@ module Rack
159
181
  def delete?; request_method == DELETE end
160
182
 
161
183
  # Checks the HTTP request method (or verb) to see if it was of type GET
162
- def get?; request_method == GET end
184
+ def get?; request_method == GET end
163
185
 
164
186
  # Checks the HTTP request method (or verb) to see if it was of type HEAD
165
- def head?; request_method == HEAD end
187
+ def head?; request_method == HEAD end
166
188
 
167
189
  # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
168
190
  def options?; request_method == OPTIONS end
@@ -197,19 +219,52 @@ module Rack
197
219
  end
198
220
  end
199
221
 
222
+ # The authority of the incoming request as defined by RFC3976.
223
+ # https://tools.ietf.org/html/rfc3986#section-3.2
224
+ #
225
+ # In HTTP/1, this is the `host` header.
226
+ # In HTTP/2, this is the `:authority` pseudo-header.
200
227
  def authority
201
- get_header(SERVER_NAME) + ':' + get_header(SERVER_PORT)
228
+ forwarded_authority || host_authority || server_authority
229
+ end
230
+
231
+ # The authority as defined by the `SERVER_NAME` and `SERVER_PORT`
232
+ # variables.
233
+ def server_authority
234
+ host = self.server_name
235
+ port = self.server_port
236
+
237
+ if host
238
+ if port
239
+ "#{host}:#{port}"
240
+ else
241
+ host
242
+ end
243
+ end
244
+ end
245
+
246
+ def server_name
247
+ get_header(SERVER_NAME)
248
+ end
249
+
250
+ def server_port
251
+ if port = get_header(SERVER_PORT)
252
+ Integer(port)
253
+ end
202
254
  end
203
255
 
204
256
  def cookies
205
- hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |k|
206
- set_header(k, {})
257
+ hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key|
258
+ set_header(key, {})
259
+ end
260
+
261
+ string = get_header(HTTP_COOKIE)
262
+
263
+ unless string == get_header(RACK_REQUEST_COOKIE_STRING)
264
+ hash.replace Utils.parse_cookies_header(string)
265
+ set_header(RACK_REQUEST_COOKIE_STRING, string)
207
266
  end
208
- string = get_header HTTP_COOKIE
209
267
 
210
- return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
211
- hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE
212
- set_header(RACK_REQUEST_COOKIE_STRING, string)
213
268
  hash
214
269
  end
215
270
 
@@ -222,46 +277,101 @@ module Rack
222
277
  get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
223
278
  end
224
279
 
225
- def host_with_port
226
- if forwarded = get_header(HTTP_X_FORWARDED_HOST)
227
- forwarded.split(/,\s?/).last
280
+ # The `HTTP_HOST` header.
281
+ def host_authority
282
+ get_header(HTTP_HOST)
283
+ end
284
+
285
+ def host_with_port(authority = self.authority)
286
+ host, _, port = split_authority(authority)
287
+
288
+ if port == DEFAULT_PORTS[self.scheme]
289
+ host
228
290
  else
229
- get_header(HTTP_HOST) || "#{get_header(SERVER_NAME) || get_header(SERVER_ADDR)}:#{get_header(SERVER_PORT)}"
291
+ authority
230
292
  end
231
293
  end
232
294
 
295
+ # Returns a formatted host, suitable for being used in a URI.
233
296
  def host
234
- # Remove port number.
235
- host_with_port.to_s.sub(/:\d+\z/, '')
297
+ split_authority(self.authority)[0]
298
+ end
299
+
300
+ # Returns an address suitable for being to resolve to an address.
301
+ # In the case of a domain name or IPv4 address, the result is the same
302
+ # as +host+. In the case of IPv6 or future address formats, the square
303
+ # brackets are removed.
304
+ def hostname
305
+ split_authority(self.authority)[1]
236
306
  end
237
307
 
238
308
  def port
239
- if port = host_with_port.split(/:/)[1]
240
- port.to_i
241
- elsif port = get_header(HTTP_X_FORWARDED_PORT)
242
- port.to_i
243
- elsif has_header?(HTTP_X_FORWARDED_HOST)
244
- DEFAULT_PORTS[scheme]
245
- elsif has_header?(HTTP_X_FORWARDED_PROTO)
246
- DEFAULT_PORTS[get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]]
247
- else
248
- get_header(SERVER_PORT).to_i
309
+ if authority = self.authority
310
+ _, _, port = split_authority(self.authority)
311
+
312
+ if port
313
+ return port
314
+ end
315
+ end
316
+
317
+ if forwarded_port = self.forwarded_port
318
+ return forwarded_port.first
319
+ end
320
+
321
+ if scheme = self.scheme
322
+ if port = DEFAULT_PORTS[self.scheme]
323
+ return port
324
+ end
325
+ end
326
+
327
+ self.server_port
328
+ end
329
+
330
+ def forwarded_for
331
+ if value = get_header(HTTP_X_FORWARDED_FOR)
332
+ split_header(value).map do |authority|
333
+ split_authority(wrap_ipv6(authority))[1]
334
+ end
335
+ end
336
+ end
337
+
338
+ def forwarded_port
339
+ if value = get_header(HTTP_X_FORWARDED_PORT)
340
+ split_header(value).map(&:to_i)
341
+ end
342
+ end
343
+
344
+ def forwarded_authority
345
+ if value = get_header(HTTP_X_FORWARDED_HOST)
346
+ wrap_ipv6(split_header(value).first)
249
347
  end
250
348
  end
251
349
 
252
350
  def ssl?
253
- scheme == 'https'
351
+ scheme == 'https' || scheme == 'wss'
254
352
  end
255
353
 
256
354
  def ip
257
- remote_addrs = split_ip_addresses(get_header('REMOTE_ADDR'))
258
- remote_addrs = reject_trusted_ip_addresses(remote_addrs)
355
+ remote_addresses = split_header(get_header('REMOTE_ADDR'))
356
+ external_addresses = reject_trusted_ip_addresses(remote_addresses)
259
357
 
260
- return remote_addrs.first if remote_addrs.any?
358
+ unless external_addresses.empty?
359
+ return external_addresses.first
360
+ end
261
361
 
262
- forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
362
+ if forwarded_for = self.forwarded_for
363
+ unless forwarded_for.empty?
364
+ # The forwarded for addresses are ordered: client, proxy1, proxy2.
365
+ # So we reject all the trusted addresses (proxy*) and return the
366
+ # last client. Or if we trust everyone, we just return the first
367
+ # address.
368
+ return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first
369
+ end
370
+ end
263
371
 
264
- return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
372
+ # If all the addresses are trusted, and we aren't forwarded, just return
373
+ # the first remote address, which represents the source of the request.
374
+ remote_addresses.first
265
375
  end
266
376
 
267
377
  # The media type (type/subtype) portion of the CONTENT_TYPE header
@@ -302,6 +412,7 @@ module Rack
302
412
  def form_data?
303
413
  type = media_type
304
414
  meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
415
+
305
416
  (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
306
417
  end
307
418
 
@@ -337,7 +448,7 @@ module Rack
337
448
 
338
449
  # Fix for Safari Ajax postings that always append \0
339
450
  # form_vars.sub!(/\0\z/, '') # performance replacement:
340
- form_vars.slice!(-1) if form_vars[-1] == ?\0
451
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
341
452
 
342
453
  set_header RACK_REQUEST_FORM_VARS, form_vars
343
454
  set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
@@ -356,8 +467,6 @@ module Rack
356
467
  # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
357
468
  def params
358
469
  self.GET.merge(self.POST)
359
- rescue EOFError
360
- self.GET.dup
361
470
  end
362
471
 
363
472
  # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
@@ -386,13 +495,12 @@ module Rack
386
495
  #
387
496
  # <tt>env['rack.input']</tt> is not touched.
388
497
  def delete_param(k)
389
- [ self.POST.delete(k), self.GET.delete(k) ].compact.first
498
+ post_value, get_value = self.POST.delete(k), self.GET.delete(k)
499
+ post_value || get_value
390
500
  end
391
501
 
392
502
  def base_url
393
- url = "#{scheme}://#{host}"
394
- url << ":#{port}" if port != DEFAULT_PORTS[scheme]
395
- url
503
+ "#{scheme}://#{host_with_port}"
396
504
  end
397
505
 
398
506
  # Tries to return a remake of the original request URL as a string.
@@ -417,7 +525,7 @@ module Rack
417
525
  end
418
526
 
419
527
  def trusted_proxy?(ip)
420
- ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
528
+ Rack::Request.ip_filter.call(ip)
421
529
  end
422
530
 
423
531
  # shortcut for <tt>request.params[key]</tt>
@@ -449,6 +557,20 @@ module Rack
449
557
 
450
558
  def default_session; {}; end
451
559
 
560
+ # Assist with compatibility when processing `X-Forwarded-For`.
561
+ def wrap_ipv6(host)
562
+ # Even thought IPv6 addresses should be wrapped in square brackets,
563
+ # sometimes this is not done in various legacy/underspecified headers.
564
+ # So we try to fix this situation for compatibility reasons.
565
+
566
+ # Try to detect IPv6 addresses which aren't escaped yet:
567
+ if !host.start_with?('[') && host.count(':') > 1
568
+ "[#{host}]"
569
+ else
570
+ host
571
+ end
572
+ end
573
+
452
574
  def parse_http_accept_header(header)
453
575
  header.to_s.split(/\s*,\s*/).map do |part|
454
576
  attribute, parameters = part.split(/\s*;\s*/, 2)
@@ -464,7 +586,7 @@ module Rack
464
586
  Utils.default_query_parser
465
587
  end
466
588
 
467
- def parse_query(qs, d='&')
589
+ def parse_query(qs, d = '&')
468
590
  query_parser.parse_nested_query(qs, d)
469
591
  end
470
592
 
@@ -472,8 +594,39 @@ module Rack
472
594
  Rack::Multipart.extract_multipart(self, query_parser)
473
595
  end
474
596
 
475
- def split_ip_addresses(ip_addresses)
476
- ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
597
+ def split_header(value)
598
+ value ? value.strip.split(/[,\s]+/) : []
599
+ end
600
+
601
+ AUTHORITY = /
602
+ # The host:
603
+ (?<host>
604
+ # An IPv6 address:
605
+ (\[(?<ip6>.*)\])
606
+ |
607
+ # An IPv4 address:
608
+ (?<ip4>[\d\.]+)
609
+ |
610
+ # A hostname:
611
+ (?<name>[a-zA-Z0-9\.\-]+)
612
+ )
613
+ # The optional port:
614
+ (:(?<port>\d+))?
615
+ /x
616
+
617
+ private_constant :AUTHORITY
618
+
619
+ def split_authority(authority)
620
+ if match = AUTHORITY.match(authority)
621
+ if address = match[:ip6]
622
+ return match[:host], address, match[:port]&.to_i
623
+ else
624
+ return match[:host], match[:host], match[:port]&.to_i
625
+ end
626
+ end
627
+
628
+ # Give up!
629
+ return authority, authority, nil
477
630
  end
478
631
 
479
632
  def reject_trusted_ip_addresses(ip_addresses)
@@ -481,16 +634,22 @@ module Rack
481
634
  end
482
635
 
483
636
  def forwarded_scheme
484
- scheme_headers = [
485
- get_header(HTTP_X_FORWARDED_SCHEME),
486
- get_header(HTTP_X_FORWARDED_PROTO).to_s.split(',')[0]
487
- ]
637
+ allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
638
+ allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
639
+ end
488
640
 
489
- scheme_headers.each do |header|
490
- return header if SCHEME_WHITELIST.include?(header)
491
- end
641
+ def allowed_scheme(header)
642
+ header if ALLOWED_SCHEMES.include?(header)
643
+ end
492
644
 
493
- nil
645
+ def extract_proto_header(header)
646
+ if header
647
+ if (comma_index = header.index(','))
648
+ header[0, comma_index]
649
+ else
650
+ header
651
+ end
652
+ end
494
653
  end
495
654
  end
496
655