rack 2.0.9.3 → 3.0.0

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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +808 -0
  3. data/CONTRIBUTING.md +142 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.md +293 -0
  6. data/SPEC.rdoc +340 -0
  7. data/lib/rack/auth/abstract/handler.rb +6 -2
  8. data/lib/rack/auth/abstract/request.rb +4 -2
  9. data/lib/rack/auth/basic.rb +7 -4
  10. data/lib/rack/auth/digest/md5.rb +1 -129
  11. data/lib/rack/auth/digest/nonce.rb +1 -51
  12. data/lib/rack/auth/digest/params.rb +1 -52
  13. data/lib/rack/auth/digest/request.rb +1 -41
  14. data/lib/rack/auth/digest.rb +256 -0
  15. data/lib/rack/body_proxy.rb +18 -15
  16. data/lib/rack/builder.rb +151 -40
  17. data/lib/rack/cascade.rb +30 -12
  18. data/lib/rack/chunked.rb +74 -23
  19. data/lib/rack/common_logger.rb +49 -36
  20. data/lib/rack/conditional_get.rb +33 -26
  21. data/lib/rack/config.rb +2 -0
  22. data/lib/rack/constants.rb +63 -0
  23. data/lib/rack/content_length.rb +13 -16
  24. data/lib/rack/content_type.rb +12 -8
  25. data/lib/rack/deflater.rb +84 -45
  26. data/lib/rack/directory.rb +90 -64
  27. data/lib/rack/etag.rb +17 -23
  28. data/lib/rack/events.rb +23 -20
  29. data/lib/rack/file.rb +5 -172
  30. data/lib/rack/files.rb +216 -0
  31. data/lib/rack/head.rb +10 -9
  32. data/lib/rack/headers.rb +154 -0
  33. data/lib/rack/lint.rb +786 -645
  34. data/lib/rack/lock.rb +4 -6
  35. data/lib/rack/logger.rb +4 -0
  36. data/lib/rack/media_type.rb +10 -5
  37. data/lib/rack/method_override.rb +8 -2
  38. data/lib/rack/mime.rb +17 -1
  39. data/lib/rack/mock.rb +2 -195
  40. data/lib/rack/mock_request.rb +166 -0
  41. data/lib/rack/mock_response.rb +126 -0
  42. data/lib/rack/multipart/generator.rb +21 -15
  43. data/lib/rack/multipart/parser.rb +161 -118
  44. data/lib/rack/multipart/uploaded_file.rb +19 -7
  45. data/lib/rack/multipart.rb +23 -41
  46. data/lib/rack/null_logger.rb +11 -0
  47. data/lib/rack/query_parser.rb +126 -65
  48. data/lib/rack/recursive.rb +9 -5
  49. data/lib/rack/reloader.rb +6 -4
  50. data/lib/rack/request.rb +331 -74
  51. data/lib/rack/response.rb +223 -70
  52. data/lib/rack/rewindable_input.rb +28 -8
  53. data/lib/rack/runtime.rb +11 -8
  54. data/lib/rack/sendfile.rb +42 -33
  55. data/lib/rack/show_exceptions.rb +35 -18
  56. data/lib/rack/show_status.rb +25 -15
  57. data/lib/rack/static.rb +30 -18
  58. data/lib/rack/tempfile_reaper.rb +16 -5
  59. data/lib/rack/urlmap.rb +14 -6
  60. data/lib/rack/utils.rb +268 -260
  61. data/lib/rack/version.rb +34 -0
  62. data/lib/rack.rb +15 -92
  63. metadata +44 -207
  64. data/HISTORY.md +0 -520
  65. data/README.rdoc +0 -316
  66. data/Rakefile +0 -116
  67. data/SPEC +0 -263
  68. data/bin/rackup +0 -4
  69. data/contrib/rack.png +0 -0
  70. data/contrib/rack.svg +0 -150
  71. data/contrib/rack_logo.svg +0 -164
  72. data/contrib/rdoc.css +0 -412
  73. data/example/lobster.ru +0 -4
  74. data/example/protectedlobster.rb +0 -14
  75. data/example/protectedlobster.ru +0 -8
  76. data/lib/rack/handler/cgi.rb +0 -60
  77. data/lib/rack/handler/fastcgi.rb +0 -100
  78. data/lib/rack/handler/lsws.rb +0 -61
  79. data/lib/rack/handler/scgi.rb +0 -70
  80. data/lib/rack/handler/thin.rb +0 -36
  81. data/lib/rack/handler/webrick.rb +0 -120
  82. data/lib/rack/handler.rb +0 -99
  83. data/lib/rack/lobster.rb +0 -70
  84. data/lib/rack/server.rb +0 -395
  85. data/lib/rack/session/abstract/id.rb +0 -510
  86. data/lib/rack/session/cookie.rb +0 -204
  87. data/lib/rack/session/memcache.rb +0 -99
  88. data/lib/rack/session/pool.rb +0 -83
  89. data/rack.gemspec +0 -34
  90. data/test/builder/an_underscore_app.rb +0 -5
  91. data/test/builder/anything.rb +0 -5
  92. data/test/builder/comment.ru +0 -4
  93. data/test/builder/end.ru +0 -5
  94. data/test/builder/line.ru +0 -1
  95. data/test/builder/options.ru +0 -2
  96. data/test/cgi/assets/folder/test.js +0 -1
  97. data/test/cgi/assets/fonts/font.eot +0 -1
  98. data/test/cgi/assets/images/image.png +0 -1
  99. data/test/cgi/assets/index.html +0 -1
  100. data/test/cgi/assets/javascripts/app.js +0 -1
  101. data/test/cgi/assets/stylesheets/app.css +0 -1
  102. data/test/cgi/lighttpd.conf +0 -26
  103. data/test/cgi/rackup_stub.rb +0 -6
  104. data/test/cgi/sample_rackup.ru +0 -5
  105. data/test/cgi/test +0 -9
  106. data/test/cgi/test+directory/test+file +0 -1
  107. data/test/cgi/test.fcgi +0 -9
  108. data/test/cgi/test.gz +0 -0
  109. data/test/cgi/test.ru +0 -5
  110. data/test/gemloader.rb +0 -10
  111. data/test/helper.rb +0 -34
  112. data/test/multipart/bad_robots +0 -259
  113. data/test/multipart/binary +0 -0
  114. data/test/multipart/content_type_and_no_filename +0 -6
  115. data/test/multipart/empty +0 -10
  116. data/test/multipart/fail_16384_nofile +0 -814
  117. data/test/multipart/file1.txt +0 -1
  118. data/test/multipart/filename_and_modification_param +0 -7
  119. data/test/multipart/filename_and_no_name +0 -6
  120. data/test/multipart/filename_with_encoded_words +0 -7
  121. data/test/multipart/filename_with_escaped_quotes +0 -6
  122. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  123. data/test/multipart/filename_with_null_byte +0 -7
  124. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  125. data/test/multipart/filename_with_single_quote +0 -7
  126. data/test/multipart/filename_with_unescaped_percentages +0 -6
  127. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  128. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  129. data/test/multipart/filename_with_unescaped_quotes +0 -6
  130. data/test/multipart/ie +0 -6
  131. data/test/multipart/invalid_character +0 -6
  132. data/test/multipart/mixed_files +0 -21
  133. data/test/multipart/nested +0 -10
  134. data/test/multipart/none +0 -9
  135. data/test/multipart/quoted +0 -15
  136. data/test/multipart/rack-logo.png +0 -0
  137. data/test/multipart/semicolon +0 -6
  138. data/test/multipart/text +0 -15
  139. data/test/multipart/three_files_three_fields +0 -31
  140. data/test/multipart/unity3d_wwwform +0 -11
  141. data/test/multipart/webkit +0 -32
  142. data/test/rackup/config.ru +0 -31
  143. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  144. data/test/spec_auth_basic.rb +0 -89
  145. data/test/spec_auth_digest.rb +0 -260
  146. data/test/spec_body_proxy.rb +0 -85
  147. data/test/spec_builder.rb +0 -233
  148. data/test/spec_cascade.rb +0 -63
  149. data/test/spec_cgi.rb +0 -84
  150. data/test/spec_chunked.rb +0 -103
  151. data/test/spec_common_logger.rb +0 -107
  152. data/test/spec_conditional_get.rb +0 -103
  153. data/test/spec_config.rb +0 -23
  154. data/test/spec_content_length.rb +0 -86
  155. data/test/spec_content_type.rb +0 -46
  156. data/test/spec_deflater.rb +0 -375
  157. data/test/spec_directory.rb +0 -148
  158. data/test/spec_etag.rb +0 -108
  159. data/test/spec_events.rb +0 -133
  160. data/test/spec_fastcgi.rb +0 -85
  161. data/test/spec_file.rb +0 -264
  162. data/test/spec_handler.rb +0 -57
  163. data/test/spec_head.rb +0 -46
  164. data/test/spec_lint.rb +0 -520
  165. data/test/spec_lobster.rb +0 -59
  166. data/test/spec_lock.rb +0 -204
  167. data/test/spec_logger.rb +0 -24
  168. data/test/spec_media_type.rb +0 -42
  169. data/test/spec_method_override.rb +0 -110
  170. data/test/spec_mime.rb +0 -51
  171. data/test/spec_mock.rb +0 -359
  172. data/test/spec_multipart.rb +0 -721
  173. data/test/spec_null_logger.rb +0 -21
  174. data/test/spec_recursive.rb +0 -75
  175. data/test/spec_request.rb +0 -1423
  176. data/test/spec_response.rb +0 -528
  177. data/test/spec_rewindable_input.rb +0 -128
  178. data/test/spec_runtime.rb +0 -50
  179. data/test/spec_sendfile.rb +0 -125
  180. data/test/spec_server.rb +0 -193
  181. data/test/spec_session_abstract_id.rb +0 -31
  182. data/test/spec_session_abstract_session_hash.rb +0 -45
  183. data/test/spec_session_cookie.rb +0 -442
  184. data/test/spec_session_memcache.rb +0 -357
  185. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  186. data/test/spec_session_pool.rb +0 -247
  187. data/test/spec_show_exceptions.rb +0 -93
  188. data/test/spec_show_status.rb +0 -104
  189. data/test/spec_static.rb +0 -184
  190. data/test/spec_tempfile_reaper.rb +0 -64
  191. data/test/spec_thin.rb +0 -96
  192. data/test/spec_urlmap.rb +0 -237
  193. data/test/spec_utils.rb +0 -742
  194. data/test/spec_version.rb +0 -11
  195. data/test/spec_webrick.rb +0 -206
  196. data/test/static/another/index.html +0 -1
  197. data/test/static/foo.html +0 -1
  198. data/test/static/index.html +0 -1
  199. data/test/testrequest.rb +0 -78
  200. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  201. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/request.rb CHANGED
