rack-disable_css_animations 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f4f052828fc1eef129c74f3265617450794ee647433a6be7e1a424ede568d6e
4
- data.tar.gz: 550bc940c02d553d40d4864b44750f924073b135d73e4aac7379d8d46350361d
3
+ metadata.gz: bad6feef29bab1e1a2a8e97b26657b599173d23b85fbb484a9b4cfe3efb05030
4
+ data.tar.gz: 3bca0f36e15ed91ecd7bae9deecec5d344cb82ae7cc27aae1e9064dd596a45e8
5
5
  SHA512:
6
- metadata.gz: cccb5748d6830a131c60f0d8d350ca8fe3d388e97b07cbe94819dbfea2bc14cf684b39530709c5f5adc2ddfd83919b6c062231942793894b3804b6a835932a71
7
- data.tar.gz: 51f0f41134048e3fc32865eab28dcf2492b5b09cfe9576fb31bef165a21e8ac109fa063f8028d534e2fff8543f691849d5031c06c8f0de7b8413b396b755cb55
6
+ metadata.gz: d4344c6fe201e54525d0c939c67466fe876fb305d88ec2dddd0d4d433d30efa4f4b28367e83d4bcdee5c2b0aa922fb4260d981426c57ced8711989fa8ad8fc85
7
+ data.tar.gz: d9bdb2f55b3dbe8e4ac7f2c3d5b50e35cf53b6f0082302623f6b56786eef363d35660af0915018573d6b0504c507fb4693eda74529e4c3d41b247724005e35bb
@@ -0,0 +1,18 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ strategy:
6
+ matrix:
7
+ ruby: [ "3.3", "3.4", "4.0" ]
8
+
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v5
12
+ - name: Set up Ruby
13
+ uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: ${{ matrix.ruby }}
16
+ bundler-cache: true
17
+ - name: Run the default task
18
+ run: bundle exec rake
data/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ ## 0.5.1
4
+
5
+ - Recognize the `Content-Security-Policy-Report-Only` header (in both canonical and lowercase form) when no enforcing `Content-Security-Policy` header is present, so the injected `<style>` tag's nonce matches in apps running CSP in report-only mode.
6
+ - Insert the middleware before `ActionDispatch::ContentSecurityPolicy::Middleware` so the CSP header has already been added to the response by the time we read it. Previously the middleware ran before CSP on the response, so the header was always absent.
7
+
8
+ ## 0.5.0
9
+
10
+ - Add CSP nonce support: when the response's `Content-Security-Policy` header sets a `style-src 'nonce-…'`, the injected `<style>` tag now carries a matching `nonce` attribute so it is not blocked by CSP.
11
+
12
+ ## 0.4.0
13
+
14
+ - Add stub for manual requiring.
15
+ - Automatically add to the middleware stack when required after Rails.
16
+
17
+ ## 0.3.0
18
+
19
+ - Disable `scroll-behavior` as well.
20
+ - CSS prefixes are no longer needed.
21
+
22
+ ## 0.2.0
23
+
24
+ - Actually disable the animations.
25
+ - `0` is not a valid value for the `animation-duration` property.
26
+
27
+ ## 0.1.1
28
+
29
+ - Use prefix methods too (PhantomJS needed the `-webkit` prefix).
30
+
31
+ ## 0.1.0
32
+
33
+ - Initial release.
data/Rakefile CHANGED
@@ -1,2 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rake/testtask"
2
3
 
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/test_*.rb"]
8
+ end
9
+
10
+ task default: :test
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class DisableCSSAnimations
3
- VERSION = "0.4.0"
3
+ VERSION = "0.5.1"
4
4
  end
5
5
  end
@@ -4,7 +4,11 @@ module Rack
4
4
  class DisableCSSAnimations
5
5
  if defined?(Rails)
6
6
  class Rails < Rails::Railtie
7
- config.app_middleware.use DisableCSSAnimations
7
+ initializer "rack-disable_css_animations.insert_middleware" do |app|
8
+ app.middleware.insert_before ActionDispatch::ContentSecurityPolicy::Middleware, DisableCSSAnimations
9
+ rescue RuntimeError
10
+ app.middleware.use DisableCSSAnimations
11
+ end
8
12
  end
9
13
  end
10
14
 
@@ -16,6 +20,8 @@ module Rack
16
20
  @status, @headers, @body = @app.call(env)
17
21
  return [@status, @headers, @body] unless html?
18
22
 
23
+ @style_nonce = directive_nonces["style-src"]
24
+
19
25
  response = Rack::Response.new([], @status, @headers)
20
26
  @body.each do |fragment|
21
27
  response.write inject(fragment)
@@ -31,9 +37,31 @@ module Rack
31
37
  @headers["Content-Type"] =~ /html/
32
38
  end
33
39
 
40
+ def csp_header
41
+ @headers["Content-Security-Policy"] ||
42
+ @headers["content-security-policy"] ||
43
+ @headers["Content-Security-Policy-Report-Only"] ||
44
+ @headers["content-security-policy-report-only"] ||
45
+ ""
46
+ end
47
+
48
+ def directive_nonces
49
+ csp_header.split(";").each_with_object({}) do |directive, nonces|
50
+ tokens = directive.split
51
+ name = tokens.shift
52
+ next unless name
53
+ nonce = tokens.find { |t| t =~ /\A'nonce-(.+)'\z/ } && $1
54
+ nonces[name] = nonce if nonce
55
+ end
56
+ end
57
+
58
+ def style_tag
59
+ @style_nonce ? %(<style nonce="#{@style_nonce}">) : "<style>"
60
+ end
61
+
34
62
  def inject response
35
63
  markup = <<-CSS
