rack-secure_only 0.4.1 → 0.5.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.
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +27 -0
- data/README.md +95 -0
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/rack/secure_only.rb +17 -0
- data/rack-secure_only.gemspec +9 -4
- data/spec/rack/secure_only/request_spec.rb +1 -1
- data/spec/rack/secure_only_spec.rb +103 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +1 -0
- metadata +27 -9
- data/README.rdoc +0 -87
data/.gitignore
CHANGED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rack-secure_only (0.5.0)
|
5
|
+
rack (>= 1.1.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
mocha (0.9.8)
|
11
|
+
rake
|
12
|
+
rack (1.2.1)
|
13
|
+
rack-test (0.5.4)
|
14
|
+
rack (>= 1.0)
|
15
|
+
rake (0.8.7)
|
16
|
+
rspec (1.3.0)
|
17
|
+
|
18
|
+
PLATFORMS
|
19
|
+
java
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
mocha (>= 0.9.8)
|
24
|
+
rack (>= 1.1.0)
|
25
|
+
rack-secure_only!
|
26
|
+
rack-test (>= 0.5.3)
|
27
|
+
rspec (>= 1.2.9)
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# rack-secure_only
|
2
|
+
|
3
|
+
SecureOnly will redirect to https if the request is on http.
|
4
|
+
|
5
|
+
When passed :secure => false it will do the opposite and redirect https to http.
|
6
|
+
|
7
|
+
The check if the current request is on https includes checking the HTTP_X_FORWARDED_PROTO header.
|
8
|
+
This means the redirect will also work on heroku.com
|
9
|
+
|
10
|
+
This can be disabled by setting the :use_http_x_forwarded_proto option to false.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
sudo gem install rack-secure_only
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
require 'rack-secure_only'
|
19
|
+
|
20
|
+
app = Rack::Builder.new do
|
21
|
+
map '/secure' do
|
22
|
+
use Rack::SecureOnly
|
23
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
24
|
+
end
|
25
|
+
|
26
|
+
map '/notsecure' do
|
27
|
+
use Rack::SecureOnly, :secure => false
|
28
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["NON SECURE APP"]] }
|
29
|
+
end
|
30
|
+
|
31
|
+
map '/secure_without_http_x_forwarded_proto_check' do
|
32
|
+
use Rack::SecureOnly, :use_http_x_forwarded_proto => false
|
33
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
34
|
+
end
|
35
|
+
|
36
|
+
map '/secure_with_fixed_redirect_url' do
|
37
|
+
use Rack::SecureOnly, :redirect_to => "https://my.site.org/login"
|
38
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
39
|
+
end
|
40
|
+
|
41
|
+
map '/secure_with_an_if_condition' do
|
42
|
+
use Rack::SecureOnly, :if => ENV['RACK_ENV'] == 'production'
|
43
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
44
|
+
end
|
45
|
+
|
46
|
+
map '/secure_with_an_if_condition_block' do
|
47
|
+
use Rack::SecureOnly, :if => Proc.new { |request| request.params.key?('secure_thing') }
|
48
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["APP"]] }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
run app
|
53
|
+
|
54
|
+
This will redirect all requests to /secure to https and all requests to /notsecure to http.
|
55
|
+
|
56
|
+
### Rack::Request
|
57
|
+
|
58
|
+
When rack-secure_only is required the Rack::Request will be extended with some convenience methods
|
59
|
+
to determine if the current request is http or https
|
60
|
+
|
61
|
+
require 'rack-secure_only'
|
62
|
+
|
63
|
+
run lambda { |env|
|
64
|
+
req = Request.new(env)
|
65
|
+
|
66
|
+
res_body = ""
|
67
|
+
|
68
|
+
if req.https?
|
69
|
+
res_body = "You just made a request on https"
|
70
|
+
elsif req.http?
|
71
|
+
res_body = "You just made a request on http"
|
72
|
+
elsif req.https?(false) # do not check the HTTP_X_FORWARDED_PROTO header
|
73
|
+
res_body = "You just made a request on a url with scheme https"
|
74
|
+
elsif req.http?(false) # do not check the HTTP_X_FORWARDED_PROTO header
|
75
|
+
res_body = "You just made a request on a url with scheme http, I did not check the HTTP_X_FORWARDED_PROTO header"
|
76
|
+
end
|
77
|
+
|
78
|
+
res_body << " and the HTTP_X_FORWARDED_PROTO header was set to" + req.forwarded_proto
|
79
|
+
|
80
|
+
[200, { 'Content-Type' => 'text/plain' }, res_body]
|
81
|
+
}
|
82
|
+
|
83
|
+
## Note on Patches/Pull Requests
|
84
|
+
|
85
|
+
* Fork the project.
|
86
|
+
* Make your feature addition or bug fix.
|
87
|
+
* Add tests for it. This is important so I don't break it in a
|
88
|
+
future version unintentionally.
|
89
|
+
* Commit, do not mess with rakefile, version, or history.
|
90
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
91
|
+
* Send me a pull request. Bonus points for topic branches.
|
92
|
+
|
93
|
+
## Copyright
|
94
|
+
|
95
|
+
Copyright (c) 2010 Klaas Speller. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -12,6 +12,7 @@ begin
|
|
12
12
|
gem.authors = ["Klaas Speller"]
|
13
13
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
14
|
gem.add_development_dependency "rack-test", ">= 0.5.3"
|
15
|
+
gem.add_development_dependency "mocha", ">= 0.9.8"
|
15
16
|
gem.add_dependency "rack", ">= 1.1.0"
|
16
17
|
end
|
17
18
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/lib/rack/secure_only.rb
CHANGED
@@ -49,11 +49,28 @@ module Rack
|
|
49
49
|
!secure?
|
50
50
|
end
|
51
51
|
|
52
|
+
# Returns false if the current request should
|
53
|
+
# not be handled by the middleware
|
54
|
+
#
|
55
|
+
def handle?(req)
|
56
|
+
if @opts.key?(:if)
|
57
|
+
cond = @opts[:if]
|
58
|
+
cond = cond.call(req) if cond.respond_to?(:call)
|
59
|
+
return cond
|
60
|
+
end
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
52
64
|
protected
|
53
65
|
|
54
66
|
def redirect?(env)
|
55
67
|
req = Request.new(env)
|
56
68
|
url = @opts[:redirect_to] || req.url
|
69
|
+
|
70
|
+
# Determine if the middleware should handle this request
|
71
|
+
return [false, req.url] unless handle?(req)
|
72
|
+
|
73
|
+
# Determine http(s) behavior
|
57
74
|
if secure? && req.http?(@opts[:use_http_x_forwarded_proto])
|
58
75
|
return [true, url.gsub(/^http:/,'https:')]
|
59
76
|
elsif not_secure? && req.https?(@opts[:use_http_x_forwarded_proto])
|
data/rack-secure_only.gemspec
CHANGED
@@ -5,22 +5,24 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{rack-secure_only}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.5.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Klaas Speller"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-09-10}
|
13
13
|
s.description = %q{Redirect http to https and the other way around}
|
14
14
|
s.email = %q{klaasspeller@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE",
|
17
|
-
"README.
|
17
|
+
"README.md"
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
21
|
".gitignore",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
22
24
|
"LICENSE",
|
23
|
-
"README.
|
25
|
+
"README.md",
|
24
26
|
"Rakefile",
|
25
27
|
"VERSION",
|
26
28
|
"lib/rack-secure_only.rb",
|
@@ -50,15 +52,18 @@ Gem::Specification.new do |s|
|
|
50
52
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
53
|
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
52
54
|
s.add_development_dependency(%q<rack-test>, [">= 0.5.3"])
|
55
|
+
s.add_development_dependency(%q<mocha>, [">= 0.9.8"])
|
53
56
|
s.add_runtime_dependency(%q<rack>, [">= 1.1.0"])
|
54
57
|
else
|
55
58
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
56
59
|
s.add_dependency(%q<rack-test>, [">= 0.5.3"])
|
60
|
+
s.add_dependency(%q<mocha>, [">= 0.9.8"])
|
57
61
|
s.add_dependency(%q<rack>, [">= 1.1.0"])
|
58
62
|
end
|
59
63
|
else
|
60
64
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
61
65
|
s.add_dependency(%q<rack-test>, [">= 0.5.3"])
|
66
|
+
s.add_dependency(%q<mocha>, [">= 0.9.8"])
|
62
67
|
s.add_dependency(%q<rack>, [">= 1.1.0"])
|
63
68
|
end
|
64
69
|
end
|
@@ -187,5 +187,108 @@ describe Rack::SecureOnly do
|
|
187
187
|
|
188
188
|
@response.location.should == "https://www.example.com/"
|
189
189
|
end
|
190
|
+
|
191
|
+
it "should not redirect when :if is false" do
|
192
|
+
app = Rack::Builder.new do
|
193
|
+
use Rack::SecureOnly, :if => false
|
194
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
195
|
+
end
|
196
|
+
@request = Rack::MockRequest.new(app)
|
197
|
+
@response = @request.get('http://www.example.com/')
|
198
|
+
|
199
|
+
@response.location.should be_nil
|
200
|
+
@response.status.should == 200
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should redirect when :if is true" do
|
204
|
+
app = Rack::Builder.new do
|
205
|
+
use Rack::SecureOnly, :if => true
|
206
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
207
|
+
end
|
208
|
+
@request = Rack::MockRequest.new(app)
|
209
|
+
@response = @request.get('http://www.example.com/')
|
210
|
+
|
211
|
+
@response.location.should_not be_nil
|
212
|
+
@response.status.should == 301
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should evaluate a block if it is passed to :if" do
|
216
|
+
app = Rack::Builder.new do
|
217
|
+
use Rack::SecureOnly, :if => Proc.new { |request| false }
|
218
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
219
|
+
end
|
220
|
+
@request = Rack::MockRequest.new(app)
|
221
|
+
@response = @request.get('http://www.example.com/')
|
222
|
+
|
223
|
+
@response.location.should be_nil
|
224
|
+
@response.status.should == 200
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should pass a request object to an :if block" do
|
228
|
+
handled = false
|
229
|
+
app = Rack::Builder.new do
|
230
|
+
use Rack::SecureOnly, :if => Proc.new { |request|
|
231
|
+
handled = true
|
232
|
+
request.class.should == Rack::Request
|
233
|
+
true
|
234
|
+
}
|
235
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
236
|
+
end
|
237
|
+
|
238
|
+
@request = Rack::MockRequest.new(app)
|
239
|
+
@response = @request.get('http://www.example.com/')
|
240
|
+
|
241
|
+
# sanity
|
242
|
+
handled.should == true
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should evaluate an :if block on a per request bases" do
|
246
|
+
app = Rack::Builder.new do
|
247
|
+
use Rack::SecureOnly, :if => lambda { |request| request.params.key?('do_it') }
|
248
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
249
|
+
end
|
250
|
+
|
251
|
+
@request = Rack::MockRequest.new(app)
|
252
|
+
@response = @request.get('http://www.example.com/')
|
253
|
+
@response.location.should be_nil
|
254
|
+
|
255
|
+
@request = Rack::MockRequest.new(app)
|
256
|
+
@response = @request.get('http://www.example.com/?do_it=true')
|
257
|
+
@response.location.should_not be_nil
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
describe "README examples" do
|
262
|
+
|
263
|
+
|
264
|
+
it "works for /secure_with_an_if_condition_block" do
|
265
|
+
app = Rack::Builder.new do
|
266
|
+
map '/secure_with_an_if_condition' do
|
267
|
+
use Rack::SecureOnly, :if => ENV['RACK_ENV'] == 'production'
|
268
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
@request = Rack::MockRequest.new(app)
|
273
|
+
@response = @request.get('http://www.example.com/secure_with_an_if_condition')
|
274
|
+
@response.location.should be_nil
|
275
|
+
end
|
276
|
+
|
277
|
+
it "works for /secure_with_an_if_condition_block" do
|
278
|
+
app = Rack::Builder.new do
|
279
|
+
map '/secure_with_an_if_condition_block' do
|
280
|
+
use Rack::SecureOnly, :if => Proc.new { |request| request.params.key?('secure_thing') }
|
281
|
+
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["APP"]] }
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
@request = Rack::MockRequest.new(app)
|
286
|
+
@response = @request.get('http://www.example.com/secure_with_an_if_condition_block')
|
287
|
+
@response.location.should be_nil
|
288
|
+
|
289
|
+
@request = Rack::MockRequest.new(app)
|
290
|
+
@response = @request.get('http://www.example.com/secure_with_an_if_condition_block?secure_thing=true')
|
291
|
+
@response.location.should_not be_nil
|
292
|
+
end
|
190
293
|
end
|
191
294
|
end
|
data/spec/spec.opts
CHANGED
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-secure_only
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 5
|
9
|
+
- 0
|
10
|
+
version: 0.5.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Klaas Speller
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-09-10 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -51,9 +51,25 @@ dependencies:
|
|
51
51
|
type: :development
|
52
52
|
version_requirements: *id002
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
|
-
name:
|
54
|
+
name: mocha
|
55
55
|
prerelease: false
|
56
56
|
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 43
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
- 9
|
65
|
+
- 8
|
66
|
+
version: 0.9.8
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rack
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
57
73
|
none: false
|
58
74
|
requirements:
|
59
75
|
- - ">="
|
@@ -65,7 +81,7 @@ dependencies:
|
|
65
81
|
- 0
|
66
82
|
version: 1.1.0
|
67
83
|
type: :runtime
|
68
|
-
version_requirements: *
|
84
|
+
version_requirements: *id004
|
69
85
|
description: Redirect http to https and the other way around
|
70
86
|
email: klaasspeller@gmail.com
|
71
87
|
executables: []
|
@@ -74,12 +90,14 @@ extensions: []
|
|
74
90
|
|
75
91
|
extra_rdoc_files:
|
76
92
|
- LICENSE
|
77
|
-
- README.
|
93
|
+
- README.md
|
78
94
|
files:
|
79
95
|
- .document
|
80
96
|
- .gitignore
|
97
|
+
- Gemfile
|
98
|
+
- Gemfile.lock
|
81
99
|
- LICENSE
|
82
|
-
- README.
|
100
|
+
- README.md
|
83
101
|
- Rakefile
|
84
102
|
- VERSION
|
85
103
|
- lib/rack-secure_only.rb
|
data/README.rdoc
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
= rack-secure_only
|
2
|
-
|
3
|
-
SecureOnly will redirect to https if the request is on http.
|
4
|
-
|
5
|
-
When passed :secure => false it will do the opposite and redirect https to http.
|
6
|
-
|
7
|
-
The check if the current request is on https includes checking the HTTP_X_FORWARDED_PROTO header.
|
8
|
-
This means the redirect will also work on heroku.com
|
9
|
-
|
10
|
-
This can be disabled by setting the :use_http_x_forwarded_proto option to false.
|
11
|
-
|
12
|
-
It is currently only tested on ruby 1.9
|
13
|
-
|
14
|
-
== Installation
|
15
|
-
|
16
|
-
sudo gem install rack-secure_only
|
17
|
-
|
18
|
-
== Usage
|
19
|
-
|
20
|
-
require 'rack-secure_only'
|
21
|
-
|
22
|
-
app = Rack::Builder.new do
|
23
|
-
map '/secure' do
|
24
|
-
use Rack::SecureOnly
|
25
|
-
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
26
|
-
end
|
27
|
-
|
28
|
-
map '/notsecure' do
|
29
|
-
use Rack::SecureOnly, :secure => false
|
30
|
-
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["NON SECURE APP"]] }
|
31
|
-
end
|
32
|
-
|
33
|
-
map '/secure_without_http_x_forwarded_proto_check' do
|
34
|
-
use Rack::SecureOnly, :use_http_x_forwarded_proto => false
|
35
|
-
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
36
|
-
end
|
37
|
-
|
38
|
-
map '/secure_with_fixed_redirect_url' do
|
39
|
-
use Rack::SecureOnly, :redirect_to => "https://my.site.org/login"
|
40
|
-
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ["SECURE APP"]] }
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
run app
|
45
|
-
|
46
|
-
This will redirect all requests to /secure to https and all requests to /notsecure to http.
|
47
|
-
|
48
|
-
=== Rack::Request
|
49
|
-
|
50
|
-
When rack-secure_only is required the Rack::Request will be extended with some convenience methods
|
51
|
-
to determine if the current request is http or https
|
52
|
-
|
53
|
-
require 'rack-secure_only'
|
54
|
-
|
55
|
-
run lambda { |env|
|
56
|
-
req = Request.new(env)
|
57
|
-
|
58
|
-
res_body = ""
|
59
|
-
|
60
|
-
if req.https?
|
61
|
-
res_body = "You just made a request on https"
|
62
|
-
elsif req.http?
|
63
|
-
res_body = "You just made a request on http"
|
64
|
-
elsif req.https?(false) # do not check the HTTP_X_FORWARDED_PROTO header
|
65
|
-
res_body = "You just made a request on a url with scheme https"
|
66
|
-
elsif req.http?(false) # do not check the HTTP_X_FORWARDED_PROTO header
|
67
|
-
res_body = "You just made a request on a url with scheme http, I did not check the HTTP_X_FORWARDED_PROTO header"
|
68
|
-
end
|
69
|
-
|
70
|
-
res_body << " and the HTTP_X_FORWARDED_PROTO header was set to" + req.forwarded_proto
|
71
|
-
|
72
|
-
[200, { 'Content-Type' => 'text/plain' }, res_body]
|
73
|
-
}
|
74
|
-
|
75
|
-
== Note on Patches/Pull Requests
|
76
|
-
|
77
|
-
* Fork the project.
|
78
|
-
* Make your feature addition or bug fix.
|
79
|
-
* Add tests for it. This is important so I don't break it in a
|
80
|
-
future version unintentionally.
|
81
|
-
* Commit, do not mess with rakefile, version, or history.
|
82
|
-
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
83
|
-
* Send me a pull request. Bonus points for topic branches.
|
84
|
-
|
85
|
-
== Copyright
|
86
|
-
|
87
|
-
Copyright (c) 2010 Klaas Speller. See LICENSE for details.
|