edgar-rack 1.2.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 (138) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +401 -0
  4. data/Rakefile +101 -0
  5. data/SPEC +171 -0
  6. data/bin/rackup +4 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/example/lobster.ru +4 -0
  9. data/example/protectedlobster.rb +14 -0
  10. data/example/protectedlobster.ru +8 -0
  11. data/lib/rack.rb +81 -0
  12. data/lib/rack/auth/abstract/handler.rb +37 -0
  13. data/lib/rack/auth/abstract/request.rb +43 -0
  14. data/lib/rack/auth/basic.rb +58 -0
  15. data/lib/rack/auth/digest/md5.rb +124 -0
  16. data/lib/rack/auth/digest/nonce.rb +51 -0
  17. data/lib/rack/auth/digest/params.rb +53 -0
  18. data/lib/rack/auth/digest/request.rb +40 -0
  19. data/lib/rack/builder.rb +80 -0
  20. data/lib/rack/cascade.rb +41 -0
  21. data/lib/rack/chunked.rb +52 -0
  22. data/lib/rack/commonlogger.rb +49 -0
  23. data/lib/rack/conditionalget.rb +63 -0
  24. data/lib/rack/config.rb +15 -0
  25. data/lib/rack/content_length.rb +29 -0
  26. data/lib/rack/content_type.rb +23 -0
  27. data/lib/rack/deflater.rb +96 -0
  28. data/lib/rack/directory.rb +157 -0
  29. data/lib/rack/etag.rb +59 -0
  30. data/lib/rack/file.rb +118 -0
  31. data/lib/rack/handler.rb +88 -0
  32. data/lib/rack/handler/cgi.rb +61 -0
  33. data/lib/rack/handler/evented_mongrel.rb +8 -0
  34. data/lib/rack/handler/fastcgi.rb +90 -0
  35. data/lib/rack/handler/lsws.rb +61 -0
  36. data/lib/rack/handler/mongrel.rb +90 -0
  37. data/lib/rack/handler/scgi.rb +59 -0
  38. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  39. data/lib/rack/handler/thin.rb +17 -0
  40. data/lib/rack/handler/webrick.rb +73 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +567 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +44 -0
  45. data/lib/rack/logger.rb +18 -0
  46. data/lib/rack/methodoverride.rb +27 -0
  47. data/lib/rack/mime.rb +210 -0
  48. data/lib/rack/mock.rb +185 -0
  49. data/lib/rack/nulllogger.rb +18 -0
  50. data/lib/rack/recursive.rb +61 -0
  51. data/lib/rack/reloader.rb +109 -0
  52. data/lib/rack/request.rb +307 -0
  53. data/lib/rack/response.rb +151 -0
  54. data/lib/rack/rewindable_input.rb +104 -0
  55. data/lib/rack/runtime.rb +27 -0
  56. data/lib/rack/sendfile.rb +139 -0
  57. data/lib/rack/server.rb +289 -0
  58. data/lib/rack/session/abstract/id.rb +348 -0
  59. data/lib/rack/session/cookie.rb +152 -0
  60. data/lib/rack/session/memcache.rb +93 -0
  61. data/lib/rack/session/pool.rb +79 -0
  62. data/lib/rack/showexceptions.rb +378 -0
  63. data/lib/rack/showstatus.rb +113 -0
  64. data/lib/rack/static.rb +53 -0
  65. data/lib/rack/urlmap.rb +55 -0
  66. data/lib/rack/utils.rb +698 -0
  67. data/rack.gemspec +39 -0
  68. data/test/cgi/lighttpd.conf +25 -0
  69. data/test/cgi/rackup_stub.rb +6 -0
  70. data/test/cgi/sample_rackup.ru +5 -0
  71. data/test/cgi/test +9 -0
  72. data/test/cgi/test.fcgi +8 -0
  73. data/test/cgi/test.ru +5 -0
  74. data/test/gemloader.rb +6 -0
  75. data/test/multipart/bad_robots +259 -0
  76. data/test/multipart/binary +0 -0
  77. data/test/multipart/empty +10 -0
  78. data/test/multipart/fail_16384_nofile +814 -0
  79. data/test/multipart/file1.txt +1 -0
  80. data/test/multipart/filename_and_modification_param +7 -0
  81. data/test/multipart/filename_with_escaped_quotes +6 -0
  82. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  83. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  84. data/test/multipart/filename_with_unescaped_quotes +6 -0
  85. data/test/multipart/ie +6 -0
  86. data/test/multipart/nested +10 -0
  87. data/test/multipart/none +9 -0
  88. data/test/multipart/semicolon +6 -0
  89. data/test/multipart/text +15 -0
  90. data/test/rackup/config.ru +31 -0
  91. data/test/spec_auth_basic.rb +70 -0
  92. data/test/spec_auth_digest.rb +241 -0
  93. data/test/spec_builder.rb +123 -0
  94. data/test/spec_cascade.rb +45 -0
  95. data/test/spec_cgi.rb +102 -0
  96. data/test/spec_chunked.rb +60 -0
  97. data/test/spec_commonlogger.rb +56 -0
  98. data/test/spec_conditionalget.rb +86 -0
  99. data/test/spec_config.rb +23 -0
  100. data/test/spec_content_length.rb +36 -0
  101. data/test/spec_content_type.rb +29 -0
  102. data/test/spec_deflater.rb +125 -0
  103. data/test/spec_directory.rb +57 -0
  104. data/test/spec_etag.rb +75 -0
  105. data/test/spec_fastcgi.rb +107 -0
  106. data/test/spec_file.rb +92 -0
  107. data/test/spec_handler.rb +49 -0
  108. data/test/spec_head.rb +30 -0
  109. data/test/spec_lint.rb +515 -0
  110. data/test/spec_lobster.rb +43 -0
  111. data/test/spec_lock.rb +142 -0
  112. data/test/spec_logger.rb +28 -0
  113. data/test/spec_methodoverride.rb +58 -0
  114. data/test/spec_mock.rb +241 -0
  115. data/test/spec_mongrel.rb +182 -0
  116. data/test/spec_nulllogger.rb +12 -0
  117. data/test/spec_recursive.rb +69 -0
  118. data/test/spec_request.rb +774 -0
  119. data/test/spec_response.rb +245 -0
  120. data/test/spec_rewindable_input.rb +118 -0
  121. data/test/spec_runtime.rb +39 -0
  122. data/test/spec_sendfile.rb +83 -0
  123. data/test/spec_server.rb +8 -0
  124. data/test/spec_session_abstract_id.rb +43 -0
  125. data/test/spec_session_cookie.rb +171 -0
  126. data/test/spec_session_memcache.rb +289 -0
  127. data/test/spec_session_pool.rb +200 -0
  128. data/test/spec_showexceptions.rb +87 -0
  129. data/test/spec_showstatus.rb +79 -0
  130. data/test/spec_static.rb +48 -0
  131. data/test/spec_thin.rb +86 -0
  132. data/test/spec_urlmap.rb +213 -0
  133. data/test/spec_utils.rb +678 -0
  134. data/test/spec_webrick.rb +141 -0
  135. data/test/testrequest.rb +78 -0
  136. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  137. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  138. metadata +329 -0
