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 |