rails-html-sanitizer 1.5.0 → 1.6.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.
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
- module Html
3
- # === Rails::Html::PermitScrubber
4
+ module HTML
5
+ # === Rails::HTML::PermitScrubber
4
6
  #
5
- # +Rails::Html::PermitScrubber+ allows you to permit only your own tags and/or attributes.
7
+ # +Rails::HTML::PermitScrubber+ allows you to permit only your own tags and/or attributes.
6
8
  #
7
- # +Rails::Html::PermitScrubber+ can be subclassed to determine:
9
+ # +Rails::HTML::PermitScrubber+ can be subclassed to determine:
8
10
  # - When a node should be skipped via +skip_node?+.
9
11
  # - When a node is allowed via +allowed_node?+.
10
12
  # - When an attribute should be scrubbed via +scrub_attribute?+.
@@ -27,7 +29,7 @@ module Rails
27
29
  # If set, attributes excluded will be removed.
28
30
  # If not, attributes are removed based on Loofahs +HTML5::Scrub.scrub_attributes+.
29
31
  #
30
- # class CommentScrubber < Html::PermitScrubber
32
+ # class CommentScrubber < Rails::HTML::PermitScrubber
31
33
  # def initialize
32
34
  # super
33
35
  # self.tags = %w(form script comment blockquote)
@@ -54,11 +56,11 @@ module Rails
54
56
  end
55
57
 
56
58
  def tags=(tags)
57
- @tags = validate!(tags, :tags)
59
+ @tags = validate!(tags.dup, :tags)
58
60
  end
59
61
 
60
62
  def attributes=(attributes)
61
- @attributes = validate!(attributes, :attributes)
63
+ @attributes = validate!(attributes.dup, :attributes)
62
64
  end
63
65
 
64
66
  def scrub(node)
@@ -70,97 +72,120 @@ module Rails
70
72
  return CONTINUE if skip_node?(node)
71
73
 
72
74
  unless (node.element? || node.comment?) && keep_node?(node)
73
- return STOP if scrub_node(node) == STOP
75
+ return STOP unless scrub_node(node) == CONTINUE
74
76
  end
75
77
 
76
78
  scrub_attributes(node)
79
+ CONTINUE
77
80
  end
78
81
 
79
82
  protected
83
+ def allowed_node?(node)
84
+ @tags.include?(node.name)
85
+ end
80
86
 
81
- def allowed_node?(node)
82
- @tags.include?(node.name)
83
- end
84
-
85
- def skip_node?(node)
86
- node.text?
87
- end
88
-
89
- def scrub_attribute?(name)
90
- !@attributes.include?(name)
91
- end
92
-
93
- def keep_node?(node)
94
- if @tags
95
- allowed_node?(node)
96
- else
97
- Loofah::HTML5::Scrub.allowed_element?(node.name)
87
+ def skip_node?(node)
88
+ node.text?
98
89
  end
99
- end
100
90
 
101
- def scrub_node(node)
102
- node.before(node.children) unless prune # strip
103
- node.remove
104
- end
91
+ def scrub_attribute?(name)
92
+ !@attributes.include?(name)
93
+ end
105
94
 
106
- def scrub_attributes(node)
107
- if @attributes
108
- node.attribute_nodes.each do |attr|
109
- attr.remove if scrub_attribute?(attr.name)
110
- scrub_attribute(node, attr)
95
+ def keep_node?(node)
96
+ if @tags
97
+ allowed_node?(node)
98
+ else
99
+ Loofah::HTML5::Scrub.allowed_element?(node.name)
111
100
  end
101
+ end
112
102
 
113
- scrub_css_attribute(node)
114
- else
115
- Loofah::HTML5::Scrub.scrub_attributes(node)
103
+ def scrub_node(node)
104
+ # If a node has a namespace, then it's a tag in either a `math` or `svg` foreign context,
105
+ # and we should always prune it to avoid namespace confusion and mutation XSS vectors.
106
+ unless prune || node.namespace
107
+ node.before(node.children)
108
+ end
109
+ node.remove
116
110
  end
117
- end
118
111
 
119
- def scrub_css_attribute(node)
120
- if Loofah::HTML5::Scrub.respond_to?(:scrub_css_attribute)
121
- Loofah::HTML5::Scrub.scrub_css_attribute(node)
122
- else
123
- style = node.attributes['style']
124
- style.value = Loofah::HTML5::Scrub.scrub_css(style.value) if style
112
+ def scrub_attributes(node)
113
+ if @attributes
114
+ node.attribute_nodes.each do |attr|
115
+ if scrub_attribute?(attr.name)
116
+ attr.remove
117
+ else
118
+ scrub_attribute(node, attr)
119
+ end
120
+ end
121
+
122
+ scrub_css_attribute(node)
123
+ else
124
+ Loofah::HTML5::Scrub.scrub_attributes(node)
125
+ end
125
126
  end
126
- end
127
127
 
128
- def validate!(var, name)
129
- if var && !var.is_a?(Enumerable)
130
- raise ArgumentError, "You should pass :#{name} as an Enumerable"
128
+ def scrub_css_attribute(node)
129
+ if Loofah::HTML5::Scrub.respond_to?(:scrub_css_attribute)
130
+ Loofah::HTML5::Scrub.scrub_css_attribute(node)
131
+ else
132
+ style = node.attributes["style"]
133
+ style.value = Loofah::HTML5::Scrub.scrub_css(style.value) if style
134
+ end
131
135
  end
132
- var
133
- end
134
136
 
135
- def scrub_attribute(node, attr_node)
136
- attr_name = if attr_node.namespace
137
- "#{attr_node.namespace.prefix}:#{attr_node.node_name}"
138
- else
139
- attr_node.node_name
140
- end
137
+ def validate!(var, name)
138
+ if var && !var.is_a?(Enumerable)
139
+ raise ArgumentError, "You should pass :#{name} as an Enumerable"
140
+ end
141
141
 
142
- if Loofah::HTML5::SafeList::ATTR_VAL_IS_URI.include?(attr_name)
143
- return if Loofah::HTML5::Scrub.scrub_uri_attribute(attr_node)
144
- end
142
+ if var && name == :tags
143
+ if var.include?("mglyph")
144
+ warn("WARNING: 'mglyph' tags cannot be allowed by the PermitScrubber and will be scrubbed")
145
+ var.delete("mglyph")
146
+ end
147
+
148
+ if var.include?("malignmark")
149
+ warn("WARNING: 'malignmark' tags cannot be allowed by the PermitScrubber and will be scrubbed")
150
+ var.delete("malignmark")
151
+ end
152
+
153
+ if var.include?("noscript")
154
+ warn("WARNING: 'noscript' tags cannot be allowed by the PermitScrubber and will be scrubbed")
155
+ var.delete("noscript")
156
+ end
157
+ end
145
158
 
146
- if Loofah::HTML5::SafeList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name)
147
- Loofah::HTML5::Scrub.scrub_attribute_that_allows_local_ref(attr_node)
159
+ var
148
160
  end
149
161
 
