rack 2.1.4.4 → 2.2.17

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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +713 -10
  3. data/CONTRIBUTING.md +136 -0
  4. data/README.rdoc +109 -38
  5. data/Rakefile +14 -7
  6. data/{SPEC → SPEC.rdoc} +35 -6
  7. data/lib/rack/auth/abstract/request.rb +0 -2
  8. data/lib/rack/auth/basic.rb +4 -5
  9. data/lib/rack/auth/digest/md5.rb +4 -4
  10. data/lib/rack/auth/digest/nonce.rb +2 -3
  11. data/lib/rack/auth/digest/request.rb +3 -3
  12. data/lib/rack/body_proxy.rb +13 -9
  13. data/lib/rack/builder.rb +77 -8
  14. data/lib/rack/cascade.rb +23 -8
  15. data/lib/rack/chunked.rb +48 -23
  16. data/lib/rack/common_logger.rb +27 -19
  17. data/lib/rack/conditional_get.rb +18 -16
  18. data/lib/rack/content_length.rb +6 -7
  19. data/lib/rack/content_type.rb +3 -4
  20. data/lib/rack/deflater.rb +45 -35
  21. data/lib/rack/directory.rb +77 -60
  22. data/lib/rack/etag.rb +4 -3
  23. data/lib/rack/events.rb +15 -18
  24. data/lib/rack/file.rb +1 -1
  25. data/lib/rack/files.rb +96 -56
  26. data/lib/rack/handler/cgi.rb +1 -4
  27. data/lib/rack/handler/fastcgi.rb +1 -3
  28. data/lib/rack/handler/lsws.rb +1 -3
  29. data/lib/rack/handler/scgi.rb +1 -3
  30. data/lib/rack/handler/thin.rb +1 -3
  31. data/lib/rack/handler/webrick.rb +12 -5
  32. data/lib/rack/head.rb +0 -2
  33. data/lib/rack/lint.rb +211 -179
  34. data/lib/rack/lobster.rb +3 -5
  35. data/lib/rack/lock.rb +0 -1
  36. data/lib/rack/media_type.rb +17 -7
  37. data/lib/rack/method_override.rb +1 -1
  38. data/lib/rack/mock.rb +54 -7
  39. data/lib/rack/multipart/generator.rb +11 -6
  40. data/lib/rack/multipart/parser.rb +17 -17
  41. data/lib/rack/multipart/uploaded_file.rb +13 -7
  42. data/lib/rack/multipart.rb +1 -1
  43. data/lib/rack/query_parser.rb +62 -16
  44. data/lib/rack/recursive.rb +1 -1
  45. data/lib/rack/reloader.rb +1 -3
  46. data/lib/rack/request.rb +184 -78
  47. data/lib/rack/response.rb +62 -19
  48. data/lib/rack/rewindable_input.rb +0 -1
  49. data/lib/rack/runtime.rb +3 -3
  50. data/lib/rack/sendfile.rb +1 -4
  51. data/lib/rack/server.rb +9 -8
  52. data/lib/rack/session/abstract/id.rb +21 -18
  53. data/lib/rack/session/cookie.rb +4 -6
  54. data/lib/rack/session/pool.rb +7 -2
  55. data/lib/rack/show_exceptions.rb +6 -8
  56. data/lib/rack/show_status.rb +5 -7
  57. data/lib/rack/static.rb +15 -7
  58. data/lib/rack/tempfile_reaper.rb +0 -2
  59. data/lib/rack/urlmap.rb +2 -5
  60. data/lib/rack/utils.rb +69 -58
  61. data/lib/rack/version.rb +29 -0
  62. data/lib/rack.rb +7 -16
  63. data/rack.gemspec +31 -29
  64. metadata +12 -16
data/lib/rack/lint.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/utils'
4
3
  require 'forwardable'
5
4
 
6
5
  module Rack
@@ -41,20 +40,27 @@ module Rack
41
40
 
42
41
  def _call(env)
43
42
  ## It takes exactly one argument, the *environment*
44
- assert("No env given") { env }
43
+ raise LintError, "No env given" unless env
45
44
  check_env env
46
45
 
47
46
  env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
48
47
  env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
49
48
 
50
49
  ## and returns an Array of exactly three values:
51
- status, headers, @body = @app.call(env)
50
+ ary = @app.call(env)
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
53
+
54
+ status, headers, @body = ary
52
55
  ## The *status*,
53
56
  check_status status
54
57
  ## the *headers*,
55
58
  check_headers headers
56
59
 
57
- check_hijack_response headers, env
60
+ hijack_proc = check_hijack_response headers, env
61
+ if hijack_proc && headers.is_a?(Hash)
62
+ headers[RACK_HIJACK] = hijack_proc
63
+ end
58
64
 
59
65
  ## and the *body*.
60
66
  check_content_type status, headers
@@ -65,12 +71,11 @@ module Rack
65
71
 
66
72
  ## == The Environment
67
73
  def check_env(env)
68
- ## The environment must be an instance of Hash that includes
74
+ ## The environment must be an unfrozen instance of Hash that includes
69
75
  ## CGI-like headers. The application is free to modify the
70
76
  ## environment.
71
- assert("env #{env.inspect} is not a Hash, but #{env.class}") {
72
- env.kind_of? Hash
73
- }
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?
74
79
 
75
80
  ##
76
81
  ## The environment is required to include these variables
@@ -104,17 +109,19 @@ module Rack
104
109
  ## follows the <tt>?</tt>, if any. May be
105
110
  ## empty, but is always required!
106
111
 
107
- ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
108
- ## When combined with <tt>SCRIPT_NAME</tt> and
112
+ ## <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
109
113
  ## <tt>PATH_INFO</tt>, these variables can be
110
114
  ## used to complete the URL. Note, however,
111
115
  ## that <tt>HTTP_HOST</tt>, if present,
112
116
  ## should be used in preference to
113
117
  ## <tt>SERVER_NAME</tt> for reconstructing
114
118
  ## the request URL.
115
- ## <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
116
- ## can never be empty strings, and so
117
- ## are always required.
119
+ ## <tt>SERVER_NAME</tt> can never be an empty
120
+ ## string, and so is always required.
121
+
122
+ ## <tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
123
+ ## server is running on. Should be specified if
124
+ ## the server is running on a non-standard port.
118
125
 
119
126
  ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
120
127
  ## client-supplied HTTP request
@@ -180,68 +187,73 @@ module Rack
180
187
  ## The store must implement:
181
188
  if session = env[RACK_SESSION]
182
189
  ## store(key, value) (aliased as []=);
183
- assert("session #{session.inspect} must respond to store and []=") {
184
- session.respond_to?(:store) && session.respond_to?(:[]=)
185
- }
190
+ unless session.respond_to?(:store) && session.respond_to?(:[]=)
191
+ raise LintError, "session #{session.inspect} must respond to store and []="
192
+ end
186
193
 
187
194
  ## fetch(key, default = nil) (aliased as []);
188
- assert("session #{session.inspect} must respond to fetch and []") {
189
- session.respond_to?(:fetch) && session.respond_to?(:[])
190
- }
195
+ unless session.respond_to?(:fetch) && session.respond_to?(:[])
196
+ raise LintError, "session #{session.inspect} must respond to fetch and []"
197
+ end
191
198
 
192
199
  ## delete(key);
193
- assert("session #{session.inspect} must respond to delete") {
194
- session.respond_to?(:delete)
195
- }
200
+ unless session.respond_to?(:delete)
201
+ raise LintError, "session #{session.inspect} must respond to delete"
202
+ end
196
203
 
197
204
  ## clear;
198
- assert("session #{session.inspect} must respond to clear") {
199
- session.respond_to?(:clear)
200
- }
205
+ unless session.respond_to?(:clear)
206
+ raise LintError, "session #{session.inspect} must respond to clear"
207
+ end
208
+
209
+ ## to_hash (returning unfrozen Hash instance);
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
201
213
  end
202
214
 
203
215
  ## <tt>rack.logger</tt>:: A common object interface for logging messages.
204
216
  ## The object must implement:
205
217
  if logger = env[RACK_LOGGER]
206
218
  ## info(message, &block)
207
- assert("logger #{logger.inspect} must respond to info") {
208
- logger.respond_to?(:info)
209
- }
219
+ unless logger.respond_to?(:info)
220
+ raise LintError, "logger #{logger.inspect} must respond to info"
221
+ end
210
222
 
211
223
  ## debug(message, &block)
212
- assert("logger #{logger.inspect} must respond to debug") {
213
- logger.respond_to?(:debug)
214
- }
224
+ unless logger.respond_to?(:debug)
225
+ raise LintError, "logger #{logger.inspect} must respond to debug"
226
+ end
215
227
 
216
228
  ## warn(message, &block)
217
- assert("logger #{logger.inspect} must respond to warn") {
218
- logger.respond_to?(:warn)
219
- }
229
+ unless logger.respond_to?(:warn)
230
+ raise LintError, "logger #{logger.inspect} must respond to warn"
231
+ end
220
232
 
221
233
  ## error(message, &block)
222
- assert("logger #{logger.inspect} must respond to error") {
223
- logger.respond_to?(:error)
224
- }
234
+ unless logger.respond_to?(:error)
235
+ raise LintError, "logger #{logger.inspect} must respond to error"
236
+ end
225
237
 
226
238
  ## fatal(message, &block)
227
- assert("logger #{logger.inspect} must respond to fatal") {
228
- logger.respond_to?(:fatal)
229
- }
239
+ unless logger.respond_to?(:fatal)
240
+ raise LintError, "logger #{logger.inspect} must respond to fatal"
241
+ end
230
242
  end
231
243
 
232
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.
233
245
  if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
234
- assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
235
- bufsize.is_a?(Integer) && bufsize > 0
236
- }
246
+ unless bufsize.is_a?(Integer) && bufsize > 0
247
+ raise LintError, "rack.multipart.buffer_size must be an Integer > 0 if specified"
248
+ end
237
249
  end
238
250
 
239
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.
240
252
  if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
241
- assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
253
+ raise LintError, "rack.multipart.tempfile_factory must respond to #call" unless tempfile_factory.respond_to?(:call)
242
254
  env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
243
255
  io = tempfile_factory.call(filename, content_type)
244
- assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
256
+ raise LintError, "rack.multipart.tempfile_factory return value must respond to #<<" unless io.respond_to?(:<<)
245
257
  io
246
258
  end
247
259
  end
@@ -253,40 +265,61 @@ module Rack
253
265
  ## accepted specifications and must not be used otherwise.
254
266
  ##
255
267
 
256
- %w[REQUEST_METHOD SERVER_NAME SERVER_PORT
257
- QUERY_STRING
268
+ %w[REQUEST_METHOD SERVER_NAME QUERY_STRING
258
269
  rack.version rack.input rack.errors
259
270
  rack.multithread rack.multiprocess rack.run_once].each { |header|
260
- assert("env missing required key #{header}") { env.include? header }
271
+ raise LintError, "env missing required key #{header}" unless env.include? header
261
272
  }
262
273
 
274
+ ## The <tt>SERVER_PORT</tt> must be an Integer if set.
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"
278
+ end
279
+
280
+ ## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
281
+ unless (URI.parse("http://#{env[SERVER_NAME]}/") rescue false)
282
+ raise LintError, "#{env[SERVER_NAME]} must be a valid authority"
283
+ end
284
+
285
+ ## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
286
+ unless (URI.parse("http://#{env[HTTP_HOST]}/") rescue false)
287
+ raise LintError, "#{env[HTTP_HOST]} must be a valid authority"
288
+ end
289
+
263
290
  ## The environment must not contain the keys
264
291
  ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
265
292
  ## (use the versions without <tt>HTTP_</tt>).
266
293
  %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
267
- assert("env contains #{header}, must use #{header[5, -1]}") {
268
- not env.include? header
269
- }
294
+ if env.include? header
295
+ raise LintError, "env contains #{header}, must use #{header[5, -1]}"
296
+ end
270
297
  }
271
298
 
272
299
  ## The CGI keys (named without a period) must have String values.
300
+ ## If the string values for CGI keys contain non-ASCII characters,
301
+ ## they should use ASCII-8BIT encoding.
273
302
  env.each { |key, value|
274
303
  next if key.include? "." # Skip extensions
275
- assert("env variable #{key} has non-string value #{value.inspect}") {
276
- value.kind_of? String
277
- }
304
+ unless value.kind_of? String
305
+ raise LintError, "env variable #{key} has non-string value #{value.inspect}"
306
+ end
307
+ next if value.encoding == Encoding::ASCII_8BIT
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
278
311
  }
279
312
 
280
313
  ## There are the following restrictions:
281
314
 
282
315
  ## * <tt>rack.version</tt> must be an array of Integers.
