Project

General

Profile

Defect #37394 » 37394-v2.patch

Go MAEDA, 2025-04-27 10:03

View differences:

Gemfile
36 36
gem "html-pipeline", "~> 2.13.2"
37 37
gem "sanitize", "~> 6.0"
38 38

  
39
gem "commonmarker", '~> 2.3.0'
40
gem 'deckar01-task_list', '2.3.2'
41

  
39 42
# Optional gem for LDAP authentication
40 43
group :ldap do
41 44
  gem 'net-ldap', '~> 0.17.0'
......
46 49
  gem 'mini_magick', '~> 5.2.0'
47 50
end
48 51

  
49
# Optional CommonMark support, not for JRuby
50
group :common_mark do
51
  gem "commonmarker", '~> 2.3.0'
52
  gem 'deckar01-task_list', '2.3.2'
53
end
54

  
55 52
# Include database gems for the adapters found in the database
56 53
# configuration file
57 54
database_file = File.join(File.dirname(__FILE__), "config/database.yml")
lib/redmine.rb
24 24
rescue LoadError
25 25
  # MiniMagick is not available
26 26
end
27
begin
28
  require 'commonmarker' unless Object.const_defined?(:Commonmarker)
29
rescue LoadError
30
  # CommonMarker is not available
31
end
32 27

  
33 28
module Redmine
34 29
end
lib/redmine/preparation.rb
408 408

  
409 409
      WikiFormatting.map do |format|
410 410
        format.register :textile
411
        if Object.const_defined?(:Commonmarker)
412
          format.register :common_mark, label: 'CommonMark Markdown (GitHub Flavored)'
413
        end
411
        format.register :common_mark, label: 'CommonMark Markdown (GitHub Flavored)'
414 412
      end
415 413

  
416 414
      ActionView::Template.register_template_handler :rsb, Views::ApiTemplateHandler
test/helpers/application_helper_test.rb
252 252
  end
253 253

  
254 254
  def test_attached_images_with_markdown_and_non_ascii_filename
255
    skip unless Object.const_defined?(:CommonMarker)
