rack-protection 1.5.3 → 2.0.0
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.
Potentially problematic release.
This version of rack-protection might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +13 -0
- data/License +4 -1
- data/README.md +16 -2
- data/Rakefile +21 -5
- data/lib/rack/protection.rb +38 -24
- data/lib/rack/protection/authenticity_token.rb +107 -6
- data/lib/rack/protection/base.rb +5 -0
- data/lib/rack/protection/content_security_policy.rb +80 -0
- data/lib/rack/protection/cookie_tossing.rb +75 -0
- data/lib/rack/protection/escaped_params.rb +2 -0
- data/lib/rack/protection/form_token.rb +1 -1
- data/lib/rack/protection/http_origin.rb +8 -0
- data/lib/rack/protection/json_csrf.rb +26 -4
- data/lib/rack/protection/remote_token.rb +1 -1
- data/lib/rack/protection/strict_transport.rb +39 -0
- data/lib/rack/protection/version.rb +1 -12
- data/lib/rack/protection/xss_header.rb +1 -1
- data/rack-protection.gemspec +11 -104
- metadata +16 -81
- data/spec/authenticity_token_spec.rb +0 -48
- data/spec/base_spec.rb +0 -40
- data/spec/escaped_params_spec.rb +0 -43
- data/spec/form_token_spec.rb +0 -33
- data/spec/frame_options_spec.rb +0 -39
- data/spec/http_origin_spec.rb +0 -38
- data/spec/ip_spoofing_spec.rb +0 -35
- data/spec/json_csrf_spec.rb +0 -58
- data/spec/path_traversal_spec.rb +0 -41
- data/spec/protection_spec.rb +0 -105
- data/spec/remote_referrer_spec.rb +0 -31
- data/spec/remote_token_spec.rb +0 -42
- data/spec/session_hijacking_spec.rb +0 -55
- data/spec/spec_helper.rb +0 -163
- data/spec/xss_header_spec.rb +0 -56
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rack/protection'
|
2
2
|
require 'rack/utils'
|
3
|
+
require 'tempfile'
|
3
4
|
|
4
5
|
begin
|
5
6
|
require 'escape_utils'
|
@@ -66,6 +67,7 @@ module Rack
|
|
66
67
|
when Hash then escape_hash(object)
|
67
68
|
when Array then object.map { |o| escape(o) }
|
68
69
|
when String then escape_string(object)
|
70
|
+
when Tempfile then object
|
69
71
|
else nil
|
70
72
|
end
|
71
73
|
end
|
@@ -13,7 +13,7 @@ module Rack
|
|
13
13
|
# This middleware is not used when using the Rack::Protection collection,
|
14
14
|
# since it might be a security issue, depending on your application
|
15
15
|
#
|
16
|
-
# Compatible with
|
16
|
+
# Compatible with rack-csrf.
|
17
17
|
class FormToken < AuthenticityToken
|
18
18
|
def accepts?(env)
|
19
19
|
env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super
|
@@ -10,9 +10,16 @@ module Rack
|
|
10
10
|
#
|
11
11
|
# Does not accept unsafe HTTP requests when value of Origin HTTP request header
|
12
12
|
# does not match default or whitelisted URIs.
|
13
|
+
#
|
14
|
+
# If you want to whitelist a specific domain, you can pass in as the `:origin_whitelist` option:
|
15
|
+
#
|
16
|
+
# use Rack::Protection, origin_whitelist: ["http://localhost:3000", "http://127.0.01:3000"]
|
17
|
+
#
|
18
|
+
# The `:allow_if` option can also be set to a proc to use custom allow/deny logic.
|
13
19
|
class HttpOrigin < Base
|
14
20
|
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
15
21
|
default_reaction :deny
|
22
|
+
default_options :allow_if => nil
|
16
23
|
|
17
24
|
def base_url(env)
|
18
25
|
request = Rack::Request.new(env)
|
@@ -24,6 +31,7 @@ module Rack
|
|
24
31
|
return true if safe? env
|
25
32
|
return true unless origin = env['HTTP_ORIGIN']
|
26
33
|
return true if base_url(env) == origin
|
34
|
+
return true if options[:allow_if] && options[:allow_if].call(env)
|
27
35
|
Array(options[:origin_whitelist]).include? origin
|
28
36
|
end
|
29
37
|
|
@@ -5,21 +5,30 @@ module Rack
|
|
5
5
|
##
|
6
6
|
# Prevented attack:: CSRF
|
7
7
|
# Supported browsers:: all
|
8
|
-
# More infos:: http://flask.pocoo.org/docs/security/#json-security
|
8
|
+
# More infos:: http://flask.pocoo.org/docs/0.10/security/#json-security
|
9
|
+
# http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
|
9
10
|
#
|
10
|
-
# JSON GET APIs are vulnerable to being embedded as JavaScript
|
11
|
+
# JSON GET APIs are vulnerable to being embedded as JavaScript when the
|
11
12
|
# Array prototype has been patched to track data. Checks the referrer
|
12
13
|
# even on GET requests if the content type is JSON.
|
14
|
+
#
|
15
|
+
# If request includes Origin HTTP header, defers to HttpOrigin to determine
|
16
|
+
# if the request is safe. Please refer to the documentation for more info.
|
17
|
+
#
|
18
|
+
# The `:allow_if` option can be set to a proc to use custom allow/deny logic.
|
13
19
|
class JsonCsrf < Base
|
20
|
+
default_options :allow_if => nil
|
21
|
+
|
14
22
|
alias react deny
|
15
23
|
|
16
24
|
def call(env)
|
17
25
|
request = Request.new(env)
|
18
26
|
status, headers, body = app.call(env)
|
19
27
|
|
20
|
-
if has_vector?
|
28
|
+
if has_vector?(request, headers)
|
21
29
|
warn env, "attack prevented by #{self.class}"
|
22
|
-
|
30
|
+
|
31
|
+
react_and_close(env, body) or [status, headers, body]
|
23
32
|
else
|
24
33
|
[status, headers, body]
|
25
34
|
end
|
@@ -27,9 +36,22 @@ module Rack
|
|
27
36
|
|
28
37
|
def has_vector?(request, headers)
|
29
38
|
return false if request.xhr?
|
39
|
+
return false if options[:allow_if] && options[:allow_if].call(request.env)
|
30
40
|
return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/
|
31
41
|
origin(request.env).nil? and referrer(request.env) != request.host
|
32
42
|
end
|
43
|
+
|
44
|
+
def react_and_close(env, body)
|
45
|
+
reaction = react(env)
|
46
|
+
|
47
|
+
close_body(body) if reaction
|
48
|
+
|
49
|
+
reaction
|
50
|
+
end
|
51
|
+
|
52
|
+
def close_body(body)
|
53
|
+
body.close if body.respond_to?(:close)
|
54
|
+
end
|
33
55
|
end
|
34
56
|
end
|
35
57
|
end
|
@@ -10,7 +10,7 @@ module Rack
|
|
10
10
|
# Only accepts unsafe HTTP requests if a given access token matches the token
|
11
11
|
# included in the session *or* the request comes from the same origin.
|
12
12
|
#
|
13
|
-
# Compatible with
|
13
|
+
# Compatible with rack-csrf.
|
14
14
|
class RemoteToken < AuthenticityToken
|
15
15
|
default_reaction :deny
|
16
16
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rack/protection'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Protection
|
5
|
+
##
|
6
|
+
# Prevented attack:: Protects against against protocol downgrade attacks and cookie hijacking.
|
7
|
+
# Supported browsers:: all
|
8
|
+
# More infos:: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
|
9
|
+
#
|
10
|
+
# browser will prevent any communications from being sent over HTTP
|
11
|
+
# to the specified domain and will instead send all communications over HTTPS.
|
12
|
+
# It also prevents HTTPS click through prompts on browsers.
|
13
|
+
#
|
14
|
+
# Options:
|
15
|
+
#
|
16
|
+
# max_age:: How long future requests to the domain should go over HTTPS; specified in seconds
|
17
|
+
# include_subdomains:: If all present and future subdomains will be HTTPS
|
18
|
+
# preload:: Allow this domain to be included in browsers HSTS preload list. See https://hstspreload.appspot.com/
|
19
|
+
|
20
|
+
class StrictTransport < Base
|
21
|
+
default_options :max_age => 31_536_000, :include_subdomains => false, :preload => false
|
22
|
+
|
23
|
+
def strict_transport
|
24
|
+
@strict_transport ||= begin
|
25
|
+
strict_transport = 'max-age=' + options[:max_age].to_s
|
26
|
+
strict_transport += '; includeSubDomains' if options[:include_subdomains]
|
27
|
+
strict_transport += '; preload' if options[:preload]
|
28
|
+
strict_transport.to_str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(env)
|
33
|
+
status, headers, body = @app.call(env)
|
34
|
+
headers['Strict-Transport-Security'] ||= strict_transport
|
35
|
+
[status, headers, body]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,16 +1,5 @@
|
|
1
1
|
module Rack
|
2
2
|
module Protection
|
3
|
-
|
4
|
-
VERSION
|
5
|
-
end
|
6
|
-
|
7
|
-
SIGNATURE = [1, 5, 3]
|
8
|
-
VERSION = SIGNATURE.join('.')
|
9
|
-
|
10
|
-
VERSION.extend Comparable
|
11
|
-
def VERSION.<=>(other)
|
12
|
-
other = other.split('.').map { |i| i.to_i } if other.respond_to? :split
|
13
|
-
SIGNATURE <=> Array(other)
|
14
|
-
end
|
3
|
+
VERSION = '2.0.0'
|
15
4
|
end
|
16
5
|
end
|
@@ -4,7 +4,7 @@ module Rack
|
|
4
4
|
module Protection
|
5
5
|
##
|
6
6
|
# Prevented attack:: Non-permanent XSS
|
7
|
-
# Supported browsers:: Internet Explorer 8 and
|
7
|
+
# Supported browsers:: Internet Explorer 8+ and Chrome
|
8
8
|
# More infos:: http://blogs.msdn.com/b/ie/archive/2008/07/01/ie8-security-part-iv-the-xss-filter.aspx
|
9
9
|
#
|
10
10
|
# Sets X-XSS-Protection header to tell the browser to block attacks.
|
data/rack-protection.gemspec
CHANGED
@@ -1,118 +1,25 @@
|
|
1
|
-
|
1
|
+
version = File.read(File.expand_path("../../VERSION", __FILE__)).strip
|
2
|
+
|
2
3
|
Gem::Specification.new do |s|
|
3
4
|
# general infos
|
4
5
|
s.name = "rack-protection"
|
5
|
-
s.version =
|
6
|
-
s.description = "
|
7
|
-
s.homepage = "http://github.com/
|
6
|
+
s.version = version
|
7
|
+
s.description = "Protect against typical web attacks, works with all Rack apps, including Rails."
|
8
|
+
s.homepage = "http://github.com/sinatra/sinatra/tree/master/rack-protection"
|
8
9
|
s.summary = s.description
|
9
10
|
s.license = 'MIT'
|
10
|
-
|
11
|
-
|
12
|
-
s.
|
13
|
-
"Konstantin Haase",
|
14
|
-
"Alex Rodionov",
|
15
|
-
"Patrick Ellis",
|
16
|
-
"Jason Staten",
|
17
|
-
"ITO Nobuaki",
|
18
|
-
"Jeff Welling",
|
19
|
-
"Matteo Centenaro",
|
20
|
-
"Egor Homakov",
|
21
|
-
"Florian Gilcher",
|
22
|
-
"Fojas",
|
23
|
-
"Igor Bochkariov",
|
24
|
-
"Mael Clerambault",
|
25
|
-
"Martin Mauch",
|
26
|
-
"Renne Nissinen",
|
27
|
-
"SAKAI, Kazuaki",
|
28
|
-
"Stanislav Savulchik",
|
29
|
-
"Steve Agalloco",
|
30
|
-
"TOBY",
|
31
|
-
"Thais Camilo and Konstantin Haase",
|
32
|
-
"Vipul A M",
|
33
|
-
"Akzhan Abdulin",
|
34
|
-
"brookemckim",
|
35
|
-
"Bj\u{f8}rge N\u{e6}ss",
|
36
|
-
"Chris Heald",
|
37
|
-
"Chris Mytton",
|
38
|
-
"Corey Ward",
|
39
|
-
"Dario Cravero",
|
40
|
-
"David Kellum"
|
41
|
-
]
|
42
|
-
|
43
|
-
# generated from git shortlog -sne
|
44
|
-
s.email = [
|
45
|
-
"konstantin.mailinglists@googlemail.com",
|
46
|
-
"p0deje@gmail.com",
|
47
|
-
"jstaten07@gmail.com",
|
48
|
-
"patrick@soundcloud.com",
|
49
|
-
"jeff.welling@gmail.com",
|
50
|
-
"bugant@gmail.com",
|
51
|
-
"daydream.trippers@gmail.com",
|
52
|
-
"florian.gilcher@asquera.de",
|
53
|
-
"developer@fojasaur.us",
|
54
|
-
"ujifgc@gmail.com",
|
55
|
-
"mael@clerambault.fr",
|
56
|
-
"martin.mauch@gmail.com",
|
57
|
-
"rennex@iki.fi",
|
58
|
-
"kaz.july.7@gmail.com",
|
59
|
-
"s.savulchik@gmail.com",
|
60
|
-
"steve.agalloco@gmail.com",
|
61
|
-
"toby.net.info.mail+git@gmail.com",
|
62
|
-
"dev+narwen+rkh@rkh.im",
|
63
|
-
"vipulnsward@gmail.com",
|
64
|
-
"akzhan.abdulin@gmail.com",
|
65
|
-
"brooke@digitalocean.com",
|
66
|
-
"bjoerge@bengler.no",
|
67
|
-
"cheald@gmail.com",
|
68
|
-
"self@hecticjeff.net",
|
69
|
-
"coreyward@me.com",
|
70
|
-
"dario@uxtemple.com",
|
71
|
-
"dek-oss@gravitext.com",
|
72
|
-
"homakov@gmail.com"
|
73
|
-
]
|
74
|
-
|
75
|
-
# generated from git ls-files
|
76
|
-
s.files = [
|
11
|
+
s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"]
|
12
|
+
s.email = "sinatrarb@googlegroups.com"
|
13
|
+
s.files = Dir["lib/**/*.rb"] + [
|
77
14
|
"License",
|
78
15
|
"README.md",
|
79
16
|
"Rakefile",
|
80
|
-
"
|
81
|
-
"
|
82
|
-
"lib/rack/protection/authenticity_token.rb",
|
83
|
-
"lib/rack/protection/base.rb",
|
84
|
-
"lib/rack/protection/escaped_params.rb",
|
85
|
-
"lib/rack/protection/form_token.rb",
|
86
|
-
"lib/rack/protection/frame_options.rb",
|
87
|
-
"lib/rack/protection/http_origin.rb",
|
88
|
-
"lib/rack/protection/ip_spoofing.rb",
|
89
|
-
"lib/rack/protection/json_csrf.rb",
|
90
|
-
"lib/rack/protection/path_traversal.rb",
|
91
|
-
"lib/rack/protection/remote_referrer.rb",
|
92
|
-
"lib/rack/protection/remote_token.rb",
|
93
|
-
"lib/rack/protection/session_hijacking.rb",
|
94
|
-
"lib/rack/protection/version.rb",
|
95
|
-
"lib/rack/protection/xss_header.rb",
|
96
|
-
"rack-protection.gemspec",
|
97
|
-
"spec/authenticity_token_spec.rb",
|
98
|
-
"spec/base_spec.rb",
|
99
|
-
"spec/escaped_params_spec.rb",
|
100
|
-
"spec/form_token_spec.rb",
|
101
|
-
"spec/frame_options_spec.rb",
|
102
|
-
"spec/http_origin_spec.rb",
|
103
|
-
"spec/ip_spoofing_spec.rb",
|
104
|
-
"spec/json_csrf_spec.rb",
|
105
|
-
"spec/path_traversal_spec.rb",
|
106
|
-
"spec/protection_spec.rb",
|
107
|
-
"spec/remote_referrer_spec.rb",
|
108
|
-
"spec/remote_token_spec.rb",
|
109
|
-
"spec/session_hijacking_spec.rb",
|
110
|
-
"spec/spec_helper.rb",
|
111
|
-
"spec/xss_header_spec.rb"
|
17
|
+
"Gemfile",
|
18
|
+
"rack-protection.gemspec"
|
112
19
|
]
|
113
20
|
|
114
21
|
# dependencies
|
115
22
|
s.add_dependency "rack"
|
116
23
|
s.add_development_dependency "rack-test"
|
117
|
-
s.add_development_dependency "rspec", "~>
|
24
|
+
s.add_development_dependency "rspec", "~> 3.0.0"
|
118
25
|
end
|
metadata
CHANGED
@@ -1,41 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-protection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
- Alex Rodionov
|
9
|
-
- Patrick Ellis
|
10
|
-
- Jason Staten
|
11
|
-
- ITO Nobuaki
|
12
|
-
- Jeff Welling
|
13
|
-
- Matteo Centenaro
|
14
|
-
- Egor Homakov
|
15
|
-
- Florian Gilcher
|
16
|
-
- Fojas
|
17
|
-
- Igor Bochkariov
|
18
|
-
- Mael Clerambault
|
19
|
-
- Martin Mauch
|
20
|
-
- Renne Nissinen
|
21
|
-
- SAKAI, Kazuaki
|
22
|
-
- Stanislav Savulchik
|
23
|
-
- Steve Agalloco
|
24
|
-
- TOBY
|
25
|
-
- Thais Camilo and Konstantin Haase
|
26
|
-
- Vipul A M
|
27
|
-
- Akzhan Abdulin
|
28
|
-
- brookemckim
|
29
|
-
- Bjørge Næss
|
30
|
-
- Chris Heald
|
31
|
-
- Chris Mytton
|
32
|
-
- Corey Ward
|
33
|
-
- Dario Cravero
|
34
|
-
- David Kellum
|
7
|
+
- https://github.com/sinatra/sinatra/graphs/contributors
|
35
8
|
autorequire:
|
36
9
|
bindir: bin
|
37
10
|
cert_chain: []
|
38
|
-
date:
|
11
|
+
date: 2017-05-07 00:00:00.000000000 Z
|
39
12
|
dependencies:
|
40
13
|
- !ruby/object:Gem::Dependency
|
41
14
|
name: rack
|
@@ -71,48 +44,22 @@ dependencies:
|
|
71
44
|
requirements:
|
72
45
|
- - "~>"
|
73
46
|
- !ruby/object:Gem::Version
|
74
|
-
version:
|
47
|
+
version: 3.0.0
|
75
48
|
type: :development
|
76
49
|
prerelease: false
|
77
50
|
version_requirements: !ruby/object:Gem::Requirement
|
78
51
|
requirements:
|
79
52
|
- - "~>"
|
80
53
|
- !ruby/object:Gem::Version
|
81
|
-
version:
|
82
|
-
description:
|
83
|
-
|
84
|
-
|
85
|
-
- p0deje@gmail.com
|
86
|
-
- jstaten07@gmail.com
|
87
|
-
- patrick@soundcloud.com
|
88
|
-
- jeff.welling@gmail.com
|
89
|
-
- bugant@gmail.com
|
90
|
-
- daydream.trippers@gmail.com
|
91
|
-
- florian.gilcher@asquera.de
|
92
|
-
- developer@fojasaur.us
|
93
|
-
- ujifgc@gmail.com
|
94
|
-
- mael@clerambault.fr
|
95
|
-
- martin.mauch@gmail.com
|
96
|
-
- rennex@iki.fi
|
97
|
-
- kaz.july.7@gmail.com
|
98
|
-
- s.savulchik@gmail.com
|
99
|
-
- steve.agalloco@gmail.com
|
100
|
-
- toby.net.info.mail+git@gmail.com
|
101
|
-
- dev+narwen+rkh@rkh.im
|
102
|
-
- vipulnsward@gmail.com
|
103
|
-
- akzhan.abdulin@gmail.com
|
104
|
-
- brooke@digitalocean.com
|
105
|
-
- bjoerge@bengler.no
|
106
|
-
- cheald@gmail.com
|
107
|
-
- self@hecticjeff.net
|
108
|
-
- coreyward@me.com
|
109
|
-
- dario@uxtemple.com
|
110
|
-
- dek-oss@gravitext.com
|
111
|
-
- homakov@gmail.com
|
54
|
+
version: 3.0.0
|
55
|
+
description: Protect against typical web attacks, works with all Rack apps, including
|
56
|
+
Rails.
|
57
|
+
email: sinatrarb@googlegroups.com
|
112
58
|
executables: []
|
113
59
|
extensions: []
|
114
60
|
extra_rdoc_files: []
|
115
61
|
files:
|
62
|
+
- Gemfile
|
116
63
|
- License
|
117
64
|
- README.md
|
118
65
|
- Rakefile
|
@@ -120,6 +67,8 @@ files:
|
|
120
67
|
- lib/rack/protection.rb
|
121
68
|
- lib/rack/protection/authenticity_token.rb
|
122
69
|
- lib/rack/protection/base.rb
|
70
|
+
- lib/rack/protection/content_security_policy.rb
|
71
|
+
- lib/rack/protection/cookie_tossing.rb
|
123
72
|
- lib/rack/protection/escaped_params.rb
|
124
73
|
- lib/rack/protection/form_token.rb
|
125
74
|
- lib/rack/protection/frame_options.rb
|
@@ -130,25 +79,11 @@ files:
|
|
130
79
|
- lib/rack/protection/remote_referrer.rb
|
131
80
|
- lib/rack/protection/remote_token.rb
|
132
81
|
- lib/rack/protection/session_hijacking.rb
|
82
|
+
- lib/rack/protection/strict_transport.rb
|
133
83
|
- lib/rack/protection/version.rb
|
134
84
|
- lib/rack/protection/xss_header.rb
|
135
85
|
- rack-protection.gemspec
|
136
|
-
|
137
|
-
- spec/base_spec.rb
|
138
|
-
- spec/escaped_params_spec.rb
|
139
|
-
- spec/form_token_spec.rb
|
140
|
-
- spec/frame_options_spec.rb
|
141
|
-
- spec/http_origin_spec.rb
|
142
|
-
- spec/ip_spoofing_spec.rb
|
143
|
-
- spec/json_csrf_spec.rb
|
144
|
-
- spec/path_traversal_spec.rb
|
145
|
-
- spec/protection_spec.rb
|
146
|
-
- spec/remote_referrer_spec.rb
|
147
|
-
- spec/remote_token_spec.rb
|
148
|
-
- spec/session_hijacking_spec.rb
|
149
|
-
- spec/spec_helper.rb
|
150
|
-
- spec/xss_header_spec.rb
|
151
|
-
homepage: http://github.com/rkh/rack-protection
|
86
|
+
homepage: http://github.com/sinatra/sinatra/tree/master/rack-protection
|
152
87
|
licenses:
|
153
88
|
- MIT
|
154
89
|
metadata: {}
|
@@ -168,9 +103,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
103
|
version: '0'
|
169
104
|
requirements: []
|
170
105
|
rubyforge_project:
|
171
|
-
rubygems_version: 2.
|
106
|
+
rubygems_version: 2.6.11
|
172
107
|
signing_key:
|
173
108
|
specification_version: 4
|
174
|
-
summary:
|
109
|
+
summary: Protect against typical web attacks, works with all Rack apps, including
|
110
|
+
Rails.
|
175
111
|
test_files: []
|
176
|
-
has_rdoc:
|