tynn 1.0.0.rc1 → 1.0.0.rc2
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/tynn.rb +49 -15
- data/lib/tynn/all_methods.rb +12 -10
- data/lib/tynn/environment.rb +39 -37
- data/lib/tynn/erubis.rb +10 -8
- data/lib/tynn/json.rb +36 -5
- data/lib/tynn/matchers.rb +49 -47
- data/lib/tynn/not_found.rb +12 -10
- data/lib/tynn/protection.rb +35 -6
- data/lib/tynn/render.rb +34 -42
- data/lib/tynn/request.rb +2 -0
- data/lib/tynn/response.rb +36 -2
- data/lib/tynn/secure_headers.rb +42 -40
- data/lib/tynn/session.rb +80 -78
- data/lib/tynn/ssl.rb +52 -50
- data/lib/tynn/static.rb +34 -32
- data/lib/tynn/test.rb +42 -40
- data/lib/tynn/version.rb +2 -2
- data/test/core_test.rb +0 -8
- data/test/middleware_test.rb +4 -4
- data/test/render_test.rb +1 -1
- metadata +5 -11
- data/lib/tynn/hmote.rb +0 -40
- data/lib/tynn/json_parser.rb +0 -36
- data/test/hmote_test.rb +0 -78
- data/test/json_parser_test.rb +0 -22
data/lib/tynn/ssl.rb
CHANGED
@@ -1,71 +1,73 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
class Middleware # :nodoc:
|
7
|
-
def initialize(app, hsts: {})
|
8
|
-
@app = app
|
9
|
-
@hsts_header = build_hsts_header(hsts)
|
1
|
+
class Tynn
|
2
|
+
module SSL
|
3
|
+
def self.setup(app, hsts: {}) # :nodoc:
|
4
|
+
app.use(Tynn::SSL::Middleware, hsts: hsts)
|
10
5
|
end
|
11
6
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
return [301, redirect_headers(request), []]
|
7
|
+
class Middleware # :nodoc:
|
8
|
+
def initialize(app, hsts: {})
|
9
|
+
@app = app
|
10
|
+
@hsts_header = build_hsts_header(hsts)
|
17
11
|
end
|
18
12
|
|
19
|
-
|
13
|
+
def call(env)
|
14
|
+
request = Rack::Request.new(env)
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
return result
|
25
|
-
end
|
16
|
+
unless request.ssl?
|
17
|
+
return [301, redirect_headers(request), []]
|
18
|
+
end
|
26
19
|
|
27
|
-
|
20
|
+
result = @app.call(env)
|
28
21
|
|
29
|
-
|
30
|
-
|
22
|
+
set_hsts_header(result[1])
|
23
|
+
set_cookies_as_secure(result[1])
|
31
24
|
|
32
|
-
|
33
|
-
|
34
|
-
header << "; includeSubdomains" if options.fetch(:subdomains, true)
|
35
|
-
header << "; preload" if options[:preload]
|
25
|
+
return result
|
26
|
+
end
|
36
27
|
|
37
|
-
|
38
|
-
end
|
28
|
+
private
|
39
29
|
|
40
|
-
|
41
|
-
|
42
|
-
"Content-Type" => "text/html",
|
43
|
-
"Location" => https_location(request)
|
44
|
-
}
|
45
|
-
end
|
30
|
+
HSTS_HEADER = "Strict-Transport-Security".freeze
|
31
|
+
HSTS_EXPIRE = 15_552_000 # 180 days
|
46
32
|
|
47
|
-
|
33
|
+
def build_hsts_header(options)
|
34
|
+
header = sprintf("max-age=%i", options.fetch(:expires, HSTS_EXPIRE))
|
35
|
+
header << "; includeSubdomains" if options.fetch(:subdomains, true)
|
36
|
+
header << "; preload" if options[:preload]
|
48
37
|
|
49
|
-
|
50
|
-
|
51
|
-
end
|
38
|
+
return header
|
39
|
+
end
|
52
40
|
|
53
|
-
|
54
|
-
|
55
|
-
|
41
|
+
def redirect_headers(request)
|
42
|
+
return {
|
43
|
+
"Content-Type" => "text/html",
|
44
|
+
"Location" => https_location(request)
|
45
|
+
}
|
46
|
+
end
|
56
47
|
|
57
|
-
|
58
|
-
COOKIE_SEPARATOR = "\n".freeze
|
59
|
-
COOKIE_REGEXP = /;\s*secure\s*(;|$)/i
|
48
|
+
HTTPS_LOCATION = "https://%s%s".freeze
|
60
49
|
|
61
|
-
|
62
|
-
|
50
|
+
def https_location(request)
|
51
|
+
return sprintf(HTTPS_LOCATION, request.host, request.fullpath)
|
52
|
+
end
|
63
53
|
|
64
|
-
|
65
|
-
|
54
|
+
def set_hsts_header(headers)
|
55
|
+
headers[HSTS_HEADER] = @hsts_header
|
66
56
|
end
|
67
57
|
|
68
|
-
|
58
|
+
COOKIE_HEADER = "Set-Cookie".freeze
|
59
|
+
COOKIE_SEPARATOR = "\n".freeze
|
60
|
+
COOKIE_REGEXP = /;\s*secure\s*(;|$)/i
|
61
|
+
|
62
|
+
def set_cookies_as_secure(headers)
|
63
|
+
return unless cookies = headers[COOKIE_HEADER]
|
64
|
+
|
65
|
+
cookies = cookies.split(COOKIE_SEPARATOR).map do |cookie|
|
66
|
+
(cookie !~ COOKIE_REGEXP) ? "#{ cookie }; secure" : cookie
|
67
|
+
end
|
68
|
+
|
69
|
+
headers[COOKIE_HEADER] = cookies.join(COOKIE_SEPARATOR)
|
70
|
+
end
|
69
71
|
end
|
70
72
|
end
|
71
73
|
end
|
data/lib/tynn/static.rb
CHANGED
@@ -1,36 +1,38 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# require "tynn
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
options =
|
1
|
+
class Tynn
|
2
|
+
# Adds support for static files (javascript files, images, stylesheets, etc).
|
3
|
+
#
|
4
|
+
# ```
|
5
|
+
# require "tynn"
|
6
|
+
# require "tynn/static"
|
7
|
+
#
|
8
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"])
|
9
|
+
# ```
|
10
|
+
#
|
11
|
+
# By default, serve all requests beginning with the given paths from the folder
|
12
|
+
# `public` in the current directory (e.g. `public/js/*`, `public/css/*`). You
|
13
|
+
# can change the default by passing the `:root` option.
|
14
|
+
#
|
15
|
+
# ```
|
16
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"], root: "assets")
|
17
|
+
# ```
|
18
|
+
#
|
19
|
+
# Under the hood, it uses the [Rack::Static][rack-static] middleware.
|
20
|
+
# Thus, supports all the options available for this middleware.
|
21
|
+
#
|
22
|
+
# ```
|
23
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"], index: "index.html")
|
24
|
+
# ```
|
25
|
+
#
|
26
|
+
# [rack-static]: http://www.rubydoc.info/gems/rack/Rack/Static
|
27
|
+
#
|
28
|
+
module Static
|
29
|
+
def self.setup(app, urls, options = {}) # :nodoc:
|
30
|
+
options = options.dup
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
options[:urls] ||= urls
|
33
|
+
options[:root] ||= File.expand_path("public", Dir.pwd)
|
33
34
|
|
34
|
-
|
35
|
+
app.use(Rack::Static, options)
|
36
|
+
end
|
35
37
|
end
|
36
38
|
end
|
data/lib/tynn/test.rb
CHANGED
@@ -1,51 +1,53 @@
|
|
1
1
|
require "rack/test"
|
2
2
|
|
3
|
-
|
4
|
-
# to
|
5
|
-
#
|
6
|
-
# ```
|
7
|
-
# require "tynn"
|
8
|
-
# require "tynn/test"
|
9
|
-
#
|
10
|
-
# Tynn.define do
|
11
|
-
# root do
|
12
|
-
# res.write("hei")
|
13
|
-
# end
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# app = Tynn::Test.new
|
17
|
-
# app.get("/")
|
18
|
-
#
|
19
|
-
# 200 == app.res.status # => true
|
20
|
-
# "hei" == app.res.body # => true
|
21
|
-
# ```
|
22
|
-
#
|
23
|
-
# **NOTE:** Tynn doesn't ship with [rack-test][rack-test]. In order to
|
24
|
-
# use this plugin, you need to install it first.
|
25
|
-
#
|
26
|
-
# [rack-test]: http://rubygems.org/gems/rack-test
|
27
|
-
#
|
28
|
-
class Tynn::Test
|
29
|
-
include Rack::Test::Methods
|
30
|
-
|
31
|
-
# Instantiates a new Tynn::Test object with the given `application` to test.
|
3
|
+
class Tynn
|
4
|
+
# A simple helper class that uses [rack-test][rack-test] to simulate requests
|
5
|
+
# to your application.
|
32
6
|
#
|
33
7
|
# ```
|
34
|
-
#
|
8
|
+
# require "tynn"
|
9
|
+
# require "tynn/test"
|
10
|
+
#
|
11
|
+
# Tynn.define do
|
12
|
+
# root do
|
13
|
+
# res.write("hei")
|
14
|
+
# end
|
35
15
|
# end
|
36
16
|
#
|
37
|
-
# app = Tynn::Test.new
|
38
|
-
# app.get("/
|
17
|
+
# app = Tynn::Test.new
|
18
|
+
# app.get("/")
|
19
|
+
#
|
20
|
+
# 200 == app.res.status # => true
|
21
|
+
# "hei" == app.res.body # => true
|
39
22
|
# ```
|
40
23
|
#
|
41
|
-
|
42
|
-
|
43
|
-
|
24
|
+
# **NOTE:** Tynn doesn't ship with [rack-test][rack-test]. In order to
|
25
|
+
# use this plugin, you need to install it first.
|
26
|
+
#
|
27
|
+
# [rack-test]: http://rubygems.org/gems/rack-test
|
28
|
+
#
|
29
|
+
class Test
|
30
|
+
include Rack::Test::Methods
|
44
31
|
|
45
|
-
|
46
|
-
|
47
|
-
|
32
|
+
# Instantiates a new Tynn::Test object with the given `application` to test.
|
33
|
+
#
|
34
|
+
# ```
|
35
|
+
# class API < Tynn
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# app = Tynn::Test.new(API)
|
39
|
+
# app.get("/json")
|
40
|
+
# ```
|
41
|
+
#
|
42
|
+
def initialize(application = Tynn)
|
43
|
+
@app = application
|
44
|
+
end
|
48
45
|
|
49
|
-
|
50
|
-
|
46
|
+
def app # :nodoc:
|
47
|
+
return @app
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :res, :last_response
|
51
|
+
alias_method :req, :last_request
|
52
|
+
end
|
51
53
|
end
|
data/lib/tynn/version.rb
CHANGED
data/test/core_test.rb
CHANGED
data/test/middleware_test.rb
CHANGED
@@ -54,16 +54,16 @@ test "middleware with composition" do
|
|
54
54
|
end
|
55
55
|
|
56
56
|
test "middleware only in child application" do
|
57
|
+
class API < Tynn
|
58
|
+
use(Shrimp)
|
59
|
+
end
|
60
|
+
|
57
61
|
Tynn.define do
|
58
62
|
on "api" do
|
59
63
|
run(API)
|
60
64
|
end
|
61
65
|
end
|
62
66
|
|
63
|
-
class API < Tynn
|
64
|
-
use(Shrimp)
|
65
|
-
end
|
66
|
-
|
67
67
|
API.define do
|
68
68
|
get do
|
69
69
|
res.write("1")
|
data/test/render_test.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tynn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Francesco Rodríguez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -122,7 +122,7 @@ dependencies:
|
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '2.0'
|
125
|
-
description:
|
125
|
+
description: Thin library to create web applications
|
126
126
|
email:
|
127
127
|
- frodsan@protonmail.ch
|
128
128
|
executables: []
|
@@ -135,9 +135,7 @@ files:
|
|
135
135
|
- lib/tynn/all_methods.rb
|
136
136
|
- lib/tynn/environment.rb
|
137
137
|
- lib/tynn/erubis.rb
|
138
|
-
- lib/tynn/hmote.rb
|
139
138
|
- lib/tynn/json.rb
|
140
|
-
- lib/tynn/json_parser.rb
|
141
139
|
- lib/tynn/matchers.rb
|
142
140
|
- lib/tynn/not_found.rb
|
143
141
|
- lib/tynn/protection.rb
|
@@ -155,8 +153,6 @@ files:
|
|
155
153
|
- test/environment_test.rb
|
156
154
|
- test/erubis_test.rb
|
157
155
|
- test/helper.rb
|
158
|
-
- test/hmote_test.rb
|
159
|
-
- test/json_parser_test.rb
|
160
156
|
- test/json_test.rb
|
161
157
|
- test/matchers_test.rb
|
162
158
|
- test/middleware_test.rb
|
@@ -167,7 +163,7 @@ files:
|
|
167
163
|
- test/session_test.rb
|
168
164
|
- test/ssl_test.rb
|
169
165
|
- test/static_test.rb
|
170
|
-
homepage: https://github.com/
|
166
|
+
homepage: https://github.com/frodsan/tynn
|
171
167
|
licenses:
|
172
168
|
- MIT
|
173
169
|
metadata: {}
|
@@ -190,15 +186,13 @@ rubyforge_project:
|
|
190
186
|
rubygems_version: 2.4.8
|
191
187
|
signing_key:
|
192
188
|
specification_version: 4
|
193
|
-
summary:
|
189
|
+
summary: Thin library to create web applications
|
194
190
|
test_files:
|
195
191
|
- test/all_methods_test.rb
|
196
192
|
- test/core_test.rb
|
197
193
|
- test/environment_test.rb
|
198
194
|
- test/erubis_test.rb
|
199
195
|
- test/helper.rb
|
200
|
-
- test/hmote_test.rb
|
201
|
-
- test/json_parser_test.rb
|
202
196
|
- test/json_test.rb
|
203
197
|
- test/matchers_test.rb
|
204
198
|
- test/middleware_test.rb
|
data/lib/tynn/hmote.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
require "hmote"
|
2
|
-
|
3
|
-
module Tynn::HMote
|
4
|
-
include ::HMote::Helpers
|
5
|
-
|
6
|
-
def self.setup(app, options = {}) # :nodoc:
|
7
|
-
options = options.dup
|
8
|
-
|
9
|
-
options[:layout] ||= "layout"
|
10
|
-
options[:views] ||= File.expand_path("views", Dir.pwd)
|
11
|
-
|
12
|
-
app.settings[:hmote] ||= options
|
13
|
-
end
|
14
|
-
|
15
|
-
module ClassMethods
|
16
|
-
def layout(layout)
|
17
|
-
settings[:hmote][:layout] = layout
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def render(template, locals = {}, layout = settings[:hmote][:layout])
|
22
|
-
res.headers[Rack::CONTENT_TYPE] ||= Syro::Response::DEFAULT
|
23
|
-
|
24
|
-
res.write(view(template, locals, layout))
|
25
|
-
end
|
26
|
-
|
27
|
-
def view(template, locals = {}, layout = settings[:hmote][:layout])
|
28
|
-
return partial(layout, locals.merge(content: partial(template, locals)))
|
29
|
-
end
|
30
|
-
|
31
|
-
def partial(template, locals = {})
|
32
|
-
return hmote(template_path(template), locals.merge(app: self), TOPLEVEL_BINDING)
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def template_path(template)
|
38
|
-
return File.join(settings[:hmote][:views], "#{ template }.mote")
|
39
|
-
end
|
40
|
-
end
|