rack 1.4.7 → 1.5.0.beta.1

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 (60) hide show
  1. data/README.rdoc +2 -30
  2. data/Rakefile +1 -0
  3. data/SPEC +68 -4
  4. data/example/protectedlobster.rb +1 -1
  5. data/lib/rack.rb +2 -14
  6. data/lib/rack/auth/abstract/request.rb +1 -5
  7. data/lib/rack/builder.rb +8 -4
  8. data/lib/rack/cascade.rb +2 -2
  9. data/lib/rack/config.rb +5 -0
  10. data/lib/rack/deflater.rb +2 -1
  11. data/lib/rack/file.rb +25 -28
  12. data/lib/rack/handler.rb +18 -5
  13. data/lib/rack/handler/mongrel.rb +1 -1
  14. data/lib/rack/handler/scgi.rb +1 -1
  15. data/lib/rack/handler/thin.rb +6 -3
  16. data/lib/rack/handler/webrick.rb +1 -0
  17. data/lib/rack/head.rb +2 -0
  18. data/lib/rack/lint.rb +132 -7
  19. data/lib/rack/lobster.rb +3 -3
  20. data/lib/rack/lock.rb +2 -0
  21. data/lib/rack/methodoverride.rb +0 -2
  22. data/lib/rack/mime.rb +29 -0
  23. data/lib/rack/multipart/parser.rb +0 -9
  24. data/lib/rack/request.rb +66 -25
  25. data/lib/rack/response.rb +1 -2
  26. data/lib/rack/sendfile.rb +18 -4
  27. data/lib/rack/server.rb +20 -12
  28. data/lib/rack/session/abstract/id.rb +60 -59
  29. data/lib/rack/session/cookie.rb +11 -16
  30. data/lib/rack/utils.rb +97 -85
  31. data/rack.gemspec +1 -6
  32. data/test/spec_builder.rb +7 -0
  33. data/test/spec_cgi.rb +1 -1
  34. data/test/spec_chunked.rb +3 -5
  35. data/test/spec_content_length.rb +3 -6
  36. data/test/spec_deflater.rb +26 -9
  37. data/test/spec_fastcgi.rb +1 -1
  38. data/test/spec_file.rb +24 -11
  39. data/test/spec_head.rb +3 -8
  40. data/test/spec_lint.rb +6 -6
  41. data/test/spec_lock.rb +4 -7
  42. data/test/spec_methodoverride.rb +4 -1
  43. data/test/spec_mime.rb +51 -0
  44. data/test/spec_mongrel.rb +1 -1
  45. data/test/spec_multipart.rb +15 -49
  46. data/test/spec_nulllogger.rb +3 -6
  47. data/test/spec_request.rb +112 -18
  48. data/test/spec_response.rb +8 -8
  49. data/test/spec_sendfile.rb +52 -13
  50. data/test/spec_server.rb +6 -0
  51. data/test/spec_session_abstract_id.rb +11 -1
  52. data/test/spec_session_cookie.rb +140 -153
  53. data/test/spec_thin.rb +6 -1
  54. data/test/spec_utils.rb +23 -17
  55. data/test/spec_webrick.rb +1 -1
  56. metadata +37 -83
  57. checksums.yaml +0 -7
  58. data/test/cgi/lighttpd.errors +0 -1
  59. data/test/multipart/three_files_three_fields +0 -31
  60. data/test/spec_auth.rb +0 -57
@@ -29,6 +29,7 @@ These web servers include Rack handlers in their distributions:
29
29
  * Phusion Passenger (which is mod_rack for Apache and for nginx)
30
30
  * Puma
31
31
  * Rainbows!
32
+ * Reel
32
33
  * Unicorn
33
34
  * unixrack
34
35
  * uWSGI
@@ -42,6 +43,7 @@ changing anything.
42
43
  These frameworks include Rack adapters in their distributions:
43
44
  * Camping
44
45
  * Coset
46
+ * Espresso
45
47
  * Halcyon
46
48
  * Mack
47
49
  * Maveric
@@ -57,9 +59,6 @@ These frameworks include Rack adapters in their distributions:
57
59
  * Wee
58
60
  * ... and many others.
59
61
 
60
- Current links to these projects can be found at
61
- http://wiki.ramaze.net/Home#other-frameworks
62
-
63
62
  == Available middleware
64
63
 
65
64
  Between the server and the framework, Rack can be customized to your
@@ -479,38 +478,11 @@ run on port 11211) and memcache-client installed.
479
478
  * January 7th, 2013: Thirty first public release 1.4.3
480
479
  * Security: Prevent unbounded reads in large multipart boundaries
481
480
 
482
- * January 13th, 2013: Thirty second public release 1.4.4, 1.3.9, 1.2.7, 1.1.5
483
- * [SEC] Rack::Auth::AbstractRequest no longer symbolizes arbitrary strings
484
- * Fixed erroneous test case in the 1.3.x series
485
-
486
- * February 7th, Thirty fifth public release 1.1.6, 1.2.8, 1.3.10
487
- * Fix CVE-2013-0263, timing attack against Rack::Session::Cookie
488
-
489
- * February 7th, Thirty fifth public release 1.4.5
490
- * Fix CVE-2013-0263, timing attack against Rack::Session::Cookie
491
- * Fix CVE-2013-0262, symlink path traversal in Rack::File
492
-
493
- * February 7th, Thirty fifth public release 1.5.2
494
- * Fix CVE-2013-0263, timing attack against Rack::Session::Cookie
495
- * Fix CVE-2013-0262, symlink path traversal in Rack::File
496
- * Add various methods to Session for enhanced Rails compatibility
497
- * Request#trusted_proxy? now only matches whole stirngs
498
- * Add JSON cookie coder, to be default in Rack 1.6+ due to security concerns
499
- * URLMap host matching in environments that don't set the Host header fixed
500
- * Fix a race condition that could result in overwritten pidfiles
501
- * Various documentation additions
502
-
503
481
  == Contact
504
482
 
505
483
  Please post bugs, suggestions and patches to
506
484
  the bug tracker at <http://github.com/rack/rack/issues>.
507
485
 
508
- Please post security related bugs and suggestions to the core team at
509
- <https://groups.google.com/group/rack-core> or rack-core@googlegroups.com. Due
510
- to wide usage of the library, it is strongly preferred that we manage timing in
511
- order to provide viable patches at the time of disclosure. Your assistance in
512
- this matter is greatly appreciated.
513
-
514
486
  Mailing list archives are available at
515
487
  <http://groups.google.com/group/rack-devel>.
516
488
 
data/Rakefile CHANGED
@@ -6,6 +6,7 @@ task :default => [:test]
6
6
  desc "Install gem dependencies"
7
7
  task :deps do
8
8
  require 'rubygems'
9
+ require 'rbconfig'
9
10
  spec = Gem::Specification.load('rack.gemspec')
10
11
  spec.dependencies.each do |dep|
11
12
  reqs = dep.requirements_list
data/SPEC CHANGED
@@ -49,7 +49,8 @@ below.
49
49
  variables should correspond with
50
50
  the presence or absence of the
51
51
  appropriate HTTP header in the
52
- request.
52
+ request. See <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
53
+ RFC3875 section 4.1.18</a> for specific behavior.
53
54
  In addition to this, the Rack environment must include these
54
55
  Rack-specific variables:
55
56
  <tt>rack.version</tt>:: The Array [1,1], representing this version of Rack.
@@ -59,6 +60,9 @@ Rack-specific variables:
59
60
  <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
60
61
  <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
61
62
  <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).
63
+ <tt>rack.hijack?</tt>:: present and true if the server supports connection hijacking. See below, hijacking.
64
+ <tt>rack.hijack</tt>:: an object responding to #call that must be called at least once before using rack.hijack_io. It is recommended #call return rack.hijack_io as well as setting it in env if necessary.
65
+ <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack has received #call, this will contain an object resembling an IO. See hijacking.
62
66
  Additional environment specifications have approved to
63
67
  standardized middleware APIs. None of these are required to
64
68
  be implemented by the server.
@@ -89,6 +93,7 @@ There are the following restrictions:
89
93
  * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
90
94
  * There must be a valid input stream in <tt>rack.input</tt>.
91
95
  * There must be a valid error stream in <tt>rack.errors</tt>.
96
+ * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
92
97
  * The <tt>REQUEST_METHOD</tt> must be a valid token.
93
98
  * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
94
99
  * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
@@ -128,12 +133,72 @@ The error stream must respond to +puts+, +write+ and +flush+.
128
133
  * +flush+ must be called without arguments and must be called
129
134
  in order to make the error appear for sure.
130
135
  * +close+ must never be called on the error stream.
