rack 3.2.5 → 3.2.6
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 +33 -1
- data/lib/rack/directory.rb +1 -1
- data/lib/rack/files.rb +1 -1
- data/lib/rack/multipart/parser.rb +43 -2
- data/lib/rack/request.rb +2 -2
- data/lib/rack/sendfile.rb +2 -2
- data/lib/rack/static.rb +7 -3
- data/lib/rack/utils.rb +107 -15
- data/lib/rack/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37024c5110b365f1dd0b5627dd903edf42344fa9ad98a99af672e220d2370288
|
|
4
|
+
data.tar.gz: d0bd60323a75ff2828b33713963dcb5769898abc82a7ab3c2b9ac4ad36550b88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 825e73fe75136333217b5f395f25245fe295fa4749a8068711011ba5c47350043941e4a4319837015e211f6cbd29952970fdc79f5deb0364c76d995c8d9de457
|
|
7
|
+
data.tar.gz: 57b8333f46704c6aead2d24ab214ab99ddb8296f93cc62298385d20bf4bffad829fae3b5e3a658e399d1628ab355a9abd69e4e1f2d08971740547b6ff1425f5b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## [3.2.6] - 2026-04-01
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
|
|
9
|
+
- [CVE-2026-34763](https://github.com/advisories/GHSA-7mqq-6cf9-v2qp) Root directory disclosure via unescaped regex interpolation in `Rack::Directory`.
|
|
10
|
+
- [CVE-2026-34230](https://github.com/advisories/GHSA-v569-hp3g-36wr) Avoid O(n^2) algorithm in `Rack::Utils.select_best_encoding` which could lead to denial of service.
|
|
11
|
+
- [CVE-2026-32762](https://github.com/advisories/GHSA-qfgr-crr9-7r49) Forwarded header semicolon injection enables Host and Scheme spoofing.
|
|
12
|
+
- [CVE-2026-26961](https://github.com/advisories/GHSA-vgpv-f759-9wx3) Raise error for multipart requests with multiple boundary parameters.
|
|
13
|
+
- [CVE-2026-34786](https://github.com/advisories/GHSA-q4qf-9j86-f5mh) `Rack::Static` `header_rules` bypass via URL-encoded path mismatch.
|
|
14
|
+
- [CVE-2026-34831](https://github.com/advisories/GHSA-q2ww-5357-x388) `Content-Length` mismatch in `Rack::Files` error responses.
|
|
15
|
+
- [CVE-2026-34826](https://github.com/advisories/GHSA-x8cg-fq8g-mxfx) Multipart byte range processing allows denial of service via excessive overlapping ranges.
|
|
16
|
+
- [CVE-2026-34835](https://github.com/advisories/GHSA-g2pf-xv49-m2h5) `Rack::Request` accepts invalid Host characters, enabling host allowlist bypass.
|
|
17
|
+
- [CVE-2026-34830](https://github.com/advisories/GHSA-qv7j-4883-hwh7) `Rack::Sendfile` header-based `X-Accel-Mapping` regex injection enables unauthorized `X-Accel-Redirect`.
|
|
18
|
+
- [CVE-2026-34785](https://github.com/advisories/GHSA-h2jq-g4cq-5ppq) `Rack::Static` prefix matching can expose unintended files under the static root.
|
|
19
|
+
- [CVE-2026-34829](https://github.com/advisories/GHSA-8vqr-qjwx-82mw) Multipart parsing without `Content-Length` header allows unbounded chunked file uploads.
|
|
20
|
+
- [CVE-2026-34827](https://github.com/advisories/GHSA-v6x5-cg8r-vv6x) Quadratic-time multipart header parsing allows denial of service via escape-heavy quoted parameters.
|
|
21
|
+
- [CVE-2026-26962](https://github.com/advisories/GHSA-rx22-g9mx-qrhv) Improper unfolding of folded multipart headers preserves CRLF in parsed parameter values.
|
|
22
|
+
|
|
23
|
+
## [3.2.5] - 2026-02-16
|
|
6
24
|
|
|
7
25
|
### Security
|
|
8
26
|
|
|
@@ -91,6 +109,13 @@ This release continues Rack's evolution toward a cleaner, more efficient foundat
|
|
|
91
109
|
- `SERVER_NAME` and `HTTP_HOST` are now more strictly validated according to the relevant specifications. ([#2298](https://github.com/rack/rack/pull/2298), [@ioquatix])
|
|
92
110
|
- `Rack::Lint` now disallows `PATH_INFO="" SCRIPT_NAME=""`. ([#2298](https://github.com/rack/rack/issues/2307), [@jeremyevans])
|
|
93
111
|
|
|
112
|
+
## [3.1.20] - 2026-02-16
|
|
113
|
+
|
|
114
|
+
### Security
|
|
115
|
+
|
|
116
|
+
- [CVE-2026-25500](https://github.com/advisories/GHSA-whrj-4476-wvmp) XSS injection via malicious filename in `Rack::Directory`.
|
|
117
|
+
- [CVE-2026-22860](https://github.com/advisories/GHSA-mxw3-3hh2-x2mh) Directory traversal via root prefix bypass in `Rack::Directory`.
|
|
118
|
+
|
|
94
119
|
## [3.1.19] - 2025-11-03
|
|
95
120
|
|
|
96
121
|
### Fixed
|
|
@@ -481,6 +506,13 @@ This release introduces major improvements to Rack, including enhanced support f
|
|
|
481
506
|
- Fix multipart filename generation for filenames that contain spaces. Encode spaces as "%20" instead of "+" which will be decoded properly by the multipart parser. ([#1736](https://github.com/rack/rack/pull/1645), [@muirdm](https://github.com/muirdm))
|
|
482
507
|
- `Rack::Request#scheme` returns `ws` or `wss` when one of the `X-Forwarded-Scheme` / `X-Forwarded-Proto` headers is set to `ws` or `wss`, respectively. ([#1730](https://github.com/rack/rack/issues/1730), [@erwanst](https://github.com/erwanst))
|
|
483
508
|
|
|
509
|
+
## [2.2.22] - 2026-02-16
|
|
510
|
+
|
|
511
|
+
### Security
|
|
512
|
+
|
|
513
|
+
- [CVE-2026-25500](https://github.com/advisories/GHSA-whrj-4476-wvmp) XSS injection via malicious filename in `Rack::Directory`.
|
|
514
|
+
- [CVE-2026-22860](https://github.com/advisories/GHSA-mxw3-3hh2-x2mh) Directory traversal via root prefix bypass in `Rack::Directory`.
|
|
515
|
+
|
|
484
516
|
## [2.2.21] - 2025-11-03
|
|
485
517
|
|
|
486
518
|
### Fixed
|
data/lib/rack/directory.rb
CHANGED
|
@@ -51,7 +51,7 @@ table { width:100%%; }
|
|
|
51
51
|
class DirectoryBody < Struct.new(:root, :path, :files)
|
|
52
52
|
# Yield strings for each part of the directory entry
|
|
53
53
|
def each
|
|
54
|
-
show_path = Utils.escape_html(path.sub(
|
|
54
|
+
show_path = Utils.escape_html(path.sub(/\A#{Regexp.escape(root)}/, ''))
|
|
55
55
|
yield(DIR_PAGE_HEADER % [ show_path, show_path ])
|
|
56
56
|
|
|
57
57
|
unless path.chomp('/') == root
|
data/lib/rack/files.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Rack
|
|
|
33
33
|
EOL = "\r\n"
|
|
34
34
|
FWS = /[ \t]+(?:\r\n[ \t]+)?/ # whitespace with optional folding
|
|
35
35
|
HEADER_VALUE = "(?:[^\r\n]|\r\n[ \t])*" # anything but a non-folding CRLF
|
|
36
|
-
MULTIPART = %r|\Amultipart
|
|
36
|
+
MULTIPART = %r|\Amultipart/.*?boundary(\s*)=\"?([^\";,]+)\"?|ni
|
|
37
37
|
MULTIPART_CONTENT_TYPE = /^Content-Type:#{FWS}?(#{HEADER_VALUE})/ni
|
|
38
38
|
MULTIPART_CONTENT_DISPOSITION = /^Content-Disposition:#{FWS}?(#{HEADER_VALUE})/ni
|
|
39
39
|
MULTIPART_CONTENT_ID = /^Content-ID:#{FWS}?(#{HEADER_VALUE})/ni
|
|
@@ -80,6 +80,13 @@ module Rack
|
|
|
80
80
|
BUFFERED_UPLOAD_BYTESIZE_LIMIT = env_int.call("RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT", 16 * 1024 * 1024)
|
|
81
81
|
private_constant :BUFFERED_UPLOAD_BYTESIZE_LIMIT
|
|
82
82
|
|
|
83
|
+
bytesize_limit = env_int.call("RACK_MULTIPART_PARSER_BYTESIZE_LIMIT", 10 * 1024 * 1024 * 1024)
|
|
84
|
+
PARSER_BYTESIZE_LIMIT = bytesize_limit > 0 ? bytesize_limit : nil
|
|
85
|
+
private_constant :PARSER_BYTESIZE_LIMIT
|
|
86
|
+
|
|
87
|
+
CONTENT_DISPOSITION_QUOTED_ESCAPES_LIMIT = env_int.call("RACK_MULTIPART_CONTENT_DISPOSITION_QUOTED_ESCAPES_LIMIT", 8 * 1024)
|
|
88
|
+
private_constant :CONTENT_DISPOSITION_QUOTED_ESCAPES_LIMIT
|
|
89
|
+
|
|
83
90
|
class BoundedIO # :nodoc:
|
|
84
91
|
def initialize(io, content_length)
|
|
85
92
|
@io = io
|
|
@@ -116,7 +123,15 @@ module Rack
|
|
|
116
123
|
return unless content_type
|
|
117
124
|
data = content_type.match(MULTIPART)
|
|
118
125
|
return unless data
|
|
119
|
-
|
|
126
|
+
|
|
127
|
+
unless data[1].empty?
|
|
128
|
+
raise Error, "whitespace between boundary parameter name and equal sign"
|
|
129
|
+
end
|
|
130
|
+
if data.post_match.match?(/boundary\s*=/i)
|
|
131
|
+
raise BoundaryTooLongError, "multiple boundary parameters found in multipart content type"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
data[2]
|
|
120
135
|
end
|
|
121
136
|
|
|
122
137
|
def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
|
|
@@ -125,6 +140,10 @@ module Rack
|
|
|
125
140
|
boundary = parse_boundary content_type
|
|
126
141
|
return EMPTY unless boundary
|
|
127
142
|
|
|
143
|
+
if PARSER_BYTESIZE_LIMIT && content_length && content_length > PARSER_BYTESIZE_LIMIT
|
|
144
|
+
raise Error, "multipart Content-Length #{content_length} exceeds limit of #{PARSER_BYTESIZE_LIMIT} bytes"
|
|
145
|
+
end
|
|
146
|
+
|
|
128
147
|
if boundary.length > 70
|
|
129
148
|
# RFC 1521 Section 7.2.1 imposes a 70 character maximum for the boundary.
|
|
130
149
|
# Most clients use no more than 55 characters.
|
|
@@ -241,6 +260,8 @@ module Rack
|
|
|
241
260
|
@mime_index = 0
|
|
242
261
|
@body_retained = nil
|
|
243
262
|
@retained_size = 0
|
|
263
|
+
@total_bytes_read = (0 if PARSER_BYTESIZE_LIMIT)
|
|
264
|
+
@content_disposition_quoted_escapes = 0
|
|
244
265
|
@collector = Collector.new tempfile
|
|
245
266
|
|
|
246
267
|
@sbuf = StringScanner.new("".dup)
|
|
@@ -252,6 +273,7 @@ module Rack
|
|
|
252
273
|
end
|
|
253
274
|
|
|
254
275
|
def parse(io)
|
|
276
|
+
@total_bytes_read &&= nil if io.is_a?(BoundedIO)
|
|
255
277
|
outbuf = String.new
|
|
256
278
|
read_data(io, outbuf)
|
|
257
279
|
|
|
@@ -290,6 +312,12 @@ module Rack
|
|
|
290
312
|
def read_data(io, outbuf)
|
|
291
313
|
content = io.read(@bufsize, outbuf)
|
|
292
314
|
handle_empty_content!(content)
|
|
315
|
+
if @total_bytes_read
|
|
316
|
+
@total_bytes_read += content.bytesize
|
|
317
|
+
if @total_bytes_read > PARSER_BYTESIZE_LIMIT
|
|
318
|
+
raise Error, "multipart upload exceeds limit of #{PARSER_BYTESIZE_LIMIT} bytes"
|
|
319
|
+
end
|
|
320
|
+
end
|
|
293
321
|
@sbuf.concat(content)
|
|
294
322
|
end
|
|
295
323
|
|
|
@@ -339,13 +367,21 @@ module Rack
|
|
|
339
367
|
|
|
340
368
|
CONTENT_DISPOSITION_MAX_PARAMS = 16
|
|
341
369
|
CONTENT_DISPOSITION_MAX_BYTES = 1536
|
|
370
|
+
OBS_UNFOLD = /\r\n([ \t])/
|
|
371
|
+
private_constant :OBS_UNFOLD
|
|
372
|
+
|
|
342
373
|
def handle_mime_head
|
|
343
374
|
if @sbuf.scan_until(@head_regex)
|
|
344
375
|
head = @sbuf[1]
|
|
345
376
|
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
|
377
|
+
content_type.gsub!(OBS_UNFOLD, '\1') if content_type
|
|
378
|
+
|
|
346
379
|
if (disposition = head[MULTIPART_CONTENT_DISPOSITION, 1]) &&
|
|
347
380
|
disposition.bytesize <= CONTENT_DISPOSITION_MAX_BYTES
|
|
348
381
|
|
|
382
|
+
# Implement OBS unfolding (RFC 5322 Section 2.2.3)
|
|
383
|
+
disposition.gsub!(OBS_UNFOLD, '\1')
|
|
384
|
+
|
|
349
385
|
# ignore actual content-disposition value (should always be form-data)
|
|
350
386
|
i = disposition.index(';')
|
|
351
387
|
disposition.slice!(0, i+1)
|
|
@@ -383,6 +419,11 @@ module Rack
|
|
|
383
419
|
# stop parsing parameter value if found ending quote
|
|
384
420
|
break if c == '"'
|
|
385
421
|
|
|
422
|
+
@content_disposition_quoted_escapes += 1
|
|
423
|
+
if @content_disposition_quoted_escapes > CONTENT_DISPOSITION_QUOTED_ESCAPES_LIMIT
|
|
424
|
+
raise Error, "number of quoted escapes during content disposition parsing exceeds limit"
|
|
425
|
+
end
|
|
426
|
+
|
|
386
427
|
escaped_char = disposition.slice!(0, 1)
|
|
387
428
|
if param == 'filename' && escaped_char != '"'
|
|
388
429
|
# Possible IE uploaded filename, append both escape backslash and value
|
data/lib/rack/request.rb
CHANGED
|
@@ -723,8 +723,8 @@ module Rack
|
|
|
723
723
|
# Match IPv6 as a string of hex digits and colons in square brackets
|
|
724
724
|
\[(?<address>#{ipv6})\]
|
|
725
725
|
|
|
|
726
|
-
# Match
|
|
727
|
-
(?<address>[
|
|
726
|
+
# Match characters allowed by RFC 3986 Section 3.2.2
|
|
727
|
+
(?<address>[-a-zA-Z0-9._~%!$&'()*+,;=]*?)
|
|
728
728
|
)
|
|
729
729
|
(:(?<port>\d+))?
|
|
730
730
|
\z
|
data/lib/rack/sendfile.rb
CHANGED
|
@@ -51,7 +51,7 @@ module Rack
|
|
|
51
51
|
#
|
|
52
52
|
# The `x-accel-mapping` header should specify the location on the file system,
|
|
53
53
|
# followed by an equals sign (=), followed name of the private URL pattern
|
|
54
|
-
# that it maps to. The middleware performs a
|
|
54
|
+
# that it maps to. The middleware performs a case-insensitive substitution on the
|
|
55
55
|
# resulting path.
|
|
56
56
|
#
|
|
57
57
|
# To enable `x-accel-redirect`, you must configure the middleware explicitly:
|
|
@@ -186,7 +186,7 @@ module Rack
|
|
|
186
186
|
# Safe to use header: explicit config + no app mappings:
|
|
187
187
|
mapping.split(',').map(&:strip).each do |m|
|
|
188
188
|
internal, external = m.split('=', 2).map(&:strip)
|
|
189
|
-
new_path = path.sub(/\A#{internal}/i, external)
|
|
189
|
+
new_path = path.sub(/\A#{Regexp.escape(internal)}/i, external)
|
|
190
190
|
return new_path unless path == new_path
|
|
191
191
|
end
|
|
192
192
|
|
data/lib/rack/static.rb
CHANGED
|
@@ -93,6 +93,9 @@ module Rack
|
|
|
93
93
|
def initialize(app, options = {})
|
|
94
94
|
@app = app
|
|
95
95
|
@urls = options[:urls] || ["/favicon.ico"]
|
|
96
|
+
if @urls.kind_of?(Array)
|
|
97
|
+
@urls = @urls.map { |url| [url, url.end_with?('/') ? url : "#{url}/".freeze].freeze }.freeze
|
|
98
|
+
end
|
|
96
99
|
@index = options[:index]
|
|
97
100
|
@gzip = options[:gzip]
|
|
98
101
|
@cascade = options[:cascade]
|
|
@@ -115,7 +118,7 @@ module Rack
|
|
|
115
118
|
end
|
|
116
119
|
|
|
117
120
|
def route_file(path)
|
|
118
|
-
@urls.kind_of?(Array) && @urls.any? { |url| path.
|
|
121
|
+
@urls.kind_of?(Array) && @urls.any? { |url, url_slash| path == url || path.start_with?(url_slash) }
|
|
119
122
|
end
|
|
120
123
|
|
|
121
124
|
def can_serve(path)
|
|
@@ -165,6 +168,8 @@ module Rack
|
|
|
165
168
|
|
|
166
169
|
# Convert HTTP header rules to HTTP headers
|
|
167
170
|
def applicable_rules(path)
|
|
171
|
+
path = ::Rack::Utils.unescape_path(path)
|
|
172
|
+
|
|
168
173
|
@header_rules.find_all do |rule, new_headers|
|
|
169
174
|
case rule
|
|
170
175
|
when :all
|
|
@@ -172,10 +177,9 @@ module Rack
|
|
|
172
177
|
when :fonts
|
|
173
178
|
/\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
|
|
174
179
|
when String
|
|
175
|
-
path = ::Rack::Utils.unescape(path)
|
|
176
180
|
path.start_with?(rule) || path.start_with?('/' + rule)
|
|
177
181
|
when Array
|
|
178
|
-
|
|
182
|
+
/\.#{Regexp.union(rule)}\z/.match?(path)
|
|
179
183
|
when Regexp
|
|
180
184
|
rule.match?(path)
|
|
181
185
|
else
|
data/lib/rack/utils.rb
CHANGED
|
@@ -146,17 +146,77 @@ module Rack
|
|
|
146
146
|
end
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
+
ALLOWED_FORWARED_PARAMS = %w[by for host proto].to_h { |name| [name, name.to_sym] }.freeze
|
|
150
|
+
private_constant :ALLOWED_FORWARED_PARAMS
|
|
151
|
+
|
|
149
152
|
def forwarded_values(forwarded_header)
|
|
150
|
-
return
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
return unless forwarded_header
|
|
154
|
+
header = forwarded_header.to_s.tr("\n", ";")
|
|
155
|
+
header.sub!(/\A[\s;,]+/, '')
|
|
156
|
+
num_params = num_escapes = 0
|
|
157
|
+
max_params = max_escapes = 1024
|
|
158
|
+
params = {}
|
|
159
|
+
|
|
160
|
+
# Parse parameter list
|
|
161
|
+
while i = header.index('=')
|
|
162
|
+
# Only parse up to max parameters, to avoid potential denial of service
|
|
163
|
+
num_params += 1
|
|
164
|
+
return if num_params > max_params
|
|
165
|
+
|
|
166
|
+
# Found end of parameter name, ensure forward progress in loop
|
|
167
|
+
param = header.slice!(0, i+1)
|
|
168
|
+
|
|
169
|
+
# Remove ending equals and preceding whitespace from parameter name
|
|
170
|
+
param.chomp!('=')
|
|
171
|
+
param.strip!
|
|
172
|
+
param.downcase!
|
|
173
|
+
return unless param = ALLOWED_FORWARED_PARAMS[param]
|
|
174
|
+
|
|
175
|
+
if header[0] == '"'
|
|
176
|
+
# Parameter value is quoted, parse it, handling backslash escapes
|
|
177
|
+
header.slice!(0, 1)
|
|
178
|
+
value = String.new
|
|
179
|
+
|
|
180
|
+
while i = header.index(/(["\\])/)
|
|
181
|
+
c = $1
|
|
182
|
+
|
|
183
|
+
# Append all content until ending quote or escape
|
|
184
|
+
value << header.slice!(0, i)
|
|
185
|
+
|
|
186
|
+
# Remove either backslash or ending quote,
|
|
187
|
+
# ensures forward progress in loop
|
|
188
|
+
header.slice!(0, 1)
|
|
189
|
+
|
|
190
|
+
# stop parsing parameter value if found ending quote
|
|
191
|
+
break if c == '"'
|
|
192
|
+
|
|
193
|
+
# Only allow up to max escapes, to avoid potential denial of service
|
|
194
|
+
num_escapes += 1
|
|
195
|
+
return if num_escapes > max_escapes
|
|
196
|
+
escaped_char = header.slice!(0, 1)
|
|
197
|
+
value << escaped_char
|
|
198
|
+
end
|
|
199
|
+
else
|
|
200
|
+
if i = header.index(/[;,]/)
|
|
201
|
+
# Parameter value unquoted (which may be invalid), value ends at comma or semicolon
|
|
202
|
+
value = header.slice!(0, i)
|
|
203
|
+
value.sub!(/[\s;,]+\z/, '')
|
|
204
|
+
else
|
|
205
|
+
# If no ending semicolon, assume remainder of line is value and stop parsing
|
|
206
|
+
header.strip!
|
|
207
|
+
value = header
|
|
208
|
+
header = ''
|
|
209
|
+
end
|
|
210
|
+
value.lstrip!
|
|
158
211
|
end
|
|
212
|
+
|
|
213
|
+
(params[param] ||= []) << value
|
|
214
|
+
|
|
215
|
+
# skip trailing semicolons/commas/whitespace, to proceed to next parameter
|
|
216
|
+
header.sub!(/\A[\s;,]+/, '') unless header.empty?
|
|
159
217
|
end
|
|
218
|
+
|
|
219
|
+
params
|
|
160
220
|
end
|
|
161
221
|
module_function :forwarded_values
|
|
162
222
|
|
|
@@ -193,17 +253,41 @@ module Rack
|
|
|
193
253
|
# :nocov:
|
|
194
254
|
end
|
|
195
255
|
|
|
256
|
+
# Given an array of available encoding strings, and an array of
|
|
257
|
+
# acceptable encodings for a request, where each element of the
|
|
258
|
+
# acceptable encodings array is an array where the first element
|
|
259
|
+
# is an encoding name and the second element is the numeric
|
|
260
|
+
# priority for the encoding, return the available encoding with
|
|
261
|
+
# the highest priority.
|
|
262
|
+
#
|
|
263
|
+
# The accept_encoding argument is typically generated by calling
|
|
264
|
+
# Request#accept_encoding.
|
|
265
|
+
#
|
|
266
|
+
# Example:
|
|
267
|
+
#
|
|
268
|
+
# select_best_encoding(%w(compress gzip identity),
|
|
269
|
+
# [["compress", 0.5], ["gzip", 1.0]])
|
|
270
|
+
# # => "gzip"
|
|
271
|
+
#
|
|
272
|
+
# To reduce denial of service potential, only the first 16
|
|
273
|
+
# acceptable encodings are considered.
|
|
196
274
|
def select_best_encoding(available_encodings, accept_encoding)
|
|
197
275
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
198
276
|
|
|
277
|
+
# Only process the first 16 encodings
|
|
278
|
+
accept_encoding = accept_encoding[0...16]
|
|
199
279
|
expanded_accept_encoding = []
|
|
280
|
+
wildcard_seen = false
|
|
200
281
|
|
|
201
282
|
accept_encoding.each do |m, q|
|
|
202
283
|
preference = available_encodings.index(m) || available_encodings.size
|
|
203
284
|
|
|
204
285
|
if m == "*"
|
|
205
|
-
|
|
206
|
-
|
|
286
|
+
unless wildcard_seen
|
|
287
|
+
(available_encodings - accept_encoding.map(&:first)).each do |m2|
|
|
288
|
+
expanded_accept_encoding << [m2, q, preference]
|
|
289
|
+
end
|
|
290
|
+
wildcard_seen = true
|
|
207
291
|
end
|
|
208
292
|
else
|
|
209
293
|
expanded_accept_encoding << [m, q, preference]
|
|
@@ -211,7 +295,13 @@ module Rack
|
|
|
211
295
|
end
|
|
212
296
|
|
|
213
297
|
encoding_candidates = expanded_accept_encoding
|
|
214
|
-
.
|
|
298
|
+
.sort do |(_, q1, p1), (_, q2, p2)|
|
|
299
|
+
if r = (q1 <=> q2).nonzero?
|
|
300
|
+
-r
|
|
301
|
+
else
|
|
302
|
+
(p1 <=> p2).nonzero? || 0
|
|
303
|
+
end
|
|
304
|
+
end
|
|
215
305
|
.map!(&:first)
|
|
216
306
|
|
|
217
307
|
unless encoding_candidates.include?("identity")
|
|
@@ -399,17 +489,19 @@ module Rack
|
|
|
399
489
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
|
400
490
|
# Returns nil if the header is missing or syntactically invalid.
|
|
401
491
|
# Returns an empty array if none of the ranges are satisfiable.
|
|
402
|
-
def byte_ranges(env, size)
|
|
403
|
-
get_byte_ranges env['HTTP_RANGE'], size
|
|
492
|
+
def byte_ranges(env, size, max_ranges: 100)
|
|
493
|
+
get_byte_ranges env['HTTP_RANGE'], size, max_ranges: max_ranges
|
|
404
494
|
end
|
|
405
495
|
|
|
406
|
-
def get_byte_ranges(http_range, size)
|
|
496
|
+
def get_byte_ranges(http_range, size, max_ranges: 100)
|
|
407
497
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
|
408
498
|
# Ignore Range when file size is 0 to avoid a 416 error.
|
|
409
499
|
return nil if size.zero?
|
|
410
500
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
|
501
|
+
byte_range = $1
|
|
502
|
+
return nil if byte_range.count(',') >= max_ranges
|
|
411
503
|
ranges = []
|
|
412
|
-
|
|
504
|
+
byte_range.split(/,[ \t]*/).each do |range_spec|
|
|
413
505
|
return nil unless range_spec.include?('-')
|
|
414
506
|
range = range_spec.split('-')
|
|
415
507
|
r0, r1 = range[0], range[1]
|
data/lib/rack/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.2.
|
|
4
|
+
version: 3.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leah Neukirchen
|
|
@@ -156,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
156
156
|
- !ruby/object:Gem::Version
|
|
157
157
|
version: '0'
|
|
158
158
|
requirements: []
|
|
159
|
-
rubygems_version: 4.0.
|
|
159
|
+
rubygems_version: 4.0.6
|
|
160
160
|
specification_version: 4
|
|
161
161
|
summary: A modular Ruby webserver interface.
|
|
162
162
|
test_files: []
|