rack 1.6.11 → 2.2.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (190) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +694 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +157 -163
  6. data/Rakefile +38 -32
  7. data/{SPEC → SPEC.rdoc} +41 -13
  8. data/bin/rackup +1 -0
  9. data/contrib/rack_logo.svg +164 -111
  10. data/example/lobster.ru +2 -0
  11. data/example/protectedlobster.rb +4 -2
  12. data/example/protectedlobster.ru +3 -1
  13. data/lib/rack/auth/abstract/handler.rb +3 -1
  14. data/lib/rack/auth/abstract/request.rb +6 -2
  15. data/lib/rack/auth/basic.rb +7 -4
  16. data/lib/rack/auth/digest/md5.rb +13 -11
  17. data/lib/rack/auth/digest/nonce.rb +6 -3
  18. data/lib/rack/auth/digest/params.rb +5 -4
  19. data/lib/rack/auth/digest/request.rb +6 -4
  20. data/lib/rack/body_proxy.rb +21 -15
  21. data/lib/rack/builder.rb +119 -26
  22. data/lib/rack/cascade.rb +28 -12
  23. data/lib/rack/chunked.rb +70 -22
  24. data/lib/rack/common_logger.rb +80 -0
  25. data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -16
  26. data/lib/rack/config.rb +2 -0
  27. data/lib/rack/content_length.rb +9 -8
  28. data/lib/rack/content_type.rb +5 -4
  29. data/lib/rack/core_ext/regexp.rb +14 -0
  30. data/lib/rack/deflater.rb +60 -70
  31. data/lib/rack/directory.rb +117 -85
  32. data/lib/rack/etag.rb +9 -7
  33. data/lib/rack/events.rb +153 -0
  34. data/lib/rack/file.rb +4 -149
  35. data/lib/rack/files.rb +218 -0
  36. data/lib/rack/handler/cgi.rb +17 -19
  37. data/lib/rack/handler/fastcgi.rb +17 -18
  38. data/lib/rack/handler/lsws.rb +14 -14
  39. data/lib/rack/handler/scgi.rb +22 -21
  40. data/lib/rack/handler/thin.rb +6 -3
  41. data/lib/rack/handler/webrick.rb +39 -32
  42. data/lib/rack/handler.rb +9 -26
  43. data/lib/rack/head.rb +16 -18
  44. data/lib/rack/lint.rb +110 -64
  45. data/lib/rack/lobster.rb +10 -10
  46. data/lib/rack/lock.rb +17 -11
  47. data/lib/rack/logger.rb +4 -2
  48. data/lib/rack/media_type.rb +43 -0
  49. data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
  50. data/lib/rack/mime.rb +27 -6
  51. data/lib/rack/mock.rb +124 -65
  52. data/lib/rack/multipart/generator.rb +20 -16
  53. data/lib/rack/multipart/parser.rb +273 -162
  54. data/lib/rack/multipart/uploaded_file.rb +15 -8
  55. data/lib/rack/multipart.rb +39 -8
  56. data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
  57. data/lib/rack/query_parser.rb +217 -0
  58. data/lib/rack/recursive.rb +11 -9
  59. data/lib/rack/reloader.rb +8 -4
  60. data/lib/rack/request.rb +553 -305
  61. data/lib/rack/response.rb +244 -88
  62. data/lib/rack/rewindable_input.rb +5 -15
  63. data/lib/rack/runtime.rb +12 -18
  64. data/lib/rack/sendfile.rb +17 -15
  65. data/lib/rack/server.rb +125 -47
  66. data/lib/rack/session/abstract/id.rb +217 -93
  67. data/lib/rack/session/cookie.rb +46 -31
  68. data/lib/rack/session/memcache.rb +4 -87
  69. data/lib/rack/session/pool.rb +26 -17
  70. data/lib/rack/show_exceptions.rb +390 -0
  71. data/lib/rack/{showstatus.rb → show_status.rb} +12 -12
  72. data/lib/rack/static.rb +48 -11
  73. data/lib/rack/tempfile_reaper.rb +3 -3
  74. data/lib/rack/urlmap.rb +26 -19
  75. data/lib/rack/utils.rb +212 -294
  76. data/lib/rack/version.rb +29 -0
  77. data/lib/rack.rb +76 -33
  78. data/rack.gemspec +43 -30
  79. metadata +65 -186
  80. data/HISTORY.md +0 -375
  81. data/KNOWN-ISSUES +0 -44
  82. data/lib/rack/backports/uri/common_18.rb +0 -56
  83. data/lib/rack/backports/uri/common_192.rb +0 -52
  84. data/lib/rack/backports/uri/common_193.rb +0 -29
  85. data/lib/rack/commonlogger.rb +0 -72
  86. data/lib/rack/handler/evented_mongrel.rb +0 -8
  87. data/lib/rack/handler/mongrel.rb +0 -106
  88. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  89. data/lib/rack/showexceptions.rb +0 -387
  90. data/lib/rack/utils/okjson.rb +0 -600
  91. data/test/builder/anything.rb +0 -5
  92. data/test/builder/comment.ru +0 -4
  93. data/test/builder/end.ru +0 -5
  94. data/test/builder/line.ru +0 -1
  95. data/test/builder/options.ru +0 -2
  96. data/test/cgi/assets/folder/test.js +0 -1
  97. data/test/cgi/assets/fonts/font.eot +0 -1
  98. data/test/cgi/assets/images/image.png +0 -1
  99. data/test/cgi/assets/index.html +0 -1
  100. data/test/cgi/assets/javascripts/app.js +0 -1
  101. data/test/cgi/assets/stylesheets/app.css +0 -1
  102. data/test/cgi/lighttpd.conf +0 -26
  103. data/test/cgi/rackup_stub.rb +0 -6
  104. data/test/cgi/sample_rackup.ru +0 -5
  105. data/test/cgi/test +0 -9
  106. data/test/cgi/test+directory/test+file +0 -1
  107. data/test/cgi/test.fcgi +0 -8
  108. data/test/cgi/test.ru +0 -5
  109. data/test/gemloader.rb +0 -10
  110. data/test/multipart/bad_robots +0 -259
  111. data/test/multipart/binary +0 -0
  112. data/test/multipart/content_type_and_no_filename +0 -6
  113. data/test/multipart/empty +0 -10
  114. data/test/multipart/fail_16384_nofile +0 -814
  115. data/test/multipart/file1.txt +0 -1
  116. data/test/multipart/filename_and_modification_param +0 -7
  117. data/test/multipart/filename_and_no_name +0 -6
  118. data/test/multipart/filename_with_escaped_quotes +0 -6
  119. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  120. data/test/multipart/filename_with_null_byte +0 -7
  121. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  122. data/test/multipart/filename_with_unescaped_percentages +0 -6
  123. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  124. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  125. data/test/multipart/filename_with_unescaped_quotes +0 -6
  126. data/test/multipart/ie +0 -6
  127. data/test/multipart/invalid_character +0 -6
  128. data/test/multipart/mixed_files +0 -21
  129. data/test/multipart/nested +0 -10
  130. data/test/multipart/none +0 -9
  131. data/test/multipart/semicolon +0 -6
  132. data/test/multipart/text +0 -15
  133. data/test/multipart/three_files_three_fields +0 -31
  134. data/test/multipart/webkit +0 -32
  135. data/test/rackup/config.ru +0 -31
  136. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  137. data/test/spec_auth_basic.rb +0 -81
  138. data/test/spec_auth_digest.rb +0 -259
  139. data/test/spec_body_proxy.rb +0 -85
  140. data/test/spec_builder.rb +0 -223
  141. data/test/spec_cascade.rb +0 -61
  142. data/test/spec_cgi.rb +0 -102
  143. data/test/spec_chunked.rb +0 -101
  144. data/test/spec_commonlogger.rb +0 -93
  145. data/test/spec_conditionalget.rb +0 -102
  146. data/test/spec_config.rb +0 -22
  147. data/test/spec_content_length.rb +0 -85
  148. data/test/spec_content_type.rb +0 -45
  149. data/test/spec_deflater.rb +0 -339
  150. data/test/spec_directory.rb +0 -88
  151. data/test/spec_etag.rb +0 -107
  152. data/test/spec_fastcgi.rb +0 -107
  153. data/test/spec_file.rb +0 -221
  154. data/test/spec_handler.rb +0 -72
  155. data/test/spec_head.rb +0 -45
  156. data/test/spec_lint.rb +0 -550
  157. data/test/spec_lobster.rb +0 -58
  158. data/test/spec_lock.rb +0 -164
  159. data/test/spec_logger.rb +0 -23
  160. data/test/spec_methodoverride.rb +0 -111
  161. data/test/spec_mime.rb +0 -51
  162. data/test/spec_mock.rb +0 -297
  163. data/test/spec_mongrel.rb +0 -182
  164. data/test/spec_multipart.rb +0 -600
  165. data/test/spec_nulllogger.rb +0 -20
  166. data/test/spec_recursive.rb +0 -72
  167. data/test/spec_request.rb +0 -1232
  168. data/test/spec_response.rb +0 -407
  169. data/test/spec_rewindable_input.rb +0 -118
  170. data/test/spec_runtime.rb +0 -49
  171. data/test/spec_sendfile.rb +0 -130
  172. data/test/spec_server.rb +0 -167
  173. data/test/spec_session_abstract_id.rb +0 -53
  174. data/test/spec_session_cookie.rb +0 -410
  175. data/test/spec_session_memcache.rb +0 -321
  176. data/test/spec_session_pool.rb +0 -209
  177. data/test/spec_showexceptions.rb +0 -98
  178. data/test/spec_showstatus.rb +0 -103
  179. data/test/spec_static.rb +0 -145
  180. data/test/spec_tempfile_reaper.rb +0 -63
  181. data/test/spec_thin.rb +0 -91
  182. data/test/spec_urlmap.rb +0 -236
  183. data/test/spec_utils.rb +0 -647
  184. data/test/spec_version.rb +0 -17
  185. data/test/spec_webrick.rb +0 -184
  186. data/test/static/another/index.html +0 -1
  187. data/test/static/index.html +0 -1
  188. data/test/testrequest.rb +0 -78
  189. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  190. 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
