rack-canonical-host 0.2.1 → 0.2.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 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