Skip to content

Commit 1970771

Browse files
authoredDec 8, 2020
Fix inefficient assert pattern in Rack::Lint (rack#1724)
* Use `raise "message" unless condition` instead of assert in Rack::Lint * The existing #assert is very inefficient because it computes the error message string even if no error is raised. * Fixes rack#1723 * Deprecate Rack::Lint::Assertion#assert * No need to include the Assertion module anymore in lint.rb
1 parent d3225f7 commit 1970771

File tree

1 file changed

+179
-196
lines changed

1 file changed

+179
-196
lines changed
 

‎lib/rack/lint.rb

+179-196
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ def initialize(app)
1717
class LintError < RuntimeError; end
1818
module Assertion
1919
def assert(message)
20+
warn("Rack::Lint::Assertion#assert is deprecated as it is inherently inefficient. " \
21+
"Use `raise Rack::Lint::LintError, 'msg' unless condition` instead", uplevel: 1)
2022
unless yield
2123
raise LintError, message
2224
end
2325
end
2426
end
25-
include Assertion
2627

2728
## This specification aims to formalize the Rack protocol. You
2829
## can (and should) use Rack::Lint to enforce it.
@@ -40,20 +41,16 @@ def call(env = nil)
4041

4142
def _call(env)
4243
## It takes exactly one argument, the *environment*
43-
assert("No env given") { env }
44+
raise LintError, "No env given" unless env
4445
check_env env
4546

4647
env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
4748
env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
4849

4950
## and returns an Array of exactly three values:
5051
ary = @app.call(env)
51-
assert("response is not an Array, but #{ary.class}") {
52-
ary.kind_of? Array
53-
}
54-
assert("response array has #{ary.size} elements instead of 3") {
55-
ary.size == 3
56-
}
52+
raise LintError, "response is not an Array, but #{ary.class}" unless ary.kind_of? Array
53+
raise LintError, "response array has #{ary.size} elements instead of 3" unless ary.size == 3
5754

5855
status, headers, @body = ary
5956
## The *status*,
@@ -78,12 +75,8 @@ def check_env(env)
7875
## The environment must be an unfrozen instance of Hash that includes
7976
## CGI-like headers. The application is free to modify the
8077
## environment.
81-
assert("env #{env.inspect} is not a Hash, but #{env.class}") {
82-
env.kind_of? Hash
83-
}
84-
assert("env should not be frozen, but is") {
85-
!env.frozen?
86-
}
78+
raise LintError, "env #{env.inspect} is not a Hash, but #{env.class}" unless env.kind_of? Hash
79+
raise LintError, "env should not be frozen, but is" if env.frozen?
8780

8881
##
8982
## The environment is required to include these variables
@@ -195,73 +188,73 @@ def check_env(env)
195188
## The store must implement:
196189
if session = env[RACK_SESSION]
197190
## store(key, value) (aliased as []=);
198-
assert("session #{session.inspect} must respond to store and []=") {
199-
session.respond_to?(:store) && session.respond_to?(:[]=)
200-
}
191+
unless session.respond_to?(:store) && session.respond_to?(:[]=)
192+
raise LintError, "session #{session.inspect} must respond to store and []="
193+
end
201194

202195
## fetch(key, default = nil) (aliased as []);
203-
assert("session #{session.inspect} must respond to fetch and []") {
204-
session.respond_to?(:fetch) && session.respond_to?(:[])
205-
}
196+
unless session.respond_to?(:fetch) && session.respond_to?(:[])
197+
raise LintError, "session #{session.inspect} must respond to fetch and []"
198+
end
206199

207200
## delete(key);
208-
assert("session #{session.inspect} must respond to delete") {
209-
session.respond_to?(:delete)
210-
}
201+
unless session.respond_to?(:delete)
202+
raise LintError, "session #{session.inspect} must respond to delete"
203+
end
211204

212205
## clear;
213-
assert("session #{session.inspect} must respond to clear") {
214-
session.respond_to?(:clear)
215-
}
206+
unless session.respond_to?(:clear)
207+
raise LintError, "session #{session.inspect} must respond to clear"
208+
end
216209

217210
## to_hash (returning unfrozen Hash instance);
218-
assert("session #{session.inspect} must respond to to_hash and return unfrozen Hash instance") {
219-
session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
220-
}
211+
unless session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
212+
raise LintError, "session #{session.inspect} must respond to to_hash and return unfrozen Hash instance"
213+
end
221214
end
222215

223216
## <tt>rack.logger</tt>:: A common object interface for logging messages.
224217
## The object must implement:
225218
if logger = env[RACK_LOGGER]
226219
## info(message, &block)
227-
assert("logger #{logger.inspect} must respond to info") {
228-
logger.respond_to?(:info)
229-
}
220+
unless logger.respond_to?(:info)
221+
raise LintError, "logger #{logger.inspect} must respond to info"
222+
end
230223

231224
## debug(message, &block)
232-
assert("logger #{logger.inspect} must respond to debug") {
233-
logger.respond_to?(:debug)
234-
}
225+
unless logger.respond_to?(:debug)
226+
raise LintError, "logger #{logger.inspect} must respond to debug"
227+
end
235228

236229
## warn(message, &block)
237-
assert("logger #{logger.inspect} must respond to warn") {
238-
logger.respond_to?(:warn)
239-
}
230+
unless logger.respond_to?(:warn)
231+
raise LintError, "logger #{logger.inspect} must respond to warn"
232+
end
240233

241234
## error(message, &block)
242-
assert("logger #{logger.inspect} must respond to error") {
243-
logger.respond_to?(:error)
244-
}
235+
unless logger.respond_to?(:error)
236+
raise LintError, "logger #{logger.inspect} must respond to error"
237+
end
245238

246239
## fatal(message, &block)
247-
assert("logger #{logger.inspect} must respond to fatal") {
248-
logger.respond_to?(:fatal)
249-
}
240+
unless logger.respond_to?(:fatal)
241+
raise LintError, "logger #{logger.inspect} must respond to fatal"
242+
end
250243
end
251244

252245
## <tt>rack.multipart.buffer_size</tt>:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
253246
if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
254-
assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
255-
bufsize.is_a?(Integer) && bufsize > 0
256-
}
247+
unless bufsize.is_a?(Integer) && bufsize > 0
248+
raise LintError, "rack.multipart.buffer_size must be an Integer > 0 if specified"
249+
end
257250
end
258251

