rack 1.4.7 → 2.1.4

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 (183) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +77 -0
  3. data/{COPYING → MIT-LICENSE} +4 -2
  4. data/README.rdoc +122 -456
  5. data/Rakefile +32 -31
  6. data/SPEC +119 -29
  7. data/bin/rackup +1 -0
  8. data/contrib/rack_logo.svg +164 -111
  9. data/example/lobster.ru +2 -0
  10. data/example/protectedlobster.rb +4 -2
  11. data/example/protectedlobster.ru +3 -1
  12. data/lib/rack/auth/abstract/handler.rb +7 -5
  13. data/lib/rack/auth/abstract/request.rb +8 -6
  14. data/lib/rack/auth/basic.rb +5 -2
  15. data/lib/rack/auth/digest/md5.rb +10 -8
  16. data/lib/rack/auth/digest/nonce.rb +6 -3
  17. data/lib/rack/auth/digest/params.rb +5 -4
  18. data/lib/rack/auth/digest/request.rb +4 -2
  19. data/lib/rack/body_proxy.rb +11 -9
  20. data/lib/rack/builder.rb +63 -20
  21. data/lib/rack/cascade.rb +10 -9
  22. data/lib/rack/chunked.rb +45 -11
  23. data/lib/rack/{commonlogger.rb → common_logger.rb} +24 -15
  24. data/lib/rack/{conditionalget.rb → conditional_get.rb} +20 -6
  25. data/lib/rack/config.rb +7 -0
  26. data/lib/rack/content_length.rb +12 -6
  27. data/lib/rack/content_type.rb +4 -2
  28. data/lib/rack/core_ext/regexp.rb +14 -0
  29. data/lib/rack/deflater.rb +73 -42
  30. data/lib/rack/directory.rb +77 -56
  31. data/lib/rack/etag.rb +25 -13
  32. data/lib/rack/events.rb +156 -0
  33. data/lib/rack/file.rb +4 -143
  34. data/lib/rack/files.rb +178 -0
  35. data/lib/rack/handler/cgi.rb +18 -17
  36. data/lib/rack/handler/fastcgi.rb +21 -17
  37. data/lib/rack/handler/lsws.rb +14 -12
  38. data/lib/rack/handler/scgi.rb +27 -21
  39. data/lib/rack/handler/thin.rb +19 -5
  40. data/lib/rack/handler/webrick.rb +66 -24
  41. data/lib/rack/handler.rb +29 -19
  42. data/lib/rack/head.rb +21 -14
  43. data/lib/rack/lint.rb +259 -65
  44. data/lib/rack/lobster.rb +17 -10
  45. data/lib/rack/lock.rb +19 -10
  46. data/lib/rack/logger.rb +4 -2
  47. data/lib/rack/media_type.rb +43 -0
  48. data/lib/rack/method_override.rb +52 -0
  49. data/lib/rack/mime.rb +43 -6
  50. data/lib/rack/mock.rb +109 -44
  51. data/lib/rack/multipart/generator.rb +11 -12
  52. data/lib/rack/multipart/parser.rb +302 -115
  53. data/lib/rack/multipart/uploaded_file.rb +4 -3
  54. data/lib/rack/multipart.rb +40 -9
  55. data/lib/rack/null_logger.rb +39 -0
  56. data/lib/rack/query_parser.rb +218 -0
  57. data/lib/rack/recursive.rb +14 -11
  58. data/lib/rack/reloader.rb +12 -5
  59. data/lib/rack/request.rb +484 -270
  60. data/lib/rack/response.rb +196 -77
  61. data/lib/rack/rewindable_input.rb +5 -14
  62. data/lib/rack/runtime.rb +13 -6
  63. data/lib/rack/sendfile.rb +44 -20
  64. data/lib/rack/server.rb +175 -61
  65. data/lib/rack/session/abstract/id.rb +276 -133
  66. data/lib/rack/session/cookie.rb +75 -40
  67. data/lib/rack/session/memcache.rb +4 -87
  68. data/lib/rack/session/pool.rb +24 -18
  69. data/lib/rack/show_exceptions.rb +392 -0
  70. data/lib/rack/{showstatus.rb → show_status.rb} +11 -9
  71. data/lib/rack/static.rb +65 -38
  72. data/lib/rack/tempfile_reaper.rb +24 -0
  73. data/lib/rack/urlmap.rb +40 -15
  74. data/lib/rack/utils.rb +316 -285
  75. data/lib/rack.rb +78 -23
  76. data/rack.gemspec +26 -19
  77. metadata +44 -209
  78. data/KNOWN-ISSUES +0 -30
  79. data/lib/rack/backports/uri/common_18.rb +0 -56
  80. data/lib/rack/backports/uri/common_192.rb +0 -52
  81. data/lib/rack/backports/uri/common_193.rb +0 -29
  82. data/lib/rack/handler/evented_mongrel.rb +0 -8
  83. data/lib/rack/handler/mongrel.rb +0 -100
  84. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  85. data/lib/rack/methodoverride.rb +0 -33
  86. data/lib/rack/nulllogger.rb +0 -18
  87. data/lib/rack/showexceptions.rb +0 -378
  88. data/test/builder/anything.rb +0 -5
  89. data/test/builder/comment.ru +0 -4
  90. data/test/builder/end.ru +0 -5
  91. data/test/builder/line.ru +0 -1
  92. data/test/builder/options.ru +0 -2
  93. data/test/cgi/assets/folder/test.js +0 -1
  94. data/test/cgi/assets/fonts/font.eot +0 -1
  95. data/test/cgi/assets/images/image.png +0 -1
  96. data/test/cgi/assets/index.html +0 -1
  97. data/test/cgi/assets/javascripts/app.js +0 -1
  98. data/test/cgi/assets/stylesheets/app.css +0 -1
  99. data/test/cgi/lighttpd.conf +0 -26
  100. data/test/cgi/lighttpd.errors +0 -1
  101. data/test/cgi/rackup_stub.rb +0 -6
  102. data/test/cgi/sample_rackup.ru +0 -5
  103. data/test/cgi/test +0 -9
  104. data/test/cgi/test+directory/test+file +0 -1
  105. data/test/cgi/test.fcgi +0 -8
  106. data/test/cgi/test.ru +0 -5
  107. data/test/gemloader.rb +0 -10
  108. data/test/multipart/bad_robots +0 -259
  109. data/test/multipart/binary +0 -0
  110. data/test/multipart/content_type_and_no_filename +0 -6
  111. data/test/multipart/empty +0 -10
  112. data/test/multipart/fail_16384_nofile +0 -814
  113. data/test/multipart/file1.txt +0 -1
  114. data/test/multipart/filename_and_modification_param +0 -7
  115. data/test/multipart/filename_with_escaped_quotes +0 -6
  116. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  117. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  118. data/test/multipart/filename_with_unescaped_percentages +0 -6
  119. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  120. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  121. data/test/multipart/filename_with_unescaped_quotes +0 -6
  122. data/test/multipart/ie +0 -6
  123. data/test/multipart/mixed_files +0 -21
  124. data/test/multipart/nested +0 -10
  125. data/test/multipart/none +0 -9
  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/webkit +0 -32
  130. data/test/rackup/config.ru +0 -31
  131. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  132. data/test/spec_auth.rb +0 -57
  133. data/test/spec_auth_basic.rb +0 -81
  134. data/test/spec_auth_digest.rb +0 -259
  135. data/test/spec_body_proxy.rb +0 -69
  136. data/test/spec_builder.rb +0 -207
  137. data/test/spec_cascade.rb +0 -61
  138. data/test/spec_cgi.rb +0 -102
  139. data/test/spec_chunked.rb +0 -87
  140. data/test/spec_commonlogger.rb +0 -57
  141. data/test/spec_conditionalget.rb +0 -102
  142. data/test/spec_config.rb +0 -22
  143. data/test/spec_content_length.rb +0 -86
  144. data/test/spec_content_type.rb +0 -45
  145. data/test/spec_deflater.rb +0 -187
  146. data/test/spec_directory.rb +0 -88
  147. data/test/spec_etag.rb +0 -98
  148. data/test/spec_fastcgi.rb +0 -107
  149. data/test/spec_file.rb +0 -200
  150. data/test/spec_handler.rb +0 -59
  151. data/test/spec_head.rb +0 -48
  152. data/test/spec_lint.rb +0 -515
  153. data/test/spec_lobster.rb +0 -58
  154. data/test/spec_lock.rb +0 -167
  155. data/test/spec_logger.rb +0 -23
  156. data/test/spec_methodoverride.rb +0 -72
  157. data/test/spec_mock.rb +0 -269
  158. data/test/spec_mongrel.rb +0 -182
  159. data/test/spec_multipart.rb +0 -479
  160. data/test/spec_nulllogger.rb +0 -23
  161. data/test/spec_recursive.rb +0 -72
  162. data/test/spec_request.rb +0 -955
  163. data/test/spec_response.rb +0 -313
  164. data/test/spec_rewindable_input.rb +0 -118
  165. data/test/spec_runtime.rb +0 -49
  166. data/test/spec_sendfile.rb +0 -90
  167. data/test/spec_server.rb +0 -121
  168. data/test/spec_session_abstract_id.rb +0 -43
  169. data/test/spec_session_cookie.rb +0 -361
  170. data/test/spec_session_memcache.rb +0 -321
  171. data/test/spec_session_pool.rb +0 -209
  172. data/test/spec_showexceptions.rb +0 -92
  173. data/test/spec_showstatus.rb +0 -84
  174. data/test/spec_static.rb +0 -145
  175. data/test/spec_thin.rb +0 -86
  176. data/test/spec_urlmap.rb +0 -213
  177. data/test/spec_utils.rb +0 -554
  178. data/test/spec_webrick.rb +0 -143
  179. data/test/static/another/index.html +0 -1
  180. data/test/static/index.html +0 -1
  181. data/test/testrequest.rb +0 -78
  182. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  183. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/lint.rb CHANGED
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
4
+ require 'forwardable'
2
5
 
3
6
  module Rack
4
7
  # Rack::Lint validates your application and the requests and
@@ -14,8 +17,8 @@ module Rack
14
17
 
15
18
  class LintError < RuntimeError; end
16
19
  module Assertion
17
- def assert(message, &block)
18
- unless block.call
20
+ def assert(message)
21
+ unless yield
19
22
  raise LintError, message
20
23
  end
21
24
  end
@@ -32,7 +35,7 @@ module Rack
32
35
 
33
36
  ## A Rack application is a Ruby object (not a class) that
34
37
  ## responds to +call+.
35
- def call(env=nil)
38
+ def call(env = nil)
36
39
  dup._call(env)
37
40
  end
38
41
 
@@ -41,8 +44,8 @@ module Rack
41
44
  assert("No env given") { env }
42
45
  check_env env
43
46
 
44
- env['rack.input'] = InputWrapper.new(env['rack.input'])
45
- env['rack.errors'] = ErrorWrapper.new(env['rack.errors'])
47
+ env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
48
+ env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
46
49
 
47
50
  ## and returns an Array of exactly three values:
48
51
  status, headers, @body = @app.call(env)
@@ -50,10 +53,13 @@ module Rack
50
53
  check_status status
51
54
  ## the *headers*,
52
55
  check_headers headers
