data_style_sanitizer 0.1.0 → 0.2.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: 3817b2ce8055b36be47116321a6e62265ae6fdcfbc7c12aa1882f084276bdc6e
4
- data.tar.gz: b57bf6ca274a8e139379ff502b0958ac465183be180655fc3ef8edd0d1ffe0e7
3
+ metadata.gz: 92d2a1679e57bb4634ab85c0e4883314fde9f7380f0da02a81ef7939e7f7833c
4
+ data.tar.gz: 0a71f5c5c1ef2abf82d5630a055ff663d0597b663467b449a115119a3ac149f9
5
5
  SHA512:
6
- metadata.gz: c0a0239a32f913f42e12604f34646e06c337daf2ea74d58189fb729dcb3346ccc823fdfcc8fafa87f2bbdc19069346fef48f68d17c85fe2169ad11e18d09c8ef
7
- data.tar.gz: 2584f0c30cd3f3b0d7a63a6d10b73e2c38c5e103d0202d48d3f3ebdacff7c39dd3577344a060ad8b89693d1ae6604b81f588ab12ce149ad4c148ff4ac5e1ef6e
6
+ metadata.gz: bb8c460227b1839353264d939293563ed458b7e6546c306784d1c69e95e5fb925ebf5e33109272cd3720184d2fed2d4791158864ec695b828608038cc15b077f
7
+ data.tar.gz: cbedd72c8a7601e7ff045055e75519db982cb7a100aeb8253b041512f451cdae3a703e9d44ac52c3916a4f2b91350a515b660c9d1aa7d7142d4991c4d737a3cb
@@ -1,50 +1,38 @@
1
- # lib/data_style_sanitizer/middleware.rb
2
- require "nokogiri"
3
- require "digest"
1
+ require_relative "data_style_sanitizer/processor"
4
2
 
5
- class DataStyleSanitizer
6
- def initialize(app)
7
- @app = app
8
- end
3
+ module DataStyleSanitizer
4
+ class Middleware
5
+ def initialize(app)
6
+ @app = app
7
+ end
9
8
 
10
- def call(env)
11
- status, headers, response = @app.call(env)
9
+ def call(env)
10
+ status, headers, response = @app.call(env)
12
11
 
13
- if html_response?(headers)
14
- body = response.body.join
15
- doc = Nokogiri::HTML(body)
16
- style_map = {}
12
+ if html_response?(headers)
13
+ body = +""
14
+ response.each { |part| body << part }
17
15
 
18
- doc.css("[data-style]").each do |el|
19
- style = el["data-style"].strip
20
- class_name = "ds-#{Digest::MD5.hexdigest(style)[0..6]}"
21
- el.remove_attribute("data-style")
22
- el["class"] = [el["class"], class_name].compact.join(" ")
23
- style_map[class_name] ||= style
24
- end
16
+ nonce = extract_nonce_from_env(env)
17
+ processed = Processor.new(body, nonce: nonce).process
25
18
 
26
- if style_map.any?
27
- nonce = extract_nonce(doc)
28
- style_tag = Nokogiri::XML::Node.new("style", doc)
29
- style_tag["nonce"] = nonce if nonce
30
- style_tag.content = style_map.map { |klass, style| ".#{klass} { #{style} }" }.join("\n")
31
- doc.at("head") << style_tag
19
+ headers["Content-Length"] = processed.bytesize.to_s
20
+ [status, headers, [processed]]
21
+ else
22
+ [status, headers, response]
32
23
  end
33
-
34
- response = [doc.to_html]
35
24
  end
36
25
 
37
- [status, headers, response]
38
- end
39
-
40
- private
26
+ private
41
27
 
42
- def html_response?(headers)
43
- headers["Content-Type"]&.include?("text/html")
44
- end
28
+ def html_response?(headers)
29
+ headers["Content-Type"]&.include?("text/html")
30
+ end
45
31
 
46
- def extract_nonce(doc)
47
- meta = doc.at('meta[name="csp-nonce"]')
48
- meta["content"] if meta
32
+ def extract_nonce_from_env(env)
33
+ if env["action_dispatch.content_security_policy_nonce"].respond_to?(:call)
34
+ env["action_dispatch.content_security_policy_nonce"].call(:style)
35
+ end
36
+ end
49
37
  end
50
38
  end
@@ -19,8 +19,16 @@ module DataStyleSanitizer
19
19
  private
20
20
 
21
21
  def extract_styles
22
- @doc.css("[data-style]").each_with_index do |node, i|
22
+ @doc.css("[data-style]").each do |node|
23
23
  style_string = node.get_attribute("data-style")
