rack 2.2.4 → 3.0.8
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 +223 -71
- data/CONTRIBUTING.md +53 -47
- data/MIT-LICENSE +1 -1
- data/README.md +309 -0
- data/SPEC.rdoc +183 -131
- 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 +783 -682
- 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 +6 -2
- 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 +134 -65
- 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 -46
- data/lib/rack/recursive.rb +2 -0
- data/lib/rack/reloader.rb +0 -2
- data/lib/rack/request.rb +226 -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 +4 -2
- data/lib/rack/utils.rb +223 -185
- data/lib/rack/version.rb +9 -4
- data/lib/rack.rb +6 -76
- metadata +18 -38
- data/README.rdoc +0 -306
- 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 -203
- 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
|
@@ -35,7 +37,7 @@ module Rack
|
|
35
37
|
end
|
36
38
|
|
37
39
|
location = location.chomp('/')
|
38
|
-
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)",
|
40
|
+
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
|
39
41
|
|
40
42
|
[host, location, match, app]
|
41
43
|
}.sort_by do |(host, location, _, _)|
|
@@ -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,13 +58,24 @@ module Rack
|
|
58
58
|
end
|
59
59
|
|
60
60
|
class << self
|
61
|
-
attr_accessor :
|
61
|
+
attr_accessor :multipart_total_part_limit
|
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=
|
62
69
|
end
|
63
70
|
|
64
|
-
# The maximum number of parts a request can contain. Accepting too
|
65
|
-
# can lead to the server running out of file handles.
|
71
|
+
# The maximum number of file parts a request can contain. Accepting too
|
72
|
+
# many parts can lead to the server running out of file handles.
|
66
73
|
# Set to `0` for no limit.
|
67
|
-
self.
|
74
|
+
self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
|
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
|
68
79
|
|
69
80
|
def self.param_depth_limit
|
70
81
|
default_query_parser.param_depth_limit
|
@@ -75,11 +86,12 @@ module Rack
|
|
75
86
|
end
|
76
87
|
|
77
88
|
def self.key_space_limit
|
78
|
-
|
89
|
+
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)
|
90
|
+
65536
|
79
91
|
end
|
80
92
|
|
81
93
|
def self.key_space_limit=(v)
|
82
|
-
|
94
|
+
warn("`Rack::Utils.key_space_limit=` is deprecated and no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
|
83
95
|
end
|
84
96
|
|
85
97
|
if defined?(Process::CLOCK_MONOTONIC)
|
@@ -120,13 +132,13 @@ module Rack
|
|
120
132
|
}.join("&")
|
121
133
|
when Hash
|
122
134
|
value.map { |k, v|
|
123
|
-
build_nested_query(v, prefix ? "#{prefix}[#{
|
135
|
+
build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
|
124
136
|
}.delete_if(&:empty?).join('&')
|
125
137
|
when nil
|
126
|
-
prefix
|
138
|
+
escape(prefix)
|
127
139
|
else
|
128
140
|
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
129
|
-
"#{prefix}=#{escape(value)}"
|
141
|
+
"#{escape(prefix)}=#{escape(value)}"
|
130
142
|
end
|
131
143
|
end
|
132
144
|
|
@@ -141,6 +153,19 @@ module Rack
|
|
141
153
|
end
|
142
154
|
end
|
143
155
|
|
156
|
+
def forwarded_values(forwarded_header)
|
157
|
+
return nil unless forwarded_header
|
158
|
+
forwarded_header = forwarded_header.to_s.gsub("\n", ";")
|
159
|
+
|
160
|
+
forwarded_header.split(/\s*;\s*/).each_with_object({}) do |field, values|
|
161
|
+
field.split(/\s*,\s*/).each do |pair|
|
162
|
+
return nil unless pair =~ /\A\s*(by|for|host|proto)\s*=\s*"?([^"]+)"?\s*\Z/i
|
163
|
+
(values[$1.downcase.to_sym] ||= []) << $2
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
module_function :forwarded_values
|
168
|
+
|
144
169
|
# Return best accept value to use, based on the algorithm
|
145
170
|
# in RFC 2616 Section 14. If there are multiple best
|
146
171
|
# matches (same specificity and quality), the value returned
|
@@ -155,7 +180,7 @@ module Rack
|
|
155
180
|
end.compact.sort_by do |match, quality|
|
156
181
|
(match.split('/', 2).count('*') * -10) + quality
|
157
182
|
end.last
|
158
|
-
matches
|
183
|
+
matches&.first
|
159
184
|
end
|
160
185
|
|
161
186
|
ESCAPE_HTML = {
|
@@ -206,17 +231,20 @@ module Rack
|
|
206
231
|
(encoding_candidates & available_encodings)[0]
|
207
232
|
end
|
208
233
|
|
209
|
-
|
210
|
-
|
211
|
-
|
234
|
+
# :call-seq:
|
235
|
+
# parse_cookies_header(value) -> hash
|
236
|
+
#
|
237
|
+
# Parse cookies from the provided header +value+ according to RFC6265. The
|
238
|
+
# syntax for cookie headers only supports semicolons. Returns a map of
|
239
|
+
# cookie +key+ to cookie +value+.
|
240
|
+
#
|
241
|
+
# parse_cookies_header('myname=myvalue; max-age=0')
|
242
|
+
# # => {"myname"=>"myvalue", "max-age"=>"0"}
|
243
|
+
#
|
244
|
+
def parse_cookies_header(value)
|
245
|
+
return {} unless value
|
212
246
|
|
213
|
-
|
214
|
-
# According to RFC 6265:
|
215
|
-
# The syntax for cookie headers only supports semicolons
|
216
|
-
# User Agent -> Server ==
|
217
|
-
# Cookie: SID=31d4d96e407aad42; lang=en-US
|
218
|
-
return {} unless header
|
219
|
-
header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
|
247
|
+
value.split(/; */n).each_with_object({}) do |cookie, cookies|
|
220
248
|
next if cookie.empty?
|
221
249
|
key, value = cookie.split('=', 2)
|
222
250
|
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
@@ -224,14 +252,66 @@ module Rack
|
|
224
252
|
end
|
225
253
|
|
226
254
|
def add_cookie_to_header(header, key, value)
|
255
|
+
warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
|
256
|
+
|
257
|
+
case header
|
258
|
+
when nil, ''
|
259
|
+
return set_cookie_header(key, value)
|
260
|
+
when String
|
261
|
+
[header, set_cookie_header(key, value)]
|
262
|
+
when Array
|
263
|
+
header + [set_cookie_header(key, value)]
|
264
|
+
else
|
265
|
+
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# :call-seq:
|
270
|
+
# parse_cookies(env) -> hash
|
271
|
+
#
|
272
|
+
# Parse cookies from the provided request environment using
|
273
|
+
# parse_cookies_header. Returns a map of cookie +key+ to cookie +value+.
|
274
|
+
#
|
275
|
+
# parse_cookies({'HTTP_COOKIE' => 'myname=myvalue'})
|
276
|
+
# # => {'myname' => 'myvalue'}
|
277
|
+
#
|
278
|
+
def parse_cookies(env)
|
279
|
+
parse_cookies_header env[HTTP_COOKIE]
|
280
|
+
end
|
281
|
+
|
282
|
+
# :call-seq:
|
283
|
+
# set_cookie_header(key, value) -> encoded string
|
284
|
+
#
|
285
|
+
# Generate an encoded string using the provided +key+ and +value+ suitable
|
286
|
+
# for the +set-cookie+ header according to RFC6265. The +value+ may be an
|
287
|
+
# instance of either +String+ or +Hash+.
|
288
|
+
#
|
289
|
+
# If the cookie +value+ is an instance of +Hash+, it considers the following
|
290
|
+
# cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
|
291
|
+
# of +Time+), +secure+, +http_only+, +same_site+ and +value+. For more
|
292
|
+
# details about the interpretation of these fields, consult
|
293
|
+
# [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
|
294
|
+
#
|
295
|
+
# An extra cookie attribute +escape_key+ can be provided to control whether
|
296
|
+
# or not the cookie key is URL encoded. If explicitly set to +false+, the
|
297
|
+
# cookie key name will not be url encoded (escaped). The default is +true+.
|
298
|
+
#
|
299
|
+
# set_cookie_header("myname", "myvalue")
|
300
|
+
# # => "myname=myvalue"
|
301
|
+
#
|
302
|
+
# set_cookie_header("myname", {value: "myvalue", max_age: 10})
|
303
|
+
# # => "myname=myvalue; max-age=10"
|
304
|
+
#
|
305
|
+
def set_cookie_header(key, value)
|
227
306
|
case value
|
228
307
|
when Hash
|
308
|
+
key = escape(key) unless value[:escape_key] == false
|
229
309
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
230
310
|
path = "; path=#{value[:path]}" if value[:path]
|
231
311
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
232
312
|
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
233
313
|
secure = "; secure" if value[:secure]
|
234
|
-
httponly = ";
|
314
|
+
httponly = "; httponly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
235
315
|
same_site =
|
236
316
|
case value[:same_site]
|
237
317
|
when false, nil
|
@@ -246,100 +326,109 @@ module Rack
|
|
246
326
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
247
327
|
end
|
248
328
|
value = value[:value]
|
329
|
+
else
|
330
|
+
key = escape(key)
|
249
331
|
end
|
332
|
+
|
250
333
|
value = [value] unless Array === value
|
251
334
|
|
252
|
-
|
335
|
+
return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
253
336
|
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
337
|
+
end
|
254
338
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
339
|
+
# :call-seq:
|
340
|
+
# set_cookie_header!(headers, key, value) -> header value
|
341
|
+
#
|
342
|
+
# Append a cookie in the specified headers with the given cookie +key+ and
|
343
|
+
# +value+ using set_cookie_header.
|
344
|
+
#
|
345
|
+
# If the headers already contains a +set-cookie+ key, it will be converted
|
346
|
+
# to an +Array+ if not already, and appended to.
|
347
|
+
def set_cookie_header!(headers, key, value)
|
348
|
+
if header = headers[SET_COOKIE]
|
349
|
+
if header.is_a?(Array)
|
350
|
+
header << set_cookie_header(key, value)
|
351
|
+
else
|
352
|
+
headers[SET_COOKIE] = [header, set_cookie_header(key, value)]
|
353
|
+
end
|
262
354
|
else
|
263
|
-
|
355
|
+
headers[SET_COOKIE] = set_cookie_header(key, value)
|
264
356
|
end
|
265
357
|
end
|
266
358
|
|
267
|
-
|
268
|
-
|
269
|
-
|
359
|
+
# :call-seq:
|
360
|
+
# delete_set_cookie_header(key, value = {}) -> encoded string
|
361
|
+
#
|
362
|
+
# Generate an encoded string based on the given +key+ and +value+ using
|
363
|
+
# set_cookie_header for the purpose of causing the specified cookie to be
|
364
|
+
# deleted. The +value+ may be an instance of +Hash+ and can include
|
365
|
+
# attributes as outlined by set_cookie_header. The encoded cookie will have
|
366
|
+
# a +max_age+ of 0 seconds, an +expires+ date in the past and an empty
|
367
|
+
# +value+. When used with the +set-cookie+ header, it will cause the client
|
368
|
+
# to *remove* any matching cookie.
|
369
|
+
#
|
370
|
+
# delete_set_cookie_header("myname")
|
371
|
+
# # => "myname=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
|
372
|
+
#
|
373
|
+
def delete_set_cookie_header(key, value = {})
|
374
|
+
set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
|
270
375
|
end
|
271
376
|
|
272
377
|
def make_delete_cookie_header(header, key, value)
|
273
|
-
|
274
|
-
when nil, ''
|
275
|
-
cookies = []
|
276
|
-
when String
|
277
|
-
cookies = header.split("\n")
|
278
|
-
when Array
|
279
|
-
cookies = header
|
280
|
-
end
|
378
|
+
warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
281
379
|
|
282
|
-
key
|
283
|
-
domain = value[:domain]
|
284
|
-
path = value[:path]
|
285
|
-
regexp = if domain
|
286
|
-
if path
|
287
|
-
/\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
|
288
|
-
else
|
289
|
-
/\A#{key}=.*domain=#{domain}(?:;|$)/
|
290
|
-
end
|
291
|
-
elsif path
|
292
|
-
/\A#{key}=.*path=#{path}(?:;|$)/
|
293
|
-
else
|
294
|
-
/\A#{key}=/
|
295
|
-
end
|
296
|
-
|
297
|
-
cookies.reject! { |cookie| regexp.match? cookie }
|
298
|
-
|
299
|
-
cookies.join("\n")
|
380
|
+
delete_set_cookie_header!(header, key, value)
|
300
381
|
end
|
301
382
|
|
302
|
-
def delete_cookie_header!(
|
303
|
-
|
304
|
-
|
383
|
+
def delete_cookie_header!(headers, key, value = {})
|
384
|
+
headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
|
385
|
+
|
386
|
+
return nil
|
305
387
|
end
|
306
388
|
|
307
|
-
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
308
|
-
# strange method name.
|
309
389
|
def add_remove_cookie_to_header(header, key, value = {})
|
310
|
-
|
390
|
+
warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
311
391
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
392
|
+
delete_set_cookie_header!(header, key, value)
|
393
|
+
end
|
394
|
+
|
395
|
+
# :call-seq:
|
396
|
+
# delete_set_cookie_header!(header, key, value = {}) -> header value
|
397
|
+
#
|
398
|
+
# Set an expired cookie in the specified headers with the given cookie
|
399
|
+
# +key+ and +value+ using delete_set_cookie_header. This causes
|
400
|
+
# the client to immediately delete the specified cookie.
|
401
|
+
#
|
402
|
+
# delete_set_cookie_header!(nil, "mycookie")
|
403
|
+
# # => "mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
|
404
|
+
#
|
405
|
+
# If the header is non-nil, it will be modified in place.
|
406
|
+
#
|
407
|
+
# header = []
|
408
|
+
# delete_set_cookie_header!(header, "mycookie")
|
409
|
+
# # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
|
410
|
+
# header
|
411
|
+
# # => ["mycookie=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"]
|
412
|
+
#
|
413
|
+
def delete_set_cookie_header!(header, key, value = {})
|
414
|
+
if header
|
415
|
+
header = Array(header)
|
416
|
+
header << delete_set_cookie_header(key, value)
|
417
|
+
else
|
418
|
+
header = delete_set_cookie_header(key, value)
|
419
|
+
end
|
316
420
|
|
421
|
+
return header
|
317
422
|
end
|
318
423
|
|
319
424
|
def rfc2822(time)
|
320
425
|
time.rfc2822
|
321
426
|
end
|
322
427
|
|
323
|
-
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
324
|
-
# of '% %b %Y'.
|
325
|
-
# It assumes that the time is in GMT to comply to the RFC 2109.
|
326
|
-
#
|
327
|
-
# NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
|
328
|
-
# that I'm certain someone implemented only that option.
|
329
|
-
# Do not use %a and %b from Time.strptime, it would use localized names for
|
330
|
-
# weekday and month.
|
331
|
-
#
|
332
|
-
def rfc2109(time)
|
333
|
-
wday = RFC2822_DAY_NAME[time.wday]
|
334
|
-
mon = RFC2822_MONTH_NAME[time.mon - 1]
|
335
|
-
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
336
|
-
end
|
337
|
-
|
338
428
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
339
429
|
# Returns nil if the header is missing or syntactically invalid.
|
340
430
|
# Returns an empty array if none of the ranges are satisfiable.
|
341
431
|
def byte_ranges(env, size)
|
342
|
-
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
343
432
|
get_byte_ranges env['HTTP_RANGE'], size
|
344
433
|
end
|
345
434
|
|
@@ -348,17 +437,18 @@ module Rack
|
|
348
437
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
349
438
|
ranges = []
|
350
439
|
$1.split(/,\s*/).each do |range_spec|
|
351
|
-
return nil
|
352
|
-
|
353
|
-
|
354
|
-
|
440
|
+
return nil unless range_spec.include?('-')
|
441
|
+
range = range_spec.split('-')
|
442
|
+
r0, r1 = range[0], range[1]
|
443
|
+
if r0.nil? || r0.empty?
|
444
|
+
return nil if r1.nil?
|
355
445
|
# suffix-byte-range-spec, represents trailing suffix of file
|
356
446
|
r0 = size - r1.to_i
|
357
447
|
r0 = 0 if r0 < 0
|
358
448
|
r1 = size - 1
|
359
449
|
else
|
360
450
|
r0 = r0.to_i
|
361
|
-
if r1.
|
451
|
+
if r1.nil?
|
362
452
|
r1 = size - 1
|
363
453
|
else
|
364
454
|
r1 = r1.to_i
|
@@ -371,20 +461,30 @@ module Rack
|
|
371
461
|
ranges
|
372
462
|
end
|
373
463
|
|
374
|
-
#
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
464
|
+
# :nocov:
|
465
|
+
if defined?(OpenSSL.fixed_length_secure_compare)
|
466
|
+
# Constant time string comparison.
|
467
|
+
#
|
468
|
+
# NOTE: the values compared should be of fixed length, such as strings
|
469
|
+
# that have already been processed by HMAC. This should not be used
|
470
|
+
# on variable length plaintext strings because it could leak length info
|
471
|
+
# via timing attacks.
|
472
|
+
def secure_compare(a, b)
|
473
|
+
return false unless a.bytesize == b.bytesize
|
474
|
+
|
475
|
+
OpenSSL.fixed_length_secure_compare(a, b)
|
476
|
+
end
|
477
|
+
# :nocov:
|
478
|
+
else
|
479
|
+
def secure_compare(a, b)
|
480
|
+
return false unless a.bytesize == b.bytesize
|
382
481
|
|
383
|
-
|
482
|
+
l = a.unpack("C*")
|
384
483
|
|
385
|
-
|
386
|
-
|
387
|
-
|
484
|
+
r, i = 0, -1
|
485
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
486
|
+
r == 0
|
487
|
+
end
|
388
488
|
end
|
389
489
|
|
390
490
|
# Context allows the use of a compatible middleware at different points
|
@@ -413,94 +513,32 @@ module Rack
|
|
413
513
|
end
|
414
514
|
end
|
415
515
|
|
416
|
-
# A
|
516
|
+
# A wrapper around Headers
|
417
517
|
# header when set.
|
418
518
|
#
|
419
519
|
# @api private
|
420
520
|
class HeaderHash < Hash # :nodoc:
|
421
521
|
def self.[](headers)
|
422
|
-
|
522
|
+
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
523
|
+
if headers.is_a?(Headers) && !headers.frozen?
|
423
524
|
return headers
|
424
|
-
else
|
425
|
-
return self.new(headers)
|
426
525
|
end
|
427
|
-
end
|
428
|
-
|
429
|
-
def initialize(hash = {})
|
430
|
-
super()
|
431
|
-
@names = {}
|
432
|
-
hash.each { |k, v| self[k] = v }
|
433
|
-
end
|
434
|
-
|
435
|
-
# on dup/clone, we need to duplicate @names hash
|
436
|
-
def initialize_copy(other)
|
437
|
-
super
|
438
|
-
@names = other.names.dup
|
439
|
-
end
|
440
|
-
|
441
|
-
# on clear, we need to clear @names hash
|
442
|
-
def clear
|
443
|
-
super
|
444
|
-
@names.clear
|
445
|
-
end
|
446
|
-
|
447
|
-
def each
|
448
|
-
super do |k, v|
|
449
|
-
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
450
|
-
end
|
451
|
-
end
|
452
526
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
hash
|
527
|
+
new_headers = Headers.new
|
528
|
+
headers.each{|k,v| new_headers[k] = v}
|
529
|
+
new_headers
|
457
530
|
end
|
458
531
|
|
459
|
-
def
|
460
|
-
|
532
|
+
def self.new(hash = {})
|
533
|
+
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
534
|
+
headers = Headers.new
|
535
|
+
hash.each{|k,v| headers[k] = v}
|
536
|
+
headers
|
461
537
|
end
|
462
538
|
|
463
|
-
def
|
464
|
-
|
465
|
-
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
466
|
-
@names[canonical] = k
|
467
|
-
super k, v
|
539
|
+
def self.allocate
|
540
|
+
raise TypeError, "cannot allocate HeaderHash"
|
468
541
|
end
|
469
|
-
|
470
|
-
def delete(k)
|
471
|
-
canonical = k.downcase
|
472
|
-
result = super @names.delete(canonical)
|
473
|
-
result
|
474
|
-
end
|
475
|
-
|
476
|
-
def include?(k)
|
477
|
-
super || @names.include?(k.downcase)
|
478
|
-
end
|
479
|
-
|
480
|
-
alias_method :has_key?, :include?
|
481
|
-
alias_method :member?, :include?
|
482
|
-
alias_method :key?, :include?
|
483
|
-
|
484
|
-
def merge!(other)
|
485
|
-
other.each { |k, v| self[k] = v }
|
486
|
-
self
|
487
|
-
end
|
488
|
-
|
489
|
-
def merge(other)
|
490
|
-
hash = dup
|
491
|
-
hash.merge! other
|
492
|
-
end
|
493
|
-
|
494
|
-
def replace(other)
|
495
|
-
clear
|
496
|
-
other.each { |k, v| self[k] = v }
|
497
|
-
self
|
498
|
-
end
|
499
|
-
|
500
|
-
protected
|
501
|
-
def names
|
502
|
-
@names
|
503
|
-
end
|
504
542
|
end
|
505
543
|
|
506
544
|
# Every standard HTTP code mapped to the appropriate message.
|