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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +125 -0
- data/MIT-LICENSE +1 -1
- data/README.md +124 -72
- data/lib/rails/html/sanitizer/version.rb +4 -2
- data/lib/rails/html/sanitizer.rb +372 -104
- data/lib/rails/html/scrubbers.rb +98 -73
- data/lib/rails-html-sanitizer.rb +7 -23
- data/test/rails_api_test.rb +88 -0
- data/test/sanitizer_test.rb +1095 -584
- data/test/scrubbers_test.rb +129 -38
- metadata +68 -58
data/lib/rails/html/scrubbers.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rails
|
2
|
-
module
|
3
|
-
# === Rails::
|
4
|
+
module HTML
|
5
|
+
# === Rails::HTML::PermitScrubber
|
4
6
|
#
|
5
|
-
# +Rails::
|
7
|
+
# +Rails::HTML::PermitScrubber+ allows you to permit only your own tags and/or attributes.
|
6
8
|
#
|
7
|
-
# +Rails::
|
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 <
|
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
|
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
|
-
|
82
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
91
|
+
def scrub_attribute?(name)
|
92
|
+
!@attributes.include?(name)
|
93
|
+
end
|
105
94
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
147
|
-
Loofah::HTML5::Scrub.scrub_attribute_that_allows_local_ref(attr_node)
|
159
|
+
var
|
148
160
|
end
|
149
161
|
|
150
|
-
|
151
|
-
attr_node.
|
152
|
-
|
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
|
-
|
169
|
+
return if Loofah::HTML5::SafeList::ATTR_VAL_IS_URI.include?(attr_name) && Loofah::HTML5::Scrub.scrub_uri_attribute(attr_node)
|
155
170
|
|
156
|
-
|
157
|
-
|
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::
|
185
|
+
# === Rails::HTML::TargetScrubber
|
161
186
|
#
|
162
|
-
# Where +Rails::
|
163
|
-
# sanitization, +Rails::
|
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::
|
205
|
+
# === Rails::HTML::TextOnlyScrubber
|
181
206
|
#
|
182
|
-
# +Rails::
|
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
|
data/lib/rails-html-sanitizer.rb
CHANGED
@@ -1,30 +1,14 @@
|
|
1
|
-
|
2
|
-
require "loofah"
|
3
|
-
require "rails/html/scrubbers"
|
4
|
-
require "rails/html/sanitizer"
|
1
|
+
# frozen_string_literal: true
|
5
2
|
|
6
|
-
|
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
|
-
|
15
|
-
Html::LinkSanitizer
|
16
|
-
end
|
5
|
+
require "loofah"
|
17
6
|
|
18
|
-
|
19
|
-
|
20
|
-
end
|
7
|
+
require_relative "rails/html/scrubbers"
|
8
|
+
require_relative "rails/html/sanitizer"
|
21
9
|
|
22
|
-
|
23
|
-
|
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
|