Feature #32424 » 0006-CommonMark-remove-extensive-link-target-restriction.patch
lib/redmine/helpers/url.rb | ||
---|---|---|
22 | 22 |
module Redmine |
23 | 23 |
module Helpers |
24 | 24 |
module URL |
25 |
# safe for resources fetched without user interaction? |
|
25 | 26 |
def uri_with_safe_scheme?(uri, schemes = ['http', 'https', 'ftp', 'mailto', nil]) |
26 | 27 |
# URLs relative to the current document or document root (without a protocol |
27 | 28 |
# separator, should be harmless |
... | ... | |
32 | 33 |
rescue URI::Error |
33 | 34 |
false |
34 | 35 |
end |
36 | ||
37 |
# safe to render links to given uri? |
|
38 |
def uri_with_link_safe_scheme?(uri) |
|
39 |
# regexp adapted from Sanitize (we need to catch even invalid protocol specs) |
|
40 |
return true unless uri =~ /\A\s*([^\/#]*?)(?:\:|�*58|�*3a)/i |
|
41 |
# absolute scheme |
|
42 |
scheme = $1.downcase |
|
43 |
return false unless scheme =~ /\A[a-z][a-z0-9\+\.\-]*\z/ # RFC 3986 |
|
44 |
%w(data javascript vbscript).none?(scheme) |
|
45 |
end |
|
35 | 46 |
end |
36 | 47 |
end |
37 | 48 |
end |
lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb | ||
---|---|---|
24 | 24 | |
25 | 25 |
# sanitizes rendered HTML using the Sanitize gem |
26 | 26 |
class SanitizationFilter < HTML::Pipeline::SanitizationFilter |
27 |
include Redmine::Helpers::URL |
|
28 | ||
29 |
RELAXED_PROTOCOL_ATTRS = { |
|
30 |
"a" => %w(href).freeze, |
|
31 |
}.freeze |
|
32 | ||
27 | 33 |
def whitelist |
28 | 34 |
@@whitelist ||= customize_whitelist(super.deep_dup) |
29 | 35 |
end |
... | ... | |
72 | 78 |
node.remove_attribute("id") |
73 | 79 |
} |
74 | 80 | |
75 |
# allw the same set of URL schemes for links as is the default in |
|
76 |
# Redmine::Helpers::URL#uri_with_safe_scheme? |
|
77 |
whitelist[:protocols][:a] = [ |
|
78 |
'http', 'https', 'ftp', 'mailto', :relative |
|
79 |
] |
|
81 |
# https://github.com/rgrove/sanitize/issues/209 |
|
82 |
whitelist[:protocols].delete("a") |
|
83 |
whitelist[:transformers].push lambda{|env| |
|
84 |
node = env[:node] |
|
85 |
return if node.type != Nokogiri::XML::Node::ELEMENT_NODE |
|
86 |
name = env[:node_name] |
|
87 |
return unless RELAXED_PROTOCOL_ATTRS.include?(name) |
|
88 |
RELAXED_PROTOCOL_ATTRS[name].each do |attr| |
|
89 |
next unless node.has_attribute?(attr) |
|
90 |
node[attr] = node[attr].strip |
|
91 |
unless !node[attr].empty? && uri_with_link_safe_scheme?(node[attr]) |
|
92 |
node.remove_attribute(attr) |
|
93 |
end |
|
94 |
end |
|
95 |
} |
|
80 | 96 | |
81 | 97 |
whitelist |
82 | 98 |
end |
test/unit/lib/redmine/helpers/url_test.rb | ||
---|---|---|
33 | 33 |
assert_not uri_with_safe_scheme?("httpx://example.com/") |
34 | 34 |
assert_not uri_with_safe_scheme?("mailto:root@") |
35 | 35 |
end |
36 | ||
37 |
LINK_SAFE_URIS = [ |
|
38 |
"http://example.com/", |
|
39 |
"https://example.com/", |
|
40 |
"ftp://example.com/", |
|
41 |
"foo://example.org", |
|
42 |
"mailto:foo@example.org", |
|
43 |
" http://example.com/", |
|
44 |
"", |
|
45 |
"/javascript:alert(\'filename\')", |
|
46 |
] |
|
47 |
def test_uri_with_link_safe_scheme_should_recognize_safe_uris |
|
48 |
LINK_SAFE_URIS.each do |uri| |
|
49 |
assert uri_with_link_safe_scheme?(uri), "'#{uri}' should be safe" |
|
50 |
end |
|
51 |
end |
|
52 | ||
53 |
LINK_UNSAFE_URIS = [ |
|
54 |
"javascript:alert(\'XSS\');", |
|
55 |
"javascript :alert(\'XSS\');", |
|
56 |
"javascript: alert(\'XSS\');", |
|
57 |
"javascript : alert(\'XSS\');", |
|
58 |
":javascript:alert(\'XSS\');", |
|
59 |
"javascript:", |
|
60 |
"javascript:", |
|
61 |
"javascript:", |
|
62 |
"javascript:", |
|
63 |
"java\0script:alert(\"XSS\")", |
|
64 |
"java\script:alert(\"XSS\")", |
|
65 |
" \x0e javascript:alert(\'XSS\');", |
|
66 |
"data:image/png;base64,foobar", |
|
67 |
"vbscript:foobar", |
|
68 |
"data:text/html;base64,foobar", |
|
69 |
] |
|
70 |
def test_uri_with_link_safe_scheme_should_recognize_unsafe_uris |
|
71 |
LINK_UNSAFE_URIS.each do |uri| |
|
72 |
assert_not uri_with_link_safe_scheme?(uri), "'#{uri}' should not be safe" |
|
73 |
end |
|
74 |
end |
|
36 | 75 |
end |
test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb | ||
---|---|---|
54 | 54 |
assert_equal %(<code>foo</code>), filter(input) |
55 | 55 |
end |
56 | 56 | |
57 |
def test_should_allow_links_with_safe_url_schemes |
|
58 |
%w(http https ftp ssh foo).each do |scheme| |
|
59 |
input = %(<a href="#{scheme}://example.org/">foo</a>) |
|
60 |
assert_equal input, filter(input) |
|
61 |
end |
|
62 |
end |
|
63 | ||
64 |
def test_should_allow_mailto_links |
|
65 |
input = %(<a href="mailto:foo@example.org">bar</a>) |
|
66 |
assert_equal input, filter(input) |
|
67 |
end |
|
68 | ||
69 |
def test_should_remove_empty_link |
|
70 |
input = %(<a href="">bar</a>) |
|
71 |
assert_equal %(<a>bar</a>), filter(input) |
|
72 | ||
73 |
input = %(<a href=" ">bar</a>) |
|
74 |
assert_equal %(<a>bar</a>), filter(input) |
|
75 |
end |
|
57 | 76 | |
58 | 77 |
# samples taken from the Sanitize test suite |
59 | 78 |
STRINGS = [ |
... | ... | |
174 | 193 |
'<a href="vbscript:foobar">XSS</a>', |
175 | 194 |
'<a>XSS</a>' |
176 | 195 |
], |
177 | ||
178 |
'invalid URIs' => [ |
|
179 |
'<a href="foo://example.org">link</a>', |
|
180 |
'<a>link</a>' |
|
181 |
], |
|
182 | 196 |
} |
183 | 197 | |
184 | 198 |
PROTOCOLS.each do |name, strings| |