rack 3.0.0 → 3.2.7
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 +592 -1
- data/CONTRIBUTING.md +11 -9
- data/README.md +120 -29
- data/SPEC.rdoc +206 -288
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/auth/basic.rb +1 -4
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/body_proxy.rb +18 -2
- data/lib/rack/builder.rb +61 -40
- data/lib/rack/cascade.rb +0 -3
- data/lib/rack/common_logger.rb +3 -2
- data/lib/rack/conditional_get.rb +4 -3
- data/lib/rack/constants.rb +6 -1
- data/lib/rack/directory.rb +6 -3
- data/lib/rack/etag.rb +3 -0
- data/lib/rack/events.rb +21 -6
- data/lib/rack/files.rb +1 -1
- data/lib/rack/head.rb +2 -3
- data/lib/rack/headers.rb +98 -14
- data/lib/rack/lint.rb +491 -428
- data/lib/rack/media_type.rb +18 -9
- data/lib/rack/method_override.rb +2 -2
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +10 -15
- data/lib/rack/mock_response.rb +50 -20
- data/lib/rack/multipart/parser.rb +279 -80
- data/lib/rack/multipart/uploaded_file.rb +42 -5
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +88 -80
- data/lib/rack/request.rb +98 -65
- data/lib/rack/response.rb +35 -20
- data/lib/rack/rewindable_input.rb +4 -1
- data/lib/rack/sendfile.rb +53 -23
- data/lib/rack/show_exceptions.rb +10 -4
- data/lib/rack/show_status.rb +0 -2
- data/lib/rack/static.rb +9 -4
- data/lib/rack/urlmap.rb +1 -1
- data/lib/rack/utils.rb +205 -133
- data/lib/rack/version.rb +3 -20
- data/lib/rack.rb +11 -17
- metadata +6 -15
- 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/logger.rb +0 -22
data/lib/rack/utils.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'fileutils'
|
|
|
6
6
|
require 'set'
|
|
7
7
|
require 'tempfile'
|
|
8
8
|
require 'time'
|
|
9
|
+
require 'erb'
|
|
9
10
|
|
|
10
11
|
require_relative 'query_parser'
|
|
11
12
|
require_relative 'mime'
|
|
@@ -23,6 +24,7 @@ module Rack
|
|
|
23
24
|
DEFAULT_SEP = QueryParser::DEFAULT_SEP
|
|
24
25
|
COMMON_SEP = QueryParser::COMMON_SEP
|
|
25
26
|
KeySpaceConstrainedParams = QueryParser::Params
|
|
27
|
+
URI_PARSER = defined?(::URI::RFC2396_PARSER) ? ::URI::RFC2396_PARSER : ::URI::DEFAULT_PARSER
|
|
26
28
|
|
|
27
29
|
class << self
|
|
28
30
|
attr_accessor :default_query_parser
|
|
@@ -42,13 +44,13 @@ module Rack
|
|
|
42
44
|
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
|
43
45
|
# true URI escaping.
|
|
44
46
|
def escape_path(s)
|
|
45
|
-
|
|
47
|
+
URI_PARSER.escape s
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
# Unescapes the **path** component of a URI. See Rack::Utils.unescape for
|
|
49
51
|
# unescaping query parameters or form components.
|
|
50
52
|
def unescape_path(s)
|
|
51
|
-
|
|
53
|
+
URI_PARSER.unescape s
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
|
@@ -58,13 +60,24 @@ module Rack
|
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
class << self
|
|
61
|
-
attr_accessor :
|
|
63
|
+
attr_accessor :multipart_total_part_limit
|
|
64
|
+
|
|
65
|
+
attr_accessor :multipart_file_limit
|
|
66
|
+
|
|
67
|
+
# multipart_part_limit is the original name of multipart_file_limit, but
|
|
68
|
+
# the limit only counts parts with filenames.
|
|
69
|
+
alias multipart_part_limit multipart_file_limit
|
|
70
|
+
alias multipart_part_limit= multipart_file_limit=
|
|
62
71
|
end
|
|
63
72
|
|
|
64
|
-
# The maximum number of parts a request can contain. Accepting too
|
|
65
|
-
# can lead to the server running out of file handles.
|
|
73
|
+
# The maximum number of file parts a request can contain. Accepting too
|
|
74
|
+
# many parts can lead to the server running out of file handles.
|
|
66
75
|
# Set to `0` for no limit.
|
|
67
|
-
self.
|
|
76
|
+
self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
|
|
77
|
+
|
|
78
|
+
# The maximum total number of parts a request can contain. Accepting too
|
|
79
|
+
# many can lead to excessive memory use and parsing time.
|
|
80
|
+
self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
|
|
68
81
|
|
|
69
82
|
def self.param_depth_limit
|
|
70
83
|
default_query_parser.param_depth_limit
|
|
@@ -74,15 +87,6 @@ module Rack
|
|
|
74
87
|
self.default_query_parser = self.default_query_parser.new_depth_limit(v)
|
|
75
88
|
end
|
|
76
89
|
|
|
77
|
-
def self.key_space_limit
|
|
78
|
-
warn("`Rack::Utils.key_space_limit` is deprecated as this value no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
|
|
79
|
-
65536
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def self.key_space_limit=(v)
|
|
83
|
-
warn("`Rack::Utils.key_space_limit=` is deprecated and no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
90
|
if defined?(Process::CLOCK_MONOTONIC)
|
|
87
91
|
def clock_time
|
|
88
92
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -121,19 +125,19 @@ module Rack
|
|
|
121
125
|
}.join("&")
|
|
122
126
|
when Hash
|
|
123
127
|
value.map { |k, v|
|
|
124
|
-
build_nested_query(v, prefix ? "#{prefix}[#{
|
|
128
|
+
build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
|
|
125
129
|
}.delete_if(&:empty?).join('&')
|
|
126
130
|
when nil
|
|
127
|
-
prefix
|
|
131
|
+
escape(prefix)
|
|
128
132
|
else
|
|
129
133
|
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
|
130
|
-
"#{prefix}=#{escape(value)}"
|
|
134
|
+
"#{escape(prefix)}=#{escape(value)}"
|
|
131
135
|
end
|
|
132
136
|
end
|
|
133
137
|
|
|
134
138
|
def q_values(q_value_header)
|
|
135
|
-
q_value_header.to_s.split(
|
|
136
|
-
value, parameters = part.split(
|
|
139
|
+
q_value_header.to_s.split(',').map do |part|
|
|
140
|
+
value, parameters = part.split(';', 2).map(&:strip)
|
|
137
141
|
quality = 1.0
|
|
138
142
|
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
|
139
143
|
quality = md[1].to_f
|
|
@@ -142,16 +146,77 @@ module Rack
|
|
|
142
146
|
end
|
|
143
147
|
end
|
|
144
148
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
forwarded_header = forwarded_header.to_s.gsub("\n", ";")
|
|
149
|
+
ALLOWED_FORWARDED_PARAMS = %w[by for host proto].map { |name| [name, name.to_sym] }.to_h.freeze
|
|
150
|
+
private_constant :ALLOWED_FORWARDED_PARAMS
|
|
148
151
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
def forwarded_values(forwarded_header)
|
|
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_FORWARDED_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!
|
|
153
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?
|
|
154
217
|
end
|
|
218
|
+
|
|
219
|
+
params
|
|
155
220
|
end
|
|
156
221
|
module_function :forwarded_values
|
|
157
222
|
|
|
@@ -172,33 +237,57 @@ module Rack
|
|
|
172
237
|
matches&.first
|
|
173
238
|
end
|
|
174
239
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
240
|
+
# Introduced in ERB 4.0. ERB::Escape is an alias for ERB::Utils which
|
|
241
|
+
# doesn't get monkey-patched by rails
|
|
242
|
+
if defined?(ERB::Escape) && ERB::Escape.instance_method(:html_escape)
|
|
243
|
+
define_method(:escape_html, ERB::Escape.instance_method(:html_escape))
|
|
244
|
+
# :nocov:
|
|
245
|
+
# Ruby 3.2/ERB 4.0 added ERB::Escape#html_escape, so the else
|
|
246
|
+
# branch cannot be hit on the current Ruby version.
|
|
247
|
+
else
|
|
248
|
+
require 'cgi/escape'
|
|
249
|
+
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
|
250
|
+
def escape_html(string)
|
|
251
|
+
CGI.escapeHTML(string.to_s)
|
|
252
|
+
end
|
|
253
|
+
# :nocov:
|
|
189
254
|
end
|
|
190
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.
|
|
191
274
|
def select_best_encoding(available_encodings, accept_encoding)
|
|
192
275
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
193
276
|
|
|
277
|
+
# Only process the first 16 encodings
|
|
278
|
+
accept_encoding = accept_encoding[0...16]
|
|
194
279
|
expanded_accept_encoding = []
|
|
280
|
+
wildcard_seen = false
|
|
195
281
|
|
|
196
282
|
accept_encoding.each do |m, q|
|
|
197
283
|
preference = available_encodings.index(m) || available_encodings.size
|
|
198
284
|
|
|
199
285
|
if m == "*"
|
|
200
|
-
|
|
201
|
-
|
|
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
|
|
202
291
|
end
|
|
203
292
|
else
|
|
204
293
|
expanded_accept_encoding << [m, q, preference]
|
|
@@ -206,7 +295,13 @@ module Rack
|
|
|
206
295
|
end
|
|
207
296
|
|
|
208
297
|
encoding_candidates = expanded_accept_encoding
|
|
209
|
-
.
|
|
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
|
|
210
305
|
.map!(&:first)
|
|
211
306
|
|
|
212
307
|
unless encoding_candidates.include?("identity")
|
|
@@ -240,21 +335,6 @@ module Rack
|
|
|
240
335
|
end
|
|
241
336
|
end
|
|
242
337
|
|
|
243
|
-
def add_cookie_to_header(header, key, value)
|
|
244
|
-
warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
|
|
245
|
-
|
|
246
|
-
case header
|
|
247
|
-
when nil, ''
|
|
248
|
-
return set_cookie_header(key, value)
|
|
249
|
-
when String
|
|
250
|
-
[header, set_cookie_header(key, value)]
|
|
251
|
-
when Array
|
|
252
|
-
header + [set_cookie_header(key, value)]
|
|
253
|
-
else
|
|
254
|
-
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
|
|
258
338
|
# :call-seq:
|
|
259
339
|
# parse_cookies(env) -> hash
|
|
260
340
|
#
|
|
@@ -268,23 +348,25 @@ module Rack
|
|
|
268
348
|
parse_cookies_header env[HTTP_COOKIE]
|
|
269
349
|
end
|
|
270
350
|
|
|
351
|
+
# A valid cookie key according to RFC6265 and RFC2616.
|
|
352
|
+
# A <cookie-name> can be any US-ASCII characters, except control characters, spaces, or tabs. It also must not contain a separator character like the following: ( ) < > @ , ; : \ " / [ ] ? = { }.
|
|
353
|
+
VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
|
|
354
|
+
private_constant :VALID_COOKIE_KEY
|
|
355
|
+
|
|
271
356
|
# :call-seq:
|
|
272
357
|
# set_cookie_header(key, value) -> encoded string
|
|
273
358
|
#
|
|
274
359
|
# Generate an encoded string using the provided +key+ and +value+ suitable
|
|
275
360
|
# for the +set-cookie+ header according to RFC6265. The +value+ may be an
|
|
276
|
-
# instance of either +String+ or +Hash+.
|
|
361
|
+
# instance of either +String+ or +Hash+. If the cookie key is invalid (as
|
|
362
|
+
# defined by RFC6265), an +ArgumentError+ will be raised.
|
|
277
363
|
#
|
|
278
364
|
# If the cookie +value+ is an instance of +Hash+, it considers the following
|
|
279
365
|
# cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
|
|
280
366
|
# of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
|
|
281
|
-
# details about the interpretation of these fields, consult
|
|
367
|
+
# details about the interpretation of these fields, consult
|
|
282
368
|
# [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
|
|
283
369
|
#
|
|
284
|
-
# An extra cookie attribute +escape_key+ can be provided to control whether
|
|
285
|
-
# or not the cookie key is URL encoded. If explicitly set to +false+, the
|
|
286
|
-
# cookie key name will not be url encoded (escaped). The default is +true+.
|
|
287
|
-
#
|
|
288
370
|
# set_cookie_header("myname", "myvalue")
|
|
289
371
|
# # => "myname=myvalue"
|
|
290
372
|
#
|
|
@@ -292,9 +374,12 @@ module Rack
|
|
|
292
374
|
# # => "myname=myvalue; max-age=10"
|
|
293
375
|
#
|
|
294
376
|
def set_cookie_header(key, value)
|
|
377
|
+
unless key =~ VALID_COOKIE_KEY
|
|
378
|
+
raise ArgumentError, "invalid cookie key: #{key.inspect}"
|
|
379
|
+
end
|
|
380
|
+
|
|
295
381
|
case value
|
|
296
382
|
when Hash
|
|
297
|
-
key = escape(key) unless value[:escape_key] == false
|
|
298
383
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
|
299
384
|
path = "; path=#{value[:path]}" if value[:path]
|
|
300
385
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
|
@@ -306,23 +391,22 @@ module Rack
|
|
|
306
391
|
when false, nil
|
|
307
392
|
nil
|
|
308
393
|
when :none, 'None', :None
|
|
309
|
-
';
|
|
394
|
+
'; samesite=none'
|
|
310
395
|
when :lax, 'Lax', :Lax
|
|
311
|
-
';
|
|
396
|
+
'; samesite=lax'
|
|
312
397
|
when true, :strict, 'Strict', :Strict
|
|
313
|
-
';
|
|
398
|
+
'; samesite=strict'
|
|
314
399
|
else
|
|
315
|
-
raise ArgumentError, "Invalid
|
|
400
|
+
raise ArgumentError, "Invalid :same_site value: #{value[:same_site].inspect}"
|
|
316
401
|
end
|
|
402
|
+
partitioned = "; partitioned" if value[:partitioned]
|
|
317
403
|
value = value[:value]
|
|
318
|
-
else
|
|
319
|
-
key = escape(key)
|
|
320
404
|
end
|
|
321
405
|
|
|
322
406
|
value = [value] unless Array === value
|
|
323
407
|
|
|
324
408
|
return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
|
325
|
-
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
|
409
|
+
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}"
|
|
326
410
|
end
|
|
327
411
|
|
|
328
412
|
# :call-seq:
|
|
@@ -363,24 +447,12 @@ module Rack
|
|
|
363
447
|
set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
|
|
364
448
|
end
|
|
365
449
|
|
|
366
|
-
def make_delete_cookie_header(header, key, value)
|
|
367
|
-
warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
|
368
|
-
|
|
369
|
-
delete_set_cookie_header!(header, key, value)
|
|
370
|
-
end
|
|
371
|
-
|
|
372
450
|
def delete_cookie_header!(headers, key, value = {})
|
|
373
451
|
headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
|
|
374
452
|
|
|
375
453
|
return nil
|
|
376
454
|
end
|
|
377
455
|
|
|
378
|
-
def add_remove_cookie_to_header(header, key, value = {})
|
|
379
|
-
warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
|
380
|
-
|
|
381
|
-
delete_set_cookie_header!(header, key, value)
|
|
382
|
-
end
|
|
383
|
-
|
|
384
456
|
# :call-seq:
|
|
385
457
|
# delete_set_cookie_header!(header, key, value = {}) -> header value
|
|
386
458
|
#
|
|
@@ -417,26 +489,31 @@ module Rack
|
|
|
417
489
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
|
418
490
|
# Returns nil if the header is missing or syntactically invalid.
|
|
419
491
|
# Returns an empty array if none of the ranges are satisfiable.
|
|
420
|
-
def byte_ranges(env, size)
|
|
421
|
-
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
|
|
422
494
|
end
|
|
423
495
|
|
|
424
|
-
def get_byte_ranges(http_range, size)
|
|
496
|
+
def get_byte_ranges(http_range, size, max_ranges: 100)
|
|
425
497
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
|
498
|
+
# Ignore Range when file size is 0 to avoid a 416 error.
|
|
499
|
+
return nil if size.zero?
|
|
426
500
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
|
501
|
+
byte_range = $1
|
|
502
|
+
return nil if byte_range.count(',') >= max_ranges
|
|
427
503
|
ranges = []
|
|
428
|
-
|
|
429
|
-
return nil
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
504
|
+
byte_range.split(/,[ \t]*/).each do |range_spec|
|
|
505
|
+
return nil unless range_spec.include?('-')
|
|
506
|
+
range = range_spec.split('-')
|
|
507
|
+
r0, r1 = range[0], range[1]
|
|
508
|
+
if r0.nil? || r0.empty?
|
|
509
|
+
return nil if r1.nil?
|
|
433
510
|
# suffix-byte-range-spec, represents trailing suffix of file
|
|
434
511
|
r0 = size - r1.to_i
|
|
435
512
|
r0 = 0 if r0 < 0
|
|
436
513
|
r1 = size - 1
|
|
437
514
|
else
|
|
438
515
|
r0 = r0.to_i
|
|
439
|
-
if r1.
|
|
516
|
+
if r1.nil?
|
|
440
517
|
r1 = size - 1
|
|
441
518
|
else
|
|
442
519
|
r1 = r1.to_i
|
|
@@ -446,6 +523,9 @@ module Rack
|
|
|
446
523
|
end
|
|
447
524
|
ranges << (r0..r1) if r0 <= r1
|
|
448
525
|
end
|
|
526
|
+
|
|
527
|
+
return [] if ranges.map(&:size).sum > size
|
|
528
|
+
|
|
449
529
|
ranges
|
|
450
530
|
end
|
|
451
531
|
|
|
@@ -501,39 +581,12 @@ module Rack
|
|
|
501
581
|
end
|
|
502
582
|
end
|
|
503
583
|
|
|
504
|
-
# A wrapper around Headers
|
|
505
|
-
# header when set.
|
|
506
|
-
#
|
|
507
|
-
# @api private
|
|
508
|
-
class HeaderHash < Hash # :nodoc:
|
|
509
|
-
def self.[](headers)
|
|
510
|
-
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
|
511
|
-
if headers.is_a?(Headers) && !headers.frozen?
|
|
512
|
-
return headers
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
new_headers = Headers.new
|
|
516
|
-
headers.each{|k,v| new_headers[k] = v}
|
|
517
|
-
new_headers
|
|
518
|
-
end
|
|
519
|
-
|
|
520
|
-
def self.new(hash = {})
|
|
521
|
-
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
|
522
|
-
headers = Headers.new
|
|
523
|
-
hash.each{|k,v| headers[k] = v}
|
|
524
|
-
headers
|
|
525
|
-
end
|
|
526
|
-
|
|
527
|
-
def self.allocate
|
|
528
|
-
raise TypeError, "cannot allocate HeaderHash"
|
|
529
|
-
end
|
|
530
|
-
end
|
|
531
|
-
|
|
532
584
|
# Every standard HTTP code mapped to the appropriate message.
|
|
533
585
|
# Generated with:
|
|
534
|
-
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv
|
|
535
|
-
# ruby -
|
|
536
|
-
#
|
|
586
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv \
|
|
587
|
+
# | ruby -rcsv -e "puts CSV.parse(STDIN, headers: true) \
|
|
588
|
+
# .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \
|
|
589
|
+
# .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)"
|
|
537
590
|
HTTP_STATUS_CODES = {
|
|
538
591
|
100 => 'Continue',
|
|
539
592
|
101 => 'Switching Protocols',
|
|
@@ -555,7 +608,6 @@ module Rack
|
|
|
555
608
|
303 => 'See Other',
|
|
556
609
|
304 => 'Not Modified',
|
|
557
610
|
305 => 'Use Proxy',
|
|
558
|
-
306 => '(Unused)',
|
|
559
611
|
307 => 'Temporary Redirect',
|
|
560
612
|
308 => 'Permanent Redirect',
|
|
561
613
|
400 => 'Bad Request',
|
|
@@ -571,13 +623,13 @@ module Rack
|
|
|
571
623
|
410 => 'Gone',
|
|
572
624
|
411 => 'Length Required',
|
|
573
625
|
412 => 'Precondition Failed',
|
|
574
|
-
413 => '
|
|
626
|
+
413 => 'Content Too Large',
|
|
575
627
|
414 => 'URI Too Long',
|
|
576
628
|
415 => 'Unsupported Media Type',
|
|
577
629
|
416 => 'Range Not Satisfiable',
|
|
578
630
|
417 => 'Expectation Failed',
|
|
579
631
|
421 => 'Misdirected Request',
|
|
580
|
-
422 => 'Unprocessable
|
|
632
|
+
422 => 'Unprocessable Content',
|
|
581
633
|
423 => 'Locked',
|
|
582
634
|
424 => 'Failed Dependency',
|
|
583
635
|
425 => 'Too Early',
|
|
@@ -585,7 +637,7 @@ module Rack
|
|
|
585
637
|
428 => 'Precondition Required',
|
|
586
638
|
429 => 'Too Many Requests',
|
|
587
639
|
431 => 'Request Header Fields Too Large',
|
|
588
|
-
451 => 'Unavailable
|
|
640
|
+
451 => 'Unavailable For Legal Reasons',
|
|
589
641
|
500 => 'Internal Server Error',
|
|
590
642
|
501 => 'Not Implemented',
|
|
591
643
|
502 => 'Bad Gateway',
|
|
@@ -595,8 +647,6 @@ module Rack
|
|
|
595
647
|
506 => 'Variant Also Negotiates',
|
|
596
648
|
507 => 'Insufficient Storage',
|
|
597
649
|
508 => 'Loop Detected',
|
|
598
|
-
509 => 'Bandwidth Limit Exceeded',
|
|
599
|
-
510 => 'Not Extended',
|
|
600
650
|
511 => 'Network Authentication Required'
|
|
601
651
|
}
|
|
602
652
|
|
|
@@ -604,12 +654,34 @@ module Rack
|
|
|
604
654
|
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
|
605
655
|
|
|
606
656
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
|
607
|
-
[message.downcase.gsub(/\s
|
|
657
|
+
[message.downcase.gsub(/\s|-/, '_').to_sym, code]
|
|
608
658
|
}.flatten]
|
|
609
659
|
|
|
660
|
+
OBSOLETE_SYMBOLS_TO_STATUS_CODES = {
|
|
661
|
+
payload_too_large: 413,
|
|
662
|
+
unprocessable_entity: 422,
|
|
663
|
+
bandwidth_limit_exceeded: 509,
|
|
664
|
+
not_extended: 510
|
|
665
|
+
}.freeze
|
|
666
|
+
private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES
|
|
667
|
+
|
|
668
|
+
OBSOLETE_SYMBOL_MAPPINGS = {
|
|
669
|
+
payload_too_large: :content_too_large,
|
|
670
|
+
unprocessable_entity: :unprocessable_content
|
|
671
|
+
}.freeze
|
|
672
|
+
private_constant :OBSOLETE_SYMBOL_MAPPINGS
|
|
673
|
+
|
|
610
674
|
def status_code(status)
|
|
611
675
|
if status.is_a?(Symbol)
|
|
612
|
-
SYMBOL_TO_STATUS_CODE.fetch(status)
|
|
676
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) do
|
|
677
|
+
fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
|
678
|
+
message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
|
|
679
|
+
if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
|
|
680
|
+
message = "#{message} Please use #{canonical_symbol.inspect} instead."
|
|
681
|
+
end
|
|
682
|
+
warn message, uplevel: 3
|
|
683
|
+
fallback_code
|
|
684
|
+
end
|
|
613
685
|
else
|
|
614
686
|
status.to_i
|
|
615
687
|
end
|
data/lib/rack/version.rb
CHANGED
|
@@ -5,30 +5,13 @@
|
|
|
5
5
|
# Rack is freely distributable under the terms of an MIT-style license.
|
|
6
6
|
# See MIT-LICENSE or https://opensource.org/licenses/MIT.
|
|
7
7
|
|
|
8
|
-
# The Rack main module, serving as a namespace for all core Rack
|
|
9
|
-
# modules and classes.
|
|
10
|
-
#
|
|
11
|
-
# All modules meant for use in your application are <tt>autoload</tt>ed here,
|
|
12
|
-
# so it should be enough just to <tt>require 'rack'</tt> in your code.
|
|
13
|
-
|
|
14
8
|
module Rack
|
|
15
|
-
|
|
16
|
-
VERSION = [1, 3].freeze
|
|
17
|
-
deprecate_constant :VERSION
|
|
9
|
+
VERSION = "3.2.7"
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
deprecate_constant :VERSION_STRING
|
|
21
|
-
|
|
22
|
-
# The Rack protocol version number implemented.
|
|
23
|
-
def self.version
|
|
24
|
-
warn "Rack.version is deprecated and will be removed in Rack 3.1!", uplevel: 1
|
|
25
|
-
VERSION
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
RELEASE = "3.0.0"
|
|
11
|
+
RELEASE = VERSION
|
|
29
12
|
|
|
30
13
|
# Return the Rack release as a dotted string.
|
|
31
14
|
def self.release
|
|
32
|
-
|
|
15
|
+
VERSION
|
|
33
16
|
end
|
|
34
17
|
end
|
data/lib/rack.rb
CHANGED
|
@@ -15,56 +15,50 @@ require_relative 'rack/version'
|
|
|
15
15
|
require_relative 'rack/constants'
|
|
16
16
|
|
|
17
17
|
module Rack
|
|
18
|
-
autoload :
|
|
18
|
+
autoload :BadRequest, "rack/bad_request"
|
|
19
19
|
autoload :BodyProxy, "rack/body_proxy"
|
|
20
|
+
autoload :Builder, "rack/builder"
|
|
20
21
|
autoload :Cascade, "rack/cascade"
|
|
21
|
-
autoload :Chunked, "rack/chunked"
|
|
22
22
|
autoload :CommonLogger, "rack/common_logger"
|
|
23
23
|
autoload :ConditionalGet, "rack/conditional_get"
|
|
24
24
|
autoload :Config, "rack/config"
|
|
25
25
|
autoload :ContentLength, "rack/content_length"
|
|
26
26
|
autoload :ContentType, "rack/content_type"
|
|
27
|
+
autoload :Deflater, "rack/deflater"
|
|
28
|
+
autoload :Directory, "rack/directory"
|
|
27
29
|
autoload :ETag, "rack/etag"
|
|
28
30
|
autoload :Events, "rack/events"
|
|
29
|
-
autoload :File, "rack/file"
|
|
30
31
|
autoload :Files, "rack/files"
|
|
31
|
-
autoload :Deflater, "rack/deflater"
|
|
32
|
-
autoload :Directory, "rack/directory"
|
|
33
32
|
autoload :ForwardRequest, "rack/recursive"
|
|
34
|
-
autoload :Handler, "rack/handler"
|
|
35
33
|
autoload :Head, "rack/head"
|
|
36
34
|
autoload :Headers, "rack/headers"
|
|
37
35
|
autoload :Lint, "rack/lint"
|
|
38
36
|
autoload :Lock, "rack/lock"
|
|
39
|
-
autoload :Logger, "rack/logger"
|
|
40
37
|
autoload :MediaType, "rack/media_type"
|
|
41
38
|
autoload :MethodOverride, "rack/method_override"
|
|
42
39
|
autoload :Mime, "rack/mime"
|
|
40
|
+
autoload :MockRequest, "rack/mock_request"
|
|
41
|
+
autoload :MockResponse, "rack/mock_response"
|
|
42
|
+
autoload :Multipart, "rack/multipart"
|
|
43
43
|
autoload :NullLogger, "rack/null_logger"
|
|
44
|
+
autoload :QueryParser, "rack/query_parser"
|
|
44
45
|
autoload :Recursive, "rack/recursive"
|
|
45
46
|
autoload :Reloader, "rack/reloader"
|
|
47
|
+
autoload :Request, "rack/request"
|
|
48
|
+
autoload :Response, "rack/response"
|
|
46
49
|
autoload :RewindableInput, "rack/rewindable_input"
|
|
47
50
|
autoload :Runtime, "rack/runtime"
|
|
48
51
|
autoload :Sendfile, "rack/sendfile"
|
|
49
|
-
autoload :Server, "rack/server"
|
|
50
52
|
autoload :ShowExceptions, "rack/show_exceptions"
|
|
51
53
|
autoload :ShowStatus, "rack/show_status"
|
|
52
54
|
autoload :Static, "rack/static"
|
|
53
55
|
autoload :TempfileReaper, "rack/tempfile_reaper"
|
|
54
56
|
autoload :URLMap, "rack/urlmap"
|
|
55
57
|
autoload :Utils, "rack/utils"
|
|
56
|
-
autoload :Multipart, "rack/multipart"
|
|
57
|
-
|
|
58
|
-
autoload :MockRequest, "rack/mock_request"
|
|
59
|
-
autoload :MockResponse, "rack/mock_response"
|
|
60
|
-
|
|
61
|
-
autoload :Request, "rack/request"
|
|
62
|
-
autoload :Response, "rack/response"
|
|
63
58
|
|
|
64
59
|
module Auth
|
|
65
60
|
autoload :Basic, "rack/auth/basic"
|
|
66
|
-
autoload :AbstractRequest, "rack/auth/abstract/request"
|
|
67
61
|
autoload :AbstractHandler, "rack/auth/abstract/handler"
|
|
68
|
-
autoload :
|
|
62
|
+
autoload :AbstractRequest, "rack/auth/abstract/request"
|
|
69
63
|
end
|
|
70
64
|
end
|