rack 2.2.8.1 → 3.0.0.beta1

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -89
  3. data/CONTRIBUTING.md +53 -47
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +287 -0
  6. data/Rakefile +40 -7
  7. data/SPEC.rdoc +166 -125
  8. data/contrib/LICENSE.md +7 -0
  9. data/contrib/logo.webp +0 -0
  10. data/lib/rack/auth/abstract/handler.rb +3 -1
  11. data/lib/rack/auth/abstract/request.rb +3 -1
  12. data/lib/rack/auth/digest/md5.rb +1 -131
  13. data/lib/rack/auth/digest/nonce.rb +1 -54
  14. data/lib/rack/auth/digest/params.rb +1 -54
  15. data/lib/rack/auth/digest/request.rb +1 -43
  16. data/lib/rack/auth/digest.rb +256 -0
  17. data/lib/rack/body_proxy.rb +3 -1
  18. data/lib/rack/builder.rb +60 -42
  19. data/lib/rack/cascade.rb +2 -0
  20. data/lib/rack/chunked.rb +16 -13
  21. data/lib/rack/common_logger.rb +23 -18
  22. data/lib/rack/conditional_get.rb +18 -15
  23. data/lib/rack/constants.rb +62 -0
  24. data/lib/rack/content_length.rb +12 -16
  25. data/lib/rack/content_type.rb +8 -5
  26. data/lib/rack/deflater.rb +40 -26
  27. data/lib/rack/directory.rb +9 -3
  28. data/lib/rack/etag.rb +14 -23
  29. data/lib/rack/events.rb +4 -0
  30. data/lib/rack/file.rb +2 -0
  31. data/lib/rack/files.rb +15 -17
  32. data/lib/rack/head.rb +9 -8
  33. data/lib/rack/headers.rb +154 -0
  34. data/lib/rack/lint.rb +740 -649
  35. data/lib/rack/lock.rb +2 -5
  36. data/lib/rack/logger.rb +2 -0
  37. data/lib/rack/media_type.rb +4 -9
  38. data/lib/rack/method_override.rb +5 -1
  39. data/lib/rack/mime.rb +8 -0
  40. data/lib/rack/mock.rb +1 -271
  41. data/lib/rack/mock_request.rb +166 -0
  42. data/lib/rack/mock_response.rb +124 -0
  43. data/lib/rack/multipart/generator.rb +7 -5
  44. data/lib/rack/multipart/parser.rb +123 -79
  45. data/lib/rack/multipart/uploaded_file.rb +4 -0
  46. data/lib/rack/multipart.rb +20 -40
  47. data/lib/rack/null_logger.rb +9 -0
  48. data/lib/rack/query_parser.rb +76 -44
  49. data/lib/rack/recursive.rb +2 -0
  50. data/lib/rack/reloader.rb +0 -2
  51. data/lib/rack/request.rb +189 -91
  52. data/lib/rack/response.rb +131 -61
  53. data/lib/rack/rewindable_input.rb +24 -5
  54. data/lib/rack/runtime.rb +7 -6
  55. data/lib/rack/sendfile.rb +30 -25
  56. data/lib/rack/show_exceptions.rb +15 -2
  57. data/lib/rack/show_status.rb +17 -7
  58. data/lib/rack/static.rb +8 -8
  59. data/lib/rack/tempfile_reaper.rb +15 -4
  60. data/lib/rack/urlmap.rb +4 -2
  61. data/lib/rack/utils.rb +210 -199
  62. data/lib/rack/version.rb +9 -4
  63. data/lib/rack.rb +5 -76
  64. data/rack.gemspec +6 -6
  65. metadata +19 -31
  66. data/README.rdoc +0 -320
  67. data/bin/rackup +0 -5
  68. data/contrib/rack.png +0 -0
  69. data/contrib/rack.svg +0 -150
  70. data/contrib/rack_logo.svg +0 -164
  71. data/lib/rack/core_ext/regexp.rb +0 -14
  72. data/lib/rack/handler/cgi.rb +0 -59
  73. data/lib/rack/handler/fastcgi.rb +0 -100
  74. data/lib/rack/handler/lsws.rb +0 -61
  75. data/lib/rack/handler/scgi.rb +0 -71
  76. data/lib/rack/handler/thin.rb +0 -36
  77. data/lib/rack/handler/webrick.rb +0 -129
  78. data/lib/rack/handler.rb +0 -104
  79. data/lib/rack/lobster.rb +0 -70
  80. data/lib/rack/server.rb +0 -466
  81. data/lib/rack/session/abstract/id.rb +0 -523
  82. data/lib/rack/session/cookie.rb +0 -204
  83. data/lib/rack/session/memcache.rb +0 -10
  84. data/lib/rack/session/pool.rb +0 -85
