rack-canonical-host 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8a6f24e954b7680ed4e2571398bc6d2012deaf38
4
- data.tar.gz: 224fd0063568a627b4f37bf0ca6dc33d579645ba
3
+ metadata.gz: afbfedb96e0996ded4ce9b2d21532dcbda77bded
4
+ data.tar.gz: 47d412e723522d44bf530af57fa03107fef9b745
5
5
  SHA512:
6
- metadata.gz: 981074ba73744daeff5a3e7e3256e42ed19cfdb56759ff8e133390ab353804886e9b3fce6bc5268934daa41275b6d1d38ec1c7c8e048e0d4017ce38df3539cb3
7
- data.tar.gz: 6c191906b97d467c729b055abbf9ffade959d35f32071566d0796500f431fa790f9d59c03a5f1427e374ebd3b0e0c385ac749e661314e1007a52fcc982c3ab0b
6
+ metadata.gz: 83e23130511d87ee922ea34ed3f87384179fde266f818c04aace1c0d19a98b389380fcc250db824a06894cb53921eb3c4fd9dc079c48058ead99d61b7d3c56ce
7
+ data.tar.gz: 0c8d2c995d2f341bd63d1b51e8ffc6d5d7161236263ee2eda932b95edd847ee59beb6af002c805b08e3c3a5e8d66333bb5599efe1489c3937058f030e22dca29
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2 (2016-05-17)
4
+
5
+ * Add `:cache_control` option ([Pete Nicholls][Aupajo])
6
+
3
7
  ## 0.2.1 (2016-03-28)
4
8
 
5
9
  * Relax Rack dependency to allow for Rack 2 ([Tyler Ewing][zoso10])
@@ -57,6 +61,7 @@
57
61
 
58
62
  * Initial release ([Tyler Hunt][tylerhunt])
59
63
 
64
+ [Aupajo]: http://github.com/Aupajo
60
65
  [finack]: http://github.com/finack
61
66
  [firedev]: http://github.com/firedev
62
67
  [jcarbo]: http://github.com/jcarbo
data/README.md CHANGED
@@ -95,6 +95,23 @@ use Rack::CanonicalHost, 'example.com', if: /.*\.example\.com/
95
95
  use Rack::CanonicalHost, 'example.ru', if: /.*\.example\.ru/
