rack 2.1.0 → 3.1.0

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.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +377 -16
  3. data/CONTRIBUTING.md +144 -0
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +328 -0
  6. data/SPEC.rdoc +365 -0
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +2 -2
  9. data/lib/rack/auth/basic.rb +4 -7
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +34 -12
  12. data/lib/rack/builder.rb +162 -59
  13. data/lib/rack/cascade.rb +24 -10
  14. data/lib/rack/common_logger.rb +43 -28
  15. data/lib/rack/conditional_get.rb +30 -25
  16. data/lib/rack/constants.rb +66 -0
  17. data/lib/rack/content_length.rb +10 -16
  18. data/lib/rack/content_type.rb +9 -7
  19. data/lib/rack/deflater.rb +78 -50
  20. data/lib/rack/directory.rb +86 -63
  21. data/lib/rack/etag.rb +14 -22
  22. data/lib/rack/events.rb +18 -17
  23. data/lib/rack/files.rb +99 -61
  24. data/lib/rack/head.rb +8 -9
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +868 -642
  27. data/lib/rack/lock.rb +2 -6
  28. data/lib/rack/logger.rb +3 -0
  29. data/lib/rack/media_type.rb +9 -4
  30. data/lib/rack/method_override.rb +6 -2
  31. data/lib/rack/mime.rb +14 -5
  32. data/lib/rack/mock.rb +1 -253
  33. data/lib/rack/mock_request.rb +171 -0
  34. data/lib/rack/mock_response.rb +124 -0
  35. data/lib/rack/multipart/generator.rb +15 -8
  36. data/lib/rack/multipart/parser.rb +238 -107
  37. data/lib/rack/multipart/uploaded_file.rb +17 -7
  38. data/lib/rack/multipart.rb +54 -42
  39. data/lib/rack/null_logger.rb +9 -0
  40. data/lib/rack/query_parser.rb +87 -105
  41. data/lib/rack/recursive.rb +3 -1
  42. data/lib/rack/reloader.rb +0 -4
  43. data/lib/rack/request.rb +366 -135
  44. data/lib/rack/response.rb +186 -68
  45. data/lib/rack/rewindable_input.rb +24 -6
  46. data/lib/rack/runtime.rb +8 -7
  47. data/lib/rack/sendfile.rb +29 -27
  48. data/lib/rack/show_exceptions.rb +27 -12
  49. data/lib/rack/show_status.rb +21 -13
  50. data/lib/rack/static.rb +19 -12
  51. data/lib/rack/tempfile_reaper.rb +14 -5
  52. data/lib/rack/urlmap.rb +5 -6
  53. data/lib/rack/utils.rb +274 -260
  54. data/lib/rack/version.rb +21 -0
  55. data/lib/rack.rb +18 -103
  56. metadata +25 -52
  57. data/README.rdoc +0 -262
  58. data/Rakefile +0 -123
  59. data/SPEC +0 -263
  60. data/bin/rackup +0 -5
  61. data/contrib/rack.png +0 -0
  62. data/contrib/rack.svg +0 -150
  63. data/contrib/rack_logo.svg +0 -164
  64. data/contrib/rdoc.css +0 -412
  65. data/example/lobster.ru +0 -6
  66. data/example/protectedlobster.rb +0 -16
  67. data/example/protectedlobster.ru +0 -10
  68. data/lib/rack/auth/digest/md5.rb +0 -131
  69. data/lib/rack/auth/digest/nonce.rb +0 -54
  70. data/lib/rack/auth/digest/params.rb +0 -54
  71. data/lib/rack/auth/digest/request.rb +0 -43
  72. data/lib/rack/chunked.rb +0 -92
  73. data/lib/rack/core_ext/regexp.rb +0 -14
  74. data/lib/rack/file.rb +0 -8
  75. data/lib/rack/handler/cgi.rb +0 -62
  76. data/lib/rack/handler/fastcgi.rb +0 -102
  77. data/lib/rack/handler/lsws.rb +0 -63
  78. data/lib/rack/handler/scgi.rb +0 -73
  79. data/lib/rack/handler/thin.rb +0 -38
  80. data/lib/rack/handler/webrick.rb +0 -122
  81. data/lib/rack/handler.rb +0 -104
  82. data/lib/rack/lobster.rb +0 -72
  83. data/lib/rack/server.rb +0 -467
  84. data/lib/rack/session/abstract/id.rb +0 -528
  85. data/lib/rack/session/cookie.rb +0 -205
  86. data/lib/rack/session/memcache.rb +0 -10
  87. data/lib/rack/session/pool.rb +0 -85
  88. data/rack.gemspec +0 -44
data/lib/rack/request.rb CHANGED
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/utils'
4
- require 'rack/media_type'
5
-
6
- require_relative 'core_ext/regexp'
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+ require_relative 'media_type'
7
6
 
8
7
  module Rack
9
8
  # Rack::Request provides a convenient interface to a Rack
@@ -15,22 +14,54 @@ module Rack
15
14
  # req.params["data"]
16
15
 
17
16
  class Request
18
- using ::Rack::RegexpExtensions
19
-
20
17
  class << self
21
18
  attr_accessor :ip_filter
22
- end
23
19
 
24
- 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) }
25
- ALLOWED_SCHEMES = %w(https http).freeze
26
- SCHEME_WHITELIST = ALLOWED_SCHEMES
27
- if Object.respond_to?(:deprecate_constant)
28
- deprecate_constant :SCHEME_WHITELIST
20
+ # The priority when checking forwarded headers. The default
21
+ # is <tt>[:forwarded, :x_forwarded]</tt>, which means, check the
22
+ # +Forwarded+ header first, followed by the appropriate
23
+ # <tt>X-Forwarded-*</tt> header. You can revert the priority by
24
+ # reversing the priority, or remove checking of either
25
+ # or both headers by removing elements from the array.
26
+ #
27
+ # This should be set as appropriate in your environment
28
+ # based on what reverse proxies are in use. If you are not
29
+ # using reverse proxies, you should probably use an empty
30
+ # array.
31
+ attr_accessor :forwarded_priority
32
+
33
+ # The priority when checking either the <tt>X-Forwarded-Proto</tt>
34
+ # or <tt>X-Forwarded-Scheme</tt> header for the forwarded protocol.
35
+ # The default is <tt>[:proto, :scheme]</tt>, to try the
36
+ # <tt>X-Forwarded-Proto</tt> header before the
37
+ # <tt>X-Forwarded-Scheme</tt> header. Rack 2 had behavior
38
+ # similar to <tt>[:scheme, :proto]</tt>. You can remove either or
39
+ # both of the entries in array to ignore that respective header.
40
+ attr_accessor :x_forwarded_proto_priority
29
41
  end
30
42
 
43
+ @forwarded_priority = [:forwarded, :x_forwarded]
44
+ @x_forwarded_proto_priority = [:proto, :scheme]
45
+
46
+ valid_ipv4_octet = /\.(25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])/
47
+
48
+ trusted_proxies = Regexp.union(
49
+ /\A127#{valid_ipv4_octet}{3}\z/, # localhost IPv4 range 127.x.x.x, per RFC-3330
50
+ /\A::1\z/, # localhost IPv6 ::1
51
+ /\Af[cd][0-9a-f]{2}(?::[0-9a-f]{0,4}){0,7}\z/i, # private IPv6 range fc00 .. fdff
52
+ /\A10#{valid_ipv4_octet}{3}\z/, # private IPv4 range 10.x.x.x
53
+ /\A172\.(1[6-9]|2[0-9]|3[01])#{valid_ipv4_octet}{2}\z/, # private IPv4 range 172.16.0.0 .. 172.31.255.255
54
+ /\A192\.168#{valid_ipv4_octet}{2}\z/, # private IPv4 range 192.168.x.x
55
+ /\Alocalhost\z|\Aunix(\z|:)/i, # localhost hostname, and unix domain sockets
56
+ )
57
+
58
+ self.ip_filter = lambda { |ip| trusted_proxies.match?(ip) }
59
+
60
+ ALLOWED_SCHEMES = %w(https http wss ws).freeze
61
+
31
62
  def initialize(env)
63
+ @env = env
32
64
  @params = nil
33
- super(env)
34
65
  end
35
66
 
36
67
  def params
@@ -54,6 +85,8 @@ module Rack
54
85
 
55
86
  def initialize(env)
56
87
  @env = env
88
+ # This module is included at least in `ActionDispatch::Request`
89
+ # The call to `super()` allows additional mixed-in initializers are called
57
90
  super()
58
91
  end
59
92
 
@@ -93,7 +126,7 @@ module Rack
93
126
  # assert_equal 'image/png,*/*', request.get_header('Accept')
94
127
  #
95
128
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
96
- def add_header key, v
129
+ def add_header(key, v)
97
130
  if v.nil?
98
131
  get_header key
99
132
  elsif has_header? key
@@ -134,11 +167,25 @@ module Rack
134
167
  # to include the port in a generated URI.
135
168
  DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
136
169
 
170
+ # The address of the client which connected to the proxy.
171
+ HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'
172
+
173
+ # The contents of the host/:authority header sent to the proxy.
174
+ HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
175
+
176
+ HTTP_FORWARDED = 'HTTP_FORWARDED'
177
+
178
+ # The value of the scheme sent to the proxy.
137
179
  HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
138
- HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
139
- HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
140
- HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
141
- HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
180
+
181
+ # The protocol used to connect to the proxy.
182
+ HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
183
+
184
+ # The port used to connect to the proxy.
185
+ HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
186
+
187
+ # Another way for specifying https scheme was used.
188
+ HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
142
189
 
143
190
  def body; get_header(RACK_INPUT) end
144
191
  def script_name; get_header(SCRIPT_NAME).to_s end
@@ -152,7 +199,6 @@ module Rack
152
199
  def content_length; get_header('CONTENT_LENGTH') end
153
200
  def logger; get_header(RACK_LOGGER) end
154
201
  def user_agent; get_header('HTTP_USER_AGENT') end
155
- def multithread?; get_header(RACK_MULTITHREAD) end
156
202
 
157
203
  # the referer of the client
158
204
  def referer; get_header('HTTP_REFERER') end
@@ -212,19 +258,50 @@ module Rack
212
258
  end
213
259
  end
214
260
 
261
+ # The authority of the incoming request as defined by RFC3976.
262
+ # https://tools.ietf.org/html/rfc3986#section-3.2
263
+ #
264
+ # In HTTP/1, this is the `host` header.
265
+ # In HTTP/2, this is the `:authority` pseudo-header.
215
266
  def authority
216
- get_header(SERVER_NAME) + ':' + get_header(SERVER_PORT)
267
+ forwarded_authority || host_authority || server_authority
268
+ end
269
+
270
+ # The authority as defined by the `SERVER_NAME` and `SERVER_PORT`
271
+ # variables.
272
+ def server_authority
273
+ host = self.server_name
274
+ port = self.server_port
275
+
276
+ if host
277
+ if port
278
+ "#{host}:#{port}"
279
+ else
280
+ host
281
+ end
282
+ end
283
+ end
284
+
285
+ def server_name
286
+ get_header(SERVER_NAME)
287
+ end
288
+
289
+ def server_port
290
+ get_header(SERVER_PORT)
217
291
  end
218
292
 
219
293
  def cookies
220
- hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |k|
221
- set_header(k, {})
294
+ hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key|
295
+ set_header(key, {})
296
+ end
297
+
298
+ string = get_header(HTTP_COOKIE)
299
+
300
+ unless string == get_header(RACK_REQUEST_COOKIE_STRING)
301
+ hash.replace Utils.parse_cookies_header(string)
302
+ set_header(RACK_REQUEST_COOKIE_STRING, string)
222
303
  end
223
- string = get_header HTTP_COOKIE
224
304
 
225
- return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
226
- hash.replace Utils.parse_cookies_header string
227
- set_header(RACK_REQUEST_COOKIE_STRING, string)
228
305
  hash
229
306
  end
230
307
 
@@ -237,52 +314,122 @@ module Rack
237
314
  get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
238
315
  end
239
316
 
240
- def host_with_port
241
- if forwarded = get_header(HTTP_X_FORWARDED_HOST)
242
- forwarded.split(/,\s?/).last
317
+ # The `HTTP_HOST` header.
318
+ def host_authority
319
+ get_header(HTTP_HOST)
320
+ end
321
+
322
+ def host_with_port(authority = self.authority)
323
+ host, _, port = split_authority(authority)
324
+
325
+ if port == DEFAULT_PORTS[self.scheme]
326
+ host
243
327
  else
244
- get_header(HTTP_HOST) || "#{get_header(SERVER_NAME) || get_header(SERVER_ADDR)}:#{get_header(SERVER_PORT)}"
328
+ authority
245
329
  end
246
330
  end
247
331
 
332
+ # Returns a formatted host, suitable for being used in a URI.
248
333
  def host
249
- # Remove port number.
250
- h = host_with_port
251
- if colon_index = h.index(":")
252
- h[0, colon_index]
253
- else
254
- h
255
- end
334
+ split_authority(self.authority)[0]
335
+ end
336
+
337
+ # Returns an address suitable for being to resolve to an address.
338
+ # In the case of a domain name or IPv4 address, the result is the same
339
+ # as +host+. In the case of IPv6 or future address formats, the square
340
+ # brackets are removed.
341
+ def hostname
342
+ split_authority(self.authority)[1]
256
343
  end
257
344
 
258
345
  def port
259
- if port = extract_port(host_with_port)
260
- port.to_i
261
- elsif port = get_header(HTTP_X_FORWARDED_PORT)
262
- port.to_i
263
- elsif has_header?(HTTP_X_FORWARDED_HOST)
264
- DEFAULT_PORTS[scheme]
265
- elsif has_header?(HTTP_X_FORWARDED_PROTO)
266
- DEFAULT_PORTS[extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO))]
267
- else
268
- get_header(SERVER_PORT).to_i
346
+ if authority = self.authority
347
+ _, _, port = split_authority(authority)
269
348
  end
349
+
350
+ port || forwarded_port&.last || DEFAULT_PORTS[scheme] || server_port
351
+ end
352
+
353
+ def forwarded_for
354
+ forwarded_priority.each do |type|
355
+ case type
356
+ when :forwarded
357
+ if forwarded_for = get_http_forwarded(:for)
358
+ return(forwarded_for.map! do |authority|
359
+ split_authority(authority)[1]
360
+ end)
361
+ end
362
+ when :x_forwarded
363
+ if value = get_header(HTTP_X_FORWARDED_FOR)
364
+ return(split_header(value).map do |authority|
365
+ split_authority(wrap_ipv6(authority))[1]
366
+ end)
367
+ end
368
+ end
369
+ end
370
+
371
+ nil
372
+ end
373
+
374
+ def forwarded_port
375
+ forwarded_priority.each do |type|
376
+ case type
377
+ when :forwarded
378
+ if forwarded = get_http_forwarded(:for)
379
+ return(forwarded.map do |authority|
380
+ split_authority(authority)[2]
381
+ end.compact)
382
+ end
383
+ when :x_forwarded
384
+ if value = get_header(HTTP_X_FORWARDED_PORT)
385
+ return split_header(value).map(&:to_i)
386
+ end
387
+ end
388
+ end
389
+
390
+ nil
391
+ end
392
+
393
+ def forwarded_authority
394
+ forwarded_priority.each do |type|
395
+ case type
396
+ when :forwarded
397
+ if forwarded = get_http_forwarded(:host)
398
+ return forwarded.last
399
+ end
400
+ when :x_forwarded
401
+ if value = get_header(HTTP_X_FORWARDED_HOST)
402
+ return wrap_ipv6(split_header(value).last)
403
+ end
404
+ end
405
+ end
406
+
407
+ nil
270
408
  end
271
409
 
272
410
  def ssl?
273
- scheme == 'https'
411
+ scheme == 'https' || scheme == 'wss'
274
412
  end
275
413
 
276
414
  def ip
277
- remote_addrs = split_ip_addresses(get_header('REMOTE_ADDR'))
278
- remote_addrs = reject_trusted_ip_addresses(remote_addrs)
415
+ remote_addresses = split_header(get_header('REMOTE_ADDR'))
416
+ external_addresses = reject_trusted_ip_addresses(remote_addresses)
279
417
 
280
- return remote_addrs.first if remote_addrs.any?
418
+ unless external_addresses.empty?
419
+ return external_addresses.last
420
+ end
281
421
 
282
- forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
283
- .map { |ip| strip_port(ip) }
422
+ if (forwarded_for = self.forwarded_for) && !forwarded_for.empty?
423
+ # The forwarded for addresses are ordered: client, proxy1, proxy2.
424
+ # So we reject all the trusted addresses (proxy*) and return the
425
+ # last client. Or if we trust everyone, we just return the first
426
+ # address.
427
+ return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first
428
+ end
284
429
 
285
- return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
430
+ # If all the addresses are trusted, and we aren't forwarded, just return
431
+ # the first remote address, which represents the source of the request.
432
+ remote_addresses.first
286
433
  end
287
434
 
288
435
  # The media type (type/subtype) portion of the CONTENT_TYPE header
@@ -313,16 +460,17 @@ module Rack
313
460
  end
314
461
 
315
462
  # Determine whether the request body contains form-data by checking
316
- # the request Content-Type for one of the media-types:
463
+ # the request content-type for one of the media-types:
317
464
  # "application/x-www-form-urlencoded" or "multipart/form-data". The
318
465
  # list of form-data media types can be modified through the
319
466
  # +FORM_DATA_MEDIA_TYPES+ array.
320
467
  #
321
468
  # A request body is also assumed to contain form-data when no
322
- # Content-Type header is provided and the request_method is POST.
469
+ # content-type header is provided and the request_method is POST.
323
470
  def form_data?
324
471
  type = media_type
325
472
  meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
473
+
326
474
  (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
327
475
  end
328
476
 
@@ -334,10 +482,15 @@ module Rack
334
482
 
335
483
  # Returns the data received in the query string.
336
484
  def GET
337
- if get_header(RACK_REQUEST_QUERY_STRING) == query_string
485
+ rr_query_string = get_header(RACK_REQUEST_QUERY_STRING)
486
+ query_string = self.query_string
487
+ if rr_query_string == query_string
338
488
  get_header(RACK_REQUEST_QUERY_HASH)
339
489
  else
340
- query_hash = parse_query(query_string, '&;')
490
+ if rr_query_string
491
+ warn "query string used for GET parsing different from current query string. Starting in Rack 3.2, Rack will used the cached GET value instead of parsing the current query string.", uplevel: 1
492
+ end
493
+ query_hash = parse_query(query_string, '&')
341
494
  set_header(RACK_REQUEST_QUERY_STRING, query_string)
342
495
  set_header(RACK_REQUEST_QUERY_HASH, query_hash)
343
496
  end
@@ -348,27 +501,52 @@ module Rack
348
501
  # This method support both application/x-www-form-urlencoded and
349
502
  # multipart/form-data.
350
503
  def POST
351
- if get_header(RACK_INPUT).nil?
352
- raise "Missing rack.input"
353
- elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
354
- get_header(RACK_REQUEST_FORM_HASH)
355
- elsif form_data? || parseable_data?
356
- unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
357
- form_vars = get_header(RACK_INPUT).read
358
-
359
- # Fix for Safari Ajax postings that always append \0
360
- # form_vars.sub!(/\0\z/, '') # performance replacement:
361
- form_vars.slice!(-1) if form_vars.end_with?("\0")
362
-
363
- set_header RACK_REQUEST_FORM_VARS, form_vars
364
- set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
365
-
366
- get_header(RACK_INPUT).rewind
504
+ if error = get_header(RACK_REQUEST_FORM_ERROR)
505
+ raise error.class, error.message, cause: error.cause
506
+ end
507
+
508
+ begin
509
+ rack_input = get_header(RACK_INPUT)
510
+
511
+ # If the form hash was already memoized:
512
+ if form_hash = get_header(RACK_REQUEST_FORM_HASH)
513
+ form_input = get_header(RACK_REQUEST_FORM_INPUT)
514
+ # And it was memoized from the same input:
515
+ if form_input.equal?(rack_input)
516
+ return form_hash
517
+ elsif form_input
518
+ warn "input stream used for POST parsing different from current input stream. Starting in Rack 3.2, Rack will used the cached POST value instead of parsing the current input stream.", uplevel: 1
519
+ end
367
520
  end
368
- set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
369
- get_header RACK_REQUEST_FORM_HASH
370
- else
371
- {}
521
+
522
+ # Otherwise, figure out how to parse the input:
523
+ if rack_input.nil?
524
+ set_header RACK_REQUEST_FORM_INPUT, nil
525
+ set_header(RACK_REQUEST_FORM_HASH, {})
526
+ elsif form_data? || parseable_data?
527
+ if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
528
+ set_header RACK_REQUEST_FORM_PAIRS, pairs
529
+ set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
530
+ else
531
+ form_vars = get_header(RACK_INPUT).read
532
+
533
+ # Fix for Safari Ajax postings that always append \0
534
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
535
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
536
+
537
+ set_header RACK_REQUEST_FORM_VARS, form_vars
538
+ set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
539
+ end
540
+
541
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
542
+ get_header RACK_REQUEST_FORM_HASH
543
+ else
544
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
545
+ set_header(RACK_REQUEST_FORM_HASH, {})
546
+ end
547
+ rescue => error
548
+ set_header(RACK_REQUEST_FORM_ERROR, error)
549
+ raise
372
550
  end
373
551
  end
374
552
 
@@ -377,8 +555,6 @@ module Rack
377
555
  # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
378
556
  def params
379
557
  self.GET.merge(self.POST)
380
- rescue EOFError
381
- self.GET.dup
382
558
  end
383
559
 
384
560
  # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
@@ -412,9 +588,7 @@ module Rack
412
588
  end
413
589
 
414
590
  def base_url
415
- url = "#{scheme}://#{host}"
416
- url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
417
- url
591
+ "#{scheme}://#{host_with_port}"
418
592
  end
419
593
 
420
594
  # Tries to return a remake of the original request URL as a string.
@@ -442,28 +616,10 @@ module Rack
442
616
  Rack::Request.ip_filter.call(ip)
443
617
  end
444
618
 
445
- # shortcut for <tt>request.params[key]</tt>
446
- def [](key)
447
- if $VERBOSE
448
- warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
449
- end
450
-
451
- params[key.to_s]
452
- end
453
-
454
- # shortcut for <tt>request.params[key] = value</tt>
455
- #
456
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
457
- def []=(key, value)
458
- if $VERBOSE
459
- warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
460
- end
461
-
462
- params[key.to_s] = value
463
- end
464
-
465
619
  # like Hash#values_at
466
620
  def values_at(*keys)
621
+ warn("Request#values_at is deprecated and will be removed in a future version of Rack. Please use request.params.values_at instead", uplevel: 1)
622
+
467
623
  keys.map { |key| params[key] }
468
624
  end
469
625
 
@@ -471,6 +627,20 @@ module Rack
471
627
 
472
628
  def default_session; {}; end
473
629
 
630
+ # Assist with compatibility when processing `X-Forwarded-For`.
631
+ def wrap_ipv6(host)
632
+ # Even thought IPv6 addresses should be wrapped in square brackets,
633
+ # sometimes this is not done in various legacy/underspecified headers.
634
+ # So we try to fix this situation for compatibility reasons.
635
+
636
+ # Try to detect IPv6 addresses which aren't escaped yet:
637
+ if !host.start_with?('[') && host.count(':') > 1
638
+ "[#{host}]"
639
+ else
640
+ host
641
+ end
642
+ end
643
+
474
644
  def parse_http_accept_header(header)
475
645
  header.to_s.split(/\s*,\s*/).map do |part|
476
646
  attribute, parameters = part.split(/\s*;\s*/, 2)
@@ -482,6 +652,11 @@ module Rack
482
652
  end
483
653
  end
484
654
 
655
+ # Get an array of values set in the RFC 7239 `Forwarded` request header.
656
+ def get_http_forwarded(token)
657
+ Utils.forwarded_values(get_header(HTTP_FORWARDED))&.[](token)
658
+ end
659
+
485
660
  def query_parser
486
661
  Utils.default_query_parser
487
662
  end
@@ -494,56 +669,108 @@ module Rack
494
669
  Rack::Multipart.extract_multipart(self, query_parser)
495
670
  end
496
671
 
497
- def split_ip_addresses(ip_addresses)
498
- ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
499
- end
672
+ def expand_param_pairs(pairs, query_parser = query_parser())
673
+ params = query_parser.make_params
500
674
 
501
- def strip_port(ip_address)
502
- # IPv6 format with optional port: "[2001:db8:cafe::17]:47011"
503
- # returns: "2001:db8:cafe::17"
504
- sep_start = ip_address.index('[')
505
- sep_end = ip_address.index(']')
506
- if (sep_start && sep_end)
507
- return ip_address[sep_start + 1, sep_end - 1]
675
+ pairs.each do |k, v|
676
+ query_parser.normalize_params(params, k, v)
508
677
  end
509
678
 
510
- # IPv4 format with optional port: "192.0.2.43:47011"
511
- # returns: "192.0.2.43"
512
- sep = ip_address.index(':')
513
- if (sep && ip_address.count(':') == 1)
514
- return ip_address[0, sep]
515
- end
516
-
517
- ip_address
679
+ params.to_params_hash
680
+ end
681
+
682
+ def split_header(value)
683
+ value ? value.strip.split(/[,\s]+/) : []
684
+ end
685
+
686
+ # ipv6 extracted from resolv stdlib, simplified
687
+ # to remove numbered match group creation.
688
+ ipv6 = Regexp.union(
689
+ /(?:[0-9A-Fa-f]{1,4}:){7}
690
+ [0-9A-Fa-f]{1,4}/x,
691
+ /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
692
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?/x,
693
+ /(?:[0-9A-Fa-f]{1,4}:){6,6}
694
+ \d+\.\d+\.\d+\.\d+/x,
695
+ /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
696
+ (?:[0-9A-Fa-f]{1,4}:)*
697
+ \d+\.\d+\.\d+\.\d+/x,
698
+ /[Ff][Ee]80
699
+ (?::[0-9A-Fa-f]{1,4}){7}
700
+ %[-0-9A-Za-z._~]+/x,
701
+ /[Ff][Ee]80:
702
+ (?:
703
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
704
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
705
+ |
706
+ :(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
707
+ )?
708
+ :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+/x)
709
+
710
+ AUTHORITY = /
711
+ \A
712
+ (?<host>
713
+ # Match IPv6 as a string of hex digits and colons in square brackets
714
+ \[(?<address>#{ipv6})\]
715
+ |
716
+ # Match any other printable string (except square brackets) as a hostname
717
+ (?<address>[[[:graph:]&&[^\[\]]]]*?)
718
+ )
719
+ (:(?<port>\d+))?
720
+ \z
721
+ /x
722
+
723
+ private_constant :AUTHORITY
724
+
725
+ def split_authority(authority)
726
+ return [] if authority.nil?
727
+ return [] unless match = AUTHORITY.match(authority)
728
+ return match[:host], match[:address], match[:port]&.to_i
518
729
  end
519
730
 
520
731
  def reject_trusted_ip_addresses(ip_addresses)
521
732
  ip_addresses.reject { |ip| trusted_proxy?(ip) }
522
733
  end
523
734
 
735
+ FORWARDED_SCHEME_HEADERS = {
736
+ proto: HTTP_X_FORWARDED_PROTO,
737
+ scheme: HTTP_X_FORWARDED_SCHEME
738
+ }.freeze
739
+ private_constant :FORWARDED_SCHEME_HEADERS
524
740
  def forwarded_scheme
525
- allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
526
- allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
741
+ forwarded_priority.each do |type|
742
+ case type
743
+ when :forwarded
744
+ if (forwarded_proto = get_http_forwarded(:proto)) &&
745
+ (scheme = allowed_scheme(forwarded_proto.last))
746
+ return scheme
747
+ end
748
+ when :x_forwarded
749
+ x_forwarded_proto_priority.each do |x_type|
750
+ if header = FORWARDED_SCHEME_HEADERS[x_type]
751
+ split_header(get_header(header)).reverse_each do |scheme|
752
+ if allowed_scheme(scheme)
753
+ return scheme
754
+ end
755
+ end
756
+ end
757
+ end
758
+ end
759
+ end
760
+
761
+ nil
527
762
  end
528
763
 
529
764
  def allowed_scheme(header)
530
765
  header if ALLOWED_SCHEMES.include?(header)
531
766
  end
532
767
 
533
- def extract_proto_header(header)
534
- if header
535
- if (comma_index = header.index(','))
536
- header[0, comma_index]
537
- else
538
- header
539
- end
540
- end
768
+ def forwarded_priority
769
+ Request.forwarded_priority
541
770
  end
542
771
 
543
- def extract_port(uri)
544
- if (colon_index = uri.index(':'))
545
- uri[colon_index + 1, uri.length]
546
- end
772
+ def x_forwarded_proto_priority
773
+ Request.x_forwarded_proto_priority
547
774
  end
548
775
  end
549
776
 
@@ -551,3 +778,7 @@ module Rack
551
778
  include Helpers
552
779
  end
553
780
  end
781
+
782
+ # :nocov:
783
+ require_relative 'multipart' unless defined?(Rack::Multipart)
784
+ # :nocov: