rack-protection-maximum_cookie 0.4.2
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 +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +24 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +186 -0
- data/Rakefile +10 -0
- data/bin/console +7 -0
- data/bin/setup +6 -0
- data/lib/rack-protection-maximum_cookie.rb +2 -0
- data/lib/rack/protection/maximum_cookie.rb +259 -0
- data/lib/rack/protection/maximum_cookie/version.rb +8 -0
- data/rack-protection-maximum_cookie.gemspec +32 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e0047e3e925b373963124935290d165fe6ace97a
|
4
|
+
data.tar.gz: 260e3fc72618ce92fd0e0bb9b880945bb754eee6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7027cac6bc57aeb935ecbd7e2cda7cddea6a71c19339edce7090d57fc08396be7f6758357b6b314982a49b1ee3dc8e03c41fa7dd7ab6e447130e167f2597bfb5
|
7
|
+
data.tar.gz: ee7e315fcc2652327321244d62e4de4797daa553b8a1698e202aa9a455203c9831338a311027a9c74a03eb10765d35b8c673a810915978e0fcbac7558c48bc00
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- ruby-head
|
5
|
+
- 2.4.2
|
6
|
+
- 2.3.5
|
7
|
+
- 2.2.8
|
8
|
+
- 2.1.10
|
9
|
+
- jruby-head
|
10
|
+
- jruby-9.1.13.0
|
11
|
+
- jruby-9.0.5.0
|
12
|
+
env:
|
13
|
+
- RACK_VERSION='~> 2.0.0'
|
14
|
+
- RACK_VERSION='~> 1.6.8'
|
15
|
+
- RACK_VERSION='~> 1.5.5'
|
16
|
+
- RACK_VERSION='~> 1.4.7'
|
17
|
+
before_install: gem install bundler -v 1.15.4
|
18
|
+
matrix:
|
19
|
+
allow_failures:
|
20
|
+
- rvm: ruby-head
|
21
|
+
- rvm: jruby-head
|
22
|
+
exclude:
|
23
|
+
- rvm: 2.1.10
|
24
|
+
env: RACK_VERSION='~> 2.0.0'
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Mike Pastore
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
# Rack::Protection::MaximumCookie
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/rack-protection-maximum_cookie)
|
4
|
+
[](https://travis-ci.org/mwpastore/rack-protection-maximum_cookie)
|
5
|
+
|
6
|
+
Some bugs in Rack may cause cookies to be silently dropped—or worse,
|
7
|
+
truncated, leading to token leakage (i.e. transmission of a private session
|
8
|
+
over a insecure connection), cross-site scripting vulnerabilities, and/or
|
9
|
+
cross-site request forgery vulnerabilities. This gem provides a middleware that
|
10
|
+
tries to prevent these scenarios from occurring.
|
11
|
+
|
12
|
+
### Caveats
|
13
|
+
|
14
|
+
1. Most modern browsers no longer have a [per-domain cookie size limit][1],
|
15
|
+
so if you only care about modern browsers, go ahead and set `:per_domain?`
|
16
|
+
to false. You'll benefit from the per-domain cookie limit and per-cookie
|
17
|
+
bytesize limit (since most browsers still have some form of these limits,
|
18
|
+
and Rack's built-in check for them is nonexistent in the former case and
|
19
|
+
not implemented correctly in the latter).
|
20
|
+
|
21
|
+
2. Browsers that do have per-domain cookie limits enforce these limits
|
22
|
+
client-side, cumulatively over time. If you were to set a new cookie, every
|
23
|
+
few minutes, one at a time, eventually you'd hit a limit. Or another
|
24
|
+
service could be setting cookies for the domain your service listens on. If
|
25
|
+
this is a concern for your app, see the `:stateful?` option below.
|
26
|
+
|
27
|
+
In practice, your service is most likely setting the same cookies, for the
|
28
|
+
same domains, every response, all in the same response, and it knows about
|
29
|
+
all the cookies being set, so the default behavior of this gem should be
|
30
|
+
appropriate in those cases.
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
Add this line to your app's Gemfile:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
gem 'rack-protection-maximum_cookie'
|
38
|
+
```
|
39
|
+
|
40
|
+
And then execute:
|
41
|
+
|
42
|
+
```console
|
43
|
+
$ bundle
|
44
|
+
```
|
45
|
+
|
46
|
+
Or install it yourself as:
|
47
|
+
|
48
|
+
```console
|
49
|
+
$ gem install rack-protection-maximum_cookie
|
50
|
+
```
|
51
|
+
|
52
|
+
## Usage
|
53
|
+
|
54
|
+
Add this statement to your Rackup script or middleware-capable Rack app such as
|
55
|
+
Sinatra:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
use Rack::Protection::MaximumCookie
|
59
|
+
```
|
60
|
+
|
61
|
+
Or Rails:
|
62
|
+
|
63
|
+
> TODO: Somebody please tell me how to insert this and before or after what in
|
64
|
+
> Rails' middleware stack.
|
65
|
+
|
66
|
+
This middleware raises exceptions, so you'll want to use an error-reporting
|
67
|
+
service like Sentry, Rollbar, or Airbrake, and insert this middleware after it.
|
68
|
+
|
69
|
+
### Advanced
|
70
|
+
|
71
|
+
Rack::Protection::MaximumCookie accepts the following `use`-time options:
|
72
|
+
|
73
|
+
* `:limit` *Integer*
|
74
|
+
|
75
|
+
Maximum number of cookies per domain. **50 cookies by default.** Set to a
|
76
|
+
negative number to disable.
|
77
|
+
|
78
|
+
* `:bytesize_limit` *Integer*
|
79
|
+
|
80
|
+
Maximum size—in bytes—of cookies per domain (if `:per_domain?` is
|
81
|
+
set to true), or the maximum size of each cookie (if `:per_domain?` is set to
|
82
|
+
false). **4,096 bytes by default.** Set to a negative number to disable.
|
83
|
+
|
84
|
+
* `:overhead` *Integer*
|
85
|
+
|
86
|
+
Overhead—in bytes—per cookie. **Three (3) bytes by default.** Set to
|
87
|
+
zero to disable.
|
88
|
+
|
89
|
+
* `:per_domain?` *Boolean*
|
90
|
+
|
91
|
+
If true, apply the bytesize limit (e.g. 4,096 bytes—minus any
|
92
|
+
per-cookie overhead) per domain. **This is the default behavior.**
|
93
|
+
|
94
|
+
If false, apply the bytesize limit (e.g. 4,096 bytes—minus any
|
95
|
+
overhead) per cookie.
|
96
|
+
|
97
|
+
* `:strict?` *Boolean*
|
98
|
+
|
99
|
+
If false, each sub-domain gets its own quota, separate from its second-level
|
100
|
+
domain. **This is the default behavior.**
|
101
|
+
|
102
|
+
If true, `:per_domain?` is forced to true, and each second-level domain's
|
103
|
+
cookies count towards its sub-domains' quotas. For example, if you have
|
104
|
+
cookies for example<i></i>.org totaling 4,000 bytes, you wouldn't be able to
|
105
|
+
set an additional 100-byte cookie on foo.example<i></i>.org in the same
|
106
|
+
request.
|
107
|
+
|
108
|
+
* `:stateful?` *Boolean*
|
109
|
+
|
110
|
+
If false, the cookies are evaluated for each response in isolation, i.e.
|
111
|
+
statelessly. **This is the default behavior.**
|
112
|
+
|
113
|
+
If true, `:strict?` is forced to true, and Rack::Protection::MaximumCookie
|
114
|
+
will attempt to compensate for cookies that were set for the domain in
|
115
|
+
previous responses (by this or other web services) but are not present in the
|
116
|
+
current response.
|
117
|
+
|
118
|
+
> This mechanism has its limits. First of all, browsers don't send cookie
|
119
|
+
> directives in request headers—only key=value pairs—so in order
|
120
|
+
> to compute the actual size of these cookies,
|
121
|
+
> Rack::Protection::MaximumCookie must estimate the upper bound of the
|
122
|
+
> bytesize of the directives in the original Set-Cookie headers. This
|
123
|
+
> *should* work reasonably well as long as your app doesn't rely on any
|
124
|
+
> non-standard directives or other quirky browser behavior.
|
125
|
+
>
|
126
|
+
> Secondly, browsers won't send cookies if their paths don't match the
|
127
|
+
> request path. This means that if you're setting two cookies, one for
|
128
|
+
> example<i></i>.org/foo and one for example<i></i>.org/bar, requests to
|
129
|
+
> either path won't include the other's cookie. That defeats this mechanism,
|
130
|
+
> as both cookies count toward example<i></i>.org's quota, but they aren't
|
131
|
+
> able to be properly accounted for in the request. The only "workaround" is
|
132
|
+
> to architect your app such that all cookies are set under a path common to
|
133
|
+
> all routes. Unfortunately, this may lead to other security issues.
|
134
|
+
>
|
135
|
+
> This is an ugly wart on HTTP and the reason why modern browsers don't have
|
136
|
+
> per-domain cookie size limits and drop cookies instead of truncating them.
|
137
|
+
|
138
|
+
#### Block Argument
|
139
|
+
|
140
|
+
If you don't want to raise exceptions, only want to raise exceptions under
|
141
|
+
certain conditions, or want to customize the exception, you can modify the
|
142
|
+
behavior by passing a block to the middleware initializer:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
use Rack::Protection::MaximumCookie do |env|
|
146
|
+
raise MyCustomError, 'Someone broke the cookie jar!' if env['foo.bar']
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
If the block returns a truthy value, the default exception will be raised:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
use(Rack::Protection::MaximumCookie) { |env| env['foo.bar'] }
|
154
|
+
```
|
155
|
+
|
156
|
+
Keep in mind that the block receives the mutated *response* `env`.
|
157
|
+
|
158
|
+
I'm interested in hearing use-cases for this feature and I'm open to passing
|
159
|
+
additional arguments to the block. Open a new issue to document and discuss.
|
160
|
+
|
161
|
+
## Development
|
162
|
+
|
163
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
164
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
165
|
+
prompt that will allow you to experiment.
|
166
|
+
|
167
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
168
|
+
release a new version, update the version number in `version.rb`, and then run
|
169
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
170
|
+
git commits and tags, and push the `.gem` file to
|
171
|
+
[rubygems.org](https://rubygems.org).
|
172
|
+
|
173
|
+
## Contributing
|
174
|
+
|
175
|
+
Bug reports and pull requests are welcome on GitHub at
|
176
|
+
https://github.com/mwpastore/rack-protection-maximum_cookie.
|
177
|
+
|
178
|
+
Please let me know if I've made any invalid assumptions or conclusions about
|
179
|
+
the HTTP specification or older browser behavior.
|
180
|
+
|
181
|
+
## License
|
182
|
+
|
183
|
+
The gem is available as open source under the terms of the [MIT
|
184
|
+
License](http://opensource.org/licenses/MIT).
|
185
|
+
|
186
|
+
[1]: http://browsercookielimits.squawky.net
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rack/protection/maximum_cookie/version'
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
require 'public_suffix'
|
6
|
+
require 'rack'
|
7
|
+
require 'rack/request'
|
8
|
+
require 'resolv'
|
9
|
+
require 'set'
|
10
|
+
|
11
|
+
module Rack
|
12
|
+
SET_COOKIE = 'Set-Cookie'.freeze unless defined?(SET_COOKIE)
|
13
|
+
|
14
|
+
class Request
|
15
|
+
unless method_defined?(:hostname)
|
16
|
+
# TODO: Submit a PR to add this to Rack::Request a la URI#host vs. URI#hostname.
|
17
|
+
def hostname
|
18
|
+
host = host()
|
19
|
+
host[/\A\[([^\]]+)\]\z/, 1] || host
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Protection
|
25
|
+
class MaximumCookie
|
26
|
+
HEADER_SEP_RE = %r{\r?\n|\0}.freeze
|
27
|
+
COOKIE_DOMAIN_RE = %r{;\s*[Dd][Oo][Mm][Aa][Ii][Nn]=([^;]+)}.freeze
|
28
|
+
COOKIE_KEY_RE = %r{\A[^=]+}.freeze
|
29
|
+
|
30
|
+
attr_reader :app
|
31
|
+
attr_reader :handler
|
32
|
+
attr_reader :public_suffix_list
|
33
|
+
|
34
|
+
def limit
|
35
|
+
@options[:limit]
|
36
|
+
end
|
37
|
+
|
38
|
+
def limit?
|
39
|
+
@options[:limit] >= 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def bytesize_limit
|
43
|
+
@options[:bytesize_limit]
|
44
|
+
end
|
45
|
+
|
46
|
+
def bytesize_limit?
|
47
|
+
@options[:bytesize_limit] >= 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def overhead
|
51
|
+
@options[:overhead]
|
52
|
+
end
|
53
|
+
|
54
|
+
def per_domain?
|
55
|
+
@options[:per_domain?]
|
56
|
+
end
|
57
|
+
|
58
|
+
def strict?
|
59
|
+
@options[:strict?]
|
60
|
+
end
|
61
|
+
|
62
|
+
def stateful?
|
63
|
+
@options[:stateful?]
|
64
|
+
end
|
65
|
+
|
66
|
+
def initialize(app, options={}, &block)
|
67
|
+
@app = app
|
68
|
+
@handler = block
|
69
|
+
|
70
|
+
@options = {}.tap do |h|
|
71
|
+
h[:limit] = Integer(options.fetch(:limit, 50))
|
72
|
+
h[:bytesize_limit] = Integer(options.fetch(:bytesize_limit, 4_096))
|
73
|
+
h[:overhead] = Integer(options.fetch(:overhead, 3))
|
74
|
+
h[:stateful?] = !!options.fetch(:stateful?, options.fetch(:stateful, false))
|
75
|
+
h[:strict?] = h[:stateful?] || !!options.fetch(:strict?, options.fetch(:strict, false))
|
76
|
+
h[:per_domain?] = h[:strict?] || !!options.fetch(:per_domain?, options.fetch(:per_domain, true))
|
77
|
+
|
78
|
+
h.freeze
|
79
|
+
end
|
80
|
+
|
81
|
+
if strict?
|
82
|
+
# Allow non-ICANN domains to be handled the same as ICANN domains.
|
83
|
+
@public_suffix_list = PublicSuffix::List.parse(::File.read(PublicSuffix::List::DEFAULT_LIST_PATH), :private_domains=>false)
|
84
|
+
end
|
85
|
+
|
86
|
+
unless limit? || bytesize_limit?
|
87
|
+
abort 'No limits, nothing to do!'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def call(env)
|
92
|
+
status, headers, body = app.call(env)
|
93
|
+
if headers.key?(SET_COOKIE)
|
94
|
+
check_cookies env, Rack::Request.new(env),
|
95
|
+
normalize_cookie_header(headers[SET_COOKIE])
|
96
|
+
end
|
97
|
+
[status, headers, body]
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def check_cookies(env, request, cookies)
|
103
|
+
default_subdomain = foldcase(request.hostname)
|
104
|
+
|
105
|
+
keys = Hash.new { |h, k| h[k] = Set.new } if stateful?
|
106
|
+
count = Hash.new { |h, k| h[k] = 0 }
|
107
|
+
bytesize = Hash.new { |h, k| h[k] = 0 } if per_domain?
|
108
|
+
|
109
|
+
cookies.each do |cookie|
|
110
|
+
# TODO: Skip "delete" cookies?
|
111
|
+
|
112
|
+
if (subdomain = cookie[COOKIE_DOMAIN_RE, 1])
|
113
|
+
foldcase!(subdomain)
|
114
|
+
else
|
115
|
+
subdomain = default_subdomain
|
116
|
+
end
|
117
|
+
|
118
|
+
keys[subdomain] << cookie[COOKIE_KEY_RE] if keys
|
119
|
+
count[subdomain] += 1
|
120
|
+
bytesize[subdomain] += cookie.bytesize + overhead if bytesize
|
121
|
+
end
|
122
|
+
|
123
|
+
if stateful?
|
124
|
+
# Fold the request cookies (that aren't also present in the response)
|
125
|
+
# into our totals.
|
126
|
+
fold(request, keys) do |domain, cookie_bytesize|
|
127
|
+
count[domain] += 1
|
128
|
+
bytesize[domain] += cookie_bytesize + overhead
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
if strict?
|
133
|
+
# Add the values for each second-level domain (e.g. example.com) to
|
134
|
+
# the values for its subdomains (e.g. foo. and bar.example.com).
|
135
|
+
propogate_values(count)
|
136
|
+
propogate_values(bytesize)
|
137
|
+
end
|
138
|
+
|
139
|
+
check_limit_per_domain(env, count, limit) if limit?
|
140
|
+
|
141
|
+
if bytesize_limit?
|
142
|
+
if per_domain?
|
143
|
+
check_bytesize_limit_per_domain(env, bytesize, bytesize_limit)
|
144
|
+
else
|
145
|
+
check_bytesize_limit_per_cookie(env, cookies, bytesize_limit - overhead)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def check_limit_per_domain(env, acc, limit)
|
151
|
+
bad_domains = acc
|
152
|
+
.keep_if { |_, value| value > limit }
|
153
|
+
.keys
|
154
|
+
|
155
|
+
if bad_domains.any? && handle(env)
|
156
|
+
raise_error "Too many cookies for domain(s): #{bad_domains.join(', ')}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def check_bytesize_limit_per_domain(env, acc, limit)
|
161
|
+
bad_domains = acc
|
162
|
+
.keep_if { |_, value| value > limit }
|
163
|
+
.keys
|
164
|
+
|
165
|
+
if bad_domains.any? && handle(env)
|
166
|
+
raise_error "Too much cookie data for domain(s): #{bad_domains.join(', ')}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def check_bytesize_limit_per_cookie(env, cookies, limit)
|
171
|
+
bad_cookies = cookies
|
172
|
+
.keep_if { |cookie| cookie.bytesize > limit }
|
173
|
+
.map! { |cookie| cookie[COOKIE_KEY_RE] }
|
174
|
+
.tap(&:uniq!)
|
175
|
+
|
176
|
+
if bad_cookies.any? && handle(env)
|
177
|
+
raise_error "Too much data for cookie(s): #{bad_cookies.join(', ')}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def domain(hostname)
|
182
|
+
return hostname if hostname =~ Resolv::IPv4::Regex || hostname =~ Resolv::IPv6::Regex
|
183
|
+
|
184
|
+
PublicSuffix.domain(hostname, :list=>public_suffix_list) || hostname
|
185
|
+
end
|
186
|
+
|
187
|
+
def handle(env)
|
188
|
+
handler.nil? || handler.call(env)
|
189
|
+
end
|
190
|
+
|
191
|
+
def normalize_cookie_header(value)
|
192
|
+
Array(value)
|
193
|
+
.flat_map { |h| h.to_s.split(HEADER_SEP_RE) }
|
194
|
+
.tap(&:compact!)
|
195
|
+
end
|
196
|
+
|
197
|
+
if String.instance_method(:downcase).arity.abs > 0
|
198
|
+
def foldcase(str)
|
199
|
+
str.downcase(:fold)
|
200
|
+
end
|
201
|
+
|
202
|
+
def foldcase!(str)
|
203
|
+
str.downcase!(:fold)
|
204
|
+
end
|
205
|
+
else
|
206
|
+
def foldcase(str)
|
207
|
+
str.downcase
|
208
|
+
end
|
209
|
+
|
210
|
+
def foldcase!(str)
|
211
|
+
str.downcase!
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def fold(request, response_cookie_keys)
|
216
|
+
# Assume that all request cookies have a domain of the default
|
217
|
+
# subdomain (e.g. foo.example.com) or its second-level domain (e.g.
|
218
|
+
# example.com).
|
219
|
+
domains = [foldcase(request.hostname)].tap do |a|
|
220
|
+
a.unshift(domain(a.first))
|
221
|
+
a.uniq!
|
222
|
+
end
|
223
|
+
|
224
|
+
request.cookies.each_pair do |key, value|
|
225
|
+
# *Try* to prevent double-counting cookies (i.e. on the response
|
226
|
+
# and the request).
|
227
|
+
next if domains.any? { |domain| response_cookie_keys[domain].include?(key) }
|
228
|
+
|
229
|
+
# *Try* to estimate the upper bound of the size of the cookie and its
|
230
|
+
# directives in the original Set-Cookie header.
|
231
|
+
# TODO: Replace this with a simpler byte count for efficiency?
|
232
|
+
mock_cookie = String.new("#{key}=#{value}").tap do |s|
|
233
|
+
s << "; Expires=#{Date.today.httpdate}"
|
234
|
+
s << '; Max-Age=123456'
|
235
|
+
s << "; Domain=#{domains.last}"
|
236
|
+
s << "; Path=#{request.script_name}"
|
237
|
+
s << '; Secure' if request.ssl?
|
238
|
+
s << '; HttpOnly; SameSite=strict'
|
239
|
+
end
|
240
|
+
|
241
|
+
yield domains.first, mock_cookie.bytesize
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def propogate_values(hash)
|
246
|
+
hash.each_key do |subdomain|
|
247
|
+
sld = domain(subdomain)
|
248
|
+
next if sld == subdomain
|
249
|
+
next unless hash.key?(sld)
|
250
|
+
hash[subdomain] += hash[sld]
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def raise_error(message)
|
255
|
+
fail message
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rack/protection/maximum_cookie/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'rack-protection-maximum_cookie'
|
8
|
+
spec.version = Rack::Protection::MaximumCookie::VERSION
|
9
|
+
spec.authors = ['Mike Pastore']
|
10
|
+
spec.email = ['mike@oobak.org']
|
11
|
+
|
12
|
+
spec.summary = %q{Properly enforce cookie limits in Rack responses}
|
13
|
+
spec.homepage = 'https://github.com/mwpastore/rack-protection-maximum_cookie#readme'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = %x{git ls-files -z}.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = %w[lib]
|
22
|
+
|
23
|
+
spec.required_ruby_version = '>= 2.1.10'
|
24
|
+
|
25
|
+
spec.add_dependency 'public_suffix', '~> 3.0'
|
26
|
+
spec.add_dependency 'rack', ENV.fetch('RACK_VERSION', ['>= 1.4.7', '< 2.1'])
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.15'
|
29
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
30
|
+
spec.add_development_dependency 'rack-test', '~> 0.7.0'
|
31
|
+
spec.add_development_dependency 'rake', '~> 12.0'
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-protection-maximum_cookie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Pastore
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: public_suffix
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.4.7
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '2.1'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.4.7
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.1'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.15'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.15'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: minitest
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '5.0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '5.0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rack-test
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 0.7.0
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 0.7.0
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rake
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '12.0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '12.0'
|
103
|
+
description:
|
104
|
+
email:
|
105
|
+
- mike@oobak.org
|
106
|
+
executables: []
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files: []
|
109
|
+
files:
|
110
|
+
- ".gitignore"
|
111
|
+
- ".travis.yml"
|
112
|
+
- Gemfile
|
113
|
+
- LICENSE.txt
|
114
|
+
- README.md
|
115
|
+
- Rakefile
|
116
|
+
- bin/console
|
117
|
+
- bin/setup
|
118
|
+
- lib/rack-protection-maximum_cookie.rb
|
119
|
+
- lib/rack/protection/maximum_cookie.rb
|
120
|
+
- lib/rack/protection/maximum_cookie/version.rb
|
121
|
+
- rack-protection-maximum_cookie.gemspec
|
122
|
+
homepage: https://github.com/mwpastore/rack-protection-maximum_cookie#readme
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 2.1.10
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.6.13
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Properly enforce cookie limits in Rack responses
|
146
|
+
test_files: []
|