56
+
57
+ check_hijack_response headers, env
58
+
53
59
  ## and the *body*.
54
60
  check_content_type status, headers
55
61
  check_content_length status, headers
56
- @head_request = env["REQUEST_METHOD"] == "HEAD"
62
+ @head_request = env[REQUEST_METHOD] == HEAD
57
63
  [status, headers, self]
58
64
  end
59
65
 
@@ -91,14 +97,24 @@ module Rack
91
97
  ## empty string, if the request URL targets
92
98
  ## the application root and does not have a
93
99
  ## trailing slash. This value may be
94
- ## percent-encoded when I originating from
100
+ ## percent-encoded when originating from
95
101
  ## a URL.
96
102
 
97
103
  ## <tt>QUERY_STRING</tt>:: The portion of the request URL that
98
104
  ## follows the <tt>?</tt>, if any. May be
99
105
  ## empty, but is always required!
100
106
 
101
- ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>:: When combined with <tt>SCRIPT_NAME</tt> and <tt>PATH_INFO</tt>, these variables can be used to complete the URL. Note, however, that <tt>HTTP_HOST</tt>, if present, should be used in preference to <tt>SERVER_NAME</tt> for reconstructing the request URL. <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt> can never be empty strings, and so are always required.
107
+ ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
108
+ ## When combined with <tt>SCRIPT_NAME</tt> and
109
+ ## <tt>PATH_INFO</tt>, these variables can be
110
+ ## used to complete the URL. Note, however,
111
+ ## that <tt>HTTP_HOST</tt>, if present,
112
+ ## should be used in preference to
113
+ ## <tt>SERVER_NAME</tt> for reconstructing
114
+ ## the request URL.
115
+ ## <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
116
+ ## can never be empty strings, and so
117
+ ## are always required.
102
118
 
103
119
  ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
104
120
  ## client-supplied HTTP request
@@ -108,27 +124,61 @@ module Rack
108
124
  ## variables should correspond with
109
125
  ## the presence or absence of the
110
126
  ## appropriate HTTP header in the
111
- ## request.
127
+ ## request. See
128
+ ## {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18]
129
+ ## for specific behavior.
112
130
 
113
131
  ## In addition to this, the Rack environment must include these
114
132
  ## Rack-specific variables:
115
133
 
116
- ## <tt>rack.version</tt>:: The Array [1,1], representing this version of Rack.
117
- ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
134
+ ## <tt>rack.version</tt>:: The Array representing this version of Rack
135
+ ## See Rack::VERSION, that corresponds to
136
+ ## the version of this SPEC.
137
+
138
+ ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
139
+ ## request URL.
140
+
118
141
  ## <tt>rack.input</tt>:: See below, the input stream.
142
+
119
143
  ## <tt>rack.errors</tt>:: See below, the error stream.
120
- ## <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
121
- ## <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
122
- ## <tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
123
- ##
144
+
145
+ ## <tt>rack.multithread</tt>:: true if the application object may be
146
+ ## simultaneously invoked by another thread
147
+ ## in the same process, false otherwise.
148
+
149
+ ## <tt>rack.multiprocess</tt>:: true if an equivalent application object
150
+ ## may be simultaneously invoked by another
151
+ ## process, false otherwise.
152
+
153
+ ## <tt>rack.run_once</tt>:: true if the server expects
154
+ ## (but does not guarantee!) that the
155
+ ## application will only be invoked this one
156
+ ## time during the life of its containing
157
+ ## process. Normally, this will only be true
158
+ ## for a server based on CGI
159
+ ## (or something similar).
160
+
161
+ ## <tt>rack.hijack?</tt>:: present and true if the server supports
162
+ ## connection hijacking. See below, hijacking.
163
+
164
+ ## <tt>rack.hijack</tt>:: an object responding to #call that must be
165
+ ## called at least once before using
166
+ ## rack.hijack_io.
167
+ ## It is recommended #call return rack.hijack_io
168
+ ## as well as setting it in env if necessary.
169
+
170
+ ## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
171
+ ## has received #call, this will contain
172
+ ## an object resembling an IO. See hijacking.
124
173
 
125
174
  ## Additional environment specifications have approved to
126
175
  ## standardized middleware APIs. None of these are required to
127
176
  ## be implemented by the server.
128
177
 
129
- ## <tt>rack.session</tt>:: A hash like interface for storing request session data.
178
+ ## <tt>rack.session</tt>:: A hash like interface for storing
179
+ ## request session data.
130
180
  ## The store must implement:
131
- if session = env['rack.session']
181
+ if session = env[RACK_SESSION]
132
182
  ## store(key, value) (aliased as []=);
133
183
  assert("session #{session.inspect} must respond to store and []=") {
134
184
  session.respond_to?(:store) && session.respond_to?(:[]=)
@@ -152,7 +202,7 @@ module Rack
152
202
 
153
203
  ## <tt>rack.logger</tt>:: A common object interface for logging messages.
154
204
  ## The object must implement:
155
- if logger = env['rack.logger']
205
+ if logger = env[RACK_LOGGER]
156
206
  ## info(message, &block)
157
207
  assert("logger #{logger.inspect} must respond to info") {
158
208
  logger.respond_to?(:info)
@@ -179,6 +229,23 @@ module Rack
179
229
  }
180
230
  end
181
231
 
232
+ ## <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
+ if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
234
+ assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
235
+ bufsize.is_a?(Integer) && bufsize > 0
236
+ }
237
+ end
238
+
239
+ ## <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
+ if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
241
+ assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
242
+ env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
243
+ io = tempfile_factory.call(filename, content_type)
244
+ assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
245
+ io
246
+ end
247
+ end
248
+
182
249
  ## The server or the application can store their own data in the
183
250
  ## environment, too. The keys must contain at least one dot,
184
251
  ## and should be prefixed uniquely. The prefix <tt>rack.</tt>
@@ -197,7 +264,7 @@ module Rack
197
264
  ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
198
265
  ## (use the versions without <tt>HTTP_</tt>).
199
266
  %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
200
- assert("env contains #{header}, must use #{header[5,-1]}") {
267
+ assert("env contains #{header}, must use #{header[5, -1]}") {
201
268
  not env.include? header
202
269
  }
203
270
  }
@@ -210,39 +277,40 @@ module Rack
210
277
  }
211
278
  }
212
279
 
213
- ##
214
280
  ## There are the following restrictions:
215
281
 
216
282
  ## * <tt>rack.version</tt> must be an array of Integers.
217
- assert("rack.version must be an Array, was #{env["rack.version"].class}") {
218
- env["rack.version"].kind_of? Array
283
+ assert("rack.version must be an Array, was #{env[RACK_VERSION].class}") {
284
+ env[RACK_VERSION].kind_of? Array
219
285
  }
220
286
  ## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
221
- assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") {
222
- %w[http https].include? env["rack.url_scheme"]
287
+ assert("rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}") {
288
+ %w[http https].include?(env[RACK_URL_SCHEME])
223
289
  }
224
290
 
225
291
  ## * There must be a valid input stream in <tt>rack.input</tt>.
226
- check_input env["rack.input"]
292
+ check_input env[RACK_INPUT]
227
293
  ## * There must be a valid error stream in <tt>rack.errors</tt>.
228
- check_error env["rack.errors"]
294
+ check_error env[RACK_ERRORS]
295
+ ## * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
296
+ check_hijack env
229
297
 
230
298
  ## * The <tt>REQUEST_METHOD</tt> must be a valid token.
231
- assert("REQUEST_METHOD unknown: #{env["REQUEST_METHOD"]}") {
232
- env["REQUEST_METHOD"] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
299
+ assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
300
+ env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
233
301
  }
234
302
 
235
303
  ## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
236
304
  assert("SCRIPT_NAME must start with /") {
237
- !env.include?("SCRIPT_NAME") ||
238
- env["SCRIPT_NAME"] == "" ||
239
- env["SCRIPT_NAME"] =~ /\A\//
305
+ !env.include?(SCRIPT_NAME) ||
306
+ env[SCRIPT_NAME] == "" ||
307
+ env[SCRIPT_NAME] =~ /\A\//
240
308
  }
241
309
  ## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
242
310
  assert("PATH_INFO must start with /") {
243
- !env.include?("PATH_INFO") ||
244
- env["PATH_INFO"] == "" ||
245
- env["PATH_INFO"] =~ /\A\//
311
+ !env.include?(PATH_INFO) ||
312
+ env[PATH_INFO] == "" ||
313
+ env[PATH_INFO] =~ /\A\//
246
314
  }
247
315
  ## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
248
316
  assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
@@ -253,11 +321,11 @@ module Rack
253
321
  ## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
254
322
  ## <tt>SCRIPT_NAME</tt> is empty.
255
323
  assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
256
- env["SCRIPT_NAME"] || env["PATH_INFO"]
324
+ env[SCRIPT_NAME] || env[PATH_INFO]
257
325
  }
258
326
  ## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
259
327
  assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
260
- env["SCRIPT_NAME"] != "/"
328
+ env[SCRIPT_NAME] != "/"
261
329
  }
262
330
  end
263
331
 
@@ -301,15 +369,23 @@ module Rack
301
369
  v
302
370
  end
303
371
 
304
- ## * +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
305
- ## If given, +length+ must be a non-negative Integer (>= 0) or +nil+, and +buffer+ must
306
- ## be a String and may not be nil. If +length+ is given and not nil, then this method
307
- ## reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
308
- ## then this method reads all data until EOF.
309
- ## When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
310
- ## if +length+ is not given or is nil.
311
- ## If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
312
- ## newly created String object.
372
+ ## * +read+ behaves like IO#read.
373
+ ## Its signature is <tt>read([length, [buffer]])</tt>.
374
+ ##
375
+ ## If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
376
+ ## and +buffer+ must be a String and may not be nil.
377
+ ##
378
+ ## If +length+ is given and not nil, then this method reads at most
379
+ ## +length+ bytes from the input stream.
380
+ ##
381
+ ## If +length+ is not given or nil, then this method reads
382
+ ## all data until EOF.
383
+ ##
384
+ ## When EOF is reached, this method returns nil if +length+ is given
385
+ ## and not nil, or "" if +length+ is not given or is nil.
386
+ ##
387
+ ## If +buffer+ is given, then the read data will be placed
388
+ ## into +buffer+ instead of a newly created String object.
313
389
  def read(*args)
314
390
  assert("rack.input#read called with too many arguments") {
315
391
  args.size <= 2
@@ -416,6 +492,126 @@ module Rack
416
492
  end
417
493
  end
418
494
 
495
+ class HijackWrapper
496
+ include Assertion
497
+ extend Forwardable
498
+
499
+ REQUIRED_METHODS = [
500
+ :read, :write, :read_nonblock, :write_nonblock, :flush, :close,
501
+ :close_read, :close_write, :closed?
502
+ ]
503
+
504
+ def_delegators :@io, *REQUIRED_METHODS
505
+
506
+ def initialize(io)
507
+ @io = io
508
+ REQUIRED_METHODS.each do |meth|
509
+ assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth }
510
+ end
511
+ end
512
+ end
513
+
514
+ ## === Hijacking
515
+ #
516
+ # AUTHORS: n.b. The trailing whitespace between paragraphs is important and
517
+ # should not be removed. The whitespace creates paragraphs in the RDoc
518
+ # output.
519
+ #
520
+ ## ==== Request (before status)
521
+ def check_hijack(env)
522
+ if env[RACK_IS_HIJACK]
523
+ ## If rack.hijack? is true then rack.hijack must respond to #call.
524
+ original_hijack = env[RACK_HIJACK]
525
+ assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
526
+ env[RACK_HIJACK] = proc do
527
+ ## rack.hijack must return the io that will also be assigned (or is
528
+ ## already present, in rack.hijack_io.
529
+ io = original_hijack.call
530
+ HijackWrapper.new(io)
531
+ ##
532
+ ## rack.hijack_io must respond to:
533
+ ## <tt>read, write, read_nonblock, write_nonblock, flush, close,
534
+ ## close_read, close_write, closed?</tt>
535
+ ##
536
+ ## The semantics of these IO methods must be a best effort match to
537
+ ## those of a normal ruby IO or Socket object, using standard
538
+ ## arguments and raising standard exceptions. Servers are encouraged
539
+ ## to simply pass on real IO objects, although it is recognized that
540
+ ## this approach is not directly compatible with SPDY and HTTP 2.0.
541
+ ##
542
+ ## IO provided in rack.hijack_io should preference the
543
+ ## IO::WaitReadable and IO::WaitWritable APIs wherever supported.
544
+ ##
545
+ ## There is a deliberate lack of full specification around
546
+ ## rack.hijack_io, as semantics will change from server to server.
547
+ ## Users are encouraged to utilize this API with a knowledge of their
548
+ ## server choice, and servers may extend the functionality of
549
+ ## hijack_io to provide additional features to users. The purpose of
550
+ ## rack.hijack is for Rack to "get out of the way", as such, Rack only
551
+ ## provides the minimum of specification and support.
552
+ env[RACK_HIJACK_IO] = HijackWrapper.new(env[RACK_HIJACK_IO])
553
+ io
554
+ end
555
+ else
556
+ ##
557
+ ## If rack.hijack? is false, then rack.hijack should not be set.
558
+ assert("rack.hijack? is false, but rack.hijack is present") { env[RACK_HIJACK].nil? }
559
+ ##
560
+ ## If rack.hijack? is false, then rack.hijack_io should not be set.
561
+ assert("rack.hijack? is false, but rack.hijack_io is present") { env[RACK_HIJACK_IO].nil? }
562
+ end
563
+ end
564
+
565
+ ## ==== Response (after headers)
566
+ ## It is also possible to hijack a response after the status and headers
567
+ ## have been sent.
568
+ def check_hijack_response(headers, env)
569
+
570
+ # this check uses headers like a hash, but the spec only requires
571
+ # headers respond to #each
572
+ headers = Rack::Utils::HeaderHash.new(headers)
573
+
574
+ ## In order to do this, an application may set the special header
575
+ ## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
576
+ ## accepting an argument that conforms to the <tt>rack.hijack_io</tt>
577
+ ## protocol.
578
+ ##
579
+ ## After the headers have been sent, and this hijack callback has been
580
+ ## called, the application is now responsible for the remaining lifecycle
581
+ ## of the IO. The application is also responsible for maintaining HTTP
582
+ ## semantics. Of specific note, in almost all cases in the current SPEC,
583
+ ## applications will have wanted to specify the header Connection:close in
584
+ ## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
585
+ ## returning hijacked sockets to the web server. For that purpose, use the
586
+ ## body streaming API instead (progressively yielding strings via each).
587
+ ##
588
+ ## Servers must ignore the <tt>body</tt> part of the response tuple when
589
+ ## the <tt>rack.hijack</tt> response API is in use.
590
+
591
+ if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
592
+ assert('rack.hijack header must respond to #call') {
593
+ headers[RACK_HIJACK].respond_to? :call
594
+ }
595
+ original_hijack = headers[RACK_HIJACK]
596
+ headers[RACK_HIJACK] = proc do |io|
597
+ original_hijack.call HijackWrapper.new(io)
598
+ end
599
+ else
600
+ ##
601
+ ## The special response header <tt>rack.hijack</tt> must only be set
602
+ ## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
603
+ assert('rack.hijack header must not be present if server does not support hijacking') {
604
+ headers[RACK_HIJACK].nil?
605
+ }
606
+ end
607
+ end
608
+ ## ==== Conventions
609
+ ## * Middleware should not use hijack unless it is handling the whole
610
+ ## response.
611
+ ## * Middleware may wrap the IO object for the response pattern.
612
+ ## * Middleware should not wrap the IO object for the request pattern. The
613
+ ## request pattern is intended to provide the hijacker with "raw tcp".
614
+
419
615
  ## == The Response
420
616
 
421
617
  ## === The Status
@@ -431,26 +627,28 @@ module Rack
431
627
  assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
432
628
  header.respond_to? :each
433
629
  }
630
+
434
631
  header.each { |key, value|
435
632
  ## The header keys must be Strings.
436
633
  assert("header key must be a string, was #{key.class}") {
437
634
  key.kind_of? String
438
635
  }
439
- ## The header must not contain a +Status+ key,
636
+
637
+ ## Special headers starting "rack." are for communicating with the
638
+ ## server, and must not be sent back to the client.
639
+ next if key =~ /^rack\..+$/
640
+
641
+ ## The header must not contain a +Status+ key.
440
642
  assert("header must not contain Status") { key.downcase != "status" }
441
- ## contain keys with <tt>:</tt> or newlines in their name,
442
- assert("header names must not contain : or \\n") { key !~ /[:\n]/ }
443
- ## contain keys names that end in <tt>-</tt> or <tt>_</tt>,
444
- assert("header names must not end in - or _") { key !~ /[-_]\z/ }
445
- ## but only contain keys that consist of
446
- ## letters, digits, <tt>_</tt> or <tt>-</tt> and start with a letter.
447
- assert("invalid header name: #{key}") { key =~ /\A[a-zA-Z][a-zA-Z0-9_-]*\z/ }
643
+ ## The header must conform to RFC7230 token specification, i.e. cannot
644
+ ## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
645
+ assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ }
448
646
 
449
647
  ## The values of the header must be Strings,
450
648
  assert("a header value must be a String, but the value of " +
451
649
  "'#{key}' is a #{value.class}") { value.kind_of? String }
452
650
  ## consisting of lines (for multiple header values, e.g. multiple
453
- ## <tt>Set-Cookie</tt> values) seperated by "\n".
651
+ ## <tt>Set-Cookie</tt> values) separated by "\\n".
454
652
  value.split("\n").each { |item|
455
653
  ## The lines must not contain characters below 037.
456
654
  assert("invalid header value #{key}: #{item.inspect}") {
@@ -463,19 +661,15 @@ module Rack
463
661
  ## === The Content-Type
464
662
  def check_content_type(status, headers)
465
663
  headers.each { |key, value|
466
- ## There must be a <tt>Content-Type</tt>, except when the
467
- ## +Status+ is 1xx, 204, 205 or 304, in which case there must be none
468
- ## given.
664
+ ## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
665
+ ## 204 or 304.
469
666
  if key.downcase == "content-type"
470
667
  assert("Content-Type header found in #{status} response, not allowed") {
471
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
668
+ not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
472
669
  }
473
670
  return
474
671
  end
475
672
  }
476
- assert("No Content-Type header found") {
477
- Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
478
- }
479
673
  end
480
674
 
481
675
  ## === The Content-Length
@@ -483,9 +677,9 @@ module Rack
483
677
  headers.each { |key, value|
484
678
  if key.downcase == 'content-length'
485
679
  ## There must not be a <tt>Content-Length</tt> header when the
486
- ## +Status+ is 1xx, 204, 205 or 304.
680
+ ## +Status+ is 1xx, 204 or 304.
487
681
  assert("Content-Length header found in #{status} response, not allowed") {
488
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
682
+ not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
489
683
  }
490
684
  @content_length = value
491
685
  end
@@ -519,7 +713,7 @@ module Rack
519
713
  assert("Body yielded non-string value #{part.inspect}") {
520
714
  part.kind_of? String
521
715
  }
522
- bytes += Rack::Utils.bytesize(part)
716
+ bytes += part.bytesize
523
717
  yield part
524
718
  }
525
719
  verify_content_length(bytes)
@@ -530,7 +724,7 @@ module Rack
530
724
  ##
531
725
  ## If the Body responds to +close+, it will be called after iteration. If
532
726
  ## the body is replaced by a middleware after action, the original body
533
- ## must be closed first, if it repsonds to close.
727
+ ## must be closed first, if it responds to close.
534
728
  # XXX howto: assert("Body has not been closed") { @closed }
535
729
 
536
730
 
data/lib/rack/lobster.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'zlib'
2
4
 
3
5
  require 'rack/request'
@@ -12,7 +14,7 @@ module Rack
12
14
  I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0])
13
15
 
14
16
  LambdaLobster = lambda { |env|
15
- if env["QUERY_STRING"].include?("flip")
17
+ if env[QUERY_STRING].include?("flip")
16
18
  lobster = LobsterString.split("\n").
17
19
  map { |line| line.ljust(42).reverse }.
18
20
  join("\n")
@@ -25,16 +27,21 @@ module Rack
25
27
  content = ["<title>Lobstericious!</title>",
26
28
  "<pre>", lobster, "</pre>",
27
29
  "<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]
30
+ length = content.inject(0) { |a, e| a + e.size }.to_s
31
+ [200, { CONTENT_TYPE => "text/html", CONTENT_LENGTH => length }, content]
30
32
  }
31
33
 
32
34
  def call(env)
33
35
  req = Request.new(env)
34
36
  if req.GET["flip"] == "left"
35
- lobster = LobsterString.split("\n").
36
- map { |line| line.ljust(42).reverse }.
37
- join("\n")
37
+ lobster = LobsterString.split("\n").map do |line|
38
+ line.ljust(42).reverse.
39
+ gsub('\\', 'TEMP').
40
+ gsub('/', '\\').
41
+ gsub('TEMP', '/').
42
+ gsub('{', '}').
43
+ gsub('(', ')')
44
+ end.join("\n")
38
45
  href = "?flip=right"
39
46
  elsif req.GET["flip"] == "crash"
40
47
  raise "Lobster crashed"
@@ -58,8 +65,8 @@ end
58
65
 
59
66
  if $0 == __FILE__
60
67
  require 'rack'
61
- require 'rack/showexceptions'
62
- Rack::Handler::WEBrick.run \
63
- Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)),
64
- :Port => 9292
68
+ require 'rack/show_exceptions'
69
+ Rack::Server.start(
70
+ app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
71
+ )
65
72
  end
data/lib/rack/lock.rb CHANGED
@@ -1,24 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread'
2
4
  require 'rack/body_proxy'
3
5
 
4
6
  module Rack
7
+ # Rack::Lock locks every request inside a mutex, so that every request
8
+ # will effectively be executed synchronously.
5
9
  class Lock
6
- FLAG = 'rack.multithread'.freeze
7
-
8
10
  def initialize(app, mutex = Mutex.new)
9
11
  @app, @mutex = app, mutex
10
12
  end
11
13
 
12
14
  def call(env)
13
- old, env[FLAG] = env[FLAG], false
14
15
  @mutex.lock
15
- response = @app.call(env)
16
- body = BodyProxy.new(response[2]) { @mutex.unlock }
17
- response[2] = body
18
- response
19
- ensure
20
- @mutex.unlock unless body
21
- env[FLAG] = old
16
+ @env = env
17
+ @old_rack_multithread = env[RACK_MULTITHREAD]
18
+ begin
19
+ response = @app.call(env.merge!(RACK_MULTITHREAD => false))
20
+ returned = response << BodyProxy.new(response.pop) { unlock }
21
+ ensure
22
+ unlock unless returned
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def unlock
29
+ @mutex.unlock
30
+ @env[RACK_MULTITHREAD] = @old_rack_multithread
22
31
  end
23
32
  end
24
33
  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