# 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
mermaid code
')
expect(result.to_html.delete("\n")).to eq('mermaid code
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\nelse\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