136
+ === Hijacking
137
+ ==== Request (before status)
138
+ If rack.hijack? is true then rack.hijack must respond to #call.
139
+ rack.hijack must return the io that will also be assigned (or is
140
+ already present, in rack.hijack_io.
141
+
142
+ rack.hijack_io must respond to:
143
+ <tt>read, write, read_nonblock, write_nonblock, flush, close,
144
+ close_read, close_write, closed?</tt>
145
+
146
+ The semantics of these IO methods must be a best effort match to
147
+ those of a normal ruby IO or Socket object, using standard
148
+ arguments and raising standard exceptions. Servers are encouraged
149
+ to simply pass on real IO objects, although it is recognized that
150
+ this approach is not directly compatible with SPDY and HTTP 2.0.
151
+
152
+ IO provided in rack.hijack_io should preference the
153
+ IO::WaitReadable and IO::WaitWritable APIs wherever supported.
154
+
155
+ There is a deliberate lack of full specification around
156
+ rack.hijack_io, as semantics will change from server to server.
157
+ Users are encouraged to utilize this API with a knowledge of their
158
+ server choice, and servers may extend the functionality of
159
+ hijack_io to provide additional features to users. The purpose of
160
+ rack.hijack is for Rack to "get out of the way", as such, Rack only
161
+ provides the minimum of specification and support.
162
+
163
+ If rack.hijack? is false, then rack.hijack should not be set.
164
+
165
+ If rack.hijack? is false, then rack.hijack_io should not be set.
166
+ ==== Response (after headers)
167
+ It is also possible to hijack a response after the status and headers
168
+ have been sent.
169
+ In order to do this, an application may set the special header
170
+ <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
171
+ accepting an argument that conforms to the <tt>rack.hijack_io</tt>
172
+ protocol.
173
+
174
+ After the headers have been sent, and this hijack callback has been
175
+ called, the application is now responsible for the remaining lifecycle
176
+ of the IO. The application is also responsible for maintaining HTTP
177
+ semantics. Of specific note, in almost all cases in the current SPEC,
178
+ applications will have wanted to specify the header Connection:close in
179
+ HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
180
+ returning hijacked sockets to the web server. For that purpose, use the
181
+ body streaming API instead (progressively yielding strings via each).
182
+
183
+ Servers must ignore the <tt>body</tt> part of the response tuple when
184
+ the <tt>rack.hijack</tt> response API is in use.
185
+
186
+ The special response header <tt>rack.hijack</tt> must only be set
187
+ if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
188
+ ==== Conventions
189
+ * Middleware should not use hijack unless it is handling the whole
190
+ response.
191
+ * Middleware may wrap the IO object for the response pattern.
192
+ * Middleware should not wrap the IO object for the request pattern. The
193
+ request pattern is intended to provide the hijacker with "raw tcp".
131
194
  == The Response
132
195
  === The Status
133
196
  This is an HTTP status. When parsed as integer (+to_i+), it must be
134
197
  greater than or equal to 100.
135
198
  === The Headers
136
199
  The header must respond to +each+, and yield values of key and value.
200
+ Special headers starting "rack." are for communicating with the
201
+ server, and must not be sent back to the client.
137
202
  The header keys must be Strings.
138
203
  The header must not contain a +Status+ key,
139
204
  contain keys with <tt>:</tt> or newlines in their name,
@@ -145,9 +210,8 @@ consisting of lines (for multiple header values, e.g. multiple
145
210
  <tt>Set-Cookie</tt> values) seperated by "\n".
146
211
  The lines must not contain characters below 037.
147
212
  === The Content-Type
148
- There must be a <tt>Content-Type</tt>, except when the
149
- +Status+ is 1xx, 204, 205 or 304, in which case there must be none
150
- given.
213
+ There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
214
+ 204, 205 or 304.
151
215
  === The Content-Length
152
216
  There must not be a <tt>Content-Length</tt> header when the
153
217
  +Status+ is 1xx, 204, 205 or 304.
@@ -11,4 +11,4 @@ protected_lobster.realm = 'Lobster 2.0'
11
11
 
12
12
  pretty_protected_lobster = Rack::ShowStatus.new(Rack::ShowExceptions.new(protected_lobster))
13
13
 
14
- Rack::Handler::WEBrick.run pretty_protected_lobster, :Port => 9292
14
+ Rack::Server.start :app => pretty_protected_lobster, :Port => 9292
@@ -11,7 +11,7 @@
11
11
 
12
12
  module Rack
13
13
  # The Rack protocol version number implemented.
14
- VERSION = [1,1]
14
+ VERSION = [1,2]
15
15
 
16
16
  # Return the Rack protocol version as a dotted string.
17
17
  def self.version
@@ -20,7 +20,7 @@ module Rack
20
20
 
21
21
  # Return the Rack release as a dotted string.
22
22
  def self.release
23
- "1.4.7"
23
+ "1.5"
24
24
  end
25
25
 
26
26
  autoload :Builder, "rack/builder"
@@ -73,18 +73,6 @@ module Rack
73
73
  autoload :Params, "rack/auth/digest/params"
74
74
  autoload :Request, "rack/auth/digest/request"
75
75
  end
76
-
77
- # Not all of the following schemes are "standards", but they are used often.
78
- @schemes = %w[basic digest bearer mac token oauth oauth2]
79
-
80
- def self.add_scheme scheme
81
- @schemes << scheme
82
- @schemes.uniq!
83
- end
84
-
85
- def self.schemes
86
- @schemes.dup
87
- end
88
76
  end
89
77
 
90
78
  module Session
@@ -21,11 +21,7 @@ module Rack
21
21
  end
22
22
 
23
23
  def scheme
24
- @scheme ||=
25
- begin
26
- s = parts.first.downcase
27
- Rack::Auth.schemes.include?(s) ? s.to_sym : s
28
- end
24
+ @scheme ||= parts.first.downcase.to_sym
29
25
  end
30
26
 
31
27
  def params
@@ -25,7 +25,7 @@ module Rack
25
25
  #
26
26
  # run app
27
27
  #
28
- # +use+ adds a middleware to the stack, +run+ dispatches to an application.
28
+ # +use+ adds middleware to the stack, +run+ dispatches to an application.
29
29
  # You can use +map+ to construct a Rack::URLMap in a convenient way.
30
30
 
31
31
  class Builder
@@ -37,8 +37,7 @@ module Rack
37
37
  options = opts.parse! $1.split(/\s+/)
38
38
  end
39
39
  cfgfile.sub!(/^__END__\n.*\Z/m, '')
40
- app = eval "Rack::Builder.new {\n" + cfgfile + "\n}.to_app",
41
- TOPLEVEL_BINDING, config, 0
40
+ app = new_from_string cfgfile, config
42
41
  else
43
42
  require config
44
43
  app = Object.const_get(::File.basename(config, '.rb').capitalize)
@@ -46,6 +45,11 @@ module Rack
46
45
  return app, options
47
46
  end
48
47
 
48
+ def self.new_from_string(builder_script, file="(rackup)")
49
+ eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
50
+ TOPLEVEL_BINDING, file, 0
51
+ end
52
+
49
53
  def initialize(default_app = nil,&block)
50
54
  @use, @map, @run = [], nil, default_app
51
55
  instance_eval(&block) if block_given?
@@ -55,7 +59,7 @@ module Rack
55
59
  self.new(default_app, &block).to_app
56
60
  end
57
61
 
58
- # Specifies a middleware to use in a stack.
62
+ # Specifies middleware to use in a stack.
59
63
  #
60
64
  # class Middleware
61
65
  # def initialize(app)
@@ -1,6 +1,6 @@
1
1
  module Rack
2
- # Rack::Cascade tries an request on several apps, and returns the
3
- # first response that is not 404 (or in a list of configurable
2
+ # Rack::Cascade tries a request on several apps, and returns the
3
+ # first response that is not 404 or 405 (or in a list of configurable
4
4
  # status codes).
5
5
 
6
6
  class Cascade
@@ -1,6 +1,11 @@
1
1
  module Rack
2
2
  # Rack::Config modifies the environment using the block given during
3
3
  # initialization.
4
+ #
5
+ # Example:
6
+ # use Rack::Config do |env|
7
+ # env['my-key'] = 'some-value'
8
+ # end
4
9
  class Config
5
10
  def initialize(app, &block)
6
11
  @app = app
@@ -16,7 +16,8 @@ module Rack
16
16
  # Skip compressing empty entity body responses and responses with
17
17
  # no-transform set.
18
18
  if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) ||
