diff options
Diffstat (limited to 'spec/lib/banzai/filter/syntax_highlight_filter_spec.rb')
-rw-r--r-- | spec/lib/banzai/filter/syntax_highlight_filter_spec.rb | 232 |
1 files changed, 156 insertions, 76 deletions
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index 7e45ecdd135..dfe022b51d2 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -11,130 +11,210 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do # after Markdown rendering. result = filter(%{<pre lang="#{lang}"><code><script>alert(1)</script></code></pre>}) - expect(result.to_html).not_to include("<script>alert(1)</script>") - expect(result.to_html).to include("alert(1)") + # `(1)` symbols are wrapped by lexer tags. + expect(result.to_html).not_to match(%r{<script>alert.*<\/script>}) + + # `<>` stands for lexer tags like <span ...>, not <s above. + expect(result.to_html).to match(%r{alert(<.*>)?\((<.*>)?1(<.*>)?\)}) end end - context "when no language is specified" do - it "highlights as plaintext" do - result = filter('<pre><code>def fun end</code></pre>') + 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>') - expect(result.to_html).to eq('<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>') - end + expect(result.to_html).to eq('<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>') + end - include_examples "XSS prevention", "" - end + include_examples "XSS prevention", "" + end - context "when contains mermaid diagrams" do - it "ignores mermaid blocks" do - result = filter('<pre data-mermaid-style="display"><code>mermaid code</code></pre>') + 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>') + expect(result.to_html).to eq('<pre data-mermaid-style="display"><code>mermaid code</code></pre>') + end end - end - context "when a valid language is specified" do - it "highlights as that language" do - result = filter('<pre><code lang="ruby">def fun end</code></pre>') + context "when a valid language is specified" do + it "highlights as that language" do + result = if Feature.enabled?(:use_cmark_renderer) + filter('<pre lang="ruby"><code>def fun end</code></pre>') + else + filter('<pre><code lang="ruby">def fun end</code></pre>') + end + + expect(result.to_html).to eq('<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>') + end - expect(result.to_html).to eq('<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>') + include_examples "XSS prevention", "ruby" end - include_examples "XSS prevention", "ruby" - end + context "when an invalid language is specified" do + it "highlights as plaintext" do + result = if Feature.enabled?(:use_cmark_renderer) + filter('<pre lang="gnuplot"><code>This is a test</code></pre>') + else + filter('<pre><code lang="gnuplot">This is a test</code></pre>') + end - context "when an invalid language is specified" do - it "highlights as plaintext" do - result = filter('<pre><code lang="gnuplot">This is a test</code></pre>') + expect(result.to_html).to eq('<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>') + end - expect(result.to_html).to eq('<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>') + include_examples "XSS prevention", "gnuplot" end - include_examples "XSS prevention", "gnuplot" - end + context "languages that should be passed through" do + let(:delimiter) { described_class::LANG_PARAMS_DELIMITER } + let(:data_attr) { described_class::LANG_PARAMS_ATTR } - context "languages that should be passed through" do - let(:delimiter) { described_class::PARAMS_DELIMITER } - let(:data_attr) { described_class::LANG_PARAMS_ATTR } + %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) + filter(%{<pre lang="#{lang}"><code>This is a test</code></pre>}) + else + filter(%{<pre><code lang="#{lang}">This is a test</code></pre>}) + 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><code lang="#{lang}">This is a test</code></pre>}) + expect(result.to_html).to eq(%{<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>}) + end - expect(result.to_html).to eq(%{<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>}) + include_examples "XSS prevention", lang end - include_examples "XSS prevention", lang + context "when #{lang} has extra params" do + let(:lang_params) { 'foo-bar-kux' } + + let(:xss_lang) do + if Feature.enabled?(:use_cmark_renderer) + "#{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) + 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).to eq(%{<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>}) + end + + include_examples "XSS prevention", lang + + if Feature.enabled?(:use_cmark_renderer) + 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 end - context "when #{lang} has extra params" do - let(:lang_params) { 'foo-bar-kux' } + context 'when multiple param delimiters are used' do + let(:lang) { 'suggestion' } + let(:lang_params) { '-1+10' } - it "includes data-lang-params tag with extra information" do - result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}">This is a test</code></pre>}) + let(:expected_result) do + %{<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>} + end - expect(result.to_html).to eq(%{<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>}) + context 'when delimiter is space' do + it 'delimits on the first appearance' do + if Feature.enabled?(:use_cmark_renderer) + result = filter(%{<pre lang="#{lang}" data-meta="#{lang_params} more-things"><code>This is a test</code></pre>}) + + expect(result.to_html).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).to eq(%{<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>}) + end + end end - include_examples "XSS prevention", lang - include_examples "XSS prevention", - "#{lang}#{described_class::PARAMS_DELIMITER}<script>alert(1)</script>" - include_examples "XSS prevention", - "#{lang}#{described_class::PARAMS_DELIMITER}<script>alert(1)</script>" + 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>}) + + if Feature.enabled?(:use_cmark_renderer) + expect(result.to_html).to eq(expected_result) + else + expect(result.to_html).to eq(%{<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>}) + end + end + end end end - context 'when multiple param delimiters are used' do - let(:lang) { 'suggestion' } - let(:lang_params) { '-1+10' } + 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 "delimits on the first appearance" do - result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}#{delimiter}more-things">This is a test</code></pre>}) - - expect(result.to_html).to eq(%{<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>}) + expect(result.to_html).to eq('<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>') end 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 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 - expect(result.to_html).to eq('<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>') - end - end + it "highlights as plaintext" do + result = if Feature.enabled?(:use_cmark_renderer) + 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 Rouge lexing fails" do - before do - allow_next_instance_of(Rouge::Lexers::Ruby) do |instance| - allow(instance).to receive(:stream_tokens).and_raise(StandardError) + expect(result.to_html).to eq('<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>') end + + include_examples "XSS prevention", "ruby" end - it "highlights as plaintext" do - result = filter('<pre><code lang="ruby">This is a test</code></pre>') + 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 - expect(result.to_html).to eq('<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>') - end + it "does not add highlighting classes" do + result = filter('<pre><code>This is a test</code></pre>') + + expect(result.to_html).to eq('<pre><code>This is a test</code></pre>') + end - include_examples "XSS prevention", "ruby" + include_examples "XSS prevention", "ruby" + end end - context "when Rouge lexing fails after a retry" do + context 'using ruby-based HTML renderer' do before do - allow_next_instance_of(Rouge::Lexers::PlainText) do |instance| - allow(instance).to receive(:stream_tokens).and_raise(StandardError) - end + stub_feature_flags(use_cmark_renderer: false) end - it "does not add highlighting classes" do - result = filter('<pre><code>This is a test</code></pre>') + it_behaves_like 'renders correct markdown' + end - expect(result.to_html).to eq('<pre><code>This is a test</code></pre>') + context 'using c-based HTML renderer' do + before do + stub_feature_flags(use_cmark_renderer: true) end - include_examples "XSS prevention", "ruby" + it_behaves_like 'renders correct markdown' end end |