html-sanitizer 0.0.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 +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/lib/html/sanitizer.rb +22 -0
- data/lib/html/sanitizer/sanitizer.rb +139 -0
- data/lib/html/sanitizer/scrubbers.rb +146 -0
- data/lib/html/sanitizer/version.rb +5 -0
- data/test/sanitizer_test.rb +489 -0
- data/test/scrubbers_test.rb +160 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f2aab4300a74e8223593489a108c42ccce6fc4a9
|
4
|
+
data.tar.gz: cc97dc16365fec2e38ab4ed4ac7cf8c250572236
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a1918cd81328e7051456b8e3f73cded0195fb78d3a76ccbb3cc7f8fcc8bb0af95e307699440830db5fb08363f2b5a3c463532c3528616c6e6478ee2c0905a6ff
|
7
|
+
data.tar.gz: 2df201d4cddd0df59b2d7240c818debf3d5d5b3efc93459e8648d0af54211a224b0e7f999e55caf787572d9c4ec0ad30533dbf5a02bac90cadd4c0b711a73a72
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Rafael Mendonça França, Kasper Timm Hansen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
This gem is a fork from [Rails Html Sanitizers](https://github.com/rails/rails-html-sanitizer) without rails dependency.
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
gem 'html-sanitizer', github: 'dpisarewski/html-sanitizer'
|
8
|
+
|
9
|
+
And then execute:
|
10
|
+
|
11
|
+
$ bundle
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
### Sanitizers
|
16
|
+
|
17
|
+
All sanitizers respond to `sanitize`.
|
18
|
+
|
19
|
+
#### FullSanitizer
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
full_sanitizer = Html::FullSanitizer.new
|
23
|
+
full_sanitizer.sanitize("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
|
24
|
+
# => Bold no more! See more here...
|
25
|
+
```
|
26
|
+
|
27
|
+
#### LinkSanitizer
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
link_sanitizer = Html::LinkSanitizer.new
|
31
|
+
link_sanitizer.sanitize('<a href="example.com">Only the link text will be kept.</a>')
|
32
|
+
# => Only the link text will be kept.
|
33
|
+
```
|
34
|
+
|
35
|
+
#### WhiteListSanitizer
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
white_list_sanitizer = Html::WhiteListSanitizer.new
|
39
|
+
|
40
|
+
# sanitize via an extensive white list of allowed elements
|
41
|
+
white_list_sanitizer.sanitize(@article.body)
|
42
|
+
|
43
|
+
# white list only the supplied tags and attributes
|
44
|
+
white_list_sanitizer.sanitize(@article.body, tags: %w(table tr td), attributes: %w(id class style))
|
45
|
+
|
46
|
+
# white list via a custom scrubber
|
47
|
+
white_list_sanitizer.sanitize(@article.body, scrubber: ArticleScrubber.new)
|
48
|
+
|
49
|
+
# white list sanitizer can also sanitize css
|
50
|
+
white_list_sanitizer.sanitize_css('background-color: #000;')
|
51
|
+
```
|
52
|
+
|
53
|
+
### Scrubbers
|
54
|
+
|
55
|
+
Scrubbers are objects responsible for removing nodes or attributes you don't want in your HTML document.
|
56
|
+
|
57
|
+
This gem includes two scrubbers `Html::PermitScrubber` and `Html::TargetScrubber`.
|
58
|
+
|
59
|
+
#### `Html::PermitScrubber`
|
60
|
+
|
61
|
+
This scrubber allows you to permit only the tags and attributes you want.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
scrubber = Html::PermitScrubber.new
|
65
|
+
scrubber.tags = ['a']
|
66
|
+
|
67
|
+
html_fragment = Loofah.fragment('<a><img/ ></a>')
|
68
|
+
html_fragment.scrub!(scrubber)
|
69
|
+
html_fragment.to_s # => "<a></a>"
|
70
|
+
```
|
71
|
+
|
72
|
+
#### `Html::TargetScrubber`
|
73
|
+
|
74
|
+
Where `PermitScrubber` picks out tags and attributes to permit in sanitization,
|
75
|
+
`Html::TargetScrubber` targets them for removal.
|
76
|
+
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
scrubber = Html::TargetScrubber.new
|
80
|
+
scrubber.tags = ['img']
|
81
|
+
|
82
|
+
html_fragment = Loofah.fragment('<a><img/ ></a>')
|
83
|
+
html_fragment.scrub!(scrubber)
|
84
|
+
html_fragment.to_s # => "<a></a>"
|
85
|
+
```
|
86
|
+
|
87
|
+
#### Custom Scrubbers
|
88
|
+
|
89
|
+
You can also create custom scrubbers in your application if you want to.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
class CommentScrubber < Html::PermitScrubber
|
93
|
+
def allowed_node?(node)
|
94
|
+
!%w(form script comment blockquote).include?(node.name)
|
95
|
+
end
|
96
|
+
|
97
|
+
def skip_node?(node)
|
98
|
+
node.text?
|
99
|
+
end
|
100
|
+
|
101
|
+
def scrub_attribute?(name)
|
102
|
+
name == "style"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
See `Html::PermitScrubber` documentation to learn more about which methods can be overridden.
|
108
|
+
|
109
|
+
## Read more
|
110
|
+
|
111
|
+
Loofah is what underlies the sanitizers and scrubbers of rails-html-sanitizer.
|
112
|
+
- [Loofah and Loofah Scrubbers](https://github.com/flavorjones/loofah)
|
113
|
+
|
114
|
+
The `node` argument passed to some methods in a custom scrubber is an instance of `Nokogiri::XML::Node`.
|
115
|
+
- [`Nokogiri::XML::Node`](http://nokogiri.org/Nokogiri/XML/Node.html)
|
116
|
+
- [Nokogiri](http://nokogiri.org)
|
117
|
+
|
118
|
+
## Contributing
|
119
|
+
|
120
|
+
1. Fork it
|
121
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
122
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
123
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
124
|
+
5. Create new Pull Request
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "html/sanitizer/version"
|
2
|
+
require "loofah"
|
3
|
+
require "html/sanitizer/scrubbers"
|
4
|
+
require "html/sanitizer/sanitizer"
|
5
|
+
|
6
|
+
module Html
|
7
|
+
class Sanitizer
|
8
|
+
class << self
|
9
|
+
def full_sanitizer
|
10
|
+
Html::FullSanitizer
|
11
|
+
end
|
12
|
+
|
13
|
+
def link_sanitizer
|
14
|
+
Html::LinkSanitizer
|
15
|
+
end
|
16
|
+
|
17
|
+
def white_list_sanitizer
|
18
|
+
Html::WhiteListSanitizer
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Html
|
2
|
+
XPATHS_TO_REMOVE = %w{.//script .//form comment()}
|
3
|
+
|
4
|
+
class Sanitizer # :nodoc:
|
5
|
+
def sanitize(html, options = {})
|
6
|
+
raise NotImplementedError, "subclasses must implement sanitize method."
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def remove_xpaths(node, xpaths)
|
12
|
+
node.xpath(*xpaths).remove
|
13
|
+
node
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# === Html::FullSanitizer
|
18
|
+
# Removes all tags but strips out scripts, forms and comments.
|
19
|
+
#
|
20
|
+
# full_sanitizer = Html::FullSanitizer.new
|
21
|
+
# full_sanitizer.sanitize("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
|
22
|
+
# # => Bold no more! See more here...
|
23
|
+
class FullSanitizer < Sanitizer
|
24
|
+
def sanitize(html, options = {})
|
25
|
+
return unless html
|
26
|
+
return html if html.empty?
|
27
|
+
|
28
|
+
Loofah.fragment(html).tap do |fragment|
|
29
|
+
remove_xpaths(fragment, XPATHS_TO_REMOVE)
|
30
|
+
end.text
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# === Html::LinkSanitizer
|
35
|
+
# Removes a tags and href attributes leaving only the link text
|
36
|
+
#
|
37
|
+
# link_sanitizer = Html::LinkSanitizer.new
|
38
|
+
# link_sanitizer.sanitize('<a href="example.com">Only the link text will be kept.</a>')
|
39
|
+
# # => Only the link text will be kept.
|
40
|
+
class LinkSanitizer < Sanitizer
|
41
|
+
def initialize
|
42
|
+
@link_scrubber = TargetScrubber.new
|
43
|
+
@link_scrubber.tags = %w(a href)
|
44
|
+
@link_scrubber.attributes = %w(href)
|
45
|
+
end
|
46
|
+
|
47
|
+
def sanitize(html, options = {})
|
48
|
+
Loofah.scrub_fragment(html, @link_scrubber).to_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# === Html::WhiteListSanitizer
|
53
|
+
# Sanitizes html and css from an extensive white list (see link further down).
|
54
|
+
#
|
55
|
+
# === Whitespace
|
56
|
+
# We can't make any guarentees about whitespace being kept or stripped.
|
57
|
+
# Loofah uses Nokogiri, which wraps either a C or Java parser for the
|
58
|
+
# respective Ruby implementation.
|
59
|
+
# Those two parsers determine how whitespace is ultimately handled.
|
60
|
+
#
|
61
|
+
# When the stripped markup will be rendered the users browser won't take
|
62
|
+
# whitespace into account anyway. It might be better to suggest your users
|
63
|
+
# wrap their whitespace sensitive content in pre tags or that you do
|
64
|
+
# so automatically.
|
65
|
+
#
|
66
|
+
# === Options
|
67
|
+
# Sanitizes both html and css via the white lists found here:
|
68
|
+
# https://github.com/flavorjones/loofah/blob/master/lib/loofah/html5/whitelist.rb
|
69
|
+
#
|
70
|
+
# WhiteListSanitizer also accepts options to configure
|
71
|
+
# the white list used when sanitizing html.
|
72
|
+
# There's a class level option:
|
73
|
+
# Html::WhiteListSanitizer.allowed_tags = %w(table tr td)
|
74
|
+
# Html::WhiteListSanitizer.allowed_attributes = %w(id class style)
|
75
|
+
#
|
76
|
+
# Tags and attributes can also be passed to +sanitize+.
|
77
|
+
# Passed options take precedence over the class level options.
|
78
|
+
#
|
79
|
+
# === Examples
|
80
|
+
# white_list_sanitizer = Html::WhiteListSanitizer.new
|
81
|
+
#
|
82
|
+
# Sanitize css doesn't take options
|
83
|
+
# white_list_sanitizer.sanitize_css('background-color: #000;')
|
84
|
+
#
|
85
|
+
# Default: sanitize via a extensive white list of allowed elements
|
86
|
+
# white_list_sanitizer.sanitize(@article.body)
|
87
|
+
#
|
88
|
+
# White list via the supplied tags and attributes
|
89
|
+
# white_list_sanitizer.sanitize(@article.body, tags: %w(table tr td),
|
90
|
+
# attributes: %w(id class style))
|
91
|
+
#
|
92
|
+
# White list via a custom scrubber
|
93
|
+
# white_list_sanitizer.sanitize(@article.body, scrubber: ArticleScrubber.new)
|
94
|
+
class WhiteListSanitizer < Sanitizer
|
95
|
+
class << self
|
96
|
+
attr_accessor :allowed_tags
|
97
|
+
attr_accessor :allowed_attributes
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize
|
101
|
+
@permit_scrubber = PermitScrubber.new
|
102
|
+
end
|
103
|
+
|
104
|
+
def sanitize(html, options = {})
|
105
|
+
return unless html
|
106
|
+
return html if html.empty?
|
107
|
+
|
108
|
+
loofah_fragment = Loofah.fragment(html)
|
109
|
+
|
110
|
+
if scrubber = options[:scrubber]
|
111
|
+
# No duck typing, Loofah ensures subclass of Loofah::Scrubber
|
112
|
+
loofah_fragment.scrub!(scrubber)
|
113
|
+
elsif allowed_tags(options) || allowed_attributes(options)
|
114
|
+
@permit_scrubber.tags = allowed_tags(options)
|
115
|
+
@permit_scrubber.attributes = allowed_attributes(options)
|
116
|
+
loofah_fragment.scrub!(@permit_scrubber)
|
117
|
+
else
|
118
|
+
remove_xpaths(loofah_fragment, XPATHS_TO_REMOVE)
|
119
|
+
loofah_fragment.scrub!(:strip)
|
120
|
+
end
|
121
|
+
|
122
|
+
loofah_fragment.to_s
|
123
|
+
end
|
124
|
+
|
125
|
+
def sanitize_css(style_string)
|
126
|
+
Loofah::HTML5::Scrub.scrub_css(style_string)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def allowed_tags(options)
|
132
|
+
options[:tags] || self.class.allowed_tags
|
133
|
+
end
|
134
|
+
|
135
|
+
def allowed_attributes(options)
|
136
|
+
options[:attributes] || self.class.allowed_attributes
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Html
|
2
|
+
# === Html::PermitScrubber
|
3
|
+
#
|
4
|
+
# Html::PermitScrubber allows you to permit only your own tags and/or attributes.
|
5
|
+
#
|
6
|
+
# Html::PermitScrubber can be subclassed to determine:
|
7
|
+
# - When a node should be skipped via +skip_node?+.
|
8
|
+
# - When a node is allowed via +allowed_node?+.
|
9
|
+
# - When an attribute should be scrubbed via +scrub_attribute?+.
|
10
|
+
#
|
11
|
+
# Subclasses don't need to worry if tags or attributes are set or not.
|
12
|
+
# If tags or attributes are not set, Loofah's behavior will be used.
|
13
|
+
# If you override +allowed_node?+ and no tags are set, it will not be called.
|
14
|
+
# Instead Loofahs behavior will be used.
|
15
|
+
# Likewise for +scrub_attribute?+ and attributes respectively.
|
16
|
+
#
|
17
|
+
# Text and CDATA nodes are skipped by default.
|
18
|
+
# Unallowed elements will be stripped, i.e. element is removed but its subtree kept.
|
19
|
+
# Supplied tags and attributes should be Enumerables.
|
20
|
+
#
|
21
|
+
# +tags=+
|
22
|
+
# If set, elements excluded will be stripped.
|
23
|
+
# If not, elements are stripped based on Loofahs +HTML5::Scrub.allowed_element?+.
|
24
|
+
#
|
25
|
+
# +attributes=+
|
26
|
+
# If set, attributes excluded will be removed.
|
27
|
+
# If not, attributes are removed based on Loofahs +HTML5::Scrub.scrub_attributes+.
|
28
|
+
#
|
29
|
+
# class CommentScrubber < Html::PermitScrubber
|
30
|
+
# def allowed_node?(node)
|
31
|
+
# !%w(form script comment blockquote).include?(node.name)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def skip_node?(node)
|
35
|
+
# node.text?
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# def scrub_attribute?(name)
|
39
|
+
# name == "style"
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# See the documentation for Nokogiri::XML::Node to understand what's possible
|
44
|
+
# with nodes: http://nokogiri.org/Nokogiri/XML/Node.html
|
45
|
+
class PermitScrubber < Loofah::Scrubber
|
46
|
+
attr_reader :tags, :attributes
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@direction = :bottom_up
|
50
|
+
@tags, @attributes = nil, nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def tags=(tags)
|
54
|
+
@tags = validate!(tags, :tags)
|
55
|
+
end
|
56
|
+
|
57
|
+
def attributes=(attributes)
|
58
|
+
@attributes = validate!(attributes, :attributes)
|
59
|
+
end
|
60
|
+
|
61
|
+
def scrub(node)
|
62
|
+
return CONTINUE if skip_node?(node)
|
63
|
+
|
64
|
+
unless keep_node?(node)
|
65
|
+
return STOP if scrub_node(node) == STOP
|
66
|
+
end
|
67
|
+
|
68
|
+
scrub_attributes(node)
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def allowed_node?(node)
|
74
|
+
@tags.include?(node.name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def skip_node?(node)
|
78
|
+
node.text? || node.cdata?
|
79
|
+
end
|
80
|
+
|
81
|
+
def scrub_attribute?(name)
|
82
|
+
!@attributes.include?(name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def keep_node?(node)
|
86
|
+
if @tags
|
87
|
+
allowed_node?(node)
|
88
|
+
else
|
89
|
+
Loofah::HTML5::Scrub.allowed_element?(node.name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def scrub_node(node)
|
94
|
+
node.before(node.children) # strip
|
95
|
+
node.remove
|
96
|
+
end
|
97
|
+
|
98
|
+
def scrub_attributes(node)
|
99
|
+
if @attributes
|
100
|
+
node.attribute_nodes.each do |attr|
|
101
|
+
attr.remove if scrub_attribute?(attr.name)
|
102
|
+
end
|
103
|
+
|
104
|
+
scrub_css_attribute(node)
|
105
|
+
else
|
106
|
+
Loofah::HTML5::Scrub.scrub_attributes(node)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def scrub_css_attribute(node)
|
111
|
+
if Loofah::HTML5::Scrub.respond_to?(:scrub_css_attribute)
|
112
|
+
Loofah::HTML5::Scrub.scrub_css_attribute(node)
|
113
|
+
else
|
114
|
+
style = node.attributes['style']
|
115
|
+
style.value = Loofah::HTML5::Scrub.scrub_css(style.value) if style
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def validate!(var, name)
|
120
|
+
if var && !var.is_a?(Enumerable)
|
121
|
+
raise ArgumentError, "You should pass :#{name} as an Enumerable"
|
122
|
+
end
|
123
|
+
var
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# === Html::TargetScrubber
|
128
|
+
#
|
129
|
+
# Where Html::PermitScrubber picks out tags and attributes to permit in
|
130
|
+
# sanitization, Html::TargetScrubber targets them for removal.
|
131
|
+
#
|
132
|
+
# +tags=+
|
133
|
+
# If set, elements included will be stripped.
|
134
|
+
#
|
135
|
+
# +attributes=+
|
136
|
+
# If set, attributes included will be removed.
|
137
|
+
class TargetScrubber < PermitScrubber
|
138
|
+
def allowed_node?(node)
|
139
|
+
!@tags.include?(node.name)
|
140
|
+
end
|
141
|
+
|
142
|
+
def scrub_attribute?(name)
|
143
|
+
@attributes.include?(name)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,489 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "sanitizer"
|
3
|
+
require "rails/dom/testing/assertions/dom_assertions"
|
4
|
+
|
5
|
+
class SanitizersTest < Minitest::Test
|
6
|
+
include Rails::Dom::Testing::Assertions::DomAssertions
|
7
|
+
|
8
|
+
def test_sanitizer_sanitize_raises_not_implemented_error
|
9
|
+
assert_raises NotImplementedError do
|
10
|
+
Html::Sanitizer.new.sanitize('')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class XpathRemovalTestSanitizer < Html::Sanitizer
|
15
|
+
def sanitize(html, options = {})
|
16
|
+
fragment = Loofah.fragment(html)
|
17
|
+
remove_xpaths(fragment, options[:xpaths]).to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_remove_xpaths_removes_an_xpath
|
22
|
+
html = %(<h1>hello <script>code!</script></h1>)
|
23
|
+
assert_equal %(<h1>hello </h1>), xpath_sanitize(html, xpaths: %w(.//script))
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_remove_xpaths_removes_all_occurences_of_xpath
|
27
|
+
html = %(<section><header><script>code!</script></header><p>hello <script>code!</script></p></section>)
|
28
|
+
assert_equal %(<section><header></header><p>hello </p></section>), xpath_sanitize(html, xpaths: %w(.//script))
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_remove_xpaths_called_with_faulty_xpath
|
32
|
+
assert_raises Nokogiri::XML::XPath::SyntaxError do
|
33
|
+
xpath_sanitize('<h1>hello<h1>', xpaths: %w(..faulty_xpath))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_remove_xpaths_called_with_xpath_string
|
38
|
+
assert_equal '', xpath_sanitize('<a></a>', xpaths: './/a')
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_remove_xpaths_called_with_enumerable_xpaths
|
42
|
+
assert_equal '', xpath_sanitize('<a><span></span></a>', xpaths: %w(.//a .//span))
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_strip_tags_with_quote
|
46
|
+
input = '<" <img src="trollface.gif" onload="alert(1)"> hi'
|
47
|
+
assert_equal ' hi', full_sanitize(input)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_strip_invalid_html
|
51
|
+
assert_equal "", full_sanitize("<<<bad html")
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_strip_nested_tags
|
55
|
+
expected = "Weia onclick='alert(document.cookie);'/>rdos"
|
56
|
+
input = "Wei<<a>a onclick='alert(document.cookie);'</a>/>rdos"
|
57
|
+
assert_equal expected, full_sanitize(input)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_strip_tags_multiline
|
61
|
+
expected = %{This is a test.\n\n\n\nIt no longer contains any HTML.\n}
|
62
|
+
input = %{<title>This is <b>a <a href="" target="_blank">test</a></b>.</title>\n\n<!-- it has a comment -->\n\n<p>It no <b>longer <strong>contains <em>any <strike>HTML</strike></em>.</strong></b></p>\n}
|
63
|
+
|
64
|
+
assert_equal expected, full_sanitize(input)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_strip_comments
|
68
|
+
assert_equal "This is ", full_sanitize("This is <-- not\n a comment here.")
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_strip_cdata
|
72
|
+
assert_equal "This has a ]]> here.", full_sanitize("This has a <![CDATA[<section>]]> here.")
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_strip_unclosed_cdata
|
76
|
+
assert_equal "This has an unclosed ]] here...", full_sanitize("This has an unclosed <![CDATA[<section>]] here...")
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_strip_blank_string
|
80
|
+
[nil, '', ' '].each { |blank| assert_equal blank, full_sanitize(blank) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_strip_tags_with_plaintext
|
84
|
+
assert_equal "Dont touch me", full_sanitize("Dont touch me")
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_strip_tags_with_tags
|
88
|
+
assert_equal "This is a test.", full_sanitize("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>")
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_strip_tags_with_many_open_quotes
|
92
|
+
assert_equal "", full_sanitize("<<<bad html>")
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_strip_tags_with_sentence
|
96
|
+
assert_equal "This is a test.", full_sanitize("This is a test.")
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_strip_tags_with_comment
|
100
|
+
assert_equal "This has a here.", full_sanitize("This has a <!-- comment --> here.")
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_strip_tags_with_frozen_string
|
104
|
+
assert_equal "Frozen string with no tags", full_sanitize("Frozen string with no tags".freeze)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_strip_links_with_tags_in_tags
|
108
|
+
expected = "a href='hello'>all <b>day</b> long/a>"
|
109
|
+
input = "<<a>a href='hello'>all <b>day</b> long<</A>/a>"
|
110
|
+
assert_equal expected, link_sanitize(input)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_strip_links_with_unclosed_tags
|
114
|
+
assert_equal "", link_sanitize("<a<a")
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_strip_links_with_plaintext
|
118
|
+
assert_equal "Dont touch me", link_sanitize("Dont touch me")
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_strip_links_with_line_feed_and_uppercase_tag
|
122
|
+
assert_equal "on my mind\nall day long", link_sanitize("<a href='almost'>on my mind</a>\n<A href='almost'>all day long</A>")
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_strip_links_leaves_nonlink_tags
|
126
|
+
assert_equal "My mind\nall <b>day</b> long", link_sanitize("<a href='almost'>My mind</a>\n<A href='almost'>all <b>day</b> long</A>")
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_strip_links_with_links
|
130
|
+
assert_equal "0wn3d", link_sanitize("<a href='http://www.rubyonrails.com/'><a href='http://www.rubyonrails.com/' onlclick='steal()'>0wn3d</a></a>")
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_strip_links_with_linkception
|
134
|
+
assert_equal "Magic", link_sanitize("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic")
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_strip_links_with_a_tag_in_href
|
138
|
+
assert_equal "FrrFox", link_sanitize("<href onlclick='steal()'>FrrFox</a></href>")
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_sanitize_form
|
142
|
+
assert_sanitized "<form action=\"/foo/bar\" method=\"post\"><input></form>", ''
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_sanitize_plaintext
|
146
|
+
assert_sanitized "<plaintext><span>foo</span></plaintext>", "<span>foo</span>"
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_sanitize_script
|
150
|
+
assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b cd e f"
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_sanitize_js_handlers
|
154
|
+
raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>}
|
155
|
+
assert_sanitized raw, %{onthis="do that" <a href="#" name="foo">hello</a>}
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_sanitize_javascript_href
|
159
|
+
raw = %{href="javascript:bang" <a href="javascript:bang" name="hello">foo</a>, <span href="javascript:bang">bar</span>}
|
160
|
+
assert_sanitized raw, %{href="javascript:bang" <a name="hello">foo</a>, <span>bar</span>}
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_sanitize_image_src
|
164
|
+
raw = %{src="javascript:bang" <img src="javascript:bang" width="5">foo</img>, <span src="javascript:bang">bar</span>}
|
165
|
+
assert_sanitized raw, %{src="javascript:bang" <img width="5">foo</img>, <span>bar</span>}
|
166
|
+
end
|
167
|
+
|
168
|
+
tags = Loofah::HTML5::WhiteList::ALLOWED_ELEMENTS - %w(script form)
|
169
|
+
tags.each do |tag_name|
|
170
|
+
define_method "test_should_allow_#{tag_name}_tag" do
|
171
|
+
assert_sanitized "start <#{tag_name} title=\"1\" onclick=\"foo\">foo <bad>bar</bad> baz</#{tag_name}> end", %(start <#{tag_name} title="1">foo bar baz</#{tag_name}> end)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_should_allow_anchors
|
176
|
+
assert_sanitized %(<a href="foo" onclick="bar"><script>baz</script></a>), %(<a href=\"foo\"></a>)
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_video_poster_sanitization
|
180
|
+
assert_sanitized %(<video src="videofile.ogg" autoplay poster="posterimage.jpg"></video>), %(<video src="videofile.ogg" poster="posterimage.jpg"></video>)
|
181
|
+
assert_sanitized %(<video src="videofile.ogg" poster=javascript:alert(1)></video>), %(<video src="videofile.ogg"></video>)
|
182
|
+
end
|
183
|
+
|
184
|
+
# RFC 3986, sec 4.2
|
185
|
+
def test_allow_colons_in_path_component
|
186
|
+
assert_sanitized "<a href=\"./this:that\">foo</a>"
|
187
|
+
end
|
188
|
+
|
189
|
+
%w(src width height alt).each do |img_attr|
|
190
|
+
define_method "test_should_allow_image_#{img_attr}_attribute" do
|
191
|
+
assert_sanitized %(<img #{img_attr}="foo" onclick="bar" />), %(<img #{img_attr}="foo" />)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_should_handle_non_html
|
196
|
+
assert_sanitized 'abc'
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_should_handle_blank_text
|
200
|
+
[nil, '', ' '].each { |blank| assert_sanitized blank }
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_setting_allowed_tags_affects_sanitization
|
204
|
+
scope_allowed_tags %w(u) do |sanitizer|
|
205
|
+
assert_equal '<u></u>', sanitizer.sanitize('<a><u></u></a>')
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def test_setting_allowed_attributes_affects_sanitization
|
210
|
+
scope_allowed_attributes %w(foo) do |sanitizer|
|
211
|
+
input = '<a foo="hello" bar="world"></a>'
|
212
|
+
assert_equal '<a foo="hello"></a>', sanitizer.sanitize(input)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_custom_tags_overrides_allowed_tags
|
217
|
+
scope_allowed_tags %(u) do |sanitizer|
|
218
|
+
input = '<a><u></u></a>'
|
219
|
+
assert_equal '<a></a>', sanitizer.sanitize(input, tags: %w(a))
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def test_custom_attributes_overrides_allowed_attributes
|
224
|
+
scope_allowed_attributes %(foo) do |sanitizer|
|
225
|
+
input = '<a foo="hello" bar="world"></a>'
|
226
|
+
assert_equal '<a bar="world"></a>', sanitizer.sanitize(input, attributes: %w(bar))
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_should_allow_custom_tags
|
231
|
+
text = "<u>foo</u>"
|
232
|
+
assert_equal text, white_list_sanitize(text, tags: %w(u))
|
233
|
+
end
|
234
|
+
|
235
|
+
def test_should_allow_only_custom_tags
|
236
|
+
text = "<u>foo</u> with <i>bar</i>"
|
237
|
+
assert_equal "<u>foo</u> with bar", white_list_sanitize(text, tags: %w(u))
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_should_allow_custom_tags_with_attributes
|
241
|
+
text = %(<blockquote cite="http://example.com/">foo</blockquote>)
|
242
|
+
assert_equal text, white_list_sanitize(text)
|
243
|
+
end
|
244
|
+
|
245
|
+
def test_should_allow_custom_tags_with_custom_attributes
|
246
|
+
text = %(<blockquote foo="bar">Lorem ipsum</blockquote>)
|
247
|
+
assert_equal text, white_list_sanitize(text, attributes: ['foo'])
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_scrub_style_if_style_attribute_option_is_passed
|
251
|
+
input = '<p style="color: #000; background-image: url(http://www.ragingplatypus.com/i/cam-full.jpg);"></p>'
|
252
|
+
assert_equal '<p style="color: #000;"></p>', white_list_sanitize(input, attributes: %w(style))
|
253
|
+
end
|
254
|
+
|
255
|
+
def test_should_raise_argument_error_if_tags_is_not_enumerable
|
256
|
+
assert_raises ArgumentError do
|
257
|
+
white_list_sanitize('<a>some html</a>', tags: 'foo')
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_should_raise_argument_error_if_attributes_is_not_enumerable
|
262
|
+
assert_raises ArgumentError do
|
263
|
+
white_list_sanitize('<a>some html</a>', attributes: 'foo')
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def test_should_not_accept_non_loofah_inheriting_scrubber
|
268
|
+
scrubber = Object.new
|
269
|
+
def scrubber.scrub(node); node.name = 'h1'; end
|
270
|
+
|
271
|
+
assert_raises Loofah::ScrubberNotFound do
|
272
|
+
white_list_sanitize('<a>some html</a>', scrubber: scrubber)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def test_should_accept_loofah_inheriting_scrubber
|
277
|
+
scrubber = Loofah::Scrubber.new
|
278
|
+
def scrubber.scrub(node); node.name = 'h1'; end
|
279
|
+
|
280
|
+
html = "<script>hello!</script>"
|
281
|
+
assert_equal "<h1>hello!</h1>", white_list_sanitize(html, scrubber: scrubber)
|
282
|
+
end
|
283
|
+
|
284
|
+
def test_should_accept_loofah_scrubber_that_wraps_a_block
|
285
|
+
scrubber = Loofah::Scrubber.new { |node| node.name = 'h1' }
|
286
|
+
html = "<script>hello!</script>"
|
287
|
+
assert_equal "<h1>hello!</h1>", white_list_sanitize(html, scrubber: scrubber)
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_custom_scrubber_takes_precedence_over_other_options
|
291
|
+
scrubber = Loofah::Scrubber.new { |node| node.name = 'h1' }
|
292
|
+
html = "<script>hello!</script>"
|
293
|
+
assert_equal "<h1>hello!</h1>", white_list_sanitize(html, scrubber: scrubber, tags: ['foo'])
|
294
|
+
end
|
295
|
+
|
296
|
+
[%w(img src), %w(a href)].each do |(tag, attr)|
|
297
|
+
define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols" do
|
298
|
+
assert_sanitized %(<#{tag} #{attr}="javascript:bang" title="1">boo</#{tag}>), %(<#{tag} title="1">boo</#{tag}>)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def test_should_block_script_tag
|
303
|
+
assert_sanitized %(<SCRIPT\nSRC=http://ha.ckers.org/xss.js></SCRIPT>), ""
|
304
|
+
end
|
305
|
+
|
306
|
+
def test_should_not_fall_for_xss_image_hack_with_uppercase_tags
|
307
|
+
assert_sanitized %(<IMG """><SCRIPT>alert("XSS")</SCRIPT>">), "<img>\">"
|
308
|
+
end
|
309
|
+
|
310
|
+
[%(<IMG SRC="javascript:alert('XSS');">),
|
311
|
+
%(<IMG SRC=javascript:alert('XSS')>),
|
312
|
+
%(<IMG SRC=JaVaScRiPt:alert('XSS')>),
|
313
|
+
%(<IMG SRC=javascript:alert("XSS")>),
|
314
|
+
%(<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>),
|
315
|
+
%(<IMG SRC=javascript:alert('XSS')>),
|
316
|
+
%(<IMG SRC=javascript:alert('XSS')>),
|
317
|
+
%(<IMG SRC=javascript:alert('XSS')>),
|
318
|
+
%(<IMG SRC="jav\tascript:alert('XSS');">),
|
319
|
+
%(<IMG SRC="jav	ascript:alert('XSS');">),
|
320
|
+
%(<IMG SRC="jav
ascript:alert('XSS');">),
|
321
|
+
%(<IMG SRC="jav
ascript:alert('XSS');">),
|
322
|
+
%(<IMG SRC="  javascript:alert('XSS');">),
|
323
|
+
%(<IMG SRC="javascript:alert('XSS');">),
|
324
|
+
%(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each_with_index do |img_hack, i|
|
325
|
+
define_method "test_should_not_fall_for_xss_image_hack_#{i+1}" do
|
326
|
+
assert_sanitized img_hack, "<img>"
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def test_should_sanitize_tag_broken_up_by_null
|
331
|
+
assert_sanitized %(<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>), ""
|
332
|
+
end
|
333
|
+
|
334
|
+
def test_should_sanitize_invalid_script_tag
|
335
|
+
assert_sanitized %(<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>), ""
|
336
|
+
end
|
337
|
+
|
338
|
+
def test_should_sanitize_script_tag_with_multiple_open_brackets
|
339
|
+
assert_sanitized %(<<SCRIPT>alert("XSS");//<</SCRIPT>), "alert(\"XSS\");//"
|
340
|
+
assert_sanitized %(<iframe src=http://ha.ckers.org/scriptlet.html\n<a), ""
|
341
|
+
end
|
342
|
+
|
343
|
+
def test_should_sanitize_unclosed_script
|
344
|
+
assert_sanitized %(<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>), ""
|
345
|
+
end
|
346
|
+
|
347
|
+
def test_should_sanitize_half_open_scripts
|
348
|
+
assert_sanitized %(<IMG SRC="javascript:alert('XSS')"), "<img>"
|
349
|
+
end
|
350
|
+
|
351
|
+
def test_should_not_fall_for_ridiculous_hack
|
352
|
+
img_hack = %(<IMG\nSRC\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt\n(\n'\nX\nS\nS\n'\n)\n"\n>)
|
353
|
+
assert_sanitized img_hack, "<img>"
|
354
|
+
end
|
355
|
+
|
356
|
+
def test_should_sanitize_attributes
|
357
|
+
assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="#{CGI.escapeHTML "'><script>alert()</script>"}">blah</span>)
|
358
|
+
end
|
359
|
+
|
360
|
+
def test_should_sanitize_illegal_style_properties
|
361
|
+
raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;)
|
362
|
+
expected = %(display: block; width: 100%; height: 100%; background-color: black; background-x: center; background-y: center;)
|
363
|
+
assert_equal expected, sanitize_css(raw)
|
364
|
+
end
|
365
|
+
|
366
|
+
def test_should_sanitize_with_trailing_space
|
367
|
+
raw = "display:block; "
|
368
|
+
expected = "display: block;"
|
369
|
+
assert_equal expected, sanitize_css(raw)
|
370
|
+
end
|
371
|
+
|
372
|
+
def test_should_sanitize_xul_style_attributes
|
373
|
+
raw = %(-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss'))
|
374
|
+
assert_equal '', sanitize_css(raw)
|
375
|
+
end
|
376
|
+
|
377
|
+
def test_should_sanitize_invalid_tag_names
|
378
|
+
assert_sanitized(%(a b c<script/XSS src="http://ha.ckers.org/xss.js"></script>d e f), "a b cd e f")
|
379
|
+
end
|
380
|
+
|
381
|
+
def test_should_sanitize_non_alpha_and_non_digit_characters_in_tags
|
382
|
+
assert_sanitized('<a onclick!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>foo</a>', "<a>foo</a>")
|
383
|
+
end
|
384
|
+
|
385
|
+
def test_should_sanitize_invalid_tag_names_in_single_tags
|
386
|
+
assert_sanitized('<img/src="http://ha.ckers.org/xss.js"/>', "<img />")
|
387
|
+
end
|
388
|
+
|
389
|
+
def test_should_sanitize_img_dynsrc_lowsrc
|
390
|
+
assert_sanitized(%(<img lowsrc="javascript:alert('XSS')" />), "<img />")
|
391
|
+
end
|
392
|
+
|
393
|
+
def test_should_sanitize_div_background_image_unicode_encoded
|
394
|
+
raw = %(background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029)
|
395
|
+
assert_equal '', sanitize_css(raw)
|
396
|
+
end
|
397
|
+
|
398
|
+
def test_should_sanitize_div_style_expression
|
399
|
+
raw = %(width: expression(alert('XSS'));)
|
400
|
+
assert_equal '', sanitize_css(raw)
|
401
|
+
end
|
402
|
+
|
403
|
+
def test_should_sanitize_across_newlines
|
404
|
+
raw = %(\nwidth:\nexpression(alert('XSS'));\n)
|
405
|
+
assert_equal '', sanitize_css(raw)
|
406
|
+
end
|
407
|
+
|
408
|
+
def test_should_sanitize_img_vbscript
|
409
|
+
assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), '<img />'
|
410
|
+
end
|
411
|
+
|
412
|
+
def test_should_sanitize_cdata_section
|
413
|
+
assert_sanitized "<![CDATA[<span>section</span>]]>", "section]]>"
|
414
|
+
end
|
415
|
+
|
416
|
+
def test_should_sanitize_unterminated_cdata_section
|
417
|
+
assert_sanitized "<![CDATA[<span>neverending...", "neverending..."
|
418
|
+
end
|
419
|
+
|
420
|
+
def test_should_not_mangle_urls_with_ampersand
|
421
|
+
assert_sanitized %{<a href=\"http://www.domain.com?var1=1&var2=2\">my link</a>}
|
422
|
+
end
|
423
|
+
|
424
|
+
def test_should_sanitize_neverending_attribute
|
425
|
+
assert_sanitized "<span class=\"\\", "<span class=\"\\\">"
|
426
|
+
end
|
427
|
+
|
428
|
+
[
|
429
|
+
%(<a href="javascript:alert('XSS');">),
|
430
|
+
%(<a href="javascript:alert('XSS');">),
|
431
|
+
%(<a href="javascript:alert('XSS');">),
|
432
|
+
%(<a href="javascript:alert('XSS');">)
|
433
|
+
].each_with_index do |enc_hack, i|
|
434
|
+
define_method "test_x03a_handling_#{i+1}" do
|
435
|
+
assert_sanitized enc_hack, "<a>"
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
def test_x03a_legitimate
|
440
|
+
assert_sanitized %(<a href="http://legit">), %(<a href="http://legit">)
|
441
|
+
assert_sanitized %(<a href="http://legit">), %(<a href="http://legit">)
|
442
|
+
end
|
443
|
+
|
444
|
+
protected
|
445
|
+
|
446
|
+
def xpath_sanitize(input, options = {})
|
447
|
+
XpathRemovalTestSanitizer.new.sanitize(input, options)
|
448
|
+
end
|
449
|
+
|
450
|
+
def full_sanitize(input, options = {})
|
451
|
+
Html::FullSanitizer.new.sanitize(input, options)
|
452
|
+
end
|
453
|
+
|
454
|
+
def link_sanitize(input, options = {})
|
455
|
+
Html::LinkSanitizer.new.sanitize(input, options)
|
456
|
+
end
|
457
|
+
|
458
|
+
def white_list_sanitize(input, options = {})
|
459
|
+
Html::WhiteListSanitizer.new.sanitize(input, options)
|
460
|
+
end
|
461
|
+
|
462
|
+
def assert_sanitized(input, expected = nil)
|
463
|
+
if input
|
464
|
+
assert_dom_equal expected || input, white_list_sanitize(input)
|
465
|
+
else
|
466
|
+
assert_nil white_list_sanitize(input)
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def sanitize_css(input)
|
471
|
+
Html::WhiteListSanitizer.new.sanitize_css(input)
|
472
|
+
end
|
473
|
+
|
474
|
+
def scope_allowed_tags(tags)
|
475
|
+
Html::WhiteListSanitizer.allowed_tags = %w(u)
|
476
|
+
yield Html::WhiteListSanitizer.new
|
477
|
+
|
478
|
+
ensure
|
479
|
+
Html::WhiteListSanitizer.allowed_tags = nil
|
480
|
+
end
|
481
|
+
|
482
|
+
def scope_allowed_attributes(attributes)
|
483
|
+
Html::WhiteListSanitizer.allowed_attributes = attributes
|
484
|
+
yield Html::WhiteListSanitizer.new
|
485
|
+
|
486
|
+
ensure
|
487
|
+
Html::WhiteListSanitizer.allowed_attributes = nil
|
488
|
+
end
|
489
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "sanitizer"
|
3
|
+
|
4
|
+
class ScrubberTest < Minitest::Test
|
5
|
+
protected
|
6
|
+
|
7
|
+
def assert_scrubbed(html, expected = html)
|
8
|
+
output = Loofah.scrub_fragment(html, @scrubber).to_s
|
9
|
+
assert_equal expected, output
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_node(text)
|
13
|
+
Loofah.fragment(text).children.first
|
14
|
+
end
|
15
|
+
|
16
|
+
def assert_node_skipped(text)
|
17
|
+
assert_scrub_returns(Loofah::Scrubber::CONTINUE, text)
|
18
|
+
end
|
19
|
+
|
20
|
+
def assert_scrub_stopped(text)
|
21
|
+
assert_scrub_returns(Loofah::Scrubber::STOP, text)
|
22
|
+
end
|
23
|
+
|
24
|
+
def assert_scrub_returns(return_value, text)
|
25
|
+
node = to_node(text)
|
26
|
+
assert_equal return_value, @scrubber.scrub(node)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class PermitScrubberTest < ScrubberTest
|
31
|
+
|
32
|
+
def setup
|
33
|
+
@scrubber = Html::PermitScrubber.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_responds_to_scrub
|
37
|
+
assert @scrubber.respond_to?(:scrub)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_default_scrub_behavior
|
41
|
+
assert_scrubbed '<tag>hello</tag>', 'hello'
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_default_attributes_removal_behavior
|
45
|
+
assert_scrubbed '<p cooler="hello">hello</p>', '<p>hello</p>'
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_leaves_supplied_tags
|
49
|
+
@scrubber.tags = %w(a)
|
50
|
+
assert_scrubbed '<a>hello</a>'
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_leaves_only_supplied_tags
|
54
|
+
html = '<tag>leave me <span>now</span></tag>'
|
55
|
+
@scrubber.tags = %w(tag)
|
56
|
+
assert_scrubbed html, '<tag>leave me now</tag>'
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_leaves_only_supplied_tags_nested
|
60
|
+
html = '<tag>leave <em>me <span>now</span></em></tag>'
|
61
|
+
@scrubber.tags = %w(tag)
|
62
|
+
assert_scrubbed html, '<tag>leave me now</tag>'
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_leaves_supplied_attributes
|
66
|
+
@scrubber.attributes = %w(cooler)
|
67
|
+
assert_scrubbed '<a cooler="hello"></a>'
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_leaves_only_supplied_attributes
|
71
|
+
@scrubber.attributes = %w(cooler)
|
72
|
+
assert_scrubbed '<a cooler="hello" b="c" d="e"></a>', '<a cooler="hello"></a>'
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_leaves_supplied_tags_and_attributes
|
76
|
+
@scrubber.tags = %w(tag)
|
77
|
+
@scrubber.attributes = %w(cooler)
|
78
|
+
assert_scrubbed '<tag cooler="hello"></tag>'
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_leaves_only_supplied_tags_and_attributes
|
82
|
+
@scrubber.tags = %w(tag)
|
83
|
+
@scrubber.attributes = %w(cooler)
|
84
|
+
html = '<a></a><tag href=""></tag><tag cooler=""></tag>'
|
85
|
+
assert_scrubbed html, '<tag></tag><tag cooler=""></tag>'
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_leaves_text
|
89
|
+
assert_scrubbed('some text')
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_skips_text_nodes
|
93
|
+
assert_node_skipped('some text')
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_tags_accessor_validation
|
97
|
+
e = assert_raises(ArgumentError) do
|
98
|
+
@scrubber.tags = 'tag'
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_equal "You should pass :tags as an Enumerable", e.message
|
102
|
+
assert_nil @scrubber.tags, "Tags should be nil when validation fails"
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_attributes_accessor_validation
|
106
|
+
e = assert_raises(ArgumentError) do
|
107
|
+
@scrubber.attributes = 'cooler'
|
108
|
+
end
|
109
|
+
|
110
|
+
assert_equal "You should pass :attributes as an Enumerable", e.message
|
111
|
+
assert_nil @scrubber.attributes, "Attributes should be nil when validation fails"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class TargetScrubberTest < ScrubberTest
|
116
|
+
def setup
|
117
|
+
@scrubber = Html::TargetScrubber.new
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_targeting_tags_removes_only_them
|
121
|
+
@scrubber.tags = %w(a h1)
|
122
|
+
html = '<script></script><a></a><h1></h1>'
|
123
|
+
assert_scrubbed html, '<script></script>'
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_targeting_tags_removes_only_them_nested
|
127
|
+
@scrubber.tags = %w(a)
|
128
|
+
html = '<tag><a><tag><a></a></tag></a></tag>'
|
129
|
+
assert_scrubbed html, '<tag><tag></tag></tag>'
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_targeting_attributes_removes_only_them
|
133
|
+
@scrubber.attributes = %w(class id)
|
134
|
+
html = '<a class="a" id="b" onclick="c"></a>'
|
135
|
+
assert_scrubbed html, '<a onclick="c"></a>'
|
136
|
+
end
|
137
|
+
|
138
|
+
def test_targeting_tags_and_attributes_removes_only_them
|
139
|
+
@scrubber.tags = %w(tag)
|
140
|
+
@scrubber.attributes = %w(remove)
|
141
|
+
html = '<tag remove="" other=""></tag><a remove="" other=""></a>'
|
142
|
+
assert_scrubbed html, '<a other=""></a>'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class ReturningStopFromScrubNodeTest < ScrubberTest
|
147
|
+
class ScrubStopper < Html::PermitScrubber
|
148
|
+
def scrub_node(node)
|
149
|
+
Loofah::Scrubber::STOP
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def setup
|
154
|
+
@scrubber = ScrubStopper.new
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_returns_stop_from_scrub_if_scrub_node_does
|
158
|
+
assert_scrub_stopped '<script>remove me</script>'
|
159
|
+
end
|
160
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: html-sanitizer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rafael Mendonça França
|
8
|
+
- Kasper Timm Hansen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-12-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
name: loofah
|
21
|
+
prerelease: false
|
22
|
+
type: :runtime
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '2.0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
name: bundler
|
35
|
+
prerelease: false
|
36
|
+
type: :development
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.3'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
name: rake
|
49
|
+
prerelease: false
|
50
|
+
type: :development
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
name: minitest
|
63
|
+
prerelease: false
|
64
|
+
type: :development
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
name: rails-dom-testing
|
77
|
+
prerelease: false
|
78
|
+
type: :development
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
description: HTML sanitization
|
85
|
+
email:
|
86
|
+
- dieter.pisarewski@gmail.com
|
87
|
+
- rafaelmfranca@gmail.com
|
88
|
+
- kaspth@gmail.com
|
89
|
+
executables: []
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files: []
|
92
|
+
files:
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- lib/html/sanitizer.rb
|
96
|
+
- lib/html/sanitizer/sanitizer.rb
|
97
|
+
- lib/html/sanitizer/scrubbers.rb
|
98
|
+
- lib/html/sanitizer/version.rb
|
99
|
+
- test/sanitizer_test.rb
|
100
|
+
- test/scrubbers_test.rb
|
101
|
+
homepage: https://github.com/dpisarewski/html-sanitizer
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.2.2
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: This gem is responsible to sanitize HTML fragments.
|
125
|
+
test_files:
|
126
|
+
- test/sanitizer_test.rb
|
127
|
+
- test/scrubbers_test.rb
|