@@ -15,8 +16,8 @@ module Rack
15
16
 
16
17
  class LintError < RuntimeError; end
17
18
  module Assertion
18
- def assert(message, &block)
19
- unless block.call
19
+ def assert(message)
20
+ unless yield
20
21
  raise LintError, message
21
22
  end
22
23
  end
@@ -33,7 +34,7 @@ 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
 
@@ -42,33 +43,47 @@ module Rack
42
43
  assert("No env given") { env }
43
44
  check_env env
44
45
 
45
- env['rack.input'] = InputWrapper.new(env['rack.input'])
46
- env['rack.errors'] = ErrorWrapper.new(env['rack.errors'])
46
+ env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
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
+ assert("response #{ary.inspect} is not an Array , but #{ary.class}") {
52
+ ary.kind_of? Array
53
+ }
54
+ assert("response array #{ary.inspect} has #{ary.size} elements instead of 3") {
55
+ ary.size == 3
56
+ }
57
+
58
+ status, headers, @body = ary
50
59
  ## The *status*,
51
60
  check_status status
52
61
  ## the *headers*,
53
62
  check_headers headers
54
63
 
55
- check_hijack_response headers, env
64
+ hijack_proc = check_hijack_response headers, env
65
+ if hijack_proc && headers.is_a?(Hash)
66
+ headers[RACK_HIJACK] = hijack_proc
67
+ end
56
68
 
57
69
  ## and the *body*.
58
70
  check_content_type status, headers
59
71
  check_content_length status, headers
60
- @head_request = env[REQUEST_METHOD] == "HEAD"
72
+ @head_request = env[REQUEST_METHOD] == HEAD
61
73
  [status, headers, self]
62
74
  end
63
75
 
64
76
  ## == The Environment
65
77
  def check_env(env)
66
- ## The environment must be an instance of Hash that includes
78
+ ## The environment must be an unfrozen instance of Hash that includes
67
79
  ## CGI-like headers. The application is free to modify the
68
80
  ## environment.
69
81
  assert("env #{env.inspect} is not a Hash, but #{env.class}") {
70
82
  env.kind_of? Hash
71
83
  }
84
+ assert("env should not be frozen, but is") {
85
+ !env.frozen?
86
+ }
72
87
 
73
88
  ##
74
89
  ## The environment is required to include these variables
@@ -95,24 +110,26 @@ module Rack
95
110
  ## empty string, if the request URL targets
96
111
  ## the application root and does not have a
97
112
  ## trailing slash. This value may be
98
- ## percent-encoded when I originating from
113
+ ## percent-encoded when originating from
99
114
  ## a URL.
100
115
 
101
116
  ## <tt>QUERY_STRING</tt>:: The portion of the request URL that
102
117
  ## follows the <tt>?</tt>, if any. May be
103
118
  ## empty, but is always required!
104
119
 
105
- ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
106
- ## When combined with <tt>SCRIPT_NAME</tt> and
120
+ ## <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
107
121
  ## <tt>PATH_INFO</tt>, these variables can be
108
122
  ## used to complete the URL. Note, however,
109
123
  ## that <tt>HTTP_HOST</tt>, if present,
110
124
  ## should be used in preference to
111
125
  ## <tt>SERVER_NAME</tt> for reconstructing
112
126
  ## 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.
127
+ ## <tt>SERVER_NAME</tt> can never be an empty
128
+ ## string, and so is always required.
129
+
130
+ ## <tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
131
+ ## server is running on. Should be specified if
132
+ ## the server is running on a non-standard port.
116
133
 
117
134
  ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
118
135
  ## client-supplied HTTP request
@@ -123,9 +140,8 @@ module Rack
123
140
  ## the presence or absence of the
124
141
  ## appropriate HTTP header in the
125
142
  ## 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.
143
+ ## {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18]
144
+ ## for specific behavior.
129
145
 
130
146
  ## In addition to this, the Rack environment must include these
131
147
  ## Rack-specific variables:
@@ -177,7 +193,7 @@ module Rack
177
193
  ## <tt>rack.session</tt>:: A hash like interface for storing
178
194
  ## request session data.
179
195
  ## The store must implement:
180
- if session = env['rack.session']
196
+ if session = env[RACK_SESSION]
181
197
  ## store(key, value) (aliased as []=);
182
198
  assert("session #{session.inspect} must respond to store and []=") {
183
199
  session.respond_to?(:store) && session.respond_to?(:[]=)
@@ -197,11 +213,16 @@ module Rack
197
213
  assert("session #{session.inspect} must respond to clear") {
198
214
  session.respond_to?(:clear)
199
215
  }
216
+
217
+ ## to_hash (returning unfrozen Hash instance);
218
+ assert("session #{session.inspect} must respond to to_hash and return unfrozen Hash instance") {
219
+ session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
220
+ }
200
221
  end
201
222
 
202
223
  ## <tt>rack.logger</tt>:: A common object interface for logging messages.
203
224
  ## The object must implement:
204
- if logger = env['rack.logger']
225
+ if logger = env[RACK_LOGGER]
205
226
  ## info(message, &block)
206
227
  assert("logger #{logger.inspect} must respond to info") {
207
228
  logger.respond_to?(:info)
@@ -229,16 +250,16 @@ module Rack
229
250
  end
230
251
 
231
252
  ## <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
- if bufsize = env['rack.multipart.buffer_size']
253
+ if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
233
254
  assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
234
255
  bufsize.is_a?(Integer) && bufsize > 0
235
256
  }
236
257
  end
237
258
 
238
259
  ## <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
- if tempfile_factory = env['rack.multipart.tempfile_factory']
260
+ if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
240
261
  assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
241
- env['rack.multipart.tempfile_factory'] = lambda do |filename, content_type|
262
+ env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
242
263
  io = tempfile_factory.call(filename, content_type)
243
264
  assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
244
265
  io
@@ -252,64 +273,85 @@ module Rack
252
273
  ## accepted specifications and must not be used otherwise.
253
274
  ##
254
275
 
255
- %w[REQUEST_METHOD SERVER_NAME SERVER_PORT
256
- QUERY_STRING
276
+ %w[REQUEST_METHOD SERVER_NAME QUERY_STRING
257
277
  rack.version rack.input rack.errors
258
278
  rack.multithread rack.multiprocess rack.run_once].each { |header|
259
279
  assert("env missing required key #{header}") { env.include? header }
260
280
  }
261
281
 
282
+ ## The <tt>SERVER_PORT</tt> must be an Integer if set.
283
+ assert("env[SERVER_PORT] is not an Integer") do
284
+ server_port = env["SERVER_PORT"]
285
+ server_port.nil? || (Integer(server_port) rescue false)
286
+ end
287
+
288
+ ## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
289
+ assert("#{env[SERVER_NAME]} must be a valid authority") do
290
+ URI.parse("http://#{env[SERVER_NAME]}/") rescue false
291
+ end
292
+
293
+ ## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
294
+ assert("#{env[HTTP_HOST]} must be a valid authority") do
295
+ URI.parse("http://#{env[HTTP_HOST]}/") rescue false
296
+ end
297
+
262
298
  ## The environment must not contain the keys
263
299
  ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
264
300
  ## (use the versions without <tt>HTTP_</tt>).
265
301
  %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
266
- assert("env contains #{header}, must use #{header[5,-1]}") {
302
+ assert("env contains #{header}, must use #{header[5, -1]}") {
267
303
  not env.include? header
268
304
  }
269
305
  }
270
306
 
271
307
  ## The CGI keys (named without a period) must have String values.
308
+ ## If the string values for CGI keys contain non-ASCII characters,
309
+ ## they should use ASCII-8BIT encoding.
272
310
  env.each { |key, value|
273
311
  next if key.include? "." # Skip extensions
274
312
  assert("env variable #{key} has non-string value #{value.inspect}") {
275
313
  value.kind_of? String
276
314
  }
315
+ next if value.encoding == Encoding::ASCII_8BIT
316
+ assert("env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}") {
317
+ value.b !~ /[\x80-\xff]/n
318
+ }
277
319
  }
278
320
 
279
321
  ## There are the following restrictions:
280
322
 
281
323
  ## * <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
324
+ assert("rack.version must be an Array, was #{env[RACK_VERSION].class}") {
325
+ env[RACK_VERSION].kind_of? Array
284
326
  }
285
327
  ## * <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"]
328
+ assert("rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}") {
329
+ %w[http https].include?(env[RACK_URL_SCHEME])
288
330
  }
289
331
 
290
332
  ## * There must be a valid input stream in <tt>rack.input</tt>.
291
- check_input env["rack.input"]
333
+ check_input env[RACK_INPUT]
292
334
  ## * There must be a valid error stream in <tt>rack.errors</tt>.
293
- check_error env["rack.errors"]
335
+ check_error env[RACK_ERRORS]
294
336
  ## * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
295
337
  check_hijack env
296
338
 
297
339
  ## * The <tt>REQUEST_METHOD</tt> must be a valid token.
298
340
  assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
299
- env["REQUEST_METHOD"] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
341
+ env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
300
342
  }
301
343
 
302
344
  ## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
303
345
  assert("SCRIPT_NAME must start with /") {
304
- !env.include?("SCRIPT_NAME") ||
305
- env["SCRIPT_NAME"] == "" ||
306
- env["SCRIPT_NAME"] =~ /\A\//
346
+ !env.include?(SCRIPT_NAME) ||
347
+ env[SCRIPT_NAME] == "" ||
348
+ env[SCRIPT_NAME] =~ /\A\//
307
349
  }
308
350
  ## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
309
351
  assert("PATH_INFO must start with /") {
310
- !env.include?("PATH_INFO") ||
311
- env["PATH_INFO"] == "" ||
312
- env["PATH_INFO"] =~ /\A\//
352
+ !env.include?(PATH_INFO) ||
353
+ env[PATH_INFO] == "" ||
354
+ env[PATH_INFO] =~ /\A\//
313
355
  }
314
356
  ## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
315
357
  assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
@@ -320,11 +362,11 @@ module Rack
320
362
  ## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
321
363
  ## <tt>SCRIPT_NAME</tt> is empty.
322
364
  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"]
365
+ env[SCRIPT_NAME] || env[PATH_INFO]
324
366
  }
325
367
  ## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
326
368
  assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
327
- env["SCRIPT_NAME"] != "/"
369
+ env[SCRIPT_NAME] != "/"
328
370
  }
329
371
  end
330
372
 
@@ -336,7 +378,7 @@ module Rack
336
378
  ## When applicable, its external encoding must be "ASCII-8BIT" and it
337
379
  ## must be opened in binary mode, for Ruby 1.9 compatibility.
338
380
  assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
339
- input.external_encoding.name == "ASCII-8BIT"
381
+ input.external_encoding == Encoding::ASCII_8BIT
340
382
  } if input.respond_to?(:external_encoding)
341
383
  assert("rack.input #{input} is not opened in binary mode") {
342
384
  input.binmode?
@@ -518,11 +560,11 @@ module Rack
518
560
  #
519
561
  ## ==== Request (before status)
520
562
  def check_hijack(env)
521
- if env['rack.hijack?']
563
+ if env[RACK_IS_HIJACK]
522
564
  ## If rack.hijack? is true then rack.hijack must respond to #call.
523
- original_hijack = env['rack.hijack']
565
+ original_hijack = env[RACK_HIJACK]
524
566
  assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
525
- env['rack.hijack'] = proc do
567
+ env[RACK_HIJACK] = proc do
526
568
  ## rack.hijack must return the io that will also be assigned (or is
527
569
  ## already present, in rack.hijack_io.
528
570
  io = original_hijack.call
@@ -548,16 +590,16 @@ module Rack
548
590
  ## hijack_io to provide additional features to users. The purpose of
549
591
  ## rack.hijack is for Rack to "get out of the way", as such, Rack only
550
592
  ## provides the minimum of specification and support.
551
- env['rack.hijack_io'] = HijackWrapper.new(env['rack.hijack_io'])
593
+ env[RACK_HIJACK_IO] = HijackWrapper.new(env[RACK_HIJACK_IO])
552
594
  io
553
595
  end
554
596
  else
555
597
  ##
556
598
  ## 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? }
599
+ assert("rack.hijack? is false, but rack.hijack is present") { env[RACK_HIJACK].nil? }
558
600
  ##
559
601
  ## 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? }
602
+ assert("rack.hijack? is false, but rack.hijack_io is present") { env[RACK_HIJACK_IO].nil? }
561
603
  end
562
604
  end
563
605
 
@@ -568,7 +610,7 @@ module Rack
568
610
 
569
611
  # this check uses headers like a hash, but the spec only requires
570
612
  # headers respond to #each
571
- headers = Rack::Utils::HeaderHash.new(headers)
613
+ headers = Rack::Utils::HeaderHash[headers]
572
614
 
573
615
  ## In order to do this, an application may set the special header
574
616
  ## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
@@ -587,12 +629,12 @@ module Rack
587
629
  ## Servers must ignore the <tt>body</tt> part of the response tuple when
588
630
  ## the <tt>rack.hijack</tt> response API is in use.
589
631
 
590
- if env['rack.hijack?'] && headers['rack.hijack']
632
+ if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
591
633
  assert('rack.hijack header must respond to #call') {
592
- headers['rack.hijack'].respond_to? :call
634
+ headers[RACK_HIJACK].respond_to? :call
593
635
  }
594
- original_hijack = headers['rack.hijack']
595
- headers['rack.hijack'] = proc do |io|
636
+ original_hijack = headers[RACK_HIJACK]
637
+ proc do |io|
596
638
  original_hijack.call HijackWrapper.new(io)
597
639
  end
598
640
  else
@@ -600,8 +642,10 @@ module Rack
600
642
  ## The special response header <tt>rack.hijack</tt> must only be set
601
643
  ## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
602
644
  assert('rack.hijack header must not be present if server does not support hijacking') {
603
- headers['rack.hijack'].nil?
645
+ headers[RACK_HIJACK].nil?
604
646
  }
647
+
648
+ nil
605
649
  end
606
650
  end
607
651
  ## ==== Conventions
@@ -626,15 +670,17 @@ module Rack
626
670
  assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
627
671
  header.respond_to? :each
628
672
  }
629
- header.each { |key, value|
630
- ## Special headers starting "rack." are for communicating with the
631
- ## server, and must not be sent back to the client.
632
- next if key =~ /^rack\..+$/
633
673
 
674
+ header.each { |key, value|
634
675
  ## The header keys must be Strings.
635
676
  assert("header key must be a string, was #{key.class}") {
636
677
  key.kind_of? String
637
678
  }
679
+
680
+ ## Special headers starting "rack." are for communicating with the
681
+ ## server, and must not be sent back to the client.
682
+ next if key =~ /^rack\..+$/
683
+
638
684
  ## The header must not contain a +Status+ key.
639
685
  assert("header must not contain Status") { key.downcase != "status" }
640
686
  ## The header must conform to RFC7230 token specification, i.e. cannot
@@ -659,10 +705,10 @@ module Rack
659
705
  def check_content_type(status, headers)
660
706
  headers.each { |key, value|
661
707
  ## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
662
- ## 204, 205 or 304.
708
+ ## 204 or 304.
663
709
  if key.downcase == "content-type"
664
710
  assert("Content-Type header found in #{status} response, not allowed") {
665
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
711
+ not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
666
712
  }
667
713
  return
668
714
  end
@@ -674,9 +720,9 @@ module Rack
674
720
  headers.each { |key, value|
675
721
  if key.downcase == 'content-length'
676
722
  ## There must not be a <tt>Content-Length</tt> header when the
677
- ## +Status+ is 1xx, 204, 205 or 304.
723
+ ## +Status+ is 1xx, 204 or 304.
678
724
  assert("Content-Length header found in #{status} response, not allowed") {
679
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
725
+ not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
680
726
  }
681
727
  @content_length = value
682
728
  end
@@ -710,7 +756,7 @@ module Rack
710
756
  assert("Body yielded non-string value #{part.inspect}") {
711
757
  part.kind_of? String
712
758
  }
713
- bytes += Rack::Utils.bytesize(part)
759
+ bytes += part.bytesize
714
760
  yield part
715
761
  }
716
762
  verify_content_length(bytes)
data/lib/rack/lobster.rb CHANGED
@@ -1,7 +1,6 @@
1
- require 'zlib'
1
+ # frozen_string_literal: true
2
2
 
3
- require 'rack/request'
4
- require 'rack/response'
3
+ require 'zlib'
5
4
 
6
5
  module Rack
7
6
  # Paste has a Pony, Rack has a Lobster!
@@ -25,8 +24,8 @@ module Rack
25
24
  content = ["<title>Lobstericious!</title>",
26
25
  "<pre>", lobster, "</pre>",
27
26
  "<a href='#{href}'>flip!</a>"]
28
- length = content.inject(0) { |a,e| a+e.size }.to_s
29
- [200, {CONTENT_TYPE => "text/html", CONTENT_LENGTH => length}, content]
27
+ length = content.inject(0) { |a, e| a + e.size }.to_s
28
+ [200, { CONTENT_TYPE => "text/html", CONTENT_LENGTH => length }, content]
30
29
  }
31
30
 
32
31
  def call(env)
@@ -37,8 +36,8 @@ module Rack
37
36
  gsub('\\', 'TEMP').
38
37
  gsub('/', '\\').
39
38
  gsub('TEMP', '/').
40
- gsub('{','}').
41
- gsub('(',')')
39
+ gsub('{', '}').
40
+ gsub('(', ')')
42
41
  end.join("\n")
43
42
  href = "?flip=right"
44
43
  elsif req.GET["flip"] == "crash"
@@ -62,9 +61,10 @@ module Rack
62
61
  end
63
62
 
64
63
  if $0 == __FILE__
65
- require 'rack'
66
- require 'rack/showexceptions'
64
+ # :nocov:
65
+ require_relative '../rack'
67
66
  Rack::Server.start(
68
- :app => Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), :Port => 9292
67
+ app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
69
68
  )
69
+ # :nocov:
70
70
  end
data/lib/rack/lock.rb CHANGED
@@ -1,26 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
- require 'rack/body_proxy'
3
4
 
4
5
  module Rack
5
6
  # Rack::Lock locks every request inside a mutex, so that every request
6
7
  # will effectively be executed synchronously.
7
8
  class Lock
8
- FLAG = 'rack.multithread'.freeze
9
-
10
9
  def initialize(app, mutex = Mutex.new)
11
10
  @app, @mutex = app, mutex
12
11
  end
13
12
 
14
13
  def call(env)
15
- old, env[FLAG] = env[FLAG], false
16
14
  @mutex.lock
17
- response = @app.call(env)
18
- body = BodyProxy.new(response[2]) { @mutex.unlock }
19
- response[2] = body
20
- response
21
- ensure
22
- @mutex.unlock unless body
23
- env[FLAG] = old
15
+ @env = env
16
+ @old_rack_multithread = env[RACK_MULTITHREAD]
17
+ begin
18
+ response = @app.call(env.merge!(RACK_MULTITHREAD => false))
19
+ returned = response << BodyProxy.new(response.pop) { unlock }
20
+ ensure
21
+ unlock unless returned
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def unlock
28
+ @mutex.unlock
29
+ @env[RACK_MULTITHREAD] = @old_rack_multithread
24
30
  end
25
31
  end
26
32
  end
data/lib/rack/logger.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
 
3
5
  module Rack
@@ -8,10 +10,10 @@ module Rack
8
10
  end
9
11
 
10
12
  def call(env)
11
- logger = ::Logger.new(env['rack.errors'])
13
+ logger = ::Logger.new(env[RACK_ERRORS])
12
14
  logger.level = @level
13
15
 
14
- env['rack.logger'] = logger
16
+ env[RACK_LOGGER] = logger
15
17
  @app.call(env)
16
18
  end
17
19
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Rack::MediaType parse media type and parameters out of content_type string
5
+
6
+ class MediaType
7
+ SPLIT_PATTERN = %r{\s*[;,]\s*}
8
+
9
+ class << self
10
+ # The media type (type/subtype) portion of the CONTENT_TYPE header
11
+ # without any media type parameters. e.g., when CONTENT_TYPE is
12
+ # "text/plain;charset=utf-8", the media-type is "text/plain".
13
+ #
14
+ # For more information on the use of media types in HTTP, see:
15
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
16
+ def type(content_type)
17
+ return nil unless content_type
18
+ content_type.split(SPLIT_PATTERN, 2).first.tap &:downcase!
19
+ end
20
+
21
+ # The media type parameters provided in CONTENT_TYPE as a Hash, or
22
+ # an empty Hash if no CONTENT_TYPE or media-type parameters were
23
+ # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
24
+ # this method responds with the following Hash:
25
+ # { 'charset' => 'utf-8' }
26
+ def params(content_type)
27
+ return {} if content_type.nil?
28
+
29
+ content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
30
+ k, v = s.split('=', 2)
31
+
32
+ hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def strip_doublequotes(str)
39
+ (str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class MethodOverride
3
- HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK)
5
+ HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK]
4
6
 
5
- METHOD_OVERRIDE_PARAM_KEY = "_method".freeze
6
- HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze
7
- ALLOWED_METHODS = ["POST"]
7
+ METHOD_OVERRIDE_PARAM_KEY = "_method"
8
+ HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE"
9
+ ALLOWED_METHODS = %w[POST]
8
10
 
9
11
  def initialize(app)
10
12
  @app = app
@@ -14,7 +16,7 @@ module Rack
14
16
  if allowed_methods.include?(env[REQUEST_METHOD])
15
17
  method = method_override(env)
16
18
  if HTTP_METHODS.include?(method)
17
- env["rack.methodoverride.original_method"] = env[REQUEST_METHOD]
19
+ env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] = env[REQUEST_METHOD]
18
20
  env[REQUEST_METHOD] = method
19
21
  end
20
22
  end
@@ -29,7 +31,7 @@ module Rack
29
31
  begin
30
32
  method.to_s.upcase
31
33
  rescue ArgumentError
32
- env["rack.errors"].puts "Invalid string for method"
34
+ env[RACK_ERRORS].puts "Invalid string for method"
33
35
  end
34
36
  end
35
37
 
@@ -42,9 +44,9 @@ module Rack
42
44
  def method_override_param(req)
43
45
  req.POST[METHOD_OVERRIDE_PARAM_KEY]
44
46
  rescue Utils::InvalidParameterError, Utils::ParameterTypeError
45
- req.env["rack.errors"].puts "Invalid or incomplete POST params"
47
+ req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params"
46
48
  rescue EOFError
47
- req.env["rack.errors"].puts "Bad request content body"
49
+ req.get_header(RACK_ERRORS).puts "Bad request content body"
48
50
  end
49
51
  end
50
52
  end