256

  
257 255
    to_test = {
258 256
      'CAFÉ.JPG' => 'CAF%C3%89.JPG',
259 257
      'crème.jpg' => 'cr%C3%A8me.jpg',
......
1658 1656
    end
1659 1657
  end
1660 1658

  
1661
  if Object.const_defined?(:Commonmarker)
1662
    def test_toc_with_markdown_formatting_should_be_parsed
1663
      with_settings :text_formatting => 'common_mark' do
1664
        assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
1665
        assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
1666
        assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
1667
      end
1659
  def test_toc_with_markdown_formatting_should_be_parsed
1660
    with_settings :text_formatting => 'common_mark' do
1661
      assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
1662
      assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
1663
      assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
1668 1664
    end
1669 1665
  end
1670 1666

  
test/unit/lib/redmine/wiki_formatting/common_mark/application_helper_test.rb
20 20
require_relative '../../../../../test_helper'
21 21

  
22 22
class Redmine::WikiFormatting::CommonMark::ApplicationHelperTest < Redmine::HelperTest
23
  if Object.const_defined?(:Commonmarker)
23
  include ERB::Util
24 24

  
25
    include ERB::Util
26

  
27
    def setup
28
      super
29
      set_tmp_attachments_directory
30
    end
25
  def setup
26
    super
27
    set_tmp_attachments_directory
28
  end
31 29

  
32
    def test_attached_images_with_markdown_and_non_ascii_filename
33
      to_test = {
34
        'CAFÉ.JPG' => 'CAF%C3%89.JPG',
35
        'crème.jpg' => 'cr%C3%A8me.jpg',
36
      }
37
      with_settings :text_formatting => 'common_mark' do
38
        to_test.each do |filename, result|
39
          attachment = Attachment.generate!(:filename => filename)
40
          assert_include %(<img src="/attachments/download/#{attachment.id}/#{result}" alt="" loading="lazy">), textilizable("![](#{filename})", :attachments => [attachment])
41
        end
30
  def test_attached_images_with_markdown_and_non_ascii_filename
31
    to_test = {
32
      'CAFÉ.JPG' => 'CAF%C3%89.JPG',
33
      'crème.jpg' => 'cr%C3%A8me.jpg',
34
    }
35
    with_settings :text_formatting => 'common_mark' do
36
      to_test.each do |filename, result|
37
        attachment = Attachment.generate!(:filename => filename)
38
        assert_include %(<img src="/attachments/download/#{attachment.id}/#{result}" alt="" loading="lazy">), textilizable("![](#{filename})", :attachments => [attachment])
42 39
      end
43 40
    end
41
  end
44 42

  
45
    def test_toc_with_markdown_formatting_should_be_parsed
46
      with_settings :text_formatting => 'common_mark' do
47
        assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
48
        assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
49
        assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
50
      end
43
  def test_toc_with_markdown_formatting_should_be_parsed
44
    with_settings :text_formatting => 'common_mark' do
45
      assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
46
      assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
47
      assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
51 48
    end
49
  end
52 50

  
53
    def test_attached_image_alt_attribute_with_madkrown
54
      attachments = Attachment.all
55
      with_settings text_formatting: 'common_mark' do
56
        # When alt text is set
57
        assert_match %r[<img src=".+?" alt="alt text" loading=".+?">],
58
          textilizable('![alt text](logo.gif)', attachments: attachments)
51
  def test_attached_image_alt_attribute_with_madkrown
52
    attachments = Attachment.all
53
    with_settings text_formatting: 'common_mark' do
54
      # When alt text is set
55
      assert_match %r[<img src=".+?" alt="alt text" loading=".+?">],
56
        textilizable('![alt text](logo.gif)', attachments: attachments)
59 57

  
60
        # When alt text is not set
61
        assert_match %r[<img src=".+?" title="This is a logo" alt="This is a logo" loading=".+?">],
62
          textilizable('![](logo.gif)', attachments: attachments)
58
      # When alt text is not set
59
      assert_match %r[<img src=".+?" title="This is a logo" alt="This is a logo" loading=".+?">],
60
        textilizable('![](logo.gif)', attachments: attachments)
63 61

  
64
        # When alt text is not set and the attachment has no description
65
        assert_match %r[<img src=".+?" alt="" loading=".+?">],
66
          textilizable('![](testfile.PNG)', attachments: attachments)
62
      # When alt text is not set and the attachment has no description
63
      assert_match %r[<img src=".+?" alt="" loading=".+?">],
64
        textilizable('![](testfile.PNG)', attachments: attachments)
67 65

  
68
        # When no matching attachments are found
69
        assert_match %r[<img src=".+?" alt="">],
70
          textilizable('![](no-match.jpg)', attachments: attachments)
71
        assert_match %r[<img src=".+?" alt="alt text">],
72
          textilizable('![alt text](no-match.jpg)', attachments: attachments)
66
      # When no matching attachments are found
67
      assert_match %r[<img src=".+?" alt="">],
68
        textilizable('![](no-match.jpg)', attachments: attachments)
69
      assert_match %r[<img src=".+?" alt="alt text">],
70
        textilizable('![alt text](no-match.jpg)', attachments: attachments)
73 71

  
74
        # When no attachment is registered
75
        assert_match %r[<img src=".+?" alt="">],
76
          textilizable('![](logo.gif)', attachments: [])
77
        assert_match %r[<img src=".+?" alt="alt text">],
78
          textilizable('![alt text](logo.gif)', attachments: [])
79
      end
72
      # When no attachment is registered
73
      assert_match %r[<img src=".+?" alt="">],
74
        textilizable('![](logo.gif)', attachments: [])
75
      assert_match %r[<img src=".+?" alt="alt text">],
76
        textilizable('![alt text](logo.gif)', attachments: [])
80 77
    end
81 78
  end
82 79
end
test/unit/lib/redmine/wiki_formatting/common_mark/external_links_filter_test.rb
18 18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 19

  
20 20
require_relative '../../../../../test_helper'
21
require 'redmine/wiki_formatting/common_mark/external_links_filter'
21 22

  
22
if Object.const_defined?(:Commonmarker)
23
  require 'redmine/wiki_formatting/common_mark/external_links_filter'
24

  
25
  class Redmine::WikiFormatting::CommonMark::ExternalLinksFilterTest < ActiveSupport::TestCase
26
    def filter(html)
27
      Redmine::WikiFormatting::CommonMark::ExternalLinksFilter.to_html(html, @options)
28
    end
23
class Redmine::WikiFormatting::CommonMark::ExternalLinksFilterTest < ActiveSupport::TestCase
24
  def filter(html)
25
    Redmine::WikiFormatting::CommonMark::ExternalLinksFilter.to_html(html, @options)
26
  end
29 27

  
30
    def setup
31
      @options = { }
32
    end
28
  def setup
29
    @options = { }
30
  end
33 31

  
34
    def test_external_links_should_have_external_css_class
35
      assert_equal %(<a href="http://example.net/" class="external">link</a>), filter(%(<a href="http://example.net/">link</a>))
36
    end
32
  def test_external_links_should_have_external_css_class
33
    assert_equal %(<a href="http://example.net/" class="external">link</a>), filter(%(<a href="http://example.net/">link</a>))
34
  end
37 35

  
38
    def test_locals_links_should_not_have_external_css_class
39
      assert_equal %(<a href="/">home</a>), filter(%(<a href="/">home</a>))
40
      assert_equal %(<a href="relative">relative</a>), filter(%(<a href="relative">relative</a>))
41
      assert_equal %(<a href="#anchor">anchor</a>), filter(%(<a href="#anchor">anchor</a>))
42
    end
36
  def test_locals_links_should_not_have_external_css_class
37
    assert_equal %(<a href="/">home</a>), filter(%(<a href="/">home</a>))
38
    assert_equal %(<a href="relative">relative</a>), filter(%(<a href="relative">relative</a>))
39
    assert_equal %(<a href="#anchor">anchor</a>), filter(%(<a href="#anchor">anchor</a>))
40
  end
43 41

  
44
    def test_mailto_links_should_have_email_class
45
      assert_equal %(<a href="mailto:user@example.org" class="email">user</a>), filter(%(<a href="mailto:user@example.org">user</a>))
46
    end
42
  def test_mailto_links_should_have_email_class
43
    assert_equal %(<a href="mailto:user@example.org" class="email">user</a>), filter(%(<a href="mailto:user@example.org">user</a>))
44
  end
47 45

  
48
    def test_malformed_uri_should_not_cause_exception
49
      assert_nothing_raised do
50
        filter(%(<a href="http://example.com/foo#bar#">Malformed URI</a>))
51
      end
46
  def test_malformed_uri_should_not_cause_exception
47
    assert_nothing_raised do
48
      filter(%(<a href="http://example.com/foo#bar#">Malformed URI</a>))
52 49
    end
50
  end
53 51

  
54
    def test_external_links_with_target_get_rel_noopener
55
      assert_equal(
56
        %(<a target="_blank" href="http://example.net/" class="external" rel="noopener">link</a>),
57
        filter(%(<a target="_blank" href="http://example.net/">link</a>))
58
      )
59
      assert_equal(
60
        %(<a target="_blank" href="http://example.net/" rel="nofollow noopener" class="external">link</a>),
61
        filter(%(<a target="_blank" href="http://example.net/" rel="nofollow">link</a>))
62
      )
63
    end
52
  def test_external_links_with_target_get_rel_noopener
53
    assert_equal(
54
      %(<a target="_blank" href="http://example.net/" class="external" rel="noopener">link</a>),
55
      filter(%(<a target="_blank" href="http://example.net/">link</a>))
56
    )
57
    assert_equal(
58
      %(<a target="_blank" href="http://example.net/" rel="nofollow noopener" class="external">link</a>),
59
      filter(%(<a target="_blank" href="http://example.net/" rel="nofollow">link</a>))
60
    )
64 61
  end
65 62
end
test/unit/lib/redmine/wiki_formatting/common_mark/fixup_auto_links_filter_test.rb
18 18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 19

  
20 20
require_relative '../../../../../test_helper'
21
require 'redmine/wiki_formatting/common_mark/fixup_auto_links_filter'
21 22

  
22
if Object.const_defined?(:Commonmarker)
23
  require 'redmine/wiki_formatting/common_mark/fixup_auto_links_filter'
24

  
25
  class Redmine::WikiFormatting::CommonMark::FixupAutoLinksFilterTest < ActiveSupport::TestCase
26
    def filter(html)
27
      Redmine::WikiFormatting::CommonMark::FixupAutoLinksFilter.to_html(html, @options)
28
    end
23
class Redmine::WikiFormatting::CommonMark::FixupAutoLinksFilterTest < ActiveSupport::TestCase
24
  def filter(html)
25
    Redmine::WikiFormatting::CommonMark::FixupAutoLinksFilter.to_html(html, @options)
26
  end
29 27

  
30
    def format(markdown)
31
      Redmine::WikiFormatting::CommonMark::MarkdownFilter.to_html(markdown, Redmine::WikiFormatting::CommonMark::PIPELINE_CONFIG)
32
    end
28
  def format(markdown)
29
    Redmine::WikiFormatting::CommonMark::MarkdownFilter.to_html(markdown, Redmine::WikiFormatting::CommonMark::PIPELINE_CONFIG)
30
  end
33 31

  
34
    def setup
35
      @options = { }
36
    end
32
  def setup
33
    @options = { }
34
  end
37 35

  
38
    def test_should_fixup_autolinked_user_references
39
      text = "user:user@example.org"
40
      assert_equal "<p>#{text}</p>", filter(format(text))
41
      text = "@user@example.org"
42
      assert_equal "<p>#{text}</p>", filter(format(text))
43
    end
36
  def test_should_fixup_autolinked_user_references
37
    text = "user:user@example.org"
38
    assert_equal "<p>#{text}</p>", filter(format(text))
39
    text = "@user@example.org"
40
    assert_equal "<p>#{text}</p>", filter(format(text))
41
  end
44 42

  
45
    def test_should_fixup_autolinked_hires_files
46
      text = "printscreen@2x.png"
47
      assert_equal "<p>#{text}</p>", filter(format(text))
48
    end
43
  def test_should_fixup_autolinked_hires_files
44
    text = "printscreen@2x.png"
45
    assert_equal "<p>#{text}</p>", filter(format(text))
49 46
  end
50 47
end
test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb
20 20
require_relative '../../../../../test_helper'
21 21

  
22 22
class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase
23
  if Object.const_defined?(:Commonmarker)
24

  
25
    def setup
26
      @formatter = Redmine::WikiFormatting::CommonMark::Formatter
27
    end
23
  def setup
24
    @formatter = Redmine::WikiFormatting::CommonMark::Formatter
25
  end
28 26

  
29
    def to_html(text)
30
      @formatter.new(text).to_html
31
    end
27
  def to_html(text)
28
    @formatter.new(text).to_html
29
  end
32 30

  
33
    def test_should_render_hard_breaks
34
      html ="<p>foo<br>\nbar</p>"
35
      assert_equal html, to_html("foo\\\nbar")
36
      assert_equal html, to_html("foo  \nbar")
37
    end
31
  def test_should_render_hard_breaks
32
    html ="<p>foo<br>\nbar</p>"
33
    assert_equal html, to_html("foo\\\nbar")
34
    assert_equal html, to_html("foo  \nbar")
35
  end
38 36

  
39
    def test_should_render_soft_breaks
40
      assert_equal "<p>foo<br>\nbar</p>", to_html("foo\nbar")
41
    end
37
  def test_should_render_soft_breaks
38
    assert_equal "<p>foo<br>\nbar</p>", to_html("foo\nbar")
39
  end
42 40

  
43
    def test_syntax_error_in_image_reference_should_not_raise_exception
44
      assert to_html("!>[](foo.png)")
45
    end
41
  def test_syntax_error_in_image_reference_should_not_raise_exception
42
    assert to_html("!>[](foo.png)")
43
  end
46 44

  
47
    def test_empty_image_should_not_raise_exception
48
      assert to_html("![]()")
49
    end
45
  def test_empty_image_should_not_raise_exception
46
    assert to_html("![]()")
47
  end
50 48

  
51
    def test_inline_style
52
      assert_equal "<p><strong>foo</strong></p>", to_html("**foo**")
53
    end
49
  def test_inline_style
50
    assert_equal "<p><strong>foo</strong></p>", to_html("**foo**")
51
  end
54 52

  
55
    def test_not_set_intra_emphasis
56
      assert_equal "<p>foo_bar_baz</p>", to_html("foo_bar_baz")
57
    end
53
  def test_not_set_intra_emphasis
54
    assert_equal "<p>foo_bar_baz</p>", to_html("foo_bar_baz")
55
  end
58 56

  
59
    def test_wiki_links_should_be_preserved
60
      text = 'This is a wiki link: [[Foo]]'
61
      assert_include '[[Foo]]', to_html(text)
62
    end
57
  def test_wiki_links_should_be_preserved
58
    text = 'This is a wiki link: [[Foo]]'
59
    assert_include '[[Foo]]', to_html(text)
60
  end
63 61

  
64
    def test_redmine_links_with_double_quotes_should_be_preserved
65
      text = 'This is a redmine link: version:"1.0"'
66
      assert_include 'version:"1.0"', to_html(text)
67
    end
62
  def test_redmine_links_with_double_quotes_should_be_preserved
63
    text = 'This is a redmine link: version:"1.0"'
64
    assert_include 'version:"1.0"', to_html(text)
65
  end
68 66

  
69
    def test_links_by_id_should_be_preserved
70
      text = "[project#3]"
71
      assert_equal "<p>#{text}</p>", to_html(text)
72
    end
67
  def test_links_by_id_should_be_preserved
68
    text = "[project#3]"
69
    assert_equal "<p>#{text}</p>", to_html(text)
70
  end
73 71

  
74
    def test_links_to_users_should_be_preserved
75
      text = "[@login]"
76
      assert_equal "<p>#{text}</p>", to_html(text)
77
      text = "[user:login]"
78
      assert_equal "<p>#{text}</p>", to_html(text)
79
      text = "user:user@example.org"
80
      assert_equal "<p>#{text}</p>", to_html(text)
81
      text = "[user:user@example.org]"
82
      assert_equal "<p>#{text}</p>", to_html(text)
83
      text = "@user@example.org"
84
      assert_equal "<p>#{text}</p>", to_html(text)
85
      text = "[@user@example.org]"
86
      assert_equal "<p>#{text}</p>", to_html(text)
87
    end
72
  def test_links_to_users_should_be_preserved
73
    text = "[@login]"
74
    assert_equal "<p>#{text}</p>", to_html(text)
75
    text = "[user:login]"
76
    assert_equal "<p>#{text}</p>", to_html(text)
77
    text = "user:user@example.org"
78
    assert_equal "<p>#{text}</p>", to_html(text)
79
    text = "[user:user@example.org]"
80
    assert_equal "<p>#{text}</p>", to_html(text)
81
    text = "@user@example.org"
82
    assert_equal "<p>#{text}</p>", to_html(text)
83
    text = "[@user@example.org]"
84
    assert_equal "<p>#{text}</p>", to_html(text)
85
  end
88 86

  
89
    def test_files_with_at_should_not_end_up_as_mailto_links
90
      text = "printscreen@2x.png"
91
      assert_equal "<p>#{text}</p>", to_html(text)
92
      text = "[printscreen@2x.png]"
93
      assert_equal "<p>#{text}</p>", to_html(text)
94
    end
87
  def test_files_with_at_should_not_end_up_as_mailto_links
88
    text = "printscreen@2x.png"
89
    assert_equal "<p>#{text}</p>", to_html(text)
90
    text = "[printscreen@2x.png]"
91
    assert_equal "<p>#{text}</p>", to_html(text)
92
  end
95 93

  
96
    def test_should_support_syntax_highlight
97
      text = <<~STR
98
        ~~~ruby
99
        def foo
100
        end
101
        ~~~
102
      STR
103
      assert_select_in to_html(text), 'pre code.ruby.syntaxhl' do
104
        assert_select 'span.k', :text => 'def'
105
        assert_select "[data-language='ruby']"
94
  def test_should_support_syntax_highlight
95
    text = <<~STR
96
      ~~~ruby
97
      def foo
106 98
      end
99
      ~~~
100
    STR
101
    assert_select_in to_html(text), 'pre code.ruby.syntaxhl' do
102
      assert_select 'span.k', :text => 'def'
103
      assert_select "[data-language='ruby']"
107 104
    end
105
  end
108 106

  
109
    def test_should_support_syntax_highlight_for_language_with_special_chars
110
      text = <<~STR
111
        ~~~c++
112
        int main() {
113
        }
114
        ~~~
115
      STR
116

  
117
      assert_select_in to_html(text), 'pre' do
118
        assert_select 'code[class=?]', "c++ syntaxhl"
119
        assert_select 'span.kt', :text => 'int'
120
        assert_select "[data-language=?]", "c++"
121
      end
107
  def test_should_support_syntax_highlight_for_language_with_special_chars
108
    text = <<~STR
109
      ~~~c++
110
      int main() {
111
      }
112
      ~~~
113
    STR
114

  
115
    assert_select_in to_html(text), 'pre' do
116
      assert_select 'code[class=?]', "c++ syntaxhl"
117
      assert_select 'span.kt', :text => 'int'
118
      assert_select "[data-language=?]", "c++"
122 119
    end
120
  end
123 121

  
124
    def test_external_links_should_have_external_css_class
125
      text = 'This is a [link](http://example.net/)'
126
      assert_equal '<p>This is a <a href="http://example.net/" class="external">link</a></p>', to_html(text)
127
    end
122
  def test_external_links_should_have_external_css_class
123
    text = 'This is a [link](http://example.net/)'
124
    assert_equal '<p>This is a <a href="http://example.net/" class="external">link</a></p>', to_html(text)
125
  end
128 126

  
129
    def test_locals_links_should_not_have_external_css_class
130
      text = 'This is a [link](/issues)'
131
      assert_equal '<p>This is a <a href="/issues">link</a></p>', to_html(text)
132
    end
127
  def test_locals_links_should_not_have_external_css_class
128
    text = 'This is a [link](/issues)'
129
    assert_equal '<p>This is a <a href="/issues">link</a></p>', to_html(text)
130
  end
133 131

  
134
    def test_markdown_should_not_require_surrounded_empty_line
135
      text = <<-STR
136
  This is a list:
137
  * One
138
  * Two
139
      STR
140
      assert_equal "<p>This is a list:</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n</ul>", to_html(text)
141
    end
132
  def test_markdown_should_not_require_surrounded_empty_line
133
    text = <<~STR
134
      This is a list:
135
      * One
136
      * Two
137
    STR
138
    assert_equal "<p>This is a list:</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n</ul>", to_html(text)
139
  end
142 140

  
143
    def test_footnotes
144
      text = <<~STR
145
        This is some text[^1].
141
  def test_footnotes
142
    text = <<~STR
143
      This is some text[^1].
146 144

  
147
        [^1]: This is the foot note
148
      STR
145
      [^1]: This is the foot note
146
    STR
149 147

  
150
      expected = <<~EXPECTED
151
        <p>This is some text<sup><a href="#fn-1" id="fnref-1">1</a></sup>.</p>
152
         <ol>
153
        <li id="fn-1">
154
        <p>This is the foot note <a href="#fnref-1" aria-label="Back to reference 1">↩</a></p>
155
        </li>
156
        </ol>
157
      EXPECTED
148
    expected = <<~EXPECTED
149
      <p>This is some text<sup><a href="#fn-1" id="fnref-1">1</a></sup>.</p>
150
       <ol>
151
      <li id="fn-1">
152
      <p>This is the foot note <a href="#fnref-1" aria-label="Back to reference 1">↩</a></p>
153
      </li>
154
      </ol>
155
    EXPECTED
158 156

  
159
      assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '').rstrip