259252
## <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.
260253
if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
261-
assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
254+
raise LintError, "rack.multipart.tempfile_factory must respond to #call" unless tempfile_factory.respond_to?(:call)
262255
env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
263256
io = tempfile_factory.call(filename, content_type)
264-
assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
257+
raise LintError, "rack.multipart.tempfile_factory return value must respond to #<<" unless io.respond_to?(:<<)
265258
io
266259
end
267260
end
@@ -276,58 +269,58 @@ def check_env(env)
276269
%w[REQUEST_METHOD SERVER_NAME QUERY_STRING
277270
rack.version rack.input rack.errors
278271
rack.multithread rack.multiprocess rack.run_once].each { |header|
279-
assert("env missing required key #{header}") { env.include? header }
272+
raise LintError, "env missing required key #{header}" unless env.include? header
280273
}
281274

282275
## The <tt>SERVER_PORT</tt> must be an Integer if set.
283-
assert("env[SERVER_PORT] is not an Integer") do
284-
server_port = env["SERVER_PORT"]
285-
server_port.nil? || (Integer(server_port) rescue false)
276+
server_port = env["SERVER_PORT"]
277+
unless server_port.nil? || (Integer(server_port) rescue false)
278+
raise LintError, "env[SERVER_PORT] is not an Integer"
286279
end
287280