@@ -0,0 +1,45 @@
1
+ require 'rack/cascade'
2
+ require 'rack/file'
3
+ require 'rack/urlmap'
4
+ require 'rack/mock'
5
+
6
+ describe Rack::Cascade do
7
+ docroot = File.expand_path(File.dirname(__FILE__))
8
+ app1 = Rack::File.new(docroot)
9
+
10
+ app2 = Rack::URLMap.new("/crash" => lambda { |env| raise "boom" })
11
+
12
+ app3 = Rack::URLMap.new("/foo" => lambda { |env|
13
+ [200, { "Content-Type" => "text/plain"}, [""]]})
14
+
15
+ should "dispatch onward on 404 by default" do
16
+ cascade = Rack::Cascade.new([app1, app2, app3])
17
+ Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok
18
+ Rack::MockRequest.new(cascade).get("/foo").should.be.ok
19
+ Rack::MockRequest.new(cascade).get("/toobad").should.be.not_found
20
+ Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.forbidden
21
+ end
22
+
23
+ should "dispatch onward on whatever is passed" do
24
+ cascade = Rack::Cascade.new([app1, app2, app3], [404, 403])
25
+ Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found
26
+ end
27
+
28
+ should "return 404 if empty" do
29
+ Rack::MockRequest.new(Rack::Cascade.new([])).get('/').should.be.not_found
30
+ end
31
+
32
+ should "append new app" do
33
+ cascade = Rack::Cascade.new([], [404, 403])
34
+ Rack::MockRequest.new(cascade).get('/').should.be.not_found
35
+ cascade << app2
36
+ Rack::MockRequest.new(cascade).get('/cgi/test').should.be.not_found
37
+ Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found
38
+ cascade << app1
39
+ Rack::MockRequest.new(cascade).get('/cgi/test').should.be.ok
40
+ Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.forbidden
41
+ Rack::MockRequest.new(cascade).get('/foo').should.be.not_found
42
+ cascade << app3
43
+ Rack::MockRequest.new(cascade).get('/foo').should.be.ok
44
+ end
45
+ end
@@ -0,0 +1,102 @@
1
+ begin
2
+ require File.expand_path('../testrequest', __FILE__)
3
+ require 'rack/handler/cgi'
4
+
5
+ describe Rack::Handler::CGI do
6
+ extend TestRequest::Helpers
7
+
8
+ @host = '0.0.0.0'
9
+ @port = 9203
10
+
11
+ if `which lighttpd` && !$?.success?
12
+ raise "lighttpd not found"
13
+ end
14
+
15
+ # Keep this first.
16
+ $pid = fork {
17
+ ENV['RACK_ENV'] = 'deployment'
18
+ ENV['RUBYLIB'] = [
19
+ File.expand_path('../../lib', __FILE__),
20
+ ENV['RUBYLIB'],
21
+ ].compact.join(':')
22
+
23
+ Dir.chdir(File.expand_path("../cgi", __FILE__)) do
24
+ exec "lighttpd -D -f lighttpd.conf"
25
+ end
26
+ }
27
+
28
+ should "respond" do
29
+ sleep 1
30
+ GET("/test")
31
+ response.should.not.be.nil
32
+ end
33
+
34
+ should "be a lighttpd" do
35
+ GET("/test")
36
+ status.should.equal 200
37
+ response["SERVER_SOFTWARE"].should =~ /lighttpd/
38
+ response["HTTP_VERSION"].should.equal "HTTP/1.1"
39
+ response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
40
+ response["SERVER_PORT"].should.equal @port.to_s
41
+ response["SERVER_NAME"].should.equal @host
42
+ end
43
+
44
+ should "have rack headers" do
45
+ GET("/test")
46
+ response["rack.version"].should.equal([1,1])
47
+ response["rack.multithread"].should.be.false
48
+ response["rack.multiprocess"].should.be.true
49
+ response["rack.run_once"].should.be.true
50
+ end
51
+
52
+ should "have CGI headers on GET" do
53
+ GET("/test")
54
+ response["REQUEST_METHOD"].should.equal "GET"
55
+ response["SCRIPT_NAME"].should.equal "/test"
56
+ response["REQUEST_PATH"].should.equal "/"
57
+ response["PATH_INFO"].should.be.nil
58
+ response["QUERY_STRING"].should.equal ""
59
+ response["test.postdata"].should.equal ""
60
+
61
+ GET("/test/foo?quux=1")
62
+ response["REQUEST_METHOD"].should.equal "GET"
63
+ response["SCRIPT_NAME"].should.equal "/test"
64
+ response["REQUEST_PATH"].should.equal "/"
65
+ response["PATH_INFO"].should.equal "/foo"
66
+ response["QUERY_STRING"].should.equal "quux=1"
67
+ end
68
+
69
+ should "have CGI headers on POST" do
70
+ POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
71
+ status.should.equal 200
72
+ response["REQUEST_METHOD"].should.equal "POST"
73
+ response["SCRIPT_NAME"].should.equal "/test"
74
+ response["REQUEST_PATH"].should.equal "/"
75
+ response["QUERY_STRING"].should.equal ""
76
+ response["HTTP_X_TEST_HEADER"].should.equal "42"
77
+ response["test.postdata"].should.equal "rack-form-data=23"
78
+ end
79
+
80
+ should "support HTTP auth" do
81
+ GET("/test", {:user => "ruth", :passwd => "secret"})
82
+ response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
83
+ end
84
+
85
+ should "set status" do
86
+ GET("/test?secret")
87
+ status.should.equal 403
88
+ response["rack.url_scheme"].should.equal "http"
89
+ end
90
+
91
+ # Keep this last.
92
+ should "shutdown" do
93
+ Process.kill 15, $pid
94
+ Process.wait($pid).should == $pid
95
+ end
96
+ end
97
+
98
+ rescue RuntimeError
99
+ $stderr.puts "Skipping Rack::Handler::CGI tests (lighttpd is required). Install lighttpd and try again."
100
+ rescue NotImplementedError
101
+ $stderr.puts "Your Ruby implemenation or platform does not support fork. Skipping Rack::Handler::CGI tests."
102
+ end
@@ -0,0 +1,60 @@
1
+ require 'rack/chunked'
2
+ require 'rack/mock'
3
+
4
+ describe Rack::Chunked do
5
+ before do
6
+ @env = Rack::MockRequest.
7
+ env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
8
+ end
9
+
10
+ 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))
13
+ response.headers.should.not.include 'Content-Length'
14
+ response.headers['Transfer-Encoding'].should.equal 'chunked'
15
+ response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n"
16
+ end
17
+
18
+ should 'chunks empty bodies properly' do
19
+ app = lambda { |env| [200, {}, []] }
20
+ response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
21
+ response.headers.should.not.include 'Content-Length'
22
+ response.headers['Transfer-Encoding'].should.equal 'chunked'
23
+ response.body.should.equal "0\r\n\r\n"
24
+ end
25
+
26
+ 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)
29
+ status.should.equal 200
30
+ headers.should.not.include 'Transfer-Encoding'
31
+ headers.should.include 'Content-Length'
32
+ body.join.should.equal 'Hello World!'
33
+ end
34
+
35
+ should 'not modify response when client is HTTP/1.0' do
36
+ app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
37
+ @env['HTTP_VERSION'] = 'HTTP/1.0'
38
+ status, headers, body = Rack::Chunked.new(app).call(@env)
39
+ status.should.equal 200
40
+ headers.should.not.include 'Transfer-Encoding'
41
+ body.join.should.equal 'Hello World!'
42
+ end
43
+
44
+ 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)
47
+ status.should.equal 200
48
+ headers['Transfer-Encoding'].should.equal 'identity'
49
+ body.join.should.equal 'Hello World!'
50
+ end
51
+
52
+ [100, 204, 304].each do |status_code|
53
+ should "not modify response when status code is #{status_code}" do
54
+ app = lambda { |env| [status_code, {}, []] }
55
+ status, headers, _ = Rack::Chunked.new(app).call(@env)
56
+ status.should.equal status_code
57
+ headers.should.not.include 'Transfer-Encoding'
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,56 @@
1
+ require 'rack/commonlogger'
2
+ require 'rack/mock'
3
+
4
+ describe Rack::CommonLogger do
5
+ obj = 'foobar'
6
+ length = obj.size
7
+
8
+ app = lambda { |env|
9
+ [200,
10
+ {"Content-Type" => "text/html", "Content-Length" => length.to_s},
11
+ [obj]]}
12
+ app_without_length = lambda { |env|
13
+ [200,
14
+ {"Content-Type" => "text/html"},
15
+ []]}
16
+ app_with_zero_length = lambda { |env|
17
+ [200,
18
+ {"Content-Type" => "text/html", "Content-Length" => "0"},
19
+ []]}
20
+
21
+ should "log to rack.errors by default" do
22
+ res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/")
23
+
24
+ res.errors.should.not.be.empty
25
+ res.errors.should =~ /"GET \/ " 200 #{length} /
26
+ end
27
+
28
+ should "log to anything with +write+" do
29
+ log = StringIO.new
30
+ Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
31
+
32
+ log.string.should =~ /"GET \/ " 200 #{length} /
33
+ end
34
+
35
+ should "log - content length if header is missing" do
36
+ res = Rack::MockRequest.new(Rack::CommonLogger.new(app_without_length)).get("/")
37
+
38
+ res.errors.should.not.be.empty
39
+ res.errors.should =~ /"GET \/ " 200 - /
40
+ end
41
+
42
+ should "log - content length if header is zero" do
43
+ res = Rack::MockRequest.new(Rack::CommonLogger.new(app_with_zero_length)).get("/")
44
+
45
+ res.errors.should.not.be.empty
46
+ res.errors.should =~ /"GET \/ " 200 - /
47
+ end
48
+
49
+ def length
50
+ 123
51
+ end
52
+
53
+ def self.obj
54
+ "hello world"
55
+ end
56
+ end
@@ -0,0 +1,86 @@
1
+ require 'time'
2
+ require 'rack/conditionalget'
3
+ require 'rack/mock'
4
+
5
+ describe Rack::ConditionalGet do
6
+ should "set a 304 status and truncate body when If-Modified-Since hits" do
7
+ timestamp = Time.now.httpdate
8
+ app = Rack::ConditionalGet.new(lambda { |env|
9
+ [200, {'Last-Modified'=>timestamp}, ['TEST']] })
10
+
11
+ response = Rack::MockRequest.new(app).
12
+ get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp)
13
+
14
+ response.status.should.equal 304
15
+ response.body.should.be.empty
16
+ end
17
+
18
+ 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|
20
+ [200, {'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] })
21
+
22
+ response = Rack::MockRequest.new(app).
23
+ get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate)
24
+
25
+ response.status.should.equal 304
26
+ response.body.should.be.empty
27
+ end
28
+
29
+ should "set a 304 status and truncate body when If-None-Match hits" do
30
+ app = Rack::ConditionalGet.new(lambda { |env|
31
+ [200, {'Etag'=>'1234'}, ['TEST']] })
32
+
33
+ response = Rack::MockRequest.new(app).
34
+ get("/", 'HTTP_IF_NONE_MATCH' => '1234')
35
+
36
+ response.status.should.equal 304
37
+ response.body.should.be.empty
38
+ end
39
+
40
+ should "not set a 304 status if If-Modified-Since hits but Etag does not" do
41
+ timestamp = Time.now.httpdate
42
+ app = Rack::ConditionalGet.new(lambda { |env|
43
+ [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
44
+
45
+ response = Rack::MockRequest.new(app).
46
+ get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321')
47
+
48
+ response.status.should.equal 200
49
+ response.body.should.equal 'TEST'
50
+ end
51
+
52
+ should "set a 304 status and truncate body when both If-None-Match and If-Modified-Since hits" do
53
+ timestamp = Time.now.httpdate
54
+ app = Rack::ConditionalGet.new(lambda { |env|
55
+ [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] })
56
+
57
+ response = Rack::MockRequest.new(app).
58
+ get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '1234')
59
+
60
+ response.status.should.equal 304
61
+ response.body.should.be.empty
62
+ end
63
+
64
+ should "not affect non-GET/HEAD requests" do
65
+ app = Rack::ConditionalGet.new(lambda { |env|
66
+ [200, {'Etag'=>'1234'}, ['TEST']] })
67
+
68
+ response = Rack::MockRequest.new(app).
69
+ post("/", 'HTTP_IF_NONE_MATCH' => '1234')
70
+
71
+ response.status.should.equal 200
72
+ response.body.should.equal 'TEST'
73
+ end
74
+
75
+ should "not affect non-200 requests" do
76
+ app = Rack::ConditionalGet.new(lambda { |env|
77
+ [302, {'Etag'=>'1234'}, ['TEST']] })
78
+
79
+ response = Rack::MockRequest.new(app).
80
+ get("/", 'HTTP_IF_NONE_MATCH' => '1234')
81
+
82
+ response.status.should.equal 302
83
+ response.body.should.equal 'TEST'
84
+ end
85
+
86
+ end
@@ -0,0 +1,23 @@
1
+ require 'rack/builder'
2
+ require 'rack/config'
3
+ require 'rack/content_length'
4
+ require 'rack/lint'
5
+ require 'rack/mock'
6
+
7
+ describe Rack::Config do
8
+ should "accept a block that modifies the environment" do
9
+ app = Rack::Builder.new do
10
+ use Rack::Lint
11
+ use Rack::ContentLength
12
+ use Rack::Config do |env|
13
+ env['greeting'] = 'hello'
14
+ end
15
+ run lambda { |env|
16
+ [200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
17
+ }
18
+ end
19
+
20
+ response = Rack::MockRequest.new(app).get('/')
21
+ response.body.should.equal('hello')
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ require 'rack/content_length'
2
+
3
+ describe Rack::ContentLength do
4
+ should "set Content-Length on Array bodies if none is set" do
5
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
6
+ response = Rack::ContentLength.new(app).call({})
7
+ response[1]['Content-Length'].should.equal '13'
8
+ end
9
+
10
+ should "not set Content-Length on variable length bodies" do
11
+ body = lambda { "Hello World!" }
12
+ def body.each ; yield call ; end
13
+
14
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
15
+ response = Rack::ContentLength.new(app).call({})
16
+ response[1]['Content-Length'].should.be.nil
17
+ end
18
+
19
+ should "not change Content-Length if it is already set" do
20
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] }
21
+ response = Rack::ContentLength.new(app).call({})
22
+ response[1]['Content-Length'].should.equal '1'
23
+ end
24
+
25
+ should "not set Content-Length on 304 responses" do
26
+ app = lambda { |env| [304, {'Content-Type' => 'text/plain'}, []] }
27
+ response = Rack::ContentLength.new(app).call({})
28
+ response[1]['Content-Length'].should.equal nil
29
+ end
30
+
31
+ should "not set Content-Length when Transfer-Encoding is chunked" do
32
+ app = lambda { |env| [200, {'Transfer-Encoding' => 'chunked'}, []] }
33
+ response = Rack::ContentLength.new(app).call({})
34
+ response[1]['Content-Length'].should.equal nil
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ require 'rack/content_type'
2
+
3
+ describe Rack::ContentType do
4
+ should "set Content-Type to default text/html if none is set" do
5
+ app = lambda { |env| [200, {}, "Hello, World!"] }
6
+ headers = Rack::ContentType.new(app).call({})[1]
7
+ headers['Content-Type'].should.equal 'text/html'
8
+ end
9
+
10
+ should "set Content-Type to chosen default if none is set" do
11
+ app = lambda { |env| [200, {}, "Hello, World!"] }
12
+ headers =
13
+ Rack::ContentType.new(app, 'application/octet-stream').call({})[1]
14
+ headers['Content-Type'].should.equal 'application/octet-stream'
15
+ end
16
+
17
+ should "not change Content-Type if it is already set" do
18
+ app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] }
19
+ headers = Rack::ContentType.new(app).call({})[1]
20
+ headers['Content-Type'].should.equal 'foo/bar'
21
+ end
22
+
23
+ should "detect Content-Type case insensitive" do
24
+ app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] }
25
+ headers = Rack::ContentType.new(app).call({})[1]
26
+ headers.to_a.select { |k,v| k.downcase == "content-type" }.
27
+ should.equal [["CONTENT-Type","foo/bar"]]
28
+ end
29
+ end
@@ -0,0 +1,125 @@
1
+ require 'stringio'
2
+ require 'time' # for Time#httpdate
3
+ require 'rack/deflater'
4
+ require 'rack/mock'
5
+
6
+ describe Rack::Deflater do
7
+ def build_response(status, body, accept_encoding, headers = {})
8
+ body = [body] if body.respond_to? :to_str
9
+ app = lambda { |env| [status, {}, body] }
10
+ request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
11
+ response = Rack::Deflater.new(app).call(request)
12
+
13
+ return response
14
+ end
15
+
16
+ should "be able to deflate bodies that respond to each" do
17
+ body = Object.new
18
+ class << body; def each; yield("foo"); yield("bar"); end; end
19
+
20
+ response = build_response(200, body, "deflate")
21
+
22
+ response[0].should.equal(200)
23
+ response[1].should.equal({
24
+ "Content-Encoding" => "deflate",
25
+ "Vary" => "Accept-Encoding"
26
+ })
27
+ buf = ''
28
+ response[2].each { |part| buf << part }
29
+ buf.should.equal("K\313\317OJ,\002\000")
30
+ end
31
+
32
+ # TODO: This is really just a special case of the above...
33
+ should "be able to deflate String bodies" do
34
+ response = build_response(200, "Hello world!", "deflate")
35
+
36
+ response[0].should.equal(200)
37
+ response[1].should.equal({
38
+ "Content-Encoding" => "deflate",
39
+ "Vary" => "Accept-Encoding"
40
+ })
41
+ buf = ''
42
+ response[2].each { |part| buf << part }
43
+ buf.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
44
+ end
45
+
46
+ should "be able to gzip bodies that respond to each" do
47
+ body = Object.new
48
+ class << body; def each; yield("foo"); yield("bar"); end; end
49
+
50
+ response = build_response(200, body, "gzip")
51
+
52
+ response[0].should.equal(200)
53
+ response[1].should.equal({
54
+ "Content-Encoding" => "gzip",
55
+ "Vary" => "Accept-Encoding",
56
+ })
57
+
58
+ buf = ''
59
+ response[2].each { |part| buf << part }
60
+ io = StringIO.new(buf)
61
+ gz = Zlib::GzipReader.new(io)
62
+ gz.read.should.equal("foobar")
63
+ gz.close
64
+ end
65
+
66
+ should "be able to fallback to no deflation" do
67
+ response = build_response(200, "Hello world!", "superzip")
68
+
69
+ response[0].should.equal(200)
70
+ response[1].should.equal({ "Vary" => "Accept-Encoding" })
71
+ response[2].should.equal(["Hello world!"])
72
+ end
73
+
74
+ should "be able to skip when there is no response entity body" do
75
+ response = build_response(304, [], "gzip")
76
+
77
+ response[0].should.equal(304)
78
+ response[1].should.equal({})
79
+ response[2].should.equal([])
80
+ end
81
+
82
+ should "handle the lack of an acceptable encoding" do
83
+ response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/")
84
+ response1[0].should.equal(406)
85
+ response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"})
86
+ response1[2].should.equal(["An acceptable encoding for the requested resource / could not be found."])
87
+
88
+ response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
89
+ response2[0].should.equal(406)
90
+ response2[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "78"})
91
+ response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."])
92
+ end
93
+
94
+ should "handle gzip response with Last-Modified header" do
95
+ last_modified = Time.now.httpdate
96
+
97
+ app = lambda { |env| [200, { "Last-Modified" => last_modified }, ["Hello World!"]] }
98
+ request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
99
+ response = Rack::Deflater.new(app).call(request)
100
+
101
+ response[0].should.equal(200)
102
+ response[1].should.equal({
103
+ "Content-Encoding" => "gzip",
104
+ "Vary" => "Accept-Encoding",
105
+ "Last-Modified" => last_modified
106
+ })
107
+
108
+ buf = ''
109
+ response[2].each { |part| buf << part }
110
+ io = StringIO.new(buf)
111
+ gz = Zlib::GzipReader.new(io)
112
+ gz.read.should.equal("Hello World!")
113
+ gz.close
114
+ end
115
+
116
+ should "do nothing when no-transform Cache-Control directive present" do
117
+ app = lambda { |env| [200, {'Cache-Control' => 'no-transform'}, ['Hello World!']] }
118
+ request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
119
+ response = Rack::Deflater.new(app).call(request)
120
+
121
+ response[0].should.equal(200)
122
+ response[1].should.not.include "Content-Encoding"
123
+ response[2].join.should.equal("Hello World!")
124
+ end
125
+ end