@@ -2,9 +2,7 @@
2
2
 
3
3
  module Rack
4
4
  class QueryParser
5
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
6
-
7
- DEFAULT_SEP = /[&;] */n
5
+ DEFAULT_SEP = /[&] */n
8
6
  COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
9
7
 
10
8
  # ParameterTypeError is the error that is raised when incoming structural
@@ -20,29 +18,35 @@ module Rack
20
18
  # nested over the specified limit.
21
19
  class ParamsTooDeepError < RangeError; end
22
20
 
23
- def self.make_default(key_space_limit, param_depth_limit)
24
- new Params, key_space_limit, param_depth_limit
21
+ def self.make_default(_key_space_limit=(not_deprecated = true; nil), param_depth_limit)
22
+ unless not_deprecated
23
+ warn("`first argument `key_space limit` is deprecated and no longer has an effect. Please call with only one argument, which will be required in a future version of Rack", uplevel: 1)
24
+ end
25
+
26
+ new Params, param_depth_limit
25
27
  end
26
28
 
27
- attr_reader :key_space_limit, :param_depth_limit
29
+ attr_reader :param_depth_limit
30
+
31
+ def initialize(params_class, _key_space_limit=(not_deprecated = true; nil), param_depth_limit)
32
+ unless not_deprecated
33
+ warn("`second argument `key_space limit` is deprecated and no longer has an effect. Please call with only two arguments, which will be required in a future version of Rack", uplevel: 1)
34
+ end
28
35
 
29
- def initialize(params_class, key_space_limit, param_depth_limit)
30
36
  @params_class = params_class
31
- @key_space_limit = key_space_limit
32
37
  @param_depth_limit = param_depth_limit
33
38
  end
34
39
 
35
40
  # Stolen from Mongrel, with some small modifications:
36
- # Parses a query string by breaking it up at the '&'
37
- # and ';' characters. You can also use this to parse
38
- # cookies by changing the characters used in the second
39
- # parameter (which defaults to '&;').
40
- def parse_query(qs, d = nil, &unescaper)
41
+ # Parses a query string by breaking it up at the '&'. You can also use this
42
+ # to parse cookies by changing the characters used in the second parameter
43
+ # (which defaults to '&').
44
+ def parse_query(qs, separator = nil, &unescaper)
41
45
  unescaper ||= method(:unescape)
42
46
 
43
47
  params = make_params
44
48
 
45
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
49
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
46
50
  next if p.empty?
47
51
  k, v = p.split('=', 2).map!(&unescaper)
48
52
 
@@ -65,14 +69,14 @@ module Rack
65
69
  # query strings with parameters of conflicting types, in this case a
66
70
  # ParameterTypeError is raised. Users are encouraged to return a 400 in this
67
71
  # case.
68
- def parse_nested_query(qs, d = nil)
72
+ def parse_nested_query(qs, separator = nil)
69
73
  params = make_params
70
74
 
71
75
  unless qs.nil? || qs.empty?
72
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
76
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
73
77
  k, v = p.split('=', 2).map! { |s| unescape(s) }
74
78
 
75
- normalize_params(params, k, v, param_depth_limit)
79
+ _normalize_params(params, k, v, 0)
76
80
  end
77
81
  end
78
82
 
@@ -83,58 +87,89 @@ module Rack
83
87
 