288281
## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
289-
assert("#{env[SERVER_NAME]} must be a valid authority") do
290-
URI.parse("http://#{env[SERVER_NAME]}/") rescue false
282+
unless (URI.parse("http://#{env[SERVER_NAME]}/") rescue false)
283+
raise LintError, "#{env[SERVER_NAME]} must be a valid authority"
291284
end
292285

293286
## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
294-
assert("#{env[HTTP_HOST]} must be a valid authority") do
295-
URI.parse("http://#{env[HTTP_HOST]}/") rescue false
287+
unless (URI.parse("http://#{env[HTTP_HOST]}/") rescue false)
288+
raise LintError, "#{env[HTTP_HOST]} must be a valid authority"
296289
end
297290

298291
## The environment must not contain the keys
299292
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
300293
## (use the versions without <tt>HTTP_</tt>).
301294
%w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
302-
assert("env contains #{header}, must use #{header[5, -1]}") {
303-
not env.include? header
304-
}
295+
if env.include? header
296+
raise LintError, "env contains #{header}, must use #{header[5, -1]}"
297+
end
305298
}
306299

307300
## The CGI keys (named without a period) must have String values.
308301
## If the string values for CGI keys contain non-ASCII characters,
309302
## they should use ASCII-8BIT encoding.
310303
env.each { |key, value|
311304
next if key.include? "." # Skip extensions
312-
assert("env variable #{key} has non-string value #{value.inspect}") {
313-
value.kind_of? String
314-
}
305+
unless value.kind_of? String
306+
raise LintError, "env variable #{key} has non-string value #{value.inspect}"
307+
end
315308
next if value.encoding == Encoding::ASCII_8BIT
316-
assert("env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}") {
317-
value.b !~ /[\x80-\xff]/n
318-
}
309+
unless value.b !~ /[\x80-\xff]/n
310+
raise LintError, "env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}"
311+
end
319312
}
320313

321314
## There are the following restrictions:
322315

323316
## * <tt>rack.version</tt> must be an array of Integers.
324-
assert("rack.version must be an Array, was #{env[RACK_VERSION].class}") {
325-
env[RACK_VERSION].kind_of? Array
326-
}
317+
unless env[RACK_VERSION].kind_of? Array
318+
raise LintError, "rack.version must be an Array, was #{env[RACK_VERSION].class}"
319+
end
327320
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
328-
assert("rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}") {
329-
%w[http https].include?(env[RACK_URL_SCHEME])
330-
}
321+
unless %w[http https].include?(env[RACK_URL_SCHEME])
322+
raise LintError, "rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}"
323+
end
331324

332325
## * There must be a valid input stream in <tt>rack.input</tt>.
333326
check_input env[RACK_INPUT]
@@ -337,37 +330,33 @@ def check_env(env)
337330
check_hijack env
338331

339332
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
340-
assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
341-
env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
342-
}
333+
unless env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
334+
raise LintError, "REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}"
335+
end
343336

344337
## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
345-
assert("SCRIPT_NAME must start with /") {
346-
!env.include?(SCRIPT_NAME) ||
347-
env[SCRIPT_NAME] == "" ||
348-
env[SCRIPT_NAME] =~ /\A\//
349-
}
338+
if env.include?(SCRIPT_NAME) && env[SCRIPT_NAME] != "" && env[SCRIPT_NAME] !~ /\A\//
339+
raise LintError, "SCRIPT_NAME must start with /"
340+
end
350341
## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
351-
assert("PATH_INFO must start with /") {
352-
!env.include?(PATH_INFO) ||
353-
env[PATH_INFO] == "" ||
354-
env[PATH_INFO] =~ /\A\//
355-
}
342+
if env.include?(PATH_INFO) && env[PATH_INFO] != "" && env[PATH_INFO] !~ /\A\//
343+
raise LintError, "PATH_INFO must start with /"
344+
end
356345
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
357-
assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
358-
!env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
359-
}
346+
if env.include?("CONTENT_LENGTH") && env["CONTENT_LENGTH"] !~ /\A\d+\z/
347+
raise LintError, "Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}"
348+
end
360349

361350
## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
362351
## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
363352
## <tt>SCRIPT_NAME</tt> is empty.
364-
assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
365-
env[SCRIPT_NAME] || env[PATH_INFO]
366-
}
353+
unless env[SCRIPT_NAME] || env[PATH_INFO]
354+
raise LintError, "One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)"
355+
end
367356
## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
368-
assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
369-
env[SCRIPT_NAME] != "/"
370-
}
357+
unless env[SCRIPT_NAME] != "/"
358+
raise LintError, "SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'"
359+
end
371360
end
372361

373362
## === The Input Stream
@@ -377,36 +366,34 @@ def check_env(env)
377366
def check_input(input)
378367
## When applicable, its external encoding must be "ASCII-8BIT" and it
379368
## must be opened in binary mode, for Ruby 1.9 compatibility.
380-
assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
381-
input.external_encoding == Encoding::ASCII_8BIT
382-
} if input.respond_to?(:external_encoding)
383-
assert("rack.input #{input} is not opened in binary mode") {
384-
input.binmode?
385-
} if input.respond_to?(:binmode?)
369+
if input.respond_to?(:external_encoding) && input.external_encoding != Encoding::ASCII_8BIT
370+
raise LintError, "rack.input #{input} does not have ASCII-8BIT as its external encoding"
371+
end
372+
if input.respond_to?(:binmode?) && !input.binmode?
373+
raise LintError, "rack.input #{input} is not opened in binary mode"
374+
end
386375

387376
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
388377
[:gets, :each, :read, :rewind].each { |method|
389-
assert("rack.input #{input} does not respond to ##{method}") {
390-
input.respond_to? method
391-
}
378+
unless input.respond_to? method
379+
raise LintError, "rack.input #{input} does not respond to ##{method}"
380+
end
392381
}
393382
end
394383

395384
class InputWrapper
396-
include Assertion
397-
398385
def initialize(input)
399386
@input = input
400387
end
401388

402389
## * +gets+ must be called without arguments and return a string,
403390
## or +nil+ on EOF.
404391
def gets(*args)
405-
assert("rack.input#gets called with arguments") { args.size == 0 }
392+
raise LintError, "rack.input#gets called with arguments" unless args.size == 0
406393
v = @input.gets
407-
assert("rack.input#gets didn't return a String") {
408-
v.nil? or v.kind_of? String
409-
}
394+
unless v.nil? or v.kind_of? String
395+
raise LintError, "rack.input#gets didn't return a String"
396+
end
410397
v
411398
end
412399

@@ -428,44 +415,44 @@ def gets(*args)
428415
## If +buffer+ is given, then the read data will be placed
429416
## into +buffer+ instead of a newly created String object.
430417
def read(*args)
431-
assert("rack.input#read called with too many arguments") {
432-
args.size <= 2
433-
}
418+
unless args.size <= 2
419+
raise LintError, "rack.input#read called with too many arguments"
420+
end
434421
if args.size >= 1
435-
assert("rack.input#read called with non-integer and non-nil length") {
436-
args.first.kind_of?(Integer) || args.first.nil?
437-
}
438-
assert("rack.input#read called with a negative length") {
439-
args.first.nil? || args.first >= 0
440-
}
422+
unless args.first.kind_of?(Integer) || args.first.nil?
423+
raise LintError, "rack.input#read called with non-integer and non-nil length"
424+
end
425+
unless args.first.nil? || args.first >= 0
426+
raise LintError, "rack.input#read called with a negative length"
427+
end
441428
end
442429
if args.size >= 2
443-
assert("rack.input#read called with non-String buffer") {
444-
args[1].kind_of?(String)
445-
}
430+
unless args[1].kind_of?(String)
431+
raise LintError, "rack.input#read called with non-String buffer"
432+
end
446433
end
447434

448435
v = @input.read(*args)
449436

450-
assert("rack.input#read didn't return nil or a String") {
451-
v.nil? or v.kind_of? String
452-
}
437+
unless v.nil? or v.kind_of? String
438+
raise LintError, "rack.input#read didn't return nil or a String"
439+
end
453440
if args[0].nil?
454-
assert("rack.input#read(nil) returned nil on EOF") {
455-
!v.nil?
456-
}
441+
unless !v.nil?
442+
raise LintError, "rack.input#read(nil) returned nil on EOF"
443+
end
457444
end
458445

459446
v
460447
end
461448

462449
## * +each+ must be called without arguments and only yield Strings.
463450
def each(*args)
464-
assert("rack.input#each called with arguments") { args.size == 0 }
451+
raise LintError, "rack.input#each called with arguments" unless args.size == 0
465452
@input.each { |line|
466-
assert("rack.input#each didn't yield a String") {
467-
line.kind_of? String
468-
}
453+
unless line.kind_of? String
454+
raise LintError, "rack.input#each didn't yield a String"
455+
end
469456
yield line
470457
}
471458
end
@@ -476,36 +463,32 @@ def each(*args)
476463
## developers must buffer the input data into some rewindable object
477464
## if the underlying input stream is not rewindable.
478465
def rewind(*args)
479-
assert("rack.input#rewind called with arguments") { args.size == 0 }
480-
assert("rack.input#rewind raised Errno::ESPIPE") {
481-
begin
482-
@input.rewind
483-
true
484-
rescue Errno::ESPIPE
485-
false
486-
end
487-
}
466+
raise LintError, "rack.input#rewind called with arguments" unless args.size == 0
467+
begin
468+
@input.rewind
469+
true
470+
rescue Errno::ESPIPE
471+
raise LintError, "rack.input#rewind raised Errno::ESPIPE"
472+
end
488473
end
489474

490475
## * +close+ must never be called on the input stream.
491476
def close(*args)
492-
assert("rack.input#close must not be called") { false }
477+
raise LintError, "rack.input#close must not be called"
493478
end
494479
end
495480

496481
## === The Error Stream
497482
def check_error(error)
498483
## The error stream must respond to +puts+, +write+ and +flush+.
499484
[:puts, :write, :flush].each { |method|
500-
assert("rack.error #{error} does not respond to ##{method}") {
501-
error.respond_to? method
502-
}
485+
unless error.respond_to? method
486+
raise LintError, "rack.error #{error} does not respond to ##{method}"
487+
end
503488
}
504489
end
505490

506491
class ErrorWrapper
507-
include Assertion
508-
509492
def initialize(error)
510493
@error = error
511494
end
@@ -517,7 +500,7 @@ def puts(str)
517500

518501
## * +write+ must be called with a single argument that is a String.
519502
def write(str)
520-
assert("rack.errors#write not called with a String") { str.kind_of? String }
503+
raise LintError, "rack.errors#write not called with a String" unless str.kind_of? String
521504
@error.write str
522505
end
523506

@@ -529,12 +512,11 @@ def flush
529512

530513
## * +close+ must never be called on the error stream.
531514
def close(*args)
532-
assert("rack.errors#close must not be called") { false }
515+
raise LintError, "rack.errors#close must not be called"
533516
end
534517
end
535518

536519
class HijackWrapper
537-
include Assertion
538520
extend Forwardable
539521

540522
REQUIRED_METHODS = [
@@ -547,7 +529,7 @@ class HijackWrapper
547529
def initialize(io)
548530
@io = io
549531
REQUIRED_METHODS.each do |meth|
550-
assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth }
532+
raise LintError, "rack.hijack_io must respond to #{meth}" unless io.respond_to? meth
551533
end
552534
end
553535
end
@@ -563,7 +545,7 @@ def check_hijack(env)
563545
if env[RACK_IS_HIJACK]
564546
## If rack.hijack? is true then rack.hijack must respond to #call.
565547
original_hijack = env[RACK_HIJACK]
566-
assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
548+
raise LintError, "rack.hijack must respond to call" unless original_hijack.respond_to?(:call)
567549
env[RACK_HIJACK] = proc do
568550
## rack.hijack must return the io that will also be assigned (or is
569551
## already present, in rack.hijack_io.
@@ -596,10 +578,10 @@ def check_hijack(env)
596578
else
597579
##
598580
## If rack.hijack? is false, then rack.hijack should not be set.
599-
assert("rack.hijack? is false, but rack.hijack is present") { env[RACK_HIJACK].nil? }
581+
raise LintError, "rack.hijack? is false, but rack.hijack is present" unless env[RACK_HIJACK].nil?
600582
##
601583
## If rack.hijack? is false, then rack.hijack_io should not be set.
602-
assert("rack.hijack? is false, but rack.hijack_io is present") { env[RACK_HIJACK_IO].nil? }
584+
raise LintError, "rack.hijack? is false, but rack.hijack_io is present" unless env[RACK_HIJACK_IO].nil?
603585
end
604586
end
605587

@@ -630,9 +612,9 @@ def check_hijack_response(headers, env)
630612
## the <tt>rack.hijack</tt> response API is in use.
631613

632614
if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
633-
assert('rack.hijack header must respond to #call') {
634-
headers[RACK_HIJACK].respond_to? :call
635-
}
615+
unless headers[RACK_HIJACK].respond_to? :call
616+
raise LintError, 'rack.hijack header must respond to #call'
617+
end
636618
original_hijack = headers[RACK_HIJACK]
637619
proc do |io|
638620
original_hijack.call HijackWrapper.new(io)
@@ -641,9 +623,9 @@ def check_hijack_response(headers, env)
641623
##
642624
## The special response header <tt>rack.hijack</tt> must only be set
643625
## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
644-
assert('rack.hijack header must not be present if server does not support hijacking') {
645-
headers[RACK_HIJACK].nil?
646-
}
626+
unless headers[RACK_HIJACK].nil?
627+
raise LintError, 'rack.hijack header must not be present if server does not support hijacking'
628+
end
647629

648630
nil
649631
end
@@ -661,44 +643,45 @@ def check_hijack_response(headers, env)
661643
def check_status(status)
662644
## This is an HTTP status. It must be an Integer greater than or equal to
663645
## 100.
664-
assert("Status must be an Integer >=100") {
665-
status.is_a?(Integer) && status >= 100
666-
}
646+
unless status.is_a?(Integer) && status >= 100
647+
raise LintError, "Status must be an Integer >=100"
648+
end
667649
end
668650

669651
## === The Headers
670652
def check_headers(header)
671653
## The header must respond to +each+, and yield values of key and value.
672-
assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
673-
header.respond_to? :each
674-
}
654+
unless header.respond_to? :each
655+
raise LintError, "headers object should respond to #each, but doesn't (got #{header.class} as headers)"
656+
end
675657

676658
header.each { |key, value|
677659
## The header keys must be Strings.
678-
assert("header key must be a string, was #{key.class}") {
679-
key.kind_of? String
680-
}
660+
unless key.kind_of? String
661+
raise LintError, "header key must be a string, was #{key.class}"
662+
end
681663

682664
## Special headers starting "rack." are for communicating with the
683665
## server, and must not be sent back to the client.
684666
next if key =~ /^rack\..+$/
685667

686668
## The header must not contain a +Status+ key.
687-
assert("header must not contain Status") { key.downcase != "status" }
669+
raise LintError, "header must not contain Status" if key.downcase == "status"
688670
## The header must conform to RFC7230 token specification, i.e. cannot
689671
## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
690-
assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ }
672+
raise LintError, "invalid header name: #{key}" if key =~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/
691673

692674
## The values of the header must be Strings,
693-
assert("a header value must be a String, but the value of " +
694-
"'#{key}' is a #{value.class}") { value.kind_of? String }
675+
unless value.kind_of? String
676+
raise LintError, "a header value must be a String, but the value of '#{key}' is a #{value.class}"
677+
end
695678
## consisting of lines (for multiple header values, e.g. multiple
696679
## <tt>Set-Cookie</tt> values) separated by "\\n".
697680
value.split("\n").each { |item|
698681
## The lines must not contain characters below 037.
699-
assert("invalid header value #{key}: #{item.inspect}") {
700-
item !~ /[\000-\037]/
701-
}
682+
if item =~ /[\000-\037]/
683+
raise LintError, "invalid header value #{key}: #{item.inspect}"
684+
end
702685
}
703686
}
704687
end
@@ -709,9 +692,9 @@ def check_content_type(status, headers)
709692
## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
710693
## 204 or 304.
711694
if key.downcase == "content-type"
712-
assert("Content-Type header found in #{status} response, not allowed") {
713-
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
714-
}
695+
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
696+
raise LintError, "Content-Type header found in #{status} response, not allowed"
697+
end
715698
return
716699
end
717700
}
@@ -723,23 +706,23 @@ def check_content_length(status, headers)
723706
if key.downcase == 'content-length'
724707
## There must not be a <tt>Content-Length</tt> header when the
725708
## +Status+ is 1xx, 204 or 304.
726-
assert("Content-Length header found in #{status} response, not allowed") {
727-
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
728-
}
709+
if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
710+
raise LintError, "Content-Length header found in #{status} response, not allowed"
711+
end
729712
@content_length = value
730713
end
731714
}
732715
end
733716

734717
def verify_content_length(bytes)
735718
if @head_request
736-
assert("Response body was given for HEAD request, but should be empty") {
737-
bytes == 0
738-
}
719+
unless bytes == 0
720+
raise LintError, "Response body was given for HEAD request, but should be empty"
721+
end
739722
elsif @content_length
740-
assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
741-
@content_length == bytes.to_s
742-
}
723+
unless @content_length == bytes.to_s
724+
raise LintError, "Content-Length header was #{@content_length}, but should be #{bytes}"
725+
end
743726
end
744727
end
745728

@@ -749,15 +732,15 @@ def each
749732
bytes = 0
750733

751734
## The Body must respond to +each+
752-
assert("Response body must respond to each") do
753-
@body.respond_to?(:each)
735+
unless @body.respond_to?(:each)
736+
raise LintError, "Response body must respond to each"
754737
end
755738

756739
@body.each { |part|
757740
## and must only yield String values.
758-
assert("Body yielded non-string value #{part.inspect}") {
759-
part.kind_of? String
760-
}
741+
unless part.kind_of? String
742+
raise LintError, "Body yielded non-string value #{part.inspect}"
743+
end
761744
bytes += part.bytesize
762745
yield part
763746
}
@@ -770,7 +753,7 @@ def each
770753
## If the Body responds to +close+, it will be called after iteration. If
771754
## the body is replaced by a middleware after action, the original body
772755
## must be closed first, if it responds to close.
773-
# XXX howto: assert("Body has not been closed") { @closed }
756+
# XXX howto: raise LintError, "Body has not been closed" unless @closed
774757

775758

776759
##
@@ -781,9 +764,9 @@ def each
781764
## transport the response.
782765

783766
if @body.respond_to?(:to_path)
784-
assert("The file identified by body.to_path does not exist") {
785-
::File.exist? @body.to_path
786-
}
767+
unless ::File.exist? @body.to_path
768+
raise LintError, "The file identified by body.to_path does not exist"
769+
end
787770
end
788771

789772
##

0 commit comments

Comments
 (0)
Please sign in to comment.