@@ -1,5 +1,8 @@
1
- require 'rack/utils'
2
- require 'rack/media_type'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+ require_relative 'media_type'
3
6
 
4
7
  module Rack
5
8
  # Rack::Request provides a convenient interface to a Rack
@@ -11,11 +14,54 @@ module Rack
11
14
  # req.params["data"]
12
15
 
13
16
  class Request
14
- SCHEME_WHITELIST = %w(https http).freeze
17
+ class << self
18
+ attr_accessor :ip_filter
19
+
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
41
+ end
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
15
61
 
16
62
  def initialize(env)
63
+ @env = env
17
64
  @params = nil
18
- super(env)
19
65
  end
20
66
 
21
67
  def params
@@ -39,6 +85,8 @@ module Rack
39
85
 
40
86
  def initialize(env)
41
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
42
90
  super()
43
91
  end
44
92
 
@@ -78,7 +126,7 @@ module Rack
78
126
  # assert_equal 'image/png,*/*', request.get_header('Accept')
79
127
  #
80
128
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
81
- def add_header key, v
129
+ def add_header(key, v)
82
130
  if v.nil?
83
131
  get_header key
84
132
  elsif has_header? key
@@ -100,7 +148,7 @@ module Rack
100
148
 
101
149
  module Helpers
102
150
  # The set of form-data media-types. Requests that do not indicate