150
- if Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && attr_name == 'xlink:href' && attr_node.value =~ /^\s*[^#\s].*/m
151
- attr_node.remove
152
- end
162
+ def scrub_attribute(node, attr_node)
163
+ attr_name = if attr_node.namespace
164
+ "#{attr_node.namespace.prefix}:#{attr_node.node_name}"
165
+ else
166
+ attr_node.node_name
167
+ end
153
168
 
154
- node.remove_attribute(attr_node.name) if attr_name == 'src' && attr_node.value !~ /[^[:space:]]/
169
+ return if Loofah::HTML5::SafeList::ATTR_VAL_IS_URI.include?(attr_name) && Loofah::HTML5::Scrub.scrub_uri_attribute(attr_node)
155
170
 
156
- Loofah::HTML5::Scrub.force_correct_attribute_escaping! node
157
- end
171
+ if Loofah::HTML5::SafeList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name)
172
+ Loofah::HTML5::Scrub.scrub_attribute_that_allows_local_ref(attr_node)
173
+ end
174
+
175
+ if Loofah::HTML5::SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && attr_name == "xlink:href" && attr_node.value =~ /^\s*[^#\s].*/m
176
+ attr_node.remove
177
+ end
178
+
179
+ node.remove_attribute(attr_node.name) if attr_name == "src" && attr_node.value !~ /[^[:space:]]/
180
+
181
+ Loofah::HTML5::Scrub.force_correct_attribute_escaping! node
182
+ end
158
183
  end
159
184
 
160
- # === Rails::Html::TargetScrubber
185
+ # === Rails::HTML::TargetScrubber
161
186
  #
162
- # Where +Rails::Html::PermitScrubber+ picks out tags and attributes to permit in
163
- # sanitization, +Rails::Html::TargetScrubber+ targets them for removal.
187
+ # Where +Rails::HTML::PermitScrubber+ picks out tags and attributes to permit in
188
+ # sanitization, +Rails::HTML::TargetScrubber+ targets them for removal.
164
189
  #
165
190
  # +tags=+
166
191
  # If set, elements included will be stripped.
@@ -177,9 +202,9 @@ module Rails
177
202
  end
178
203
  end
179
204
 
180
- # === Rails::Html::TextOnlyScrubber
205
+ # === Rails::HTML::TextOnlyScrubber
181
206
  #
182
- # +Rails::Html::TextOnlyScrubber+ allows you to permit text nodes.
207
+ # +Rails::HTML::TextOnlyScrubber+ allows you to permit text nodes.
183
208
  #
184
209
  # Unallowed elements will be stripped, i.e. element is removed but its subtree kept.
185
210
  class TextOnlyScrubber < Loofah::Scrubber
@@ -1,30 +1,14 @@
1
- require "rails/html/sanitizer/version"
2
- require "loofah"
3
- require "rails/html/scrubbers"
4
- require "rails/html/sanitizer"
1
+ # frozen_string_literal: true
5
2
 
6
- module Rails
7
- module Html
8
- class Sanitizer
9
- class << self
10
- def full_sanitizer
11
- Html::FullSanitizer
12
- end
3
+ require_relative "rails/html/sanitizer/version"
13
4
 
14
- def link_sanitizer
15
- Html::LinkSanitizer
16
- end
5
+ require "loofah"
17
6
 
18
- def safe_list_sanitizer
19
- Html::SafeListSanitizer
20
- end
7
+ require_relative "rails/html/scrubbers"
8
+ require_relative "rails/html/sanitizer"
21
9
 
22
- def white_list_sanitizer
23
- safe_list_sanitizer
24
- end
25
- end
26
- end
27
- end
10
+ module Rails
11
+ Html = HTML # :nodoc:
28
12
  end
29
13
 
30
14
  module ActionView
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require "rails-html-sanitizer"
5
+
6
+ class RailsApiTest < Minitest::Test
7
+ def test_html_module_name_alias
8
+ assert_equal(Rails::Html, Rails::HTML)
9
+ assert_equal("Rails::HTML", Rails::Html.name)
10
+ assert_equal("Rails::HTML", Rails::HTML.name)
11
+ end
12
+
13
+ def test_html_scrubber_class_names
14
+ assert(Rails::Html::PermitScrubber)
15
+ assert(Rails::Html::TargetScrubber)
16
+ assert(Rails::Html::TextOnlyScrubber)
17
+ assert(Rails::Html::Sanitizer)
18
+ end
19
+
20
+ def test_best_supported_vendor_when_html5_is_not_supported_returns_html4
21
+ Rails::HTML::Sanitizer.stub(:html5_support?, false) do
22
+ assert_equal(Rails::HTML4::Sanitizer, Rails::HTML::Sanitizer.best_supported_vendor)
23
+ end
24
+ end
25
+
26
+ def test_best_supported_vendor_when_html5_is_supported_returns_html5
27
+ skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support?
28
+
29
+ Rails::HTML::Sanitizer.stub(:html5_support?, true) do
30
+ assert_equal(Rails::HTML5::Sanitizer, Rails::HTML::Sanitizer.best_supported_vendor)
31
+ end
32
+ end
33
+
34
+ def test_html4_sanitizer_alias_full
35
+ assert_equal(Rails::HTML4::FullSanitizer, Rails::HTML::FullSanitizer)
36
+ assert_equal("Rails::HTML4::FullSanitizer", Rails::HTML::FullSanitizer.name)
37
+ end
38
+
39
+ def test_html4_sanitizer_alias_link
40
+ assert_equal(Rails::HTML4::LinkSanitizer, Rails::HTML::LinkSanitizer)
41
+ assert_equal("Rails::HTML4::LinkSanitizer", Rails::HTML::LinkSanitizer.name)
42
+ end
43
+
44
+ def test_html4_sanitizer_alias_safe_list
45
+ assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML::SafeListSanitizer)
46
+ assert_equal("Rails::HTML4::SafeListSanitizer", Rails::HTML::SafeListSanitizer.name)
47
+ end
48
+
49
+ def test_html4_full_sanitizer
50
+ assert_equal(Rails::HTML4::FullSanitizer, Rails::HTML::Sanitizer.full_sanitizer)
51
+ assert_equal(Rails::HTML4::FullSanitizer, Rails::HTML4::Sanitizer.full_sanitizer)
52
+ end
53
+
54
+ def test_html4_link_sanitizer
55
+ assert_equal(Rails::HTML4::LinkSanitizer, Rails::HTML::Sanitizer.link_sanitizer)
56
+ assert_equal(Rails::HTML4::LinkSanitizer, Rails::HTML4::Sanitizer.link_sanitizer)
57
+ end
58
+
59
+ def test_html4_safe_list_sanitizer
60
+ assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML::Sanitizer.safe_list_sanitizer)
61
+ assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML4::Sanitizer.safe_list_sanitizer)
62
+ end
63
+
64
+ def test_html4_white_list_sanitizer
65
+ assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML::Sanitizer.white_list_sanitizer)
66
+ assert_equal(Rails::HTML4::SafeListSanitizer, Rails::HTML4::Sanitizer.white_list_sanitizer)
67
+ end
68
+
69
+ def test_html5_full_sanitizer
70
+ skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support?
71
+ assert_equal(Rails::HTML5::FullSanitizer, Rails::HTML5::Sanitizer.full_sanitizer)
72
+ end
73
+
74
+ def test_html5_link_sanitizer
75
+ skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support?
76
+ assert_equal(Rails::HTML5::LinkSanitizer, Rails::HTML5::Sanitizer.link_sanitizer)
77
+ end
78
+
79
+ def test_html5_safe_list_sanitizer
80
+ skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support?
81
+ assert_equal(Rails::HTML5::SafeListSanitizer, Rails::HTML5::Sanitizer.safe_list_sanitizer)
82
+ end
83
+
84
+ def test_html5_white_list_sanitizer
85
+ skip("no HTML5 support on this platform") unless Rails::HTML::Sanitizer.html5_support?
86
+ assert_equal(Rails::HTML5::SafeListSanitizer, Rails::HTML5::Sanitizer.white_list_sanitizer)
87
+ end
88
+ end