diff options
Diffstat (limited to 'spec/lib/banzai')
10 files changed, 243 insertions, 525 deletions
diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb index d41f5e8633d..5ac7d3af733 100644 --- a/spec/lib/banzai/filter/footnote_filter_spec.rb +++ b/spec/lib/banzai/filter/footnote_filter_spec.rb @@ -56,52 +56,6 @@ RSpec.describe Banzai::Filter::FootnoteFilter do it 'properly adds the necessary ids and classes' do expect(doc.to_html).to eq filtered_footnote.strip end - - context 'using ruby-based HTML renderer' do - # first[^1] and second[^second] - # [^1]: one - # [^second]: two - let(:footnote) do - <<~EOF - <p>first<sup><a href="#fn1" id="fnref1">1</a></sup> and second<sup><a href="#fn2" id="fnref2">2</a></sup></p> - <p>same reference<sup><a href="#fn1" id="fnref1">1</a></sup></p> - <ol> - <li id="fn1"> - <p>one <a href="#fnref1">↩</a></p> - </li> - <li id="fn2"> - <p>two <a href="#fnref2">↩</a></p> - </li> - </ol> - EOF - end - - let(:filtered_footnote) do - <<~EOF - <p>first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p> - <p>same reference<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup></p> - <section class="footnotes"><ol> - <li id="fn1-#{identifier}"> - <p>one <a href="#fnref1-#{identifier}" class="footnote-backref">↩</a></p> - </li> - <li id="fn2-#{identifier}"> - <p>two <a href="#fnref2-#{identifier}" class="footnote-backref">↩</a></p> - </li> - </ol></section> - EOF - end - - let(:doc) { filter(footnote) } - let(:identifier) { link_node[:id].delete_prefix('fnref1-') } - - before do - stub_feature_flags(use_cmark_renderer: false) - end - - it 'properly adds the necessary ids and classes' do - expect(doc.to_html).to eq filtered_footnote - end - end end context 'when detecting footnotes' do diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb index 1c9b894e885..e3c8d121587 100644 --- a/spec/lib/banzai/filter/markdown_filter_spec.rb +++ b/spec/lib/banzai/filter/markdown_filter_spec.rb @@ -5,125 +5,90 @@ require 'spec_helper' RSpec.describe Banzai::Filter::MarkdownFilter do include FilterSpecHelper - shared_examples_for 'renders correct markdown' do - describe 'markdown engine from context' do - it 'defaults to CommonMark' do - expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance| - expect(instance).to receive(:render).and_return('test') - end - - filter('test') + describe 'markdown engine from context' do + it 'defaults to CommonMark' do + expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance| + expect(instance).to receive(:render).and_return('test') end - it 'uses CommonMark' do - expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance| - expect(instance).to receive(:render).and_return('test') - end + filter('test') + end - filter('test', { markdown_engine: :common_mark }) + it 'uses CommonMark' do + expect_next_instance_of(Banzai::Filter::MarkdownEngines::CommonMark) do |instance| + expect(instance).to receive(:render).and_return('test') end + + filter('test', { markdown_engine: :common_mark }) end + end - describe 'code block' do - context 'using CommonMark' do - before do - stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark) - end - - it 'adds language to lang attribute when specified' do - result = filter("```html\nsome code\n```", no_sourcepos: true) - - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - expect(result).to start_with('<pre lang="html"><code>') - else - expect(result).to start_with('<pre><code lang="html">') - end - end - - it 'does not add language to lang attribute when not specified' do - result = filter("```\nsome code\n```", no_sourcepos: true) - - expect(result).to start_with('<pre><code>') - end - - it 'works with utf8 chars in language' do - result = filter("```日\nsome code\n```", no_sourcepos: true) - - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - expect(result).to start_with('<pre lang="日"><code>') - else - expect(result).to start_with('<pre><code lang="日">') - end - end - - it 'works with additional language parameters' do - result = filter("```ruby:red gem foo\nsome code\n```", no_sourcepos: true) - - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - expect(result).to start_with('<pre lang="ruby:red" data-meta="gem foo"><code>') - else - expect(result).to start_with('<pre><code lang="ruby:red gem foo">') - end - end + describe 'code block' do + context 'using CommonMark' do + before do + stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark) end - end - describe 'source line position' do - context 'using CommonMark' do - before do - stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark) - end + it 'adds language to lang attribute when specified' do + result = filter("```html\nsome code\n```", no_sourcepos: true) - it 'defaults to add data-sourcepos' do - result = filter('test') + expect(result).to start_with('<pre lang="html"><code>') + end - expect(result).to eq '<p data-sourcepos="1:1-1:4">test</p>' - end + it 'does not add language to lang attribute when not specified' do + result = filter("```\nsome code\n```", no_sourcepos: true) - it 'disables data-sourcepos' do - result = filter('test', no_sourcepos: true) + expect(result).to start_with('<pre><code>') + end + + it 'works with utf8 chars in language' do + result = filter("```日\nsome code\n```", no_sourcepos: true) - expect(result).to eq '<p>test</p>' - end + expect(result).to start_with('<pre lang="日"><code>') + end + + it 'works with additional language parameters' do + result = filter("```ruby:red gem foo\nsome code\n```", no_sourcepos: true) + + expect(result).to start_with('<pre lang="ruby:red" data-meta="gem foo"><code>') end end + end - describe 'footnotes in tables' do - it 'processes footnotes in table cells' do - text = <<-MD.strip_heredoc - | Column1 | - | --------- | - | foot [^1] | + describe 'source line position' do + context 'using CommonMark' do + before do + stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark) + end - [^1]: a footnote - MD + it 'defaults to add data-sourcepos' do + result = filter('test') - result = filter(text, no_sourcepos: true) + expect(result).to eq '<p data-sourcepos="1:1-1:4">test</p>' + end - expect(result).to include('<td>foot <sup') + it 'disables data-sourcepos' do + result = filter('test', no_sourcepos: true) - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - expect(result).to include('<section class="footnotes" data-footnotes>') - else - expect(result).to include('<section class="footnotes">') - end + expect(result).to eq '<p>test</p>' end end end - context 'using ruby-based HTML renderer' do - before do - stub_feature_flags(use_cmark_renderer: false) - end + describe 'footnotes in tables' do + it 'processes footnotes in table cells' do + text = <<-MD.strip_heredoc + | Column1 | + | --------- | + | foot [^1] | - it_behaves_like 'renders correct markdown' - end + [^1]: a footnote + MD - context 'using c-based HTML renderer' do - before do - stub_feature_flags(use_cmark_renderer: true) - end + result = filter(text, no_sourcepos: true) - it_behaves_like 'renders correct markdown' + expect(result).to include('<td>foot <sup') + expect(result).to include('<section class="footnotes" data-footnotes>') + end end end diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb index e1e02c09fbe..2d1a01116e0 100644 --- a/spec/lib/banzai/filter/plantuml_filter_spec.rb +++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb @@ -5,67 +5,33 @@ require 'spec_helper' RSpec.describe Banzai::Filter::PlantumlFilter do include FilterSpecHelper - shared_examples_for 'renders correct markdown' do - it 'replaces plantuml pre tag with img tag' do - stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080") + it 'replaces plantuml pre tag with img tag' do + stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080") - input = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>' - else - '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' - end + input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>' + output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>' + doc = filter(input) - output = '<div class="imageblock"><div class="content"><img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq"></div></div>' - doc = filter(input) - - expect(doc.to_s).to eq output - end - - it 'does not replace plantuml pre tag with img tag if disabled' do - stub_application_setting(plantuml_enabled: false) - - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>' - output = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>' - else - input = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' - output = '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' - end - - doc = filter(input) - - expect(doc.to_s).to eq output - end - - it 'does not replace plantuml pre tag with img tag if url is invalid' do - stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid") + expect(doc.to_s).to eq output + end - input = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>' - else - '<pre><code lang="plantuml">Bob -> Sara : Hello</code></pre>' - end + it 'does not replace plantuml pre tag with img tag if disabled' do + stub_application_setting(plantuml_enabled: false) - output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> Error: cannot connect to PlantUML server at "invalid"</pre></div></div>' - doc = filter(input) + input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>' + output = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>' + doc = filter(input) - expect(doc.to_s).to eq output - end + expect(doc.to_s).to eq output end - context 'using ruby-based HTML renderer' do - before do - stub_feature_flags(use_cmark_renderer: false) - end - - it_behaves_like 'renders correct markdown' - end + it 'does not replace plantuml pre tag with img tag if url is invalid' do + stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid") - context 'using c-based HTML renderer' do - before do - stub_feature_flags(use_cmark_renderer: true) - end + input = '<pre lang="plantuml"><code>Bob -> Sara : Hello</code></pre>' + output = '<div class="listingblock"><div class="content"><pre class="plantuml plantuml-error"> Error: cannot connect to PlantUML server at "invalid"</pre></div></div>' + doc = filter(input) - it_behaves_like 'renders correct markdown' + expect(doc.to_s).to eq output end end diff --git a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb index 14c1542b724..b3523a25116 100644 --- a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb @@ -122,6 +122,7 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do expect(link).to have_attribute('data-reference-format') expect(link.attr('data-reference-format')).to eq('+') + expect(link.attr('href')).to eq(issue_url) end it 'includes a data-reference-format attribute for URL references' do @@ -130,6 +131,7 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do expect(link).to have_attribute('data-reference-format') expect(link.attr('data-reference-format')).to eq('+') + expect(link.attr('href')).to eq(issue_url) end it 'supports an :only_path context' do diff --git a/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb index 3c488820853..e5809ac6949 100644 --- a/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/references/merge_request_reference_filter_spec.rb @@ -51,6 +51,7 @@ RSpec.describe Banzai::Filter::References::MergeRequestReferenceFilter do context 'internal reference' do let(:reference) { merge.to_reference } + let(:merge_request_url) { urls.project_merge_request_url(project, merge) } it 'links to a valid reference' do doc = reference_filter("See #{reference}") @@ -115,14 +116,16 @@ RSpec.describe Banzai::Filter::References::MergeRequestReferenceFilter do expect(link).to have_attribute('data-reference-format') expect(link.attr('data-reference-format')).to eq('+') + expect(link.attr('href')).to eq(merge_request_url) end it 'includes a data-reference-format attribute for URL references' do - doc = reference_filter("Merge #{urls.project_merge_request_url(project, merge)}+") + doc = reference_filter("Merge #{merge_request_url}+") link = doc.css('a').first expect(link).to have_attribute('data-reference-format') expect(link.attr('data-reference-format')).to eq('+') + expect(link.attr('href')).to eq(merge_request_url) end it 'supports an :only_path context' do diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb index 24e787bddd5..039ca36af6e 100644 --- a/spec/lib/banzai/filter/sanitization_filter_spec.rb +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -177,53 +177,6 @@ RSpec.describe Banzai::Filter::SanitizationFilter do expect(act.to_html).to eq exp end end - - context 'using ruby-based HTML renderer' do - before do - stub_feature_flags(use_cmark_renderer: false) - end - - it 'allows correct footnote id property on links' do - exp = %q(<a href="#fn1" id="fnref1">foo/bar.md</a>) - act = filter(exp) - - expect(act.to_html).to eq exp - end - - it 'allows correct footnote id property on li element' do - exp = %q(<ol><li id="fn1">footnote</li></ol>) - act = filter(exp) - - expect(act.to_html).to eq exp - end - - it 'removes invalid id for footnote links' do - exp = %q(<a href="#fn1">link</a>) - - %w[fnrefx test xfnref1].each do |id| - act = filter(%(<a href="#fn1" id="#{id}">link</a>)) - - expect(act.to_html).to eq exp - end - end - - it 'removes invalid id for footnote li' do - exp = %q(<ol><li>footnote</li></ol>) - - %w[fnx test xfn1].each do |id| - act = filter(%(<ol><li id="#{id}">footnote</li></ol>)) - - expect(act.to_html).to eq exp - end - end - - it 'allows footnotes numbered higher than 9' do - exp = %q(<a href="#fn15" id="fnref15">link</a><ol><li id="fn15">footnote</li></ol>) - act = filter(exp) - - expect(act.to_html).to eq exp - end - end end end end diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index ef46fd62486..aee4bd93207 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -19,202 +19,150 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do end end - shared_examples_for 'renders correct markdown' do - context "when no language is specified" do - it "highlights as plaintext" do - result = filter('<pre><code>def fun end</code></pre>') + context "when no language is specified" do + it "highlights as plaintext" do + result = filter('<pre><code>def fun end</code></pre>') - expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre><copy-code></copy-code></div>') - end - - include_examples "XSS prevention", "" + expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre><copy-code></copy-code></div>') end - context "when contains mermaid diagrams" do - it "ignores mermaid blocks" do - result = filter('<pre data-mermaid-style="display"><code>mermaid code</code></pre>') + include_examples "XSS prevention", "" + end - expect(result.to_html).to eq('<pre data-mermaid-style="display"><code>mermaid code</code></pre>') - end + context "when contains mermaid diagrams" do + it "ignores mermaid blocks" do + result = filter('<pre data-mermaid-style="display"><code>mermaid code</code></pre>') + + expect(result.to_html).to eq('<pre data-mermaid-style="display"><code>mermaid code</code></pre>') end + end - context "when a valid language is specified" do - it "highlights as that language" do - result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - filter('<pre lang="ruby"><code>def fun end</code></pre>') - else - filter('<pre><code lang="ruby">def fun end</code></pre>') - end + context "when <pre> contains multiple <code> tags" do + it "ignores the block" do + result = filter('<pre><code>one</code> and <code>two</code></pre>') - expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre><copy-code></copy-code></div>') - end + expect(result.to_html).to eq('<pre><code>one</code> and <code>two</code></pre>') + end + end - include_examples "XSS prevention", "ruby" + context "when a valid language is specified" do + it "highlights as that language" do + result = filter('<pre lang="ruby"><code>def fun end</code></pre>') + + expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre><copy-code></copy-code></div>') end - context "when an invalid language is specified" do - it "highlights as plaintext" do - result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - filter('<pre lang="gnuplot"><code>This is a test</code></pre>') - else - filter('<pre><code lang="gnuplot">This is a test</code></pre>') - end + include_examples "XSS prevention", "ruby" + end - expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>') - end + context "when an invalid language is specified" do + it "highlights as plaintext" do + result = filter('<pre lang="gnuplot"><code>This is a test</code></pre>') - include_examples "XSS prevention", "gnuplot" + expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>') end - context "languages that should be passed through" do - let(:delimiter) { described_class::LANG_PARAMS_DELIMITER } - let(:data_attr) { described_class::LANG_PARAMS_ATTR } + include_examples "XSS prevention", "gnuplot" + end - %w(math mermaid plantuml suggestion).each do |lang| - context "when #{lang} is specified" do - it "highlights as plaintext but with the correct language attribute and class" do - result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - filter(%{<pre lang="#{lang}"><code>This is a test</code></pre>}) - else - filter(%{<pre><code lang="#{lang}">This is a test</code></pre>}) - end + context "languages that should be passed through" do + let(:delimiter) { described_class::LANG_PARAMS_DELIMITER } + let(:data_attr) { described_class::LANG_PARAMS_ATTR } - expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>}) - end + %w(math mermaid plantuml suggestion).each do |lang| + context "when #{lang} is specified" do + it "highlights as plaintext but with the correct language attribute and class" do + result = filter(%{<pre lang="#{lang}"><code>This is a test</code></pre>}) - include_examples "XSS prevention", lang + expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>}) end - context "when #{lang} has extra params" do - let(:lang_params) { 'foo-bar-kux' } - - let(:xss_lang) do - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - "#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>" - else - "#{lang}#{described_class::LANG_PARAMS_DELIMITER}<script>alert(1)</script>" - end - end - - it "includes data-lang-params tag with extra information" do - result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - filter(%{<pre lang="#{lang}" data-meta="#{lang_params}"><code>This is a test</code></pre>}) - else - filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}">This is a test</code></pre>}) - end - - expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>}) - end - - include_examples "XSS prevention", lang - - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - include_examples "XSS prevention", - "#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>" - else - include_examples "XSS prevention", - "#{lang}#{described_class::LANG_PARAMS_DELIMITER}<script>alert(1)</script>" - end - - include_examples "XSS prevention", - "#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>" - end + include_examples "XSS prevention", lang end - context 'when multiple param delimiters are used' do - let(:lang) { 'suggestion' } - let(:lang_params) { '-1+10' } + context "when #{lang} has extra params" do + let(:lang_params) { 'foo-bar-kux' } + let(:xss_lang) { "#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>" } - let(:expected_result) do - %{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params} more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>} - end - - context 'when delimiter is space' do - it 'delimits on the first appearance' do - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params} more-things"><code>This is a test</code></pre>}) + it "includes data-lang-params tag with extra information" do + result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params}"><code>This is a test</code></pre>}) - expect(result.to_html.delete("\n")).to eq(expected_result) - else - result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}#{delimiter}more-things">This is a test</code></pre>}) - - expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}#{delimiter}more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>}) - end - end + expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>}) end - context 'when delimiter is colon' do - it 'delimits on the first appearance' do - result = filter(%{<pre lang="#{lang}#{delimiter}#{lang_params} more-things"><code>This is a test</code></pre>}) + include_examples "XSS prevention", lang - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - expect(result.to_html.delete("\n")).to eq(expected_result) - else - expect(result.to_html.delete("\n")).to eq(%{<div class="gl-relative markdown-code-block js-markdown-code"><pre class=\"code highlight js-syntax-highlight language-plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">This is a test</span></code></pre><copy-code></copy-code></div>}) - end - end - end + include_examples "XSS prevention", + "#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>" + + include_examples "XSS prevention", + "#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>" end end - context "when sourcepos metadata is available" do - it "includes it in the highlighted code block" do - result = filter('<pre data-sourcepos="1:1-3:3"><code lang="plaintext">This is a test</code></pre>') + context 'when multiple param delimiters are used' do + let(:lang) { 'suggestion' } + let(:lang_params) { '-1+10' } - expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>') + let(:expected_result) do + %{<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params} more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre><copy-code></copy-code></div>} end - end - context "when Rouge lexing fails" do - before do - allow_next_instance_of(Rouge::Lexers::Ruby) do |instance| - allow(instance).to receive(:stream_tokens).and_raise(StandardError) + context 'when delimiter is space' do + it 'delimits on the first appearance' do + result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params} more-things"><code>This is a test</code></pre>}) + + expect(result.to_html.delete("\n")).to eq(expected_result) end end - it "highlights as plaintext" do - result = if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - filter('<pre lang="ruby"><code>This is a test</code></pre>') - else - filter('<pre><code lang="ruby">This is a test</code></pre>') - end + context 'when delimiter is colon' do + it 'delimits on the first appearance' do + result = filter(%{<pre lang="#{lang}#{delimiter}#{lang_params} more-things"><code>This is a test</code></pre>}) - expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre><copy-code></copy-code></div>') + expect(result.to_html.delete("\n")).to eq(expected_result) + end end - - include_examples "XSS prevention", "ruby" end + end - context "when Rouge lexing fails after a retry" do - before do - allow_next_instance_of(Rouge::Lexers::PlainText) do |instance| - allow(instance).to receive(:stream_tokens).and_raise(StandardError) - end - end + context "when sourcepos metadata is available" do + it "includes it in the highlighted code block" do + result = filter('<pre data-sourcepos="1:1-3:3"><code lang="plaintext">This is a test</code></pre>') - it "does not add highlighting classes" do - result = filter('<pre><code>This is a test</code></pre>') + expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre data-sourcepos="1:1-3:3" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre><copy-code></copy-code></div>') + end + end - expect(result.to_html).to eq('<pre><code>This is a test</code></pre>') + context "when Rouge lexing fails" do + before do + allow_next_instance_of(Rouge::Lexers::Ruby) do |instance| + allow(instance).to receive(:stream_tokens).and_raise(StandardError) end + end + + it "highlights as plaintext" do + result = filter('<pre lang="ruby"><code>This is a test</code></pre>') - include_examples "XSS prevention", "ruby" + expect(result.to_html.delete("\n")).to eq('<div class="gl-relative markdown-code-block js-markdown-code"><pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre><copy-code></copy-code></div>') end + + include_examples "XSS prevention", "ruby" end - context 'using ruby-based HTML renderer' do + context "when Rouge lexing fails after a retry" do before do - stub_feature_flags(use_cmark_renderer: false) + allow_next_instance_of(Rouge::Lexers::PlainText) do |instance| + allow(instance).to receive(:stream_tokens).and_raise(StandardError) + end end - it_behaves_like 'renders correct markdown' - end + it "does not add highlighting classes" do + result = filter('<pre><code>This is a test</code></pre>') - context 'using c-based HTML renderer' do - before do - stub_feature_flags(use_cmark_renderer: true) + expect(result.to_html).to eq('<pre><code>This is a test</code></pre>') end - it_behaves_like 'renders correct markdown' + include_examples "XSS prevention", "ruby" end end diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb index 620b7d97a5b..376edfb99fc 100644 --- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -65,47 +65,6 @@ RSpec.describe Banzai::Pipeline::FullPipeline do expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote.strip end - - context 'using ruby-based HTML renderer' do - let(:html) { described_class.to_html(footnote_markdown, project: project) } - let(:identifier) { html[/.*fnref1-(\d+).*/, 1] } - let(:footnote_markdown) do - <<~EOF - first[^1] and second[^second] and twenty[^twenty] - [^1]: one - [^second]: two - [^twenty]: twenty - EOF - end - - let(:filtered_footnote) do - <<~EOF - <p dir="auto">first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup> and twenty<sup class="footnote-ref"><a href="#fn3-#{identifier}" id="fnref3-#{identifier}">3</a></sup></p> - - <section class="footnotes"><ol> - <li id="fn1-#{identifier}"> - <p>one <a href="#fnref1-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> - </li> - <li id="fn2-#{identifier}"> - <p>two <a href="#fnref2-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> - </li> - <li id="fn3-#{identifier}"> - <p>twenty <a href="#fnref3-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> - </li> - </ol></section> - EOF - end - - before do - stub_feature_flags(use_cmark_renderer: false) - end - - it 'properly adds the necessary ids and classes' do - stub_commonmark_sourcepos_disabled - - expect(html.lines.map(&:strip).join("\n")).to eq filtered_footnote - end - end end describe 'links are detected as malicious' do diff --git a/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb b/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb index c8cd9d4fcac..80392fe264f 100644 --- a/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/plain_markdown_pipeline_spec.rb @@ -5,117 +5,93 @@ require 'spec_helper' RSpec.describe Banzai::Pipeline::PlainMarkdownPipeline do using RSpec::Parameterized::TableSyntax - shared_examples_for 'renders correct markdown' do - describe 'CommonMark tests', :aggregate_failures do - it 'converts all reference punctuation to literals' do - reference_chars = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS - markdown = reference_chars.split('').map {|char| char.prepend("\\") }.join - punctuation = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS.split('') - punctuation = punctuation.delete_if {|char| char == '&' } - punctuation << '&' - - result = described_class.call(markdown, project: project) - output = result[:output].to_html - - punctuation.each { |char| expect(output).to include("<span>#{char}</span>") } - expect(result[:escaped_literals]).to be_truthy - end + describe 'backslash escapes', :aggregate_failures do + let_it_be(:project) { create(:project, :public) } + let_it_be(:issue) { create(:issue, project: project) } - it 'ensure we handle all the GitLab reference characters', :eager_load do - reference_chars = ObjectSpace.each_object(Class).map do |klass| - next unless klass.included_modules.include?(Referable) - next unless klass.respond_to?(:reference_prefix) - next unless klass.reference_prefix.length == 1 + it 'converts all reference punctuation to literals' do + reference_chars = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS + markdown = reference_chars.split('').map {|char| char.prepend("\\") }.join + punctuation = Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS.split('') + punctuation = punctuation.delete_if {|char| char == '&' } + punctuation << '&' - klass.reference_prefix - end.compact + result = described_class.call(markdown, project: project) + output = result[:output].to_html - reference_chars.all? do |char| - Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS.include?(char) - end - end + punctuation.each { |char| expect(output).to include("<span>#{char}</span>") } + expect(result[:escaped_literals]).to be_truthy + end - it 'does not convert non-reference punctuation to spans' do - markdown = %q(\"\'\*\+\,\-\.\/\:\;\<\=\>\?\[\]\_\`\{\|\}) + %q[\(\)\\\\] + it 'ensure we handle all the GitLab reference characters', :eager_load do + reference_chars = ObjectSpace.each_object(Class).map do |klass| + next unless klass.included_modules.include?(Referable) + next unless klass.respond_to?(:reference_prefix) + next unless klass.reference_prefix.length == 1 - result = described_class.call(markdown, project: project) - output = result[:output].to_html + klass.reference_prefix + end.compact - expect(output).not_to include('<span>') - expect(result[:escaped_literals]).to be_falsey + reference_chars.all? do |char| + Banzai::Filter::MarkdownPreEscapeFilter::REFERENCE_CHARACTERS.include?(char) end + end - it 'does not convert other characters to literals' do - markdown = %q(\→\A\a\ \3\φ\«) - expected = '\→\A\a\ \3\φ\«' - - result = correct_html_included(markdown, expected) - expect(result[:escaped_literals]).to be_falsey - end + it 'does not convert non-reference punctuation to spans' do + markdown = %q(\"\'\*\+\,\-\.\/\:\;\<\=\>\?\[\]\_\`\{\|\}) + %q[\(\)\\\\] - describe 'backslash escapes do not work in code blocks, code spans, autolinks, or raw HTML' do - where(:markdown, :expected) do - %q(`` \@\! ``) | %q(<code>\@\!</code>) - %q( \@\!) | %Q(<code>\\@\\!\n</code>) - %Q(~~~\n\\@\\!\n~~~) | %Q(<code>\\@\\!\n</code>) - %q(<http://example.com?find=\@>) | %q(<a href="http://example.com?find=%5C@">http://example.com?find=\@</a>) - %q[<a href="/bar\@)">] | %q[<a href="/bar%5C@)">] - end - - with_them do - it { correct_html_included(markdown, expected) } - end - end + result = described_class.call(markdown, project: project) + output = result[:output].to_html - describe 'work in all other contexts, including URLs and link titles, link references, and info strings in fenced code blocks' do - let(:markdown) { %Q(``` foo\\@bar\nfoo\n```) } - - it 'renders correct html' do - if Feature.enabled?(:use_cmark_renderer, default_enabled: :yaml) - correct_html_included(markdown, %Q(<pre data-sourcepos="1:1-3:3" lang="foo@bar"><code>foo\n</code></pre>)) - else - correct_html_included(markdown, %Q(<code lang="foo@bar">foo\n</code>)) - end - end - - where(:markdown, :expected) do - %q![foo](/bar\@ "\@title")! | %q(<a href="/bar@" title="@title">foo</a>) - %Q![foo]\n\n[foo]: /bar\\@ "\\@title"! | %q(<a href="/bar@" title="@title">foo</a>) - end - - with_them do - it { correct_html_included(markdown, expected) } - end - end + expect(output).not_to include('<span>') + expect(result[:escaped_literals]).to be_falsey end - end - - describe 'backslash escapes' do - let_it_be(:project) { create(:project, :public) } - let_it_be(:issue) { create(:issue, project: project) } - - def correct_html_included(markdown, expected) - result = described_class.call(markdown, {}) - expect(result[:output].to_html).to include(expected) + it 'does not convert other characters to literals' do + markdown = %q(\→\A\a\ \3\φ\«) + expected = '\→\A\a\ \3\φ\«' - result + result = correct_html_included(markdown, expected) + expect(result[:escaped_literals]).to be_falsey end - context 'using ruby-based HTML renderer' do - before do - stub_feature_flags(use_cmark_renderer: false) + describe 'backslash escapes do not work in code blocks, code spans, autolinks, or raw HTML' do + where(:markdown, :expected) do + %q(`` \@\! ``) | %q(<code>\@\!</code>) + %q( \@\!) | %Q(<code>\\@\\!\n</code>) + %Q(~~~\n\\@\\!\n~~~) | %Q(<code>\\@\\!\n</code>) + %q(<http://example.com?find=\@>) | %q(<a href="http://example.com?find=%5C@">http://example.com?find=\@</a>) + %q[<a href="/bar\@)">] | %q[<a href="/bar%5C@)">] end - it_behaves_like 'renders correct markdown' + with_them do + it { correct_html_included(markdown, expected) } + end end - context 'using c-based HTML renderer' do - before do - stub_feature_flags(use_cmark_renderer: true) + describe 'work in all other contexts, including URLs and link titles, link references, and info strings in fenced code blocks' do + let(:markdown) { %Q(``` foo\\@bar\nfoo\n```) } + + it 'renders correct html' do + correct_html_included(markdown, %Q(<pre data-sourcepos="1:1-3:3" lang="foo@bar"><code>foo\n</code></pre>)) + end + + where(:markdown, :expected) do + %q![foo](/bar\@ "\@title")! | %q(<a href="/bar@" title="@title">foo</a>) + %Q![foo]\n\n[foo]: /bar\\@ "\\@title"! | %q(<a href="/bar@" title="@title">foo</a>) end - it_behaves_like 'renders correct markdown' + with_them do + it { correct_html_included(markdown, expected) } + end end end + + def correct_html_included(markdown, expected) + result = described_class.call(markdown, {}) + + expect(result[:output].to_html).to include(expected) + + result + end end diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb index 04c35c8b082..3fbda7f3239 100644 --- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb @@ -23,14 +23,6 @@ RSpec.describe Banzai::ReferenceParser::MergeRequestParser do end it_behaves_like "referenced feature visibility", "merge_requests" - - context 'when optimize_merge_request_parser feature flag is off' do - before do - stub_feature_flags(optimize_merge_request_parser: false) - end - - it_behaves_like "referenced feature visibility", "merge_requests" - end end end |