19
- headers['Cache-Control'].to_s =~ /\bno-transform\b/
19
+ headers['Cache-Control'].to_s =~ /\bno-transform\b/ ||
20
+ (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/)
20
21
  return [status, headers, body]
21
22
  end
22
23
 
@@ -21,16 +21,10 @@ module Rack
21
21
 
22
22
  alias :to_path :path
23
23
 
24
- def initialize(root, headers={})
24
+ def initialize(root, headers={}, default_mime = 'text/plain')
25
25
  @root = root
26
- # Allow a cache_control string for backwards compatibility
27
- if headers.instance_of? String
28
- warn \
29
- "Rack::File headers parameter replaces cache_control after Rack 1.5."
30
- @headers = { 'Cache-Control' => headers }
31
- else
32
- @headers = headers
33
- end
26
+ @headers = headers
27
+ @default_mime = default_mime
34
28
  end
35
29
 
36
30
  def call(env)
@@ -44,17 +38,22 @@ module Rack
44
38
  return fail(405, "Method Not Allowed")
45
39
  end
46
40
 
47
- @path_info = Utils.unescape(env["PATH_INFO"])
48
- parts = @path_info.split SEPS
49
-
50
- clean = []
51
-
52
- parts.each do |part|
53
- next if part.empty? || part == '.'
54
- part == '..' ? clean.pop : clean << part
41
+ path_info = Utils.unescape(env["PATH_INFO"])
42
+ parts = path_info.split SEPS
43
+
44
+ parts.inject(0) do |depth, part|
45
+ case part
46
+ when '', '.'
47
+ depth
48
+ when '..'
49
+ return fail(404, "Not Found") if depth - 1 < 0
50
+ depth - 1
51
+ else
52
+ depth + 1
53
+ end
55
54
  end