36
- <style>
64
+ #{style_tag}
37
65
  * {
38
66
  animation-delay: 0s !important;
39
67
  animation-duration: 0.01s !important;
@@ -22,4 +22,6 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency "bundler"
24
24
  spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "minitest"
26
+ spec.add_development_dependency "rack-test"
25
27
  end
@@ -0,0 +1,110 @@
1
+ require "minitest/autorun"
2
+ require "rack/test"
3
+ require "rack/disable_css_animations"
4
+
5
+ class TestDisableCSSAnimations < Minitest::Test
6
+ include Rack::Test::Methods
7
+
8
+ HTML_BODY = "<html><head><title>Test</title></head><body>hi</body></html>"
9
+
10
+ attr_accessor :response_status, :response_headers, :response_body
11
+
12
+ def app
13
+ outer_self = self
14
+ Rack::DisableCSSAnimations.new(lambda do |_env|
15
+ [outer_self.response_status, outer_self.response_headers, [outer_self.response_body]]
16
+ end)
17
+ end
18
+
19
+ def setup
20
+ self.response_status = 200
21
+ self.response_headers = { "Content-Type" => "text/html" }
22
+ self.response_body = HTML_BODY
23
+ end
24
+
25
+ def test_non_html_response_is_passed_through_unchanged
26
+ self.response_headers = { "Content-Type" => "application/json" }
27
+ self.response_body = %({"foo":"bar"})
28
+
29
+ get "/"
30
+
31
+ assert_equal %({"foo":"bar"}), last_response.body
32
+ end
33
+
34
+ def test_html_response_injects_style_tag
35
+ get "/"
36
+
37
+ assert_includes last_response.body, "<style>"
38
+ assert_includes last_response.body, "animation-duration: 0.01s !important"
39
+ refute_includes last_response.body, "nonce="
40
+ end
41
+
42
+ def test_injects_before_closing_head
43
+ get "/"
44
+
45
+ style_index = last_response.body.index("<style")
46
+ head_close_index = last_response.body.index("</head>")
47
+ assert style_index < head_close_index
48
+ end
49
+
50
+ def test_style_src_nonce_is_copied_onto_style_tag
51
+ self.response_headers["Content-Security-Policy"] = "style-src 'nonce-abc123' 'self'; script-src 'nonce-xyz789'"
52
+
53
+ get "/"
54
+
55
+ assert_includes last_response.body, %(<style nonce="abc123">)
56
+ refute_includes last_response.body, "<style>"
57
+ end
58
+
59
+ def test_csp_without_style_src_nonce_injects_plain_style_tag
60
+ self.response_headers["Content-Security-Policy"] = "default-src 'self'; script-src 'nonce-xyz789'"
61
+
62
+ get "/"
63
+
64
+ assert_includes last_response.body, "<style>"
65
+ refute_includes last_response.body, "nonce="
66
+ end
67
+
68
+ def test_csp_with_style_src_but_no_nonce_injects_plain_style_tag
69
+ self.response_headers["Content-Security-Policy"] = "style-src 'self' 'unsafe-inline'"
70
+
71
+ get "/"
72
+
73
+ assert_includes last_response.body, "<style>"
74
+ refute_includes last_response.body, "nonce="
75
+ end
76
+
77
+ def test_lowercase_csp_header_is_also_recognized
78
+ self.response_headers = { "Content-Type" => "text/html", "content-security-policy" => "style-src 'nonce-lower1'" }
79
+
80
+ get "/"
81
+
82
+ assert_includes last_response.body, %(<style nonce="lower1">)
83
+ end
84
+
85
+ def test_report_only_csp_header_nonce_is_used_when_enforcing_header_absent
86
+ self.response_headers["Content-Security-Policy-Report-Only"] = "style-src 'nonce-reportonly1'"
87
+
88
+ get "/"
89
+
90
+ assert_includes last_response.body, %(<style nonce="reportonly1">)
91
+ end
92
+
93
+ def test_lowercase_report_only_csp_header_is_also_recognized
94
+ self.response_headers = { "Content-Type" => "text/html", "content-security-policy-report-only" => "style-src 'nonce-reportonly2'" }
95
+
96
+ get "/"
97
+
98
+ assert_includes last_response.body, %(<style nonce="reportonly2">)
99
+ end
100
+
101
+ def test_enforcing_header_takes_precedence_over_report_only
102
+ self.response_headers["Content-Security-Policy"] = "style-src 'nonce-enforced'"
103
+ self.response_headers["Content-Security-Policy-Report-Only"] = "style-src 'nonce-reportonly'"
104
+
105
+ get "/"
106
+
107
+ assert_includes last_response.body, %(<style nonce="enforced">)
108
+ refute_includes last_response.body, "reportonly"
109
+ end
110
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-disable_css_animations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-08-19 00:00:00.000000000 Z
10
+ date: 2026-04-17 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rack
@@ -52,6 +51,34 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: minitest
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rack-test
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
55
82
  description: Rack middleware to disable CSS animations sitewide. Useful for making
56
83
  acceptance tests quicker and more deterministic.
57
84
  email:
@@ -61,7 +88,9 @@ executables:
61
88
  extensions: []
62
89
  extra_rdoc_files: []
63
90
  files:
91
+ - ".github/workflows/main.yml"
64
92
  - ".gitignore"
93
+ - CHANGELOG.md
65
94
  - Gemfile
66
95
  - LICENSE.txt
67
96
  - README.md
@@ -71,11 +100,11 @@ files:
71
100
  - lib/rack/disable_css_animations.rb
72
101
  - lib/rack/disable_css_animations/version.rb
73
102
  - rack-disable_css_animations.gemspec
103
+ - test/test_disable_css_animations.rb
74
104
  homepage: https://github.com/botandrose/rack-disable_css_animations
75
105
  licenses:
76
106
  - MIT
77
107
  metadata: {}
78
- post_install_message:
79
108
  rdoc_options: []
80
109
  require_paths:
81
110
  - lib
@@ -90,8 +119,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
119
  - !ruby/object:Gem::Version
91
120
  version: '0'
92
121
  requirements: []
93
- rubygems_version: 3.2.32
94
- signing_key:
122
+ rubygems_version: 3.6.2
95
123
  specification_version: 4
96
124
  summary: Rack middleware to disable CSS animations sitewide.
97
- test_files: []
125
+ test_files:
126
+ - test/test_disable_css_animations.rb