# frozen_string_literal: true require 'spec_helper' RSpec.describe Banzai::Filter::SyntaxHighlightFilter, feature_category: :team_planning do include FilterSpecHelper shared_examples "XSS prevention" do |lang| it "escapes HTML tags" do # This is how a script tag inside a code block is presented to this filter # after Markdown rendering. result = filter(%{
<script>alert(1)</script>
}) # `(1)` symbols are wrapped by lexer tags. expect(result.to_html).not_to match(%r{}) # `<>` stands for lexer tags like , 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('
def fun end
') expect(result.to_html.delete("\n")).to eq('
def fun end
') end include_examples "XSS prevention", "" end context "when contains mermaid diagrams" do it "ignores mermaid blocks" do result = filter('
mermaid code
') expect(result.to_html.delete("\n")).to eq('
mermaid code
') end end context "when
 contains multiple  tags" do
    it "ignores the block" do
      result = filter('
one and two
') expect(result.to_html).to eq('
one and two
') end end # This can happen with the following markdown # #
#

  # something
  #
  #     else
  # 
#
# # The blank line causes markdown to process ` else` as a code block. # Which could lead to an orphaned node being replaced and failing context "when
 is a child of 
 which is a child of a div " do
    it "captures all text and doesn't fail trying to replace a node with no parent" do
      text = "
\n
\nsomething\n
else\n
\n
" result = filter(text) expect(result.to_html.delete("\n")).to eq('
somethingelse
') end end context "when a valid language is specified" do it "highlights as that language" do result = filter('
def fun end
') expect(result.to_html.delete("\n")).to eq('
def fun end
') end include_examples "XSS prevention", "ruby" end context "when an invalid language is specified" do it "highlights as plaintext" do result = filter('
This is a test
') expect(result.to_html.delete("\n")).to eq('
This is a test
') 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 } %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(%{
This is a test
}) expect(result.to_html.delete("\n")).to eq(%{
This is a test
}) end include_examples "XSS prevention", lang end 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>" } it "includes data-lang-params tag with extra information" do result = filter(%{
This is a test
}) expect(result.to_html.delete("\n")).to eq(%{
This is a test
}) end include_examples "XSS prevention", lang include_examples "XSS prevention", "#{lang} data-meta=\"foo-bar-kux\"<script>alert(1)</script>" include_examples "XSS prevention", "#{lang} data-meta=\"foo-bar-kux\"" end end context 'when multiple param delimiters are used' do let(:lang) { 'suggestion' } let(:lang_params) { '-1+10' } let(:expected_result) do %{
This is a test
} end context 'when delimiter is space' do it 'delimits on the first appearance' do result = filter(%{
This is a test
}) expect(result.to_html.delete("\n")).to eq(expected_result) end end context 'when delimiter is colon' do it 'delimits on the first appearance' do result = filter(%{
This is a test
}) expect(result.to_html.delete("\n")).to eq(expected_result) end end end end context "when sourcepos metadata is available" do it "includes it in the highlighted code block" do result = filter('
This is a test
') expect(result.to_html.delete("\n")).to eq('
This is a test
') end it "escape sourcepos metadata to prevent XSS" do result = filter('
') expect(result.to_html.delete("\n")).to eq('
') 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) end end it "highlights as plaintext" do result = filter('
This is a test
') expect(result.to_html.delete("\n")).to eq('
This is a test
') end include_examples "XSS prevention", "ruby" 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 it "does not add highlighting classes" do result = filter('
This is a test
') expect(result.to_html).to eq('
This is a test
') end include_examples "XSS prevention", "ruby" end it_behaves_like "filter timeout" do let(:text) { '
def fun end
' } end end