rack 1.3.10 → 1.4.0

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 (83) hide show
  1. data/COPYING +1 -1
  2. data/KNOWN-ISSUES +0 -9
  3. data/README.rdoc +4 -118
  4. data/Rakefile +15 -0
  5. data/SPEC +3 -5
  6. data/lib/rack.rb +0 -12
  7. data/lib/rack/auth/abstract/request.rb +1 -5
  8. data/lib/rack/auth/basic.rb +1 -1
  9. data/lib/rack/auth/digest/nonce.rb +1 -1
  10. data/lib/rack/backports/uri/common_18.rb +28 -14
  11. data/lib/rack/backports/uri/common_192.rb +17 -14
  12. data/lib/rack/body_proxy.rb +0 -10
  13. data/lib/rack/builder.rb +26 -18
  14. data/lib/rack/cascade.rb +1 -12
  15. data/lib/rack/chunked.rb +2 -0
  16. data/lib/rack/content_type.rb +7 -1
  17. data/lib/rack/deflater.rb +1 -5
  18. data/lib/rack/directory.rb +5 -1
  19. data/lib/rack/file.rb +26 -9
  20. data/lib/rack/handler.rb +2 -2
  21. data/lib/rack/head.rb +0 -1
  22. data/lib/rack/lint.rb +3 -5
  23. data/lib/rack/methodoverride.rb +10 -4
  24. data/lib/rack/mime.rb +606 -171
  25. data/lib/rack/mock.rb +2 -1
  26. data/lib/rack/multipart.rb +2 -2
  27. data/lib/rack/multipart/parser.rb +3 -10
  28. data/lib/rack/reloader.rb +1 -1
  29. data/lib/rack/request.rb +45 -13
  30. data/lib/rack/response.rb +15 -14
  31. data/lib/rack/sendfile.rb +8 -6
  32. data/lib/rack/server.rb +4 -30
  33. data/lib/rack/session/abstract/id.rb +25 -6
  34. data/lib/rack/session/cookie.rb +12 -16
  35. data/lib/rack/static.rb +21 -8
  36. data/lib/rack/urlmap.rb +28 -13
  37. data/lib/rack/utils.rb +22 -28
  38. data/rack.gemspec +5 -5
  39. data/test/builder/end.ru +2 -0
  40. data/test/cgi/lighttpd.conf +1 -0
  41. data/test/cgi/sample_rackup.ru +1 -1
  42. data/test/cgi/test+directory/test+file +1 -0
  43. data/test/cgi/test.ru +1 -1
  44. data/test/gemloader.rb +6 -2
  45. data/test/spec_auth_basic.rb +4 -9
  46. data/test/spec_auth_digest.rb +3 -16
  47. data/test/spec_body_proxy.rb +0 -4
  48. data/test/spec_builder.rb +63 -20
  49. data/test/spec_cascade.rb +10 -13
  50. data/test/spec_cgi.rb +1 -1
  51. data/test/spec_chunked.rb +39 -12
  52. data/test/spec_commonlogger.rb +4 -3
  53. data/test/spec_conditionalget.rb +16 -12
  54. data/test/spec_content_length.rb +1 -1
  55. data/test/spec_content_type.rb +6 -0
  56. data/test/spec_deflater.rb +2 -2
  57. data/test/spec_directory.rb +12 -0
  58. data/test/spec_fastcgi.rb +1 -1
  59. data/test/spec_file.rb +58 -8
  60. data/test/spec_head.rb +6 -18
  61. data/test/spec_lint.rb +2 -2
  62. data/test/spec_methodoverride.rb +15 -0
  63. data/test/spec_mock.rb +6 -2
  64. data/test/spec_mongrel.rb +8 -8
  65. data/test/spec_multipart.rb +10 -63
  66. data/test/spec_request.rb +94 -21
  67. data/test/spec_response.rb +22 -24
  68. data/test/spec_sendfile.rb +3 -0
  69. data/test/spec_server.rb +2 -49
  70. data/test/spec_session_cookie.rb +58 -22
  71. data/test/spec_session_memcache.rb +31 -1
  72. data/test/spec_session_pool.rb +10 -4
  73. data/test/spec_static.rb +8 -0
  74. data/test/spec_thin.rb +2 -2
  75. data/test/spec_utils.rb +38 -35
  76. data/test/spec_webrick.rb +5 -3
  77. data/test/static/index.html +1 -0
  78. metadata +13 -18
  79. data/contrib/rack.png +0 -0
  80. data/contrib/rack.svg +0 -150
  81. data/lib/rack/backports/uri/common_193.rb +0 -29
  82. data/test/builder/line.ru +0 -1
  83. data/test/spec_auth.rb +0 -57
