Merge branch 'issue-63298-asciidoc-sanitization' into 'master'
Prevent excessive sanitization of AsciiDoc ouptut Closes #63298 See merge request gitlab-org/gitlab-ce!30290
This commit is contained in:
commit
8405483031
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
@import 'framework/animations';
|
@import 'framework/animations';
|
||||||
@import 'framework/vue_transitions';
|
@import 'framework/vue_transitions';
|
||||||
@import 'framework/asciidoctor';
|
|
||||||
@import 'framework/banner';
|
@import 'framework/banner';
|
||||||
@import 'framework/blocks';
|
@import 'framework/blocks';
|
||||||
@import 'framework/buttons';
|
@import 'framework/buttons';
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
.admonitionblock td.icon {
|
|
||||||
width: 1%;
|
|
||||||
|
|
||||||
[class^='fa icon-'] {
|
|
||||||
@extend .fa-2x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-note {
|
|
||||||
@extend .fa-thumb-tack;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-tip {
|
|
||||||
@extend .fa-lightbulb-o;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-warning {
|
|
||||||
@extend .fa-exclamation-triangle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-caution {
|
|
||||||
@extend .fa-fire;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-important {
|
|
||||||
@extend .fa-exclamation-circle;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Apply Markdown typography
|
* Apply Markup (Markdown/AsciiDoc) typography
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
.md:not(.use-csslab) {
|
.md:not(.use-csslab) {
|
||||||
|
@ -245,6 +245,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.checklist,
|
||||||
|
ul.none,
|
||||||
|
ol.none,
|
||||||
|
ul.no-bullet,
|
||||||
|
ol.no-bullet,
|
||||||
|
ol.unnumbered,
|
||||||
|
ul.unstyled,
|
||||||
|
ol.unstyled {
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
line-height: 1.6em;
|
line-height: 1.6em;
|
||||||
margin-left: 25px;
|
margin-left: 25px;
|
||||||
|
@ -321,6 +336,54 @@
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.big {
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overline {
|
||||||
|
text-decoration: overline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-through {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admonitionblock td.icon {
|
||||||
|
width: 1%;
|
||||||
|
|
||||||
|
[class^='fa icon-'] {
|
||||||
|
@extend .fa-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-note {
|
||||||
|
@extend .fa-thumb-tack;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tip {
|
||||||
|
@extend .fa-lightbulb-o;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-warning {
|
||||||
|
@extend .fa-exclamation-triangle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-caution {
|
||||||
|
@extend .fa-fire;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-important {
|
||||||
|
@extend .fa-exclamation-circle;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: "Prevent excessive sanitization of AsciiDoc ouptut"
|
||||||
|
merge_request: 30290
|
||||||
|
author: Guillaume Grossetie
|
||||||
|
type: added
|
|
@ -0,0 +1,62 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Banzai
|
||||||
|
module Filter
|
||||||
|
# Sanitize HTML produced by AsciiDoc/Asciidoctor.
|
||||||
|
#
|
||||||
|
# Extends Banzai::Filter::BaseSanitizationFilter with specific rules.
|
||||||
|
class AsciiDocSanitizationFilter < Banzai::Filter::BaseSanitizationFilter
|
||||||
|
# Classes used by Asciidoctor to style components
|
||||||
|
ADMONITION_CLASSES = %w(fa icon-note icon-tip icon-warning icon-caution icon-important).freeze
|
||||||
|
CALLOUT_CLASSES = ['conum'].freeze
|
||||||
|
CHECKLIST_CLASSES = %w(fa fa-check-square-o fa-square-o).freeze
|
||||||
|
|
||||||
|
LIST_CLASSES = %w(checklist none no-bullet unnumbered unstyled).freeze
|
||||||
|
|
||||||
|
ELEMENT_CLASSES_WHITELIST = {
|
||||||
|
span: %w(big small underline overline line-through).freeze,
|
||||||
|
div: ['admonitionblock'].freeze,
|
||||||
|
td: ['icon'].freeze,
|
||||||
|
i: ADMONITION_CLASSES + CALLOUT_CLASSES + CHECKLIST_CLASSES,
|
||||||
|
ul: LIST_CLASSES,
|
||||||
|
ol: LIST_CLASSES
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def customize_whitelist(whitelist)
|
||||||
|
# Allow marks
|
||||||
|
whitelist[:elements].push('mark')
|
||||||
|
|
||||||
|
# Allow any classes in `span`, `i`, `div`, `td`, `ul` and `ol` elements
|
||||||
|
# but then remove any unknown classes
|
||||||
|
whitelist[:attributes]['span'] = %w(class)
|
||||||
|
whitelist[:attributes]['div'].push('class')
|
||||||
|
whitelist[:attributes]['td'] = %w(class)
|
||||||
|
whitelist[:attributes]['i'] = %w(class)
|
||||||
|
whitelist[:attributes]['ul'] = %w(class)
|
||||||
|
whitelist[:attributes]['ol'] = %w(class)
|
||||||
|
whitelist[:transformers].push(self.class.remove_element_classes)
|
||||||
|
|
||||||
|
whitelist
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def remove_element_classes
|
||||||
|
lambda do |env|
|
||||||
|
node = env[:node]
|
||||||
|
|
||||||
|
return unless (classes_whitelist = ELEMENT_CLASSES_WHITELIST[node.name.to_sym])
|
||||||
|
return unless node.has_attribute?('class')
|
||||||
|
|
||||||
|
classes = node['class'].strip.split(' ')
|
||||||
|
allowed_classes = (classes & classes_whitelist)
|
||||||
|
if allowed_classes.empty?
|
||||||
|
node.remove_attribute('class')
|
||||||
|
else
|
||||||
|
node['class'] = allowed_classes.join(' ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,96 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Banzai
|
||||||
|
module Filter
|
||||||
|
# Sanitize HTML produced by markup languages (Markdown, AsciiDoc...).
|
||||||
|
# Specific rules are implemented in dedicated filters:
|
||||||
|
#
|
||||||
|
# - Banzai::Filter::SanitizationFilter (Markdown)
|
||||||
|
# - Banzai::Filter::AsciiDocSanitizationFilter (AsciiDoc/Asciidoctor)
|
||||||
|
#
|
||||||
|
# Extends HTML::Pipeline::SanitizationFilter with common rules.
|
||||||
|
class BaseSanitizationFilter < HTML::Pipeline::SanitizationFilter
|
||||||
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
|
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
|
||||||
|
|
||||||
|
def whitelist
|
||||||
|
strong_memoize(:whitelist) do
|
||||||
|
whitelist = super.deep_dup
|
||||||
|
|
||||||
|
# Allow span elements
|
||||||
|
whitelist[:elements].push('span')
|
||||||
|
|
||||||
|
# Allow data-math-style attribute in order to support LaTeX formatting
|
||||||
|
whitelist[:attributes]['code'] = %w(data-math-style)
|
||||||
|
whitelist[:attributes]['pre'] = %w(data-math-style)
|
||||||
|
|
||||||
|
# Allow html5 details/summary elements
|
||||||
|
whitelist[:elements].push('details')
|
||||||
|
whitelist[:elements].push('summary')
|
||||||
|
|
||||||
|
# Allow abbr elements with title attribute
|
||||||
|
whitelist[:elements].push('abbr')
|
||||||
|
whitelist[:attributes]['abbr'] = %w(title)
|
||||||
|
|
||||||
|
# Disallow `name` attribute globally, allow on `a`
|
||||||
|
whitelist[:attributes][:all].delete('name')
|
||||||
|
whitelist[:attributes]['a'].push('name')
|
||||||
|
|
||||||
|
# Allow any protocol in `a` elements
|
||||||
|
# and then remove links with unsafe protocols
|
||||||
|
whitelist[:protocols].delete('a')
|
||||||
|
whitelist[:transformers].push(self.class.remove_unsafe_links)
|
||||||
|
|
||||||
|
# Remove `rel` attribute from `a` elements
|
||||||
|
whitelist[:transformers].push(self.class.remove_rel)
|
||||||
|
|
||||||
|
customize_whitelist(whitelist)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def customize_whitelist(whitelist)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def remove_unsafe_links
|
||||||
|
lambda do |env|
|
||||||
|
node = env[:node]
|
||||||
|
|
||||||
|
return unless node.name == 'a'
|
||||||
|
return unless node.has_attribute?('href')
|
||||||
|
|
||||||
|
begin
|
||||||
|
node['href'] = node['href'].strip
|
||||||
|
uri = Addressable::URI.parse(node['href'])
|
||||||
|
|
||||||
|
return unless uri.scheme
|
||||||
|
|
||||||
|
# Remove all invalid scheme characters before checking against the
|
||||||
|
# list of unsafe protocols.
|
||||||
|
#
|
||||||
|
# See https://tools.ietf.org/html/rfc3986#section-3.1
|
||||||
|
scheme = uri.scheme
|
||||||
|
.strip
|
||||||
|
.downcase
|
||||||
|
.gsub(/[^A-Za-z0-9\+\.\-]+/, '')
|
||||||
|
|
||||||
|
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(scheme)
|
||||||
|
rescue Addressable::URI::InvalidURIError
|
||||||
|
node.remove_attribute('href')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_rel
|
||||||
|
lambda do |env|
|
||||||
|
if env[:node_name] == 'a'
|
||||||
|
env[:node].remove_attribute('rel')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,23 +2,13 @@
|
||||||
|
|
||||||
module Banzai
|
module Banzai
|
||||||
module Filter
|
module Filter
|
||||||
# Sanitize HTML
|
# Sanitize HTML produced by Markdown.
|
||||||
#
|
#
|
||||||
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
|
# Extends Banzai::Filter::BaseSanitizationFilter with specific rules.
|
||||||
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
|
class SanitizationFilter < Banzai::Filter::BaseSanitizationFilter
|
||||||
include Gitlab::Utils::StrongMemoize
|
# Styles used by Markdown for table alignment
|
||||||
|
|
||||||
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
|
|
||||||
TABLE_ALIGNMENT_PATTERN = /text-align: (?<alignment>center|left|right)/.freeze
|
TABLE_ALIGNMENT_PATTERN = /text-align: (?<alignment>center|left|right)/.freeze
|
||||||
|
|
||||||
def whitelist
|
|
||||||
strong_memoize(:whitelist) do
|
|
||||||
customize_whitelist(super.deep_dup)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def customize_whitelist(whitelist)
|
def customize_whitelist(whitelist)
|
||||||
# Allow table alignment; we whitelist specific text-align values in a
|
# Allow table alignment; we whitelist specific text-align values in a
|
||||||
# transformer below
|
# transformer below
|
||||||
|
@ -26,36 +16,9 @@ module Banzai
|
||||||
whitelist[:attributes]['td'] = %w(style)
|
whitelist[:attributes]['td'] = %w(style)
|
||||||
whitelist[:css] = { properties: ['text-align'] }
|
whitelist[:css] = { properties: ['text-align'] }
|
||||||
|
|
||||||
# Allow span elements
|
|
||||||
whitelist[:elements].push('span')
|
|
||||||
|
|
||||||
# Allow data-math-style attribute in order to support LaTeX formatting
|
|
||||||
whitelist[:attributes]['code'] = %w(data-math-style)
|
|
||||||
whitelist[:attributes]['pre'] = %w(data-math-style)
|
|
||||||
|
|
||||||
# Allow html5 details/summary elements
|
|
||||||
whitelist[:elements].push('details')
|
|
||||||
whitelist[:elements].push('summary')
|
|
||||||
|
|
||||||
# Allow abbr elements with title attribute
|
|
||||||
whitelist[:elements].push('abbr')
|
|
||||||
whitelist[:attributes]['abbr'] = %w(title)
|
|
||||||
|
|
||||||
# Allow the 'data-sourcepos' from CommonMark on all elements
|
# Allow the 'data-sourcepos' from CommonMark on all elements
|
||||||
whitelist[:attributes][:all].push('data-sourcepos')
|
whitelist[:attributes][:all].push('data-sourcepos')
|
||||||
|
|
||||||
# Disallow `name` attribute globally, allow on `a`
|
|
||||||
whitelist[:attributes][:all].delete('name')
|
|
||||||
whitelist[:attributes]['a'].push('name')
|
|
||||||
|
|
||||||
# Allow any protocol in `a` elements
|
|
||||||
# and then remove links with unsafe protocols
|
|
||||||
whitelist[:protocols].delete('a')
|
|
||||||
whitelist[:transformers].push(self.class.remove_unsafe_links)
|
|
||||||
|
|
||||||
# Remove `rel` attribute from `a` elements
|
|
||||||
whitelist[:transformers].push(self.class.remove_rel)
|
|
||||||
|
|
||||||
# Remove any `style` properties not required for table alignment
|
# Remove any `style` properties not required for table alignment
|
||||||
whitelist[:transformers].push(self.class.remove_unsafe_table_style)
|
whitelist[:transformers].push(self.class.remove_unsafe_table_style)
|
||||||
|
|
||||||
|
@ -69,43 +32,6 @@ module Banzai
|
||||||
end
|
end
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def remove_unsafe_links
|
|
||||||
lambda do |env|
|
|
||||||
node = env[:node]
|
|
||||||
|
|
||||||
return unless node.name == 'a'
|
|
||||||
return unless node.has_attribute?('href')
|
|
||||||
|
|
||||||
begin
|
|
||||||
node['href'] = node['href'].strip
|
|
||||||
uri = Addressable::URI.parse(node['href'])
|
|
||||||
|
|
||||||
return unless uri.scheme
|
|
||||||
|
|
||||||
# Remove all invalid scheme characters before checking against the
|
|
||||||
# list of unsafe protocols.
|
|
||||||
#
|
|
||||||
# See https://tools.ietf.org/html/rfc3986#section-3.1
|
|
||||||
scheme = uri.scheme
|
|
||||||
.strip
|
|
||||||
.downcase
|
|
||||||
.gsub(/[^A-Za-z0-9\+\.\-]+/, '')
|
|
||||||
|
|
||||||
node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(scheme)
|
|
||||||
rescue Addressable::URI::InvalidURIError
|
|
||||||
node.remove_attribute('href')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_rel
|
|
||||||
lambda do |env|
|
|
||||||
if env[:node_name] == 'a'
|
|
||||||
env[:node].remove_attribute('rel')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_unsafe_table_style
|
def remove_unsafe_table_style
|
||||||
lambda do |env|
|
lambda do |env|
|
||||||
node = env[:node]
|
node = env[:node]
|
||||||
|
|
|
@ -5,7 +5,7 @@ module Banzai
|
||||||
class AsciiDocPipeline < BasePipeline
|
class AsciiDocPipeline < BasePipeline
|
||||||
def self.filters
|
def self.filters
|
||||||
FilterArray[
|
FilterArray[
|
||||||
Filter::SanitizationFilter,
|
Filter::AsciiDocSanitizationFilter,
|
||||||
Filter::SyntaxHighlightFilter,
|
Filter::SyntaxHighlightFilter,
|
||||||
Filter::ExternalLinkFilter,
|
Filter::ExternalLinkFilter,
|
||||||
Filter::PlantumlFilter,
|
Filter::PlantumlFilter,
|
||||||
|
|
|
@ -45,28 +45,117 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
context "XSS" do
|
context "XSS" do
|
||||||
links = {
|
items = {
|
||||||
'links' => {
|
'link with extra attribute' => {
|
||||||
input: 'link:mylink"onmouseover="alert(1)[Click Here]',
|
input: 'link:mylink"onmouseover="alert(1)[Click Here]',
|
||||||
output: "<div>\n<p><a href=\"mylink\">Click Here</a></p>\n</div>"
|
output: "<div>\n<p><a href=\"mylink\">Click Here</a></p>\n</div>"
|
||||||
},
|
},
|
||||||
'images' => {
|
'link with unsafe scheme' => {
|
||||||
|
input: 'link:data://danger[Click Here]',
|
||||||
|
output: "<div>\n<p><a>Click Here</a></p>\n</div>"
|
||||||
|
},
|
||||||
|
'image with onerror' => {
|
||||||
input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]',
|
input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]',
|
||||||
output: "<div>\n<p><span><img src=\"https://localhost.com/image.png\" alt='Alt text\" onerror=\"alert(7)'></span></p>\n</div>"
|
output: "<div>\n<p><span><img src=\"https://localhost.com/image.png\" alt='Alt text\" onerror=\"alert(7)'></span></p>\n</div>"
|
||||||
},
|
},
|
||||||
'pre' => {
|
'fenced code with inline script' => {
|
||||||
input: '```mypre"><script>alert(3)</script>',
|
input: '```mypre"><script>alert(3)</script>',
|
||||||
output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"></span></code></pre>\n</div>\n</div>"
|
output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"></span></code></pre>\n</div>\n</div>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
links.each do |name, data|
|
items.each do |name, data|
|
||||||
it "does not convert dangerous #{name} into HTML" do
|
it "does not convert dangerous #{name} into HTML" do
|
||||||
expect(render(data[:input], context)).to include(data[:output])
|
expect(render(data[:input], context)).to include(data[:output])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with admonition' do
|
||||||
|
it 'preserves classes' do
|
||||||
|
input = <<~ADOC
|
||||||
|
NOTE: An admonition paragraph, like this note, grabs the reader’s attention.
|
||||||
|
ADOC
|
||||||
|
|
||||||
|
output = <<~HTML
|
||||||
|
<div class="admonitionblock">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td class="icon">
|
||||||
|
<i class="fa icon-note" title="Note"></i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
An admonition paragraph, like this note, grabs the reader’s attention.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
expect(render(input, context)).to include(output.strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with checklist' do
|
||||||
|
it 'preserves classes' do
|
||||||
|
input = <<~ADOC
|
||||||
|
* [x] checked
|
||||||
|
* [ ] not checked
|
||||||
|
ADOC
|
||||||
|
|
||||||
|
output = <<~HTML
|
||||||
|
<div>
|
||||||
|
<ul class="checklist">
|
||||||
|
<li>
|
||||||
|
<p><i class="fa fa-check-square-o"></i> checked</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><i class="fa fa-square-o"></i> not checked</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
expect(render(input, context)).to include(output.strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with marks' do
|
||||||
|
it 'preserves classes' do
|
||||||
|
input = <<~ADOC
|
||||||
|
Werewolves are allergic to #cassia cinnamon#.
|
||||||
|
|
||||||
|
Did the werewolves read the [.small]#small print#?
|
||||||
|
|
||||||
|
Where did all the [.underline.small]#cores# run off to?
|
||||||
|
|
||||||
|
We need [.line-through]#ten# make that twenty VMs.
|
||||||
|
|
||||||
|
[.big]##O##nce upon an infinite loop.
|
||||||
|
ADOC
|
||||||
|
|
||||||
|
output = <<~HTML
|
||||||
|
<div>
|
||||||
|
<p>Werewolves are allergic to <mark>cassia cinnamon</mark>.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>Did the werewolves read the <span class="small">small print</span>?</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>Where did all the <span class="underline small">cores</span> run off to?</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>We need <span class="line-through">ten</span> make that twenty VMs.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p><span class="big">O</span>nce upon an infinite loop.</p>
|
||||||
|
</div>
|
||||||
|
HTML
|
||||||
|
|
||||||
|
expect(render(input, context)).to include(output.strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with fenced block' do
|
context 'with fenced block' do
|
||||||
it 'highlights syntax' do
|
it 'highlights syntax' do
|
||||||
input = <<~ADOC
|
input = <<~ADOC
|
||||||
|
|
Loading…
Reference in New Issue