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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +713 -10
- data/CONTRIBUTING.md +136 -0
- data/README.rdoc +109 -38
- data/Rakefile +14 -7
- data/{SPEC → SPEC.rdoc} +35 -6
- data/lib/rack/auth/abstract/request.rb +0 -2
- data/lib/rack/auth/basic.rb +4 -5
- data/lib/rack/auth/digest/md5.rb +4 -4
- data/lib/rack/auth/digest/nonce.rb +2 -3
- data/lib/rack/auth/digest/request.rb +3 -3
- data/lib/rack/body_proxy.rb +13 -9
- data/lib/rack/builder.rb +77 -8
- data/lib/rack/cascade.rb +23 -8
- data/lib/rack/chunked.rb +48 -23
- data/lib/rack/common_logger.rb +27 -19
- data/lib/rack/conditional_get.rb +18 -16
- data/lib/rack/content_length.rb +6 -7
- data/lib/rack/content_type.rb +3 -4
- data/lib/rack/deflater.rb +45 -35
- data/lib/rack/directory.rb +77 -60
- data/lib/rack/etag.rb +4 -3
- data/lib/rack/events.rb +15 -18
- data/lib/rack/file.rb +1 -1
- data/lib/rack/files.rb +96 -56
- data/lib/rack/handler/cgi.rb +1 -4
- data/lib/rack/handler/fastcgi.rb +1 -3
- data/lib/rack/handler/lsws.rb +1 -3
- data/lib/rack/handler/scgi.rb +1 -3
- data/lib/rack/handler/thin.rb +1 -3
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/head.rb +0 -2
- data/lib/rack/lint.rb +211 -179
- data/lib/rack/lobster.rb +3 -5
- data/lib/rack/lock.rb +0 -1
- data/lib/rack/media_type.rb +17 -7
- data/lib/rack/method_override.rb +1 -1
- data/lib/rack/mock.rb +54 -7
- data/lib/rack/multipart/generator.rb +11 -6
- data/lib/rack/multipart/parser.rb +17 -17
- data/lib/rack/multipart/uploaded_file.rb +13 -7
- data/lib/rack/multipart.rb +1 -1
- data/lib/rack/query_parser.rb +62 -16
- data/lib/rack/recursive.rb +1 -1
- data/lib/rack/reloader.rb +1 -3
- data/lib/rack/request.rb +184 -78
- data/lib/rack/response.rb +62 -19
- data/lib/rack/rewindable_input.rb +0 -1
- data/lib/rack/runtime.rb +3 -3
- data/lib/rack/sendfile.rb +1 -4
- data/lib/rack/server.rb +9 -8
- data/lib/rack/session/abstract/id.rb +21 -18
- data/lib/rack/session/cookie.rb +4 -6
- data/lib/rack/session/pool.rb +7 -2
- data/lib/rack/show_exceptions.rb +6 -8
- data/lib/rack/show_status.rb +5 -7
- data/lib/rack/static.rb +15 -7
- data/lib/rack/tempfile_reaper.rb +0 -2
- data/lib/rack/urlmap.rb +2 -5
- data/lib/rack/utils.rb +69 -58
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +7 -16
- data/rack.gemspec +31 -29
- 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
|
-
|
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
|
-
|
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
|
-
|
72
|
-
|
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
|
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>
|
116
|
-
##
|
117
|
-
|
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
|
-
|
184
|
-
session.
|
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
|
-
|
189
|
-
session.
|
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
|
-
|
194
|
-
session.
|
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
|
-
|
199
|
-
session.
|
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
|
-
|
208
|
-
logger.
|
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
|
-
|
213
|
-
logger.
|
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
|
-
|
218
|
-
logger.
|
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
|
-
|
223
|
-
logger.
|
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
|
-
|
228
|
-
logger.
|
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
|
-
|
235
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
268
|
-
|
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
|
-
|
276
|
-
value.
|
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
|
-
|
284
|
-
env[RACK_VERSION].
|
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
|
-
|
288
|
-
|
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
|
-
|
300
|
-
|
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
|
-
|
305
|
-
|
306
|
-
|
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
|
-
|
311
|
-
|
312
|
-
|
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
|
-
|
317
|
-
|
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
|
-
|
324
|
-
|
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
|
-
|
328
|
-
|
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
|
-
|
340
|
-
input
|
341
|
-
|
342
|
-
|
343
|
-
input
|
344
|
-
|
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
|
-
|
349
|
-
input
|
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
|
-
|
393
|
+
raise LintError, "rack.input#gets called with arguments" unless args.size == 0
|
365
394
|
v = @input.gets
|
366
|
-
|
367
|
-
|
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
|
-
|
391
|
-
|
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
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
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
|
-
|
403
|
-
|
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
|
-
|
410
|
-
|
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
|
-
|
414
|
-
|
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
|
-
|
452
|
+
raise LintError, "rack.input#each called with arguments" unless args.size == 0
|
424
453
|
@input.each { |line|
|
425
|
-
|
426
|
-
|
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
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
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
|
-
|
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
|
-
|
460
|
-
error
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
593
|
-
|
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
|
-
|
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
|
-
|
604
|
-
|
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
|
-
|
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
|
-
|
628
|
-
|
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
|
-
|
634
|
-
key.
|
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
|
-
|
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
|
-
|
676
|
+
raise LintError, "invalid header name: #{key}" if key =~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/
|
646
677
|
|
647
678
|
## The values of the header must be Strings,
|
648
|
-
|
649
|
-
"'#{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
|
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
|
-
|
655
|
-
|
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
|
-
|
668
|
-
|
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
|
-
|
682
|
-
|
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
|
-
|
692
|
-
|
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
|
-
|
696
|
-
@content_length
|
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
|
-
|
708
|
-
|
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
|
-
|
714
|
-
part.
|
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:
|
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
|
-
|
740
|
-
|
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
|
##
|