160
    end
157
    assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '').rstrip
158
  end
159

  
160
  STR_WITH_PRE = [
161
    # 0
162
    <<~STR.chomp,
163
      # Title
161 164

  
162
    STR_WITH_PRE = [
163
      # 0
164
      <<~STR.chomp,
165
        # Title
166
  
167
        Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
168
      STR
169
      # 1
170
      <<~STR.chomp,
171
        ## Heading 2
172
  
173
        ~~~ruby
174
          def foo
175
          end
176
        ~~~
177
  
178
        Morbi facilisis accumsan orci non pharetra.
179
  
180
        ~~~ ruby
165
      Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
166
    STR
167
    # 1
168
    <<~STR.chomp,
169
      ## Heading 2
170

  
171
      ~~~ruby
181 172
        def foo
182 173
        end
183
        ~~~
184
  
185
        ```
186
        Pre Content:
187
  
188
        ## Inside pre
189
  
190
        <tag> inside pre block
191
  
192
        Morbi facilisis accumsan orci non pharetra.
193
        ```
194
      STR
195
      # 2
196
      <<~STR.chomp,
197
        ### Heading 3
198
  
199
        Nulla nunc nisi, egestas in ornare vel, posuere ac libero.
200
      STR
201
    ]
202

  
203
    def test_get_section_should_ignore_pre_content
204
      text = STR_WITH_PRE.join("\n\n")
205

  
206
      assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
207
      assert_section_with_hash STR_WITH_PRE[2], text, 3
208
    end
174
      ~~~
209 175

  
210
    def test_get_section_should_not_recognize_double_hash_issue_reference_as_heading
211
      text = <<~STR
212
        ## Section A
176
      Morbi facilisis accumsan orci non pharetra.
213 177

  
214
        This text is a part of Section A.
178
      ~~~ ruby
179
      def foo
180
      end
181
      ~~~
215 182

  
216
        ##1 : This is an issue reference, not an ATX heading.
183
      ```
184
      Pre Content:
217 185

  
218
        This text is also a part of Section A.
219
        <!-- Section A ends here -->
220
      STR
186
      ## Inside pre
221 187

  
222
      assert_section_with_hash text.chomp, text, 1
223
    end
188
      <tag> inside pre block
224 189

  
225
    def test_update_section_should_not_escape_pre_content_outside_section
226
      text = STR_WITH_PRE.join("\n\n")
227
      replacement = "New text"
190
      Morbi facilisis accumsan orci non pharetra.
191
      ```
192
    STR
193
    # 2
194
    <<~STR.chomp,
195
      ### Heading 3
228 196

  
229
      assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
230
        @formatter.new(text).update_section(3, replacement)
231
    end
197
      Nulla nunc nisi, egestas in ornare vel, posuere ac libero.
198
    STR
199
  ]