283
- assert("rack.version must be an Array, was #{env[RACK_VERSION].class}") {
284
- env[RACK_VERSION].kind_of? Array
285
- }
316
+ unless env[RACK_VERSION].kind_of? Array
317
+ raise LintError, "rack.version must be an Array, was #{env[RACK_VERSION].class}"
318
+ end
286
319
  ## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
287
- assert("rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}") {
288
- %w[http https].include?(env[RACK_URL_SCHEME])
289
- }
320
+ unless %w[http https].include?(env[RACK_URL_SCHEME])
321
+ raise LintError, "rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}"
322
+ end
290
323
 
291
324
  ## * There must be a valid input stream in <tt>rack.input</tt>.
292
325
  check_input env[RACK_INPUT]
@@ -296,37 +329,33 @@ module Rack
296
329
  check_hijack env
297
330
 
298
331
  ## * The <tt>REQUEST_METHOD</tt> must be a valid token.
299
- assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD].dump}") {
300
- env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
301
- }
332
+ unless env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
333
+ raise LintError, "REQUEST_METHOD unknown: #{env[REQUEST_METHOD].dump}"
334
+ end
302
335
 
303
336
  ## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
304
- assert("SCRIPT_NAME must start with /") {
305
- !env.include?(SCRIPT_NAME) ||
306
- env[SCRIPT_NAME] == "" ||
307
- env[SCRIPT_NAME] =~ /\A\//
308
- }
337
+ if env.include?(SCRIPT_NAME) && env[SCRIPT_NAME] != "" && env[SCRIPT_NAME] !~ /\A\//
338
+ raise LintError, "SCRIPT_NAME must start with /"
339
+ end
309
340
  ## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
310
- assert("PATH_INFO must start with /") {
311
- !env.include?(PATH_INFO) ||
312
- env[PATH_INFO] == "" ||
313
- env[PATH_INFO] =~ /\A\//
314
- }
341
+ if env.include?(PATH_INFO) && env[PATH_INFO] != "" && env[PATH_INFO] !~ /\A\//
342
+ raise LintError, "PATH_INFO must start with /"
343
+ end
315
344
  ## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
316
- assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
317
- !env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
318
- }
345
+ if env.include?("CONTENT_LENGTH") && env["CONTENT_LENGTH"] !~ /\A\d+\z/
346
+ raise LintError, "Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}"
347
+ end
319
348
 
320
349
  ## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
321
350
  ## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
322
351
  ## <tt>SCRIPT_NAME</tt> is empty.
323
- assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
324
- env[SCRIPT_NAME] || env[PATH_INFO]
325
- }
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
326
355
  ## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
327
- assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
328
- env[SCRIPT_NAME] != "/"
329
- }
356
+ unless env[SCRIPT_NAME] != "/"
357
+ raise LintError, "SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'"
358
+ end
330
359
  end
331
360
 
332
361
  ## === The Input Stream
@@ -336,18 +365,18 @@ module Rack
336
365
  def check_input(input)
337
366
  ## When applicable, its external encoding must be "ASCII-8BIT" and it
338
367
  ## must be opened in binary mode, for Ruby 1.9 compatibility.
339
- assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
340
- input.external_encoding.name == "ASCII-8BIT"
341
- } if input.respond_to?(:external_encoding)
342
- assert("rack.input #{input} is not opened in binary mode") {
343
- input.binmode?
344
- } if input.respond_to?(:binmode?)
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
345
374
 
346
375
  ## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
347
376
  [:gets, :each, :read, :rewind].each { |method|
348
- assert("rack.input #{input} does not respond to ##{method}") {
349
- input.respond_to? method
350
- }
377
+ unless input.respond_to? method
378
+ raise LintError, "rack.input #{input} does not respond to ##{method}"
379
+ end
351
380
  }
352
381
  end
353
382
 
@@ -361,11 +390,11 @@ module Rack
361
390
  ## * +gets+ must be called without arguments and return a string,
362
391
  ## or +nil+ on EOF.
363
392
  def gets(*args)
364
- assert("rack.input#gets called with arguments") { args.size == 0 }
393
+ raise LintError, "rack.input#gets called with arguments" unless args.size == 0
365
394
  v = @input.gets
366
- assert("rack.input#gets didn't return a String") {
367
- v.nil? or v.kind_of? String
368
- }
395
+ unless v.nil? or v.kind_of? String
396
+ raise LintError, "rack.input#gets didn't return a String"
397
+ end
369
398
  v