@@ -1,9 +1,14 @@
1
1
  require 'rack/cascade'
2
2
  require 'rack/file'
3
+ require 'rack/lint'
3
4
  require 'rack/urlmap'
4
5
  require 'rack/mock'
5
6
 
6
7
  describe Rack::Cascade do
8
+ def cascade(*args)
9
+ Rack::Lint.new Rack::Cascade.new(*args)
10
+ end
11
+
7
12
  docroot = File.expand_path(File.dirname(__FILE__))
8
13
  app1 = Rack::File.new(docroot)
9
14
 
@@ -13,20 +18,20 @@ describe Rack::Cascade do
13
18
  [200, { "Content-Type" => "text/plain"}, [""]]})
14
19
 
15
20
  should "dispatch onward on 404 by default" do
16
- cascade = Rack::Cascade.new([app1, app2, app3])
21
+ cascade = cascade([app1, app2, app3])
17
22
  Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok
18
23
  Rack::MockRequest.new(cascade).get("/foo").should.be.ok
19
24
  Rack::MockRequest.new(cascade).get("/toobad").should.be.not_found
20
- Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.forbidden
25
+ Rack::MockRequest.new(cascade).get("/cgi/../..").should.be.forbidden
21
26
  end
22
27
 
23
28
  should "dispatch onward on whatever is passed" do
24
- cascade = Rack::Cascade.new([app1, app2, app3], [404, 403])
29
+ cascade = cascade([app1, app2, app3], [404, 403])
25
30
  Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found
26
31
  end
27
32
 
28
33
  should "return 404 if empty" do
29
- Rack::MockRequest.new(Rack::Cascade.new([])).get('/').should.be.not_found
34
+ Rack::MockRequest.new(cascade([])).get('/').should.be.not_found
30
35
  end
31
36
 
32
37
  should "append new app" do
@@ -37,17 +42,9 @@ describe Rack::Cascade do
37
42
  Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found
38
43
  cascade << app1
39
44
  Rack::MockRequest.new(cascade).get('/cgi/test').should.be.ok
40
- Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.forbidden
45
+ Rack::MockRequest.new(cascade).get('/cgi/../..').should.be.forbidden
41
46
  Rack::MockRequest.new(cascade).get('/foo').should.be.not_found
42
47
  cascade << app3
43
48
  Rack::MockRequest.new(cascade).get('/foo').should.be.ok
44
49
  end
45
-
46
- should "close the body on cascade" do
47
- body = StringIO.new
48
- closer = lambda { |env| [404, {}, body] }
49
- cascade = Rack::Cascade.new([closer, app3], [404])
50
- Rack::MockRequest.new(cascade).get("/foo").should.be.ok
51
- body.should.be.closed
52
- end
53
50
  end
@@ -5,7 +5,7 @@ require 'rack/handler/cgi'
5
5
  describe Rack::Handler::CGI do
6
6
  extend TestRequest::Helpers
7
7
 
8
- @host = '0.0.0.0'
8
+ @host = '127.0.0.1'
9
9
  @port = 9203
10
10
 
11
11
  if `which lighttpd` && !$?.success?
@@ -1,31 +1,56 @@
1
1
  require 'rack/chunked'
2
+ require 'rack/lint'
2
3
  require 'rack/mock'
3
4
 
4
5
  describe Rack::Chunked do
6
+ Enumerator = ::Enumerable::Enumerator unless defined?(Enumerator)
7
+
8
+ def chunked(app)
9
+ proc do |env|
10
+ app = Rack::Chunked.new(app)
11
+ Rack::Lint.new(app).call(env).tap do |response|
12
+ # we want to use body like an array, but it only has #each
13
+ response[2] = Enumerator.new(response[2]).to_a
14
+ end
15
+ end
16
+ end
17
+
5
18
  before do
6
19
  @env = Rack::MockRequest.
7
20
  env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
8
21
  end
9
22
 
10
23
  should 'chunk responses with no Content-Length' do
11
- app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
12
- response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
24
+ app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
25
+ response = Rack::MockResponse.new(*chunked(app).call(@env))
13
26
  response.headers.should.not.include 'Content-Length'
14
27
  response.headers['Transfer-Encoding'].should.equal 'chunked'
15
28
  response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n"
16
29
  end
17
30
 
18
31
  should 'chunks empty bodies properly' do
19
- app = lambda { |env| [200, {}, []] }
20
- response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
32
+ app = lambda { |env| [200, {"Content-Type" => "text/plain"}, []] }
33
+ response = Rack::MockResponse.new(*chunked(app).call(@env))
21
34
  response.headers.should.not.include 'Content-Length'
22
35
  response.headers['Transfer-Encoding'].should.equal 'chunked'
23
36
  response.body.should.equal "0\r\n\r\n"
24
37
  end
25
38
 
39
+ should 'chunks encoded bodies properly' do
40
+ body = ["\uFFFEHello", " ", "World"].map {|t| t.encode("UTF-16LE") }
41
+ app = lambda { |env| [200, {"Content-Type" => "text/plain"}, body] }
42
+ response = Rack::MockResponse.new(*chunked(app).call(@env))
43
+ response.headers.should.not.include 'Content-Length'
44
+ response.headers['Transfer-Encoding'].should.equal 'chunked'
45
+ response.body.encoding.to_s.should == "ASCII-8BIT"
46
+ response.body.should.equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n"
47
+ end if RUBY_VERSION >= "1.9"
48
+
26
49
  should 'not modify response when Content-Length header present' do
27
- app = lambda { |env| [200, {'Content-Length'=>'12'}, ['Hello', ' ', 'World!']] }
28
- status, headers, body = Rack::Chunked.new(app).call(@env)
50
+ app = lambda { |env|
51
+ [200, {"Content-Type" => "text/plain", 'Content-Length'=>'12'}, ['Hello', ' ', 'World!']]
52
+ }
53
+ status, headers, body = chunked(app).call(@env)
29
54
  status.should.equal 200
30
55
  headers.should.not.include 'Transfer-Encoding'
31
56
  headers.should.include 'Content-Length'
@@ -33,26 +58,28 @@ describe Rack::Chunked do
33
58
  end
34
59
 
35
60
  should 'not modify response when client is HTTP/1.0' do
36
- app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
61
+ app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] }
37
62
  @env['HTTP_VERSION'] = 'HTTP/1.0'
38
- status, headers, body = Rack::Chunked.new(app).call(@env)
63
+ status, headers, body = chunked(app).call(@env)
39
64
  status.should.equal 200
40
65
  headers.should.not.include 'Transfer-Encoding'
41
66
  body.join.should.equal 'Hello World!'
42
67
  end
43
68
 
44
69
  should 'not modify response when Transfer-Encoding header already present' do
45
- app = lambda { |env| [200, {'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']] }
46
- status, headers, body = Rack::Chunked.new(app).call(@env)
70
+ app = lambda { |env|
71
+ [200, {"Content-Type" => "text/plain", 'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']]
72
+ }
73
+ status, headers, body = chunked(app).call(@env)
47
74
  status.should.equal 200
48
75
  headers['Transfer-Encoding'].should.equal 'identity'
49
76
  body.join.should.equal 'Hello World!'
50
77
  end
51
78
 
52
- [100, 204, 304].each do |status_code|
79
+ [100, 204, 205, 304].each do |status_code|
53
80
  should "not modify response when status code is #{status_code}" do
