rack 2.2.21 → 3.1.19

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +459 -73
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +363 -0
  6. data/SPEC.rdoc +204 -131
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -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 +102 -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 +18 -15
  16. data/lib/rack/constants.rb +67 -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 +9 -3
  21. data/lib/rack/etag.rb +17 -23
  22. data/lib/rack/events.rb +4 -0
  23. data/lib/rack/files.rb +15 -17
  24. data/lib/rack/head.rb +9 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +840 -644
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/logger.rb +3 -0
  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 +153 -0
  34. data/lib/rack/multipart/generator.rb +7 -5
  35. data/lib/rack/multipart/parser.rb +216 -102
  36. data/lib/rack/multipart/uploaded_file.rb +4 -0
  37. data/lib/rack/multipart.rb +53 -40
  38. data/lib/rack/null_logger.rb +9 -0
  39. data/lib/rack/query_parser.rb +80 -102
  40. data/lib/rack/recursive.rb +2 -0
  41. data/lib/rack/reloader.rb +0 -2
  42. data/lib/rack/request.rb +263 -126
  43. data/lib/rack/response.rb +151 -66
  44. data/lib/rack/rewindable_input.rb +24 -5
  45. data/lib/rack/runtime.rb +7 -6
  46. data/lib/rack/sendfile.rb +35 -30
  47. data/lib/rack/show_exceptions.rb +21 -4
  48. data/lib/rack/show_status.rb +17 -7
  49. data/lib/rack/static.rb +8 -8
  50. data/lib/rack/tempfile_reaper.rb +15 -4
  51. data/lib/rack/urlmap.rb +3 -1
  52. data/lib/rack/utils.rb +236 -237
  53. data/lib/rack/version.rb +1 -9
  54. data/lib/rack.rb +13 -89
  55. metadata +13 -39
  56. data/README.rdoc +0 -355
  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 -34
  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/server.rb +0 -466
  82. data/lib/rack/session/abstract/id.rb +0 -523
  83. data/lib/rack/session/cookie.rb +0 -203
  84. data/lib/rack/session/memcache.rb +0 -10
  85. data/lib/rack/session/pool.rb +0 -90
  86. 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,54 @@ 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
27
64
  @params = nil
28
- super(env)
29
65
  end
30
66
 
31
67
  def params
@@ -49,6 +85,8 @@ module Rack
49
85
 
50
86
  def initialize(env)
51
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
52
90
  super()
53
91
  end
54
92
 
@@ -135,6 +173,8 @@ module Rack
135
173
  # The contents of the host/:authority header sent to the proxy.
136
174
  HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
137
175
 
176
+ HTTP_FORWARDED = 'HTTP_FORWARDED'
177
+
138
178
  # The value of the scheme sent to the proxy.
139
179
  HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
140
180
 
@@ -144,7 +184,7 @@ module Rack
144
184
  # The port used to connect to the proxy.
145
185
  HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
146
186
 
147
- # Another way for specifing https scheme was used.
187
+ # Another way for specifying https scheme was used.
148
188
  HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
149
189
 
150
190
  def body; get_header(RACK_INPUT) end
@@ -159,7 +199,6 @@ module Rack
159
199
  def content_length; get_header('CONTENT_LENGTH') end
160
200
  def logger; get_header(RACK_LOGGER) end
161
201
  def user_agent; get_header('HTTP_USER_AGENT') end
162
- def multithread?; get_header(RACK_MULTITHREAD) end
163
202
 
164
203
  # the referer of the client
165
204
  def referer; get_header('HTTP_REFERER') end
@@ -248,9 +287,7 @@ module Rack
248
287
  end
249
288
 
250
289
  def server_port
251
- if port = get_header(SERVER_PORT)
252
- Integer(port)
253
- end
290
+ get_header(SERVER_PORT)
254
291
  end
255
292
 
256
293
  def cookies
@@ -307,44 +344,67 @@ module Rack
307
344
 
308
345
  def port
309
346
  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
347
+ _, _, port = split_authority(authority)
325
348
  end
326
349
 
327
- self.server_port
350
+ port || forwarded_port&.last || DEFAULT_PORTS[scheme] || server_port
328
351
  end
329
352
 
330
353
  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]
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
334
368
  end
335
369
  end
370
+
371
+ nil
336
372
  end
337
373
 
338
374
  def forwarded_port
339
- if value = get_header(HTTP_X_FORWARDED_PORT)
340
- split_header(value).map(&:to_i)
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
341
388
  end
389
+
390
+ nil
342
391
  end
343
392
 
344
393
  def forwarded_authority
345
- if value = get_header(HTTP_X_FORWARDED_HOST)
346
- wrap_ipv6(split_header(value).first)
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
347
405
  end
406
+
407
+ nil
348
408
  end
349
409
 
350
410
  def ssl?
@@ -356,17 +416,15 @@ module Rack
356
416
  external_addresses = reject_trusted_ip_addresses(remote_addresses)
357
417
 
358
418
  unless external_addresses.empty?
359
- return external_addresses.first
419
+ return external_addresses.last
360
420
  end