370
399
  end
371
400
 
@@ -387,32 +416,32 @@ module Rack
387
416
  ## If +buffer+ is given, then the read data will be placed
388
417
  ## into +buffer+ instead of a newly created String object.
389
418
  def read(*args)
390
- assert("rack.input#read called with too many arguments") {
391
- args.size <= 2
392
- }
419
+ unless args.size <= 2
420
+ raise LintError, "rack.input#read called with too many arguments"
421
+ end
393
422
  if args.size >= 1
394
- assert("rack.input#read called with non-integer and non-nil length") {
395
- args.first.kind_of?(Integer) || args.first.nil?
396
- }
397
- assert("rack.input#read called with a negative length") {
398
- args.first.nil? || args.first >= 0
399
- }
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
400
429
  end
401
430
  if args.size >= 2
402
- assert("rack.input#read called with non-String buffer") {
403
- args[1].kind_of?(String)
404
- }
431
+ unless args[1].kind_of?(String)
432
+ raise LintError, "rack.input#read called with non-String buffer"
433
+ end
405
434
  end
406
435
 
407
436
  v = @input.read(*args)
408
437
 
409
- assert("rack.input#read didn't return nil or a String") {
410
- v.nil? or v.kind_of? String
411
- }
438
+ unless v.nil? or v.kind_of? String
439
+ raise LintError, "rack.input#read didn't return nil or a String"
440
+ end
412
441
  if args[0].nil?
413
- assert("rack.input#read(nil) returned nil on EOF") {
414
- !v.nil?
415
- }
442
+ unless !v.nil?
443
+ raise LintError, "rack.input#read(nil) returned nil on EOF"
444
+ end
416
445
  end
417
446
 
418
447
  v
@@ -420,11 +449,11 @@ module Rack
420
449
 
421
450
  ## * +each+ must be called without arguments and only yield Strings.
422
451
  def each(*args)
423
- assert("rack.input#each called with arguments") { args.size == 0 }
452
+ raise LintError, "rack.input#each called with arguments" unless args.size == 0
424
453
  @input.each { |line|
425
- assert("rack.input#each didn't yield a String") {
426
- line.kind_of? String
427
- }
454
+ unless line.kind_of? String
455
+ raise LintError, "rack.input#each didn't yield a String"
456
+ end
428
457
  yield line
429
458
  }
430
459
  end
@@ -435,20 +464,18 @@ module Rack
435
464
  ## developers must buffer the input data into some rewindable object
436
465
  ## if the underlying input stream is not rewindable.
437
466
  def rewind(*args)
438
- assert("rack.input#rewind called with arguments") { args.size == 0 }
439
- assert("rack.input#rewind raised Errno::ESPIPE") {
440
- begin
441
- @input.rewind
442
- true
443
- rescue Errno::ESPIPE
444
- false
445
- end
446
- }
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
447
474
  end
448
475
 
449
476
  ## * +close+ must never be called on the input stream.
450
477
  def close(*args)
451
- assert("rack.input#close must not be called") { false }
478
+ raise LintError, "rack.input#close must not be called"
452
479
  end
453
480
  end
454
481
 
@@ -456,9 +483,9 @@ module Rack
456
483
  def check_error(error)
457
484
  ## The error stream must respond to +puts+, +write+ and +flush+.
458
485
  [:puts, :write, :flush].each { |method|
459
- assert("rack.error #{error} does not respond to ##{method}") {
460
- error.respond_to? method
461
- }
486
+ unless error.respond_to? method
487
+ raise LintError, "rack.error #{error} does not respond to ##{method}"
488
+ end
462
489
  }
463
490
  end
464
491
 
@@ -476,7 +503,7 @@ module Rack
476
503
 
477
504
  ## * +write+ must be called with a single argument that is a String.
478
505
  def write(str)
479
- assert("rack.errors#write not called with a String") { str.kind_of? String }
506
+ raise LintError, "rack.errors#write not called with a String" unless str.kind_of? String
480
507
  @error.write str
481
508
  end
482
509
 
@@ -488,7 +515,7 @@ module Rack
488
515
 
489
516
  ## * +close+ must never be called on the error stream.
490
517
  def close(*args)
491
- assert("rack.errors#close must not be called") { false }
518
+ raise LintError, "rack.errors#close must not be called"
492
519
  end
493
520
  end
494
521
 
@@ -506,7 +533,7 @@ module Rack
506
533
  def initialize(io)
507
534
  @io = io
508
535
  REQUIRED_METHODS.each do |meth|
509
- assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth }
536
+ raise LintError, "rack.hijack_io must respond to #{meth}" unless io.respond_to? meth
510
537
  end
511
538
  end
512
539
  end
@@ -522,7 +549,7 @@ module Rack
522
549
  if env[RACK_IS_HIJACK]
523
550
  ## If rack.hijack? is true then rack.hijack must respond to #call.
524
551
  original_hijack = env[RACK_HIJACK]
525
- assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
552
+ raise LintError, "rack.hijack must respond to call" unless original_hijack.respond_to?(:call)
526
553
  env[RACK_HIJACK] = proc do
527
554
  ## rack.hijack must return the io that will also be assigned (or is
528
555
  ## already present, in rack.hijack_io.
@@ -555,10 +582,10 @@ module Rack
555
582
  else
556
583
  ##
557
584
  ## If rack.hijack? is false, then rack.hijack should not be set.
558
- assert("rack.hijack? is false, but rack.hijack is present") { env[RACK_HIJACK].nil? }
585
+ raise LintError, "rack.hijack? is false, but rack.hijack is present" unless env[RACK_HIJACK].nil?
559
586
  ##
560
587
  ## If rack.hijack? is false, then rack.hijack_io should not be set.
561
- assert("rack.hijack? is false, but rack.hijack_io is present") { env[RACK_HIJACK_IO].nil? }
588
+ raise LintError, "rack.hijack? is false, but rack.hijack_io is present" unless env[RACK_HIJACK_IO].nil?
562
589
  end
563
590
  end
564
591
 
@@ -569,7 +596,7 @@ module Rack
569
596
 
570
597
  # this check uses headers like a hash, but the spec only requires
571
598
  # headers respond to #each
572
- headers = Rack::Utils::HeaderHash.new(headers)
599
+ headers = Rack::Utils::HeaderHash[headers]
573
600
 
574
601
  ## In order to do this, an application may set the special header
575
602
  ## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
@@ -589,20 +616,22 @@ module Rack
589
616
  ## the <tt>rack.hijack</tt> response API is in use.
590
617
 
591
618
  if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
592
- assert('rack.hijack header must respond to #call') {
593
- headers[RACK_HIJACK].respond_to? :call
594
- }
619
+ unless headers[RACK_HIJACK].respond_to? :call
620
+ raise LintError, 'rack.hijack header must respond to #call'
621
+ end
595
622
  original_hijack = headers[RACK_HIJACK]
596
- headers[RACK_HIJACK] = proc do |io|
623
+ proc do |io|
597
624
  original_hijack.call HijackWrapper.new(io)
598
625
  end
599
626
  else
600
627
  ##
601
628
  ## The special response header <tt>rack.hijack</tt> must only be set
602
629
  ## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
603
- assert('rack.hijack header must not be present if server does not support hijacking') {
604
- headers[RACK_HIJACK].nil?
605
- }
630
+ unless headers[RACK_HIJACK].nil?
631
+ raise LintError, 'rack.hijack header must not be present if server does not support hijacking'
632
+ end
633
+
634
+ nil
606
635
  end
607
636
  end
608
637
  ## ==== Conventions
@@ -618,42 +647,45 @@ module Rack
618
647
  def check_status(status)
619
648
  ## This is an HTTP status. When parsed as integer (+to_i+), it must be
620
649
  ## greater than or equal to 100.
621
- assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
650
+ unless status.to_i >= 100
651
+ raise LintError, "Status must be >=100 seen as integer"
652
+ end
622
653
  end
623
654
 
624
655
  ## === The Headers
625
656
  def check_headers(header)
626
657
  ## The header must respond to +each+, and yield values of key and value.
627
- assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
628
- header.respond_to? :each
629
- }
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
630
661
 
631
662
  header.each { |key, value|
632
663
  ## The header keys must be Strings.
633
- assert("header key must be a string, was #{key.class}") {
634
- key.kind_of? String
635
- }
664
+ unless key.kind_of? String
665
+ raise LintError, "header key must be a string, was #{key.class}"
666
+ end
636
667
 
637
668
  ## Special headers starting "rack." are for communicating with the
638
669
  ## server, and must not be sent back to the client.
639
670
  next if key =~ /^rack\..+$/
640
671
 
641
672
  ## The header must not contain a +Status+ key.
642
- assert("header must not contain Status") { key.downcase != "status" }
673
+ raise LintError, "header must not contain Status" if key.downcase == "status"
643
674
  ## The header must conform to RFC7230 token specification, i.e. cannot
644
675
  ## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
645
- assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ }
676
+ raise LintError, "invalid header name: #{key}" if key =~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/
646
677
 
647
678
  ## The values of the header must be Strings,
648
- assert("a header value must be a String, but the value of " +
649
- "'#{key}' is a #{value.class}") { value.kind_of? String }
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
650
682
  ## consisting of lines (for multiple header values, e.g. multiple
651
683
  ## <tt>Set-Cookie</tt> values) separated by "\\n".
652
684
  value.split("\n").each { |item|
653
685
  ## The lines must not contain characters below 037.
654
- assert("invalid header value #{key}: #{item.inspect}") {
655
- item !~ /[\000-\037]/
656
- }
686
+ if item =~ /[\000-\037]/
687
+ raise LintError, "invalid header value #{key}: #{item.inspect}"
688
+ end
657
689
  }
658
690
  }
659
691
  end
@@ -664,9 +696,9 @@ module Rack
664
696
  ## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
665
697
  ## 204 or 304.
666
698
  if key.downcase == "content-type"
667
- assert("Content-Type header found in #{status} response, not allowed") {
668
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
669
- }
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
670
702
  return
671
703
  end
672
704
  }
@@ -678,9 +710,9 @@ module Rack
678
710
  if key.downcase == 'content-length'
679
711
  ## There must not be a <tt>Content-Length</tt> header when the
680
712
  ## +Status+ is 1xx, 204 or 304.
681
- assert("Content-Length header found in #{status} response, not allowed") {
682
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
683
- }
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
684
716
  @content_length = value
685
717
  end
686
718
  }
@@ -688,13 +720,13 @@ module Rack
688
720
 
689
721
  def verify_content_length(bytes)
690
722
  if @head_request
691
- assert("Response body was given for HEAD request, but should be empty") {
692
- bytes == 0
693
- }
723
+ unless bytes == 0
724
+ raise LintError, "Response body was given for HEAD request, but should be empty"
725
+ end
694
726
  elsif @content_length
695
- assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
696
- @content_length == bytes.to_s
697
- }
727
+ unless @content_length == bytes.to_s
728
+ raise LintError, "Content-Length header was #{@content_length}, but should be #{bytes}"
729
+ end
698
730
  end
699
731
  end
700
732
 
@@ -704,15 +736,15 @@ module Rack
704
736
  bytes = 0
705
737
 
706
738
  ## The Body must respond to +each+
707
- assert("Response body must respond to each") do
708
- @body.respond_to?(:each)
739
+ unless @body.respond_to?(:each)
740
+ raise LintError, "Response body must respond to each"
709
741
  end
710
742
 
711
743
  @body.each { |part|
712
744
  ## and must only yield String values.
713
- assert("Body yielded non-string value #{part.inspect}") {
714
- part.kind_of? String
715
- }
745
+ unless part.kind_of? String
746
+ raise LintError, "Body yielded non-string value #{part.inspect}"
747
+ end
716
748
  bytes += part.bytesize
717
749
  yield part
718
750
  }
@@ -725,7 +757,7 @@ module Rack
725
757
  ## If the Body responds to +close+, it will be called after iteration. If
726
758
  ## the body is replaced by a middleware after action, the original body
727
759
  ## must be closed first, if it responds to close.
728
- # XXX howto: assert("Body has not been closed") { @closed }
760
+ # XXX howto: raise LintError, "Body has not been closed" unless @closed
729
761
 
730
762
 
731
763
  ##
@@ -736,9 +768,9 @@ module Rack
736
768
  ## transport the response.
737
769
 
738
770
  if @body.respond_to?(:to_path)
739
- assert("The file identified by body.to_path does not exist") {
740
- ::File.exist? @body.to_path
741
- }
771
+ unless ::File.exist? @body.to_path
772
+ raise LintError, "The file identified by body.to_path does not exist"
773
+ end
742
774
  end
743
775
 
744
776
  ##