rack 2.2.8 → 3.0.4.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +187 -87
- 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 +0 -2
- data/lib/rack/auth/digest/md5.rb +1 -131
- data/lib/rack/auth/digest/nonce.rb +1 -54
- 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 +83 -63
- data/lib/rack/cascade.rb +2 -0
- data/lib/rack/chunked.rb +16 -13
- data/lib/rack/common_logger.rb +23 -18
- data/lib/rack/conditional_get.rb +18 -15
- data/lib/rack/constants.rb +64 -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 +758 -646
- data/lib/rack/lock.rb +2 -5
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +1 -1
- 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 +126 -0
- data/lib/rack/multipart/generator.rb +7 -5
- data/lib/rack/multipart/parser.rb +123 -79
- 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 +215 -108
- data/lib/rack/response.rb +136 -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 +3 -1
- data/lib/rack/utils.rb +206 -191
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +5 -76
- metadata +15 -35
- data/README.rdoc +0 -320
- 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 -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 -204
- data/lib/rack/session/memcache.rb +0 -10
- data/lib/rack/session/pool.rb +0 -85
- data/rack.gemspec +0 -46
data/lib/rack/static.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'constants'
|
4
|
+
require_relative 'files'
|
5
|
+
require_relative 'mime'
|
6
|
+
|
3
7
|
module Rack
|
4
8
|
|
5
9
|
# The Rack::Static middleware intercepts requests for static files
|
@@ -78,16 +82,14 @@ module Rack
|
|
78
82
|
# :header_rules => [
|
79
83
|
# # Cache all static files in public caches (e.g. Rack::Cache)
|
80
84
|
# # as well as in the browser
|
81
|
-
# [:all, {'
|
85
|
+
# [:all, {'cache-control' => 'public, max-age=31536000'}],
|
82
86
|
#
|
83
87
|
# # Provide web fonts with cross-origin access-control-headers
|
84
88
|
# # Firefox requires this when serving assets using a Content Delivery Network
|
85
|
-
# [:fonts, {'
|
89
|
+
# [:fonts, {'access-control-allow-origin' => '*'}]
|
86
90
|
# ]
|
87
91
|
#
|
88
92
|
class Static
|
89
|
-
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
90
|
-
|
91
93
|
def initialize(app, options = {})
|
92
94
|
@app = app
|
93
95
|
@urls = options[:urls] || ["/favicon.ico"]
|
@@ -137,10 +139,8 @@ module Rack
|
|
137
139
|
elsif response[0] == 304
|
138
140
|
# Do nothing, leave headers as is
|
139
141
|
else
|
140
|
-
|
141
|
-
|
142
|
-
end
|
143
|
-
response[1]['Content-Encoding'] = 'gzip'
|
142
|
+
response[1][CONTENT_TYPE] = Mime.mime_type(::File.extname(path), 'text/plain')
|
143
|
+
response[1]['content-encoding'] = 'gzip'
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
data/lib/rack/tempfile_reaper.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'constants'
|
4
|
+
require_relative 'body_proxy'
|
5
|
+
|
3
6
|
module Rack
|
4
7
|
|
5
8
|
# Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
|
@@ -12,11 +15,19 @@ module Rack
|
|
12
15
|
|
13
16
|
def call(env)
|
14
17
|
env[RACK_TEMPFILES] ||= []
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
+
|
19
|
+
begin
|
20
|
+
_, _, body = response = @app.call(env)
|
21
|
+
rescue Exception
|
22
|
+
env[RACK_TEMPFILES]&.each(&:close!)
|
23
|
+
raise
|
18
24
|
end
|
19
|
-
|
25
|
+
|
26
|
+
response[2] = BodyProxy.new(body) do
|
27
|
+
env[RACK_TEMPFILES]&.each(&:close!)
|
28
|
+
end
|
29
|
+
|
30
|
+
response
|
20
31
|
end
|
21
32
|
end
|
22
33
|
end
|
data/lib/rack/urlmap.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'set'
|
4
4
|
|
5
|
+
require_relative 'constants'
|
6
|
+
|
5
7
|
module Rack
|
6
8
|
# Rack::URLMap takes a hash mapping urls or paths to apps, and
|
7
9
|
# dispatches accordingly. Support for HTTP/1.1 host names exists if
|
@@ -74,7 +76,7 @@ module Rack
|
|
74
76
|
return app.call(env)
|
75
77
|
end
|
76
78
|
|
77
|
-
[404, { CONTENT_TYPE => "text/plain", "
|
79
|
+
[404, { CONTENT_TYPE => "text/plain", "x-cascade" => "pass" }, ["Not Found: #{path}"]]
|
78
80
|
|
79
81
|
ensure
|
80
82
|
env[PATH_INFO] = path
|
data/lib/rack/utils.rb
CHANGED
@@ -8,29 +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
|
-
|
28
27
|
class << self
|
29
28
|
attr_accessor :default_query_parser
|
30
29
|
end
|
31
|
-
# The default
|
32
|
-
# This helps prevent a rogue client from
|
33
|
-
|
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)
|
34
34
|
|
35
35
|
module_function
|
36
36
|
|
@@ -58,24 +58,13 @@ module Rack
|
|
58
58
|
end
|
59
59
|
|
60
60
|
class << self
|
61
|
-
attr_accessor :
|
62
|
-
|
63
|
-
attr_accessor :multipart_file_limit
|
64
|
-
|
65
|
-
# multipart_part_limit is the original name of multipart_file_limit, but
|
66
|
-
# the limit only counts parts with filenames.
|
67
|
-
alias multipart_part_limit multipart_file_limit
|
68
|
-
alias multipart_part_limit= multipart_file_limit=
|
61
|
+
attr_accessor :multipart_part_limit
|
69
62
|
end
|
70
63
|
|
71
|
-
# The maximum number of
|
72
|
-
#
|
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.
|
73
66
|
# Set to `0` for no limit.
|
74
|
-
self.
|
75
|
-
|
76
|
-
# The maximum total number of parts a request can contain. Accepting too
|
77
|
-
# many can lead to excessive memory use and parsing time.
|
78
|
-
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
|
79
68
|
|
80
69
|
def self.param_depth_limit
|
81
70
|
default_query_parser.param_depth_limit
|
@@ -86,11 +75,12 @@ module Rack
|
|
86
75
|
end
|
87
76
|
|
88
77
|
def self.key_space_limit
|
89
|
-
|
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
|
90
80
|
end
|
91
81
|
|
92
82
|
def self.key_space_limit=(v)
|
93
|
-
|
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)
|
94
84
|
end
|
95
85
|
|
96
86
|
if defined?(Process::CLOCK_MONOTONIC)
|
@@ -131,13 +121,13 @@ module Rack
|
|
131
121
|
}.join("&")
|
132
122
|
when Hash
|
133
123
|
value.map { |k, v|
|
134
|
-
build_nested_query(v, prefix ? "#{prefix}[#{
|
124
|
+
build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
|
135
125
|
}.delete_if(&:empty?).join('&')
|
136
126
|
when nil
|
137
|
-
prefix
|
127
|
+
escape(prefix)
|
138
128
|
else
|
139
129
|
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
140
|
-
"#{prefix}=#{escape(value)}"
|
130
|
+
"#{escape(prefix)}=#{escape(value)}"
|
141
131
|
end
|
142
132
|
end
|
143
133
|
|
@@ -152,6 +142,19 @@ module Rack
|
|
152
142
|
end
|
153
143
|
end
|
154
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
|
+
|
155
158
|
# Return best accept value to use, based on the algorithm
|
156
159
|
# in RFC 2616 Section 14. If there are multiple best
|
157
160
|
# matches (same specificity and quality), the value returned
|
@@ -166,7 +169,7 @@ module Rack
|
|
166
169
|
end.compact.sort_by do |match, quality|
|
167
170
|
(match.split('/', 2).count('*') * -10) + quality
|
168
171
|
end.last
|
169
|
-
matches
|
172
|
+
matches&.first
|
170
173
|
end
|
171
174
|
|
172
175
|
ESCAPE_HTML = {
|
@@ -217,17 +220,20 @@ module Rack
|
|
217
220
|
(encoding_candidates & available_encodings)[0]
|
218
221
|
end
|
219
222
|
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
223
235
|
|
224
|
-
|
225
|
-
# According to RFC 6265:
|
226
|
-
# The syntax for cookie headers only supports semicolons
|
227
|
-
# User Agent -> Server ==
|
228
|
-
# Cookie: SID=31d4d96e407aad42; lang=en-US
|
229
|
-
return {} unless header
|
230
|
-
header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
|
236
|
+
value.split(/; */n).each_with_object({}) do |cookie, cookies|
|
231
237
|
next if cookie.empty?
|
232
238
|
key, value = cookie.split('=', 2)
|
233
239
|
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
@@ -235,14 +241,66 @@ module Rack
|
|
235
241
|
end
|
236
242
|
|
237
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)
|
238
295
|
case value
|
239
296
|
when Hash
|
297
|
+
key = escape(key) unless value[:escape_key] == false
|
240
298
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
241
299
|
path = "; path=#{value[:path]}" if value[:path]
|
242
300
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
243
301
|
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
244
302
|
secure = "; secure" if value[:secure]
|
245
|
-
httponly = ";
|
303
|
+
httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
246
304
|
same_site =
|
247
305
|
case value[:same_site]
|
248
306
|
when false, nil
|
@@ -257,100 +315,109 @@ module Rack
|
|
257
315
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
258
316
|
end
|
259
317
|
value = value[:value]
|
318
|
+
else
|
319
|
+
key = escape(key)
|
260
320
|
end
|
321
|
+
|
261
322
|
value = [value] unless Array === value
|
262
323
|
|
263
|
-
|
324
|
+
return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
264
325
|
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
326
|
+
end
|
265
327
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
273
343
|
else
|
274
|
-
|
344
|
+
headers[SET_COOKIE] = set_cookie_header(key, value)
|
275
345
|
end
|
276
346
|
end
|
277
347
|
|
278
|
-
|
279
|
-
|
280
|
-
|
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: ''))
|
281
364
|
end
|
282
365
|
|
283
366
|
def make_delete_cookie_header(header, key, value)
|
284
|
-
|
285
|
-
when nil, ''
|
286
|
-
cookies = []
|
287
|
-
when String
|
288
|
-
cookies = header.split("\n")
|
289
|
-
when Array
|
290
|
-
cookies = header
|
291
|
-
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)
|
292
368
|
|
293
|
-
key
|
294
|
-
domain = value[:domain]
|
295
|
-
path = value[:path]
|
296
|
-
regexp = if domain
|
297
|
-
if path
|
298
|
-
/\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
|
299
|
-
else
|
300
|
-
/\A#{key}=.*domain=#{domain}(?:;|$)/
|
301
|
-
end
|
302
|
-
elsif path
|
303
|
-
/\A#{key}=.*path=#{path}(?:;|$)/
|
304
|
-
else
|
305
|
-
/\A#{key}=/
|
306
|
-
end
|
307
|
-
|
308
|
-
cookies.reject! { |cookie| regexp.match? cookie }
|
309
|
-
|
310
|
-
cookies.join("\n")
|
369
|
+
delete_set_cookie_header!(header, key, value)
|
311
370
|
end
|
312
371
|
|
313
|
-
def delete_cookie_header!(
|
314
|
-
|
315
|
-
|
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
|
316
376
|
end
|
317
377
|
|
318
|
-
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
319
|
-
# strange method name.
|
320
378
|
def add_remove_cookie_to_header(header, key, value = {})
|
321
|
-
|
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)
|
322
380
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
381
|
+
delete_set_cookie_header!(header, key, value)
|
382
|
+
end
|
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
|
327
409
|
|
410
|
+
return header
|
328
411
|
end
|
329
412
|
|
330
413
|
def rfc2822(time)
|
331
414
|
time.rfc2822
|
332
415
|
end
|
333
416
|
|
334
|
-
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
335
|
-
# of '% %b %Y'.
|
336
|
-
# It assumes that the time is in GMT to comply to the RFC 2109.
|
337
|
-
#
|
338
|
-
# NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
|
339
|
-
# that I'm certain someone implemented only that option.
|
340
|
-
# Do not use %a and %b from Time.strptime, it would use localized names for
|
341
|
-
# weekday and month.
|
342
|
-
#
|
343
|
-
def rfc2109(time)
|
344
|
-
wday = RFC2822_DAY_NAME[time.wday]
|
345
|
-
mon = RFC2822_MONTH_NAME[time.mon - 1]
|
346
|
-
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
347
|
-
end
|
348
|
-
|
349
417
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
350
418
|
# Returns nil if the header is missing or syntactically invalid.
|
351
419
|
# Returns an empty array if none of the ranges are satisfiable.
|
352
420
|
def byte_ranges(env, size)
|
353
|
-
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
354
421
|
get_byte_ranges env['HTTP_RANGE'], size
|
355
422
|
end
|
356
423
|
|
@@ -383,20 +450,30 @@ module Rack
|
|
383
450
|
ranges
|
384
451
|
end
|
385
452
|
|
386
|
-
#
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
453
|
+
# :nocov:
|
454
|
+
if defined?(OpenSSL.fixed_length_secure_compare)
|
455
|
+
# Constant time string comparison.
|
456
|
+
#
|
457
|
+
# NOTE: the values compared should be of fixed length, such as strings
|
458
|
+
# that have already been processed by HMAC. This should not be used
|
459
|
+
# on variable length plaintext strings because it could leak length info
|
460
|
+
# via timing attacks.
|
461
|
+
def secure_compare(a, b)
|
462
|
+
return false unless a.bytesize == b.bytesize
|
463
|
+
|
464
|
+
OpenSSL.fixed_length_secure_compare(a, b)
|
465
|
+
end
|
466
|
+
# :nocov:
|
467
|
+
else
|
468
|
+
def secure_compare(a, b)
|
469
|
+
return false unless a.bytesize == b.bytesize
|
394
470
|
|
395
|
-
|
471
|
+
l = a.unpack("C*")
|
396
472
|
|
397
|
-
|
398
|
-
|
399
|
-
|
473
|
+
r, i = 0, -1
|
474
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
475
|
+
r == 0
|
476
|
+
end
|
400
477
|
end
|
401
478
|
|
402
479
|
# Context allows the use of a compatible middleware at different points
|
@@ -425,94 +502,32 @@ module Rack
|
|
425
502
|
end
|
426
503
|
end
|
427
504
|
|
428
|
-
# A
|
505
|
+
# A wrapper around Headers
|
429
506
|
# header when set.
|
430
507
|
#
|
431
508
|
# @api private
|
432
509
|
class HeaderHash < Hash # :nodoc:
|
433
510
|
def self.[](headers)
|
434
|
-
|
511
|
+
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
512
|
+
if headers.is_a?(Headers) && !headers.frozen?
|
435
513
|
return headers
|
436
|
-
else
|
437
|
-
return self.new(headers)
|
438
514
|
end
|
439
|
-
end
|
440
|
-
|
441
|
-
def initialize(hash = {})
|
442
|
-
super()
|
443
|
-
@names = {}
|
444
|
-
hash.each { |k, v| self[k] = v }
|
445
|
-
end
|
446
|
-
|
447
|
-
# on dup/clone, we need to duplicate @names hash
|
448
|
-
def initialize_copy(other)
|
449
|
-
super
|
450
|
-
@names = other.names.dup
|
451
|
-
end
|
452
|
-
|
453
|
-
# on clear, we need to clear @names hash
|
454
|
-
def clear
|
455
|
-
super
|
456
|
-
@names.clear
|
457
|
-
end
|
458
|
-
|
459
|
-
def each
|
460
|
-
super do |k, v|
|
461
|
-
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
462
|
-
end
|
463
|
-
end
|
464
515
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
hash
|
516
|
+
new_headers = Headers.new
|
517
|
+
headers.each{|k,v| new_headers[k] = v}
|
518
|
+
new_headers
|
469
519
|
end
|
470
520
|
|
471
|
-
def
|
472
|
-
|
521
|
+
def self.new(hash = {})
|
522
|
+
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
523
|
+
headers = Headers.new
|
524
|
+
hash.each{|k,v| headers[k] = v}
|
525
|
+
headers
|
473
526
|
end
|
474
527
|
|
475
|
-
def
|
476
|
-
|
477
|
-
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
478
|
-
@names[canonical] = k
|
479
|
-
super k, v
|
528
|
+
def self.allocate
|
529
|
+
raise TypeError, "cannot allocate HeaderHash"
|
480
530
|
end
|
481
|
-
|
482
|
-
def delete(k)
|
483
|
-
canonical = k.downcase
|
484
|
-
result = super @names.delete(canonical)
|
485
|
-
result
|
486
|
-
end
|
487
|
-
|
488
|
-
def include?(k)
|
489
|
-
super || @names.include?(k.downcase)
|
490
|
-
end
|
491
|
-
|
492
|
-
alias_method :has_key?, :include?
|
493
|
-
alias_method :member?, :include?
|
494
|
-
alias_method :key?, :include?
|
495
|
-
|
496
|
-
def merge!(other)
|
497
|
-
other.each { |k, v| self[k] = v }
|
498
|
-
self
|
499
|
-
end
|
500
|
-
|
501
|
-
def merge(other)
|
502
|
-
hash = dup
|
503
|
-
hash.merge! other
|
504
|
-
end
|
505
|
-
|
506
|
-
def replace(other)
|
507
|
-
clear
|
508
|
-
other.each { |k, v| self[k] = v }
|
509
|
-
self
|
510
|
-
end
|
511
|
-
|
512
|
-
protected
|
513
|
-
def names
|
514
|
-
@names
|
515
|
-
end
|
516
531
|
end
|
517
532
|
|
518
533
|
# 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.4.1"
|
24
29
|
|
25
30
|
# Return the Rack release as a dotted string.
|
26
31
|
def self.release
|