##// END OF EJS Templates
Fixed: Wiki section edit escapes code tags inside pre blocks (#9673)....
Jean-Philippe Lang -
r7855:c5cabfe106c9
parent child
Show More
@@ -1,181 +1,182
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redcloth3'
18 require 'redcloth3'
19 require 'digest/md5'
19 require 'digest/md5'
20
20
21 module Redmine
21 module Redmine
22 module WikiFormatting
22 module WikiFormatting
23 module Textile
23 module Textile
24 class Formatter < RedCloth3
24 class Formatter < RedCloth3
25 include ActionView::Helpers::TagHelper
25 include ActionView::Helpers::TagHelper
26
26
27 # auto_link rule after textile rules so that it doesn't break !image_url! tags
27 # auto_link rule after textile rules so that it doesn't break !image_url! tags
28 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto]
28 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto]
29
29
30 def initialize(*args)
30 def initialize(*args)
31 super
31 super
32 self.hard_breaks=true
32 self.hard_breaks=true
33 self.no_span_caps=true
33 self.no_span_caps=true
34 self.filter_styles=true
34 self.filter_styles=true
35 end
35 end
36
36
37 def to_html(*rules)
37 def to_html(*rules)
38 @toc = []
38 @toc = []
39 super(*RULES).to_s
39 super(*RULES).to_s
40 end
40 end
41
41
42 def get_section(index)
42 def get_section(index)
43 section = extract_sections(index)[1]
43 section = extract_sections(index)[1]
44 hash = Digest::MD5.hexdigest(section)
44 hash = Digest::MD5.hexdigest(section)
45 return section, hash
45 return section, hash
46 end
46 end
47
47
48 def update_section(index, update, hash=nil)
48 def update_section(index, update, hash=nil)
49 t = extract_sections(index)
49 t = extract_sections(index)
50 if hash.present? && hash != Digest::MD5.hexdigest(t[1])
50 if hash.present? && hash != Digest::MD5.hexdigest(t[1])
51 raise Redmine::WikiFormatting::StaleSectionError
51 raise Redmine::WikiFormatting::StaleSectionError
52 end
52 end
53 t[1] = update unless t[1].blank?
53 t[1] = update unless t[1].blank?
54 t.reject(&:blank?).join "\n\n"
54 t.reject(&:blank?).join "\n\n"
55 end
55 end
56
56
57 def extract_sections(index)
57 def extract_sections(index)
58 @pre_list = []
58 @pre_list = []
59 text = self.dup
59 text = self.dup
60 rip_offtags text, false
60 rip_offtags text, false
61 before = ''
61 before = ''
62 s = ''
62 s = ''
63 after = ''
63 after = ''
64 i = 0
64 i = 0
65 l = 1
65 l = 1
66 started = false
66 started = false
67 ended = false
67 ended = false
68 text.scan(/(((?:.*?)(\A|\r?\n\r?\n))(h(\d+)(#{A}#{C})\.(?::(\S+))? (.*?)$)|.*)/m).each do |all, content, lf, heading, level|
68 text.scan(/(((?:.*?)(\A|\r?\n\r?\n))(h(\d+)(#{A}#{C})\.(?::(\S+))? (.*?)$)|.*)/m).each do |all, content, lf, heading, level|
69 if heading.nil?
69 if heading.nil?
70 if ended
70 if ended
71 after << all
71 after << all
72 elsif started
72 elsif started
73 s << all
73 s << all
74 else
74 else
75 before << all
75 before << all
76 end
76 end
77 break
77 break
78 end
78 end
79 i += 1
79 i += 1
80 if ended
80 if ended
81 after << all
81 after << all
82 elsif i == index
82 elsif i == index
83 l = level.to_i
83 l = level.to_i
84 before << content
84 before << content
85 s << heading
85 s << heading
86 started = true
86 started = true
87 elsif i > index
87 elsif i > index
88 s << content
88 s << content
89 if level.to_i > l
89 if level.to_i > l
90 s << heading
90 s << heading
91 else
91 else
92 after << heading
92 after << heading
93 ended = true
93 ended = true
94 end
94 end
95 else
95 else
96 before << all
96 before << all
97 end
97 end
98 end
98 end
99 sections = [before.strip, s.strip, after.strip]
99 sections = [before.strip, s.strip, after.strip]
100 sections.each {|section| smooth_offtags section}
100 sections.each {|section| smooth_offtags_without_code_highlighting section}
101 sections
101 sections
102 end
102 end
103
103
104 private
104 private
105
105
106 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
106 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
107 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
107 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
108 def hard_break( text )
108 def hard_break( text )
109 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
109 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
110 end
110 end
111
111
112 alias :smooth_offtags_without_code_highlighting :smooth_offtags
112 # Patch to add code highlighting support to RedCloth
113 # Patch to add code highlighting support to RedCloth
113 def smooth_offtags( text )
114 def smooth_offtags( text )
114 unless @pre_list.empty?
115 unless @pre_list.empty?
115 ## replace <pre> content
116 ## replace <pre> content
116 text.gsub!(/<redpre#(\d+)>/) do
117 text.gsub!(/<redpre#(\d+)>/) do
117 content = @pre_list[$1.to_i]
118 content = @pre_list[$1.to_i]
118 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
119 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
119 content = "<code class=\"#{$1} syntaxhl\">" +
120 content = "<code class=\"#{$1} syntaxhl\">" +
120 Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
121 Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
121 end
122 end
122 content
123 content
123 end
124 end
124 end
125 end
125 end
126 end
126
127
127 AUTO_LINK_RE = %r{
128 AUTO_LINK_RE = %r{
128 ( # leading text
129 ( # leading text
129 <\w+.*?>| # leading HTML tag, or
130 <\w+.*?>| # leading HTML tag, or
130 [^=<>!:'"/]| # leading punctuation, or
131 [^=<>!:'"/]| # leading punctuation, or
131 ^ # beginning of line
132 ^ # beginning of line
132 )
133 )
133 (
134 (
134 (?:https?://)| # protocol spec, or
135 (?:https?://)| # protocol spec, or
135 (?:s?ftps?://)|
136 (?:s?ftps?://)|
136 (?:www\.) # www.*
137 (?:www\.) # www.*
137 )
138 )
138 (
139 (
139 (\S+?) # url
140 (\S+?) # url
140 (\/)? # slash
141 (\/)? # slash
141 )
142 )
142 ((?:&gt;)?|[^\w\=\/;\(\)]*?) # post
143 ((?:&gt;)?|[^\w\=\/;\(\)]*?) # post
143 (?=<|\s|$)
144 (?=<|\s|$)
144 }x unless const_defined?(:AUTO_LINK_RE)
145 }x unless const_defined?(:AUTO_LINK_RE)
145
146
146 # Turns all urls into clickable links (code from Rails).
147 # Turns all urls into clickable links (code from Rails).
147 def inline_auto_link(text)
148 def inline_auto_link(text)
148 text.gsub!(AUTO_LINK_RE) do
149 text.gsub!(AUTO_LINK_RE) do
149 all, leading, proto, url, post = $&, $1, $2, $3, $6
150 all, leading, proto, url, post = $&, $1, $2, $3, $6
150 if leading =~ /<a\s/i || leading =~ /![<>=]?/
151 if leading =~ /<a\s/i || leading =~ /![<>=]?/
151 # don't replace URL's that are already linked
152 # don't replace URL's that are already linked
152 # and URL's prefixed with ! !> !< != (textile images)
153 # and URL's prefixed with ! !> !< != (textile images)
153 all
154 all
154 else
155 else
155 # Idea below : an URL with unbalanced parethesis and
156 # Idea below : an URL with unbalanced parethesis and
156 # ending by ')' is put into external parenthesis
157 # ending by ')' is put into external parenthesis
157 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
158 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
158 url=url[0..-2] # discard closing parenth from url
159 url=url[0..-2] # discard closing parenth from url
159 post = ")"+post # add closing parenth to post
160 post = ")"+post # add closing parenth to post
160 end
161 end
161 tag = content_tag('a', proto + url, :href => "#{proto=="www."?"http://www.":proto}#{url}", :class => 'external')
162 tag = content_tag('a', proto + url, :href => "#{proto=="www."?"http://www.":proto}#{url}", :class => 'external')
162 %(#{leading}#{tag}#{post})
163 %(#{leading}#{tag}#{post})
163 end
164 end
164 end
165 end
165 end
166 end
166
167
167 # Turns all email addresses into clickable links (code from Rails).
168 # Turns all email addresses into clickable links (code from Rails).
168 def inline_auto_mailto(text)
169 def inline_auto_mailto(text)
169 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
170 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
170 mail = $1
171 mail = $1
171 if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
172 if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
172 mail
173 mail
173 else
174 else
174 content_tag('a', mail, :href => "mailto:#{mail}", :class => "email")
175 content_tag('a', mail, :href => "mailto:#{mail}", :class => "email")
175 end
176 end
176 end
177 end
177 end
178 end
178 end
179 end
179 end
180 end
180 end
181 end
181 end
182 end
@@ -1,333 +1,338
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../../../test_helper', __FILE__)
18 require File.expand_path('../../../../../test_helper', __FILE__)
19 require 'digest/md5'
19 require 'digest/md5'
20
20
21 class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
21 class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
22
22
23 def setup
23 def setup
24 @formatter = Redmine::WikiFormatting::Textile::Formatter
24 @formatter = Redmine::WikiFormatting::Textile::Formatter
25 end
25 end
26
26
27 MODIFIERS = {
27 MODIFIERS = {
28 "*" => 'strong', # bold
28 "*" => 'strong', # bold
29 "_" => 'em', # italic
29 "_" => 'em', # italic
30 "+" => 'ins', # underline
30 "+" => 'ins', # underline
31 "-" => 'del', # deleted
31 "-" => 'del', # deleted
32 "^" => 'sup', # superscript
32 "^" => 'sup', # superscript
33 "~" => 'sub' # subscript
33 "~" => 'sub' # subscript
34 }
34 }
35
35
36 def test_modifiers
36 def test_modifiers
37 assert_html_output(
37 assert_html_output(
38 '*bold*' => '<strong>bold</strong>',
38 '*bold*' => '<strong>bold</strong>',
39 'before *bold*' => 'before <strong>bold</strong>',
39 'before *bold*' => 'before <strong>bold</strong>',
40 '*bold* after' => '<strong>bold</strong> after',
40 '*bold* after' => '<strong>bold</strong> after',
41 '*two words*' => '<strong>two words</strong>',
41 '*two words*' => '<strong>two words</strong>',
42 '*two*words*' => '<strong>two*words</strong>',
42 '*two*words*' => '<strong>two*words</strong>',
43 '*two * words*' => '<strong>two * words</strong>',
43 '*two * words*' => '<strong>two * words</strong>',
44 '*two* *words*' => '<strong>two</strong> <strong>words</strong>',
44 '*two* *words*' => '<strong>two</strong> <strong>words</strong>',
45 '*(two)* *(words)*' => '<strong>(two)</strong> <strong>(words)</strong>',
45 '*(two)* *(words)*' => '<strong>(two)</strong> <strong>(words)</strong>',
46 # with class
46 # with class
47 '*(foo)two words*' => '<strong class="foo">two words</strong>'
47 '*(foo)two words*' => '<strong class="foo">two words</strong>'
48 )
48 )
49 end
49 end
50
50
51 def test_modifiers_combination
51 def test_modifiers_combination
52 MODIFIERS.each do |m1, tag1|
52 MODIFIERS.each do |m1, tag1|
53 MODIFIERS.each do |m2, tag2|
53 MODIFIERS.each do |m2, tag2|
54 next if m1 == m2
54 next if m1 == m2
55 text = "#{m2}#{m1}Phrase modifiers#{m1}#{m2}"
55 text = "#{m2}#{m1}Phrase modifiers#{m1}#{m2}"
56 html = "<#{tag2}><#{tag1}>Phrase modifiers</#{tag1}></#{tag2}>"
56 html = "<#{tag2}><#{tag1}>Phrase modifiers</#{tag1}></#{tag2}>"
57 assert_html_output text => html
57 assert_html_output text => html
58 end
58 end
59 end
59 end
60 end
60 end
61
61
62 def test_inline_code
62 def test_inline_code
63 assert_html_output(
63 assert_html_output(
64 'this is @some code@' => 'this is <code>some code</code>',
64 'this is @some code@' => 'this is <code>some code</code>',
65 '@<Location /redmine>@' => '<code>&lt;Location /redmine&gt;</code>'
65 '@<Location /redmine>@' => '<code>&lt;Location /redmine&gt;</code>'
66 )
66 )
67 end
67 end
68
68
69 def test_escaping
69 def test_escaping
70 assert_html_output(
70 assert_html_output(
71 'this is a <script>' => 'this is a &lt;script&gt;'
71 'this is a <script>' => 'this is a &lt;script&gt;'
72 )
72 )
73 end
73 end
74
74
75 def test_use_of_backslashes_followed_by_numbers_in_headers
75 def test_use_of_backslashes_followed_by_numbers_in_headers
76 assert_html_output({
76 assert_html_output({
77 'h1. 2009\02\09' => '<h1>2009\02\09</h1>'
77 'h1. 2009\02\09' => '<h1>2009\02\09</h1>'
78 }, false)
78 }, false)
79 end
79 end
80
80
81 def test_double_dashes_should_not_strikethrough
81 def test_double_dashes_should_not_strikethrough
82 assert_html_output(
82 assert_html_output(
83 'double -- dashes -- test' => 'double -- dashes -- test',
83 'double -- dashes -- test' => 'double -- dashes -- test',
84 'double -- *dashes* -- test' => 'double -- <strong>dashes</strong> -- test'
84 'double -- *dashes* -- test' => 'double -- <strong>dashes</strong> -- test'
85 )
85 )
86 end
86 end
87
87
88 def test_acronyms
88 def test_acronyms
89 assert_html_output(
89 assert_html_output(
90 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
90 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
91 '2 letters JP(Jean-Philippe) acronym' => '2 letters <acronym title="Jean-Philippe">JP</acronym> acronym',
91 '2 letters JP(Jean-Philippe) acronym' => '2 letters <acronym title="Jean-Philippe">JP</acronym> acronym',
92 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>'
92 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>'
93 )
93 )
94 end
94 end
95
95
96 def test_blockquote
96 def test_blockquote
97 # orig raw text
97 # orig raw text
98 raw = <<-RAW
98 raw = <<-RAW
99 John said:
99 John said:
100 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
100 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
101 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
101 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
102 > * Donec odio lorem,
102 > * Donec odio lorem,
103 > * sagittis ac,
103 > * sagittis ac,
104 > * malesuada in,
104 > * malesuada in,
105 > * adipiscing eu, dolor.
105 > * adipiscing eu, dolor.
106 >
106 >
107 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
107 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
108 > Proin a tellus. Nam vel neque.
108 > Proin a tellus. Nam vel neque.
109
109
110 He's right.
110 He's right.
111 RAW
111 RAW
112
112
113 # expected html
113 # expected html
114 expected = <<-EXPECTED
114 expected = <<-EXPECTED
115 <p>John said:</p>
115 <p>John said:</p>
116 <blockquote>
116 <blockquote>
117 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.<br />
117 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.<br />
118 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
118 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
119 <ul>
119 <ul>
120 <li>Donec odio lorem,</li>
120 <li>Donec odio lorem,</li>
121 <li>sagittis ac,</li>
121 <li>sagittis ac,</li>
122 <li>malesuada in,</li>
122 <li>malesuada in,</li>
123 <li>adipiscing eu, dolor.</li>
123 <li>adipiscing eu, dolor.</li>
124 </ul>
124 </ul>
125 <blockquote>
125 <blockquote>
126 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
126 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
127 </blockquote>
127 </blockquote>
128 <p>Proin a tellus. Nam vel neque.</p>
128 <p>Proin a tellus. Nam vel neque.</p>
129 </blockquote>
129 </blockquote>
130 <p>He's right.</p>
130 <p>He's right.</p>
131 EXPECTED
131 EXPECTED
132
132
133 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
133 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
134 end
134 end
135
135
136 def test_table
136 def test_table
137 raw = <<-RAW
137 raw = <<-RAW
138 This is a table with empty cells:
138 This is a table with empty cells:
139
139
140 |cell11|cell12||
140 |cell11|cell12||
141 |cell21||cell23|
141 |cell21||cell23|
142 |cell31|cell32|cell33|
142 |cell31|cell32|cell33|
143 RAW
143 RAW
144
144
145 expected = <<-EXPECTED
145 expected = <<-EXPECTED
146 <p>This is a table with empty cells:</p>
146 <p>This is a table with empty cells:</p>
147
147
148 <table>
148 <table>
149 <tr><td>cell11</td><td>cell12</td><td></td></tr>
149 <tr><td>cell11</td><td>cell12</td><td></td></tr>
150 <tr><td>cell21</td><td></td><td>cell23</td></tr>
150 <tr><td>cell21</td><td></td><td>cell23</td></tr>
151 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
151 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
152 </table>
152 </table>
153 EXPECTED
153 EXPECTED
154
154
155 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
155 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
156 end
156 end
157
157
158 def test_table_with_line_breaks
158 def test_table_with_line_breaks
159 raw = <<-RAW
159 raw = <<-RAW
160 This is a table with line breaks:
160 This is a table with line breaks:
161
161
162 |cell11
162 |cell11
163 continued|cell12||
163 continued|cell12||
164 |-cell21-||cell23
164 |-cell21-||cell23
165 cell23 line2
165 cell23 line2
166 cell23 *line3*|
166 cell23 *line3*|
167 |cell31|cell32
167 |cell31|cell32
168 cell32 line2|cell33|
168 cell32 line2|cell33|
169
169
170 RAW
170 RAW
171
171
172 expected = <<-EXPECTED
172 expected = <<-EXPECTED
173 <p>This is a table with line breaks:</p>
173 <p>This is a table with line breaks:</p>
174
174
175 <table>
175 <table>
176 <tr>
176 <tr>
177 <td>cell11<br />continued</td>
177 <td>cell11<br />continued</td>
178 <td>cell12</td>
178 <td>cell12</td>
179 <td></td>
179 <td></td>
180 </tr>
180 </tr>
181 <tr>
181 <tr>
182 <td><del>cell21</del></td>
182 <td><del>cell21</del></td>
183 <td></td>
183 <td></td>
184 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
184 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
185 </tr>
185 </tr>
186 <tr>
186 <tr>
187 <td>cell31</td>
187 <td>cell31</td>
188 <td>cell32<br/>cell32 line2</td>
188 <td>cell32<br/>cell32 line2</td>
189 <td>cell33</td>
189 <td>cell33</td>
190 </tr>
190 </tr>
191 </table>
191 </table>
192 EXPECTED
192 EXPECTED
193
193
194 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
194 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
195 end
195 end
196
196
197 def test_textile_should_not_mangle_brackets
197 def test_textile_should_not_mangle_brackets
198 assert_equal '<p>[msg1][msg2]</p>', to_html('[msg1][msg2]')
198 assert_equal '<p>[msg1][msg2]</p>', to_html('[msg1][msg2]')
199 end
199 end
200
200
201 def test_textile_should_escape_image_urls
201 def test_textile_should_escape_image_urls
202 # this is onclick="alert('XSS');" in encoded form
202 # this is onclick="alert('XSS');" in encoded form
203 raw = '!/images/comment.png"onclick=&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x27;&#x58;&#x53;&#x53;&#x27;&#x29;;&#x22;!'
203 raw = '!/images/comment.png"onclick=&#x61;&#x6c;&#x65;&#x72;&#x74;&#x28;&#x27;&#x58;&#x53;&#x53;&#x27;&#x29;;&#x22;!'
204 expected = '<p><img src="/images/comment.png&quot;onclick=&amp;#x61;&amp;#x6c;&amp;#x65;&amp;#x72;&amp;#x74;&amp;#x28;&amp;#x27;&amp;#x58;&amp;#x53;&amp;#x53;&amp;#x27;&amp;#x29;;&amp;#x22;" alt="" /></p>'
204 expected = '<p><img src="/images/comment.png&quot;onclick=&amp;#x61;&amp;#x6c;&amp;#x65;&amp;#x72;&amp;#x74;&amp;#x28;&amp;#x27;&amp;#x58;&amp;#x53;&amp;#x53;&amp;#x27;&amp;#x29;;&amp;#x22;" alt="" /></p>'
205 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
205 assert_equal expected.gsub(%r{\s+}, ''), to_html(raw).gsub(%r{\s+}, '')
206 end
206 end
207
207
208
208
209 STR_WITHOUT_PRE = [
209 STR_WITHOUT_PRE = [
210 # 0
210 # 0
211 "h1. Title
211 "h1. Title
212
212
213 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
213 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
214 # 1
214 # 1
215 "h2. Heading 2
215 "h2. Heading 2
216
216
217 Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
217 Maecenas sed elit sit amet mi accumsan vestibulum non nec velit. Proin porta tincidunt lorem, consequat rhoncus dolor fermentum in.
218
218
219 Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.",
219 Cras ipsum felis, ultrices at porttitor vel, faucibus eu nunc.",
220 # 2
220 # 2
221 "h2. Heading 2
221 "h2. Heading 2
222
222
223 Morbi facilisis accumsan orci non pharetra.
223 Morbi facilisis accumsan orci non pharetra.
224
224
225 h3. Heading 3
225 h3. Heading 3
226
226
227 Nulla nunc nisi, egestas in ornare vel, posuere ac libero.",
227 Nulla nunc nisi, egestas in ornare vel, posuere ac libero.",
228 # 3
228 # 3
229 "h3. Heading 3
229 "h3. Heading 3
230
230
231 Praesent eget turpis nibh, a lacinia nulla.",
231 Praesent eget turpis nibh, a lacinia nulla.",
232 # 4
232 # 4
233 "h2. Heading 2
233 "h2. Heading 2
234
234
235 Ut rhoncus elementum adipiscing."]
235 Ut rhoncus elementum adipiscing."]
236
236
237 TEXT_WITHOUT_PRE = STR_WITHOUT_PRE.join("\n\n").freeze
237 TEXT_WITHOUT_PRE = STR_WITHOUT_PRE.join("\n\n").freeze
238
238
239 def test_get_section_should_return_the_requested_section_and_its_hash
239 def test_get_section_should_return_the_requested_section_and_its_hash
240 assert_section_with_hash STR_WITHOUT_PRE[1], TEXT_WITHOUT_PRE, 2
240 assert_section_with_hash STR_WITHOUT_PRE[1], TEXT_WITHOUT_PRE, 2
241 assert_section_with_hash STR_WITHOUT_PRE[2..3].join("\n\n"), TEXT_WITHOUT_PRE, 3
241 assert_section_with_hash STR_WITHOUT_PRE[2..3].join("\n\n"), TEXT_WITHOUT_PRE, 3
242 assert_section_with_hash STR_WITHOUT_PRE[3], TEXT_WITHOUT_PRE, 5
242 assert_section_with_hash STR_WITHOUT_PRE[3], TEXT_WITHOUT_PRE, 5
243 assert_section_with_hash STR_WITHOUT_PRE[4], TEXT_WITHOUT_PRE, 6
243 assert_section_with_hash STR_WITHOUT_PRE[4], TEXT_WITHOUT_PRE, 6
244
244
245 assert_section_with_hash '', TEXT_WITHOUT_PRE, 0
245 assert_section_with_hash '', TEXT_WITHOUT_PRE, 0
246 assert_section_with_hash '', TEXT_WITHOUT_PRE, 10
246 assert_section_with_hash '', TEXT_WITHOUT_PRE, 10
247 end
247 end
248
248
249 def test_update_section_should_update_the_requested_section
249 def test_update_section_should_update_the_requested_section
250 replacement = "New text"
250 replacement = "New text"
251
251
252 assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement)
252 assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement)
253 assert_equal [STR_WITHOUT_PRE[0..1], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(3, replacement)
253 assert_equal [STR_WITHOUT_PRE[0..1], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(3, replacement)
254 assert_equal [STR_WITHOUT_PRE[0..2], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(5, replacement)
254 assert_equal [STR_WITHOUT_PRE[0..2], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(5, replacement)
255 assert_equal [STR_WITHOUT_PRE[0..3], replacement].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(6, replacement)
255 assert_equal [STR_WITHOUT_PRE[0..3], replacement].flatten.join("\n\n"), @formatter.new(TEXT_WITHOUT_PRE).update_section(6, replacement)
256
256
257 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(0, replacement)
257 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(0, replacement)
258 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(10, replacement)
258 assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(10, replacement)
259 end
259 end
260
260
261 def test_update_section_with_hash_should_update_the_requested_section
261 def test_update_section_with_hash_should_update_the_requested_section
262 replacement = "New text"
262 replacement = "New text"
263
263
264 assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"),
264 assert_equal [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"),
265 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement, Digest::MD5.hexdigest(STR_WITHOUT_PRE[1]))
265 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement, Digest::MD5.hexdigest(STR_WITHOUT_PRE[1]))
266 end
266 end
267
267
268 def test_update_section_with_wrong_hash_should_raise_an_error
268 def test_update_section_with_wrong_hash_should_raise_an_error
269 assert_raise Redmine::WikiFormatting::StaleSectionError do
269 assert_raise Redmine::WikiFormatting::StaleSectionError do
270 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, "New text", Digest::MD5.hexdigest("Old text"))
270 @formatter.new(TEXT_WITHOUT_PRE).update_section(2, "New text", Digest::MD5.hexdigest("Old text"))
271 end
271 end
272 end
272 end
273
273
274 STR_WITH_PRE = [
274 STR_WITH_PRE = [
275 # 0
275 # 0
276 "h1. Title
276 "h1. Title
277
277
278 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
278 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.",
279 # 1
279 # 1
280 "h2. Heading 2
280 "h2. Heading 2
281
281
282 <pre><code class=\"ruby\">
283 def foo
284 end
285 </code></pre>
286
282 Morbi facilisis accumsan orci non pharetra.
287 Morbi facilisis accumsan orci non pharetra.
283
288
284 <pre>
289 <pre>
285 Pre Content:
290 Pre Content:
286
291
287 h2. Inside pre
292 h2. Inside pre
288
293
289 <tag> inside pre block
294 <tag> inside pre block
290
295
291 Morbi facilisis accumsan orci non pharetra.
296 Morbi facilisis accumsan orci non pharetra.
292 </pre>",
297 </pre>",
293 # 2
298 # 2
294 "h3. Heading 3
299 "h3. Heading 3
295
300
296 Nulla nunc nisi, egestas in ornare vel, posuere ac libero."]
301 Nulla nunc nisi, egestas in ornare vel, posuere ac libero."]
297
302
298 def test_get_section_should_ignore_pre_content
303 def test_get_section_should_ignore_pre_content
299 text = STR_WITH_PRE.join("\n\n")
304 text = STR_WITH_PRE.join("\n\n")
300
305
301 assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
306 assert_section_with_hash STR_WITH_PRE[1..2].join("\n\n"), text, 2
302 assert_section_with_hash STR_WITH_PRE[2], text, 3
307 assert_section_with_hash STR_WITH_PRE[2], text, 3
303 end
308 end
304
309
305 def test_update_section_should_not_escape_pre_content_outside_section
310 def test_update_section_should_not_escape_pre_content_outside_section
306 text = STR_WITH_PRE.join("\n\n")
311 text = STR_WITH_PRE.join("\n\n")
307 replacement = "New text"
312 replacement = "New text"
308
313
309 assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
314 assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
310 @formatter.new(text).update_section(3, replacement)
315 @formatter.new(text).update_section(3, replacement)
311 end
316 end
312
317
313 private
318 private
314
319
315 def assert_html_output(to_test, expect_paragraph = true)
320 def assert_html_output(to_test, expect_paragraph = true)
316 to_test.each do |text, expected|
321 to_test.each do |text, expected|
317 assert_equal(( expect_paragraph ? "<p>#{expected}</p>" : expected ), @formatter.new(text).to_html, "Formatting the following text failed:\n===\n#{text}\n===\n")
322 assert_equal(( expect_paragraph ? "<p>#{expected}</p>" : expected ), @formatter.new(text).to_html, "Formatting the following text failed:\n===\n#{text}\n===\n")
318 end
323 end
319 end
324 end
320
325
321 def to_html(text)
326 def to_html(text)
322 @formatter.new(text).to_html
327 @formatter.new(text).to_html
323 end
328 end
324
329
325 def assert_section_with_hash(expected, text, index)
330 def assert_section_with_hash(expected, text, index)
326 result = @formatter.new(text).get_section(index)
331 result = @formatter.new(text).get_section(index)
327
332
328 assert_kind_of Array, result
333 assert_kind_of Array, result
329 assert_equal 2, result.size
334 assert_equal 2, result.size
330 assert_equal expected, result.first, "section content did not match"
335 assert_equal expected, result.first, "section content did not match"
331 assert_equal Digest::MD5.hexdigest(expected), result.last, "section hash did not match"
336 assert_equal Digest::MD5.hexdigest(expected), result.last, "section hash did not match"
332 end
337 end
333 end
338 end
General Comments 0
You need to be logged in to leave comments. Login now