rack 2.2.23 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +148 -183
- data/CONTRIBUTING.md +53 -47
- data/MIT-LICENSE +1 -1
- data/README.md +293 -0
- data/SPEC.rdoc +174 -126
- 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 +63 -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 +12 -9
- 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 +16 -18
- data/lib/rack/head.rb +9 -8
- data/lib/rack/headers.rb +154 -0
- data/lib/rack/lint.rb +754 -648
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +7 -17
- data/lib/rack/method_override.rb +5 -1
- data/lib/rack/mime.rb +8 -0
- data/lib/rack/mock.rb +1 -300
- data/lib/rack/mock_request.rb +166 -0
- data/lib/rack/mock_response.rb +126 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +119 -160
- 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 +78 -91
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +190 -95
- 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 +40 -65
- data/lib/rack/show_exceptions.rb +15 -2
- data/lib/rack/show_status.rb +17 -7
- data/lib/rack/static.rb +12 -17
- data/lib/rack/tempfile_reaper.rb +15 -4
- data/lib/rack/urlmap.rb +4 -2
- data/lib/rack/utils.rb +219 -240
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +5 -76
- metadata +18 -35
- data/README.rdoc +0 -355
- data/Rakefile +0 -130
- data/bin/rackup +0 -5
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +0 -150
- data/contrib/rack_logo.svg +0 -164
- data/contrib/rdoc.css +0 -412
- data/example/lobster.ru +0 -6
- data/example/protectedlobster.rb +0 -16
- data/example/protectedlobster.ru +0 -10
- data/lib/rack/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 -34
- 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 -90
- data/rack.gemspec +0 -46
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 = {
|
|
@@ -186,41 +188,17 @@ module Rack
|
|
|
186
188
|
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
|
187
189
|
end
|
|
188
190
|
|
|
189
|
-
# Given an array of available encoding strings, and an array of
|
|
190
|
-
# acceptable encodings for a request, where each element of the
|
|
191
|
-
# acceptable encodings array is an array where the first element
|
|
192
|
-
# is an encoding name and the second element is the numeric
|
|
193
|
-
# priority for the encoding, return the available encoding with
|
|
194
|
-
# the highest priority.
|
|
195
|
-
#
|
|
196
|
-
# The accept_encoding argument is typically generated by calling
|
|
197
|
-
# Request#accept_encoding.
|
|
198
|
-
#
|
|
199
|
-
# Example:
|
|
200
|
-
#
|
|
201
|
-
# select_best_encoding(%w(compress gzip identity),
|
|
202
|
-
# [["compress", 0.5], ["gzip", 1.0]])
|
|
203
|
-
# # => "gzip"
|
|
204
|
-
#
|
|
205
|
-
# To reduce denial of service potential, only the first 16
|
|
206
|
-
# acceptable encodings are considered.
|
|
207
191
|
def select_best_encoding(available_encodings, accept_encoding)
|
|
208
192
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
209
193
|
|
|
210
|
-
# Only process the first 16 encodings
|
|
211
|
-
accept_encoding = accept_encoding[0...16]
|
|
212
194
|
expanded_accept_encoding = []
|
|
213
|
-
wildcard_seen = false
|
|
214
195
|
|
|
215
196
|
accept_encoding.each do |m, q|
|
|
216
197
|
preference = available_encodings.index(m) || available_encodings.size
|
|
217
198
|
|
|
218
199
|
if m == "*"
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
expanded_accept_encoding << [m2, q, preference]
|
|
222
|
-
end
|
|
223
|
-
wildcard_seen = true
|
|
200
|
+
(available_encodings - accept_encoding.map(&:first)).each do |m2|
|
|
201
|
+
expanded_accept_encoding << [m2, q, preference]
|
|
224
202
|
end
|
|
225
203
|
else
|
|
226
204
|
expanded_accept_encoding << [m, q, preference]
|
|
@@ -228,13 +206,7 @@ module Rack
|
|
|
228
206
|
end
|
|
229
207
|
|
|
230
208
|
encoding_candidates = expanded_accept_encoding
|
|
231
|
-
.
|
|
232
|
-
if r = (q1 <=> q2).nonzero?
|
|
233
|
-
-r
|
|
234
|
-
else
|
|
235
|
-
(p1 <=> p2).nonzero? || 0
|
|
236
|
-
end
|
|
237
|
-
end
|
|
209
|
+
.sort_by { |_, q, p| [-q, p] }
|
|
238
210
|
.map!(&:first)
|
|
239
211
|
|
|
240
212
|
unless encoding_candidates.include?("identity")
|
|
@@ -248,17 +220,20 @@ module Rack
|
|
|
248
220
|
(encoding_candidates & available_encodings)[0]
|
|
249
221
|
end
|
|
250
222
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
|
254
235
|
|
|
255
|
-
|
|
256
|
-
# According to RFC 6265:
|
|
257
|
-
# The syntax for cookie headers only supports semicolons
|
|
258
|
-
# User Agent -> Server ==
|
|
259
|
-
# Cookie: SID=31d4d96e407aad42; lang=en-US
|
|
260
|
-
return {} unless header
|
|
261
|
-
header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
|
|
236
|
+
value.split(/; */n).each_with_object({}) do |cookie, cookies|
|
|
262
237
|
next if cookie.empty?
|
|
263
238
|
key, value = cookie.split('=', 2)
|
|
264
239
|
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
|
@@ -266,14 +241,66 @@ module Rack
|
|
|
266
241
|
end
|
|
267
242
|
|
|
268
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)
|
|
269
295
|
case value
|
|
270
296
|
when Hash
|
|
297
|
+
key = escape(key) unless value[:escape_key] == false
|
|
271
298
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
|
272
299
|
path = "; path=#{value[:path]}" if value[:path]
|
|
273
300
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
|
274
301
|
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
|
275
302
|
secure = "; secure" if value[:secure]
|
|
276
|
-
httponly = ";
|
|
303
|
+
httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
|
277
304
|
same_site =
|
|
278
305
|
case value[:same_site]
|
|
279
306
|
when false, nil
|
|
@@ -288,121 +315,128 @@ module Rack
|
|
|
288
315
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
|
289
316
|
end
|
|
290
317
|
value = value[:value]
|
|
318
|
+
else
|
|
319
|
+
key = escape(key)
|
|
291
320
|
end
|
|
321
|
+
|
|
292
322
|
value = [value] unless Array === value
|
|
293
323
|
|
|
294
|
-
|
|
324
|
+
return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
|
295
325
|
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
|
326
|
+
end
|
|
296
327
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
304
343
|
else
|
|
305
|
-
|
|
344
|
+
headers[SET_COOKIE] = set_cookie_header(key, value)
|
|
306
345
|
end
|
|
307
346
|
end
|
|
308
347
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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: ''))
|
|
312
364
|
end
|
|
313
365
|
|
|
314
366
|
def make_delete_cookie_header(header, key, value)
|
|
315
|
-
|
|
316
|
-
when nil, ''
|
|
317
|
-
cookies = []
|
|
318
|
-
when String
|
|
319
|
-
cookies = header.split("\n")
|
|
320
|
-
when Array
|
|
321
|
-
cookies = header
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
key = escape(key)
|
|
325
|
-
domain = value[:domain]
|
|
326
|
-
path = value[:path]
|
|
327
|
-
regexp = if domain
|
|
328
|
-
if path
|
|
329
|
-
/\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
|
|
330
|
-
else
|
|
331
|
-
/\A#{key}=.*domain=#{domain}(?:;|$)/
|
|
332
|
-
end
|
|
333
|
-
elsif path
|
|
334
|
-
/\A#{key}=.*path=#{path}(?:;|$)/
|
|
335
|
-
else
|
|
336
|
-
/\A#{key}=/
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
cookies.reject! { |cookie| regexp.match? cookie }
|
|
367
|
+
warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
|
340
368
|
|
|
341
|
-
|
|
369
|
+
delete_set_cookie_header!(header, key, value)
|
|
342
370
|
end
|
|
343
371
|
|
|
344
|
-
def delete_cookie_header!(
|
|
345
|
-
|
|
346
|
-
|
|
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
|
|
347
376
|
end
|
|
348
377
|
|
|
349
|
-
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
|
350
|
-
# strange method name.
|
|
351
378
|
def add_remove_cookie_to_header(header, key, value = {})
|
|
352
|
-
|
|
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
|
|
353
383
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
358
409
|
|
|
410
|
+
return header
|
|
359
411
|
end
|
|
360
412
|
|
|
361
413
|
def rfc2822(time)
|
|
362
414
|
time.rfc2822
|
|
363
415
|
end
|
|
364
416
|
|
|
365
|
-
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
|
366
|
-
# of '% %b %Y'.
|
|
367
|
-
# It assumes that the time is in GMT to comply to the RFC 2109.
|
|
368
|
-
#
|
|
369
|
-
# NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
|
|
370
|
-
# that I'm certain someone implemented only that option.
|
|
371
|
-
# Do not use %a and %b from Time.strptime, it would use localized names for
|
|
372
|
-
# weekday and month.
|
|
373
|
-
#
|
|
374
|
-
def rfc2109(time)
|
|
375
|
-
wday = RFC2822_DAY_NAME[time.wday]
|
|
376
|
-
mon = RFC2822_MONTH_NAME[time.mon - 1]
|
|
377
|
-
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
|
378
|
-
end
|
|
379
|
-
|
|
380
417
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
|
381
418
|
# Returns nil if the header is missing or syntactically invalid.
|
|
382
419
|
# Returns an empty array if none of the ranges are satisfiable.
|
|
383
|
-
def byte_ranges(env, size
|
|
384
|
-
get_byte_ranges env['HTTP_RANGE'], size
|
|
420
|
+
def byte_ranges(env, size)
|
|
421
|
+
get_byte_ranges env['HTTP_RANGE'], size
|
|
385
422
|
end
|
|
386
423
|
|
|
387
|
-
def get_byte_ranges(http_range, size
|
|
424
|
+
def get_byte_ranges(http_range, size)
|
|
388
425
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
|
389
426
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
|
390
|
-
byte_range = $1
|
|
391
|
-
return nil if byte_range.count(',') >= max_ranges
|
|
392
427
|
ranges = []
|
|
393
|
-
|
|
394
|
-
return nil
|
|
395
|
-
|
|
396
|
-
r0
|
|
397
|
-
|
|
398
|
-
return nil if r1.nil?
|
|
428
|
+
$1.split(/,\s*/).each do |range_spec|
|
|
429
|
+
return nil unless range_spec =~ /(\d*)-(\d*)/
|
|
430
|
+
r0, r1 = $1, $2
|
|
431
|
+
if r0.empty?
|
|
432
|
+
return nil if r1.empty?
|
|
399
433
|
# suffix-byte-range-spec, represents trailing suffix of file
|
|
400
434
|
r0 = size - r1.to_i
|
|
401
435
|
r0 = 0 if r0 < 0
|
|
402
436
|
r1 = size - 1
|
|
403
437
|
else
|
|
404
438
|
r0 = r0.to_i
|
|
405
|
-
if r1.
|
|
439
|
+
if r1.empty?
|
|
406
440
|
r1 = size - 1
|
|
407
441
|
else
|
|
408
442
|
r1 = r1.to_i
|
|
@@ -412,26 +446,33 @@ module Rack
|
|
|
412
446
|
end
|
|
413
447
|
ranges << (r0..r1) if r0 <= r1
|
|
414
448
|
end
|
|
415
|
-
|
|
416
|
-
return [] if ranges.map(&:size).inject(0, :+) > size
|
|
417
|
-
|
|
418
449
|
ranges
|
|
419
450
|
end
|
|
420
451
|
|
|
421
|
-
#
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
|
429
462
|
|
|
430
|
-
|
|
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
|
|
431
469
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
|
435
476
|
end
|
|
436
477
|
|
|
437
478
|
# Context allows the use of a compatible middleware at different points
|
|
@@ -460,94 +501,32 @@ module Rack
|
|
|
460
501
|
end
|
|
461
502
|
end
|
|
462
503
|
|
|
463
|
-
# A
|
|
504
|
+
# A wrapper around Headers
|
|
464
505
|
# header when set.
|
|
465
506
|
#
|
|
466
507
|
# @api private
|
|
467
508
|
class HeaderHash < Hash # :nodoc:
|
|
468
509
|
def self.[](headers)
|
|
469
|
-
|
|
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?
|
|
470
512
|
return headers
|
|
471
|
-
else
|
|
472
|
-
return self.new(headers)
|
|
473
|
-
end
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
def initialize(hash = {})
|
|
477
|
-
super()
|
|
478
|
-
@names = {}
|
|
479
|
-
hash.each { |k, v| self[k] = v }
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
# on dup/clone, we need to duplicate @names hash
|
|
483
|
-
def initialize_copy(other)
|
|
484
|
-
super
|
|
485
|
-
@names = other.names.dup
|
|
486
|
-
end
|
|
487
|
-
|
|
488
|
-
# on clear, we need to clear @names hash
|
|
489
|
-
def clear
|
|
490
|
-
super
|
|
491
|
-
@names.clear
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
def each
|
|
495
|
-
super do |k, v|
|
|
496
|
-
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
|
497
513
|
end
|
|
498
|
-
end
|
|
499
|
-
|
|
500
|
-
def to_hash
|
|
501
|
-
hash = {}
|
|
502
|
-
each { |k, v| hash[k] = v }
|
|
503
|
-
hash
|
|
504
|
-
end
|
|
505
|
-
|
|
506
|
-
def [](k)
|
|
507
|
-
super(k) || super(@names[k.downcase])
|
|
508
|
-
end
|
|
509
|
-
|
|
510
|
-
def []=(k, v)
|
|
511
|
-
canonical = k.downcase.freeze
|
|
512
|
-
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
|
513
|
-
@names[canonical] = k
|
|
514
|
-
super k, v
|
|
515
|
-
end
|
|
516
514
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
result
|
|
515
|
+
new_headers = Headers.new
|
|
516
|
+
headers.each{|k,v| new_headers[k] = v}
|
|
517
|
+
new_headers
|
|
521
518
|
end
|
|
522
519
|
|
|
523
|
-
def
|
|
524
|
-
|
|
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
525
|
end
|
|
526
526
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
alias_method :key?, :include?
|
|
530
|
-
|
|
531
|
-
def merge!(other)
|
|
532
|
-
other.each { |k, v| self[k] = v }
|
|
533
|
-
self
|
|
534
|
-
end
|
|
535
|
-
|
|
536
|
-
def merge(other)
|
|
537
|
-
hash = dup
|
|
538
|
-
hash.merge! other
|
|
527
|
+
def self.allocate
|
|
528
|
+
raise TypeError, "cannot allocate HeaderHash"
|
|
539
529
|
end
|
|
540
|
-
|
|
541
|
-
def replace(other)
|
|
542
|
-
clear
|
|
543
|
-
other.each { |k, v| self[k] = v }
|
|
544
|
-
self
|
|
545
|
-
end
|
|
546
|
-
|
|
547
|
-
protected
|
|
548
|
-
def names
|
|
549
|
-
@names
|
|
550
|
-
end
|
|
551
530
|
end
|
|
552
531
|
|
|
553
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"
|
|
24
29
|
|
|
25
30
|
# Return the Rack release as a dotted string.
|
|
26
31
|
def self.release
|