84
88
  # normalize_params recursively expands parameters into structural types. If
85
89
  # the structural types represented by two different parameter names are in
86
- # conflict, a ParameterTypeError is raised.
87
- def normalize_params(params, name, v, depth)
88
- raise ParamsTooDeepError if depth <= 0
89
-
90
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
91
- k = $1 || ''
92
- after = $' || ''
90
+ # conflict, a ParameterTypeError is raised. The depth argument is deprecated
91
+ # and should no longer be used, it is kept for backwards compatibility with
92
+ # earlier versions of rack.
93
+ def normalize_params(params, name, v, _depth=nil)
94
+ _normalize_params(params, name, v, 0)
95
+ end
93
96
 
94
- if k.empty?
95
- if !v.nil? && name == "[]"
96
- return Array(v)
97
+ private def _normalize_params(params, name, v, depth)
98
+ raise ParamsTooDeepError if depth >= param_depth_limit
99
+
100
+ if !name
101
+ # nil name, treat same as empty string (required by tests)
102
+ k = after = ''
103
+ elsif depth == 0
104
+ # Start of parsing, don't treat [] or [ at start of string specially
105
+ if start = name.index('[', 1)
106
+ # Start of parameter nesting, use part before brackets as key
107
+ k = name[0, start]
108
+ after = name[start, name.length]
97
109
  else
98
- return
110
+ # Plain parameter with no nesting
111
+ k = name
112
+ after = ''
99
113
  end
114
+ elsif name.start_with?('[]')
115
+ # Array nesting
116
+ k = '[]'
117
+ after = name[2, name.length]
118
+ elsif name.start_with?('[') && (start = name.index(']', 1))
119
+ # Hash nesting, use the part inside brackets as the key
120
+ k = name[1, start-1]
121
+ after = name[start+1, name.length]
122
+ else
123
+ # Probably malformed input, nested but not starting with [
124
+ # treat full name as key for backwards compatibility.
125
+ k = name
126
+ after = ''
100
127
  end
101
128
 
129
+ return if k.empty?
130
+
131
+ v ||= String.new
132
+
102
133
  if after == ''
103
- params[k] = v
134
+ if k == '[]' && depth != 0
135
+ return [v]
136
+ else
137
+ params[k] = v
138
+ end
104
139
  elsif after == "["
105
140
  params[name] = v
106
141
  elsif after == "[]"
107
142
  params[k] ||= []
108
143
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
109
144
  params[k] << v
110
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
111
- child_key = $1
145
+ elsif after.start_with?('[]')
146
+ # Recognize x[][y] (hash inside array) parameters
147
+ unless after[2] == '[' && after.end_with?(']') && (child_key = after[3, after.length-4]) && !child_key.empty? && !child_key.index('[') && !child_key.index(']')
148
+ # Handle other nested array parameters
149
+ child_key = after[2, after.length]
150
+ end
112
151
  params[k] ||= []
113
152
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
114
153
  if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
115
- normalize_params(params[k].last, child_key, v, depth - 1)
154
+ _normalize_params(params[k].last, child_key, v, depth + 1)
116
155
  else
117
- params[k] << normalize_params(make_params, child_key, v, depth - 1)
156
+ params[k] << _normalize_params(make_params, child_key, v, depth + 1)
118
157
  end
119
158
  else
120
159
  params[k] ||= make_params
121
160
  raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
122
- params[k] = normalize_params(params[k], after, v, depth - 1)
161
+ params[k] = _normalize_params(params[k], after, v, depth + 1)
123
162
  end
124
163
 
125
164
  params
126
165
  end
127
166
 
128
167
  def make_params
129
- @params_class.new @key_space_limit
130
- end
131
-
132
- def new_space_limit(key_space_limit)
133
- self.class.new @params_class, key_space_limit, param_depth_limit
168
+ @params_class.new
134
169
  end
135
170
 
136
171
  def new_depth_limit(param_depth_limit)
137
- self.class.new @params_class, key_space_limit, param_depth_limit
172
+ self.class.new @params_class, param_depth_limit
138
173
  end
