eac-rack 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +399 -0
  4. data/bin/rackup +4 -0
  5. data/contrib/rack_logo.svg +111 -0
  6. data/example/lobster.ru +4 -0
  7. data/example/protectedlobster.rb +14 -0
  8. data/example/protectedlobster.ru +8 -0
  9. data/lib/rack.rb +92 -0
  10. data/lib/rack/adapter/camping.rb +22 -0
  11. data/lib/rack/auth/abstract/handler.rb +37 -0
  12. data/lib/rack/auth/abstract/request.rb +37 -0
  13. data/lib/rack/auth/basic.rb +58 -0
  14. data/lib/rack/auth/digest/md5.rb +124 -0
  15. data/lib/rack/auth/digest/nonce.rb +51 -0
  16. data/lib/rack/auth/digest/params.rb +55 -0
  17. data/lib/rack/auth/digest/request.rb +40 -0
  18. data/lib/rack/builder.rb +80 -0
  19. data/lib/rack/cascade.rb +41 -0
  20. data/lib/rack/chunked.rb +49 -0
  21. data/lib/rack/commonlogger.rb +49 -0
  22. data/lib/rack/conditionalget.rb +47 -0
  23. data/lib/rack/config.rb +15 -0
  24. data/lib/rack/content_length.rb +29 -0
  25. data/lib/rack/content_type.rb +23 -0
  26. data/lib/rack/deflater.rb +96 -0
  27. data/lib/rack/directory.rb +157 -0
  28. data/lib/rack/etag.rb +23 -0
  29. data/lib/rack/file.rb +90 -0
  30. data/lib/rack/handler.rb +88 -0
  31. data/lib/rack/handler/cgi.rb +61 -0
  32. data/lib/rack/handler/evented_mongrel.rb +8 -0
  33. data/lib/rack/handler/fastcgi.rb +89 -0
  34. data/lib/rack/handler/lsws.rb +63 -0
  35. data/lib/rack/handler/mongrel.rb +90 -0
  36. data/lib/rack/handler/scgi.rb +62 -0
  37. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  38. data/lib/rack/handler/thin.rb +18 -0
  39. data/lib/rack/handler/webrick.rb +69 -0
  40. data/lib/rack/head.rb +19 -0
  41. data/lib/rack/lint.rb +575 -0
  42. data/lib/rack/lobster.rb +65 -0
  43. data/lib/rack/lock.rb +16 -0
  44. data/lib/rack/logger.rb +20 -0
  45. data/lib/rack/methodoverride.rb +27 -0
  46. data/lib/rack/mime.rb +206 -0
  47. data/lib/rack/mock.rb +189 -0
  48. data/lib/rack/nulllogger.rb +18 -0
  49. data/lib/rack/recursive.rb +57 -0
  50. data/lib/rack/reloader.rb +109 -0
  51. data/lib/rack/request.rb +271 -0
  52. data/lib/rack/response.rb +149 -0
  53. data/lib/rack/rewindable_input.rb +100 -0
  54. data/lib/rack/runtime.rb +27 -0
  55. data/lib/rack/sendfile.rb +142 -0
  56. data/lib/rack/server.rb +212 -0
  57. data/lib/rack/session/abstract/id.rb +140 -0
  58. data/lib/rack/session/cookie.rb +90 -0
  59. data/lib/rack/session/memcache.rb +119 -0
  60. data/lib/rack/session/pool.rb +100 -0
  61. data/lib/rack/showexceptions.rb +349 -0
  62. data/lib/rack/showstatus.rb +106 -0
  63. data/lib/rack/static.rb +38 -0
  64. data/lib/rack/urlmap.rb +56 -0
  65. data/lib/rack/utils.rb +614 -0
  66. data/rack.gemspec +38 -0
  67. data/test/spec_rack_auth_basic.rb +73 -0
  68. data/test/spec_rack_auth_digest.rb +226 -0
  69. data/test/spec_rack_builder.rb +84 -0
  70. data/test/spec_rack_camping.rb +51 -0
  71. data/test/spec_rack_cascade.rb +48 -0
  72. data/test/spec_rack_cgi.rb +89 -0
  73. data/test/spec_rack_chunked.rb +62 -0
  74. data/test/spec_rack_commonlogger.rb +61 -0
  75. data/test/spec_rack_conditionalget.rb +41 -0
  76. data/test/spec_rack_config.rb +24 -0
  77. data/test/spec_rack_content_length.rb +43 -0
  78. data/test/spec_rack_content_type.rb +30 -0
  79. data/test/spec_rack_deflater.rb +127 -0
  80. data/test/spec_rack_directory.rb +61 -0
  81. data/test/spec_rack_etag.rb +17 -0
  82. data/test/spec_rack_fastcgi.rb +89 -0
  83. data/test/spec_rack_file.rb +75 -0
  84. data/test/spec_rack_handler.rb +43 -0
  85. data/test/spec_rack_head.rb +30 -0
  86. data/test/spec_rack_lint.rb +528 -0
  87. data/test/spec_rack_lobster.rb +45 -0
  88. data/test/spec_rack_lock.rb +38 -0
  89. data/test/spec_rack_logger.rb +21 -0
  90. data/test/spec_rack_methodoverride.rb +60 -0
  91. data/test/spec_rack_mock.rb +243 -0
  92. data/test/spec_rack_mongrel.rb +189 -0
  93. data/test/spec_rack_nulllogger.rb +13 -0
  94. data/test/spec_rack_recursive.rb +77 -0
  95. data/test/spec_rack_request.rb +545 -0
  96. data/test/spec_rack_response.rb +221 -0
  97. data/test/spec_rack_rewindable_input.rb +118 -0
  98. data/test/spec_rack_runtime.rb +35 -0
  99. data/test/spec_rack_sendfile.rb +86 -0
  100. data/test/spec_rack_session_cookie.rb +73 -0
  101. data/test/spec_rack_session_memcache.rb +273 -0
  102. data/test/spec_rack_session_pool.rb +172 -0
  103. data/test/spec_rack_showexceptions.rb +21 -0
  104. data/test/spec_rack_showstatus.rb +72 -0
  105. data/test/spec_rack_static.rb +37 -0
  106. data/test/spec_rack_thin.rb +91 -0
  107. data/test/spec_rack_urlmap.rb +215 -0
  108. data/test/spec_rack_utils.rb +554 -0
  109. data/test/spec_rack_webrick.rb +130 -0
  110. data/test/spec_rackup.rb +154 -0
  111. metadata +311 -0
