rack 3.0.18 → 3.1.18
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +198 -8
- data/CONTRIBUTING.md +11 -9
- data/README.md +42 -15
- data/SPEC.rdoc +38 -13
- data/lib/rack/auth/basic.rb +1 -2
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/builder.rb +23 -10
- data/lib/rack/cascade.rb +0 -3
- data/lib/rack/constants.rb +3 -0
- data/lib/rack/headers.rb +86 -2
- data/lib/rack/lint.rb +118 -34
- data/lib/rack/logger.rb +2 -1
- data/lib/rack/media_type.rb +8 -3
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +10 -15
- data/lib/rack/mock_response.rb +14 -16
- data/lib/rack/multipart/parser.rb +178 -66
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +16 -68
- data/lib/rack/request.rb +44 -22
- data/lib/rack/response.rb +28 -20
- data/lib/rack/sendfile.rb +50 -20
- data/lib/rack/show_exceptions.rb +6 -2
- data/lib/rack/utils.rb +71 -98
- data/lib/rack/version.rb +1 -14
- data/lib/rack.rb +1 -3
- metadata +5 -11
- data/lib/rack/auth/digest/md5.rb +0 -1
- data/lib/rack/auth/digest/nonce.rb +0 -1
- data/lib/rack/auth/digest/params.rb +0 -1
- data/lib/rack/auth/digest/request.rb +0 -1
- data/lib/rack/auth/digest.rb +0 -256
- data/lib/rack/chunked.rb +0 -120
- data/lib/rack/file.rb +0 -9
data/lib/rack/request.rb
CHANGED
|
@@ -482,9 +482,14 @@ module Rack
|
|
|
482
482
|
|
|
483
483
|
# Returns the data received in the query string.
|
|
484
484
|
def GET
|
|
485
|
-
|
|
485
|
+
rr_query_string = get_header(RACK_REQUEST_QUERY_STRING)
|
|
486
|
+
query_string = self.query_string
|
|
487
|
+
if rr_query_string == query_string
|
|
486
488
|
get_header(RACK_REQUEST_QUERY_HASH)
|
|
487
489
|
else
|
|
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
|
|
488
493
|
query_hash = parse_query(query_string, '&')
|
|
489
494
|
set_header(RACK_REQUEST_QUERY_STRING, query_string)
|
|
490
495
|
set_header(RACK_REQUEST_QUERY_HASH, query_hash)
|
|
@@ -505,9 +510,12 @@ module Rack
|
|
|
505
510
|
|
|
506
511
|
# If the form hash was already memoized:
|
|
507
512
|
if form_hash = get_header(RACK_REQUEST_FORM_HASH)
|
|
513
|
+
form_input = get_header(RACK_REQUEST_FORM_INPUT)
|
|
508
514
|
# And it was memoized from the same input:
|
|
509
|
-
if
|
|
515
|
+
if form_input.equal?(rack_input)
|
|
510
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
|
|
511
519
|
end
|
|
512
520
|
end
|
|
513
521
|
|
|
@@ -516,8 +524,14 @@ module Rack
|
|
|
516
524
|
set_header RACK_REQUEST_FORM_INPUT, nil
|
|
517
525
|
set_header(RACK_REQUEST_FORM_HASH, {})
|
|
518
526
|
elsif form_data? || parseable_data?
|
|
519
|
-
|
|
520
|
-
|
|
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) || ''
|
|
521
535
|
|
|
522
536
|
# Fix for Safari Ajax postings that always append \0
|
|
523
537
|
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
|
@@ -605,24 +619,10 @@ module Rack
|
|
|
605
619
|
Rack::Request.ip_filter.call(ip)
|
|
606
620
|
end
|
|
607
621
|
|
|
608
|
-
# shortcut for <tt>request.params[key]</tt>
|
|
609
|
-
def [](key)
|
|
610
|
-
warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead", uplevel: 1)
|
|
611
|
-
|
|
612
|
-
params[key.to_s]
|
|
613
|
-
end
|
|
614
|
-
|
|
615
|
-
# shortcut for <tt>request.params[key] = value</tt>
|
|
616
|
-
#
|
|
617
|
-
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
|
618
|
-
def []=(key, value)
|
|
619
|
-
warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead", uplevel: 1)
|
|
620
|
-
|
|
621
|
-
params[key.to_s] = value
|
|
622
|
-
end
|
|
623
|
-
|
|
624
622
|
# like Hash#values_at
|
|
625
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
|
+
|
|
626
626
|
keys.map { |key| params[key] }
|
|
627
627
|
end
|
|
628
628
|
|
|
@@ -645,14 +645,26 @@ module Rack
|
|
|
645
645
|
end
|
|
646
646
|
|
|
647
647
|
def parse_http_accept_header(header)
|
|
648
|
-
|
|
649
|
-
|
|
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!
|
|
650
658
|
quality = 1.0
|
|
651
659
|
if parameters and /\Aq=([\d.]+)/ =~ parameters
|
|
652
660
|
quality = $1.to_f
|
|
653
661
|
end
|
|
654
662
|
[attribute, quality]
|
|
655
663
|
end
|
|
664
|
+
|
|
665
|
+
parts.compact!
|
|
666
|
+
|
|
667
|
+
parts
|
|
656
668
|
end
|
|
657
669
|
|
|
658
670
|
# Get an array of values set in the RFC 7239 `Forwarded` request header.
|
|
@@ -672,6 +684,16 @@ module Rack
|
|
|
672
684
|
Rack::Multipart.extract_multipart(self, query_parser)
|
|
673
685
|
end
|
|
674
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
|
+
|
|
675
697
|
def split_header(value)
|
|
676
698
|
value ? value.strip.split(/[,\s]+/) : []
|
|
677
699
|
end
|
data/lib/rack/response.rb
CHANGED
|
@@ -31,13 +31,6 @@ module Rack
|
|
|
31
31
|
attr_accessor :length, :status, :body
|
|
32
32
|
attr_reader :headers
|
|
33
33
|
|
|
34
|
-
# Deprecated, use headers instead.
|
|
35
|
-
def header
|
|
36
|
-
warn 'Rack::Response#header is deprecated and will be removed in Rack 3.1, use #headers instead', uplevel: 1
|
|
37
|
-
|
|
38
|
-
headers
|
|
39
|
-
end
|
|
40
|
-
|
|
41
34
|
# Initialize the response object with the specified +body+, +status+
|
|
42
35
|
# and +headers+.
|
|
43
36
|
#
|
|
@@ -62,7 +55,7 @@ module Rack
|
|
|
62
55
|
@status = status.to_i
|
|
63
56
|
|
|
64
57
|
unless headers.is_a?(Hash)
|
|
65
|
-
|
|
58
|
+
raise ArgumentError, "Headers must be a Hash!"
|
|
66
59
|
end
|
|
67
60
|
|
|
68
61
|
@headers = Headers.new
|
|
@@ -79,7 +72,8 @@ module Rack
|
|
|
79
72
|
if body.nil?
|
|
80
73
|
@body = []
|
|
81
74
|
@buffered = true
|
|
82
|
-
|
|
75
|
+
# Body is unspecified - it may be a buffered response, or it may be a HEAD response.
|
|
76
|
+
@length = nil
|
|
83
77
|
elsif body.respond_to?(:to_str)
|
|
84
78
|
@body = [body]
|
|
85
79
|
@buffered = true
|
|
@@ -87,7 +81,7 @@ module Rack
|
|
|
87
81
|
else
|
|
88
82
|
@body = body
|
|
89
83
|
@buffered = nil # undetermined as of yet.
|
|
90
|
-
@length =
|
|
84
|
+
@length = nil
|
|
91
85
|
end
|
|
92
86
|
|
|
93
87
|
yield self if block_given?
|
|
@@ -106,7 +100,7 @@ module Rack
|
|
|
106
100
|
# The response body is an enumerable body and it is not allowed to have an entity body.
|
|
107
101
|
@body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
|
|
108
102
|
end
|
|
109
|
-
|
|
103
|
+
|
|
110
104
|
# Generate a response array consistent with the requirements of the SPEC.
|
|
111
105
|
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
|
112
106
|
# which is suitable to be returned from the middleware `#call(env)` method.
|
|
@@ -118,9 +112,14 @@ module Rack
|
|
|
118
112
|
return [@status, @headers, []]
|
|
119
113
|
else
|
|
120
114
|
if block_given?
|
|
115
|
+
# We don't add the content-length here as the user has provided a block that can #write additional chunks to the body.
|
|
121
116
|
@block = block
|
|
122
117
|
return [@status, @headers, self]
|
|
123
118
|
else
|
|
119
|
+
# If we know the length of the body, set the content-length header... except if we are chunked? which is a legacy special case where the body might already be encoded and thus the actual encoded body length and the content-length are likely to be different.
|
|
120
|
+
if @length && !chunked?
|
|
121
|
+
@headers[CONTENT_LENGTH] = @length.to_s
|
|
122
|
+
end
|
|
124
123
|
return [@status, @headers, @body]
|
|
125
124
|
end
|
|
126
125
|
end
|
|
@@ -138,7 +137,9 @@ module Rack
|
|
|
138
137
|
end
|
|
139
138
|
end
|
|
140
139
|
|
|
141
|
-
# Append to
|
|
140
|
+
# Append a chunk to the response body.
|
|
141
|
+
#
|
|
142
|
+
# Converts the response into a buffered response if it wasn't already.
|
|
142
143
|
#
|
|
143
144
|
# NOTE: Do not mix #write and direct #body access!
|
|
144
145
|
#
|
|
@@ -320,29 +321,34 @@ module Rack
|
|
|
320
321
|
|
|
321
322
|
protected
|
|
322
323
|
|
|
324
|
+
# Convert the body of this response into an internally buffered Array if possible.
|
|
325
|
+
#
|
|
326
|
+
# `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
|
|
327
|
+
# * `nil` - The body has not been buffered yet.
|
|
328
|
+
# * `true` - The body is buffered as an Array instance.
|
|
329
|
+
# * `false` - The body is not buffered and cannot be buffered.
|
|
330
|
+
#
|
|
331
|
+
# @return [Boolean] whether the body is buffered as an Array instance.
|
|
323
332
|
def buffered_body!
|
|
324
333
|
if @buffered.nil?
|
|
325
334
|
if @body.is_a?(Array)
|
|
326
335
|
# The user supplied body was an array:
|
|
327
336
|
@body = @body.compact
|
|
328
|
-
@body.
|
|
329
|
-
@length += part.to_s.bytesize
|
|
330
|
-
end
|
|
331
|
-
|
|
337
|
+
@length = @body.sum{|part| part.bytesize}
|
|
332
338
|
@buffered = true
|
|
333
339
|
elsif @body.respond_to?(:each)
|
|
334
340
|
# Turn the user supplied body into a buffered array:
|
|
335
341
|
body = @body
|
|
336
342
|
@body = Array.new
|
|
343
|
+
@buffered = true
|
|
337
344
|
|
|
338
345
|
body.each do |part|
|
|
339
346
|
@writer.call(part.to_s)
|
|
340
347
|
end
|
|
341
348
|
|
|
342
349
|
body.close if body.respond_to?(:close)
|
|
343
|
-
|
|
344
|
-
@buffered = true
|
|
345
350
|
else
|
|
351
|
+
# We don't know how to buffer the user-supplied body:
|
|
346
352
|
@buffered = false
|
|
347
353
|
end
|
|
348
354
|
end
|
|
@@ -351,11 +357,13 @@ module Rack
|
|
|
351
357
|
end
|
|
352
358
|
|
|
353
359
|
def append(chunk)
|
|
360
|
+
chunk = chunk.dup unless chunk.frozen?
|
|
354
361
|
@body << chunk
|
|
355
362
|
|
|
356
|
-
|
|
363
|
+
if @length
|
|
357
364
|
@length += chunk.bytesize
|
|
358
|
-
|
|
365
|
+
elsif @buffered
|
|
366
|
+
@length = chunk.bytesize
|
|
359
367
|
end
|
|
360
368
|
|
|
361
369
|
return chunk
|
data/lib/rack/sendfile.rb
CHANGED
|
@@ -16,21 +16,21 @@ module Rack
|
|
|
16
16
|
# delivery code.
|
|
17
17
|
#
|
|
18
18
|
# In order to take advantage of this middleware, the response body must
|
|
19
|
-
# respond to +to_path+ and the request must include an x-sendfile-type
|
|
19
|
+
# respond to +to_path+ and the request must include an `x-sendfile-type`
|
|
20
20
|
# header. Rack::Files and other components implement +to_path+ so there's
|
|
21
|
-
# rarely anything you need to do in your application. The x-sendfile-type
|
|
21
|
+
# rarely anything you need to do in your application. The `x-sendfile-type`
|
|
22
22
|
# header is typically set in your web servers configuration. The following
|
|
23
23
|
# sections attempt to document
|
|
24
24
|
#
|
|
25
25
|
# === Nginx
|
|
26
26
|
#
|
|
27
|
-
# Nginx supports the x-accel-redirect header. This is similar to x-sendfile
|
|
27
|
+
# Nginx supports the `x-accel-redirect` header. This is similar to `x-sendfile`
|
|
28
28
|
# but requires parts of the filesystem to be mapped into a private URL
|
|
29
29
|
# hierarchy.
|
|
30
30
|
#
|
|
31
31
|
# The following example shows the Nginx configuration required to create
|
|
32
|
-
# a private "/files/" area, enable x-accel-redirect
|
|
33
|
-
# x-
|
|
32
|
+
# a private "/files/" area, enable `x-accel-redirect`, and pass the special
|
|
33
|
+
# `x-accel-mapping` header to the backend:
|
|
34
34
|
#
|
|
35
35
|
# location ~ /files/(.*) {
|
|
36
36
|
# internal;
|
|
@@ -44,24 +44,29 @@ module Rack
|
|
|
44
44
|
# proxy_set_header X-Real-IP $remote_addr;
|
|
45
45
|
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
46
46
|
#
|
|
47
|
-
# proxy_set_header x-sendfile-type x-accel-redirect;
|
|
48
47
|
# proxy_set_header x-accel-mapping /var/www/=/files/;
|
|
49
48
|
#
|
|
50
49
|
# proxy_pass http://127.0.0.1:8080/;
|
|
51
50
|
# }
|
|
52
51
|
#
|
|
53
|
-
#
|
|
54
|
-
# The x-accel-mapping header should specify the location on the file system,
|
|
52
|
+
# The `x-accel-mapping` header should specify the location on the file system,
|
|
55
53
|
# followed by an equals sign (=), followed name of the private URL pattern
|
|
56
54
|
# that it maps to. The middleware performs a simple substitution on the
|
|
57
55
|
# resulting path.
|
|
58
56
|
#
|
|
57
|
+
# To enable `x-accel-redirect`, you must configure the middleware explicitly:
|
|
58
|
+
#
|
|
59
|
+
# use Rack::Sendfile, "x-accel-redirect"
|
|
60
|
+
#
|
|
61
|
+
# For security reasons, the `x-sendfile-type` header from requests is ignored.
|
|
62
|
+
# The sendfile variation must be set via the middleware constructor.
|
|
63
|
+
#
|
|
59
64
|
# See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
|
|
60
65
|
#
|
|
61
66
|
# === lighttpd
|
|
62
67
|
#
|
|
63
|
-
# Lighttpd has supported some variation of the x-sendfile header for some
|
|
64
|
-
# time, although only recent version support x-sendfile in a reverse proxy
|
|
68
|
+
# Lighttpd has supported some variation of the `x-sendfile` header for some
|
|
69
|
+
# time, although only recent version support `x-sendfile` in a reverse proxy
|
|
65
70
|
# configuration.
|
|
66
71
|
#
|
|
67
72
|
# $HTTP["host"] == "example.com" {
|
|
@@ -83,7 +88,7 @@ module Rack
|
|
|
83
88
|
#
|
|
84
89
|
# === Apache
|
|
85
90
|
#
|
|
86
|
-
# x-sendfile is supported under Apache 2.x using a separate module:
|
|
91
|
+
# `x-sendfile` is supported under Apache 2.x using a separate module:
|
|
87
92
|
#
|
|
88
93
|
# https://tn123.org/mod_xsendfile/
|
|
89
94
|
#
|
|
@@ -97,16 +102,28 @@ module Rack
|
|
|
97
102
|
# === Mapping parameter
|
|
98
103
|
#
|
|
99
104
|
# The third parameter allows for an overriding extension of the
|
|
100
|
-
# x-accel-mapping header. Mappings should be provided in tuples of internal to
|
|
105
|
+
# `x-accel-mapping` header. Mappings should be provided in tuples of internal to
|
|
101
106
|
# external. The internal values may contain regular expression syntax, they
|
|
102
107
|
# will be matched with case indifference.
|
|
108
|
+
#
|
|
109
|
+
# When `x-accel-redirect` is explicitly enabled via the variation parameter,
|
|
110
|
+
# and no application-level mappings are provided, the middleware will read
|
|
111
|
+
# the `x-accel-mapping` header from the proxy. This allows nginx to control
|
|
112
|
+
# the path mapping without requiring application-level configuration.
|
|
113
|
+
#
|
|
114
|
+
# === Security
|
|
115
|
+
#
|
|
116
|
+
# For security reasons, the `x-sendfile-type` header from HTTP requests is
|
|
117
|
+
# ignored. The sendfile variation must be explicitly configured via the
|
|
118
|
+
# middleware constructor to prevent information disclosure vulnerabilities
|
|
119
|
+
# where attackers could bypass proxy restrictions.
|
|
103
120
|
|
|
104
121
|
class Sendfile
|
|
105
122
|
def initialize(app, variation = nil, mappings = [])
|
|
106
123
|
@app = app
|
|
107
124
|
@variation = variation
|
|
108
125
|
@mappings = mappings.map do |internal, external|
|
|
109
|
-
[
|
|
126
|
+
[/\A#{internal}/i, external]
|
|
110
127
|
end
|
|
111
128
|
end
|
|
112
129
|
|
|
@@ -145,22 +162,35 @@ module Rack
|
|
|
145
162
|
end
|
|
146
163
|
|
|
147
164
|
private
|
|
165
|
+
|
|
148
166
|
def variation(env)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
167
|
+
# Note: HTTP_X_SENDFILE_TYPE is intentionally NOT read for security reasons.
|
|
168
|
+
# Attackers could use this header to enable x-accel-redirect and bypass proxy restrictions.
|
|
169
|
+
@variation || env['sendfile.type']
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def x_accel_mapping(env)
|
|
173
|
+
# Only allow header when:
|
|
174
|
+
# 1. `x-accel-redirect` is explicitly enabled via constructor.
|
|
175
|
+
# 2. No application-level mappings are configured.
|
|
176
|
+
return nil unless @variation =~ /x-accel-redirect/i
|
|
177
|
+
return nil if @mappings.any?
|
|
178
|
+
|
|
179
|
+
env['HTTP_X_ACCEL_MAPPING']
|
|
152
180
|
end
|
|
153
181
|
|
|
154
182
|
def map_accel_path(env, path)
|
|
155
183
|
if mapping = @mappings.find { |internal, _| internal =~ path }
|
|
156
|
-
path.sub(*mapping)
|
|
157
|
-
elsif mapping = env
|
|
184
|
+
return path.sub(*mapping)
|
|
185
|
+
elsif mapping = x_accel_mapping(env)
|
|
186
|
+
# Safe to use header: explicit config + no app mappings:
|
|
158
187
|
mapping.split(',').map(&:strip).each do |m|
|
|
159
188
|
internal, external = m.split('=', 2).map(&:strip)
|
|
160
|
-
new_path = path.sub(
|
|
189
|
+
new_path = path.sub(/\A#{internal}/i, external)
|
|
161
190
|
return new_path unless path == new_path
|
|
162
191
|
end
|
|
163
|
-
|
|
192
|
+
|
|
193
|
+
return path
|
|
164
194
|
end
|
|
165
195
|
end
|
|
166
196
|
end
|
data/lib/rack/show_exceptions.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'ostruct'
|
|
4
3
|
require 'erb'
|
|
5
4
|
|
|
6
5
|
require_relative 'constants'
|
|
@@ -19,6 +18,11 @@ module Rack
|
|
|
19
18
|
class ShowExceptions
|
|
20
19
|
CONTEXT = 7
|
|
21
20
|
|
|
21
|
+
Frame = Struct.new(:filename, :lineno, :function,
|
|
22
|
+
:pre_context_lineno, :pre_context,
|
|
23
|
+
:context_line, :post_context_lineno,
|
|
24
|
+
:post_context)
|
|
25
|
+
|
|
22
26
|
def initialize(app)
|
|
23
27
|
@app = app
|
|
24
28
|
end
|
|
@@ -79,7 +83,7 @@ module Rack
|
|
|
79
83
|
# This double assignment is to prevent an "unused variable" warning.
|
|
80
84
|
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
|
81
85
|
frames = frames = exception.backtrace.map { |line|
|
|
82
|
-
frame =
|
|
86
|
+
frame = Frame.new
|
|
83
87
|
if line =~ /(.*?):(\d+)(:in `(.*)')?/
|
|
84
88
|
frame.filename = $1
|
|
85
89
|
frame.lineno = $2.to_i
|