139
174
 
140
175
  private
@@ -160,8 +195,7 @@ module Rack
160
195
  end
161
196
 
162
197
  class Params
163
- def initialize(limit)
164
- @limit = limit
198
+ def initialize
165
199
  @size = 0
166
200
  @params = {}
167
201
  end
@@ -171,8 +205,6 @@ module Rack
171
205
  end
172
206
 
173
207
  def []=(key, value)
174
- @size += key.size if key && !@params.key?(key)
175
- raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
176
208
  @params[key] = value
177
209
  end
178
210
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'uri'
4
4
 
5
+ require_relative 'constants'
6
+
5
7
  module Rack
6
8
  # Rack::ForwardRequest gets caught by Rack::Recursive and redirects
7
9
  # the current request to the app at +url+.
data/lib/rack/reloader.rb CHANGED
@@ -22,8 +22,6 @@ module Rack
22
22
  # It is performing a check/reload cycle at the start of every request, but
23
23
  # also respects a cool down time, during which nothing will be done.
24
24
  class Reloader
25
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
26
-
27
25
  def initialize(app, cooldown = 10, backend = Stat)
28
26
  @app = app
29
27
  @cooldown = cooldown
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
347
+ _, _, port = split_authority(authority)
315
348
  end
316
349
 
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
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)
@@ -427,7 +485,7 @@ module Rack
427
485
  if get_header(RACK_REQUEST_QUERY_STRING) == query_string
428
486
  get_header(RACK_REQUEST_QUERY_HASH)
429
487
  else
430
- query_hash = parse_query(query_string, '&;')
488
+ query_hash = parse_query(query_string, '&')
431
489
  set_header(RACK_REQUEST_QUERY_STRING, query_string)
432
490
  set_header(RACK_REQUEST_QUERY_HASH, query_hash)
433
491
  end
@@ -452,13 +510,12 @@ module Rack
452
510
 
453
511
  set_header RACK_REQUEST_FORM_VARS, form_vars
454
512
  set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
455
-
456
- get_header(RACK_INPUT).rewind
457
513
  end
458
514
  set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
459
515
  get_header RACK_REQUEST_FORM_HASH
460
516
  else
461
- {}
517
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
518
+ set_header(RACK_REQUEST_FORM_HASH, {})
462
519
  end
463
520
  end
464
521
 
@@ -530,9 +587,7 @@ module Rack
530
587
 
531
588
  # shortcut for <tt>request.params[key]</tt>
532
589
  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
590
+ warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
536
591
 
537
592
  params[key.to_s]
538
593
  end
@@ -541,9 +596,7 @@ module Rack
541
596
  #
542
597
  # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
543
598
  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
599
+ warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
547
600
 
548
601
  params[key.to_s] = value
549
602
  end
@@ -572,8 +625,8 @@ module Rack
572
625
  end
573
626
 
574
627
  def parse_http_accept_header(header)
575
- header.to_s.split(",").each(&:strip!).map do |part|
576
- attribute, parameters = part.split(";", 2).each(&:strip!)
628
+ header.to_s.split(/\s*,\s*/).map do |part|
629
+ attribute, parameters = part.split(/\s*;\s*/, 2)
577
630
  quality = 1.0
578
631
  if parameters and /\Aq=([\d.]+)/ =~ parameters
579
632
  quality = $1.to_f
@@ -582,6 +635,11 @@ module Rack
582
635
  end
583
636
  end
584
637
 
638
+ # Get an array of values set in the RFC 7239 `Forwarded` request header.
639
+ def get_http_forwarded(token)
640
+ Utils.forwarded_values(get_header(HTTP_FORWARDED))&.[](token)
641
+ end
642
+
585
643
  def query_parser
586
644
  Utils.default_query_parser
587
645
  end
@@ -598,58 +656,94 @@ module Rack
598
656
  value ? value.strip.split(/[,\s]+/) : []
599
657
  end
600
658
 