232 200

  
233
    def test_should_emphasize_text
234
      text = 'This _text_ should be emphasized'
235
      assert_equal '<p>This <em>text</em> should be emphasized</p>', to_html(text)
236
    end
201
  def test_get_section_should_ignore_pre_content
202
    text = STR_WITH_PRE.join("\n\n")
237 203

  
238
    def test_should_strike_through_text
239
      text = 'This ~~text~~ should be striked through'
240
      assert_equal '<p>This <del>text</del> should be striked through</p>', to_html(text)
241
    end
204
    assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
205
    assert_section_with_hash STR_WITH_PRE[2], text, 3
206
  end
242 207

  
243
    def test_should_autolink_urls_and_emails
244
      [
245
        ["http://example.org", '<p><a href="http://example.org" class="external">http://example.org</a></p>'],
246
        ["http://www.redmine.org/projects/redmine/issues?utf8=✓",
247
         '<p><a href="http://www.redmine.org/projects/redmine/issues?utf8=%E2%9C%93" class="external">http://www.redmine.org/projects/redmine/issues?utf8=✓</a></p>'],
248
        ['[Letters](https://yandex.ru/search/?text=кол-во)', '<p><a href="https://yandex.ru/search/?text=%D0%BA%D0%BE%D0%BB-%D0%B2%D0%BE" class="external">Letters</a></p>'],
249
        ["www.example.org", '<p><a href="http://www.example.org" class="external">www.example.org</a></p>'],
250
        ["user@example.org", '<p><a href="mailto:user@example.org" class="email">user@example.org</a></p>']
251
      ].each do |text, html|
252
        assert_equal html, to_html(text)
253
      end
254
    end
208
  def test_get_section_should_not_recognize_double_hash_issue_reference_as_heading
