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