56
55
 
57
- @path = F.join(@root, *clean)
56
+ @path = F.join(@root, *parts)
58
57
 
59
58
  available = begin
60
59
  F.file?(@path) && F.readable?(@path)
@@ -65,24 +64,22 @@ module Rack
65
64
  if available
66
65
  serving(env)
67
66
  else
68
- fail(404, "File not found: #{@path_info}")
67
+ fail(404, "File not found: #{path_info}")
69
68
  end
70
69
  end
71
70
 
72
71
  def serving(env)
73
72
  last_modified = F.mtime(@path).httpdate
74
73
  return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified
75
- response = [
76
- 200,
77
- {
78
- "Last-Modified" => last_modified,
79
- "Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain')
80
- },
81
- env["REQUEST_METHOD"] == "HEAD" ? [] : self
82
- ]
74
+
75
+ headers = { "Last-Modified" => last_modified }
76
+ mime = Mime.mime_type(F.extname(@path), @default_mime)
77
+ headers["Content-Type"] = mime if mime
83
78
 
84
79
  # Set custom headers
85
- @headers.each { |field, content| response[1][field] = content } if @headers
80
+ @headers.each { |field, content| headers[field] = content } if @headers
81
+
82
+ response = [ 200, headers, env["REQUEST_METHOD"] == "HEAD" ? [] : self ]
86
83
 
87
84
  # NOTE:
88
85
  # We check via File::size? whether this file provides size info
@@ -26,6 +26,23 @@ module Rack
26
26
  raise load_error || name_error
27
27
  end
28
28
 
29
+ # Select first available Rack handler given an `Array` of server names.
30
+ # Raises `LoadError` if no handler was found.
31
+ #
32
+ # > pick ['thin', 'webrick']
33
+ # => Rack::Handler::WEBrick
34
+ def self.pick(server_names)
35
+ server_names = Array(server_names)
36
+ server_names.each do |server_name|
37
+ begin
38
+ return get(server_name.to_s)
39
+ rescue LoadError, NameError
40
+ end
41
+ end
42
+
43
+ raise LoadError, "Couldn't find handler for: #{server_names.join(', ')}."
44
+ end
45
+
29
46
  def self.default(options = {})
30
47
  # Guess.
31
48
  if ENV.include?("PHP_FCGI_CHILDREN")
@@ -37,11 +54,7 @@ module Rack
37
54
  elsif ENV.include?("REQUEST_METHOD")
38
55
  Rack::Handler::CGI
39
56
  else
40
- begin
41
- Rack::Handler::Thin
42
- rescue LoadError
43
- Rack::Handler::WEBrick
44
- end
57
+ pick ['thin', 'puma', 'webrick']
45
58
  end
46
59
  end
47
60
 
@@ -53,7 +53,7 @@ module Rack
53
53
  end
54
54
 
55
55
  def process(request, response)
56
- env = {}.replace(request.params)
56
+ env = Hash[request.params]
57
57
  env.delete "HTTP_CONTENT_TYPE"
58
58
  env.delete "HTTP_CONTENT_LENGTH"
59
59
 
@@ -29,7 +29,7 @@ module Rack
29
29
  end
30
30
 
31
31
  def process_request(request, input_body, socket)
32
- env = {}.replace(request)
32
+ env = Hash[request]
33
33
  env.delete "HTTP_CONTENT_TYPE"
34
34
  env.delete "HTTP_CONTENT_LENGTH"
35
35
  env["REQUEST_PATH"], env["QUERY_STRING"] = env["REQUEST_URI"].split('?', 2)