601
- AUTHORITY = /^
602
- # The host:
659
+ # ipv6 extracted from resolv stdlib, simplified
660
+ # to remove numbered match group creation.
661
+ ipv6 = Regexp.union(
662
+ /(?:[0-9A-Fa-f]{1,4}:){7}
663
+ [0-9A-Fa-f]{1,4}/x,
664
+ /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
665
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?/x,
666
+ /(?:[0-9A-Fa-f]{1,4}:){6,6}
667
+ \d+\.\d+\.\d+\.\d+/x,
668
+ /(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
669
+ (?:[0-9A-Fa-f]{1,4}:)*
670
+ \d+\.\d+\.\d+\.\d+/x,
671
+ /[Ff][Ee]80
672
+ (?::[0-9A-Fa-f]{1,4}){7}
673
+ %[-0-9A-Za-z._~]+/x,
674
+ /[Ff][Ee]80:
675
+ (?:
676
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)? ::
677
+ (?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
678
+ |
679
+ :(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?
680
+ )?
681
+ :[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+/x)
682
+
683
+ AUTHORITY = /
684
+ \A
603
685
  (?<host>
604
- # An IPv6 address:
605
- (\[(?<ip6>.*)\])
606
- |
607
- # An IPv4 address:
608
- (?<ip4>[\d\.]+)
686
+ # Match IPv6 as a string of hex digits and colons in square brackets
687
+ \[(?<address>#{ipv6})\]
609
688
  |
610
- # A hostname:
611
- (?<name>[a-zA-Z0-9\.\-_]+)
689
+ # Match any other printable string (except square brackets) as a hostname
690
+ (?<address>[[[:graph:]&&[^\[\]]]]*?)
612
691
  )
613
- # The optional port:
614
692
  (:(?<port>\d+))?
615
- $/x
693
+ \z
694
+ /x
616
695
 
617
696
  private_constant :AUTHORITY
618
697
 
619
698
  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
699
+ return [] if authority.nil?
700
+ return [] unless match = AUTHORITY.match(authority)
701
+ return match[:host], match[:address], match[:port]&.to_i
630
702
  end
631
703
 
632
704
  def reject_trusted_ip_addresses(ip_addresses)
633
705
  ip_addresses.reject { |ip| trusted_proxy?(ip) }
634
706
  end
635
707
 
708
+ FORWARDED_SCHEME_HEADERS = {
709
+ proto: HTTP_X_FORWARDED_PROTO,
710
+ scheme: HTTP_X_FORWARDED_SCHEME
711
+ }.freeze
712
+ private_constant :FORWARDED_SCHEME_HEADERS
636
713
  def forwarded_scheme
637
- allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
638
- allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
714
+ forwarded_priority.each do |type|
715
+ case type
716
+ when :forwarded
717
+ if (forwarded_proto = get_http_forwarded(:proto)) &&
718
+ (scheme = allowed_scheme(forwarded_proto.last))
719
+ return scheme
720
+ end
721
+ when :x_forwarded
722
+ x_forwarded_proto_priority.each do |x_type|
723
+ if header = FORWARDED_SCHEME_HEADERS[x_type]
724
+ split_header(get_header(header)).reverse_each do |scheme|
725
+ if allowed_scheme(scheme)
726
+ return scheme
727
+ end
728
+ end
729
+ end
730
+ end
731
+ end
732
+ end
733
+
734
+ nil
639
735
  end
640
736
 
641
737
  def allowed_scheme(header)
642
738
  header if ALLOWED_SCHEMES.include?(header)
643
739
  end
644
740
 
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
741
+ def forwarded_priority
742
+ Request.forwarded_priority
743
+ end
744
+
745
+ def x_forwarded_proto_priority
746
+ Request.x_forwarded_proto_priority
653
747
  end
654
748
  end
655
749
 
@@ -657,3 +751,7 @@ module Rack
657
751
  include Helpers
658
752
  end
659
753
  end
754
+
755
+ # :nocov:
756
+ require_relative 'multipart' unless defined?(Rack::Multipart)
757
+ # :nocov: