cavalle-rack-contrib 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/COPYING +18 -0
  2. data/README.rdoc +76 -0
  3. data/Rakefile +97 -0
  4. data/lib/rack/contrib.rb +33 -0
  5. data/lib/rack/contrib/accept_format.rb +44 -0
  6. data/lib/rack/contrib/backstage.rb +20 -0
  7. data/lib/rack/contrib/bounce_favicon.rb +16 -0
  8. data/lib/rack/contrib/callbacks.rb +37 -0
  9. data/lib/rack/contrib/config.rb +16 -0
  10. data/lib/rack/contrib/csshttprequest.rb +39 -0
  11. data/lib/rack/contrib/deflect.rb +137 -0
  12. data/lib/rack/contrib/etag.rb +20 -0
  13. data/lib/rack/contrib/evil.rb +12 -0
  14. data/lib/rack/contrib/garbagecollector.rb +14 -0
  15. data/lib/rack/contrib/jsonp.rb +41 -0
  16. data/lib/rack/contrib/lighttpd_script_name_fix.rb +16 -0
  17. data/lib/rack/contrib/locale.rb +31 -0
  18. data/lib/rack/contrib/mailexceptions.rb +120 -0
  19. data/lib/rack/contrib/nested_params.rb +143 -0
  20. data/lib/rack/contrib/not_found.rb +18 -0
  21. data/lib/rack/contrib/post_body_content_type_parser.rb +40 -0
  22. data/lib/rack/contrib/proctitle.rb +30 -0
  23. data/lib/rack/contrib/profiler.rb +106 -0
  24. data/lib/rack/contrib/relative_redirect.rb +44 -0
  25. data/lib/rack/contrib/response_cache.rb +59 -0
  26. data/lib/rack/contrib/route_exceptions.rb +49 -0
  27. data/lib/rack/contrib/sendfile.rb +142 -0
  28. data/lib/rack/contrib/signals.rb +63 -0
  29. data/lib/rack/contrib/time_zone.rb +25 -0
  30. data/rack-contrib.gemspec +87 -0
  31. data/test/404.html +1 -0
  32. data/test/Maintenance.html +1 -0
  33. data/test/mail_settings.rb +12 -0
  34. data/test/spec_rack_accept_format.rb +41 -0
  35. data/test/spec_rack_backstage.rb +26 -0
  36. data/test/spec_rack_callbacks.rb +65 -0
  37. data/test/spec_rack_config.rb +22 -0
  38. data/test/spec_rack_contrib.rb +8 -0
  39. data/test/spec_rack_csshttprequest.rb +66 -0
  40. data/test/spec_rack_deflect.rb +107 -0
  41. data/test/spec_rack_etag.rb +23 -0
  42. data/test/spec_rack_evil.rb +19 -0
  43. data/test/spec_rack_garbagecollector.rb +13 -0
  44. data/test/spec_rack_jsonp.rb +34 -0
  45. data/test/spec_rack_lighttpd_script_name_fix.rb +16 -0
  46. data/test/spec_rack_mailexceptions.rb +97 -0
  47. data/test/spec_rack_nested_params.rb +46 -0
  48. data/test/spec_rack_not_found.rb +17 -0
  49. data/test/spec_rack_post_body_content_type_parser.rb +32 -0
  50. data/test/spec_rack_proctitle.rb +26 -0
  51. data/test/spec_rack_profiler.rb +37 -0
  52. data/test/spec_rack_relative_redirect.rb +78 -0
  53. data/test/spec_rack_response_cache.rb +137 -0
  54. data/test/spec_rack_sendfile.rb +86 -0
  55. metadata +171 -0