209
    text = <<~STR
210
      ## Section A
211

  
212
      This text is a part of Section A.
213

  
214
      ##1 : This is an issue reference, not an ATX heading.
215

  
216
      This text is also a part of Section A.
217
      <!-- Section A ends here -->
218
    STR
219

  
220
    assert_section_with_hash text.chomp, text, 1
221
  end
255 222

  
256
    def test_should_support_html_tables
257
      text = '<table style="background: red"><tr><td>Cell</td></tr></table>'
258
      assert_equal '<table><tr><td>Cell</td></tr></table>', to_html(text)
223
  def test_update_section_should_not_escape_pre_content_outside_section
224
    text = STR_WITH_PRE.join("\n\n")
225
    replacement = "New text"
226

  
227
    assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
228
      @formatter.new(text).update_section(3, replacement)
229
  end
230

  
231
  def test_should_emphasize_text
232
    text = 'This _text_ should be emphasized'
233
    assert_equal '<p>This <em>text</em> should be emphasized</p>', to_html(text)
234
  end
235

  
236
  def test_should_strike_through_text
237
    text = 'This ~~text~~ should be striked through'
238
    assert_equal '<p>This <del>text</del> should be striked through</p>', to_html(text)
239
  end
240

  
241
  def test_should_autolink_urls_and_emails
242
    [
243
      ["http://example.org", '<p><a href="http://example.org" class="external">http://example.org</a></p>'],
244
      ["http://www.redmine.org/projects/redmine/issues?utf8=✓",
245
       '<p><a href="http://www.redmine.org/projects/redmine/issues?utf8=%E2%9C%93" class="external">http://www.redmine.org/projects/redmine/issues?utf8=✓</a></p>'],
246
      ['[Letters](https://yandex.ru/search/?text=кол-во)', '<p><a href="https://yandex.ru/search/?text=%D0%BA%D0%BE%D0%BB-%D0%B2%D0%BE" class="external">Letters</a></p>'],
247
      ["www.example.org", '<p><a href="http://www.example.org" class="external">www.example.org</a></p>'],
248
      ["user@example.org", '<p><a href="mailto:user@example.org" class="email">user@example.org</a></p>']
249
    ].each do |text, html|
250
      assert_equal html, to_html(text)
259 251
    end
252
  end
260 253

  
261
    def test_should_remove_unsafe_uris
262
      [
263
        ['<img src="data:foobar">', '<img>'],
264
        ['<a href="javascript:bla">click me</a>', '<p><a>click me</a></p>'],
265
      ].each do |text, html|
266
        assert_equal html, to_html(text)
267
      end
254
  def test_should_support_html_tables
255
    text = '<table style="background: red"><tr><td>Cell</td></tr></table>'
256
    assert_equal '<table><tr><td>Cell</td></tr></table>', to_html(text)
257
  end
258

  
259
  def test_should_remove_unsafe_uris
260
    [
261
      ['<img src="data:foobar">', '<img>'],
262
      ['<a href="javascript:bla">click me</a>', '<p><a>click me</a></p>'],
263
    ].each do |text, html|
264
      assert_equal html, to_html(text)
268 265
    end
266
  end
269 267

  
270
    def test_should_escape_unwanted_tags
268
  def test_should_escape_unwanted_tags
