rack-steady_etag 0.2.3 → 0.3.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
  SHA256:
3
- metadata.gz: '028b0d0f93545fddbfef80b9154a815c230d200f052d352645e00e8d1a147379'
4
- data.tar.gz: 1c1851fbdb760066488caf9baadb3dae9682d20f149dbb34c95c2502e8781e34
3
+ metadata.gz: bcfda15fd495c39d8c54909f7c634bc117180278a69d1d5950009c5e50b65f66
4
+ data.tar.gz: 77fc5f0e7e53af4dd3f30aaa9617775f636e38b340a3cd0789dfbc1db3473a1f
5
5
  SHA512:
6
- metadata.gz: 6a35c285174946b4c3c7116eb270fc03ed0ef60722c5b13801690ee89e516688b7135088eca05825a58e8893c9d1ba12de25edc3900591aa8f6d51d2f1c972b7
7
- data.tar.gz: 2dd6b70d8fff2818a9da44437d8dd79b8feeb242cf27fc9edde6d94db11b662ed9f52af32e8f8e49979683371e622d48785779a417163ee8df22e97ae3fc59bf
6
+ metadata.gz: 769df0e0d8ef0f037f4c176b83f6639a08fce011c18bcc6d718fb5b2b56040be2512c751fb77b17c4d9da65ceae8f72d785a3a4cc4fa0b3b670824dbf9666223
7
+ data.tar.gz: 8af6b7f73d97c396e6acbdc505a28fe0519f765684aad151d19cef2e1b0d79c2e364fb929a153a58d07d364d2b4bb23df677214f074f3e1fa971f787e2d6f69e
data/CHANGELOG.md CHANGED
@@ -2,9 +2,21 @@ All notable changes to this project will be documented in this file.
2
2
 
3
3
  This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
4
4
 
5
-
6
5
  ## Unreleased
7
6
 
7
+ ## 0.3.2 - 2022-07-29
8
+
9
+ - Digest includes the [unmasked Rails CSRF token](https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html#method-i-real_csrf_token) in case a Rails controller manually rotates the token.
10
+
11
+ ## 0.3.1 - 2022-07-19
12
+
13
+ - Fix a bug where we would not strip HTML responses with an embedded charset (e.g. `text/html; charset=utf-8`).
14
+
15
+ ## 0.3.0 - 2022-05-13
16
+
17
+ - Support for old Rack 1.4.7 (last version supported by Rails 3.2)
18
+ - No longer depends on activesupport.
19
+
8
20
  ## 0.2.3 - 2022-05-12
9
21
 
10
22
  - Don't depend on `byebug` being in the user bundle.
data/Gemfile CHANGED
@@ -5,8 +5,7 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in rack-steady-etag.gemspec
6
6
  gemspec
7
7
 
8
+ gem "rack", "~> 2.0"
8
9
  gem "rake", "~> 13.0"
9
-
10
10
  gem "rspec", "~> 3.0"
11
-
12
11
  gem 'byebug'
data/Gemfile.lock CHANGED
@@ -1,24 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rack-steady_etag (0.2.3)
5
- activesupport (>= 3.2)
6
- rack (~> 2.0)
4
+ rack-steady_etag (0.3.2)
5
+ rack (>= 1.4.7, < 3)
7
6
 
8
7
  GEM
9
8
  remote: https://rubygems.org/
10
9
  specs:
11
- activesupport (7.0.1)
12
- concurrent-ruby (~> 1.0, >= 1.0.2)
13
- i18n (>= 1.6, < 2)
14
- minitest (>= 5.1)
15
- tzinfo (~> 2.0)
16
10
  byebug (11.1.3)
17
- concurrent-ruby (1.1.10)
18
11
  diff-lcs (1.4.4)
19
- i18n (1.10.0)
20
- concurrent-ruby (~> 1.0)
21
- minitest (5.15.0)
22
12
  rack (2.2.3)
23
13
  rake (13.0.6)
24
14
  rspec (3.10.0)
@@ -34,14 +24,13 @@ GEM
34
24
  diff-lcs (>= 1.2.0, < 2.0)
35
25
  rspec-support (~> 3.10.0)
36
26
  rspec-support (3.10.3)
37
- tzinfo (2.0.4)
38
- concurrent-ruby (~> 1.0)
39
27
 
40
28
  PLATFORMS
41
29
  x86_64-linux
42
30
 
43
31
  DEPENDENCIES
44
32
  byebug
33
+ rack (~> 2.0)
45
34
  rack-steady_etag!
46
35
  rake (~> 13.0)
47
36
  rspec (~> 3.0)
data/Gemfile.rack1 ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in rack-steady-etag.gemspec
6
+ gemspec
7
+
8
+ gem "rack", "= 1.4.7"
9
+ gem "rake", "~> 13.0"
10
+ gem "rspec", "~> 3.0"
11
+ gem 'byebug'
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-steady_etag (0.3.0)
5
+ rack (>= 1.4.7, < 3)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (11.1.3)
11
+ diff-lcs (1.4.4)
12
+ rack (1.4.7)
13
+ rake (13.0.6)
14
+ rspec (3.10.0)
15
+ rspec-core (~> 3.10.0)
16
+ rspec-expectations (~> 3.10.0)
17
+ rspec-mocks (~> 3.10.0)
18
+ rspec-core (3.10.1)
19
+ rspec-support (~> 3.10.0)
20
+ rspec-expectations (3.10.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.10.0)
23
+ rspec-mocks (3.10.2)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.10.0)
26
+ rspec-support (3.10.3)
27
+
28
+ PLATFORMS
29
+ x86_64-linux
30
+
31
+ DEPENDENCIES
32
+ byebug
33
+ rack (= 1.4.7)
34
+ rack-steady_etag!
35
+ rake (~> 13.0)
36
+ rspec (~> 3.0)
37
+
38
+ BUNDLED WITH
39
+ 2.2.32
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rack::SteadyETag
2
2
 
3
- `Rack::SteadyTag` is a Rack middleware that generates the same default [`ETag`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) for responses that only differ in CSRF tokens or CSP nonces.
3
+ `Rack::SteadyETag` is a Rack middleware that generates the same default [`ETag`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) for responses that only differ in XOR-masked CSRF tokens or CSP nonces.
4
4
 
5
5
  By default Rails uses [`Rack::ETag`](https://rdoc.info/github/rack/rack/Rack/ETag) to generate `ETag` headers by hashing the response body. In theory this would [enable caching](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) for multiple requests to the same resource. However, since most Rails application layouts insert randomly rotating CSRF tokens and CSP nonces into the HTML, two requests for the same content and user will never produce the same response bytes. This means `Rack::ETag` will never send the same ETag twice, causing responses to [never hit a cache](https://github.com/rails/rails/issues/29889).
6
6
 
@@ -46,6 +46,7 @@ This middleware can also add a default `Cache-Control` header for responses it *
46
46
  ## Covered edge cases
47
47
 
48
48
  - Different `ETags` are generated when the same content is accessed with different Rack sessions.
49
+ - Different `ETags` are generated when a Rails controller manually rotates the CSRF token.
49
50
  - `ETags` are only generated when the response is `Cache-Control: private` (this is a default in Rails).
50
51
  - No `ETag` is generated when the response already has an `ETag` header.
51
52
  - No `ETag` is generated when the response already has an `Last-Modified` header.
@@ -65,10 +66,10 @@ And then execute:
65
66
  bundle install
66
67
  ```
67
68
 
68
- In your `config/application.rb`:
69
+ Make an initializer `config/initializer/etags.rb`:
69
70
 
70
71
  ```ruby
71
- config.middleware.swap Rack::ETag, Rack::SteadyETag, 'no-cache'
72
+ Rails.application.config.middleware.swap Rack::ETag, Rack::SteadyETag, 'no-cache'
72
73
  ```
73
74
 
74
75
  The `'no-cache'` argument is the default `Cache-Control` for responses that cannot be digested. While it may feel surprising that the middleware changes the `Cache-Control` header in such a case, the [Rails default middleware stack](https://github.com/rails/rails/blob/d96609505511a76c618dc3adfa3ca4679317d008/railties/lib/rails/application/default_middleware_stack.rb#L81) configures the same behavior.
@@ -78,6 +79,7 @@ The `'no-cache'` argument is the default `Cache-Control` for responses that cann
78
79
 
79
80
  - After checking out the repo, run `bin/setup` to install dependencies.
80
81
  - Run `bundle exec rspec` to run the tests.
82
+ - Run `BUNDLE_GEMFILE=Gemfile.rack1 bundle exec rspec` to run tests for old Rack 1.
81
83
  - You can also run `bin/console` for an interactive prompt that will allow you to experiment.
82
84
  - To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
83
85
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  class SteadyEtag
5
- VERSION = "0.2.3"
5
+ VERSION = "0.3.2"
6
6
  end
7
7
  end
@@ -1,6 +1,5 @@
1
+ require "rack"
1
2
  require 'digest/sha2'
2
- require "active_support/all"
3
- require_relative "steady_etag"
4
3
  require_relative "steady_etag/version"
5
4
 
6
5
  module Rack
@@ -42,8 +41,8 @@ module Rack
42
41
 
43
42
  def call(env)
44
43
  status, headers, body = @app.call(env)
45
- headers = Utils::HeaderHash[headers]
46
- session = env[RACK_SESSION]
44
+ headers = case_insensitive_headers(headers)
45
+ session = env['rack.session']
47
46
 
48
47
  if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
49
48
  original_body = body
@@ -51,7 +50,7 @@ module Rack
51
50
  body = Rack::BodyProxy.new(new_body) do
52
51
  original_body.close if original_body.respond_to?(:close)
53
52
  end
54
- headers[ETAG] = %(W/"#{digest}") if digest
53
+ headers['ETag'] = %(W/"#{digest}") if digest
55
54
  end
56
55
 
57
56
  # It would make more sense to only set a Cache-Control for responses that we process.
@@ -71,12 +70,25 @@ module Rack
71
70
 
72
71
  private
73
72
 
73
+ # HTTP headers are case-insensitive.
74
+ # Wrap hedders into a hash with case-insensitive keys
75
+ def case_insensitive_headers(headers)
76
+ case Rack.release[0]
77
+ when '1'
78
+ Utils::HeaderHash.new(headers)
79
+ when '2'
80
+ Utils::HeaderHash[headers]
81
+ when '3'
82
+ raise "HeaderHash will be removed in Rack 3. Probably switch to new Headers."
83
+ end
84
+ end
85
+
74
86
  def set_cache_control_with_digest(headers)
75
- headers[CACHE_CONTROL] ||= @digest_cache_control if @digest_cache_control
87
+ headers['Cache-Control'] ||= @digest_cache_control if @digest_cache_control
76
88
  end
77
89
 
78
90
  def set_cache_control_without_digest(headers)
79
- headers[CACHE_CONTROL] ||= @no_digest_cache_control if @no_digest_cache_control
91
+ headers['Cache-Control'] ||= @no_digest_cache_control if @no_digest_cache_control
80
92
  end
81
93
 
82
94
  def etag_status?(status)
@@ -91,14 +103,14 @@ module Rack
91
103
  end
92
104
 
93
105
  def skip_caching?(headers)
94
- headers.key?(ETAG) || headers.key?('Last-Modified')
106
+ headers.key?('ETag') || headers.key?('Last-Modified')
95
107
  end
96
108
 
97
109
  def digest_body(body, headers, session)
98
110
  parts = []
99
111
  digest = nil
100
112
 
101
- strippable_response = STRIP_CONTENT_TYPES.include?(headers['Content-Type'])
113
+ strippable_response = strippable_response?(headers)
102
114
 
103
115
  body.each do |part|
104
116
  parts << part
@@ -123,8 +135,17 @@ module Rack
123
135
  def initialize_digest(session)
124
136
  digest = Digest::SHA256.new
125
137
 
126
- if session && (session_id = session['session_id'])
127
- digest << session_id.to_s
138
+ if session
139
+ if (session_id = session['session_id'])
140
+ digest << session_id.to_s
141
+ end
142
+
143
+ # When we sign in or out with Devise, we always get a new session ID
144
+ # and CSRF token. Lets anyway include the real (unmasked) CSRF token in the
145
+ # digest in case a Rails controller manually rotates the token.
146
+ if (rails_csrf_token = session['_csrf_token'])
147
+ digest << rails_csrf_token.to_s
148
+ end
128
149
  end
129
150
 
130
151
  digest
@@ -141,5 +162,16 @@ module Rack
141
162
  html
142
163
  end
143
164
 
165
+ private
166
+
167
+ def strippable_response?(headers)
168
+ content_type = headers['Content-Type']
169
+ return false unless content_type
170
+
171
+ # Convert "text/tml; charset=utf-8" to just "text/html"
172
+ content_type = content_type.split(/\s*;\s*/)[0]
173
+ STRIP_CONTENT_TYPES.include?(content_type)
174
+ end
175
+
144
176
  end
145
177
  end
@@ -31,10 +31,10 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ["lib"]
33
33
 
34
+ # Rack 1.4.7 is the last version compatible with Rails 3.2.
35
+ #
34
36
  # I expect Rack 3 to have a new ETag middleware to no longer buffer
35
37
  # streaming responses: https://github.com/rack/rack/issues/1619
36
38
  # Once Rack 3 is out we should release a new version of this gem.
37
- spec.add_dependency "rack", '~>2.0'
38
-
39
- spec.add_dependency 'activesupport', '>= 3.2'
39
+ spec.add_dependency "rack", '>=1.4.7', '<3'
40
40
  end
metadata CHANGED
@@ -1,43 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-steady_etag
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henning Koch
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-12 00:00:00.000000000 Z
11
+ date: 2022-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.0'
27
- - !ruby/object:Gem::Dependency
28
- name: activesupport
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
17
  - - ">="
32
18
  - !ruby/object:Gem::Version
33
- version: '3.2'
19
+ version: 1.4.7
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
34
23
  type: :runtime
35
24
  prerelease: false
36
25
  version_requirements: !ruby/object:Gem::Requirement
37
26
  requirements:
38
27
  - - ">="
39
28
  - !ruby/object:Gem::Version
40
- version: '3.2'
29
+ version: 1.4.7
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
41
33
  description: Rack Middleware that produces the same ETag for responses that only differ
42
34
  in CSRF tokens or CSP nonce
43
35
  email:
@@ -51,6 +43,8 @@ files:
51
43
  - CHANGELOG.md
52
44
  - Gemfile
53
45
  - Gemfile.lock
46
+ - Gemfile.rack1
47
+ - Gemfile.rack1.lock
54
48
  - LICENSE.txt
55
49
  - README.md
56
50
  - Rakefile