@@ -0,0 +1,25 @@
1
+ module Rack
2
+ class TimeZone
3
+ Javascript = <<-EOJ
4
+ function setTimezoneCookie() {
5
+ var offset = (new Date()).getTimezoneOffset()
6
+ var date = new Date();
7
+ date.setTime(date.getTime()+3600000);
8
+ document.cookie = "utc_offset="+offset+"; expires="+date.toGMTString();+"; path=/";
9
+ }
10
+ EOJ
11
+
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ def call(env)
17
+ request = Rack::Request.new(env)
18
+ if utc_offset = request.cookies["utc_offset"]
19
+ env["rack.timezone.utc_offset"] = -(utc_offset.to_i * 60)
20
+ end
21
+
22
+ @app.call(env)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,87 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+
5
+ s.name = 'rack-contrib'
6
+ s.version = '1.0.0'
7
+ s.date = '2009-03-07'
8
+
9
+ s.description = "Contributed Rack Middleware and Utilities"
10
+ s.summary = "Contributed Rack Middleware and Utilities"
11
+
12
+ s.authors = ["rack-devel"]
13
+ s.email = "rack-devel@googlegroups.com"
14
+
15
+ # = MANIFEST =
16
+ s.files = %w[
17
+ COPYING
18
+ README.rdoc
19
+ Rakefile
20
+ lib/rack/contrib.rb
21
+ lib/rack/contrib/accept_format.rb
22
+ lib/rack/contrib/backstage.rb
23
+ lib/rack/contrib/bounce_favicon.rb
24
+ lib/rack/contrib/callbacks.rb
25
+ lib/rack/contrib/config.rb
26
+ lib/rack/contrib/csshttprequest.rb
27
+ lib/rack/contrib/deflect.rb
28
+ lib/rack/contrib/etag.rb
29
+ lib/rack/contrib/evil.rb
30
+ lib/rack/contrib/garbagecollector.rb
31
+ lib/rack/contrib/jsonp.rb
32
+ lib/rack/contrib/lighttpd_script_name_fix.rb
33
+ lib/rack/contrib/locale.rb
34
+ lib/rack/contrib/mailexceptions.rb
35
+ lib/rack/contrib/nested_params.rb
36
+ lib/rack/contrib/not_found.rb
37
+ lib/rack/contrib/post_body_content_type_parser.rb
38
+ lib/rack/contrib/proctitle.rb
39
+ lib/rack/contrib/profiler.rb
40
+ lib/rack/contrib/relative_redirect.rb
41
+ lib/rack/contrib/response_cache.rb
42
+ lib/rack/contrib/route_exceptions.rb
43
+ lib/rack/contrib/sendfile.rb
44
+ lib/rack/contrib/signals.rb
45
+ lib/rack/contrib/time_zone.rb
46
+ rack-contrib.gemspec
47
+ test/404.html
48
+ test/Maintenance.html
49
+ test/mail_settings.rb
50
+ test/spec_rack_accept_format.rb
51
+ test/spec_rack_backstage.rb
52
+ test/spec_rack_callbacks.rb
53
+ test/spec_rack_config.rb
54
+ test/spec_rack_contrib.rb
55
+ test/spec_rack_csshttprequest.rb
56
+ test/spec_rack_deflect.rb
57
+ test/spec_rack_etag.rb
58
+ test/spec_rack_evil.rb
59
+ test/spec_rack_garbagecollector.rb
60
+ test/spec_rack_jsonp.rb
61
+ test/spec_rack_lighttpd_script_name_fix.rb
62
+ test/spec_rack_mailexceptions.rb
63
+ test/spec_rack_nested_params.rb
64
+ test/spec_rack_not_found.rb
65
+ test/spec_rack_post_body_content_type_parser.rb
66
+ test/spec_rack_proctitle.rb
67
+ test/spec_rack_profiler.rb
68
+ test/spec_rack_relative_redirect.rb
69
+ test/spec_rack_response_cache.rb
70
+ test/spec_rack_sendfile.rb
71
+ ]
72
+ # = MANIFEST =
73
+
74
+ s.test_files = s.files.select {|path| path =~ /^test\/spec_.*\.rb/}
75
+
76
+ s.extra_rdoc_files = %w[README.rdoc COPYING]
77
+ s.add_dependency 'rack', '~> 1.0.0'
78
+ s.add_dependency 'test-spec', '~> 0.9.0'
79
+ s.add_development_dependency 'tmail', '>= 1.2'
80
+ s.add_development_dependency 'json', '>= 1.1'
81
+
82
+ s.has_rdoc = true
83
+ s.homepage = "http://github.com/rack/rack-contrib/"
84
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "rack-contrib", "--main", "README"]
85
+ s.require_paths = %w[lib]
86
+ s.rubygems_version = '1.1.1'
87
+ end
@@ -0,0 +1 @@
1
+ Not Found
@@ -0,0 +1 @@
1
+ Under maintenance.
@@ -0,0 +1,12 @@
1
+ TEST_SMTP = nil
2
+
3
+ # Enable SMTP tests by providing the following for your SMTP server.
4
+ #
5
+ # TEST_SMTP = {
6
+ # :server => 'localhost',
7
+ # :domain => 'localhost',
8
+ # :port => 25,
9
+ # :authentication => :login,
10
+ # :user_name => nil,
11
+ # :password => nil
12
+ # }
@@ -0,0 +1,41 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/accept_format'
4
+ require 'rack/mime'
5
+
6
+ context "Rack::AcceptFormat" do
7
+ specify "should do nothing when a format extension is already provided" do
8
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, env['PATH_INFO']] }
9
+ request = Rack::MockRequest.env_for("/resource.json")
10
+ body = Rack::AcceptFormat.new(app).call(request).last
11
+ body.should == "/resource.json"
12
+ end
13
+
14
+ context "there is no format extension" do
15
+ Rack::Mime::MIME_TYPES.clear
16
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, env['PATH_INFO']] }
17
+ def mime(ext, type)
18
+ ext = ".#{ext}" unless ext.to_s[0] == ?.
19
+ Rack::Mime::MIME_TYPES[ext.to_s] = type
20
+ end
21
+
22
+ specify "should add the default extension if no Accept header" do
23
+ request = Rack::MockRequest.env_for("/resource")
24
+ body = Rack::AcceptFormat.new(app).call(request).last
25
+ body.should == "/resource#{Rack::AcceptFormat::DEFAULT_EXTENSION}"
26
+ end
27
+
28
+ specify "should add the default extension if the Accept header is not registered in the Mime::Types" do
29
+ request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
30
+ body = Rack::AcceptFormat.new(app).call(request).last
31
+ body.should == "/resource#{Rack::AcceptFormat::DEFAULT_EXTENSION}"
32
+ end
33
+
34
+ specify "should add the correct extension if the Accept header is registered in the Mime::Types" do
35
+ mime :json, 'application/json'
36
+ request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
37
+ body = Rack::AcceptFormat.new(app).call(request).last
38
+ body.should == "/resource.json"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ require 'test/spec'
2
+ require 'rack/builder'
3
+ require 'rack/mock'
4
+ require 'rack/contrib/backstage'
5
+
6
+ context "Rack::Backstage" do
7
+ specify "shows maintenances page if present" do
8
+ app = Rack::Builder.new do
9
+ use Rack::Backstage, 'test/Maintenance.html'
10
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
11
+ end
12
+ response = Rack::MockRequest.new(app).get('/')
13
+ response.body.should.equal('Under maintenance.')
14
+ response.status.should.equal(503)
15
+ end
16
+
17
+ specify "passes on request if page is not present" do
18
+ app = Rack::Builder.new do
19
+ use Rack::Backstage, 'test/Nonsense.html'
20
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
21
+ end
22
+ response = Rack::MockRequest.new(app).get('/')
23
+ response.body.should.equal('Hello, World!')
24
+ response.status.should.equal(200)
25
+ end
26
+ end
@@ -0,0 +1,65 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+
4
+ class Flame
5
+ def call(env)
6
+ env['flame'] = 'F Lifo..'
7
+ end
8
+ end
9
+
10
+ class Pacify
11
+ def initialize(with)
12
+ @with = with
13
+ end
14
+
15
+ def call(env)
16
+ env['peace'] = @with
17
+ end
18
+ end
19
+
20
+ class Finale
21
+ def call(response)
22
+ status, headers, body = response
23
+
24
+ headers['last'] = 'Finale'
25
+ $old_status = status
26
+
27
+ [201, headers, body]
28
+ end
29
+ end
30
+
31
+ class TheEnd
32
+ def call(response)
33
+ status, headers, body = response
34
+
35
+ headers['last'] = 'TheEnd'
36
+ [201, headers, body]
37
+ end
38
+ end
39
+
40
+ context "Rack::Callbacks" do
41
+ specify "works for love and small stack trace" do
42
+ callback_app = Rack::Callbacks.new do
43
+ before Flame
44
+ before Pacify, "with love"
45
+
46
+ run lambda {|env| [200, {}, [env['flame'], env['peace']]] }
47
+
48
+ after Finale
49
+ after TheEnd
50
+ end
51
+
52
+ app = Rack::Builder.new do
53
+ run callback_app
54
+ end.to_app
55
+
56
+ response = Rack::MockRequest.new(app).get("/")
57
+
58
+ response.body.should.equal 'F Lifo..with love'
59
+
60
+ $old_status.should.equal 200
61
+ response.status.should.equal 201
62
+
63
+ response.headers['last'].should.equal 'TheEnd'
64
+ end
65
+ end
@@ -0,0 +1,22 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/config'
4
+
5
+ context "Rack::Config" do
6
+
7
+ specify "should accept a block that modifies the environment" do
8
+ app = Rack::Builder.new do
9
+ use Rack::Lint
10
+ use Rack::ContentLength
11
+ use Rack::Config do |env|
12
+ env['greeting'] = 'hello'
13
+ end
14
+ run lambda { |env|
15
+ [200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
16
+ }
17
+ end
18
+ response = Rack::MockRequest.new(app).get('/')
19
+ response.body.should.equal('hello')
20
+ end
21
+
22
+ end
@@ -0,0 +1,8 @@
1
+ require 'test/spec'
2
+ require 'rack/contrib'
3
+
4
+ context "Rack::Contrib" do
5
+ specify "should expose release" do
6
+ Rack::Contrib.should.respond_to :release
7
+ end
8
+ end
@@ -0,0 +1,66 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+
4
+ begin
5
+ require 'csshttprequest'
6
+ require 'rack/contrib/csshttprequest'
7
+
8
+ context "Rack::CSSHTTPRequest" do
9
+
10
+ before(:each) do
11
+ @test_body = '{"bar":"foo"}'
12
+ @test_headers = {'Content-Type' => 'text/plain'}
13
+ @encoded_body = CSSHTTPRequest.encode(@test_body)
14
+ @app = lambda { |env| [200, @test_headers, [@test_body]] }
15
+ end
16
+
17
+ specify "env['csshttprequest.chr'] should be set to true when \
18
+ PATH_INFO ends with '.chr'" do
19
+ request = Rack::MockRequest.env_for("/blah.chr", :lint => true, :fatal => true)
20
+ Rack::CSSHTTPRequest.new(@app).call(request)
21
+ request['csshttprequest.chr'].should.equal true
22
+ end
23
+
24
+ specify "env['csshttprequest.chr'] should be set to true when \
25
+ request parameter _format == 'chr'" do
26
+ request = Rack::MockRequest.env_for("/?_format=chr", :lint => true, :fatal => true)
27
+ Rack::CSSHTTPRequest.new(@app).call(request)
28
+ request['csshttprequest.chr'].should.equal true
29
+ end
30
+
31
+ specify "should not change the headers or response when !env['csshttprequest.chr']" do
32
+ request = Rack::MockRequest.env_for("/", :lint => true, :fatal => true)
33
+ status, headers, response = Rack::CSSHTTPRequest.new(@app).call(request)
34
+ headers.should.equal @test_headers
35
+ response.join.should.equal @test_body
36
+ end
37
+
38
+ context "when env['csshttprequest.chr']" do
39
+ before(:each) do
40
+ @request = Rack::MockRequest.env_for("/",
41
+ 'csshttprequest.chr' => true, :lint => true, :fatal => true)
42
+ end
43
+
44
+ specify "should modify the content length to the correct value" do
45
+ headers = Rack::CSSHTTPRequest.new(@app).call(@request)[1]
46
+ headers['Content-Length'].should.equal @encoded_body.length.to_s
47
+ end
48
+
49
+ specify "should modify the content type to the correct value" do
50
+ headers = Rack::CSSHTTPRequest.new(@app).call(@request)[1]
51
+ headers['Content-Type'].should.equal 'text/css'
52
+ end
53
+
54
+ specify "should not modify any other headers" do
55
+ headers = Rack::CSSHTTPRequest.new(@app).call(@request)[1]
56
+ headers.should.equal @test_headers.merge({
57
+ 'Content-Type' => 'text/css',
58
+ 'Content-Length' => @encoded_body.length.to_s
59
+ })
60
+ end
61
+ end
62
+
63
+ end
64
+ rescue LoadError => boom
65
+ STDERR.puts "WARN: Skipping Rack::CSSHTTPRequest tests (nbio-csshttprequest not installed)"
66
+ end
@@ -0,0 +1,107 @@
1
+ require 'test/spec'
2
+ require 'rack/mock'
3
+ require 'rack/contrib/deflect'
4
+
5
+ context "Rack::Deflect" do
6
+
7
+ setup do
8
+ @app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, 'cookies'] }
9
+ @mock_addr_1 = '111.111.111.111'
10
+ @mock_addr_2 = '222.222.222.222'
11
+ @mock_addr_3 = '333.333.333.333'
12
+ end
13
+
14
+ def mock_env remote_addr, path = '/'
15
+ Rack::MockRequest.env_for path, { 'REMOTE_ADDR' => remote_addr }
16
+ end
17
+
18
+ def mock_deflect options = {}
19
+ Rack::Deflect.new @app, options
20
+ end
21
+
22
+ specify "should allow regular requests to follow through" do
23
+ app = mock_deflect
24
+ status, headers, body = app.call mock_env(@mock_addr_1)
25
+ status.should.equal 200
26
+ body.should.equal 'cookies'
27
+ end
28
+
29
+ specify "should deflect requests exceeding the request threshold" do
30
+ log = StringIO.new
31
+ app = mock_deflect :request_threshold => 5, :interval => 10, :block_duration => 10, :log => log
32
+ env = mock_env @mock_addr_1
33
+
34
+ # First 5 should be fine
35
+ 5.times do
36
+ status, headers, body = app.call env
37
+ status.should.equal 200
38
+ body.should.equal 'cookies'
39
+ end
40
+
41
+ # Remaining requests should fail for 10 seconds
42
+ 10.times do
43
+ status, headers, body = app.call env
44
+ status.should.equal 403
45
+ body.should.equal ''
46
+ end
47
+
48
+ # Log should reflect that we have blocked an address
49
+ log.string.should.match(/^deflect\(\d+\/\d+\/\d+\): blocked 111.111.111.111\n/)
50
+ end
51
+
52
+ specify "should expire blocking" do
53
+ log = StringIO.new
54
+ app = mock_deflect :request_threshold => 5, :interval => 2, :block_duration => 2, :log => log
55
+ env = mock_env @mock_addr_1
56
+
57
+ # First 5 should be fine
58
+ 5.times do
59
+ status, headers, body = app.call env
60
+ status.should.equal 200
61
+ body.should.equal 'cookies'
62
+ end
63
+
64
+ # Exceeds request threshold
65
+ status, headers, body = app.call env
66
+ status.should.equal 403
67
+ body.should.equal ''
68
+
69
+ # Allow block to expire
70
+ sleep 3
71
+
72
+ # Another 5 is fine now
73
+ 5.times do
74
+ status, headers, body = app.call env
75
+ status.should.equal 200
76
+ body.should.equal 'cookies'
77
+ end
78
+
79
+ # Log should reflect block and release
80
+ log.string.should.match(/deflect.*: blocked 111\.111\.111\.111\ndeflect.*: released 111\.111\.111\.111\n/)
81
+ end
82
+
83
+ specify "should allow whitelisting of remote addresses" do
84
+ app = mock_deflect :whitelist => [@mock_addr_1], :request_threshold => 5, :interval => 2
85
+ env = mock_env @mock_addr_1
86
+
87
+ # Whitelisted addresses are always fine
88
+ 10.times do
89
+ status, headers, body = app.call env
90
+ status.should.equal 200
91
+ body.should.equal 'cookies'
92
+ end
93
+ end
94
+
95
+ specify "should allow blacklisting of remote addresses" do
96
+ app = mock_deflect :blacklist => [@mock_addr_2]
97
+
98
+ status, headers, body = app.call mock_env(@mock_addr_1)
99
+ status.should.equal 200
100
+ body.should.equal 'cookies'
101
+
102
+ status, headers, body = app.call mock_env(@mock_addr_2)
103
+ status.should.equal 403
104
+ body.should.equal ''
105
+ end
106
+
107
+ end