tricycle-rack-contrib 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/COPYING +18 -0
  2. data/README.rdoc +78 -0
  3. data/Rakefile +97 -0
  4. data/lib/rack/contrib.rb +37 -0
  5. data/lib/rack/contrib/accept_format.rb +46 -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/cookies.rb +50 -0
  11. data/lib/rack/contrib/csshttprequest.rb +39 -0
  12. data/lib/rack/contrib/deflect.rb +137 -0
  13. data/lib/rack/contrib/etag.rb +20 -0
  14. data/lib/rack/contrib/evil.rb +12 -0
  15. data/lib/rack/contrib/garbagecollector.rb +14 -0
  16. data/lib/rack/contrib/jsonp.rb +41 -0
  17. data/lib/rack/contrib/lighttpd_script_name_fix.rb +16 -0
  18. data/lib/rack/contrib/locale.rb +31 -0
  19. data/lib/rack/contrib/mailexceptions.rb +120 -0
  20. data/lib/rack/contrib/nested_params.rb +143 -0
  21. data/lib/rack/contrib/not_found.rb +18 -0
  22. data/lib/rack/contrib/post_body_content_type_parser.rb +40 -0
  23. data/lib/rack/contrib/proctitle.rb +30 -0
  24. data/lib/rack/contrib/profiler.rb +108 -0
  25. data/lib/rack/contrib/relative_redirect.rb +44 -0
  26. data/lib/rack/contrib/response_cache.rb +116 -0
  27. data/lib/rack/contrib/route_exceptions.rb +49 -0
  28. data/lib/rack/contrib/sendfile.rb +142 -0
  29. data/lib/rack/contrib/signals.rb +63 -0
  30. data/lib/rack/contrib/time_zone.rb +25 -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 +72 -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 +181 -0
  54. data/test/spec_rack_sendfile.rb +86 -0
  55. data/tricycle-rack-contrib.gemspec +88 -0
  56. metadata +175 -0
@@ -0,0 +1,63 @@
1
+ module Rack
2
+ # Installs signal handlers that are safely processed after a request
3
+ #
4
+ # NOTE: This middleware should not be used in a threaded environment
5
+ #
6
+ # use Rack::Signals.new do
7
+ # trap 'INT', lambda {
8
+ # puts "Exiting now"
9
+ # exit
10
+ # }
11
+ #
12
+ # trap_when_ready 'USR1', lambda {
13
+ # puts "Exiting when ready"
14
+ # exit
15
+ # }
16
+ # end
17
+ class Signals
18
+ class BodyWithCallback
19
+ def initialize(body, callback)
20
+ @body, @callback = body, callback
21
+ end
22
+
23
+ def each(&block)
24
+ @body.each(&block)
25
+ @callback.call
26
+ end
27
+ end
28
+
29
+ def initialize(app, &block)
30
+ @app = app
31
+ @processing = false
32
+ @when_ready = nil
33
+ instance_eval(&block)
34
+ end
35
+
36
+ def call(env)
37
+ begin
38
+ @processing, @when_ready = true, nil
39
+ status, headers, body = @app.call(env)
40
+
41
+ if handler = @when_ready
42
+ body = BodyWithCallback.new(body, handler)
43
+ @when_ready = nil
44
+ end
45
+ ensure
46
+ @processing = false
47
+ end
48
+
49
+ [status, headers, body]
50
+ end
51
+
52
+ def trap_when_ready(signal, handler)
53
+ when_ready_handler = lambda { |signal|
54
+ if @processing
55
+ @when_ready = lambda { handler.call(signal) }
56
+ else
57
+ handler.call(signal)
58
+ end
59
+ }
60
+ trap(signal, when_ready_handler)
61
+ end
62
+ end
63
+ end
@@ -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
data/test/404.html ADDED
@@ -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,72 @@
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
+ app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, env['PATH_INFO']] }
8
+
9
+ specify "should do nothing when a format extension is already provided" do
10
+ request = Rack::MockRequest.env_for("/resource.json")
11
+ body = Rack::AcceptFormat.new(app).call(request).last
12
+ body.should == "/resource.json"
13
+ end
14
+
15
+ context "default extention" do
16
+ specify "should allow custom default" do
17
+ request = Rack::MockRequest.env_for("/resource")
18
+ body = Rack::AcceptFormat.new(app, '.xml').call(request).last
19
+ body.should == "/resource.xml"
20
+ end
21
+
22
+ specify "should default to html" do
23
+ request = Rack::MockRequest.env_for("/resource")
24
+ body = Rack::AcceptFormat.new(app).call(request).last
25
+ body.should == "/resource.html"
26
+ end
27
+
28
+ specify "should notmalize custom extention" do
29
+ request = Rack::MockRequest.env_for("/resource")
30
+
31
+ body = Rack::AcceptFormat.new(app,'xml').call(request).last #no dot prefix
32
+ body.should == "/resource.xml"
33
+
34
+ body = Rack::AcceptFormat.new(app, :xml).call(request).last
35
+ body.should == "/resource.xml"
36
+ end
37
+ end
38
+
39
+ context "there is no format extension" do
40
+ Rack::Mime::MIME_TYPES.clear
41
+
42
+ def mime(ext, type)
43
+ ext = ".#{ext}" unless ext.to_s[0] == ?.
44
+ Rack::Mime::MIME_TYPES[ext.to_s] = type
45
+ end
46
+
47
+ specify "should add the default extension if no Accept header" do
48
+ request = Rack::MockRequest.env_for("/resource")
49
+ body = Rack::AcceptFormat.new(app).call(request).last
50
+ body.should == "/resource.html"
51
+ end
52
+
53
+ specify "should add the default extension if the Accept header is not registered in the Mime::Types" do
54
+ request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
55
+ body = Rack::AcceptFormat.new(app).call(request).last
56
+ body.should == "/resource.html"
57
+ end
58
+
59
+ specify "should add the correct extension if the Accept header is registered in the Mime::Types" do
60
+ mime :json, 'application/json'
61
+ request = Rack::MockRequest.env_for("/resource", 'HTTP_ACCEPT' => 'application/json;q=1.0, text/html;q=0.8, */*;q=0.1')
62
+ body = Rack::AcceptFormat.new(app).call(request).last
63
+ body.should == "/resource.json"
64
+ end
65
+ end
66
+
67
+ specify "shouldn't confuse extention when there are dots in path" do
68
+ request = Rack::MockRequest.env_for("/parent.resource/resource")
69
+ body = Rack::AcceptFormat.new(app, '.html').call(request).last
70
+ body.should == "/parent.resource/resource.html"
71
+ end
72
+ 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