rack-stale-call 0.0.1
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 +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +57 -0
- data/Guardfile +8 -0
- data/LICENSE +21 -0
- data/README.md +12 -0
- data/Rakefile +6 -0
- data/lib/rack/contrib/stale_call.rb +72 -0
- data/rack-stale-call.gemspec +24 -0
- data/spec/rack/contrib/stale_call_spec.rb +204 -0
- data/spec/spec_helper.rb +3 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ba1b5f39717e8fb296408ba8c46532fde5111dbd
|
4
|
+
data.tar.gz: cd4e5e812eec3103cd298aa9f93506787b93b6cf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7a4c71a307ecb7a08a40226a9488afd52b8f650afeef4b795e7a40824a32545ce7f2aeae2dc084d418924ce6b5bedd086e160151c79b0c35afb1105e03f72330
|
7
|
+
data.tar.gz: 24cf371934ec8a5a2059aa33a52a14236810c7b4c3964f16bb064f94b04cee87e3c5a11e221b786b3698ba23ca495d47c3878cceeca9ed3459a0b6a693ef9b33
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rack-stale-call (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.0.9)
|
10
|
+
diff-lcs (1.2.4)
|
11
|
+
ffi (1.9.0)
|
12
|
+
formatador (0.2.4)
|
13
|
+
guard (1.8.1)
|
14
|
+
formatador (>= 0.2.4)
|
15
|
+
listen (>= 1.0.0)
|
16
|
+
lumberjack (>= 1.0.2)
|
17
|
+
pry (>= 0.9.10)
|
18
|
+
thor (>= 0.14.6)
|
19
|
+
guard-rspec (3.0.2)
|
20
|
+
guard (>= 1.8)
|
21
|
+
rspec (~> 2.13)
|
22
|
+
listen (1.2.2)
|
23
|
+
rb-fsevent (>= 0.9.3)
|
24
|
+
rb-inotify (>= 0.9)
|
25
|
+
rb-kqueue (>= 0.2)
|
26
|
+
lumberjack (1.0.4)
|
27
|
+
method_source (0.8.1)
|
28
|
+
pry (0.9.12.2)
|
29
|
+
coderay (~> 1.0.5)
|
30
|
+
method_source (~> 0.8)
|
31
|
+
slop (~> 3.4)
|
32
|
+
rake (10.1.0)
|
33
|
+
rb-fsevent (0.9.3)
|
34
|
+
rb-inotify (0.9.0)
|
35
|
+
ffi (>= 0.5.0)
|
36
|
+
rb-kqueue (0.2.0)
|
37
|
+
ffi (>= 0.5.0)
|
38
|
+
rspec (2.14.1)
|
39
|
+
rspec-core (~> 2.14.0)
|
40
|
+
rspec-expectations (~> 2.14.0)
|
41
|
+
rspec-mocks (~> 2.14.0)
|
42
|
+
rspec-core (2.14.3)
|
43
|
+
rspec-expectations (2.14.0)
|
44
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
45
|
+
rspec-mocks (2.14.1)
|
46
|
+
slop (3.4.5)
|
47
|
+
thor (0.18.1)
|
48
|
+
|
49
|
+
PLATFORMS
|
50
|
+
ruby
|
51
|
+
|
52
|
+
DEPENDENCIES
|
53
|
+
guard
|
54
|
+
guard-rspec
|
55
|
+
rack-stale-call!
|
56
|
+
rake
|
57
|
+
rspec
|
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 ZippyKid
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
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, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Rack::Contrib::StaleCall
|
2
|
+
|
3
|
+
Provide a simple method for validating that a request isn't a time traveler.
|
4
|
+
Prevents old requests from being run again, and future requests from being run.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
# config.ru
|
10
|
+
|
11
|
+
use Rack::Contrib::StaleCall
|
12
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
module Contrib
|
6
|
+
#
|
7
|
+
# Validate a request's age isn't too old or too young, indicating a
|
8
|
+
# replayed request.
|
9
|
+
#
|
10
|
+
class StaleCall
|
11
|
+
VERSION = '0.0.1'
|
12
|
+
|
13
|
+
attr_reader :opts
|
14
|
+
|
15
|
+
|
16
|
+
def initialize app, opts = {}
|
17
|
+
@app = app
|
18
|
+
@opts = opts
|
19
|
+
@opts[:header] ||= 'Date'
|
20
|
+
@opts[:grace] ||= 5
|
21
|
+
@opts[:logger] ||= Logger.new('/dev/null')
|
22
|
+
end
|
23
|
+
|
24
|
+
# Translate a header name to a variable which Rack might provide
|
25
|
+
def header_variable
|
26
|
+
'HTTP_' + opts[:header].upcase.gsub(/-/, '_')
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return the standard date error contents
|
30
|
+
def date_error
|
31
|
+
[401, {}, []]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convert an RFC2822 compatible date string to a diff against now
|
35
|
+
def date_to_diff_seconds date
|
36
|
+
date = DateTime.rfc2822(date)
|
37
|
+
datediff = date - DateTime.now # Number of days apart as a float
|
38
|
+
datediff *= 24 # hours apart
|
39
|
+
datediff *= 60 # minutes apart
|
40
|
+
datediff *= 60 # seconds apart
|
41
|
+
seconds = datediff.to_i.abs
|
42
|
+
end
|
43
|
+
|
44
|
+
# Determine if the date is recent enough
|
45
|
+
def date_is_recent seconds
|
46
|
+
seconds <= opts[:grace]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Verify the age of the request
|
50
|
+
def call env
|
51
|
+
unless env.has_key? header_variable
|
52
|
+
@opts[:logger].info "Denied: #{opts[:header]} header missing."
|
53
|
+
return date_error
|
54
|
+
end
|
55
|
+
|
56
|
+
diff_seconds = date_to_diff_seconds(env[header_variable])
|
57
|
+
unless date_is_recent diff_seconds
|
58
|
+
@opts[:logger].info sprintf(
|
59
|
+
"Denied: %s header is %ss over the %ss grace period.",
|
60
|
+
@opts[:header],
|
61
|
+
diff_seconds - @opts[:grace],
|
62
|
+
@opts[:grace]
|
63
|
+
)
|
64
|
+
return date_error
|
65
|
+
end
|
66
|
+
|
67
|
+
@app.call env
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'rack/contrib/stale_call'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'rack-stale-call'
|
8
|
+
gem.version = Rack::Contrib::StaleCall::VERSION
|
9
|
+
gem.summary = 'Validate that the request was recently generated'
|
10
|
+
gem.homepage = 'https://github.com/zippykid/rack-stale-call'
|
11
|
+
gem.authors = ['Graham Christensen']
|
12
|
+
gem.email = ['info@zippykid.com']
|
13
|
+
gem.licenses = ['MIT']
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ['lib']
|
18
|
+
|
19
|
+
gem.add_development_dependency('rake')
|
20
|
+
gem.add_development_dependency('rspec')
|
21
|
+
gem.add_development_dependency('guard')
|
22
|
+
gem.add_development_dependency('guard-rspec')
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe Rack::Contrib::StaleCall do
|
5
|
+
let (:response) { [200, {}, ['foo', 'bar']]}
|
6
|
+
let (:log_string) { StringIO.new }
|
7
|
+
let (:logger) do
|
8
|
+
l = Logger.new(log_string, Logger::DEBUG)
|
9
|
+
l.formatter = proc do |severity, datetime, progname, msg|
|
10
|
+
"#{severity} - #{msg}\n"
|
11
|
+
end
|
12
|
+
|
13
|
+
l
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#initialize" do
|
17
|
+
it "accepts an app" do
|
18
|
+
expect { ware = Rack::Contrib::StaleCall.new nil }.not_to raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it "requires an app" do
|
22
|
+
expect { ware = Rack::Contrib::StaleCall.new }.to raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
it "accepts a hash of options" do
|
26
|
+
expect { ware = Rack::Contrib::StaleCall.new(nil, {}) }.not_to raise_error
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#opts" do
|
31
|
+
it "returns all default options" do
|
32
|
+
ware = Rack::Contrib::StaleCall.new nil
|
33
|
+
|
34
|
+
opts = ware.opts
|
35
|
+
|
36
|
+
opts[:logger].should be_an_instance_of Logger
|
37
|
+
opts[:grace].should eq(5)
|
38
|
+
opts[:header].should eq('Date')
|
39
|
+
end
|
40
|
+
|
41
|
+
it "accounts for options passed in the initialize" do
|
42
|
+
ware = Rack::Contrib::StaleCall.new nil,
|
43
|
+
:grace => 6, :header => "foo", :logger => logger
|
44
|
+
|
45
|
+
opts = ware.opts
|
46
|
+
|
47
|
+
opts.should eq({
|
48
|
+
:header => "foo",
|
49
|
+
:grace => 6,
|
50
|
+
:logger => logger
|
51
|
+
})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#header_variable" do
|
56
|
+
it "converts a header name to an environment variable" do
|
57
|
+
ware = Rack::Contrib::StaleCall.new nil, :header => 'FoO-Ba-r-Date'
|
58
|
+
|
59
|
+
variable_name = ware.header_variable
|
60
|
+
|
61
|
+
variable_name.should eq('HTTP_FOO_BA_R_DATE')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#date_error" do
|
66
|
+
it "returns a 401 with no headers and body" do
|
67
|
+
ware = Rack::Contrib::StaleCall.new nil, :header => 'FoO-Ba-r-Date'
|
68
|
+
|
69
|
+
code, headers, body = ware.date_error
|
70
|
+
|
71
|
+
code.should eq(401)
|
72
|
+
headers.should eq({})
|
73
|
+
body.should eq([])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "#date_to_diff_seconds" do
|
78
|
+
context "converts an HTTPDate to the differing number of seconds" do
|
79
|
+
it "handles dates in the future" do
|
80
|
+
ware = Rack::Contrib::StaleCall.new nil
|
81
|
+
time = (Time.now + 301).httpdate
|
82
|
+
|
83
|
+
diff = ware.date_to_diff_seconds time
|
84
|
+
|
85
|
+
diff.should eq(300)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "absolute values the dates in the past" do
|
89
|
+
ware = Rack::Contrib::StaleCall.new nil
|
90
|
+
time = (Time.now - 300).httpdate
|
91
|
+
|
92
|
+
diff = ware.date_to_diff_seconds time
|
93
|
+
|
94
|
+
diff.should eq(300)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#date_is_recent" do
|
100
|
+
it "is recent with a time less than grace" do
|
101
|
+
ware = Rack::Contrib::StaleCall.new nil, :grace => 500
|
102
|
+
|
103
|
+
is_recent = ware.date_is_recent 400
|
104
|
+
|
105
|
+
is_recent.should eq(true)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "is recent with a time equal to grace" do
|
109
|
+
ware = Rack::Contrib::StaleCall.new nil, :grace => 500
|
110
|
+
|
111
|
+
is_recent = ware.date_is_recent 500
|
112
|
+
|
113
|
+
is_recent.should eq(true)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "is recent with a time greater than grace" do
|
117
|
+
ware = Rack::Contrib::StaleCall.new nil, :grace => 1
|
118
|
+
|
119
|
+
is_recent = ware.date_is_recent 2
|
120
|
+
|
121
|
+
is_recent.should eq(false)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "#call" do
|
126
|
+
it "requires an env" do
|
127
|
+
ware = Rack::Contrib::StaleCall.new nil
|
128
|
+
expect { ware.call }.to raise_error
|
129
|
+
end
|
130
|
+
|
131
|
+
context "there is an invalid date header" do
|
132
|
+
context "the date is missing" do
|
133
|
+
it "returns a 401" do
|
134
|
+
ware = Rack::Contrib::StaleCall.new nil
|
135
|
+
env = {}
|
136
|
+
|
137
|
+
code, headers, body = ware.call env
|
138
|
+
|
139
|
+
code.should eq(401)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "logs the failure" do
|
143
|
+
ware = Rack::Contrib::StaleCall.new nil,
|
144
|
+
:logger => logger,
|
145
|
+
:header => 'My-Date'
|
146
|
+
env = {}
|
147
|
+
|
148
|
+
ware.call env
|
149
|
+
|
150
|
+
log_string.string.should eq("INFO - Denied: My-Date header missing.\n")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "the date is older than the grace" do
|
155
|
+
it "returns a 401" do
|
156
|
+
ware = Rack::Contrib::StaleCall.new nil
|
157
|
+
env = {'HTTP_DATE' => (Time.now - 10).httpdate}
|
158
|
+
ware.stub(:date_is_recent => false)
|
159
|
+
|
160
|
+
code, headers, body = ware.call env
|
161
|
+
|
162
|
+
code.should eq(401)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "logs the failure" do
|
166
|
+
ware = Rack::Contrib::StaleCall.new nil,
|
167
|
+
:header => 'My-Date',
|
168
|
+
:logger => logger
|
169
|
+
env = {'HTTP_MY_DATE' => (Time.now - 10).httpdate}
|
170
|
+
ware.stub(:date_is_recent => false)
|
171
|
+
|
172
|
+
ware.call env
|
173
|
+
|
174
|
+
expected_message = "INFO - Denied: My-Date header is 5s over the " +
|
175
|
+
"5s grace period.\n"
|
176
|
+
log_string.string.should eq(expected_message)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context "there is a valid date header" do
|
182
|
+
it "calls the app's .call method" do
|
183
|
+
app = double(nil, call: response)
|
184
|
+
env = {'foo' => 'bar', 'HTTP_DATE' => Time.now.httpdate}
|
185
|
+
ware = Rack::Contrib::StaleCall.new app
|
186
|
+
|
187
|
+
ware.call env
|
188
|
+
|
189
|
+
app.should have_received(:call).once.with(env)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "returns the apps' return" do
|
193
|
+
app = double(nil, call: response)
|
194
|
+
env = {'foo' => 'bar', 'HTTP_DATE' => Time.now.httpdate}
|
195
|
+
ware = Rack::Contrib::StaleCall.new app
|
196
|
+
|
197
|
+
ret = ware.call env
|
198
|
+
|
199
|
+
ret.should eq(response)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-stale-call
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Graham Christensen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: guard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- info@zippykid.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .travis.yml
|
78
|
+
- Gemfile
|
79
|
+
- Gemfile.lock
|
80
|
+
- Guardfile
|
81
|
+
- LICENSE
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- lib/rack/contrib/stale_call.rb
|
85
|
+
- rack-stale-call.gemspec
|
86
|
+
- spec/rack/contrib/stale_call_spec.rb
|
87
|
+
- spec/spec_helper.rb
|
88
|
+
homepage: https://github.com/zippykid/rack-stale-call
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.0.5
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Validate that the request was recently generated
|
112
|
+
test_files:
|
113
|
+
- spec/rack/contrib/stale_call_spec.rb
|
114
|
+
- spec/spec_helper.rb
|