96
96
  ```
97
97
 
98
+ ### Cache-Control
99
+
100
+ To avoid browsers indefinitely caching a `301` redirect, it’s a sensible idea
101
+ to set an expiry on each redirect, to hedge against the chance you may need to
102
+ change that redirect in the future.
103
+
104
+ ``` ruby
105
+ # Leave caching up to the browser (which could cache it indefinitely):
106
+ use Rack::CanonicalHost, 'example.com'
107
+
108
+ # Cache the redirect for up to an hour:
109
+ use Rack::CanonicalHost, 'example.com', cache_control: 'max-age=3600'
110
+
111
+ # Prevent caching of redirects:
112
+ use Rack::CanonicalHost, 'example.com', cache_control: 'no-cache'
113
+ ```
114
+
98
115
  ## Contributing
99
116
 
100
117
  1. Fork it
@@ -109,6 +126,7 @@ use Rack::CanonicalHost, 'example.ru', if: /.*\.example\.ru/
109
126
  Thanks to the following people who have contributed patches or helpful
110
127
  suggestions:
111
128
 
129
+ * [Pete Nicholls](https://github.com/Aupajo)
112
130
  * [Tyler Ewing](https://github.com/zoso10)
113
131
  * [Thomas Maurer](https://github.com/tma)
114
132
  * [Jeff Carbonella](https://github.com/jcarbo)
@@ -20,6 +20,7 @@ module Rack
20
20
  self.host = host
21
21
  self.ignore = Array(options[:ignore])
22
22
  self.conditions = Array(options[:if])
23
+ self.cache_control = options[:cache_control]
23
24
  end
24
25
 
25
26
  def canonical?
@@ -37,6 +38,7 @@ module Rack
37
38
  attr_accessor :host
38
39
  attr_accessor :ignore
39
40
  attr_accessor :conditions
41
+ attr_accessor :cache_control
40
42
 
41
43
  private
42
44
 
@@ -46,9 +48,10 @@ module Rack
46
48
 
47
49
  def headers
48
50
  {
49
- 'Location' => new_url,
51
+ 'Cache-Control' => cache_control,
50
52
  'Content-Type' => 'text/html',
51
- }
53
+ 'Location' => new_url,
54
+ }.reject { |_, value| !value }
52
55
  end
53
56
 
54
57
  def enabled?
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class CanonicalHost
3
- VERSION = '0.2.1'
3
+ VERSION = '0.2.2'
4
4
  end
5
5
  end
@@ -1,9 +1,14 @@
1
- require 'spec_helper'
2
-
3
- describe Rack::CanonicalHost do
4
- let(:response) { [200, { 'Content-Type' => 'text/plain' }, ['OK']] }
1
+ RSpec.describe Rack::CanonicalHost do
2
+ let(:app_response) { [200, { 'Content-Type' => 'text/plain' }, %w(OK)] }
5
3
  let(:inner_app) { lambda { |env| response } }
6
4
 
5
+ before do
6
+ allow(inner_app)
7
+ .to receive(:call)
8
+ .with(env)
9
+ .and_return(app_response)
10
+ end
11
+
7
12
  def build_app(host=nil, options={}, inner_app=inner_app(), &block)
8
13
  Rack::Builder.new do
9
14
  use Rack::Lint
@@ -19,8 +24,8 @@ describe Rack::CanonicalHost do
19
24
  it { should_not be_redirect }
20
25
 
21
26
  it 'calls the inner app' do
22
- expect(inner_app).to receive(:call).with(env).and_return(response)
23
- subject
27
+ expect(inner_app).to receive(:call).with(env)
28
+ call_app
24
29
  end
25
30
  end
26
31
  end
@@ -29,12 +34,14 @@ describe Rack::CanonicalHost do
29
34
  context 'with a request to a non-matching host' do
30
35
  let(:url) { 'http://www.example.com/full/path' }
31
36
 
32
- it { should be_redirect.via(301).to('http://example.com/full/path') }
37
+ it { should redirect_to('http://example.com/full/path') }
33
38
 
34
39
  it 'does not call the inner app' do
35
40
  expect(inner_app).to_not receive(:call)
36
- subject
41
+ call_app
37
42
  end
43
+
44
+ it { expect(response).to_not have_header('Cache-Control') }
38
45
  end
39
46
  end
40
47
 
@@ -44,51 +51,70 @@ describe Rack::CanonicalHost do
44
51
  end
45
52
 
46
53
  context '#call' do
47
- let(:env) { Rack::MockRequest.env_for(url) }
54
+ let(:headers) { {} }
48
55
 
49
- subject { app.call(env) }
56
+ let(:app) { build_app('example.com') }
57
+ let(:env) { Rack::MockRequest.env_for(url, headers) }
50
58
 
51
- context 'without a host' do
52
- let(:app) { build_app(nil) }
59
+ def call_app
60
+ app.call(env)
61
+ end
53
62
 
54
- include_context 'a matching request'
63
+ subject(:response) { call_app }
64
+
65
+ include_context 'a matching request'
66
+ include_context 'a non-matching request'
67
+
68
+ context 'when the request has a pipe in the URL' do
69
+ let(:url) { 'https://example.com/full/path?value=withPIPE' }
70
+
71
+ before { env['QUERY_STRING'].sub!('PIPE', '|') }
72
+
73
+ it { expect { call_app }.to_not raise_error }
55
74
  end
56
75
 
57
- context 'with an X-Forwarded-Host' do
76
+ context 'when the request has JavaScript in the URL' do
77
+ let(:url) { 'http://www.example.com/full/path' }
78
+
79
+ let(:headers) {
80
+ { 'QUERY_STRING' => '"><script>alert(73541);</script>' }
81
+ }
82
+
58
83
  let(:app) { build_app('example.com') }
59
- let(:url) { 'http://proxied.url/full/path' }
84
+
85
+ it 'escapes the JavaScript' do
86
+ expect(response)
87
+ .to redirect_to('http://example.com/full/path?%22%3E%3Cscript%3Ealert(73541)%3B%3C/script%3E')
88
+ end
89
+ end
90
+
91
+ context 'with an X-Forwarded-Host' do
92
+ let(:url) { 'http://proxy.test/full/path' }
60
93
 
61
94
  context 'which matches the canonical host' do
62
- let(:env) { Rack::MockRequest.env_for(url, 'HTTP_X_FORWARDED_HOST' => 'example.com:80') }
95
+ let(:headers) { { 'HTTP_X_FORWARDED_HOST' => 'example.com:80' } }
63
96
 
64
97
  include_context 'a matching request'
65
98
  end
66
99
 
67
100
  context 'which does not match the canonical host' do
68
- let(:env) { Rack::MockRequest.env_for(url, 'HTTP_X_FORWARDED_HOST' => 'www.example.com:80') }
101
+ let(:headers) { { 'HTTP_X_FORWARDED_HOST' => 'www.example.com:80' } }
69
102
 
70
103
  include_context 'a non-matching request'
71
104
  end
72
105
  end
73
106
 
74
- context 'without any options' do
75
- let(:app) { build_app('example.com') }
76
-
77
- include_context 'matching and non-matching requests'
78
-
79
- context 'when the request has a pipe in the URL' do
80
- let(:url) { 'https://example.com/full/path?value=withPIPE' }
81
-
82
- before { env['QUERY_STRING'].sub!('PIPE', '|') }
107
+ context 'without a host' do
108
+ let(:app) { build_app(nil) }
83
109
 
84
- it { expect { subject }.to_not raise_error }
85
- end
110
+ include_context 'a matching request'
86
111
  end
87
112
 
88
113
  context 'with :ignore option' do
89
114
  let(:app) { build_app('example.com', :ignore => 'example.net') }
90
115
 
91
- include_context 'matching and non-matching requests'
116
+ include_context 'a matching request'
117
+ include_context 'a non-matching request'
92
118
 
93
119
  context 'with a request to an ignored host' do
94
120
  let(:url) { 'http://example.net/full/path' }
@@ -96,67 +122,88 @@ describe Rack::CanonicalHost do
96
122
  it { should_not be_redirect }
97
123
 
98
124
  it 'calls the inner app' do
99
- expect(inner_app).to receive(:call).with(env).and_return(response)
100
- subject
125
+ expect(inner_app).to receive(:call).with(env)
126
+ call_app
101
127
  end
102
128
  end
103
129
  end
104
130
 
105
131
  context 'with :if option' do
106
-
107
132
  let(:app) { build_app('example.com', :if => 'www.example.net') }
108
133
 
109
- context 'with a request to a :if matching host' do
134
+ context 'with a request to a matching host' do
110
135
  let(:url) { 'http://www.example.net/full/path' }
111
- it { should be_redirect.to('http://example.com/full/path') }
136
+
137
+ it { should redirect_to('http://example.com/full/path') }
112
138
  end
113
139
 
114
- context 'with a request to a :if non-matching host' do
115
- let(:url) { 'http://www.sexample.net/full/path' }
140
+ context 'with a request to a non-matching host' do
141
+ let(:url) { 'http://www.example.com/full/path' }
142
+
116
143
  it { should_not be_redirect }
117
144
  end
118
-
119
145
  end
120
146
 
121
- context 'with :if and regexp as an option' do
122
-
147
+ context 'with a regular expression :if option' do
123
148
  let(:app) { build_app('example.com', :if => /.*\.example\.net/) }
124
149
 
125
- context 'with a request to a :if matching host' do
150
+ context 'with a request to a matching host' do
126
151
  let(:url) { 'http://subdomain.example.net/full/path' }
127
- it { should be_redirect.to('http://example.com/full/path') }
152
+
153
+ it { should redirect_to('http://example.com/full/path') }
128
154
  end
129
155
 
130
- context 'with a request to a :if non-matching host' do
156
+ context 'with a request to a non-matching host' do
131
157
  let(:url) { 'http://example.net/full/path' }
158
+
132
159
  it { should_not be_redirect }
133
160
  end
161
+ end
162
+
163
+ context 'with a :cache_control option' do
164
+ let(:url) { 'http://subdomain.example.net/full/path' }
165
+
166
+ context 'with a max-age value' do
167
+ let(:app) {
168
+ build_app('example.com', :cache_control => 'max-age=3600')
169
+ }
170
+
171
+ it {
172
+ expect(response).to have_header('Cache-Control').with('max-age=3600')
173
+ }
174
+ end
175
+
176
+ context 'with a no-cache value' do
177
+ let(:app) { build_app('example.com', :cache_control => 'no-cache') }
178
+
179
+ it { expect(subject).to have_header('Cache-Control').with('no-cache') }
180
+ end
134
181
 
182
+ context 'with a false value' do
183
+ let(:app) { build_app('example.com', :cache_control => false) }
184
+
185
+ it { expect(subject).to_not have_header('Cache-Control') }
186
+ end
187
+
188
+ context 'with a nil value' do
189
+ let(:app) { build_app('example.com', :cache_control => false) }
190
+
191
+ it { expect(subject).to_not have_header('Cache-Control') }
192
+ end
135
193
  end
136
194
 
137
195
  context 'with a block' do
138
196
  let(:app) { build_app { 'example.com' } }
139
197
 
140
- include_context 'matching and non-matching requests'
198
+ include_context 'a matching request'
199
+ include_context 'a non-matching request'
141
200
 
142
201
  context 'that returns nil' do
143
202
  let(:app) { build_app('example.com') { nil } }
144
203
 
145
- include_context 'matching and non-matching requests'
146
- end
147
- end
148
-
149
- context 'with URL containing JavaScript XSS' do
150
- let(:url) { 'http://subdomain.example.net/full/path' }
151
- let(:env) do
152
- Rack::MockRequest.env_for(url).tap do |env|
153
- env['QUERY_STRING'] = '"><script>alert(73541);</script>'
154
- end
204
+ include_context 'a matching request'
205
+ include_context 'a non-matching request'
155
206
  end
156
-
157
- let(:app) { build_app('example.com') }
158
-
159
- it { should be_redirect.to('http://example.com/full/path?%22%3E%3Cscript%3Ealert(73541)%3B%3C/script%3E') }
160
207
  end
161
208
  end
162
209
  end
@@ -0,0 +1,56 @@
1
+ module HaveHeader
2
+ class Matcher
3
+ def initialize(expected_header)
4
+ self.expected_header = expected_header
5
+ end
6
+
7
+ def matches?(response)
8
+ _, self.actual_headers, _ = response
9
+
10
+ if expected_value
11
+ actual_header == expected_value
12
+ else
13
+ actual_header
14
+ end
15
+ end
16
+
17
+ def with(expected_value)
18
+ self.expected_value = expected_value
19
+ self
20
+ end
21
+
22
+ def description
23
+ sentence = "have header #{expected_header.inspect}"
24
+ sentence << " with #{expected_value.inspect}" if expected_value
25
+ sentence
26
+ end
27
+
28
+ def failure_message
29
+ "Expected response to #{description}"
30
+ end
31
+
32
+ def failure_message_when_negated
33
+ "Did not expect response to #{description}"
34
+ end
35
+
36
+ protected
37
+
38
+ attr_accessor :actual_headers
39
+ attr_accessor :expected_header
40
+ attr_accessor :expected_value
41
+
42
+ private
43
+
44
+ def actual_header
45
+ actual_headers[expected_header]
46
+ end
47
+ end
48
+
49
+ def have_header(name)
50
+ Matcher.new(name)
51
+ end
52
+ end
53
+
54
+ RSpec.configure do |config|
55
+ config.include HaveHeader
56
+ end
@@ -1,36 +1,30 @@
1
- module BeRedirect
1
+ module RedirectTo
2
2
  class Matcher
3
- attr :expected_status_code
4
- attr :expected_location
5
- attr :actual_status_code
6
- attr :actual_location
3
+ def initialize(expected_location)
4
+ self.expected_location = expected_location
5
+ self.expected_status_code = STATUS
6
+ end
7
7
 
8
8
  def matches?(response)
9
- @actual_status_code, headers, _ = response
10
- @actual_location = headers['Location']
9
+ self.actual_status_code, self.actual_headers, _ = response
11
10
 
12
11
  status_code_matches? && location_matches?
13
12
  end
14
13
 
15
14
  def via(expected_status_code)
16
- @expected_status_code = expected_status_code
17
- self
18
- end
19
-
20
- def to(expected_location)
21
- @expected_location = expected_location
15
+ self.expected_status_code = expected_status_code
22
16
  self
23
17
  end
24
18
 
25
19
  def description
26
20
  if expected_status_code && expected_location
27
- "redirect via #{expected_status_code} to #{expected_location.inspect}"
21
+ "redirect to #{expected_location.inspect} via #{expected_status_code}"
28
22
  elsif expected_status_code
29
23
  "redirect via #{expected_status_code}"
30
24
  elsif expected_location
31
25
  "redirect to #{expected_location.inspect}"
32
26
  else
33
- "be a redirect"
27
+ 'be a redirect'
34
28
  end
35
29
  end
36
30
 
@@ -42,6 +36,22 @@ module BeRedirect
42
36
  "Did not expect response to #{description}"
43
37
  end
44
38
 
39
+ protected
40
+
41
+ attr_accessor :actual_headers
42
+ attr_accessor :actual_status_code
43
+ attr_accessor :expected_location
44
+ attr_accessor :expected_status_code
45
+
46
+ private
47
+
48
+ LOCATION = 'Location'
49
+ STATUS = 301
50
+
51
+ def actual_location
52
+ actual_headers[LOCATION]
53
+ end
54
+
45
55
  def status_code_matches?
46
56
  if expected_status_code
47
57
  actual_status_code == expected_status_code
@@ -49,19 +59,23 @@ module BeRedirect
49
59
  actual_status_code.to_s =~ /^30[1237]$/
50
60
  end
51
61
  end
52
- private :status_code_matches?
53
62
 
54
63
  def location_matches?
55
- !expected_location || (expected_location == actual_location)
64
+ if expected_location
65
+ expected_location == actual_location
66
+ end
56
67
  end
57
- private :location_matches?
68
+ end
69
+
70
+ def redirect_to(location)
71
+ Matcher.new(location)
58
72
  end
59
73
 
60
74
  def be_redirect
61
- Matcher.new
75
+ Matcher.new(nil).via(nil)
62
76
  end
63
77
  end
64
78
 
65
79
  RSpec.configure do |config|
66
- config.include(BeRedirect)
80
+ config.include RedirectTo
67
81
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-canonical-host
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Hunt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-28 00:00:00.000000000 Z
11
+ date: 2016-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -99,7 +99,8 @@ files:
99
99
  - rack-canonical-host.gemspec
100
100
  - spec/rack/canonical_host_spec.rb
101
101
  - spec/spec_helper.rb
102
- - spec/support/matchers/be_redirect.rb
102
+ - spec/support/matchers/have_header.rb
103
+ - spec/support/matchers/redirect_to.rb
103
104
  homepage: http://github.com/tylerhunt/rack-canonical-host
104
105
  licenses: []
105
106
  metadata: {}
@@ -126,4 +127,5 @@ summary: Rack middleware for defining a canonical host name.
126
127
  test_files:
127
128
  - spec/rack/canonical_host_spec.rb
128
129
  - spec/spec_helper.rb
129
- - spec/support/matchers/be_redirect.rb
130
+ - spec/support/matchers/have_header.rb
131
+ - spec/support/matchers/redirect_to.rb