103
- # one of the media types presents in this list will not be eligible
151
+ # one of the media types present in this list will not be eligible
104
152
  # for form-data / param parsing.
105
153
  FORM_DATA_MEDIA_TYPES = [
106
154
  'application/x-www-form-urlencoded',
@@ -108,7 +156,7 @@ module Rack
108
156
  ]
109
157
 
110
158
  # The set of media-types. Requests that do not indicate
111
- # one of the media types presents in this list will not be eligible
159
+ # one of the media types present in this list will not be eligible
112
160
  # for param parsing like soap attachments or generic multiparts
113
161
  PARSEABLE_DATA_MEDIA_TYPES = [
114
162
  'multipart/related',
@@ -119,11 +167,25 @@ module Rack
119
167
  # to include the port in a generated URI.
120
168
  DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
121
169
 
122
- HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'.freeze
123
- HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'.freeze
124
- HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'.freeze
125
- HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'.freeze
126
- HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'.freeze
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.
179
+ HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
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'
127
189
 
128
190
  def body; get_header(RACK_INPUT) end
129
191
  def script_name; get_header(SCRIPT_NAME).to_s end
@@ -137,7 +199,6 @@ module Rack
137
199
  def content_length; get_header('CONTENT_LENGTH') end
138
200
  def logger; get_header(RACK_LOGGER) end
139
201
  def user_agent; get_header('HTTP_USER_AGENT') end
140
- def multithread?; get_header(RACK_MULTITHREAD) end
141
202
 
142
203
  # the referer of the client
143
204
  def referer; get_header('HTTP_REFERER') end
@@ -159,10 +220,10 @@ module Rack
159
220
  def delete?; request_method == DELETE end
160
221
 
161
222
  # Checks the HTTP request method (or verb) to see if it was of type GET
162
- def get?; request_method == GET end
223
+ def get?; request_method == GET end
163
224
 
164
225
  # Checks the HTTP request method (or verb) to see if it was of type HEAD
165
- def head?; request_method == HEAD end
226
+ def head?; request_method == HEAD end
166
227
 
167
228
  # Checks the HTTP request method (or verb) to see if it was of type OPTIONS
168
229
  def options?; request_method == OPTIONS end
@@ -197,19 +258,50 @@ module Rack
197
258
  end
198
259
  end
199
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.
200
266
  def authority
201
- 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)
202
291
  end
203
292
 
204
293
  def cookies
205
- hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |k|
206
- 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)
207
303
  end
208
- string = get_header HTTP_COOKIE
209
304
 
210
- return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
211
- hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE
212
- set_header(RACK_REQUEST_COOKIE_STRING, string)
213
305
  hash
214
306
  end
215
307
 
@@ -222,46 +314,122 @@ module Rack
222
314
  get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
223
315
  end
224
316
 
225
- def host_with_port
226
- if forwarded = get_header(HTTP_X_FORWARDED_HOST)
227
- 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
228
327
  else
229
- get_header(HTTP_HOST) || "#{get_header(SERVER_NAME) || get_header(SERVER_ADDR)}:#{get_header(SERVER_PORT)}"
328
+ authority
230
329
  end
231
330
  end
232
331
 
332
+ # Returns a formatted host, suitable for being used in a URI.
233
333
  def host
234
- # Remove port number.
235
- host_with_port.to_s.sub(/:\d+\z/, '')
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]
236
343
  end
237
344
 
238
345
  def port
239
- if port = host_with_port.split(/:/)[1]
240
- port.to_i
241
- elsif port = get_header(HTTP_X_FORWARDED_PORT)
242
- port.to_i
243
- elsif has_header?(HTTP_X_FORWARDED_HOST)
244
- DEFAULT_PORTS[scheme]
245
- elsif has_header?(HTTP_X_FORWARDED_PROTO)
246
- DEFAULT_PORTS[get_header(HTTP_X_FORWARDED_PROTO).split(',')[0]]
247
- else
248
- get_header(SERVER_PORT).to_i
346
+ if authority = self.authority
347
+ _, _, port = split_authority(authority)
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
249
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
250
408
  end