54
81
  app = lambda { |env| [status_code, {}, []] }
55
- status, headers, _ = Rack::Chunked.new(app).call(@env)
82
+ status, headers, _ = chunked(app).call(@env)
56
83
  status.should.equal status_code
57
84
  headers.should.not.include 'Transfer-Encoding'
58
85
  end
@@ -1,19 +1,20 @@
1
1
  require 'rack/commonlogger'
2
+ require 'rack/lint'
2
3
  require 'rack/mock'
3
4
 
4
5
  describe Rack::CommonLogger do
5
6
  obj = 'foobar'
6
7
  length = obj.size
7
8
 
8
- app = lambda { |env|
9
+ app = Rack::Lint.new lambda { |env|
9
10
  [200,
10
11
  {"Content-Type" => "text/html", "Content-Length" => length.to_s},
11
12
  [obj]]}
12
- app_without_length = lambda { |env|
13
+ app_without_length = Rack::Lint.new lambda { |env|
13
14
  [200,
14
15
  {"Content-Type" => "text/html"},
15
16
  []]}
16
- app_with_zero_length = lambda { |env|
17
+ app_with_zero_length = Rack::Lint.new lambda { |env|
17
18
  [200,
18
19
  {"Content-Type" => "text/html", "Content-Length" => "0"},
19
20
  []]}
@@ -3,9 +3,13 @@ require 'rack/conditionalget'
3
3
  require 'rack/mock'
4
4
 
5
5
  describe Rack::ConditionalGet do
6
+ def conditional_get(app)
7
+ Rack::Lint.new Rack::ConditionalGet.new(app)
8
+ end
9
+
6
10
  should "set a 304 status and truncate body when If-Modified-Since hits" do
7
11
  timestamp = Time.now.httpdate
8
- app = Rack::ConditionalGet.new(lambda { |env|
12
+ app = conditional_get(lambda { |env|
9
13
  [200, {'Last-Modified'=>timestamp}, ['TEST']] })
10
14
 
11
15
  response = Rack::MockRequest.new(app).
@@ -16,7 +20,7 @@ describe Rack::ConditionalGet do
16
20
  end
17
21
 
18
22
  should "set a 304 status and truncate body when If-Modified-Since hits and is higher than current time" do
19
- app = Rack::ConditionalGet.new(lambda { |env|
23
+ app = conditional_get(lambda { |env|
20
24
  [200, {'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] })
21
25
 
22
26
  response = Rack::MockRequest.new(app).
@@ -27,7 +31,7 @@ describe Rack::ConditionalGet do
27
31
  end
28
32
 
29
33
  should "set a 304 status and truncate body when If-None-Match hits" do
30
- app = Rack::ConditionalGet.new(lambda { |env|
34
+ app = conditional_get(lambda { |env|
31
35
  [200, {'Etag'=>'1234'}, ['TEST']] })
32
36
 
33
37
  response = Rack::MockRequest.new(app).
@@ -39,8 +43,8 @@ describe Rack::ConditionalGet do
39
43
 
40
44
  should "not set a 304 status if If-Modified-Since hits but Etag does not" do
41
45
  timestamp = Time.now.httpdate
42
- app = Rack::ConditionalGet.new(lambda { |env|
43
- [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
46
+ app = conditional_get(lambda { |env|
47
+ [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
44
48
 
45
49
  response = Rack::MockRequest.new(app).
46
50
  get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321')
@@ -51,7 +55,7 @@ describe Rack::ConditionalGet do
51
55
 
52
56
  should "set a 304 status and truncate body when both If-None-Match and If-Modified-Since hits" do
53
57
  timestamp = Time.now.httpdate
54
- app = Rack::ConditionalGet.new(lambda { |env|
58
+ app = conditional_get(lambda { |env|
55
59
  [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
56
60
 
57
61
  response = Rack::MockRequest.new(app).
@@ -62,8 +66,8 @@ describe Rack::ConditionalGet do
62
66
  end
63
67
 
64
68
  should "not affect non-GET/HEAD requests" do
65
- app = Rack::ConditionalGet.new(lambda { |env|
66
- [200, {'Etag'=>'1234'}, ['TEST']] })
69
+ app = conditional_get(lambda { |env|
70
+ [200, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
67
71
 
68
72
  response = Rack::MockRequest.new(app).
69
73
  post("/", 'HTTP_IF_NONE_MATCH' => '1234')
@@ -73,8 +77,8 @@ describe Rack::ConditionalGet do
73
77
  end
74
78
 
75
79
  should "not affect non-200 requests" do
76
- app = Rack::ConditionalGet.new(lambda { |env|
77
- [302, {'Etag'=>'1234'}, ['TEST']] })
80
+ app = conditional_get(lambda { |env|
81
+ [302, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] })
78
82
 
79
83
  response = Rack::MockRequest.new(app).
80
84
  get("/", 'HTTP_IF_NONE_MATCH' => '1234')
@@ -85,8 +89,8 @@ describe Rack::ConditionalGet do
85
89
 
86
90
  should "not affect requests with malformed HTTP_IF_NONE_MATCH" do
87
91
  bad_timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S %z')
88
- app = Rack::ConditionalGet.new(lambda { |env|
89
- [200,{'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] })
92
+ app = conditional_get(lambda { |env|
93
+ [200,{'Last-Modified'=>(Time.now - 3600).httpdate, 'Content-Type' => 'text/plain'}, ['TEST']] })
90
94
 
91
95
  response = Rack::MockRequest.new(app).
92
96
  get("/", 'HTTP_IF_MODIFIED_SINCE' => bad_timestamp)
@@ -52,7 +52,7 @@ describe Rack::ContentLength do
52
52
  end.new(%w[one two three])
53
53
 
54
54
  app = lambda { |env| [200, {}, body] }
55
- response = Rack::ContentLength.new(app).call({})
55
+ Rack::ContentLength.new(app).call({})
56
56
  body.closed.should.equal true
57
57
  end
58
58
 
@@ -26,4 +26,10 @@ describe Rack::ContentType do
26
26
  headers.to_a.select { |k,v| k.downcase == "content-type" }.
27
27
  should.equal [["CONTENT-Type","foo/bar"]]
28
28
  end
29
+
30
+ should "not set Content-Type on 304 responses" do
31
+ app = lambda { |env| [304, {}, []] }
32
+ response = Rack::ContentType.new(app, "text/html").call({})
33
+ response[1]['Content-Type'].should.equal nil
34
+ end
29
35
  end
@@ -51,7 +51,7 @@ describe Rack::Deflater do
51
51
  response[2].each { |part| buf << inflater.inflate(part) }
52
52
  buf << inflater.finish
53
53
  buf.delete_if { |part| part.empty? }
54
- buf.should.equal(%w(foo bar))
54
+ buf.join.should.equal("foobar")
55
55
  end
56
56
 
57
57
  # TODO: This is really just a special case of the above...
@@ -104,7 +104,7 @@ describe Rack::Deflater do
104
104
  response[2].each { |part| buf << inflater.inflate(part) }
105
105
  buf << inflater.finish
106
106
  buf.delete_if { |part| part.empty? }
107
- buf.should.equal(%w(foo bar))
107
+ buf.join.should.equal("foobar")
108
108
  end
109
109
 
110
110
  should "be able to fallback to no deflation" do
@@ -54,4 +54,16 @@ describe Rack::Directory do
54
54
 
55
55
  res.should.be.not_found
56
56
  end
57
+
58
+ should "uri escape path parts" do # #265, properly escape file names
59
+ mr = Rack::MockRequest.new(Rack::Lint.new(app))
60
+
61
+ res = mr.get("/cgi/test%2bdirectory")
62
+
63
+ res.should.be.ok
64
+ res.body.should =~ %r[/cgi/test%2Bdirectory/test%2Bfile]
65
+
66
+ res = mr.get("/cgi/test%2bdirectory/test%2bfile")
67
+ res.should.be.ok
68
+ end
57
69
  end
@@ -5,7 +5,7 @@ require 'rack/handler/fastcgi'
5
5
  describe Rack::Handler::FastCGI do
6
6
  extend TestRequest::Helpers
7
7
 
8
- @host = '0.0.0.0'
8
+ @host = '127.0.0.1'
9
9
  @port = 9203
10
10
 
11
11
  if `which lighttpd` && !$?.success?
@@ -22,6 +22,23 @@ describe Rack::File do
22
22
  res["Last-Modified"].should.equal File.mtime(path).httpdate
23
23
  end
24
24
 
25
+ should "return 304 if file isn't modified since last serve" do
26
+ path = File.join(DOCROOT, "/cgi/test")
27
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
28
+ get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path).httpdate)
29
+
30
+ res.status.should.equal 304
31
+ res.body.should.be.empty
32
+ end
33
+
34
+ should "return the file if it's modified since last serve" do
35
+ path = File.join(DOCROOT, "/cgi/test")
36
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
37
+ get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => (File.mtime(path) - 100).httpdate)
38
+
39
+ res.should.be.ok
40
+ end
41
+
25
42
  should "serve files with URL encoded filenames" do
26
43
  res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
27
44
  get("/cgi/%74%65%73%74") # "/cgi/test"
@@ -30,9 +47,23 @@ describe Rack::File do
30
47
  res.should =~ /ruby/
31
48
  end
32
49
 
33
- should "not allow directory traversal" do
50
+ should "allow safe directory traversal" do
51
+ req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
52
+
53
+ res = req.get('/cgi/../cgi/test')
54
+ res.should.be.successful
55
+
56
+ res = req.get('.')
57
+ res.should.be.not_found
58
+
59
+ res = req.get("test/..")
60
+ res.should.be.not_found
61
+ end
62
+
63
+ should "not allow unsafe directory traversal" do
34
64
  req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
35
- res = req.get("/cgi/../test")
65
+
66
+ res = req.get("/../README")
36
67
  res.should.be.forbidden
37
68
 
38
69
  res = req.get("../test")
@@ -40,9 +71,6 @@ describe Rack::File do
40
71
 
41
72
  res = req.get("..")
42
73
  res.should.be.forbidden
43
-
44
- res = req.get("test/..")
45
- res.should.be.forbidden
46
74
  end
47
75
 
48
76
  should "allow files with .. in their name" do
@@ -57,13 +85,20 @@ describe Rack::File do
57
85
  res.should.be.not_found
58
86
  end
59
87
 
60
- should "not allow directory traversal with encoded periods" do
88
+ should "not allow unsafe directory traversal with encoded periods" do
61
89
  res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
62
90
  get("/%2E%2E/README")
63
91
 
64
92
  res.should.be.forbidden
65
93
  end
66
94
 
95
+ should "allow safe directory traversal with encoded periods" do
96
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
97
+ get("/cgi/%2E%2E/cgi/test")
98
+
99
+ res.should.be.successful
100
+ end
101
+
67
102
  should "404 if it can't find the file" do
68
103
  res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
69
104
  get("/cgi/blubb")
@@ -113,10 +148,25 @@ describe Rack::File do
113
148
  env = Rack::MockRequest.env_for("/cgi/test")
114
149
  status, heads, _ = Rack::File.new(DOCROOT, 'public, max-age=38').call(env)
115
150
 
116
- path = File.join(DOCROOT, "/cgi/test")
117
-
118
151
  status.should.equal 200
119
152
  heads['Cache-Control'].should.equal 'public, max-age=38'
120
153
  end
121
154
 
155
+ should "only support GET and HEAD requests" do
156
+ req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT)))
157
+
158
+ forbidden = %w[post put delete]
159
+ forbidden.each do |method|
160
+
161
+ res = req.send(method, "/cgi/test")
162
+ res.should.be.forbidden
163
+ end
164
+
165
+ allowed = %w[get head]
166
+ allowed.each do |method|
167
+ res = req.send(method, "/cgi/test")
168
+ res.should.be.successful
169
+ end
170
+ end
171
+
122
172
  end