Patch #42737 » 0001-Replacing-html-pipeline-with-Loofah-for-HTML-Filteri.patch
Gemfile | ||
---|---|---|
33 | 33 |
gem 'rotp', '>= 5.0.0' |
34 | 34 |
gem 'rqrcode' |
35 | 35 | |
36 |
# HTML pipeline and sanitization |
|
37 |
gem "html-pipeline", "~> 2.13.2" |
|
38 |
gem "sanitize", "~> 6.0" |
|
36 |
# HTML sanitization |
|
37 |
gem "sanitize", "~> 7.0" |
|
39 | 38 | |
40 | 39 |
# Optional gem for LDAP authentication |
41 | 40 |
group :ldap do |
lib/redmine/wiki_formatting/common_mark/external_links_filter.rb → lib/redmine/wiki_formatting/common_mark/external_links_scrubber.rb | ||
---|---|---|
24 | 24 |
module CommonMark |
25 | 25 |
# adds class="external" to external links, and class="email" to mailto |
26 | 26 |
# links |
27 |
class ExternalLinksFilter < HTML::Pipeline::Filter
|
|
28 |
def call
|
|
29 |
doc.search("a").each do |node|
|
|
27 |
class ExternalLinksScrubber < Loofah::Scrubber
|
|
28 |
def scrub(node)
|
|
29 |
if node.name == 'a'
|
|
30 | 30 |
url = node["href"] |
31 |
next unless url
|
|
32 |
next if url.starts_with?("/") || url.starts_with?("#") || !url.include?(':')
|
|
31 |
return unless url
|
|
32 |
return if url.starts_with?("/") || url.starts_with?("#") || !url.include?(':')
|
|
33 | 33 | |
34 | 34 |
scheme = begin |
35 | 35 |
URI.parse(url).scheme |
36 | 36 |
rescue |
37 | 37 |
nil |
38 | 38 |
end |
39 |
next if scheme.blank?
|
|
39 |
return if scheme.blank?
|
|
40 | 40 | |
41 | 41 |
klass = node["class"].presence |
42 | 42 |
node["class"] = [ |
... | ... | |
50 | 50 |
node["rel"] = rel.join(" ") |
51 | 51 |
end |
52 | 52 |
end |
53 |
doc |
|
54 | 53 |
end |
55 | 54 |
end |
56 | 55 |
end |
lib/redmine/wiki_formatting/common_mark/fixup_auto_links_filter.rb → lib/redmine/wiki_formatting/common_mark/fixup_auto_links_scrubber.rb | ||
---|---|---|
26 | 26 |
# @<a href="mailto:user@example.org">user@example.org</a> |
27 | 27 |
# - autolinked hi res image names that look like email addresses: |
28 | 28 |
# <a href="mailto:printscreen@2x.png">printscreen@2x.png</a> |
29 |
class FixupAutoLinksFilter < HTML::Pipeline::Filter
|
|
29 |
class FixupAutoLinksScrubber < Loofah::Scrubber
|
|
30 | 30 |
USER_LINK_PREFIX = /(@|user:)\z/ |
31 | 31 |
HIRES_IMAGE = /.+@\dx\.(bmp|gif|jpg|jpe|jpeg|png)\z/ |
32 | 32 | |
33 |
def call |
|
34 |
doc.search("a").each do |node| |
|
35 |
unless (url = node['href']) && url.starts_with?('mailto:') |
|
36 |
next |
|
37 |
end |
|
33 |
def scrub(node) |
|
34 |
if node.name == 'a' |
|
35 |
return unless (url = node['href']) && url.starts_with?('mailto:') |
|
38 | 36 | |
39 | 37 |
if ((p = node.previous) && p.text? && |
40 | 38 |
p.text =~(USER_LINK_PREFIX)) || |
... | ... | |
43 | 41 |
node.replace node.text |
44 | 42 |
end |
45 | 43 |
end |
46 |
doc |
|
47 | 44 |
end |
48 | 45 |
end |
49 | 46 |
end |
lib/redmine/wiki_formatting/common_mark/formatter.rb | ||
---|---|---|
17 | 17 |
# along with this program; if not, write to the Free Software |
18 | 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | 19 | |
20 |
require 'html/pipeline' |
|
21 | ||
22 | 20 |
module Redmine |
23 | 21 |
module WikiFormatting |
24 | 22 |
module CommonMark |
... | ... | |
53 | 51 |
}.freeze, |
54 | 52 |
}.freeze |
55 | 53 | |
56 |
MarkdownPipeline = HTML::Pipeline.new [ |
|
57 |
MarkdownFilter, |
|
58 |
SanitizationFilter, |
|
59 |
SyntaxHighlightFilter, |
|
60 |
FixupAutoLinksFilter, |
|
61 |
ExternalLinksFilter |
|
62 |
], PIPELINE_CONFIG |
|
54 |
SANITIZER = SanitizationFilter.new |
|
55 |
SCRUBBERS = [ |
|
56 |
SyntaxHighlightScrubber.new, |
|
57 |
FixupAutoLinksScrubber.new, |
|
58 |
ExternalLinksScrubber.new |
|
59 |
] |
|
63 | 60 | |
64 | 61 |
class Formatter |
65 | 62 |
include Redmine::WikiFormatting::SectionHelper |
... | ... | |
69 | 66 |
end |
70 | 67 | |
71 | 68 |
def to_html(*args) |
72 |
result = MarkdownPipeline.call @text |
|
73 |
result[:output].to_s |
|
69 |
html = MarkdownFilter.new(@text, PIPELINE_CONFIG).call |
|
70 |
fragment = Redmine::WikiFormatting::HtmlParser.parse(html) |
|
71 |
SANITIZER.call(fragment) |
|
72 |
SCRUBBERS.each do |scrubber| |
|
73 |
fragment.scrub!(scrubber) |
|
74 |
end |
|
75 |
fragment.to_s |
|
74 | 76 |
end |
75 | 77 |
end |
76 | 78 |
end |
lib/redmine/wiki_formatting/common_mark/markdown_filter.rb | ||
---|---|---|
25 | 25 |
# We do not use the stock HTML::Pipeline::MarkdownFilter because this |
26 | 26 |
# does not allow for straightforward configuration of render and parsing |
27 | 27 |
# options |
28 |
class MarkdownFilter < HTML::Pipeline::TextFilter |
|
29 |
def initialize(text, context = nil, result = nil) |
|
30 |
super |
|
31 |
@text = @text.delete "\r" |
|
28 |
class MarkdownFilter |
|
29 |
attr_reader :context |
|
30 | ||
31 |
def initialize(text, context = nil) |
|
32 |
@text = text.delete "\r" |
|
33 |
@context = context |
|
32 | 34 |
end |
33 | 35 | |
34 | 36 |
def call |
lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb | ||
---|---|---|
21 | 21 |
module WikiFormatting |
22 | 22 |
module CommonMark |
23 | 23 |
# sanitizes rendered HTML using the Sanitize gem |
24 |
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
|
|
24 |
class SanitizationFilter |
|
25 | 25 |
include Redmine::Helpers::URL |
26 | ||
27 |
attr_accessor :allowlist |
|
28 | ||
29 |
LISTS = Set.new(%w[ul ol].freeze) |
|
30 |
LIST_ITEM = 'li' |
|
31 | ||
32 |
# List of table child elements. These must be contained by a <table> element |
|
33 |
# or they are not allowed through. Otherwise they can be used to break out |
|
34 |
# of places we're using tables to contain formatted user content (like pull |
|
35 |
# request review comments). |
|
36 |
TABLE_ITEMS = Set.new(%w[tr td th].freeze) |
|
37 |
TABLE = 'table' |
|
38 |
TABLE_SECTIONS = Set.new(%w[thead tbody tfoot].freeze) |
|
39 | ||
40 |
# The main sanitization allowlist. Only these elements and attributes are |
|
41 |
# allowed through by default. |
|
42 |
ALLOWLIST = { |
|
43 |
:elements => %w[ |
|
44 |
h1 h2 h3 h4 h5 h6 br b i strong em a pre code img input tt u |
|
45 |
div ins del sup sub p ol ul table thead tbody tfoot blockquote |
|
46 |
dl dt dd kbd q samp var hr ruby rt rp li tr td th s strike summary |
|
47 |
details caption figure figcaption |
|
48 |
abbr bdo cite dfn mark small span time wbr |
|
49 |
].freeze, |
|
50 |
:remove_contents => ['script'].freeze, |
|
51 |
:attributes => { |
|
52 |
'a' => %w[href id name].freeze, |
|
53 |
'img' => %w[src longdesc].freeze, |
|
54 |
'code' => ['class'].freeze, |
|
55 |
'div' => %w[class itemscope itemtype].freeze, |
|
56 |
'li' => %w[id class].freeze, |
|
57 |
'input' => %w[class type].freeze, |
|
58 |
'p' => ['class'].freeze, |
|
59 |
'ul' => ['class'].freeze, |
|
60 |
'blockquote' => ['cite'].freeze, |
|
61 |
'del' => ['cite'].freeze, |
|
62 |
'ins' => ['cite'].freeze, |
|
63 |
'q' => ['cite'].freeze, |
|
64 |
:all => %w[ |
|
65 |
abbr accept accept-charset |
|
66 |
accesskey action align alt |
|
67 |
aria-describedby aria-hidden aria-label aria-labelledby |
|
68 |
axis border cellpadding cellspacing char |
|
69 |
charoff charset checked |
|
70 |
clear cols colspan color |
|
71 |
compact coords datetime dir |
|
72 |
disabled enctype for frame |
|
73 |
headers height hreflang |
|
74 |
hspace ismap label lang |
|
75 |
maxlength media method |
|
76 |
multiple nohref noshade |
|
77 |
nowrap open progress prompt readonly rel rev |
|
78 |
role rows rowspan rules scope |
|
79 |
selected shape size span |
|
80 |
start style summary tabindex target |
|
81 |
title type usemap valign value |
|
82 |
vspace width itemprop |
|
83 |
].freeze |
|
84 |
}.freeze, |
|
85 |
:protocols => { |
|
86 |
'blockquote' => { 'cite' => ['http', 'https', :relative].freeze }, |
|
87 |
'del' => { 'cite' => ['http', 'https', :relative].freeze }, |
|
88 |
'ins' => { 'cite' => ['http', 'https', :relative].freeze }, |
|
89 |
'q' => { 'cite' => ['http', 'https', :relative].freeze }, |
|
90 |
'img' => { |
|
91 |
'src' => ['http', 'https', :relative].freeze, |
|
92 |
'longdesc' => ['http', 'https', :relative].freeze |
|
93 |
}.freeze |
|
94 |
}, |
|
95 |
:transformers => [ |
|
96 |
# Top-level <li> elements are removed because they can break out of |
|
97 |
# containing markup. |
|
98 |
lambda { |env| |
|
99 |
name = env[:node_name] |
|
100 |
node = env[:node] |
|
101 |
if name == LIST_ITEM && node.ancestors.none? { |n| LISTS.include?(n.name) } |
|
102 |
node.replace(node.children) |
|
103 |
end |
|
104 |
}, |
|
105 | ||
106 |
# Table child elements that are not contained by a <table> are removed. |
|
107 |
lambda { |env| |
|
108 |
name = env[:node_name] |
|
109 |
node = env[:node] |
|
110 |
if (TABLE_SECTIONS.include?(name) || TABLE_ITEMS.include?(name)) && node.ancestors.none? { |n| n.name == TABLE } |
|
111 |
node.replace(node.children) |
|
112 |
end |
|
113 |
} |
|
114 |
].freeze, |
|
115 |
:css => { |
|
116 |
:properties => %w[ |
|
117 |
color background-color |
|
118 |
width min-width max-width |
|
119 |
height min-height max-height |
|
120 |
padding padding-left padding-right padding-top padding-bottom |
|
121 |
margin margin-left margin-right margin-top margin-bottom |
|
122 |
border border-left border-right border-top border-bottom border-radius border-style border-collapse border-spacing |
|
123 |
font font-style font-variant font-weight font-stretch font-size line-height font-family |
|
124 |
text-align |
|
125 |
float |
|
126 |
].freeze |
|
127 |
} |
|
128 |
}.freeze |
|
129 | ||
26 | 130 |
RELAXED_PROTOCOL_ATTRS = { |
27 | 131 |
"a" => %w(href).freeze, |
28 | 132 |
}.freeze |
29 | 133 | |
30 |
ALLOWED_CSS_PROPERTIES = %w[ |
|
31 |
color background-color |
|
32 |
width min-width max-width |
|
33 |
height min-height max-height |
|
34 |
padding padding-left padding-right padding-top padding-bottom |
|
35 |
margin margin-left margin-right margin-top margin-bottom |
|
36 |
border border-left border-right border-top border-bottom border-radius border-style border-collapse border-spacing |
|
37 |
font font-style font-variant font-weight font-stretch font-size line-height font-family |
|
38 |
text-align |
|
39 |
float |
|
40 |
].freeze |
|
41 | ||
42 |
def allowlist |
|
43 |
@allowlist ||= customize_allowlist(super.deep_dup) |
|
134 |
def initialize |
|
135 |
@allowlist = default_allowlist |
|
136 |
add_transformers |
|
44 | 137 |
end |
45 | 138 | |
46 |
private |
|
47 | ||
48 |
# customizes the allowlist defined in |
|
49 |
# https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/sanitization_filter.rb |
|
50 |
def customize_allowlist(allowlist) |
|
51 |
# Disallow `name` attribute globally, allow on `a` |
|
52 |
allowlist[:attributes][:all].delete("name") |
|
53 |
allowlist[:attributes]["a"].push("name") |
|
139 |
def call(doc) |
|
140 |
# Sanitize is applied to the whole document, so the API is different from loofeh's scrubber. |
|
141 |
Sanitize.clean_node!(doc, allowlist) |
|
142 |
end |
|
54 | 143 | |
55 |
allowlist[:attributes][:all].push("style") |
|
56 |
allowlist[:css] = { properties: ALLOWED_CSS_PROPERTIES } |
|
144 |
private |
|
57 | 145 | |
146 |
def add_transformers |
|
58 | 147 |
# allow class on code tags (this holds the language info from fenced |
59 | 148 |
# code bocks and has the format language-foo) |
60 |
allowlist[:attributes]["code"] = %w(class) |
|
61 |
allowlist[:transformers].push lambda{|env| |
|
149 |
allowlist[:transformers].push lambda {|env| |
|
62 | 150 |
node = env[:node] |
63 | 151 |
return unless node.name == "code" |
64 | 152 |
return unless node.has_attribute?("class") |
... | ... | |
70 | 158 | |
71 | 159 |
# Allow class on div and p tags only for alert blocks |
72 | 160 |
# (must be exactly: "markdown-alert markdown-alert-*" for div, and "markdown-alert-title" for p) |
73 |
(allowlist[:attributes]["div"] ||= []) << "class" |
|
74 |
(allowlist[:attributes]["p"] ||= []) << "class" |
|
75 |
allowlist[:transformers].push lambda{|env| |
|
161 |
allowlist[:transformers].push lambda {|env| |
|
76 | 162 |
node = env[:node] |
77 | 163 |
return unless node.element? |
78 | 164 | |
... | ... | |
98 | 184 |
# allowlist[:attributes]["td"] = %w(style) |
99 | 185 |
# allowlist[:css] = { properties: ["text-align"] } |
100 | 186 | |
101 |
# Allow `id` in a elements for footnotes |
|
102 |
allowlist[:attributes]["a"].push "id" |
|
103 | 187 |
# Remove any `id` property not matching for footnotes |
104 |
allowlist[:transformers].push lambda{|env| |
|
188 |
allowlist[:transformers].push lambda {|env|
|
|
105 | 189 |
node = env[:node] |
106 | 190 |
return unless node.name == "a" |
107 | 191 |
return unless node.has_attribute?("id") |
... | ... | |
112 | 196 | |
113 | 197 |
# allow `id` in li element for footnotes |
114 | 198 |
# allow `class` in li element for task list items |
115 |
allowlist[:attributes]["li"] = %w(id class) |
|
116 |
allowlist[:transformers].push lambda{|env| |
|
199 |
allowlist[:transformers].push lambda {|env| |
|
117 | 200 |
node = env[:node] |
118 | 201 |
return unless node.name == "li" |
119 | 202 | |
... | ... | |
128 | 211 | |
129 | 212 |
# allow input type = "checkbox" with class "task-list-item-checkbox" |
130 | 213 |
# for task list items |
131 |
allowlist[:elements].push('input') |
|
132 |
allowlist[:attributes]["input"] = %w(class type) |
|
133 |
allowlist[:transformers].push lambda{|env| |
|
214 |
allowlist[:transformers].push lambda {|env| |
|
134 | 215 |
node = env[:node] |
135 | ||
136 | 216 |
return unless node.name == "input" |
137 | 217 |
return if node['type'] == "checkbox" && node['class'] == "task-list-item-checkbox" |
138 | 218 | |
... | ... | |
140 | 220 |
} |
141 | 221 | |
142 | 222 |
# allow class "contains-task-list" on ul for task list items |
143 |
allowlist[:attributes]["ul"] = %w(class) |
|
144 |
allowlist[:transformers].push lambda{|env| |
|
223 |
allowlist[:transformers].push lambda {|env| |
|
145 | 224 |
node = env[:node] |
146 | ||
147 | 225 |
return unless node.name == "ul" |
148 | 226 |
return if node["class"] == "contains-task-list" |
149 | 227 | |
... | ... | |
151 | 229 |
} |
152 | 230 | |
153 | 231 |
# https://github.com/rgrove/sanitize/issues/209 |
154 |
allowlist[:protocols].delete("a") |
|
155 |
allowlist[:transformers].push lambda{|env| |
|
232 |
allowlist[:transformers].push lambda {|env| |
|
156 | 233 |
node = env[:node] |
157 | 234 |
return if node.type != Nokogiri::XML::Node::ELEMENT_NODE |
158 | 235 | |
... | ... | |
168 | 245 |
end |
169 | 246 |
end |
170 | 247 |
} |
248 |
end |
|
171 | 249 | |
172 |
# Allow `u` element to enable underline
|
|
173 |
allowlist[:elements].push('u')
|
|
174 | ||
175 |
allowlist
|
|
250 |
# The allowlist to use when sanitizing. This can be passed in the context
|
|
251 |
# hash to the filter but defaults to ALLOWLIST constant value above.
|
|
252 |
def default_allowlist |
|
253 |
ALLOWLIST.deep_dup
|
|
176 | 254 |
end |
177 | 255 |
end |
178 | 256 |
end |
lib/redmine/wiki_formatting/common_mark/syntax_highlight_filter.rb → lib/redmine/wiki_formatting/common_mark/syntax_highlight_scrubber.rb | ||
---|---|---|
22 | 22 |
module CommonMark |
23 | 23 |
# Redmine Syntax highlighting for <pre><code class="language-foo"> |
24 | 24 |
# blocks as generated by commonmarker |
25 |
class SyntaxHighlightFilter < HTML::Pipeline::Filter
|
|
26 |
def call
|
|
27 |
doc.search("pre > code").each do |node|
|
|
28 |
next unless lang = node["class"].presence
|
|
29 |
next unless lang =~ /\Alanguage-(\S+)\z/
|
|
25 |
class SyntaxHighlightScrubber < Loofah::Scrubber
|
|
26 |
def scrub(node)
|
|
27 |
if node.matches?("pre > code")
|
|
28 |
return unless lang = node["class"].presence
|
|
29 |
return unless lang =~ /\Alanguage-(\S+)\z/
|
|
30 | 30 | |
31 | 31 |
lang = $1 |
32 | 32 |
text = node.inner_text |
... | ... | |
36 | 36 | |
37 | 37 |
if Redmine::SyntaxHighlighting.language_supported?(lang) |
38 | 38 |
html = Redmine::SyntaxHighlighting.highlight_by_language(text, lang) |
39 |
next if html.nil?
|
|
39 |
return if html.nil?
|
|
40 | 40 | |
41 | 41 |
node.inner_html = html |
42 | 42 |
node["class"] = "#{lang} syntaxhl" |
... | ... | |
45 | 45 |
node.remove_attribute("class") |
46 | 46 |
end |
47 | 47 |
end |
48 |
doc |
|
49 | 48 |
end |
50 | 49 |
end |
51 | 50 |
end |
lib/redmine/wiki_formatting/html_parser.rb | ||
---|---|---|
28 | 28 |
'style' => '' |
29 | 29 |
} |
30 | 30 | |
31 |
def self.parse(html) |
|
32 |
Loofah.html5_fragment(html) |
|
33 |
end |
|
34 | ||
31 | 35 |
def self.to_text(html) |
32 | 36 |
html = html.gsub(/[\n\r]/, ' ') |
33 | 37 |
lib/redmine/wiki_formatting/html_sanitizer.rb | ||
---|---|---|
21 | 21 |
module WikiFormatting |
22 | 22 |
# Combination of SanitizationFilter and ExternalLinksFilter |
23 | 23 |
class HtmlSanitizer |
24 |
Pipeline = HTML::Pipeline.new( |
|
25 |
[ |
|
26 |
Redmine::WikiFormatting::CommonMark::SanitizationFilter, |
|
27 |
Redmine::WikiFormatting::CommonMark::ExternalLinksFilter, |
|
28 |
], {}) |
|
24 |
SANITIZER = Redmine::WikiFormatting::CommonMark::SanitizationFilter.new |
|
25 |
SCRUBBERS = [Redmine::WikiFormatting::CommonMark::ExternalLinksScrubber.new] |
|
29 | 26 | |
30 | 27 |
def self.call(html) |
31 |
result = Pipeline.call html |
|
32 |
result[:output].to_s |
|
28 |
fragment = HtmlParser.parse(html) |
|
29 |
SANITIZER.call(fragment) |
|
30 |
SCRUBBERS.each do |scrubber| |
|
31 |
fragment.scrub!(scrubber) |
|
32 |
end |
|
33 |
fragment.to_s |
|
33 | 34 |
end |
34 | 35 |
end |
35 | 36 |
end |
test/unit/lib/redmine/wiki_formatting/common_mark/external_links_filter_test.rb → test/unit/lib/redmine/wiki_formatting/common_mark/external_links_scrubber_test.rb | ||
---|---|---|
20 | 20 |
require_relative '../../../../../test_helper' |
21 | 21 | |
22 | 22 |
if Object.const_defined?(:Commonmarker) |
23 |
require 'redmine/wiki_formatting/common_mark/external_links_filter' |
|
24 | 23 | |
25 |
class Redmine::WikiFormatting::CommonMark::ExternalLinksFilterTest < ActiveSupport::TestCase
|
|
24 |
class Redmine::WikiFormatting::CommonMark::ExternalScrubberFilterTest < ActiveSupport::TestCase
|
|
26 | 25 |
def filter(html) |
27 |
Redmine::WikiFormatting::CommonMark::ExternalLinksFilter.to_html(html, @options) |
|
28 |
end |
|
29 | ||
30 |
def setup |
|
31 |
@options = { } |
|
26 |
fragment = Redmine::WikiFormatting::HtmlParser.parse(html) |
|
27 |
scrubber = Redmine::WikiFormatting::CommonMark::ExternalLinksScrubber.new |
|
28 |
fragment.scrub!(scrubber) |
|
29 |
fragment.to_s |
|
32 | 30 |
end |
33 | 31 | |
34 | 32 |
def test_external_links_should_have_external_css_class |
test/unit/lib/redmine/wiki_formatting/common_mark/fixup_auto_links_filter_test.rb → test/unit/lib/redmine/wiki_formatting/common_mark/fixup_auto_links_scrubber_test.rb | ||
---|---|---|
20 | 20 |
require_relative '../../../../../test_helper' |
21 | 21 | |
22 | 22 |
if Object.const_defined?(:Commonmarker) |
23 |
require 'redmine/wiki_formatting/common_mark/fixup_auto_links_filter' |
|
24 | 23 | |
25 |
class Redmine::WikiFormatting::CommonMark::FixupAutoLinksFilterTest < ActiveSupport::TestCase
|
|
24 |
class Redmine::WikiFormatting::CommonMark::FixupAutoLinksScrubberTest < ActiveSupport::TestCase
|
|
26 | 25 |
def filter(html) |
27 |
Redmine::WikiFormatting::CommonMark::FixupAutoLinksFilter.to_html(html, @options) |
|
26 |
fragment = Redmine::WikiFormatting::HtmlParser.parse(html) |
|
27 |
scrubber = Redmine::WikiFormatting::CommonMark::FixupAutoLinksScrubber.new |
|
28 |
fragment.scrub!(scrubber) |
|
29 |
fragment.to_s |
|
28 | 30 |
end |
29 | 31 | |
30 | 32 |
def format(markdown) |
31 |
Redmine::WikiFormatting::CommonMark::MarkdownFilter.to_html(markdown, Redmine::WikiFormatting::CommonMark::PIPELINE_CONFIG) |
|
32 |
end |
|
33 | ||
34 |
def setup |
|
35 |
@options = { } |
|
33 |
Redmine::WikiFormatting::CommonMark::MarkdownFilter.new(markdown, Redmine::WikiFormatting::CommonMark::PIPELINE_CONFIG).call |
|
36 | 34 |
end |
37 | 35 | |
38 | 36 |
def test_should_fixup_autolinked_user_references |
test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb | ||
---|---|---|
255 | 255 | |
256 | 256 |
def test_should_support_html_tables |
257 | 257 |
text = '<table style="background: red"><tr><td>Cell</td></tr></table>' |
258 |
assert_equal '<table><tr><td>Cell</td></tr></table>', to_html(text)
|
|
258 |
assert_equal '<table><tbody><tr><td>Cell</td></tr></tbody></table>', to_html(text)
|
|
259 | 259 |
end |
260 | 260 | |
261 | 261 |
def test_should_remove_unsafe_uris |
... | ... | |
289 | 289 |
<p>Task list:</p> |
290 | 290 |
<ul class="contains-task-list"> |
291 | 291 |
<li class="task-list-item"> |
292 |
<input type="checkbox" class="task-list-item-checkbox" disabled> Task 1 |
|
292 |
<input type="checkbox" class="task-list-item-checkbox" disabled=""> Task 1
|
|
293 | 293 |
</li> |
294 | 294 |
<li class="task-list-item"> |
295 |
<input type="checkbox" class="task-list-item-checkbox" checked disabled> Task 2</li>
|
|
295 |
<input type="checkbox" class="task-list-item-checkbox" checked="" disabled=""> Task 2</li>
|
|
296 | 296 |
</ul> |
297 | 297 |
EXPECTED |
298 | 298 |
test/unit/lib/redmine/wiki_formatting/common_mark/markdown_filter_test.rb | ||
---|---|---|
24 | 24 | |
25 | 25 |
class Redmine::WikiFormatting::CommonMark::MarkdownFilterTest < ActiveSupport::TestCase |
26 | 26 |
def filter(markdown) |
27 |
Redmine::WikiFormatting::CommonMark::MarkdownFilter.to_html(markdown) |
|
27 |
filter = Redmine::WikiFormatting::CommonMark::MarkdownFilter.new( |
|
28 |
markdown, |
|
29 |
Redmine::WikiFormatting::CommonMark::PIPELINE_CONFIG) |
|
30 |
filter.call |
|
28 | 31 |
end |
29 | 32 | |
30 | 33 |
# just a basic sanity test. more formatting tests in the formatter_test |
test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb | ||
---|---|---|
20 | 20 |
require_relative '../../../../../test_helper' |
21 | 21 | |
22 | 22 |
if Object.const_defined?(:Commonmarker) |
23 |
require 'redmine/wiki_formatting/common_mark/sanitization_filter' |
|
24 | 23 | |
25 | 24 |
class Redmine::WikiFormatting::CommonMark::SanitizationFilterTest < ActiveSupport::TestCase |
26 | 25 |
def filter(html) |
27 |
Redmine::WikiFormatting::CommonMark::SanitizationFilter.to_html(html, @options) |
|
28 |
end |
|
29 | ||
30 |
def setup |
|
31 |
@options = { } |
|
26 |
fragment = Redmine::WikiFormatting::HtmlParser.parse(html) |
|
27 |
sanitizer = Redmine::WikiFormatting::CommonMark::SanitizationFilter.new |
|
28 |
sanitizer.call(fragment) |
|
29 |
fragment.to_s |
|
32 | 30 |
end |
33 | 31 | |
34 | 32 |
def test_should_filter_tags |
... | ... | |
137 | 135 |
], |
138 | 136 |
[ |
139 | 137 |
'Lo<!-- comment -->rem</b> <a href=pants title="foo>ipsum <a href="http://foo.com/"><strong>dolor</a></strong> sit<br/>amet <script>alert("hello world");', |
140 |
'Lorem <a href="pants" title="foo>ipsum <a href="><strong>dolor</strong></a> sit<br>amet '
|
|
138 |
'Lorem <a href="pants" title="foo>ipsum <a href="><strong>dolor</strong></a> sit<br>amet '
|
|
141 | 139 |
], |
142 | 140 |
[ |
143 | 141 |
'<p>a</p><blockquote>b', |
... | ... | |
217 | 215 | |
218 | 216 |
'protocol-based JS injection: null char' => [ |
219 | 217 |
"<img src=java\0script:alert(\"XSS\")>", |
220 |
'<img src="java">' |
|
221 |
# '<img>' |
|
218 |
'<img>' |
|
222 | 219 |
], |
223 | 220 | |
224 | 221 |
'protocol-based JS injection: invalid URL char' => [ |
... | ... | |
228 | 225 | |
229 | 226 |
'protocol-based JS injection: spaces and entities' => [ |
230 | 227 |
'<img src="  javascript:alert(\'XSS\');">', |
231 |
'<img src="">' |
|
232 |
# '<img>' |
|
228 |
'<img>' |
|
233 | 229 |
], |
234 | 230 | |
235 | 231 |
'protocol whitespace' => [ |
test/unit/lib/redmine/wiki_formatting/common_mark/syntax_highlight_filter_test.rb → test/unit/lib/redmine/wiki_formatting/common_mark/syntax_highlight_scrubber_test.rb | ||
---|---|---|
19 | 19 | |
20 | 20 |
require_relative '../../../../../test_helper' |
21 | 21 |
if Object.const_defined?(:Commonmarker) |
22 |
require 'redmine/wiki_formatting/common_mark/syntax_highlight_filter' |
|
23 | 22 | |
24 |
class Redmine::WikiFormatting::CommonMark::SyntaxHighlightFilterTest < ActiveSupport::TestCase
|
|
23 |
class Redmine::WikiFormatting::CommonMark::SyntaxHighlightScrubberTest < ActiveSupport::TestCase
|
|
25 | 24 |
def filter(html) |
26 |
Redmine::WikiFormatting::CommonMark::SyntaxHighlightFilter.to_html(html, @options) |
|
27 |
end |
|
28 | ||
29 |
def setup |
|
30 |
@options = { } |
|
25 |
fragment = Redmine::WikiFormatting::HtmlParser.parse(html) |
|
26 |
scrubber = Redmine::WikiFormatting::CommonMark::SyntaxHighlightScrubber.new |
|
27 |
fragment.scrub!(scrubber) |
|
28 |
fragment.to_s |
|
31 | 29 |
end |
32 | 30 | |
33 | 31 |
def test_should_highlight_supported_language |