361
421
 
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
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
370
428
  end
371
429
 
372
430
  # If all the addresses are trusted, and we aren't forwarded, just return
@@ -402,13 +460,13 @@ module Rack
402
460
  end
403
461
 
404
462
  # Determine whether the request body contains form-data by checking
405
- # the request Content-Type for one of the media-types:
463
+ # the request content-type for one of the media-types:
406
464
  # "application/x-www-form-urlencoded" or "multipart/form-data". The
407
465
  # list of form-data media types can be modified through the
408
466
  # +FORM_DATA_MEDIA_TYPES+ array.
409
467
  #
410
468
  # A request body is also assumed to contain form-data when no
411
- # Content-Type header is provided and the request_method is POST.
469
+ # content-type header is provided and the request_method is POST.
412
470
  def form_data?
413
471
  type = media_type
414
472
  meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
@@ -424,10 +482,15 @@ module Rack
424
482
 
425
483
  # Returns the data received in the query string.
426
484
  def GET
427
- 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
428
488
  get_header(RACK_REQUEST_QUERY_HASH)
429
489
  else
430
- 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, '&')
431
494
  set_header(RACK_REQUEST_QUERY_STRING, query_string)
432
495
  set_header(RACK_REQUEST_QUERY_HASH, query_hash)
433
496
  end
@@ -438,30 +501,55 @@ module Rack
438
501
  # This method support both application/x-www-form-urlencoded and
439
502
  # multipart/form-data.
440
503
  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
- # Add 2 bytes. One to check whether it is over the limit, and a second
448
- # in case the slice! call below removes the last byte
449
- # If read returns nil, use the empty string
450
- form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
451
-
452
- # Fix for Safari Ajax postings that always append \0
453
- # form_vars.sub!(/\0\z/, '') # performance replacement:
454
- form_vars.slice!(-1) if form_vars.end_with?("\0")
455
-
456
- set_header RACK_REQUEST_FORM_VARS, form_vars
457
- set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
458
-
459
- 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
460
520
  end
461
- set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
462
- get_header RACK_REQUEST_FORM_HASH
463
- else
464
- {}
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
+ # Add 2 bytes. One to check whether it is over the limit, and a second
532
+ # in case the slice! call below removes the last byte
533
+ # If read returns nil, use the empty string
534
+ form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
535
+
536
+ # Fix for Safari Ajax postings that always append \0
537
+ # form_vars.sub!(/\0\z/, '') # performance replacement:
538
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
539
+
540
+ set_header RACK_REQUEST_FORM_VARS, form_vars
541
+ set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
542
+ end
543
+
544
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
545
+ get_header RACK_REQUEST_FORM_HASH
546
+ else
547
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
548
+ set_header(RACK_REQUEST_FORM_HASH, {})
549
+ end
550
+ rescue => error
551
+ set_header(RACK_REQUEST_FORM_ERROR, error)
552
+ raise
465
553
  end
466
554
  end
467
555
 
@@ -531,28 +619,10 @@ module Rack
531
619
  Rack::Request.ip_filter.call(ip)
532
620
  end
533
621
 
534
- # shortcut for <tt>request.params[key]</tt>
535
- def [](key)
536
- if $VERBOSE
537
- warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
538
- end
539
-
540
- params[key.to_s]
541
- end
542
-
543
- # shortcut for <tt>request.params[key] = value</tt>
544
- #
545
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
546
- def []=(key, value)
547
- if $VERBOSE
548
- warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
549
- end
550
-
551
- params[key.to_s] = value
552
- end
553
-
554
622
  # like Hash#values_at
555
623
  def values_at(*keys)
624
+ 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)
625
+
556
626
  keys.map { |key| params[key] }
557
627
  end
558
628
 
@@ -575,14 +645,31 @@ module Rack
575
645
  end
576
646
 
577
647
  def parse_http_accept_header(header)
578
- header.to_s.split(",").each(&:strip!).map do |part|
579
- attribute, parameters = part.split(";", 2).each(&:strip!)
648
+ # It would be nice to use filter_map here, but it's Ruby 2.7+
649
+ parts = header.to_s.split(',')
650
+
651
+ parts.map! do |part|
652
+ part.strip!
653
+ next if part.empty?
654
+
655
+ attribute, parameters = part.split(';', 2)
656
+ attribute.strip!
657
+ parameters&.strip!
580
658
  quality = 1.0
581
659
  if parameters and /\Aq=([\d.]+)/ =~ parameters
582
660
  quality = $1.to_f
583
661
  end
584
662
  [attribute, quality]
585
663
  end
664
+
665
+ parts.compact!
666
+
667
+ parts
668
+ end
669
+
670
+ # Get an array of values set in the RFC 7239 `Forwarded` request header.
671
+ def get_http_forwarded(token)
672
+ Utils.forwarded_values(get_header(HTTP_FORWARDED))&.[](token)
586
673
  end
587
674
 
588
675
  def query_parser
@@ -597,62 +684,108 @@ module Rack
597
684
  Rack::Multipart.extract_multipart(self, query_parser)
