rack 2.2.7 → 2.2.8
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 +5 -0
- data/SPEC.rdoc +9 -5
- data/lib/rack/lint.rb +177 -188
- data/lib/rack/multipart/parser.rb +3 -1
- data/lib/rack/session/cookie.rb +1 -0
- data/lib/rack/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06ce8687cfd4c7e0e35738d32303ed958d62615ee99624ce7fbab2f9268f58cc
|
4
|
+
data.tar.gz: 373ee5a240a556f70dace8667543c80329d3f990ade031b79cfb366ff5b3e051
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9d60d3a4798c7593a48d8bb85996c15f6fac4009d1a90a7545ca80fd38e3ed6515b17dc20f22e6052aa074de9c3502485273a6cebe0e9d6d30be596061bf1e1
|
7
|
+
data.tar.gz: 4331edc1a9fbcf9e61f8041bb4626cb2c89d55783aefa90628ae7bd3224f9bfd0ec859f8b9f4bb33709f0f9d66e53804f4296919f7749bd7a51d447d56d2aad6
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/).
|
4
4
|
|
5
|
+
## [2.2.7] - 2023-03-13
|
6
|
+
|
7
|
+
- Correct the year number in the changelog ([#2015](https://github.com/rack/rack/pull/2015), [@kimulab](https://github.com/kimulab))
|
8
|
+
- Support underscore in host names for Rack 2.2 (Fixes [#2070](https://github.com/rack/rack/issues/2070)) ([#2015](https://github.com/rack/rack/pull/2071), [@jeremyevans](https://github.com/jeremyevans))
|
9
|
+
|
5
10
|
## [2.2.6.4] - 2023-03-13
|
6
11
|
|
7
12
|
- [CVE-2023-27539] Avoid ReDoS in header parsing
|
data/SPEC.rdoc
CHANGED
@@ -42,17 +42,18 @@ below.
|
|
42
42
|
<tt>QUERY_STRING</tt>:: The portion of the request URL that
|
43
43
|
follows the <tt>?</tt>, if any. May be
|
44
44
|
empty, but is always required!
|
45
|
-
<tt>SERVER_NAME</tt
|
46
|
-
When combined with <tt>SCRIPT_NAME</tt> and
|
45
|
+
<tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
|
47
46
|
<tt>PATH_INFO</tt>, these variables can be
|
48
47
|
used to complete the URL. Note, however,
|
49
48
|
that <tt>HTTP_HOST</tt>, if present,
|
50
49
|
should be used in preference to
|
51
50
|
<tt>SERVER_NAME</tt> for reconstructing
|
52
51
|
the request URL.
|
53
|
-
<tt>SERVER_NAME</tt>
|
54
|
-
|
55
|
-
|
52
|
+
<tt>SERVER_NAME</tt> can never be an empty
|
53
|
+
string, and so is always required.
|
54
|
+
<tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
|
55
|
+
server is running on. Should be specified if
|
56
|
+
the server is running on a non-standard port.
|
56
57
|
<tt>HTTP_</tt> Variables:: Variables corresponding to the
|
57
58
|
client-supplied HTTP request
|
58
59
|
headers (i.e., variables whose
|
@@ -122,6 +123,9 @@ and should be prefixed uniquely. The prefix <tt>rack.</tt>
|
|
122
123
|
is reserved for use with the Rack core distribution and other
|
123
124
|
accepted specifications and must not be used otherwise.
|
124
125
|
|
126
|
+
The <tt>SERVER_PORT</tt> must be an Integer if set.
|
127
|
+
The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
|
128
|
+
The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
|
125
129
|
The environment must not contain the keys
|
126
130
|
<tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
|
127
131
|
(use the versions without <tt>HTTP_</tt>).
|
data/lib/rack/lint.rb
CHANGED
@@ -40,7 +40,7 @@ module Rack
|
|
40
40
|
|
41
41
|
def _call(env)
|
42
42
|
## It takes exactly one argument, the *environment*
|
43
|
-
|
43
|
+
raise LintError, "No env given" unless env
|
44
44
|
check_env env
|
45
45
|
|
46
46
|
env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
|
@@ -48,12 +48,8 @@ module Rack
|
|
48
48
|
|
49
49
|
## and returns an Array of exactly three values:
|
50
50
|
ary = @app.call(env)
|
51
|
-
|
52
|
-
|
53
|
-
}
|
54
|
-
assert("response array has #{ary.size} elements instead of 3") {
|
55
|
-
ary.size == 3
|
56
|
-
}
|
51
|
+
raise LintError, "response is not an Array, but #{ary.class}" unless ary.kind_of? Array
|
52
|
+
raise LintError, "response array has #{ary.size} elements instead of 3" unless ary.size == 3
|
57
53
|
|
58
54
|
status, headers, @body = ary
|
59
55
|
## The *status*,
|
@@ -78,12 +74,8 @@ module Rack
|
|
78
74
|
## The environment must be an unfrozen instance of Hash that includes
|
79
75
|
## CGI-like headers. The application is free to modify the
|
80
76
|
## environment.
|
81
|
-
|
82
|
-
|
83
|
-
}
|
84
|
-
assert("env should not be frozen, but is") {
|
85
|
-
!env.frozen?
|
86
|
-
}
|
77
|
+
raise LintError, "env #{env.inspect} is not a Hash, but #{env.class}" unless env.kind_of? Hash
|
78
|
+
raise LintError, "env should not be frozen, but is" if env.frozen?
|
87
79
|
|
88
80
|
##
|
89
81
|
## The environment is required to include these variables
|
@@ -195,73 +187,73 @@ module Rack
|
|
195
187
|
## The store must implement:
|
196
188
|
if session = env[RACK_SESSION]
|
197
189
|
## store(key, value) (aliased as []=);
|
198
|
-
|
199
|
-
session.
|
200
|
-
|
190
|
+
unless session.respond_to?(:store) && session.respond_to?(:[]=)
|
191
|
+
raise LintError, "session #{session.inspect} must respond to store and []="
|
192
|
+
end
|
201
193
|
|
202
194
|
## fetch(key, default = nil) (aliased as []);
|
203
|
-
|
204
|
-
session.
|
205
|
-
|
195
|
+
unless session.respond_to?(:fetch) && session.respond_to?(:[])
|
196
|
+
raise LintError, "session #{session.inspect} must respond to fetch and []"
|
197
|
+
end
|
206
198
|
|
207
199
|
## delete(key);
|
208
|
-
|
209
|
-
session.
|
210
|
-
|
200
|
+
unless session.respond_to?(:delete)
|
201
|
+
raise LintError, "session #{session.inspect} must respond to delete"
|
202
|
+
end
|
211
203
|
|
212
204
|
## clear;
|
213
|
-
|
214
|
-
session.
|
215
|
-
|
205
|
+
unless session.respond_to?(:clear)
|
206
|
+
raise LintError, "session #{session.inspect} must respond to clear"
|
207
|
+
end
|
216
208
|
|
217
209
|
## to_hash (returning unfrozen Hash instance);
|
218
|
-
|
219
|
-
session.
|
220
|
-
|
210
|
+
unless session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
|
211
|
+
raise LintError, "session #{session.inspect} must respond to to_hash and return unfrozen Hash instance"
|
212
|
+
end
|
221
213
|
end
|
222
214
|
|
223
215
|
## <tt>rack.logger</tt>:: A common object interface for logging messages.
|
224
216
|
## The object must implement:
|
225
217
|
if logger = env[RACK_LOGGER]
|
226
218
|
## info(message, &block)
|
227
|
-
|
228
|
-
logger.
|
229
|
-
|
219
|
+
unless logger.respond_to?(:info)
|
220
|
+
raise LintError, "logger #{logger.inspect} must respond to info"
|
221
|
+
end
|
230
222
|
|
231
223
|
## debug(message, &block)
|
232
|
-
|
233
|
-
logger.
|
234
|
-
|
224
|
+
unless logger.respond_to?(:debug)
|
225
|
+
raise LintError, "logger #{logger.inspect} must respond to debug"
|
226
|
+
end
|
235
227
|
|
236
228
|
## warn(message, &block)
|
237
|
-
|
238
|
-
logger.
|
239
|
-
|
229
|
+
unless logger.respond_to?(:warn)
|
230
|
+
raise LintError, "logger #{logger.inspect} must respond to warn"
|
231
|
+
end
|
240
232
|
|
241
233
|
## error(message, &block)
|
242
|
-
|
243
|
-
logger.
|
244
|
-
|
234
|
+
unless logger.respond_to?(:error)
|
235
|
+
raise LintError, "logger #{logger.inspect} must respond to error"
|
236
|
+
end
|
245
237
|
|
246
238
|
## fatal(message, &block)
|
247
|
-
|
248
|
-
logger.
|
249
|
-
|
239
|
+
unless logger.respond_to?(:fatal)
|
240
|
+
raise LintError, "logger #{logger.inspect} must respond to fatal"
|
241
|
+
end
|
250
242
|
end
|
251
243
|
|
252
244
|
## <tt>rack.multipart.buffer_size</tt>:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
|
253
245
|
if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
|
254
|
-
|
255
|
-
|
256
|
-
|
246
|
+
unless bufsize.is_a?(Integer) && bufsize > 0
|
247
|
+
raise LintError, "rack.multipart.buffer_size must be an Integer > 0 if specified"
|
248
|
+
end
|
257
249
|
end
|
258
250
|
|
259
251
|
## <tt>rack.multipart.tempfile_factory</tt>:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile.
|
260
252
|
if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
|
261
|
-
|
253
|
+
raise LintError, "rack.multipart.tempfile_factory must respond to #call" unless tempfile_factory.respond_to?(:call)
|
262
254
|
env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
|
263
255
|
io = tempfile_factory.call(filename, content_type)
|
264
|
-
|
256
|
+
raise LintError, "rack.multipart.tempfile_factory return value must respond to #<<" unless io.respond_to?(:<<)
|
265
257
|
io
|
266
258
|
end
|
267
259
|
end
|
@@ -276,32 +268,32 @@ module Rack
|
|
276
268
|
%w[REQUEST_METHOD SERVER_NAME QUERY_STRING
|
277
269
|
rack.version rack.input rack.errors
|
278
270
|
rack.multithread rack.multiprocess rack.run_once].each { |header|
|
279
|
-
|
271
|
+
raise LintError, "env missing required key #{header}" unless env.include? header
|
280
272
|
}
|
281
273
|
|
282
274
|
## The <tt>SERVER_PORT</tt> must be an Integer if set.
|
283
|
-
|
284
|
-
|
285
|
-
|
275
|
+
server_port = env["SERVER_PORT"]
|
276
|
+
unless server_port.nil? || (Integer(server_port) rescue false)
|
277
|
+
raise LintError, "env[SERVER_PORT] is not an Integer"
|
286
278
|
end
|
287
279
|
|
288
280
|
## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
|
289
|
-
|
290
|
-
|
281
|
+
unless (URI.parse("http://#{env[SERVER_NAME]}/") rescue false)
|
282
|
+
raise LintError, "#{env[SERVER_NAME]} must be a valid authority"
|
291
283
|
end
|
292
284
|
|
293
285
|
## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
|
294
|
-
|
295
|
-
|
286
|
+
unless (URI.parse("http://#{env[HTTP_HOST]}/") rescue false)
|
287
|
+
raise LintError, "#{env[HTTP_HOST]} must be a valid authority"
|
296
288
|
end
|
297
289
|
|
298
290
|
## The environment must not contain the keys
|
299
291
|
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
|
300
292
|
## (use the versions without <tt>HTTP_</tt>).
|
301
293
|
%w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
|
302
|
-
|
303
|
-
|
304
|
-
|
294
|
+
if env.include? header
|
295
|
+
raise LintError, "env contains #{header}, must use #{header[5, -1]}"
|
296
|
+
end
|
305
297
|
}
|
306
298
|
|
307
299
|
## The CGI keys (named without a period) must have String values.
|
@@ -309,25 +301,25 @@ module Rack
|
|
309
301
|
## they should use ASCII-8BIT encoding.
|
310
302
|
env.each { |key, value|
|
311
303
|
next if key.include? "." # Skip extensions
|
312
|
-
|
313
|
-
value.
|
314
|
-
|
304
|
+
unless value.kind_of? String
|
305
|
+
raise LintError, "env variable #{key} has non-string value #{value.inspect}"
|
306
|
+
end
|
315
307
|
next if value.encoding == Encoding::ASCII_8BIT
|
316
|
-
|
317
|
-
value.
|
318
|
-
|
308
|
+
unless value.b !~ /[\x80-\xff]/n
|
309
|
+
raise LintError, "env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}"
|
310
|
+
end
|
319
311
|
}
|
320
312
|
|
321
313
|
## There are the following restrictions:
|
322
314
|
|
323
315
|
## * <tt>rack.version</tt> must be an array of Integers.
|
324
|
-
|
325
|
-
env[RACK_VERSION].
|
326
|
-
|
316
|
+
unless env[RACK_VERSION].kind_of? Array
|
317
|
+
raise LintError, "rack.version must be an Array, was #{env[RACK_VERSION].class}"
|
318
|
+
end
|
327
319
|
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
|
328
|
-
|
329
|
-
|
330
|
-
|
320
|
+
unless %w[http https].include?(env[RACK_URL_SCHEME])
|
321
|
+
raise LintError, "rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}"
|
322
|
+
end
|
331
323
|
|
332
324
|
## * There must be a valid input stream in <tt>rack.input</tt>.
|
333
325
|
check_input env[RACK_INPUT]
|
@@ -337,37 +329,33 @@ module Rack
|
|
337
329
|
check_hijack env
|
338
330
|
|
339
331
|
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
|
340
|
-
|
341
|
-
|
342
|
-
|
332
|
+
unless env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
|
333
|
+
raise LintError, "REQUEST_METHOD unknown: #{env[REQUEST_METHOD].dump}"
|
334
|
+
end
|
343
335
|
|
344
336
|
## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
env[SCRIPT_NAME] =~ /\A\//
|
349
|
-
}
|
337
|
+
if env.include?(SCRIPT_NAME) && env[SCRIPT_NAME] != "" && env[SCRIPT_NAME] !~ /\A\//
|
338
|
+
raise LintError, "SCRIPT_NAME must start with /"
|
339
|
+
end
|
350
340
|
## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
env[PATH_INFO] =~ /\A\//
|
355
|
-
}
|
341
|
+
if env.include?(PATH_INFO) && env[PATH_INFO] != "" && env[PATH_INFO] !~ /\A\//
|
342
|
+
raise LintError, "PATH_INFO must start with /"
|
343
|
+
end
|
356
344
|
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
|
357
|
-
|
358
|
-
|
359
|
-
|
345
|
+
if env.include?("CONTENT_LENGTH") && env["CONTENT_LENGTH"] !~ /\A\d+\z/
|
346
|
+
raise LintError, "Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}"
|
347
|
+
end
|
360
348
|
|
361
349
|
## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
|
362
350
|
## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
|
363
351
|
## <tt>SCRIPT_NAME</tt> is empty.
|
364
|
-
|
365
|
-
|
366
|
-
|
352
|
+
unless env[SCRIPT_NAME] || env[PATH_INFO]
|
353
|
+
raise LintError, "One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)"
|
354
|
+
end
|
367
355
|
## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
|
368
|
-
|
369
|
-
|
370
|
-
|
356
|
+
unless env[SCRIPT_NAME] != "/"
|
357
|
+
raise LintError, "SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'"
|
358
|
+
end
|
371
359
|
end
|
372
360
|
|
373
361
|
## === The Input Stream
|
@@ -377,18 +365,18 @@ module Rack
|
|
377
365
|
def check_input(input)
|
378
366
|
## When applicable, its external encoding must be "ASCII-8BIT" and it
|
379
367
|
## must be opened in binary mode, for Ruby 1.9 compatibility.
|
380
|
-
|
381
|
-
input
|
382
|
-
|
383
|
-
|
384
|
-
input
|
385
|
-
|
368
|
+
if input.respond_to?(:external_encoding) && input.external_encoding != Encoding::ASCII_8BIT
|
369
|
+
raise LintError, "rack.input #{input} does not have ASCII-8BIT as its external encoding"
|
370
|
+
end
|
371
|
+
if input.respond_to?(:binmode?) && !input.binmode?
|
372
|
+
raise LintError, "rack.input #{input} is not opened in binary mode"
|
373
|
+
end
|
386
374
|
|
387
375
|
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
|
388
376
|
[:gets, :each, :read, :rewind].each { |method|
|
389
|
-
|
390
|
-
input
|
391
|
-
|
377
|
+
unless input.respond_to? method
|
378
|
+
raise LintError, "rack.input #{input} does not respond to ##{method}"
|
379
|
+
end
|
392
380
|
}
|
393
381
|
end
|
394
382
|
|
@@ -402,11 +390,11 @@ module Rack
|
|
402
390
|
## * +gets+ must be called without arguments and return a string,
|
403
391
|
## or +nil+ on EOF.
|
404
392
|
def gets(*args)
|
405
|
-
|
393
|
+
raise LintError, "rack.input#gets called with arguments" unless args.size == 0
|
406
394
|
v = @input.gets
|
407
|
-
|
408
|
-
|
409
|
-
|
395
|
+
unless v.nil? or v.kind_of? String
|
396
|
+
raise LintError, "rack.input#gets didn't return a String"
|
397
|
+
end
|
410
398
|
v
|
411
399
|
end
|
412
400
|
|
@@ -428,32 +416,32 @@ module Rack
|
|
428
416
|
## If +buffer+ is given, then the read data will be placed
|
429
417
|
## into +buffer+ instead of a newly created String object.
|
430
418
|
def read(*args)
|
431
|
-
|
432
|
-
|
433
|
-
|
419
|
+
unless args.size <= 2
|
420
|
+
raise LintError, "rack.input#read called with too many arguments"
|
421
|
+
end
|
434
422
|
if args.size >= 1
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
423
|
+
unless args.first.kind_of?(Integer) || args.first.nil?
|
424
|
+
raise LintError, "rack.input#read called with non-integer and non-nil length"
|
425
|
+
end
|
426
|
+
unless args.first.nil? || args.first >= 0
|
427
|
+
raise LintError, "rack.input#read called with a negative length"
|
428
|
+
end
|
441
429
|
end
|
442
430
|
if args.size >= 2
|
443
|
-
|
444
|
-
|
445
|
-
|
431
|
+
unless args[1].kind_of?(String)
|
432
|
+
raise LintError, "rack.input#read called with non-String buffer"
|
433
|
+
end
|
446
434
|
end
|
447
435
|
|
448
436
|
v = @input.read(*args)
|
449
437
|
|
450
|
-
|
451
|
-
|
452
|
-
|
438
|
+
unless v.nil? or v.kind_of? String
|
439
|
+
raise LintError, "rack.input#read didn't return nil or a String"
|
440
|
+
end
|
453
441
|
if args[0].nil?
|
454
|
-
|
455
|
-
|
456
|
-
|
442
|
+
unless !v.nil?
|
443
|
+
raise LintError, "rack.input#read(nil) returned nil on EOF"
|
444
|
+
end
|
457
445
|
end
|
458
446
|
|
459
447
|
v
|
@@ -461,11 +449,11 @@ module Rack
|
|
461
449
|
|
462
450
|
## * +each+ must be called without arguments and only yield Strings.
|
463
451
|
def each(*args)
|
464
|
-
|
452
|
+
raise LintError, "rack.input#each called with arguments" unless args.size == 0
|
465
453
|
@input.each { |line|
|
466
|
-
|
467
|
-
|
468
|
-
|
454
|
+
unless line.kind_of? String
|
455
|
+
raise LintError, "rack.input#each didn't yield a String"
|
456
|
+
end
|
469
457
|
yield line
|
470
458
|
}
|
471
459
|
end
|
@@ -476,20 +464,18 @@ module Rack
|
|
476
464
|
## developers must buffer the input data into some rewindable object
|
477
465
|
## if the underlying input stream is not rewindable.
|
478
466
|
def rewind(*args)
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
end
|
487
|
-
}
|
467
|
+
raise LintError, "rack.input#rewind called with arguments" unless args.size == 0
|
468
|
+
begin
|
469
|
+
@input.rewind
|
470
|
+
true
|
471
|
+
rescue Errno::ESPIPE
|
472
|
+
raise LintError, "rack.input#rewind raised Errno::ESPIPE"
|
473
|
+
end
|
488
474
|
end
|
489
475
|
|
490
476
|
## * +close+ must never be called on the input stream.
|
491
477
|
def close(*args)
|
492
|
-
|
478
|
+
raise LintError, "rack.input#close must not be called"
|
493
479
|
end
|
494
480
|
end
|
495
481
|
|
@@ -497,9 +483,9 @@ module Rack
|
|
497
483
|
def check_error(error)
|
498
484
|
## The error stream must respond to +puts+, +write+ and +flush+.
|
499
485
|
[:puts, :write, :flush].each { |method|
|
500
|
-
|
501
|
-
error
|
502
|
-
|
486
|
+
unless error.respond_to? method
|
487
|
+
raise LintError, "rack.error #{error} does not respond to ##{method}"
|
488
|
+
end
|
503
489
|
}
|
504
490
|
end
|
505
491
|
|
@@ -517,7 +503,7 @@ module Rack
|
|
517
503
|
|
518
504
|
## * +write+ must be called with a single argument that is a String.
|
519
505
|
def write(str)
|
520
|
-
|
506
|
+
raise LintError, "rack.errors#write not called with a String" unless str.kind_of? String
|
521
507
|
@error.write str
|
522
508
|
end
|
523
509
|
|
@@ -529,7 +515,7 @@ module Rack
|
|
529
515
|
|
530
516
|
## * +close+ must never be called on the error stream.
|
531
517
|
def close(*args)
|
532
|
-
|
518
|
+
raise LintError, "rack.errors#close must not be called"
|
533
519
|
end
|
534
520
|
end
|
535
521
|
|
@@ -547,7 +533,7 @@ module Rack
|
|
547
533
|
def initialize(io)
|
548
534
|
@io = io
|
549
535
|
REQUIRED_METHODS.each do |meth|
|
550
|
-
|
536
|
+
raise LintError, "rack.hijack_io must respond to #{meth}" unless io.respond_to? meth
|
551
537
|
end
|
552
538
|
end
|
553
539
|
end
|
@@ -563,7 +549,7 @@ module Rack
|
|
563
549
|
if env[RACK_IS_HIJACK]
|
564
550
|
## If rack.hijack? is true then rack.hijack must respond to #call.
|
565
551
|
original_hijack = env[RACK_HIJACK]
|
566
|
-
|
552
|
+
raise LintError, "rack.hijack must respond to call" unless original_hijack.respond_to?(:call)
|
567
553
|
env[RACK_HIJACK] = proc do
|
568
554
|
## rack.hijack must return the io that will also be assigned (or is
|
569
555
|
## already present, in rack.hijack_io.
|
@@ -596,10 +582,10 @@ module Rack
|
|
596
582
|
else
|
597
583
|
##
|
598
584
|
## If rack.hijack? is false, then rack.hijack should not be set.
|
599
|
-
|
585
|
+
raise LintError, "rack.hijack? is false, but rack.hijack is present" unless env[RACK_HIJACK].nil?
|
600
586
|
##
|
601
587
|
## If rack.hijack? is false, then rack.hijack_io should not be set.
|
602
|
-
|
588
|
+
raise LintError, "rack.hijack? is false, but rack.hijack_io is present" unless env[RACK_HIJACK_IO].nil?
|
603
589
|
end
|
604
590
|
end
|
605
591
|
|
@@ -630,9 +616,9 @@ module Rack
|
|
630
616
|
## the <tt>rack.hijack</tt> response API is in use.
|
631
617
|
|
632
618
|
if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
|
633
|
-
|
634
|
-
|
635
|
-
|
619
|
+
unless headers[RACK_HIJACK].respond_to? :call
|
620
|
+
raise LintError, 'rack.hijack header must respond to #call'
|
621
|
+
end
|
636
622
|
original_hijack = headers[RACK_HIJACK]
|
637
623
|
proc do |io|
|
638
624
|
original_hijack.call HijackWrapper.new(io)
|
@@ -641,9 +627,9 @@ module Rack
|
|
641
627
|
##
|
642
628
|
## The special response header <tt>rack.hijack</tt> must only be set
|
643
629
|
## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
|
644
|
-
|
645
|
-
|
646
|
-
|
630
|
+
unless headers[RACK_HIJACK].nil?
|
631
|
+
raise LintError, 'rack.hijack header must not be present if server does not support hijacking'
|
632
|
+
end
|
647
633
|
|
648
634
|
nil
|
649
635
|
end
|
@@ -661,42 +647,45 @@ module Rack
|
|
661
647
|
def check_status(status)
|
662
648
|
## This is an HTTP status. When parsed as integer (+to_i+), it must be
|
663
649
|
## greater than or equal to 100.
|
664
|
-
|
650
|
+
unless status.to_i >= 100
|
651
|
+
raise LintError, "Status must be >=100 seen as integer"
|
652
|
+
end
|
665
653
|
end
|
666
654
|
|
667
655
|
## === The Headers
|
668
656
|
def check_headers(header)
|
669
657
|
## The header must respond to +each+, and yield values of key and value.
|
670
|
-
|
671
|
-
|
672
|
-
|
658
|
+
unless header.respond_to? :each
|
659
|
+
raise LintError, "headers object should respond to #each, but doesn't (got #{header.class} as headers)"
|
660
|
+
end
|
673
661
|
|
674
662
|
header.each { |key, value|
|
675
663
|
## The header keys must be Strings.
|
676
|
-
|
677
|
-
key.
|
678
|
-
|
664
|
+
unless key.kind_of? String
|
665
|
+
raise LintError, "header key must be a string, was #{key.class}"
|
666
|
+
end
|
679
667
|
|
680
668
|
## Special headers starting "rack." are for communicating with the
|
681
669
|
## server, and must not be sent back to the client.
|
682
670
|
next if key =~ /^rack\..+$/
|
683
671
|
|
684
672
|
## The header must not contain a +Status+ key.
|
685
|
-
|
673
|
+
raise LintError, "header must not contain Status" if key.downcase == "status"
|
686
674
|
## The header must conform to RFC7230 token specification, i.e. cannot
|
687
675
|
## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
|
688
|
-
|
676
|
+
raise LintError, "invalid header name: #{key}" if key =~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/
|
689
677
|
|
690
678
|
## The values of the header must be Strings,
|
691
|
-
|
692
|
-
"'#{key}' is a #{value.class}"
|
679
|
+
unless value.kind_of? String
|
680
|
+
raise LintError, "a header value must be a String, but the value of '#{key}' is a #{value.class}"
|
681
|
+
end
|
693
682
|
## consisting of lines (for multiple header values, e.g. multiple
|
694
683
|
## <tt>Set-Cookie</tt> values) separated by "\\n".
|
695
684
|
value.split("\n").each { |item|
|
696
685
|
## The lines must not contain characters below 037.
|
697
|
-
|
698
|
-
|
699
|
-
|
686
|
+
if item =~ /[\000-\037]/
|
687
|
+
raise LintError, "invalid header value #{key}: #{item.inspect}"
|
688
|
+
end
|
700
689
|
}
|
701
690
|
}
|
702
691
|
end
|
@@ -707,9 +696,9 @@ module Rack
|
|
707
696
|
## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
|
708
697
|
## 204 or 304.
|
709
698
|
if key.downcase == "content-type"
|
710
|
-
|
711
|
-
|
712
|
-
|
699
|
+
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
|
700
|
+
raise LintError, "Content-Type header found in #{status} response, not allowed"
|
701
|
+
end
|
713
702
|
return
|
714
703
|
end
|
715
704
|
}
|
@@ -721,9 +710,9 @@ module Rack
|
|
721
710
|
if key.downcase == 'content-length'
|
722
711
|
## There must not be a <tt>Content-Length</tt> header when the
|
723
712
|
## +Status+ is 1xx, 204 or 304.
|
724
|
-
|
725
|
-
|
726
|
-
|
713
|
+
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
|
714
|
+
raise LintError, "Content-Length header found in #{status} response, not allowed"
|
715
|
+
end
|
727
716
|
@content_length = value
|
728
717
|
end
|
729
718
|
}
|
@@ -731,13 +720,13 @@ module Rack
|
|
731
720
|
|
732
721
|
def verify_content_length(bytes)
|
733
722
|
if @head_request
|
734
|
-
|
735
|
-
|
736
|
-
|
723
|
+
unless bytes == 0
|
724
|
+
raise LintError, "Response body was given for HEAD request, but should be empty"
|
725
|
+
end
|
737
726
|
elsif @content_length
|
738
|
-
|
739
|
-
@content_length
|
740
|
-
|
727
|
+
unless @content_length == bytes.to_s
|
728
|
+
raise LintError, "Content-Length header was #{@content_length}, but should be #{bytes}"
|
729
|
+
end
|
741
730
|
end
|
742
731
|
end
|
743
732
|
|
@@ -747,15 +736,15 @@ module Rack
|
|
747
736
|
bytes = 0
|
748
737
|
|
749
738
|
## The Body must respond to +each+
|
750
|
-
|
751
|
-
|
739
|
+
unless @body.respond_to?(:each)
|
740
|
+
raise LintError, "Response body must respond to each"
|
752
741
|
end
|
753
742
|
|
754
743
|
@body.each { |part|
|
755
744
|
## and must only yield String values.
|
756
|
-
|
757
|
-
part.
|
758
|
-
|
745
|
+
unless part.kind_of? String
|
746
|
+
raise LintError, "Body yielded non-string value #{part.inspect}"
|
747
|
+
end
|
759
748
|
bytes += part.bytesize
|
760
749
|
yield part
|
761
750
|
}
|
@@ -768,7 +757,7 @@ module Rack
|
|
768
757
|
## If the Body responds to +close+, it will be called after iteration. If
|
769
758
|
## the body is replaced by a middleware after action, the original body
|
770
759
|
## must be closed first, if it responds to close.
|
771
|
-
# XXX howto:
|
760
|
+
# XXX howto: raise LintError, "Body has not been closed" unless @closed
|
772
761
|
|
773
762
|
|
774
763
|
##
|
@@ -779,9 +768,9 @@ module Rack
|
|
779
768
|
## transport the response.
|
780
769
|
|
781
770
|
if @body.respond_to?(:to_path)
|
782
|
-
|
783
|
-
|
784
|
-
|
771
|
+
unless ::File.exist? @body.to_path
|
772
|
+
raise LintError, "The file identified by body.to_path does not exist"
|
773
|
+
end
|
785
774
|
end
|
786
775
|
|
787
776
|
##
|
@@ -13,7 +13,9 @@ module Rack
|
|
13
13
|
BUFSIZE = 1_048_576
|
14
14
|
TEXT_PLAIN = "text/plain"
|
15
15
|
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
16
|
-
|
16
|
+
extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
|
17
|
+
|
18
|
+
Tempfile.new(["RackMultipart", extension])
|
17
19
|
}
|
18
20
|
|
19
21
|
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
data/lib/rack/session/cookie.rb
CHANGED
data/lib/rack/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leah Neukirchen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|