eac-rack 1.1.1

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 (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