251
409
 
252
410
  def ssl?
253
- scheme == 'https'
411
+ scheme == 'https' || scheme == 'wss'
254
412
  end
255
413
 
256
414
  def ip
257
- remote_addrs = split_ip_addresses(get_header('REMOTE_ADDR'))
258
- 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)
259
417
 
260
- return remote_addrs.first if remote_addrs.any?
418
+ unless external_addresses.empty?
419
+ return external_addresses.last
420
+ end
261
421
 
262
- forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
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
263
429
 
264
- 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
265
433
  end
266
434
 
267
435
  # The media type (type/subtype) portion of the CONTENT_TYPE header
@@ -292,16 +460,17 @@ module Rack
292
460
  end
293
461
 
294
462
  # Determine whether the request body contains form-data by checking
295
- # the request Content-Type for one of the media-types:
463
+ # the request content-type for one of the media-types:
296
464
  # "application/x-www-form-urlencoded" or "multipart/form-data". The
297
465
  # list of form-data media types can be modified through the
298
466
  # +FORM_DATA_MEDIA_TYPES+ array.
299
467
  #
300
468
  # A request body is also assumed to contain form-data when no
301
- # Content-Type header is provided and the request_method is POST.
469
+ # content-type header is provided and the request_method is POST.
302
470
  def form_data?
303
471
  type = media_type
304
472
  meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