598
685
  end
599
686
 
687
+ def expand_param_pairs(pairs, query_parser = query_parser())
688
+ params = query_parser.make_params
689
+
690
+ pairs.each do |k, v|
691
+ query_parser.normalize_params(params, k, v)
692
+ end
693
+
694
+ params.to_params_hash
695
+ end
696
+
600
697
  def split_header(value)
601
698
  value ? value.strip.split(/[,\s]+/) : []
602
699
  end
603
700
 
604
- AUTHORITY = /^
605
- # The host:
701
+ # ipv6 extracted from resolv stdlib, simplified
702
+ # to remove numbered match group creation.
703
+ ipv6 = Regexp.union(
704
+ /(?:[0-9A-Fa-f]{1,4}:){7}
705
+ [0-9A-Fa-f]{1,4}/x,
706
+ /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
707
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?/x,
708
+ /(?:[0-9A-Fa-f]{1,4}:){6,6}
709
+ \d+\.\d+\.\d+\.\d+/x,
710
+ /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
711
+ (?:[0-9A-Fa-f]{1,4}:)*
712
+ \d+\.\d+\.\d+\.\d+/x,
713
+ /[Ff][Ee]80
714
+ (?::[0-9A-Fa-f]{1,4}){7}
715
+ %[-0-9A-Za-z._~]+/x,
716
+ /[Ff][Ee]80:
717
+ (?:
718
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
719
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
720
+ |
721
+ :(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
722
+ )?
723
+ :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+/x)
724
+
725
+ AUTHORITY = /
726
+ \A
606
727
  (?<host>
607
- # An IPv6 address:
608
- (\[(?<ip6>.*)\])
728
+ # Match IPv6 as a string of hex digits and colons in square brackets
729
+ \[(?<address>#{ipv6})\]
609
730
  |
610
- # An IPv4 address:
611
- (?<ip4>[\d\.]+)
612
- |
613
- # A hostname:
614
- (?<name>[a-zA-Z0-9\.\-_]+)
731
+ # Match any other printable string (except square brackets) as a hostname
732
+ (?<address>[[[:graph:]&&[^\[\]]]]*?)
615
733
  )
616
- # The optional port:
617
734
  (:(?<port>\d+))?
618
- $/x
735
+ \z
736
+ /x
619
737
 
620
738
  private_constant :AUTHORITY
621
739
 
622
740
  def split_authority(authority)
623
- if match = AUTHORITY.match(authority)
624
- if address = match[:ip6]
625
- return match[:host], address, match[:port]&.to_i
626
- else
627
- return match[:host], match[:host], match[:port]&.to_i
628
- end
629
- end
630
-
631
- # Give up!
632
- return authority, authority, nil
741
+ return [] if authority.nil?
742
+ return [] unless match = AUTHORITY.match(authority)
743
+ return match[:host], match[:address], match[:port]&.to_i
633
744
  end
634
745
 
635
746
  def reject_trusted_ip_addresses(ip_addresses)
636
747
  ip_addresses.reject { |ip| trusted_proxy?(ip) }
637
748
  end
638
749
 
750
+ FORWARDED_SCHEME_HEADERS = {
751
+ proto: HTTP_X_FORWARDED_PROTO,
752
+ scheme: HTTP_X_FORWARDED_SCHEME
753
+ }.freeze
754
+ private_constant :FORWARDED_SCHEME_HEADERS
639
755
  def forwarded_scheme
640
- allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
641
- allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
756
+ forwarded_priority.each do |type|
757
+ case type
758
+ when :forwarded
759
+ if (forwarded_proto = get_http_forwarded(:proto)) &&
760
+ (scheme = allowed_scheme(forwarded_proto.last))
761
+ return scheme
762
+ end
763
+ when :x_forwarded
764
+ x_forwarded_proto_priority.each do |x_type|
765
+ if header = FORWARDED_SCHEME_HEADERS[x_type]
766
+ split_header(get_header(header)).reverse_each do |scheme|
767
+ if allowed_scheme(scheme)
768
+ return scheme
769
+ end
770
+ end
771
+ end
772
+ end
773
+ end
774
+ end
775
+
776
+ nil
642
777
  end
643
778
 
644
779
  def allowed_scheme(header)
645
780
  header if ALLOWED_SCHEMES.include?(header)
646
781
  end
647
782
 
648
- def extract_proto_header(header)
649
- if header
650
- if (comma_index = header.index(','))
651
- header[0, comma_index]
652
- else
653
- header
654
- end
655
- end
783
+ def forwarded_priority
784
+ Request.forwarded_priority
785
+ end
786
+
787
+ def x_forwarded_proto_priority
788
+ Request.x_forwarded_proto_priority
656
789
  end
657
790
  end
658
791
 
@@ -660,3 +793,7 @@ module Rack
660
793
  include Helpers
661
794
  end
662
795
  end
796
+
797
+ # :nocov:
798
+ require_relative 'multipart' unless defined?(Rack::Multipart)
799
+ # :nocov: