rack 2.2.11 → 3.0.0.beta1
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 +137 -110
- data/CONTRIBUTING.md +53 -47
- data/MIT-LICENSE +1 -1
- data/README.md +287 -0
- data/Rakefile +40 -7
- data/SPEC.rdoc +166 -125
- data/contrib/LICENSE.md +7 -0
- data/contrib/logo.webp +0 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +3 -1
- data/lib/rack/auth/basic.rb +2 -1
- data/lib/rack/auth/digest/md5.rb +1 -131
- data/lib/rack/auth/digest/nonce.rb +1 -53
- data/lib/rack/auth/digest/params.rb +1 -54
- data/lib/rack/auth/digest/request.rb +1 -43
- data/lib/rack/auth/digest.rb +256 -0
- data/lib/rack/body_proxy.rb +3 -1
- data/lib/rack/builder.rb +60 -42
- data/lib/rack/cascade.rb +2 -0
- data/lib/rack/chunked.rb +16 -13
- data/lib/rack/common_logger.rb +24 -20
- data/lib/rack/conditional_get.rb +18 -15
- data/lib/rack/constants.rb +62 -0
- data/lib/rack/content_length.rb +12 -16
- data/lib/rack/content_type.rb +8 -5
- data/lib/rack/deflater.rb +40 -26
- data/lib/rack/directory.rb +9 -3
- data/lib/rack/etag.rb +14 -23
- data/lib/rack/events.rb +4 -0
- data/lib/rack/file.rb +2 -0
- data/lib/rack/files.rb +15 -17
- data/lib/rack/head.rb +9 -8
- data/lib/rack/headers.rb +154 -0
- data/lib/rack/lint.rb +740 -649
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +4 -9
- data/lib/rack/method_override.rb +5 -1
- data/lib/rack/mime.rb +8 -0
- data/lib/rack/mock.rb +1 -271
- data/lib/rack/mock_request.rb +166 -0
- data/lib/rack/mock_response.rb +124 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +123 -85
- data/lib/rack/multipart/uploaded_file.rb +4 -0
- data/lib/rack/multipart.rb +20 -40
- data/lib/rack/null_logger.rb +9 -0
- data/lib/rack/query_parser.rb +76 -44
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +189 -91
- data/lib/rack/response.rb +131 -61
- data/lib/rack/rewindable_input.rb +24 -5
- data/lib/rack/runtime.rb +7 -6
- data/lib/rack/sendfile.rb +30 -25
- data/lib/rack/show_exceptions.rb +15 -2
- data/lib/rack/show_status.rb +17 -7
- data/lib/rack/static.rb +8 -8
- data/lib/rack/tempfile_reaper.rb +15 -4
- data/lib/rack/urlmap.rb +4 -2
- data/lib/rack/utils.rb +212 -202
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +5 -76
- data/rack.gemspec +6 -6
- metadata +19 -31
- data/README.rdoc +0 -320
- 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/lib/rack/core_ext/regexp.rb +0 -14
- data/lib/rack/handler/cgi.rb +0 -59
- data/lib/rack/handler/fastcgi.rb +0 -100
- data/lib/rack/handler/lsws.rb +0 -61
- data/lib/rack/handler/scgi.rb +0 -71
- data/lib/rack/handler/thin.rb +0 -36
- data/lib/rack/handler/webrick.rb +0 -129
- data/lib/rack/handler.rb +0 -104
- data/lib/rack/lobster.rb +0 -70
- data/lib/rack/server.rb +0 -466
- data/lib/rack/session/abstract/id.rb +0 -523
- data/lib/rack/session/cookie.rb +0 -203
- data/lib/rack/session/memcache.rb +0 -10
- data/lib/rack/session/pool.rb +0 -85
data/lib/rack/utils.rb
CHANGED
@@ -8,30 +8,29 @@ require 'tempfile'
|
|
8
8
|
require 'time'
|
9
9
|
|
10
10
|
require_relative 'query_parser'
|
11
|
+
require_relative 'mime'
|
12
|
+
require_relative 'headers'
|
13
|
+
require_relative 'constants'
|
11
14
|
|
12
15
|
module Rack
|
13
16
|
# Rack::Utils contains a grab-bag of useful methods for writing web
|
14
17
|
# applications adopted from all kinds of Ruby libraries.
|
15
18
|
|
16
19
|
module Utils
|
17
|
-
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
18
|
-
|
19
20
|
ParameterTypeError = QueryParser::ParameterTypeError
|
20
21
|
InvalidParameterError = QueryParser::InvalidParameterError
|
22
|
+
ParamsTooDeepError = QueryParser::ParamsTooDeepError
|
21
23
|
DEFAULT_SEP = QueryParser::DEFAULT_SEP
|
22
24
|
COMMON_SEP = QueryParser::COMMON_SEP
|
23
25
|
KeySpaceConstrainedParams = QueryParser::Params
|
24
26
|
|
25
|
-
RFC2822_DAY_NAME = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
|
26
|
-
RFC2822_MONTH_NAME = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
|
27
|
-
RFC2396_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
|
28
|
-
|
29
27
|
class << self
|
30
28
|
attr_accessor :default_query_parser
|
31
29
|
end
|
32
|
-
# The default
|
33
|
-
# This helps prevent a rogue client from
|
34
|
-
|
30
|
+
# The default amount of nesting to allowed by hash parameters.
|
31
|
+
# This helps prevent a rogue client from triggering a possible stack overflow
|
32
|
+
# when parsing parameters.
|
33
|
+
self.default_query_parser = QueryParser.make_default(32)
|
35
34
|
|
36
35
|
module_function
|
37
36
|
|
@@ -43,13 +42,13 @@ module Rack
|
|
43
42
|
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
44
43
|
# true URI escaping.
|
45
44
|
def escape_path(s)
|
46
|
-
|
45
|
+
::URI::DEFAULT_PARSER.escape s
|
47
46
|
end
|
48
47
|
|
49
48
|
# Unescapes the **path** component of a URI. See Rack::Utils.unescape for
|
50
49
|
# unescaping query parameters or form components.
|
51
50
|
def unescape_path(s)
|
52
|
-
|
51
|
+
::URI::DEFAULT_PARSER.unescape s
|
53
52
|
end
|
54
53
|
|
55
54
|
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
@@ -59,24 +58,13 @@ module Rack
|
|
59
58
|
end
|
60
59
|
|
61
60
|
class << self
|
62
|
-
attr_accessor :
|
63
|
-
|
64
|
-
attr_accessor :multipart_file_limit
|
65
|
-
|
66
|
-
# multipart_part_limit is the original name of multipart_file_limit, but
|
67
|
-
# the limit only counts parts with filenames.
|
68
|
-
alias multipart_part_limit multipart_file_limit
|
69
|
-
alias multipart_part_limit= multipart_file_limit=
|
61
|
+
attr_accessor :multipart_part_limit
|
70
62
|
end
|
71
63
|
|
72
|
-
# The maximum number of
|
73
|
-
#
|
64
|
+
# The maximum number of parts a request can contain. Accepting too many part
|
65
|
+
# can lead to the server running out of file handles.
|
74
66
|
# Set to `0` for no limit.
|
75
|
-
self.
|
76
|
-
|
77
|
-
# The maximum total number of parts a request can contain. Accepting too
|
78
|
-
# many can lead to excessive memory use and parsing time.
|
79
|
-
self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
|
67
|
+
self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
|
80
68
|
|
81
69
|
def self.param_depth_limit
|
82
70
|
default_query_parser.param_depth_limit
|
@@ -87,11 +75,12 @@ module Rack
|
|
87
75
|
end
|
88
76
|
|
89
77
|
def self.key_space_limit
|
90
|
-
|
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
|
91
80
|
end
|
92
81
|
|
93
82
|
def self.key_space_limit=(v)
|
94
|
-
|
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)
|
95
84
|
end
|
96
85
|
|
97
86
|
if defined?(Process::CLOCK_MONOTONIC)
|
@@ -143,8 +132,8 @@ module Rack
|
|
143
132
|
end
|
144
133
|
|
145
134
|
def q_values(q_value_header)
|
146
|
-
q_value_header.to_s.split(
|
147
|
-
value, parameters = part.split(
|
135
|
+
q_value_header.to_s.split(/\s*,\s*/).map do |part|
|
136
|
+
value, parameters = part.split(/\s*;\s*/, 2)
|
148
137
|
quality = 1.0
|
149
138
|
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
150
139
|
quality = md[1].to_f
|
@@ -153,6 +142,19 @@ module Rack
|
|
153
142
|
end
|
154
143
|
end
|
155
144
|
|
145
|
+
def forwarded_values(forwarded_header)
|
146
|
+
return nil unless forwarded_header
|
147
|
+
forwarded_header = forwarded_header.to_s.gsub("\n", ";")
|
148
|
+
|
149
|
+
forwarded_header.split(/\s*;\s*/).each_with_object({}) do |field, values|
|
150
|
+
field.split(/\s*,\s*/).each do |pair|
|
151
|
+
return nil unless pair =~ /\A\s*(by|for|host|proto)\s*=\s*"?([^"]+)"?\s*\Z/i
|
152
|
+
(values[$1.downcase.to_sym] ||= []) << $2
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
module_function :forwarded_values
|
157
|
+
|
156
158
|
# Return best accept value to use, based on the algorithm
|
157
159
|
# in RFC 2616 Section 14. If there are multiple best
|
158
160
|
# matches (same specificity and quality), the value returned
|
@@ -167,7 +169,7 @@ module Rack
|
|
167
169
|
end.compact.sort_by do |match, quality|
|
168
170
|
(match.split('/', 2).count('*') * -10) + quality
|
169
171
|
end.last
|
170
|
-
matches
|
172
|
+
matches&.first
|
171
173
|
end
|
172
174
|
|
173
175
|
ESCAPE_HTML = {
|
@@ -218,17 +220,20 @@ module Rack
|
|
218
220
|
(encoding_candidates & available_encodings)[0]
|
219
221
|
end
|
220
222
|
|
221
|
-
|
222
|
-
|
223
|
-
|
223
|
+
# :call-seq:
|
224
|
+
# parse_cookies_header(value) -> hash
|
225
|
+
#
|
226
|
+
# Parse cookies from the provided header +value+ according to RFC6265. The
|
227
|
+
# syntax for cookie headers only supports semicolons. Returns a map of
|
228
|
+
# cookie +key+ to cookie +value+.
|
229
|
+
#
|
230
|
+
# parse_cookies_header('myname=myvalue; max-age=0')
|
231
|
+
# # => {"myname"=>"myvalue", "max-age"=>"0"}
|
232
|
+
#
|
233
|
+
def parse_cookies_header(value)
|
234
|
+
return {} unless value
|
224
235
|
|
225
|
-
|
226
|
-
# According to RFC 6265:
|
227
|
-
# The syntax for cookie headers only supports semicolons
|
228
|
-
# User Agent -> Server ==
|
229
|
-
# Cookie: SID=31d4d96e407aad42; lang=en-US
|
230
|
-
return {} unless header
|
231
|
-
header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
|
236
|
+
value.split(/; */n).each_with_object({}) do |cookie, cookies|
|
232
237
|
next if cookie.empty?
|
233
238
|
key, value = cookie.split('=', 2)
|
234
239
|
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
@@ -236,14 +241,66 @@ module Rack
|
|
236
241
|
end
|
237
242
|
|
238
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
|
+
# :call-seq:
|
259
|
+
# parse_cookies(env) -> hash
|
260
|
+
#
|
261
|
+
# Parse cookies from the provided request environment using
|
262
|
+
# parse_cookies_header. Returns a map of cookie +key+ to cookie +value+.
|
263
|
+
#
|
264
|
+
# parse_cookies({'HTTP_COOKIE' => 'myname=myvalue'})
|
265
|
+
# # => {'myname' => 'myvalue'}
|
266
|
+
#
|
267
|
+
def parse_cookies(env)
|
268
|
+
parse_cookies_header env[HTTP_COOKIE]
|
269
|
+
end
|
270
|
+
|
271
|
+
# :call-seq:
|
272
|
+
# set_cookie_header(key, value) -> encoded string
|
273
|
+
#
|
274
|
+
# Generate an encoded string using the provided +key+ and +value+ suitable
|
275
|
+
# for the +set-cookie+ header according to RFC6265. The +value+ may be an
|
276
|
+
# instance of either +String+ or +Hash+.
|
277
|
+
#
|
278
|
+
# If the cookie +value+ is an instance of +Hash+, it considers the following
|
279
|
+
# cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
|
280
|
+
# of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
|
281
|
+
# details about the interpretation of these fields, consult
|
282
|
+
# [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
|
283
|
+
#
|
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
|
+
# set_cookie_header("myname", "myvalue")
|
289
|
+
# # => "myname=myvalue"
|
290
|
+
#
|
291
|
+
# set_cookie_header("myname", {value: "myvalue", max_age: 10})
|
292
|
+
# # => "myname=myvalue; max-age=10"
|
293
|
+
#
|
294
|
+
def set_cookie_header(key, value)
|
239
295
|
case value
|
240
296
|
when Hash
|
297
|
+
key = escape(key) unless value[:escape_key] == false
|
241
298
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
242
299
|
path = "; path=#{value[:path]}" if value[:path]
|
243
300
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
244
301
|
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
245
302
|
secure = "; secure" if value[:secure]
|
246
|
-
httponly = ";
|
303
|
+
httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
247
304
|
same_site =
|
248
305
|
case value[:same_site]
|
249
306
|
when false, nil
|
@@ -258,100 +315,109 @@ module Rack
|
|
258
315
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
259
316
|
end
|
260
317
|
value = value[:value]
|
318
|
+
else
|
319
|
+
key = escape(key)
|
261
320
|
end
|
321
|
+
|
262
322
|
value = [value] unless Array === value
|
263
323
|
|
264
|
-
|
324
|
+
return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
265
325
|
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
326
|
+
end
|
266
327
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
328
|
+
# :call-seq:
|
329
|
+
# set_cookie_header!(headers, key, value) -> header value
|
330
|
+
#
|
331
|
+
# Append a cookie in the specified headers with the given cookie +key+ and
|
332
|
+
# +value+ using set_cookie_header.
|
333
|
+
#
|
334
|
+
# If the headers already contains a +set-cookie+ key, it will be converted
|
335
|
+
# to an +Array+ if not already, and appended to.
|
336
|
+
def set_cookie_header!(headers, key, value)
|
337
|
+
if header = headers[SET_COOKIE]
|
338
|
+
if header.is_a?(Array)
|
339
|
+
header << set_cookie_header(key, value)
|
340
|
+
else
|
341
|
+
headers[SET_COOKIE] = [header, set_cookie_header(key, value)]
|
342
|
+
end
|
274
343
|
else
|
275
|
-
|
344
|
+
headers[SET_COOKIE] = set_cookie_header(key, value)
|
276
345
|
end
|
277
346
|
end
|
278
347
|
|
279
|
-
|
280
|
-
|
281
|
-
|
348
|
+
# :call-seq:
|
349
|
+
# delete_set_cookie_header(key, value = {}) -> encoded string
|
350
|
+
#
|
351
|
+
# Generate an encoded string based on the given +key+ and +value+ using
|
352
|
+
# set_cookie_header for the purpose of causing the specified cookie to be
|
353
|
+
# deleted. The +value+ may be an instance of +Hash+ and can include
|
354
|
+
# attributes as outlined by set_cookie_header. The encoded cookie will have
|
355
|
+
# a +max_age+ of 0 seconds, an +expires+ date in the past and an empty
|
356
|
+
# +value+. When used with the +set-cookie+ header, it will cause the client
|
357
|
+
# to *remove* any matching cookie.
|
358
|
+
#
|
359
|
+
# delete_set_cookie_header("myname")
|
360
|
+
# # => "myname=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
|
361
|
+
#
|
362
|
+
def delete_set_cookie_header(key, value = {})
|
363
|
+
set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
|
282
364
|
end
|
283
365
|
|
284
366
|
def make_delete_cookie_header(header, key, value)
|
285
|
-
|
286
|
-
when nil, ''
|
287
|
-
cookies = []
|
288
|
-
when String
|
289
|
-
cookies = header.split("\n")
|
290
|
-
when Array
|
291
|
-
cookies = header
|
292
|
-
end
|
293
|
-
|
294
|
-
key = escape(key)
|
295
|
-
domain = value[:domain]
|
296
|
-
path = value[:path]
|
297
|
-
regexp = if domain
|
298
|
-
if path
|
299
|
-
/\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
|
300
|
-
else
|
301
|
-
/\A#{key}=.*domain=#{domain}(?:;|$)/
|
302
|
-
end
|
303
|
-
elsif path
|
304
|
-
/\A#{key}=.*path=#{path}(?:;|$)/
|
305
|
-
else
|
306
|
-
/\A#{key}=/
|
307
|
-
end
|
367
|
+
warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
308
368
|
|
309
|
-
|
310
|
-
|
311
|
-
cookies.join("\n")
|
369
|
+
delete_set_cookie_header!(header, key, value)
|
312
370
|
end
|
313
371
|
|
314
|
-
def delete_cookie_header!(
|
315
|
-
|
316
|
-
|
372
|
+
def delete_cookie_header!(headers, key, value = {})
|
373
|
+
headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
|
374
|
+
|
375
|
+
return nil
|
317
376
|
end
|
318
377
|
|
319
|
-
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
320
|
-
# strange method name.
|
321
378
|
def add_remove_cookie_to_header(header, key, value = {})
|
322
|
-
|
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)
|
323
380
|
|
324
|
-
|
325
|
-
|
326
|
-
max_age: '0',
|
327
|
-
expires: Time.at(0) }.merge(value))
|
381
|
+
delete_set_cookie_header!(header, key, value)
|
382
|
+
end
|
328
383
|
|
384
|
+
# :call-seq:
|
385
|
+
# delete_set_cookie_header!(header, key, value = {}) -> header value
|
386
|
+
#
|
387
|
+
# Set an expired cookie in the specified headers with the given cookie
|
388
|
+
# +key+ and +value+ using delete_set_cookie_header. This causes
|
389
|
+
# the client to immediately delete the specified cookie.
|
390
|
+
#
|
391
|
+
# delete_set_cookie_header!(nil, "mycookie")
|
392
|
+
# # => "mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
|
393
|
+
#
|
394
|
+
# If the header is non-nil, it will be modified in place.
|
395
|
+
#
|
396
|
+
# header = []
|
397
|
+
# delete_set_cookie_header!(header, "mycookie")
|
398
|
+
# # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
|
399
|
+
# header
|
400
|
+
# # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
|
401
|
+
#
|
402
|
+
def delete_set_cookie_header!(header, key, value = {})
|
403
|
+
if header
|
404
|
+
header = Array(header)
|
405
|
+
header << delete_set_cookie_header(key, value)
|
406
|
+
else
|
407
|
+
header = delete_set_cookie_header(key, value)
|
408
|
+
end
|
409
|
+
|
410
|
+
return header
|
329
411
|
end
|
330
412
|
|
331
413
|
def rfc2822(time)
|
332
414
|
time.rfc2822
|
333
415
|
end
|
334
416
|
|
335
|
-
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
336
|
-
# of '% %b %Y'.
|
337
|
-
# It assumes that the time is in GMT to comply to the RFC 2109.
|
338
|
-
#
|
339
|
-
# NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
|
340
|
-
# that I'm certain someone implemented only that option.
|
341
|
-
# Do not use %a and %b from Time.strptime, it would use localized names for
|
342
|
-
# weekday and month.
|
343
|
-
#
|
344
|
-
def rfc2109(time)
|
345
|
-
wday = RFC2822_DAY_NAME[time.wday]
|
346
|
-
mon = RFC2822_MONTH_NAME[time.mon - 1]
|
347
|
-
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
348
|
-
end
|
349
|
-
|
350
417
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
351
418
|
# Returns nil if the header is missing or syntactically invalid.
|
352
419
|
# Returns an empty array if none of the ranges are satisfiable.
|
353
420
|
def byte_ranges(env, size)
|
354
|
-
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
355
421
|
get_byte_ranges env['HTTP_RANGE'], size
|
356
422
|
end
|
357
423
|
|
@@ -360,18 +426,17 @@ module Rack
|
|
360
426
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
361
427
|
ranges = []
|
362
428
|
$1.split(/,\s*/).each do |range_spec|
|
363
|
-
return nil
|
364
|
-
|
365
|
-
r0
|
366
|
-
|
367
|
-
return nil if r1.nil?
|
429
|
+
return nil unless range_spec =~ /(\d*)-(\d*)/
|
430
|
+
r0, r1 = $1, $2
|
431
|
+
if r0.empty?
|
432
|
+
return nil if r1.empty?
|
368
433
|
# suffix-byte-range-spec, represents trailing suffix of file
|
369
434
|
r0 = size - r1.to_i
|
370
435
|
r0 = 0 if r0 < 0
|
371
436
|
r1 = size - 1
|
372
437
|
else
|
373
438
|
r0 = r0.to_i
|
374
|
-
if r1.
|
439
|
+
if r1.empty?
|
375
440
|
r1 = size - 1
|
376
441
|
else
|
377
442
|
r1 = r1.to_i
|
@@ -381,26 +446,33 @@ module Rack
|
|
381
446
|
end
|
382
447
|
ranges << (r0..r1) if r0 <= r1
|
383
448
|
end
|
384
|
-
|
385
|
-
return [] if ranges.map(&:size).inject(0, :+) > size
|
386
|
-
|
387
449
|
ranges
|
388
450
|
end
|
389
451
|
|
390
|
-
#
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
452
|
+
# :nocov:
|
453
|
+
if defined?(OpenSSL.fixed_length_secure_compare)
|
454
|
+
# Constant time string comparison.
|
455
|
+
#
|
456
|
+
# NOTE: the values compared should be of fixed length, such as strings
|
457
|
+
# that have already been processed by HMAC. This should not be used
|
458
|
+
# on variable length plaintext strings because it could leak length info
|
459
|
+
# via timing attacks.
|
460
|
+
def secure_compare(a, b)
|
461
|
+
return false unless a.bytesize == b.bytesize
|
398
462
|
|
399
|
-
|
463
|
+
OpenSSL.fixed_length_secure_compare(a, b)
|
464
|
+
end
|
465
|
+
# :nocov:
|
466
|
+
else
|
467
|
+
def secure_compare(a, b)
|
468
|
+
return false unless a.bytesize == b.bytesize
|
400
469
|
|
401
|
-
|
402
|
-
|
403
|
-
|
470
|
+
l = a.unpack("C*")
|
471
|
+
|
472
|
+
r, i = 0, -1
|
473
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
474
|
+
r == 0
|
475
|
+
end
|
404
476
|
end
|
405
477
|
|
406
478
|
# Context allows the use of a compatible middleware at different points
|
@@ -429,94 +501,32 @@ module Rack
|
|
429
501
|
end
|
430
502
|
end
|
431
503
|
|
432
|
-
# A
|
504
|
+
# A wrapper around Headers
|
433
505
|
# header when set.
|
434
506
|
#
|
435
507
|
# @api private
|
436
508
|
class HeaderHash < Hash # :nodoc:
|
437
509
|
def self.[](headers)
|
438
|
-
|
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?
|
439
512
|
return headers
|
440
|
-
else
|
441
|
-
return self.new(headers)
|
442
513
|
end
|
443
|
-
end
|
444
|
-
|
445
|
-
def initialize(hash = {})
|
446
|
-
super()
|
447
|
-
@names = {}
|
448
|
-
hash.each { |k, v| self[k] = v }
|
449
|
-
end
|
450
|
-
|
451
|
-
# on dup/clone, we need to duplicate @names hash
|
452
|
-
def initialize_copy(other)
|
453
|
-
super
|
454
|
-
@names = other.names.dup
|
455
|
-
end
|
456
|
-
|
457
|
-
# on clear, we need to clear @names hash
|
458
|
-
def clear
|
459
|
-
super
|
460
|
-
@names.clear
|
461
|
-
end
|
462
|
-
|
463
|
-
def each
|
464
|
-
super do |k, v|
|
465
|
-
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
466
|
-
end
|
467
|
-
end
|
468
514
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
hash
|
515
|
+
new_headers = Headers.new
|
516
|
+
headers.each{|k,v| new_headers[k] = v}
|
517
|
+
new_headers
|
473
518
|
end
|
474
519
|
|
475
|
-
def
|
476
|
-
|
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
|
477
525
|
end
|
478
526
|
|
479
|
-
def
|
480
|
-
|
481
|
-
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
482
|
-
@names[canonical] = k
|
483
|
-
super k, v
|
527
|
+
def self.allocate
|
528
|
+
raise TypeError, "cannot allocate HeaderHash"
|
484
529
|
end
|
485
|
-
|
486
|
-
def delete(k)
|
487
|
-
canonical = k.downcase
|
488
|
-
result = super @names.delete(canonical)
|
489
|
-
result
|
490
|
-
end
|
491
|
-
|
492
|
-
def include?(k)
|
493
|
-
super || @names.include?(k.downcase)
|
494
|
-
end
|
495
|
-
|
496
|
-
alias_method :has_key?, :include?
|
497
|
-
alias_method :member?, :include?
|
498
|
-
alias_method :key?, :include?
|
499
|
-
|
500
|
-
def merge!(other)
|
501
|
-
other.each { |k, v| self[k] = v }
|
502
|
-
self
|
503
|
-
end
|
504
|
-
|
505
|
-
def merge(other)
|
506
|
-
hash = dup
|
507
|
-
hash.merge! other
|
508
|
-
end
|
509
|
-
|
510
|
-
def replace(other)
|
511
|
-
clear
|
512
|
-
other.each { |k, v| self[k] = v }
|
513
|
-
self
|
514
|
-
end
|
515
|
-
|
516
|
-
protected
|
517
|
-
def names
|
518
|
-
@names
|
519
|
-
end
|
520
530
|
end
|
521
531
|
|
522
532
|
# Every standard HTTP code mapped to the appropriate message.
|
data/lib/rack/version.rb
CHANGED
@@ -13,14 +13,19 @@
|
|
13
13
|
|
14
14
|
module Rack
|
15
15
|
# The Rack protocol version number implemented.
|
16
|
-
VERSION = [1, 3]
|
16
|
+
VERSION = [1, 3].freeze
|
17
|
+
deprecate_constant :VERSION
|
17
18
|
|
18
|
-
|
19
|
+
VERSION_STRING = "1.3".freeze
|
20
|
+
deprecate_constant :VERSION_STRING
|
21
|
+
|
22
|
+
# The Rack protocol version number implemented.
|
19
23
|
def self.version
|
20
|
-
|
24
|
+
warn "Rack.version is deprecated and will be removed in Rack 3.1!", uplevel: 1
|
25
|
+
VERSION
|
21
26
|
end
|
22
27
|
|
23
|
-
RELEASE = "
|
28
|
+
RELEASE = "3.0.0.beta1"
|
24
29
|
|
25
30
|
# Return the Rack release as a dotted string.
|
26
31
|
def self.release
|