24
+ next if style_string.nil? || style_string.strip.empty? # Skip empty attributes
25
+
26
+ # Remove CSS comments and normalize spacing
27
+ style_string = style_string.gsub(/\/\*.*?\*\//, "").strip
28
+ style_string = style_string.split(";").map(&:strip).reject(&:empty?).join("; ")
29
+
30
+ next if style_string.empty? # Skip if the style becomes empty after cleaning
31
+
24
32
  class_name = generate_class_name(style_string)
25
33
 
26
34
  # Apply class and remove attribute
@@ -33,7 +41,7 @@ module DataStyleSanitizer
33
41
  end
34
42
 
35
43
  def generate_class_name(style_string)
36
- hash = Digest::SHA256.hexdigest(style_string)[0..7]
44
+ hash = Digest::SHA256.hexdigest(style_string.downcase)[0..7] # Ensure case-insensitivity
37
45
  "ds-#{hash}"
38
46
  end
39
47
 
@@ -53,12 +61,17 @@ module DataStyleSanitizer
53
61
 
54
62
  style_tag.content = css_rules
55
63
 
56
- # Add the <style> tag to the <head> if it exists, otherwise to the root
64
+ # Add the <style> tag to the <head> if it exists, otherwise to the <body> or fragment
57
65
  head = @doc.at_css("head")
58
66
  if head
59
67
  head.add_child(style_tag)
60
68
  else
61
- @doc.add_child(style_tag)
69
+ body = @doc.at_css("body")
70
+ if body
71
+ body.add_child(style_tag)
72
+ else
73
+ @doc.add_child(style_tag) # Append directly to the fragment
74
+ end
62
75
  end
63
76
  end
64
77
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DataStyleSanitizer
4
+ module Rails
5
+ module ControllerIntegration
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ after_action :inject_data_style_sanitizer_styles
10
+ end
11
+
12
+ private
13
+
14
+ def inject_data_style_sanitizer_styles
15
+ return unless html_response? && response.body.include?("data-style")
16
+
17
+ nonce = begin
18
+ content_security_policy_nonce(:style)
19
+ rescue
20
+ nil
21
+ end
22
+ style_block = DataStyleSanitizer::Renderer.generate_style_block(response.body, nonce: nonce)
23
+
24
+ # Inject into <head>
25
+ response.body.sub!("</head>", "#{style_block}</head>")
26
+ end
27
+
28
+ def html_response?
29
+ response.content_type == "text/html"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,5 @@
1
+ require "rails/railtie"
2
+
1
3
  module DataStyleSanitizer
2
4
  class Railtie < Rails::Railtie
3
5
  initializer "data_style_sanitizer.middleware" do |app|
@@ -14,14 +16,13 @@ module DataStyleSanitizer
14
16
  status, headers, response = @app.call(env)
15
17
 
16
18
  if headers["Content-Type"]&.include?("text/html")
17
- body = ""
19
+ body = +""
18
20
  response.each { |part| body << part }
19
21
 
20
- nonce = env["secure_headers_nonce"] || extract_nonce(env)
22
+ nonce = extract_nonce(env)
23
+ new_body = DataStyleSanitizer.process(body, nonce: nonce)
21
24
 
22
- new_body = DataStyleSanitizer.sanitize_html(body, nonce: nonce)
23
25
  headers["Content-Length"] = new_body.bytesize.to_s
24
-
25
26
  [status, headers, [new_body]]
26
27
  else
27
28
  [status, headers, response]
@@ -31,12 +32,7 @@ module DataStyleSanitizer
31
32
  private
32
33
 
33
34
  def extract_nonce(env)
34
- # Customize based on how you expose CSP nonce
35
- if env["action_dispatch.content_security_policy_nonce"]
36
- env["action_dispatch.content_security_policy_nonce"].call
37
- else
38
- SecureRandom.base64(16) # fallback
39
- end
35
+ env.dig("action_dispatch.content_security_policy_nonce", :style)
40
36
  end
41
37
  end
42
38
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DataStyleSanitizer
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: data_style_sanitizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - tedaford
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-07 00:00:00.000000000 Z
11
+ date: 2025-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -53,8 +53,9 @@ files:
53
53
  - lib/data_style_sanitizer.rb
54
54
  - lib/data_style_sanitizer/middleware.rb
55
55
  - lib/data_style_sanitizer/processor.rb
56
+ - lib/data_style_sanitizer/rails/controller_integration.rb
57
+ - lib/data_style_sanitizer/railtie.rb
56
58
  - lib/data_style_sanitizer/version.rb
57
- - lib/railtie.rb
58
59
  homepage: https://github.com/tedaford/data_style_sanitizer
59
60
  licenses:
60
61
  - MIT