goofy 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gems +4 -0
  3. data/.gitignore +2 -0
  4. data/CHANGELOG +47 -0
  5. data/CONTRIBUTING +19 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +23 -0
  8. data/README.md +67 -0
  9. data/app/.rspec +2 -0
  10. data/app/Gemfile +13 -0
  11. data/app/app/controllers/application_controller.rb +3 -0
  12. data/app/app/services/.keep +0 -0
  13. data/app/config.ru +4 -0
  14. data/app/config/environment.rb +9 -0
  15. data/app/config/initializers/.keep +0 -0
  16. data/app/config/routes.rb +7 -0
  17. data/app/config/settings.rb +0 -0
  18. data/app/spec/helpers.rb +2 -0
  19. data/app/spec/helpers/goofy.rb +11 -0
  20. data/app/spec/spec_helper.rb +107 -0
  21. data/benchmark/measure.rb +35 -0
  22. data/bin/check_its_goofy.rb +15 -0
  23. data/bin/goofy +61 -0
  24. data/bin/goofy_generator.rb +357 -0
  25. data/bin/goofy_instance_creator.rb +40 -0
  26. data/examples/config.ru +18 -0
  27. data/examples/measure.rb +17 -0
  28. data/examples/rack-response.ru +21 -0
  29. data/examples/views/home.mote +7 -0
  30. data/examples/views/layout.mote +11 -0
  31. data/goofy.gemspec +26 -0
  32. data/lib/goofy.rb +405 -0
  33. data/lib/goofy/capybara.rb +13 -0
  34. data/lib/goofy/controller.rb +14 -0
  35. data/lib/goofy/controller/base.rb +21 -0
  36. data/lib/goofy/controller/callbacks.rb +19 -0
  37. data/lib/goofy/render.rb +63 -0
  38. data/lib/goofy/router.rb +9 -0
  39. data/lib/goofy/safe.rb +23 -0
  40. data/lib/goofy/safe/csrf.rb +47 -0
  41. data/lib/goofy/safe/secure_headers.rb +40 -0
  42. data/lib/goofy/test.rb +11 -0
  43. data/makefile +4 -0
  44. data/test/accept.rb +32 -0
  45. data/test/captures.rb +162 -0
  46. data/test/composition.rb +69 -0
  47. data/test/controller.rb +29 -0
  48. data/test/cookie.rb +34 -0
  49. data/test/csrf.rb +139 -0
  50. data/test/extension.rb +21 -0
  51. data/test/helper.rb +11 -0
  52. data/test/host.rb +29 -0
  53. data/test/integration.rb +114 -0
  54. data/test/match.rb +86 -0
  55. data/test/middleware.rb +46 -0
  56. data/test/number.rb +36 -0
  57. data/test/on.rb +157 -0
  58. data/test/param.rb +66 -0
  59. data/test/path.rb +86 -0
  60. data/test/plugin.rb +68 -0
  61. data/test/rack.rb +22 -0
  62. data/test/redirect.rb +21 -0
  63. data/test/render.rb +128 -0
  64. data/test/root.rb +83 -0
  65. data/test/run.rb +23 -0
  66. data/test/safe.rb +74 -0
  67. data/test/segment.rb +45 -0
  68. data/test/session.rb +21 -0
  69. data/test/settings.rb +52 -0
  70. data/test/views/about.erb +1 -0
  71. data/test/views/about.str +1 -0
  72. data/test/views/content-yield.erb +1 -0
  73. data/test/views/custom/abs_path.mote +1 -0
  74. data/test/views/frag.mote +1 -0
  75. data/test/views/home.erb +2 -0
  76. data/test/views/home.mote +1 -0
  77. data/test/views/home.str +2 -0
  78. data/test/views/layout-alternative.erb +2 -0
  79. data/test/views/layout-yield.erb +3 -0
  80. data/test/views/layout.erb +2 -0
  81. data/test/views/layout.mote +2 -0
  82. data/test/views/layout.str +2 -0
  83. data/test/views/test.erb +1 -0
  84. data/test/with.rb +42 -0
  85. metadata +271 -0
@@ -0,0 +1,13 @@
1
+ require "goofy"
2
+ require "cutest"
3
+ require "capybara/dsl"
4
+
5
+ class Cutest::Scope
6
+ if defined? Capybara::DSL
7
+ include Capybara::DSL
8
+ else
9
+ include Capybara
10
+ end
11
+ end
12
+
13
+ Capybara.app = Goofy
@@ -0,0 +1,14 @@
1
+ require_relative "controller/callbacks"
2
+ require_relative "controller/base"
3
+ require_relative "router"
4
+
5
+ # include router functionality into Goofy
6
+ Goofy.include Goofy::Router
7
+
8
+ class Goofy
9
+
10
+ # Define Controller Class
11
+ class Controller
12
+ end
13
+
14
+ end
@@ -0,0 +1,21 @@
1
+ class Goofy
2
+ class Controller
3
+
4
+ def self.construct(arg)
5
+ self.new(arg).entry
6
+ end
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def response
13
+ res.write "<h3>You should define `response` instance method on your controller class!</h3>"
14
+ end
15
+
16
+ def method_missing(name, *args, &block)
17
+ @app.send(name, *args, &block)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'prong'
2
+
3
+ class Goofy
4
+ class Controller
5
+
6
+ include Prong
7
+
8
+ # Define before, around, after callbacks for #response
9
+ define_hook :response
10
+
11
+ def entry
12
+ run_hooks :response do
13
+ # Call response method with running callbacks
14
+ response
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,63 @@
1
+ require "tilt"
2
+
3
+ class Goofy
4
+ module Render
5
+ def self.setup(app)
6
+ app.settings[:render] ||= {}
7
+ app.settings[:render][:template_engine] ||= "erb"
8
+ app.settings[:render][:layout] ||= "layout"
9
+ app.settings[:render][:views] ||= File.expand_path("views", Dir.pwd)
10
+ app.settings[:render][:options] ||= {
11
+ default_encoding: Encoding.default_external
12
+ }
13
+ end
14
+
15
+ def render(template, locals = {}, layout = settings[:render][:layout])
16
+ res.headers["Content-Type"] ||= "text/html; charset=utf-8"
17
+ res.write(view(template, locals, layout))
18
+ end
19
+
20
+ def view(template, locals = {}, layout = settings[:render][:layout])
21
+ partial(layout, locals.merge(content: partial(template, locals)))
22
+ end
23
+
24
+ def partial(template, locals = {})
25
+ _render(template_path(template), locals, settings[:render][:options])
26
+ end
27
+
28
+ def template_path(template)
29
+ dir = settings[:render][:views]
30
+ ext = settings[:render][:template_engine]
31
+
32
+ return File.join(dir, "#{ template }.#{ ext }")
33
+ end
34
+
35
+ # @private Renders any type of template file supported by Tilt.
36
+ #
37
+ # @example
38
+ #
39
+ # # Renders home, and is assumed to be HAML.
40
+ # _render("home.haml")
41
+ #
42
+ # # Renders with some local variables
43
+ # _render("home.haml", site_name: "My Site")
44
+ #
45
+ # # Renders with HAML options
46
+ # _render("home.haml", {}, ugly: true, format: :html5)
47
+ #
48
+ # # Renders in layout
49
+ # _render("layout.haml") { _render("home.haml") }
50
+ #
51
+ def _render(template, locals = {}, options = {}, &block)
52
+ _cache.fetch(template) {
53
+ Tilt.new(template, 1, options.merge(outvar: '@_output'))
54
+ }.render(self, locals, &block)
55
+ end
56
+
57
+ # @private Used internally by #_render to cache the
58
+ # Tilt templates.
59
+ def _cache
60
+ Thread.current[:_cache] ||= Tilt::Cache.new
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,9 @@
1
+ class Goofy
2
+ module Router
3
+
4
+ def controller(ctrl_class)
5
+ ctrl_class.construct self
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "safe/csrf"
2
+ require_relative "safe/secure_headers"
3
+
4
+ class Goofy
5
+ # == Goofy::Safe
6
+ #
7
+ # This plugin contains security related features for Goofy
8
+ # applications. It takes ideas from secureheaders[1].
9
+ #
10
+ # == Usage
11
+ #
12
+ # require "goofy"
13
+ # require "goofy/safe"
14
+ #
15
+ # Goofy.plugin(Goofy::Safe)
16
+ #
17
+ module Safe
18
+ def self.setup(app)
19
+ app.plugin(Safe::SecureHeaders)
20
+ app.plugin(Safe::CSRF)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ class Goofy
2
+ module Safe
3
+ module CSRF
4
+ def csrf
5
+ @csrf ||= Goofy::Safe::CSRF::Helper.new(req)
6
+ end
7
+
8
+ class Helper
9
+ attr :req
10
+
11
+ def initialize(req)
12
+ @req = req
13
+ end
14
+
15
+ def token
16
+ session[:csrf_token] ||= SecureRandom.base64(32)
17
+ end
18
+
19
+ def reset!
20
+ session.delete(:csrf_token)
21
+ end
22
+
23
+ def safe?
24
+ return req.get? || req.head? ||
25
+ req[:csrf_token] == token ||
26
+ req.env["HTTP_X_CSRF_TOKEN"] == token
27
+ end
28
+
29
+ def unsafe?
30
+ return !safe?
31
+ end
32
+
33
+ def form_tag
34
+ return %Q(<input type="hidden" name="csrf_token" value="#{ token }">)
35
+ end
36
+
37
+ def meta_tag
38
+ return %Q(<meta name="csrf_token" content="#{ token }">)
39
+ end
40
+
41
+ def session
42
+ return req.env["rack.session"]
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,40 @@
1
+ # == Secure HTTP Headers
2
+ #
3
+ # This plugin will automatically apply several headers that are
4
+ # related to security. This includes:
5
+ #
6
+ # - HTTP Strict Transport Security (HSTS) [2].
7
+ # - X-Frame-Options [3].
8
+ # - X-XSS-Protection [4].
9
+ # - X-Content-Type-Options [5].
10
+ # - X-Download-Options [6].
11
+ # - X-Permitted-Cross-Domain-Policies [7].
12
+ #
13
+ # == References
14
+ #
15
+ # [1]: https://github.com/twitter/secureheaders
16
+ # [2]: https://tools.ietf.org/html/rfc6797
17
+ # [3]: https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02
18
+ # [4]: http://msdn.microsoft.com/en-us/library/dd565647(v=vs.85).aspx
19
+ # [5]: http://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx
20
+ # [6]: http://msdn.microsoft.com/en-us/library/ie/jj542450(v=vs.85).aspx
21
+ # [7]: https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html
22
+ #
23
+ class Goofy
24
+ module Safe
25
+ module SecureHeaders
26
+ HEADERS = {
27
+ "X-Content-Type-Options" => "nosniff",
28
+ "X-Download-Options" => "noopen",
29
+ "X-Frame-Options" => "SAMEORIGIN",
30
+ "X-Permitted-Cross-Domain-Policies" => "none",
31
+ "X-XSS-Protection" => "1; mode=block",
32
+ "Strict-Transport-Security" => "max-age=2628000"
33
+ }
34
+
35
+ def self.setup(app)
36
+ app.settings[:default_headers].merge!(HEADERS)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ require "goofy"
2
+ require "cutest"
3
+ require "rack/test"
4
+
5
+ class Cutest::Scope
6
+ include Rack::Test::Methods
7
+
8
+ def app
9
+ Goofy
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ cutest ./test/*.rb
@@ -0,0 +1,32 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ test "accept mimetypes" do
4
+ Goofy.define do
5
+ on accept("application/xml") do
6
+ res.write res["Content-Type"]
7
+ end
8
+ end
9
+
10
+ env = { "HTTP_ACCEPT" => "application/xml",
11
+ "SCRIPT_NAME" => "/", "PATH_INFO" => "/post" }
12
+
13
+ _, _, body = Goofy.call(env)
14
+
15
+ assert_response body, ["application/xml"]
16
+ end
17
+
18
+ test "tests don't fail when you don't specify an accept type" do
19
+ Goofy.define do
20
+ on accept("application/xml") do
21
+ res.write res["Content-Type"]
22
+ end
23
+
24
+ on default do
25
+ res.write "Default action"
26
+ end
27
+ end
28
+
29
+ _, _, body = Goofy.call({})
30
+
31
+ assert_response body, ["Default action"]
32
+ end
@@ -0,0 +1,162 @@
1
+ require File.expand_path("helper", File.dirname(__FILE__))
2
+
3
+ test "doesn't yield HOST" do
4
+ Goofy.define do
5
+ on host("example.com") do |*args|
6
+ res.write args.size
7
+ end
8
+ end
9
+
10
+ env = { "HTTP_HOST" => "example.com" }
11
+
12
+ _, _, resp = Goofy.call(env)
13
+
14
+ assert_response resp, ["0"]
15
+ end
16
+
17
+ test "doesn't yield the verb" do
18
+ Goofy.define do
19
+ on get do |*args|
20
+ res.write args.size
21
+ end
22
+ end
23
+
24
+ env = { "REQUEST_METHOD" => "GET" }
25
+
26
+ _, _, resp = Goofy.call(env)
27
+
28
+ assert_response resp, ["0"]
29
+ end
30
+
31
+ test "doesn't yield the path" do
32
+ Goofy.define do
33
+ on get, "home" do |*args|
34
+ res.write args.size
35
+ end
36
+ end
37
+
38
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/home",
39
+ "SCRIPT_NAME" => "/" }
40
+
41
+ _, _, resp = Goofy.call(env)
42
+
43
+ assert_response resp, ["0"]
44
+ end
45
+
46
+ test "yields the segment" do
47
+ Goofy.define do
48
+ on get, "user", :id do |id|
49
+ res.write id
50
+ end
51
+ end
52
+
53
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/user/johndoe",
54
+ "SCRIPT_NAME" => "/" }
55
+
56
+ _, _, resp = Goofy.call(env)
57
+
58
+ assert_response resp, ["johndoe"]
59
+ end
60
+
61
+ test "yields a number" do
62
+ Goofy.define do
63
+ on get, "user", :id do |id|
64
+ res.write id
65
+ end
66
+ end
67
+
68
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/user/101",
69
+ "SCRIPT_NAME" => "/" }
70
+
71
+ _, _, resp = Goofy.call(env)
72
+
73
+ assert_response resp, ["101"]
74
+ end
75
+
76
+ test "yield a file name with a matching extension" do
77
+ Goofy.define do
78
+ on get, "css", extension("css") do |file|
79
+ res.write file
80
+ end
81
+ end
82
+
83
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/css/app.css",
84
+ "SCRIPT_NAME" => "/" }
85
+
86
+ _, _, resp = Goofy.call(env)
87
+
88
+ assert_response resp, ["app"]
89
+ end
90
+
91
+ test "yields a segment per nested block" do
92
+ Goofy.define do
93
+ on :one do |one|
94
+ on :two do |two|
95
+ on :three do |three|
96
+ res.write one
97
+ res.write two
98
+ res.write three
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/one/two/three",
105
+ "SCRIPT_NAME" => "/" }
106
+
107
+ _, _, resp = Goofy.call(env)
108
+
109
+ assert_response resp, ["one", "two", "three"]
110
+ end
111
+
112
+ test "consumes a slash if needed" do
113
+ Goofy.define do
114
+ on get, "(.+\\.css)" do |file|
115
+ res.write file
116
+ end
117
+ end
118
+
119
+ env = { "REQUEST_METHOD" => "GET", "PATH_INFO" => "/foo/bar.css",
120
+ "SCRIPT_NAME" => "/" }
121
+
122
+ _, _, resp = Goofy.call(env)
123
+
124
+ assert_response resp, ["foo/bar.css"]
125
+ end
126
+
127
+ test "regex captures in string format" do
128
+ Goofy.define do
129
+ on get, "posts/(\\d+)-(.*)" do |id, slug|
130
+ res.write id
131
+ res.write slug
132
+ end
133
+ end
134
+
135
+
136
+ env = { "REQUEST_METHOD" => "GET",
137
+ "PATH_INFO" => "/posts/123-postal-service",
138
+ "SCRIPT_NAME" => "/" }
139
+
140
+ _, _, resp = Goofy.call(env)
141
+
142
+
143
+ assert_response resp, ["123", "postal-service"]
144
+ end
145
+
146
+ test "regex captures in regex format" do
147
+ Goofy.define do
148
+ on get, %r{posts/(\d+)-(.*)} do |id, slug|
149
+ res.write id
150
+ res.write slug
151
+ end
152
+ end
153
+
154
+ env = { "REQUEST_METHOD" => "GET",
155
+ "PATH_INFO" => "/posts/123-postal-service",
156
+ "SCRIPT_NAME" => "/" }
157
+
158
+ _, _, resp = Goofy.call(env)
159
+
160
+
161
+ assert_response resp, ["123", "postal-service"]
162
+ end