@@ -0,0 +1,61 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/directory'
4
+ require 'rack/lint'
5
+
6
+ require 'rack/mock'
7
+
8
+ context "Rack::Directory" do
9
+ DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
10
+ FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] }
11
+ app = Rack::Directory.new DOCROOT, FILE_CATCH
12
+
13
+ specify "serves directory indices" do
14
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
15
+ get("/cgi/")
16
+
17
+ res.should.be.ok
18
+ res.should =~ /<html><head>/
19
+ end
20
+
21
+ specify "passes to app if file found" do
22
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
23
+ get("/cgi/test")
24
+
25
+ res.should.be.ok
26
+ res.should =~ /passed!/
27
+ end
28
+
29
+ specify "serves uri with URL encoded filenames" do
30
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
31
+ get("/%63%67%69/") # "/cgi/test"
32
+
33
+ res.should.be.ok
34
+ res.should =~ /<html><head>/
35
+
36
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
37
+ get("/cgi/%74%65%73%74") # "/cgi/test"
38
+
39
+ res.should.be.ok
40
+ res.should =~ /passed!/
41
+ end
42
+
43
+ specify "does not allow directory traversal" do
44
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
45
+ get("/cgi/../test")
46
+
47
+ res.should.be.forbidden
48
+
49
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
50
+ get("/cgi/%2E%2E/test")
51
+
52
+ res.should.be.forbidden
53
+ end
54
+
55
+ specify "404s if it can't find the file" do
56
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
57
+ get("/cgi/blubb")
58
+
59
+ res.should.be.not_found
60
+ end
61
+ end
@@ -0,0 +1,17 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/etag'
4
+
5
+ context "Rack::ETag" do
6
+ specify "sets ETag if none is set" do
7
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
8
+ response = Rack::ETag.new(app).call({})
9
+ response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
10
+ end
11
+
12
+ specify "does not change ETag if it is already set" do
13
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
14
+ response = Rack::ETag.new(app).call({})
15
+ response[1]['ETag'].should.equal "\"abc\""
16
+ end
17
+ end
@@ -0,0 +1,89 @@
1
+ require 'test/spec'
2
+ require 'testrequest'
3
+
4
+ context "Rack::Handler::FastCGI" do
5
+ include TestRequest::Helpers
6
+
7
+ setup do
8
+ @host = '0.0.0.0'
9
+ @port = 9203
10
+ end
11
+
12
+ # Keep this first.
13
+ specify "startup" do
14
+ $pid = fork {
15
+ Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi"))
16
+ exec "lighttpd -D -f lighttpd.conf"
17
+ }
18
+ end
19
+
20
+ specify "should respond" do
21
+ sleep 1
22
+ lambda {
23
+ GET("/test.fcgi")
24
+ }.should.not.raise
25
+ end
26
+
27
+ specify "should be a lighttpd" do
28
+ GET("/test.fcgi")
29
+ status.should.be 200
30
+ response["SERVER_SOFTWARE"].should =~ /lighttpd/
31
+ response["HTTP_VERSION"].should.equal "HTTP/1.1"
32
+ response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
33
+ response["SERVER_PORT"].should.equal @port.to_s
34
+ response["SERVER_NAME"].should =~ @host
35
+ end
36
+
37
+ specify "should have rack headers" do
38
+ GET("/test.fcgi")
39
+ response["rack.version"].should.equal [1,1]
40
+ response["rack.multithread"].should.be false
41
+ response["rack.multiprocess"].should.be true
42
+ response["rack.run_once"].should.be false
43
+ end
44
+
45
+ specify "should have CGI headers on GET" do
46
+ GET("/test.fcgi")
47
+ response["REQUEST_METHOD"].should.equal "GET"
48
+ response["SCRIPT_NAME"].should.equal "/test.fcgi"
49
+ response["REQUEST_PATH"].should.equal "/"
50
+ response["PATH_INFO"].should.equal ""
51
+ response["QUERY_STRING"].should.equal ""
52
+ response["test.postdata"].should.equal ""
53
+
54
+ GET("/test.fcgi/foo?quux=1")
55
+ response["REQUEST_METHOD"].should.equal "GET"
56
+ response["SCRIPT_NAME"].should.equal "/test.fcgi"
57
+ response["REQUEST_PATH"].should.equal "/"
58
+ response["PATH_INFO"].should.equal "/foo"
59
+ response["QUERY_STRING"].should.equal "quux=1"
60
+ end
61
+
62
+ specify "should have CGI headers on POST" do
63
+ POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
64
+ status.should.equal 200
65
+ response["REQUEST_METHOD"].should.equal "POST"
66
+ response["SCRIPT_NAME"].should.equal "/test.fcgi"
67
+ response["REQUEST_PATH"].should.equal "/"
68
+ response["QUERY_STRING"].should.equal ""
69
+ response["HTTP_X_TEST_HEADER"].should.equal "42"
70
+ response["test.postdata"].should.equal "rack-form-data=23"
71
+ end
72
+
73
+ specify "should support HTTP auth" do
74
+ GET("/test.fcgi", {:user => "ruth", :passwd => "secret"})
75
+ response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
76
+ end
77
+
78
+ specify "should set status" do
79
+ GET("/test.fcgi?secret")
80
+ status.should.equal 403
81
+ response["rack.url_scheme"].should.equal "http"
82
+ end
83
+
84
+ # Keep this last.
85
+ specify "shutdown" do
86
+ Process.kill 15, $pid
87
+ Process.wait($pid).should.equal $pid
88
+ end
89
+ end
@@ -0,0 +1,75 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/file'
4
+ require 'rack/lint'
5
+
6
+ require 'rack/mock'
7
+
8
+ context "Rack::File" do
9
+ DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
10
+
11
+ specify "serves files" do
12
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
13
+ get("/cgi/test")
14
+
15
+ res.should.be.ok
16
+ res.should =~ /ruby/
17
+ end
18
+
19
+ specify "sets Last-Modified header" do
20
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
21
+ get("/cgi/test")
22
+
23
+ path = File.join(DOCROOT, "/cgi/test")
24
+
25
+ res.should.be.ok
26
+ res["Last-Modified"].should.equal File.mtime(path).httpdate
27
+ end
28
+
29
+ specify "serves files with URL encoded filenames" do
30
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
31
+ get("/cgi/%74%65%73%74") # "/cgi/test"
32
+
33
+ res.should.be.ok
34
+ res.should =~ /ruby/
35
+ end
36
+
37
+ specify "does not allow directory traversal" do
38
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
39
+ get("/cgi/../test")
40
+
41
+ res.should.be.forbidden
42
+ end
43
+
44
+ specify "does not allow directory traversal with encoded periods" do
45
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
46
+ get("/%2E%2E/README")
47
+
48
+ res.should.be.forbidden
49
+ end
50
+
51
+ specify "404s if it can't find the file" do
52
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
53
+ get("/cgi/blubb")
54
+
55
+ res.should.be.not_found
56
+ end
57
+
58
+ specify "detects SystemCallErrors" do
59
+ res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
60
+ get("/cgi")
61
+
62
+ res.should.be.not_found
63
+ end
64
+
65
+ specify "returns bodies that respond to #to_path" do
66
+ env = Rack::MockRequest.env_for("/cgi/test")
67
+ status, headers, body = Rack::File.new(DOCROOT).call(env)
68
+
69
+ path = File.join(DOCROOT, "/cgi/test")
70
+
71
+ status.should.equal 200
72
+ body.should.respond_to :to_path
73
+ body.to_path.should.equal path
74
+ end
75
+ end
@@ -0,0 +1,43 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/handler'
4
+
5
+ class Rack::Handler::Lobster; end
6
+ class RockLobster; end
7
+
8
+ context "Rack::Handler" do
9
+ specify "has registered default handlers" do
10
+ Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
11
+ Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
12
+ Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
13
+ Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick
14
+ end
15
+
16
+ specify "handler that doesn't exist should raise a NameError" do
17
+ lambda {
18
+ Rack::Handler.get('boom')
19
+ }.should.raise(NameError)
20
+ end
21
+
22
+ specify "should get unregistered, but already required, handler by name" do
23
+ Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster
24
+ end
25
+
26
+ specify "should register custom handler" do
27
+ Rack::Handler.register('rock_lobster', 'RockLobster')
28
+ Rack::Handler.get('rock_lobster').should.equal RockLobster
29
+ end
30
+
31
+ specify "should not need registration for properly coded handlers even if not already required" do
32
+ begin
33
+ $:.push "test/unregistered_handler"
34
+ Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered
35
+ lambda {
36
+ Rack::Handler.get('UnRegistered')
37
+ }.should.raise(NameError)
38
+ Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne
39
+ ensure
40
+ $:.delete "test/unregistered_handler"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ require 'rack/head'
2
+ require 'rack/mock'
3
+
4
+ context "Rack::Head" do
5
+ def test_response(headers = {})
6
+ app = lambda { |env| [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] }
7
+ request = Rack::MockRequest.env_for("/", headers)
8
+ response = Rack::Head.new(app).call(request)
9
+
10
+ return response
11
+ end
12
+
13
+ specify "passes GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do
14
+ %w[GET POST PUT DELETE OPTIONS TRACE].each do |type|
15
+ resp = test_response("REQUEST_METHOD" => type)
16
+
17
+ resp[0].should.equal(200)
18
+ resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
19
+ resp[2].should.equal(["foo"])
20
+ end
21
+ end
22
+
23
+ specify "removes body from HEAD requests" do
24
+ resp = test_response("REQUEST_METHOD" => "HEAD")
25
+
26
+ resp[0].should.equal(200)
27
+ resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"})
28
+ resp[2].should.equal([])
29
+ end
30
+ end
@@ -0,0 +1,528 @@
1
+ require 'test/spec'
2
+ require 'stringio'
3
+
4
+ require 'rack/lint'
5
+ require 'rack/mock'
6
+
7
+ context "Rack::Lint" do
8
+ def env(*args)
9
+ Rack::MockRequest.env_for("/", *args)
10
+ end
11
+
12
+ specify "passes valid request" do
13
+ lambda {
14
+ Rack::Lint.new(lambda { |env|
15
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
16
+ }).call(env({}))
17
+ }.should.not.raise
18
+ end
19
+
20
+ specify "notices fatal errors" do
21
+ lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
22
+ message.should.match(/No env given/)
23
+ end
24
+
25
+ specify "notices environment errors" do
26
+ lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError).
27
+ message.should.match(/not a Hash/)
28
+
29
+ lambda {
30
+ e = env
31
+ e.delete("REQUEST_METHOD")
32
+ Rack::Lint.new(nil).call(e)
33
+ }.should.raise(Rack::Lint::LintError).
34
+ message.should.match(/missing required key REQUEST_METHOD/)
35
+
36
+ lambda {
37
+ e = env
38
+ e.delete("SERVER_NAME")
39
+ Rack::Lint.new(nil).call(e)
40
+ }.should.raise(Rack::Lint::LintError).
41
+ message.should.match(/missing required key SERVER_NAME/)
42
+
43
+
44
+ lambda {
45
+ Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain"))
46
+ }.should.raise(Rack::Lint::LintError).
47
+ message.should.match(/contains HTTP_CONTENT_TYPE/)
48
+
49
+ lambda {
50
+ Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42"))
51
+ }.should.raise(Rack::Lint::LintError).
52
+ message.should.match(/contains HTTP_CONTENT_LENGTH/)
53
+
54
+ lambda {
55
+ Rack::Lint.new(nil).call(env("FOO" => Object.new))
56
+ }.should.raise(Rack::Lint::LintError).
57
+ message.should.match(/non-string value/)
58
+
59
+ lambda {
60
+ Rack::Lint.new(nil).call(env("rack.version" => "0.2"))
61
+ }.should.raise(Rack::Lint::LintError).
62
+ message.should.match(/must be an Array/)
63
+
64
+ lambda {
65
+ Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher"))
66
+ }.should.raise(Rack::Lint::LintError).
67
+ message.should.match(/url_scheme unknown/)
68
+
69
+ lambda {
70
+ Rack::Lint.new(nil).call(env("rack.session" => []))
71
+ }.should.raise(Rack::Lint::LintError).
72
+ message.should.equal("session [] must respond to store and []=")
73
+
74
+ lambda {
75
+ Rack::Lint.new(nil).call(env("rack.logger" => []))
76
+ }.should.raise(Rack::Lint::LintError).
77
+ message.should.equal("logger [] must respond to info")
78
+
79
+ lambda {
80
+ Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
81
+ }.should.raise(Rack::Lint::LintError).
82
+ message.should.match(/REQUEST_METHOD/)
83
+
84
+ lambda {
85
+ Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
86
+ }.should.raise(Rack::Lint::LintError).
87
+ message.should.match(/must start with/)
88
+
89
+ lambda {
90
+ Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo"))
91
+ }.should.raise(Rack::Lint::LintError).
92
+ message.should.match(/must start with/)
93
+
94
+ lambda {
95
+ Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii"))
96
+ }.should.raise(Rack::Lint::LintError).
97
+ message.should.match(/Invalid CONTENT_LENGTH/)
98
+
99
+ lambda {
100
+ e = env
101
+ e.delete("PATH_INFO")
102
+ e.delete("SCRIPT_NAME")
103
+ Rack::Lint.new(nil).call(e)
104
+ }.should.raise(Rack::Lint::LintError).
105
+ message.should.match(/One of .* must be set/)
106
+
107
+ lambda {
108
+ Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
109
+ }.should.raise(Rack::Lint::LintError).
110
+ message.should.match(/cannot be .* make it ''/)
111
+ end
112
+
113
+ specify "notices input errors" do
114
+ lambda {
115
+ Rack::Lint.new(nil).call(env("rack.input" => ""))
116
+ }.should.raise(Rack::Lint::LintError).
117
+ message.should.match(/does not respond to #gets/)
118
+
119
+ lambda {
120
+ input = Object.new
121
+ def input.binmode?
122
+ false
123
+ end
124
+ Rack::Lint.new(nil).call(env("rack.input" => input))
125
+ }.should.raise(Rack::Lint::LintError).
126
+ message.should.match(/is not opened in binary mode/)
127
+
128
+ lambda {
129
+ input = Object.new
130
+ def input.external_encoding
131
+ result = Object.new
132
+ def result.name
133
+ "US-ASCII"
134
+ end
135
+ result
136
+ end
137
+ Rack::Lint.new(nil).call(env("rack.input" => input))
138
+ }.should.raise(Rack::Lint::LintError).
139
+ message.should.match(/does not have ASCII-8BIT as its external encoding/)
140
+ end
141
+
142
+ specify "notices error errors" do
143
+ lambda {
144
+ Rack::Lint.new(nil).call(env("rack.errors" => ""))
145
+ }.should.raise(Rack::Lint::LintError).
146
+ message.should.match(/does not respond to #puts/)
147
+ end
148
+
149
+ specify "notices status errors" do
150
+ lambda {
151
+ Rack::Lint.new(lambda { |env|
152
+ ["cc", {}, ""]
153
+ }).call(env({}))
154
+ }.should.raise(Rack::Lint::LintError).
155
+ message.should.match(/must be >=100 seen as integer/)
156
+
157
+ lambda {
158
+ Rack::Lint.new(lambda { |env|
159
+ [42, {}, ""]
160
+ }).call(env({}))
161
+ }.should.raise(Rack::Lint::LintError).
162
+ message.should.match(/must be >=100 seen as integer/)
163
+ end
164
+
165
+ specify "notices header errors" do
166
+ lambda {
167
+ Rack::Lint.new(lambda { |env|
168
+ [200, Object.new, []]
169
+ }).call(env({}))
170
+ }.should.raise(Rack::Lint::LintError).
171
+ message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
172
+
173
+ lambda {
174
+ Rack::Lint.new(lambda { |env|
175
+ [200, {true=>false}, []]
176
+ }).call(env({}))
177
+ }.should.raise(Rack::Lint::LintError).
178
+ message.should.equal("header key must be a string, was TrueClass")
179
+
180
+ lambda {
181
+ Rack::Lint.new(lambda { |env|
182
+ [200, {"Status" => "404"}, []]
183
+ }).call(env({}))
184
+ }.should.raise(Rack::Lint::LintError).
185
+ message.should.match(/must not contain Status/)
186
+
187
+ lambda {
188
+ Rack::Lint.new(lambda { |env|
189
+ [200, {"Content-Type:" => "text/plain"}, []]
190
+ }).call(env({}))
191
+ }.should.raise(Rack::Lint::LintError).
192
+ message.should.match(/must not contain :/)
193
+
194
+ lambda {
195
+ Rack::Lint.new(lambda { |env|
196
+ [200, {"Content-" => "text/plain"}, []]
197
+ }).call(env({}))
198
+ }.should.raise(Rack::Lint::LintError).
199
+ message.should.match(/must not end/)
200
+
201
+ lambda {
202
+ Rack::Lint.new(lambda { |env|
203
+ [200, {"..%%quark%%.." => "text/plain"}, []]
204
+ }).call(env({}))
205
+ }.should.raise(Rack::Lint::LintError).
206
+ message.should.equal("invalid header name: ..%%quark%%..")
207
+
208
+ lambda {
209
+ Rack::Lint.new(lambda { |env|
210
+ [200, {"Foo" => Object.new}, []]
211
+ }).call(env({}))
212
+ }.should.raise(Rack::Lint::LintError).
213
+ message.should.equal("a header value must be a String, but the value of 'Foo' is a Object")
214
+
215
+ lambda {
216
+ Rack::Lint.new(lambda { |env|
217
+ [200, {"Foo" => [1, 2, 3]}, []]
218
+ }).call(env({}))
219
+ }.should.raise(Rack::Lint::LintError).
220
+ message.should.equal("a header value must be a String, but the value of 'Foo' is a Array")
221
+
222
+
223
+ lambda {
224
+ Rack::Lint.new(lambda { |env|
225
+ [200, {"Foo-Bar" => "text\000plain"}, []]
226
+ }).call(env({}))
227
+ }.should.raise(Rack::Lint::LintError).
228
+ message.should.match(/invalid header/)
229
+
230
+ # line ends (010) should be allowed in header values.
231
+ lambda {
232
+ Rack::Lint.new(lambda { |env|
233
+ [200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
234
+ }).call(env({}))
235
+ }.should.not.raise(Rack::Lint::LintError)
236
+ end
237
+
238
+ specify "notices content-type errors" do
239
+ lambda {
240
+ Rack::Lint.new(lambda { |env|
241
+ [200, {"Content-length" => "0"}, []]
242
+ }).call(env({}))
243
+ }.should.raise(Rack::Lint::LintError).
244
+ message.should.match(/No Content-Type/)
245
+
246
+ [100, 101, 204, 304].each do |status|
247
+ lambda {
248
+ Rack::Lint.new(lambda { |env|
249
+ [status, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
250
+ }).call(env({}))
251
+ }.should.raise(Rack::Lint::LintError).
252
+ message.should.match(/Content-Type header found/)
253
+ end
254
+ end
255
+
256
+ specify "notices content-length errors" do
257
+ [100, 101, 204, 304].each do |status|
258
+ lambda {
259
+ Rack::Lint.new(lambda { |env|
260
+ [status, {"Content-length" => "0"}, []]
261
+ }).call(env({}))
262
+ }.should.raise(Rack::Lint::LintError).
263
+ message.should.match(/Content-Length header found/)
264
+ end
265
+
266
+ lambda {
267
+ Rack::Lint.new(lambda { |env|
268
+ [200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
269
+ }).call(env({}))
270
+ }.should.raise(Rack::Lint::LintError).
271
+ message.should.match(/Content-Length header was 1, but should be 0/)
272
+ end
273
+
274
+ specify "notices body errors" do
275
+ lambda {
276
+ status, header, body = Rack::Lint.new(lambda { |env|
277
+ [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
278
+ }).call(env({}))
279
+ body.each { |part| }
280
+ }.should.raise(Rack::Lint::LintError).
281
+ message.should.match(/yielded non-string/)
282
+ end
283
+
284
+ specify "notices input handling errors" do
285
+ lambda {
286
+ Rack::Lint.new(lambda { |env|
287
+ env["rack.input"].gets("\r\n")
288
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
289
+ }).call(env({}))
290
+ }.should.raise(Rack::Lint::LintError).
291
+ message.should.match(/gets called with arguments/)
292
+
293
+ lambda {
294
+ Rack::Lint.new(lambda { |env|
295
+ env["rack.input"].read(1, 2, 3)
296
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
297
+ }).call(env({}))
298
+ }.should.raise(Rack::Lint::LintError).
299
+ message.should.match(/read called with too many arguments/)
300
+
301
+ lambda {
302
+ Rack::Lint.new(lambda { |env|
303
+ env["rack.input"].read("foo")
304
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
305
+ }).call(env({}))
306
+ }.should.raise(Rack::Lint::LintError).
307
+ message.should.match(/read called with non-integer and non-nil length/)
308
+
309
+ lambda {
310
+ Rack::Lint.new(lambda { |env|
311
+ env["rack.input"].read(-1)
312
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
313
+ }).call(env({}))
314
+ }.should.raise(Rack::Lint::LintError).
315
+ message.should.match(/read called with a negative length/)
316
+
317
+ lambda {
318
+ Rack::Lint.new(lambda { |env|
319
+ env["rack.input"].read(nil, nil)
320
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
321
+ }).call(env({}))
322
+ }.should.raise(Rack::Lint::LintError).
323
+ message.should.match(/read called with non-String buffer/)
324
+
325
+ lambda {
326
+ Rack::Lint.new(lambda { |env|
327
+ env["rack.input"].read(nil, 1)
328
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
329
+ }).call(env({}))
330
+ }.should.raise(Rack::Lint::LintError).
331
+ message.should.match(/read called with non-String buffer/)
332
+
333
+ lambda {
334
+ Rack::Lint.new(lambda { |env|
335
+ env["rack.input"].rewind(0)
336
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
337
+ }).call(env({}))
338
+ }.should.raise(Rack::Lint::LintError).
339
+ message.should.match(/rewind called with arguments/)
340
+
341
+ weirdio = Object.new
342
+ class << weirdio
343
+ def gets
344
+ 42
345
+ end
346
+
347
+ def read
348
+ 23
349
+ end
350
+
351
+ def each
352
+ yield 23
353
+ yield 42
354
+ end
355
+
356
+ def rewind
357
+ raise Errno::ESPIPE, "Errno::ESPIPE"
358
+ end
359
+ end
360
+
361
+ eof_weirdio = Object.new
362
+ class << eof_weirdio
363
+ def gets
364
+ nil
365
+ end
366
+
367
+ def read(*args)
368
+ nil
369
+ end
370
+
371
+ def each
372
+ end
373
+
374
+ def rewind
375
+ end
376
+ end
377
+
378
+ lambda {
379
+ Rack::Lint.new(lambda { |env|
380
+ env["rack.input"].gets
381
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
382
+ }).call(env("rack.input" => weirdio))
383
+ }.should.raise(Rack::Lint::LintError).
384
+ message.should.match(/gets didn't return a String/)
385
+
386
+ lambda {
387
+ Rack::Lint.new(lambda { |env|
388
+ env["rack.input"].each { |x| }
389
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
390
+ }).call(env("rack.input" => weirdio))
391
+ }.should.raise(Rack::Lint::LintError).
392
+ message.should.match(/each didn't yield a String/)
393
+
394
+ lambda {
395
+ Rack::Lint.new(lambda { |env|
396
+ env["rack.input"].read
397
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
398
+ }).call(env("rack.input" => weirdio))
399
+ }.should.raise(Rack::Lint::LintError).
400
+ message.should.match(/read didn't return nil or a String/)
401
+
402
+ lambda {
403
+ Rack::Lint.new(lambda { |env|
404
+ env["rack.input"].read
405
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
406
+ }).call(env("rack.input" => eof_weirdio))
407
+ }.should.raise(Rack::Lint::LintError).
408
+ message.should.match(/read\(nil\) returned nil on EOF/)
409
+
410
+ lambda {
411
+ Rack::Lint.new(lambda { |env|
412
+ env["rack.input"].rewind
413
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
414
+ }).call(env("rack.input" => weirdio))
415
+ }.should.raise(Rack::Lint::LintError).
416
+ message.should.match(/rewind raised Errno::ESPIPE/)
417
+
418
+
419
+ lambda {
420
+ Rack::Lint.new(lambda { |env|
421
+ env["rack.input"].close
422
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
423
+ }).call(env({}))
424
+ }.should.raise(Rack::Lint::LintError).
425
+ message.should.match(/close must not be called/)
426
+ end
427
+
428
+ specify "notices error handling errors" do
429
+ lambda {
430
+ Rack::Lint.new(lambda { |env|
431
+ env["rack.errors"].write(42)
432
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
433
+ }).call(env({}))
434
+ }.should.raise(Rack::Lint::LintError).
435
+ message.should.match(/write not called with a String/)
436
+
437
+ lambda {
438
+ Rack::Lint.new(lambda { |env|
439
+ env["rack.errors"].close
440
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
441
+ }).call(env({}))
442
+ }.should.raise(Rack::Lint::LintError).
443
+ message.should.match(/close must not be called/)
444
+ end
445
+
446
+ specify "notices HEAD errors" do
447
+ lambda {
448
+ Rack::Lint.new(lambda { |env|
449
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
450
+ }).call(env({"REQUEST_METHOD" => "HEAD"}))
451
+ }.should.not.raise
452
+
453
+ lambda {
454
+ Rack::Lint.new(lambda { |env|
455
+ [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
456
+ }).call(env({"REQUEST_METHOD" => "HEAD"}))
457
+ }.should.raise(Rack::Lint::LintError).
458
+ message.should.match(/body was given for HEAD/)
459
+ end
460
+
461
+ specify "passes valid read calls" do
462
+ hello_str = "hello world"
463
+ hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
464
+ lambda {
465
+ Rack::Lint.new(lambda { |env|
466
+ env["rack.input"].read
467
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
468
+ }).call(env({"rack.input" => StringIO.new(hello_str)}))
469
+ }.should.not.raise(Rack::Lint::LintError)
470
+
471
+ lambda {
472
+ Rack::Lint.new(lambda { |env|
473
+ env["rack.input"].read(0)
474
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
475
+ }).call(env({"rack.input" => StringIO.new(hello_str)}))
476
+ }.should.not.raise(Rack::Lint::LintError)
477
+
478
+ lambda {
479
+ Rack::Lint.new(lambda { |env|
480
+ env["rack.input"].read(1)
481
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
482
+ }).call(env({"rack.input" => StringIO.new(hello_str)}))
483
+ }.should.not.raise(Rack::Lint::LintError)
484
+
485
+ lambda {
486
+ Rack::Lint.new(lambda { |env|
487
+ env["rack.input"].read(nil)
488
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
489
+ }).call(env({"rack.input" => StringIO.new(hello_str)}))
490
+ }.should.not.raise(Rack::Lint::LintError)
491
+
492
+ lambda {
493
+ Rack::Lint.new(lambda { |env|
494
+ env["rack.input"].read(nil, '')
495
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
496
+ }).call(env({"rack.input" => StringIO.new(hello_str)}))
497
+ }.should.not.raise(Rack::Lint::LintError)
498
+
499
+ lambda {
500
+ Rack::Lint.new(lambda { |env|
501
+ env["rack.input"].read(1, '')
502
+ [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
503
+ }).call(env({"rack.input" => StringIO.new(hello_str)}))
504
+ }.should.not.raise(Rack::Lint::LintError)
505
+ end
506
+ end
507
+
508
+ context "Rack::Lint::InputWrapper" do
509
+ specify "delegates :size to underlying IO object" do
510
+ class IOMock
511
+ def size
512
+ 101
513
+ end
514
+ end
515
+
516
+ wrapper = Rack::Lint::InputWrapper.new(IOMock.new)
517
+ wrapper.size.should == 101
518
+ end
519
+
520
+ specify "delegates :rewind to underlying IO object" do
521
+ io = StringIO.new("123")
522
+ wrapper = Rack::Lint::InputWrapper.new(io)
523
+ wrapper.read.should.equal "123"
524
+ wrapper.read.should.equal ""
525
+ wrapper.rewind
526
+ wrapper.read.should.equal "123"
527
+ end
528
+ end