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