473
+
305
474
  (meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
306
475
  end
307
476
 
@@ -316,7 +485,7 @@ module Rack
316
485
  if get_header(RACK_REQUEST_QUERY_STRING) == query_string
317
486
  get_header(RACK_REQUEST_QUERY_HASH)
318
487
  else
319
- query_hash = parse_query(query_string, '&;')
488
+ query_hash = parse_query(query_string, '&')
320
489
  set_header(RACK_REQUEST_QUERY_STRING, query_string)
321
490
  set_header(RACK_REQUEST_QUERY_HASH, query_hash)
322
491
  end
@@ -337,17 +506,16 @@ module Rack
337
506
 
338
507
  # Fix for Safari Ajax postings that always append \0
339
508
  # form_vars.sub!(/\0\z/, '') # performance replacement:
340
- form_vars.slice!(-1) if form_vars[-1] == ?\0
509
+ form_vars.slice!(-1) if form_vars.end_with?("\0")
341
510
 
342
511
  set_header RACK_REQUEST_FORM_VARS, form_vars
343
512
  set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
344
-
345
- get_header(RACK_INPUT).rewind
346
513
  end
347
514
  set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
348
515
  get_header RACK_REQUEST_FORM_HASH
349
516
  else
350
- {}
517
+ set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
518
+ set_header(RACK_REQUEST_FORM_HASH, {})
351
519
  end
352
520
  end
353
521
 
@@ -356,8 +524,6 @@ module Rack
356
524
  # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
357
525
  def params
358
526
  self.GET.merge(self.POST)
359
- rescue EOFError
360
- self.GET.dup
361
527
  end
362
528
 
363
529
  # Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
@@ -386,13 +552,12 @@ module Rack
386
552
  #
387
553
  # <tt>env['rack.input']</tt> is not touched.
388
554
  def delete_param(k)
389
- [ self.POST.delete(k), self.GET.delete(k) ].compact.first
555
+ post_value, get_value = self.POST.delete(k), self.GET.delete(k)
556
+ post_value || get_value
390
557
  end
391
558
 
392
559
  def base_url
393
- url = "#{scheme}://#{host}"
394
- url << ":#{port}" if port != DEFAULT_PORTS[scheme]
395
- url
560
+ "#{scheme}://#{host_with_port}"
396
561
  end
397
562
 
398
563
  # Tries to return a remake of the original request URL as a string.
@@ -417,14 +582,12 @@ module Rack
417
582
  end
418
583
 
419
584
  def trusted_proxy?(ip)
420
- ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
585
+ Rack::Request.ip_filter.call(ip)
421
586
  end
422
587
 
423
588
  # shortcut for <tt>request.params[key]</tt>
424
589
  def [](key)
425
- if $VERBOSE
426
- warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
427
- end
590
+ warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
428
591
 
429
592
  params[key.to_s]
430
593
  end
@@ -433,9 +596,7 @@ module Rack
433
596
  #
434
597
  # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
435
598
  def []=(key, value)
436
- if $VERBOSE
437
- warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
438
- end
599
+ warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
439
600
 
440
601
  params[key.to_s] = value
441
602
  end
@@ -449,6 +610,20 @@ module Rack
449
610
 
450
611
  def default_session; {}; end
451
612
 
613
+ # Assist with compatibility when processing `X-Forwarded-For`.
614
+ def wrap_ipv6(host)
615
+ # Even thought IPv6 addresses should be wrapped in square brackets,
616
+ # sometimes this is not done in various legacy/underspecified headers.
617
+ # So we try to fix this situation for compatibility reasons.
618
+
619
+ # Try to detect IPv6 addresses which aren't escaped yet:
620
+ if !host.start_with?('[') && host.count(':') > 1
621
+ "[#{host}]"
622
+ else
623
+ host
624
+ end
625
+ end
626
+
452
627
  def parse_http_accept_header(header)
453
628
  header.to_s.split(/\s*,\s*/).map do |part|
454
629
  attribute, parameters = part.split(/\s*;\s*/, 2)
@@ -460,11 +635,16 @@ module Rack
460
635
  end
461
636
  end
462
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
+
463
643
  def query_parser
464
644
  Utils.default_query_parser
465
645
  end
466
646
 
467
- def parse_query(qs, d='&')
647
+ def parse_query(qs, d = '&')
468
648
  query_parser.parse_nested_query(qs, d)
469
649
  end
470
650
 
@@ -472,29 +652,106 @@ module Rack
472
652
  Rack::Multipart.extract_multipart(self, query_parser)
473
653
  end
474
654
 
475
- def split_ip_addresses(ip_addresses)
476
- ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
655
+ def split_header(value)
656
+ value ? value.strip.split(/[,\s]+/) : []
657
+ end
658
+
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
685
+ (?<host>
686
+ # Match IPv6 as a string of hex digits and colons in square brackets
687
+ \[(?<address>#{ipv6})\]
688
+ |
689
+ # Match any other printable string (except square brackets) as a hostname
690
+ (?<address>[[[:graph:]&&[^\[\]]]]*?)
691
+ )
692
+ (:(?<port>\d+))?
693
+ \z
694
+ /x
695
+
696
+ private_constant :AUTHORITY
697
+
698
+ def split_authority(authority)
699
+ return [] if authority.nil?
700
+ return [] unless match = AUTHORITY.match(authority)
701
+ return match[:host], match[:address], match[:port]&.to_i
477
702
  end
478
703
 
479
704
  def reject_trusted_ip_addresses(ip_addresses)
480
705
  ip_addresses.reject { |ip| trusted_proxy?(ip) }
481
706
  end
482
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
483
713
  def forwarded_scheme
484
- scheme_headers = [
485
- get_header(HTTP_X_FORWARDED_SCHEME),
486
- get_header(HTTP_X_FORWARDED_PROTO).to_s.split(',')[0]
487
- ]
488
-
489
- scheme_headers.each do |header|
490
- return header if SCHEME_WHITELIST.include?(header)
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
491
732
  end
492
733
 
493
734
  nil
494
735
  end
736
+
737
+ def allowed_scheme(header)
738
+ header if ALLOWED_SCHEMES.include?(header)
739
+ end
740
+
741
+ def forwarded_priority
742
+ Request.forwarded_priority
743
+ end
744
+
745
+ def x_forwarded_proto_priority
746
+ Request.x_forwarded_proto_priority
747
+ end
495
748
  end
496
749
 
497
750
  include Env
498
751
  include Helpers
499
752
  end
500
753
  end
754
+
755
+ # :nocov:
756
+ require_relative 'multipart' unless defined?(Rack::Multipart)
757
+ # :nocov: