rack 2.2.17 → 3.2.8

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +712 -74
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +384 -0
  6. data/SPEC.rdoc +243 -277
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +5 -1
  9. data/lib/rack/auth/basic.rb +1 -3
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +21 -3
  12. data/lib/rack/builder.rb +108 -69
  13. data/lib/rack/cascade.rb +2 -3
  14. data/lib/rack/common_logger.rb +22 -17
  15. data/lib/rack/conditional_get.rb +20 -16
  16. data/lib/rack/constants.rb +68 -0
  17. data/lib/rack/content_length.rb +12 -16
  18. data/lib/rack/content_type.rb +8 -5
  19. data/lib/rack/deflater.rb +40 -26
  20. data/lib/rack/directory.rb +15 -6
  21. data/lib/rack/etag.rb +17 -23
  22. data/lib/rack/events.rb +25 -6
  23. data/lib/rack/files.rb +16 -18
  24. data/lib/rack/head.rb +8 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +817 -648
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/media_type.rb +6 -7
  29. data/lib/rack/method_override.rb +5 -1
  30. data/lib/rack/mime.rb +14 -5
  31. data/lib/rack/mock.rb +1 -300
  32. data/lib/rack/mock_request.rb +161 -0
  33. data/lib/rack/mock_response.rb +156 -0
  34. data/lib/rack/multipart/generator.rb +7 -5
  35. data/lib/rack/multipart/parser.rb +337 -95
  36. data/lib/rack/multipart/uploaded_file.rb +46 -4
  37. data/lib/rack/multipart.rb +53 -40
  38. data/lib/rack/null_logger.rb +9 -0
  39. data/lib/rack/query_parser.rb +118 -121
  40. data/lib/rack/recursive.rb +2 -0
  41. data/lib/rack/reloader.rb +0 -2
  42. data/lib/rack/request.rb +277 -143
  43. data/lib/rack/response.rb +151 -66
  44. data/lib/rack/rewindable_input.rb +27 -5
  45. data/lib/rack/runtime.rb +7 -6
  46. data/lib/rack/sendfile.rb +69 -34
  47. data/lib/rack/show_exceptions.rb +25 -6
  48. data/lib/rack/show_status.rb +17 -9
  49. data/lib/rack/static.rb +15 -11
  50. data/lib/rack/tempfile_reaper.rb +15 -4
  51. data/lib/rack/urlmap.rb +3 -1
  52. data/lib/rack/utils.rb +326 -244
  53. data/lib/rack/version.rb +3 -15
  54. data/lib/rack.rb +13 -90
  55. metadata +15 -41
  56. data/README.rdoc +0 -347
  57. data/Rakefile +0 -130
  58. data/bin/rackup +0 -5
  59. data/contrib/rack.png +0 -0
  60. data/contrib/rack.svg +0 -150
  61. data/contrib/rack_logo.svg +0 -164
  62. data/contrib/rdoc.css +0 -412
  63. data/example/lobster.ru +0 -6
  64. data/example/protectedlobster.rb +0 -16
  65. data/example/protectedlobster.ru +0 -10
  66. data/lib/rack/auth/digest/md5.rb +0 -131
  67. data/lib/rack/auth/digest/nonce.rb +0 -53
  68. data/lib/rack/auth/digest/params.rb +0 -54
  69. data/lib/rack/auth/digest/request.rb +0 -43
  70. data/lib/rack/chunked.rb +0 -117
  71. data/lib/rack/core_ext/regexp.rb +0 -14
  72. data/lib/rack/file.rb +0 -7
  73. data/lib/rack/handler/cgi.rb +0 -59
  74. data/lib/rack/handler/fastcgi.rb +0 -100
  75. data/lib/rack/handler/lsws.rb +0 -61
  76. data/lib/rack/handler/scgi.rb +0 -71
  77. data/lib/rack/handler/thin.rb +0 -36
  78. data/lib/rack/handler/webrick.rb +0 -129
  79. data/lib/rack/handler.rb +0 -104
  80. data/lib/rack/lobster.rb +0 -70
  81. data/lib/rack/logger.rb +0 -20
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -203
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -90
  87. data/rack.gemspec +0 -46
data/lib/rack/request.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+ require_relative 'media_type'
6
+
3
7
  module Rack
4
8
  # Rack::Request provides a convenient interface to a Rack
5
9
  # environment. It is stateless, the environment +env+ passed to the
@@ -10,22 +14,59 @@ module Rack
10
14
  # req.params["data"]
11
15
 
12
16
  class Request
13
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
14
-
15
17
  class << self
16
18
  attr_accessor :ip_filter
17
- end
18
19
 
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
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
24
41
  end
25
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
+
26
62
  def initialize(env)
63
+ @env = env
64
+ @ip = nil
27
65
  @params = nil
28
- super(env)
66
+ end
67
+
68
+ def ip
69
+ @ip ||= super
29
70
  end
30
71
 
31
72
  def params
@@ -49,6 +90,8 @@ module Rack
49
90
 
50
91
  def initialize(env)
51
92
  @env = env
93
+ # This module is included at least in `ActionDispatch::Request`
94
+ # The call to `super()` allows additional mixed-in initializers are called
52
95
  super()
53
96
  end
54
97
 
@@ -135,6 +178,8 @@ module Rack
135
178
  # The contents of the host/:authority header sent to the proxy.
136
179
  HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
137
180
 
181
+ HTTP_FORWARDED = 'HTTP_FORWARDED'
182
+
138
183
  # The value of the scheme sent to the proxy.
139
184
  HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
140
185
 
@@ -144,7 +189,7 @@ module Rack
144
189
  # The port used to connect to the proxy.
145
190
  HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
146
191
 
147
- # Another way for specifing https scheme was used.
192
+ # Another way for specifying https scheme was used.
148
193
  HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
149
194
 
150
195
  def body; get_header(RACK_INPUT) end
@@ -159,7 +204,6 @@ module Rack
159
204
  def content_length; get_header('CONTENT_LENGTH') end
160
205
  def logger; get_header(RACK_LOGGER) end
161
206
  def user_agent; get_header('HTTP_USER_AGENT') end
162
- def multithread?; get_header(RACK_MULTITHREAD) end
163
207
 
164
208
  # the referer of the client
165
209
  def referer; get_header('HTTP_REFERER') end
@@ -248,9 +292,7 @@ module Rack
248
292
  end
249
293
 
250
294
  def server_port
251
- if port = get_header(SERVER_PORT)
252
- Integer(port)
253
- end
295
+ get_header(SERVER_PORT)
254
296
  end
255
297
 
256
298
  def cookies
@@ -283,8 +325,10 @@ module Rack
283
325
  end
284
326
 
285
327
  def host_with_port(authority = self.authority)
286
- host, _, port = split_authority(authority)
328
+ parts = split_authority(authority)
329
+ return nil if parts.empty?
287
330
 
331
+ host, _, port = parts
288
332
  if port == DEFAULT_PORTS[self.scheme]
289
333
  host
290
334
  else
@@ -307,44 +351,67 @@ module Rack
307
351
 
308
352
  def port
309
353
  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
354
+ _, _, port = split_authority(authority)
325
355
  end
326
356
 
327
- self.server_port
357
+ port || forwarded_port&.last || DEFAULT_PORTS[scheme] || server_port
328
358
  end
329
359
 
330
360
  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]
361
+ forwarded_priority.each do |type|
362
+ case type
363
+ when :forwarded
364
+ if forwarded_for = get_http_forwarded(:for)
365
+ return(forwarded_for.map! do |authority|
366
+ split_authority(authority)[1]
367
+ end)
368
+ end
369
+ when :x_forwarded
370
+ if value = get_header(HTTP_X_FORWARDED_FOR)
371
+ return(split_header(value).map do |authority|
372
+ split_authority(wrap_ipv6(authority))[1]
373
+ end)
374
+ end
334
375
  end
335
376
  end
377
+
378
+ nil
336
379
  end
337
380
 
338
381
  def forwarded_port
339
- if value = get_header(HTTP_X_FORWARDED_PORT)
340
- split_header(value).map(&:to_i)
382
+ forwarded_priority.each do |type|
383
+ case type
384
+ when :forwarded
385
+ if forwarded = get_http_forwarded(:for)
386
+ return(forwarded.map do |authority|
387
+ split_authority(authority)[2]
388
+ end.compact)
389
+ end
390
+ when :x_forwarded
391
+ if value = get_header(HTTP_X_FORWARDED_PORT)
392
+ return split_header(value).map(&:to_i)
393
+ end
394
+ end
341
395
  end
396
+
397
+ nil
342
398
  end
343
399
 
344
400
  def forwarded_authority
345
- if value = get_header(HTTP_X_FORWARDED_HOST)
346
- wrap_ipv6(split_header(value).first)
401
+ forwarded_priority.each do |type|
402
+ case type
403
+ when :forwarded
404
+ if forwarded = get_http_forwarded(:host)
405
+ return forwarded.last
406
+ end
407
+ when :x_forwarded
408
+ if (value = get_header(HTTP_X_FORWARDED_HOST)) && (x_forwarded_host = split_header(value).last)
409
+ return wrap_ipv6(x_forwarded_host)
410
+ end
411
+ end
347
412
  end
413
+
414
+ nil
348
415
  end
349
416
 
350
417
  def ssl?
@@ -353,20 +420,20 @@ module Rack
353
420
 
354
421
  def ip
355
422
  remote_addresses = split_header(get_header('REMOTE_ADDR'))
356
- external_addresses = reject_trusted_ip_addresses(remote_addresses)
357
423
 
358
- unless external_addresses.empty?
359
- return external_addresses.first
424
+ remote_addresses.reverse_each do |ip|
425
+ return ip unless trusted_proxy?(ip)
360
426
  end
361
427
 
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
428
+ if (forwarded_for = self.forwarded_for) && !forwarded_for.empty?
429
+ # The forwarded for addresses are ordered: client, proxy1, proxy2.
430
+ # So we reject all the trusted addresses (proxy*) and return the
431
+ # last client. Or if we trust everyone, we just return the first
432
+ # address.
433
+ forwarded_for.reverse_each do |ip|
434
+ return ip unless trusted_proxy?(ip)
369
435
  end
436
+ return forwarded_for.first
370
437
  end
371
438
 
372
439
  # If all the addresses are trusted, and we aren't forwarded, just return
@@ -402,13 +469,13 @@ module Rack
402
469
  end
403
470
 
404
471
  # Determine whether the request body contains form-data by checking
405
- # the request Content-Type for one of the media-types:
472
+ # the request content-type for one of the media-types:
406
473
  # "application/x-www-form-urlencoded" or "multipart/form-data". The
407
474
  # list of form-data media types can be modified through the
408
475
  # +FORM_DATA_MEDIA_TYPES+ array.
409
476
  #
410
477
  # A request body is also assumed to contain form-data when no
411
- # Content-Type header is provided and the request_method is POST.
478
+ # content-type header is provided and the request_method is POST.
412
479
  def form_data?
413
480
  type = media_type
414
481
  meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
@@ -424,12 +491,49 @@ module Rack
424
491
 
425
492
  # Returns the data received in the query string.
426
493
  def GET
427
- if get_header(RACK_REQUEST_QUERY_STRING) == query_string
428
- get_header(RACK_REQUEST_QUERY_HASH)
429
- else
430
- query_hash = parse_query(query_string, '&;')
431
- set_header(RACK_REQUEST_QUERY_STRING, query_string)
432
- set_header(RACK_REQUEST_QUERY_HASH, query_hash)
494
+ get_header(RACK_REQUEST_QUERY_HASH) || set_header(RACK_REQUEST_QUERY_HASH, parse_query(query_string, '&'))
495
+ end
496
+
497
+ # Returns the form data pairs received in the request body.
498
+ #
499
+ # This method support both application/x-www-form-urlencoded and
500
+ # multipart/form-data.
501
+ def form_pairs
502
+ if pairs = get_header(RACK_REQUEST_FORM_PAIRS)
503
+ return pairs
504
+ elsif 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
+ # Otherwise, figure out how to parse the input:
512
+ if rack_input.nil?
513
+ set_header(RACK_REQUEST_FORM_PAIRS, [])
514
+ elsif form_data? || parseable_data?
515
+ if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
516
+ set_header RACK_REQUEST_FORM_PAIRS, pairs
517
+ else
518
+ # Add 2 bytes. One to check whether it is over the limit, and a second
519
+ # in case the slice! call below removes the last byte
520
+ # If read returns nil, use the empty string
521
+ form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
522
+
523
+ # Fix for Safari Ajax postings that always append \0
524
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
525
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
526
+
527
+ set_header RACK_REQUEST_FORM_VARS, form_vars
528
+ pairs = query_parser.parse_query_pairs(form_vars, '&')
529
+ set_header(RACK_REQUEST_FORM_PAIRS, pairs)
530
+ end
531
+ else
532
+ set_header(RACK_REQUEST_FORM_PAIRS, [])
533
+ end
534
+ rescue => error
535
+ set_header(RACK_REQUEST_FORM_ERROR, error)
536
+ raise
433
537
  end
434
538
  end
435
539
 
@@ -438,28 +542,14 @@ module Rack
438
542
  # This method support both application/x-www-form-urlencoded and
439
543
  # multipart/form-data.
440
544
  def POST
441
- if get_header(RACK_INPUT).nil?
442
- raise "Missing rack.input"
443
- elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
444
- get_header(RACK_REQUEST_FORM_HASH)
445
- elsif form_data? || parseable_data?
446
- unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
447
- form_vars = get_header(RACK_INPUT).read
448
-
449
- # Fix for Safari Ajax postings that always append \0
450
- # form_vars.sub!(/\0\z/, '') # performance replacement:
451
- form_vars.slice!(-1) if form_vars.end_with?("\0")
452
-
453
- set_header RACK_REQUEST_FORM_VARS, form_vars
454
- set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
455
-
456
- get_header(RACK_INPUT).rewind
457
- end
458
- set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
459
- get_header RACK_REQUEST_FORM_HASH
460
- else
461
- {}
545
+ if form_hash = get_header(RACK_REQUEST_FORM_HASH)
546
+ return form_hash
547
+ elsif error = get_header(RACK_REQUEST_FORM_ERROR)
548
+ raise error.class, error.message, cause: error.cause
462
549
  end
550
+
551
+ pairs = form_pairs
552
+ set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
463
553
  end
464
554
 
465
555
  # The union of GET and POST data.
@@ -469,6 +559,10 @@ module Rack
469
559
  self.GET.merge(self.POST)
470
560
  end
471
561
 
562
+ # Allow overriding the query parser that the receiver will use.
563
+ # By default Rack::Utils.default_query_parser is used.
564
+ attr_writer :query_parser
565
+
472
566
  # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
473
567
  #
474
568
  # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
@@ -500,7 +594,8 @@ module Rack
500
594
  end
501
595
 
502
596
  def base_url
503
- "#{scheme}://#{host_with_port}"
597
+ hostport = host_with_port || host_with_port(server_authority)
598
+ "#{scheme}://#{hostport}"
504
599
  end
505
600
 
506
601
  # Tries to return a remake of the original request URL as a string.
@@ -528,31 +623,6 @@ module Rack
528
623
  Rack::Request.ip_filter.call(ip)
529
624
  end
530
625
 
531
- # shortcut for <tt>request.params[key]</tt>
532
- def [](key)
533
- if $VERBOSE
534
- warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
535
- end
536
-
537
- params[key.to_s]
538
- end
539
-
540
- # shortcut for <tt>request.params[key] = value</tt>
541
- #
542
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
543
- def []=(key, value)
544
- if $VERBOSE
545
- warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
546
- end
547
-
548
- params[key.to_s] = value
549
- end
550
-
551
- # like Hash#values_at
552
- def values_at(*keys)
553
- keys.map { |key| params[key] }
554
- end
555
-
556
626
  private
557
627
 
558
628
  def default_session; {}; end
@@ -572,18 +642,35 @@ module Rack
572
642
  end
573
643
 
574
644
  def parse_http_accept_header(header)
575
- header.to_s.split(",").each(&:strip!).map do |part|
576
- attribute, parameters = part.split(";", 2).each(&:strip!)
645
+ # It would be nice to use filter_map here, but it's Ruby 2.7+
646
+ parts = header.to_s.split(',')
647
+
648
+ parts.map! do |part|
649
+ part.strip!
650
+ next if part.empty?
651
+
652
+ attribute, parameters = part.split(';', 2)
653
+ attribute.strip!
654
+ parameters&.strip!
577
655
  quality = 1.0
578
656
  if parameters and /\Aq=([\d.]+)/ =~ parameters
579
657
  quality = $1.to_f
580
658
  end
581
659
  [attribute, quality]
582
660
  end
661
+
662
+ parts.compact!
663
+
664
+ parts
665
+ end
666
+
667
+ # Get an array of values set in the RFC 7239 `Forwarded` request header.
668
+ def get_http_forwarded(token)
669
+ Utils.forwarded_values(get_header(HTTP_FORWARDED))&.[](token)
583
670
  end
584
671
 
585
672
  def query_parser
586
- Utils.default_query_parser
673
+ @query_parser || Utils.default_query_parser
587
674
  end
588
675
 
589
676
  def parse_query(qs, d = '&')
@@ -591,65 +678,108 @@ module Rack
591
678
  end
592
679
 
593
680
  def parse_multipart
681
+ warn "Rack::Request#parse_multipart is deprecated and will be removed in a future version of Rack.", uplevel: 1
594
682
  Rack::Multipart.extract_multipart(self, query_parser)
595
683
  end
596
684
 
597
- def split_header(value)
598
- value ? value.strip.split(/[,\s]+/) : []
685
+ def expand_param_pairs(pairs, query_parser = query_parser())
686
+ params = query_parser.make_params
687
+
688
+ pairs.each do |k, v|
689
+ query_parser.normalize_params(params, k, v)
690
+ end
691
+
692
+ params.to_params_hash
599
693
  end
600
694
 
601
- AUTHORITY = /^
602
- # The host:
695
+ def split_header(value)
696
+ value ? value.strip.split(/[, \t]+/) : []
697
+ end
698
+
699
+ # ipv6 extracted from resolv stdlib, simplified
700
+ # to remove numbered match group creation.
701
+ ipv6 = Regexp.union(
702
+ /(?:[0-9A-Fa-f]{1,4}:){7}
703
+ [0-9A-Fa-f]{1,4}/x,
704
+ /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
705
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?/x,
706
+ /(?:[0-9A-Fa-f]{1,4}:){6,6}
707
+ \d+\.\d+\.\d+\.\d+/x,
708
+ /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
709
+ (?:[0-9A-Fa-f]{1,4}:)*
710
+ \d+\.\d+\.\d+\.\d+/x,
711
+ /[Ff][Ee]80
712
+ (?::[0-9A-Fa-f]{1,4}){7}
713
+ %[-0-9A-Za-z._~]+/x,
714
+ /[Ff][Ee]80:
715
+ (?:
716
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
717
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
718
+ |
719
+ :(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
720
+ )?
721
+ :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+/x)
722
+
723
+ AUTHORITY = /
724
+ \A
603
725
  (?<host>
604
- # An IPv6 address:
605
- (\[(?<ip6>.*)\])
726
+ # Match IPv6 as a string of hex digits and colons in square brackets
727
+ \[(?<address>#{ipv6})\]
606
728
  |
607
- # An IPv4 address:
608
- (?<ip4>[\d\.]+)
609
- |
610
- # A hostname:
611
- (?<name>[a-zA-Z0-9\.\-_]+)
729
+ # Match characters allowed by RFC 3986 Section 3.2.2
730
+ (?<address>[-a-zA-Z0-9._~%!$&'()*+,;=]*?)
612
731
  )
613
- # The optional port:
614
732
  (:(?<port>\d+))?
615
- $/x
733
+ \z
734
+ /x
616
735
 
617
736
  private_constant :AUTHORITY
618
737
 
619
738
  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
630
- end
631
-
632
- def reject_trusted_ip_addresses(ip_addresses)
633
- ip_addresses.reject { |ip| trusted_proxy?(ip) }
739
+ return [] if authority.nil?
740
+ return [] unless match = AUTHORITY.match(authority)
741
+ return match[:host], match[:address], match[:port]&.to_i
634
742
  end
635
743
 
744
+ FORWARDED_SCHEME_HEADERS = {
745
+ proto: HTTP_X_FORWARDED_PROTO,
746
+ scheme: HTTP_X_FORWARDED_SCHEME
747
+ }.freeze
748
+ private_constant :FORWARDED_SCHEME_HEADERS
636
749
  def forwarded_scheme
637
- allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
638
- allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
750
+ forwarded_priority.each do |type|
751
+ case type
752
+ when :forwarded
753
+ if (forwarded_proto = get_http_forwarded(:proto)) &&
754
+ (scheme = allowed_scheme(forwarded_proto.last))
755
+ return scheme
756
+ end
757
+ when :x_forwarded
758
+ x_forwarded_proto_priority.each do |x_type|
759
+ if header = FORWARDED_SCHEME_HEADERS[x_type]
760
+ split_header(get_header(header)).reverse_each do |scheme|
761
+ if allowed_scheme(scheme)
762
+ return scheme
763
+ end
764
+ end
765
+ end
766
+ end
767
+ end
768
+ end
769
+
770
+ nil
639
771
  end
640
772
 
641
773
  def allowed_scheme(header)
642
774
  header if ALLOWED_SCHEMES.include?(header)
643
775
  end
644
776
 
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
777
+ def forwarded_priority
778
+ Request.forwarded_priority
779
+ end
780
+
781
+ def x_forwarded_proto_priority
782
+ Request.x_forwarded_proto_priority
653
783
  end
654
784
  end
655
785
 
@@ -657,3 +787,7 @@ module Rack
657
787
  include Helpers
658
788
  end
659
789
  end
790
+
791
+ # :nocov:
792
+ require_relative 'multipart' unless defined?(Rack::Multipart)
793
+ # :nocov: