rack 1.1.6 → 1.6.9

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