269
    [
271 270
      [
272
        [
273
          %[<p>sit<br>amet &lt;style&gt;.foo { color: #fff; }&lt;/style&gt; &lt;script&gt;alert("hello world");&lt;/script&gt;</p>],
274
          %[sit<br/>amet <style>.foo { color: #fff; }</style> <script>alert("hello world");</script>]
275
        ]
276
      ].each do |expected, input|
277
        assert_equal expected, to_html(input)
278
      end
271
        %[<p>sit<br>amet &lt;style&gt;.foo { color: #fff; }&lt;/style&gt; &lt;script&gt;alert("hello world");&lt;/script&gt;</p>],
272
        %[sit<br/>amet <style>.foo { color: #fff; }</style> <script>alert("hello world");</script>]
273
      ]
274
    ].each do |expected, input|
275
      assert_equal expected, to_html(input)
279 276
    end
277
  end
280 278

  
281
    def test_should_support_task_list
282
      text = <<~STR
283
        Task list:
284
        * [ ] Task 1
285
        * [x] Task 2
286
      STR
287

  
288
      expected = <<~EXPECTED
289
        <p>Task list:</p>
290
        <ul class="task-list">
291
        <li class="task-list-item">
292
        <input type="checkbox" class="task-list-item-checkbox" disabled> Task 1
293
        </li>
294
        <li class="task-list-item">
295
        <input type="checkbox" class="task-list-item-checkbox" checked disabled> Task 2</li>
296
        </ul>
297
      EXPECTED
298

  
299
      assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '').rstrip
300
    end
279
  def test_should_support_task_list
280
    text = <<~STR
281
      Task list:
282
      * [ ] Task 1
283
      * [x] Task 2
284
    STR
285

  
286
    expected = <<~EXPECTED
287
      <p>Task list:</p>
288
      <ul class="task-list">
289
      <li class="task-list-item">
290
      <input type="checkbox" class="task-list-item-checkbox" disabled> Task 1
291
      </li>
292
      <li class="task-list-item">
293
      <input type="checkbox" class="task-list-item-checkbox" checked disabled> Task 2</li>
294
      </ul>
295
    EXPECTED
296

  
297
    assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '').rstrip
298
  end
301 299

  
302
    private
300
  private
303 301

  
304
    def assert_section_with_hash(expected, text, index)
305
      result = @formatter.new(text).get_section(index)
302
  def assert_section_with_hash(expected, text, index)
303
    result = @formatter.new(text).get_section(index)
306 304

  
307
      assert_kind_of Array, result
308
      assert_equal 2, result.size
309
      assert_equal expected, result.first, "section content did not match"
310
      assert_equal ActiveSupport::Digest.hexdigest(expected), result.last, "section hash did not match"
311
    end
305
    assert_kind_of Array, result
306
    assert_equal 2, result.size
307
    assert_equal expected, result.first, "section content did not match"
308
    assert_equal ActiveSupport::Digest.hexdigest(expected), result.last, "section hash did not match"
312 309
  end
313 310
end
test/unit/lib/redmine/wiki_formatting/common_mark/markdown_filter_test.rb
18 18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 19

  
20 20
require_relative '../../../../../test_helper'
21
require 'redmine/wiki_formatting/common_mark/markdown_filter'
21 22

  
22
if Object.const_defined?(:Commonmarker)
23
  require 'redmine/wiki_formatting/common_mark/markdown_filter'
24

  
25
  class Redmine::WikiFormatting::CommonMark::MarkdownFilterTest < ActiveSupport::TestCase
26
    def filter(markdown)
27
      Redmine::WikiFormatting::CommonMark::MarkdownFilter.to_html(markdown)
28
    end
23
class Redmine::WikiFormatting::CommonMark::MarkdownFilterTest < ActiveSupport::TestCase
24
  def filter(markdown)
25
    Redmine::WikiFormatting::CommonMark::MarkdownFilter.to_html(markdown)
26
  end
29 27

  
30
    # just a basic sanity test. more formatting tests in the formatter_test
31
    def test_should_render_markdown
32
      assert_equal "<p><strong>bold</strong></p>", filter("**bold**")
33
    end
28
  # just a basic sanity test. more formatting tests in the formatter_test
29
  def test_should_render_markdown
30
    assert_equal "<p><strong>bold</strong></p>", filter("**bold**")
34 31
  end
35 32
end
test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb
18 18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 19

  
20 20
require_relative '../../../../../test_helper'
21
require 'redmine/wiki_formatting/common_mark/sanitization_filter'
21 22

  
22
if Object.const_defined?(:Commonmarker)
23
  require 'redmine/wiki_formatting/common_mark/sanitization_filter'
23
class Redmine::WikiFormatting::CommonMark::SanitizationFilterTest < ActiveSupport::TestCase
24
  def filter(html)
25
    Redmine::WikiFormatting::CommonMark::SanitizationFilter.to_html(html, @options)
26
  end
24 27

  
25
  class Redmine::WikiFormatting::CommonMark::SanitizationFilterTest < ActiveSupport::TestCase
26
    def filter(html)
27
      Redmine::WikiFormatting::CommonMark::SanitizationFilter.to_html(html, @options)
28
    end
28
  def setup
29
    @options = { }
30
  end
29 31

  
30
    def setup
31
      @options = { }
32
    end
32
  def test_should_filter_tags
33
    input = %(<textarea>foo</textarea> <blink>dont blink</blink>)
34
    assert_equal %(foo dont blink), filter(input)
35
  end
33 36

  
34
    def test_should_filter_tags
35
      input = %(<textarea>foo</textarea> <blink>dont blink</blink>)
36
      assert_equal %(foo dont blink), filter(input)
37
    end
37
  def test_should_sanitize_attributes
38
    input = %(<a href="foo" onclick="bar" baz="foo">link</a>)
39
    assert_equal %(<a href="foo">link</a>), filter(input)
40
  end
38 41

  
39
    def test_should_sanitize_attributes
40
      input = %(<a href="foo" onclick="bar" baz="foo">link</a>)
41
      assert_equal %(<a href="foo">link</a>), filter(input)
42
    end
42
  def test_should_allow_relative_links
43
    input = %(<a href="foo/bar">foo/bar</a>)
44
    assert_equal input, filter(input)
45
  end
43 46

  
44
    def test_should_allow_relative_links
45
      input = %(<a href="foo/bar">foo/bar</a>)
46
      assert_equal input, filter(input)
47
    end
47
  def test_should_support_footnotes
48
    input = %(<a href="#fn-1" id="fnref-1">foo</a>)
49
    assert_equal input, filter(input)
50
    input = %(<ol><li id="fn-1">footnote</li></ol>)
51
    assert_equal input, filter(input)
52
  end
48 53

  
49
    def test_should_support_footnotes
50
      input = %(<a href="#fn-1" id="fnref-1">foo</a>)
51
      assert_equal input, filter(input)
52
      input = %(<ol><li id="fn-1">footnote</li></ol>)
53
      assert_equal input, filter(input)
54
    end
54
  def test_should_remove_invalid_ids
55
    input = %(<a href="#fn1" id="foo">foo</a>)
56
    assert_equal %(<a href="#fn1">foo</a>), filter(input)
57
    input = %(<ol><li id="foo">footnote</li></ol>)
58
    assert_equal %(<ol><li>footnote</li></ol>), filter(input)
59
  end
55 60

  
56
    def test_should_remove_invalid_ids
57
      input = %(<a href="#fn1" id="foo">foo</a>)
58
      assert_equal %(<a href="#fn1">foo</a>), filter(input)
59
      input = %(<ol><li id="foo">footnote</li></ol>)
60
      assert_equal %(<ol><li>footnote</li></ol>), filter(input)
61
    end
61
  def test_should_allow_class_on_code_only
62
    input = %(<p class="foo">bar</p>)
63
    assert_equal %(<p>bar</p>), filter(input)
62 64

  
63
    def test_should_allow_class_on_code_only
64
      input = %(<p class="foo">bar</p>)
65
      assert_equal %(<p>bar</p>), filter(input)
65
    input = %(<code class="language-ruby">foo</code>)
66
    assert_equal input, filter(input)
66 67

  
67
      input = %(<code class="language-ruby">foo</code>)
68
      assert_equal input, filter(input)
68
    input = %(<code class="foo">foo</code>)
69
    assert_equal %(<code>foo</code>), filter(input)
70
  end
69 71

  
70
      input = %(<code class="foo">foo</code>)
71
      assert_equal %(<code>foo</code>), filter(input)
72
  def test_should_allow_links_with_safe_url_schemes
73
    %w(http https ftp ssh foo).each do |scheme|
74
      input = %(<a href="#{scheme}://example.org/">foo</a>)
75
      assert_equal input, filter(input)
72 76
    end
77
  end
73 78

  
74
    def test_should_allow_links_with_safe_url_schemes
75
      %w(http https ftp ssh foo).each do |scheme|
76
        input = %(<a href="#{scheme}://example.org/">foo</a>)
77
        assert_equal input, filter(input)
78
      end
79
    end
79
  def test_should_allow_mailto_links
80
    input = %(<a href="mailto:foo@example.org">bar</a>)
81
    assert_equal input, filter(input)
82
  end
80 83

  
81
    def test_should_allow_mailto_links
82
      input = %(<a href="mailto:foo@example.org">bar</a>)
83
      assert_equal input, filter(input)
84
    end
84
  def test_should_remove_empty_link
85
    input = %(<a href="">bar</a>)
86
    assert_equal %(<a>bar</a>), filter(input)
87
    input = %(<a href=" ">bar</a>)
88
    assert_equal %(<a>bar</a>), filter(input)
89
  end
90

  
91
  # samples taken from the Sanitize test suite
92
  # rubocop:disable Layout/LineLength
93
  STRINGS = [
94
    [
95
      '<span style="color: #333; background: url(\'https://example.com/evil.svg\')">hello</span>"',
96
      '<span style="color: #333; ">hello</span>"'
97
    ],
98
    [
99
      '<img src="photo.jpg" style="min-width: 100px; max-width: 200px; min-height: 100px; max-height: 200px;">',
100
      '<img src="photo.jpg" style="min-width: 100px; max-width: 200px; min-height: 100px; max-height: 200px;">'
101
    ],
102
    [
103
      '<b>Lo<!-- comment -->rem</b> <a href="pants" title="foo" style="text-decoration: underline;">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br/>amet <style>.foo { color: #fff; }</style> <script>alert("hello world");</script>',
104
      '<b>Lorem</b> <a href="pants" title="foo">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br>amet .foo { color: #fff; } '
105
    ],
106
    [
107
      'Lo<!-- comment -->rem</b> <a href=pants title="foo>ipsum <a href="http://foo.com/"><strong>dolor</a></strong> sit<br/>amet <script>alert("hello world");',
108
      'Lorem <a href="pants" title="foo&gt;ipsum &lt;a href="><strong>dolor</strong></a> sit<br>amet '
109
    ],
110
    [
111
      '<p>a</p><blockquote>b',
112
      '<p>a</p><blockquote>b</blockquote>'
113
    ],
114
    [
115
      '<b>Lo<!-- comment -->rem</b> <a href="javascript:pants" title="foo">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br/>amet <<foo>script>alert("hello world");</script>',
116
      '<b>Lorem</b> <a title="foo">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br>amet &lt;script&gt;alert("hello world");'
117
    ]
118
  ]
119
  # rubocop:enable Layout/LineLength
85 120

  
86
    def test_should_remove_empty_link
87
      input = %(<a href="">bar</a>)
88
      assert_equal %(<a>bar</a>), filter(input)
89
      input = %(<a href=" ">bar</a>)
90
      assert_equal %(<a>bar</a>), filter(input)
121
  def test_should_sanitize_html_strings
122
    STRINGS.each do |input, expected|
123
      assert_equal expected, filter(input)
91 124
    end
125
  end
126

  
127
  # samples taken from the Sanitize test suite
128
  PROTOCOLS = {
129
    'protocol-based JS injection: simple, no spaces' => [
130
      '<a href="javascript:alert(\'XSS\');">foo</a>',
131
      '<a>foo</a>'
132
    ],
133

  
134
    'protocol-based JS injection: simple, spaces before' => [
135
      '<a href="javascript    :alert(\'XSS\');">foo</a>',
136
      '<a>foo</a>'
137
    ],
138

  
139
    'protocol-based JS injection: simple, spaces after' => [
140
      '<a href="javascript:    alert(\'XSS\');">foo</a>',
141
      '<a>foo</a>'
142
    ],
143

  
144
    'protocol-based JS injection: simple, spaces before and after' => [
145
      '<a href="javascript    :   alert(\'XSS\');">foo</a>',
146
      '<a>foo</a>'
147
    ],
148

  
149
    'protocol-based JS injection: preceding colon' => [
150
      '<a href=":javascript:alert(\'XSS\');">foo</a>',
151
      '<a>foo</a>'
152
    ],
153

  
154
    'protocol-based JS injection: UTF-8 encoding' => [
155
      '<a href="javascript&#58;">foo</a>',
156
      '<a>foo</a>'
157
    ],
158

  
159
    'protocol-based JS injection: long UTF-8 encoding' => [
160
      '<a href="javascript&#0058;">foo</a>',
161
      '<a>foo</a>'
162
    ],
92 163

  
93
    # samples taken from the Sanitize test suite
94 164
    # rubocop:disable Layout/LineLength
95
    STRINGS = [
96
      [
97
        '<span style="color: #333; background: url(\'https://example.com/evil.svg\')">hello</span>"',
98
        '<span style="color: #333; ">hello</span>"'
99
      ],
100
      [
101
        '<img src="photo.jpg" style="min-width: 100px; max-width: 200px; min-height: 100px; max-height: 200px;">',
102
        '<img src="photo.jpg" style="min-width: 100px; max-width: 200px; min-height: 100px; max-height: 200px;">'
103
      ],
104
      [
105
        '<b>Lo<!-- comment -->rem</b> <a href="pants" title="foo" style="text-decoration: underline;">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br/>amet <style>.foo { color: #fff; }</style> <script>alert("hello world");</script>',
106
        '<b>Lorem</b> <a href="pants" title="foo">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br>amet .foo { color: #fff; } '
107
      ],
108
      [
109
        'Lo<!-- comment -->rem</b> <a href=pants title="foo>ipsum <a href="http://foo.com/"><strong>dolor</a></strong> sit<br/>amet <script>alert("hello world");',
110
        'Lorem <a href="pants" title="foo&gt;ipsum &lt;a href="><strong>dolor</strong></a> sit<br>amet '
111
      ],
112
      [
113
        '<p>a</p><blockquote>b',
114
        '<p>a</p><blockquote>b</blockquote>'
115
      ],
116
      [
117
        '<b>Lo<!-- comment -->rem</b> <a href="javascript:pants" title="foo">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br/>amet <<foo>script>alert("hello world");</script>',
118
        '<b>Lorem</b> <a title="foo">ipsum</a> <a href="http://foo.com/"><strong>dolor</strong></a> sit<br>amet &lt;script&gt;alert("hello world");'
119
      ]
120
    ]
165
    'protocol-based JS injection: long UTF-8 encoding without semicolons' => [
166
      '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
167
      '<a>foo</a>'
168
    ],
121 169
    # rubocop:enable Layout/LineLength
122 170

  
123
    def test_should_sanitize_html_strings
124
      STRINGS.each do |input, expected|
125
        assert_equal expected, filter(input)
126
      end
127
    end
128

  
129
    # samples taken from the Sanitize test suite
130
    PROTOCOLS = {
131
      'protocol-based JS injection: simple, no spaces' => [
132
        '<a href="javascript:alert(\'XSS\');">foo</a>',
133
        '<a>foo</a>'
134
      ],
135

  
136
      'protocol-based JS injection: simple, spaces before' => [
137
        '<a href="javascript    :alert(\'XSS\');">foo</a>',
138
        '<a>foo</a>'
139
      ],
140

  
141
      'protocol-based JS injection: simple, spaces after' => [
142
        '<a href="javascript:    alert(\'XSS\');">foo</a>',
143
        '<a>foo</a>'
144
      ],
145

  
146
      'protocol-based JS injection: simple, spaces before and after' => [
147
        '<a href="javascript    :   alert(\'XSS\');">foo</a>',
148
        '<a>foo</a>'
149
      ],
150

  
151
      'protocol-based JS injection: preceding colon' => [
152
        '<a href=":javascript:alert(\'XSS\');">foo</a>',
153
        '<a>foo</a>'
154
      ],
155

  
156
      'protocol-based JS injection: UTF-8 encoding' => [
157
        '<a href="javascript&#58;">foo</a>',
158
        '<a>foo</a>'
159
      ],
160

  
161
      'protocol-based JS injection: long UTF-8 encoding' => [
162
        '<a href="javascript&#0058;">foo</a>',
163
        '<a>foo</a>'
164
      ],
165

  
166
      # rubocop:disable Layout/LineLength
167
      'protocol-based JS injection: long UTF-8 encoding without semicolons' => [
168
        '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
169
        '<a>foo</a>'
170
      ],
171
      # rubocop:enable Layout/LineLength
172

  
173
      'protocol-based JS injection: hex encoding' => [
174
        '<a href="javascript&#x3A;">foo</a>',
175
        '<a>foo</a>'
176
      ],
177

  
178
      'protocol-based JS injection: long hex encoding' => [
179
        '<a href="javascript&#x003A;">foo</a>',
180
        '<a>foo</a>'
181
      ],
182

  
183
      'protocol-based JS injection: hex encoding without semicolons' => [
184
        '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
185
        '<a>foo</a>'
186
      ],
187

  
188
      'protocol-based JS injection: null char' => [
189
        "<img src=java\0script:alert(\"XSS\")>",
190
        '<img src="java">'
191
        # '<img>'
192
      ],
193

  
194
      'protocol-based JS injection: invalid URL char' => [
195
        '<img src=java\script:alert("XSS")>',
196
        '<img>'
197
      ],
198

  
199
      'protocol-based JS injection: spaces and entities' => [
200
        '<img src=" &#14;  javascript:alert(\'XSS\');">',
201
        '<img src="">'
202
        # '<img>'
203
      ],
204

  
205
      'protocol whitespace' => [
206
        '<a href=" http://example.com/"></a>',
207
        '<a href="http://example.com/"></a>'
208
      ],
209

  
210
      'data images sources' => [
211
        '<img src="data:image/png;base64,foobar">',
212
        '<img>'
213
      ],
214

  
215
      'data URIs' => [
216
        '<a href="data:text/html;base64,foobar">XSS</a>',
217
        '<a>XSS</a>'
218
      ],
219

  
220
      'vbscript URIs' => [
221
        '<a href="vbscript:foobar">XSS</a>',
222
        '<a>XSS</a>'
223
      ],
224
    }
225

  
226
    PROTOCOLS.each do |name, strings|
227
      test "should not allow #{name}" do
228
        input, expected = *strings
229
        assert_equal expected, filter(input)
230
      end
171
    'protocol-based JS injection: hex encoding' => [
172
      '<a href="javascript&#x3A;">foo</a>',
173
      '<a>foo</a>'
174
    ],
175

  
176
    'protocol-based JS injection: long hex encoding' => [
177
      '<a href="javascript&#x003A;">foo</a>',
178
      '<a>foo</a>'
179
    ],
180

  
181
    'protocol-based JS injection: hex encoding without semicolons' => [
182
      '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>foo</a>',
183
      '<a>foo</a>'
184
    ],
185

  
186
    'protocol-based JS injection: null char' => [
187
      "<img src=java\0script:alert(\"XSS\")>",
188
      '<img src="java">'
189
      # '<img>'
190
    ],
191

  
192
    'protocol-based JS injection: invalid URL char' => [
193
      '<img src=java\script:alert("XSS")>',
194
      '<img>'
195
    ],
196

  
197
    'protocol-based JS injection: spaces and entities' => [
198
      '<img src=" &#14;  javascript:alert(\'XSS\');">',
199
      '<img src="">'
200
      # '<img>'
201
    ],
202

  
203
    'protocol whitespace' => [
204
      '<a href=" http://example.com/"></a>',
205
      '<a href="http://example.com/"></a>'
206
    ],
207

  
208
    'data images sources' => [
209
      '<img src="data:image/png;base64,foobar">',
210
      '<img>'
211
    ],
212

  
213
    'data URIs' => [
214
      '<a href="data:text/html;base64,foobar">XSS</a>',
215
      '<a>XSS</a>'
216
    ],
217

  
218
    'vbscript URIs' => [
219
      '<a href="vbscript:foobar">XSS</a>',
220
      '<a>XSS</a>'
221
    ],
222
  }
223

  
224
  PROTOCOLS.each do |name, strings|
225
    test "should not allow #{name}" do
226
      input, expected = *strings
227
      assert_equal expected, filter(input)
231 228
    end
232 229
  end
233 230
end
test/unit/lib/redmine/wiki_formatting/common_mark/syntax_highlight_filter_test.rb
18 18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 19

  
20 20
require_relative '../../../../../test_helper'
21
if Object.const_defined?(:Commonmarker)
22
  require 'redmine/wiki_formatting/common_mark/syntax_highlight_filter'
21
require 'redmine/wiki_formatting/common_mark/syntax_highlight_filter'
23 22

  
24
  class Redmine::WikiFormatting::CommonMark::SyntaxHighlightFilterTest < ActiveSupport::TestCase
25
    def filter(html)
26
      Redmine::WikiFormatting::CommonMark::SyntaxHighlightFilter.to_html(html, @options)
27
    end
23
class Redmine::WikiFormatting::CommonMark::SyntaxHighlightFilterTest < ActiveSupport::TestCase
24
  def filter(html)
25
    Redmine::WikiFormatting::CommonMark::SyntaxHighlightFilter.to_html(html, @options)
26
  end
28 27

  
29
    def setup
30
      @options = { }
31
    end
28
  def setup
29
    @options = { }
30
  end
32 31

  
33
    def test_should_highlight_supported_language
34
      input = <<~HTML
35
        <pre><code class="language-ruby">
36
        def foo
37
        end
38
        </code></pre>
39
      HTML
40
      expected = <<~HTML
41
        <pre><code class="ruby syntaxhl" data-language="ruby">
42
        <span class="k">def</span> <span class="nf">foo</span>
43
        <span class="k">end</span>
44
        </code></pre>
45
      HTML
46
      assert_equal expected, filter(input)
47
    end
32
  def test_should_highlight_supported_language
33
    input = <<~HTML
34
      <pre><code class="language-ruby">
35
      def foo
36
      end
37
      </code></pre>
38
    HTML
39
    expected = <<~HTML
40
      <pre><code class="ruby syntaxhl" data-language="ruby">
41
      <span class="k">def</span> <span class="nf">foo</span>
42
      <span class="k">end</span>
43
      </code></pre>
44
    HTML
45
    assert_equal expected, filter(input)
46
  end
48 47

  
49
    def test_should_highlight_supported_language_with_special_chars
50
      input = <<~HTML
51
        <pre><code class="language-c-k&amp;r">
52
        int i;
53
        </code></pre>
54
      HTML
55
      expected = <<~HTML
56
        <pre><code data-language="c-k&amp;r">
57
        int i;
58
        </code></pre>
59
      HTML
60
      assert_equal expected, filter(input)
61
    end
48
  def test_should_highlight_supported_language_with_special_chars
49
    input = <<~HTML
50
      <pre><code class="language-c-k&amp;r">
51
      int i;
52
      </code></pre>
53
    HTML
54
    expected = <<~HTML
55
      <pre><code data-language="c-k&amp;r">
56
      int i;
57
      </code></pre>
58
    HTML
59
    assert_equal expected, filter(input)
60
  end
62 61

  
63
    def test_should_strip_code_class_and_preserve_data_language_attr_for_unknown_language
64
      input = <<~HTML
65
        <pre><code class="language-foobar">
66
        def foo
67
        end
68
        </code></pre>
69
      HTML
70
      expected = <<~HTML
71
        <pre><code data-language="foobar">
72
        def foo
73
        end
74
        </code></pre>
75
      HTML
76
      assert_equal expected, filter(input)
77
    end
62
  def test_should_strip_code_class_and_preserve_data_language_attr_for_unknown_language
63
    input = <<~HTML
64
      <pre><code class="language-foobar">
65
      def foo
66
      end
67
      </code></pre>
68
    HTML
69
    expected = <<~HTML
70
      <pre><code data-language="foobar">
71
      def foo
72
      end
73
      </code></pre>
74
    HTML
75
    assert_equal expected, filter(input)
76
  end
78 77

  
79
    def test_should_ignore_code_without_class
80
      input = <<~HTML
81
        <pre><code>
82
        def foo
83
        end
84
        </code></pre>
85
      HTML
86
      assert_equal input, filter(input)
87
    end
78
  def test_should_ignore_code_without_class
79
    input = <<~HTML
80
      <pre><code>
81
      def foo
82
      end
83
      </code></pre>
84
    HTML
85
    assert_equal input, filter(input)
88 86
  end
89 87
end
(2-2/2)