##// END OF EJS Templates
Merged r15431 to r15435 (#22924, #22925, #22926)....
Jean-Philippe Lang -
r15059:ee408687c61d
parent child
Show More
@@ -0,0 +1,35
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require 'uri'
19
20 module Redmine
21 module Helpers
22 module URL
23 def uri_with_safe_scheme?(uri, schemes = ['http', 'https', 'ftp', 'mailto', nil])
24 # URLs relative to the current document or document root (without a protocol
25 # separator, should be harmless
26 return true unless uri.include? ":"
27
28 # Other URLs need to be parsed
29 schemes.include? URI.parse(uri).scheme
30 rescue URI::InvalidURIError
31 false
32 end
33 end
34 end
35 end
@@ -28,6 +28,7 module ApplicationHelper
28 include Redmine::SudoMode::Helper
28 include Redmine::SudoMode::Helper
29 include Redmine::Themes::Helper
29 include Redmine::Themes::Helper
30 include Redmine::Hook::Helper
30 include Redmine::Hook::Helper
31 include Redmine::Helpers::URL
31
32
32 extend Forwardable
33 extend Forwardable
33 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
34 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
@@ -262,6 +262,14 class CustomField < ActiveRecord::Base
262 args.include?(field_format)
262 args.include?(field_format)
263 end
263 end
264
264
265 def self.human_attribute_name(attribute_key_name, *args)
266 attr_name = attribute_key_name.to_s
267 if attr_name == 'url_pattern'
268 attr_name = "url"
269 end
270 super(attr_name, *args)
271 end
272
265 protected
273 protected
266
274
267 # Removes multiple values for the custom field after setting the multiple attribute to false
275 # Removes multiple values for the custom field after setting the multiple attribute to false
@@ -26,7 +26,7
26 <% if @project.homepage.present? || @subprojects.any? || @project.visible_custom_field_values.any?(&:present?) %>
26 <% if @project.homepage.present? || @subprojects.any? || @project.visible_custom_field_values.any?(&:present?) %>
27 <ul>
27 <ul>
28 <% unless @project.homepage.blank? %>
28 <% unless @project.homepage.blank? %>
29 <li><span class="label"><%=l(:field_homepage)%>:</span> <%= link_to @project.homepage, @project.homepage %></li>
29 <li><span class="label"><%=l(:field_homepage)%>:</span> <%= link_to_if uri_with_safe_scheme?(@project.homepage), @project.homepage, @project.homepage %></li>
30 <% end %>
30 <% end %>
31 <% if @subprojects.any? %>
31 <% if @subprojects.any? %>
32 <li><span class="label"><%=l(:label_subproject_plural)%>:</span>
32 <li><span class="label"><%=l(:label_subproject_plural)%>:</span>
@@ -165,6 +165,7
165 # class RedCloth::Textile.new( str )
165 # class RedCloth::Textile.new( str )
166
166
167 class RedCloth3 < String
167 class RedCloth3 < String
168 include Redmine::Helpers::URL
168
169
169 VERSION = '3.0.4'
170 VERSION = '3.0.4'
170 DEFAULT_RULES = [:textile, :markdown]
171 DEFAULT_RULES = [:textile, :markdown]
@@ -960,6 +961,8 class RedCloth3 < String
960 href, alt_title = check_refs( href ) if href
961 href, alt_title = check_refs( href ) if href
961 url, url_title = check_refs( url )
962 url, url_title = check_refs( url )
962
963
964 return m unless uri_with_safe_scheme?(url)
965
963 out = ''
966 out = ''
964 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
967 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
965 out << "<img#{ shelve( atts ) } />"
968 out << "<img#{ shelve( atts ) } />"
@@ -48,6 +48,7 module Redmine
48 class Base
48 class Base
49 include Singleton
49 include Singleton
50 include Redmine::I18n
50 include Redmine::I18n
51 include Redmine::Helpers::URL
51 include ERB::Util
52 include ERB::Util
52
53
53 class_attribute :format_name
54 class_attribute :format_name
@@ -149,7 +150,12 module Redmine
149 # Returns the validation errors for custom_field
150 # Returns the validation errors for custom_field
150 # Should return an empty array if custom_field is valid
151 # Should return an empty array if custom_field is valid
151 def validate_custom_field(custom_field)
152 def validate_custom_field(custom_field)
152 []
153 errors = []
154 pattern = custom_field.url_pattern
155 if pattern.present? && !uri_with_safe_scheme?(url_pattern_without_tokens(pattern))
156 errors << [:url_pattern, :invalid]
157 end
158 errors
153 end
159 end
154
160
155 # Returns the validation error messages for custom_value
161 # Returns the validation error messages for custom_value
@@ -178,7 +184,7 module Redmine
178 url = url_from_pattern(custom_field, single_value, customized)
184 url = url_from_pattern(custom_field, single_value, customized)
179 [text, url]
185 [text, url]
180 end
186 end
181 links = texts_and_urls.sort_by(&:first).map {|text, url| view.link_to text, url}
187 links = texts_and_urls.sort_by(&:first).map {|text, url| view.link_to_if uri_with_safe_scheme?(url), text, url}
182 links.join(', ').html_safe
188 links.join(', ').html_safe
183 else
189 else
184 casted
190 casted
@@ -210,6 +216,13 module Redmine
210 end
216 end
211 protected :url_from_pattern
217 protected :url_from_pattern
212
218
219 # Returns the URL pattern with substitution tokens removed,
220 # for validation purpose
221 def url_pattern_without_tokens(url_pattern)
222 url_pattern.to_s.gsub(/%(value|id|project_id|project_identifier|m\d+)%/, '')
223 end
224 protected :url_pattern_without_tokens
225
213 def edit_tag(view, tag_id, tag_name, custom_value, options={})
226 def edit_tag(view, tag_id, tag_name, custom_value, options={})
214 view.text_field_tag(tag_name, custom_value.value, options.merge(:id => tag_id))
227 view.text_field_tag(tag_name, custom_value.value, options.merge(:id => tag_id))
215 end
228 end
@@ -22,8 +22,11 module Redmine
22 module Markdown
22 module Markdown
23 class HTML < Redcarpet::Render::HTML
23 class HTML < Redcarpet::Render::HTML
24 include ActionView::Helpers::TagHelper
24 include ActionView::Helpers::TagHelper
25 include Redmine::Helpers::URL
25
26
26 def link(link, title, content)
27 def link(link, title, content)
28 return nil unless uri_with_safe_scheme?(link)
29
27 css = nil
30 css = nil
28 unless link && link.starts_with?('/')
31 unless link && link.starts_with?('/')
29 css = 'external'
32 css = 'external'
@@ -40,6 +43,12 module Redmine
40 "<pre>" + CGI.escapeHTML(code) + "</pre>"
43 "<pre>" + CGI.escapeHTML(code) + "</pre>"
41 end
44 end
42 end
45 end
46
47 def image(link, title, alt_text)
48 return unless uri_with_safe_scheme?(link)
49
50 tag('img', :src => link, :alt => alt_text || "", :title => title)
51 end
43 end
52 end
44
53
45 class Formatter
54 class Formatter
@@ -164,7 +164,7 RAW
164
164
165 attachment = Attachment.generate!(:filename => 'café.jpg')
165 attachment = Attachment.generate!(:filename => 'café.jpg')
166 with_settings :text_formatting => 'markdown' do
166 with_settings :text_formatting => 'markdown' do
167 assert_include %(<img src="/attachments/download/#{attachment.id}/caf%C3%A9.jpg" alt="">),
167 assert_include %(<img src="/attachments/download/#{attachment.id}/caf%C3%A9.jpg" alt="" />),
168 textilizable("![](café.jpg)", :attachments => [attachment])
168 textilizable("![](café.jpg)", :attachments => [attachment])
169 end
169 end
170 end
170 end
@@ -20,6 +20,10 require File.expand_path('../../../../../test_helper', __FILE__)
20 class Redmine::FieldFormatTest < ActionView::TestCase
20 class Redmine::FieldFormatTest < ActionView::TestCase
21 include ApplicationHelper
21 include ApplicationHelper
22
22
23 def setup
24 set_language_if_valid 'en'
25 end
26
23 def test_string_field_with_text_formatting_disabled_should_not_format_text
27 def test_string_field_with_text_formatting_disabled_should_not_format_text
24 field = IssueCustomField.new(:field_format => 'string')
28 field = IssueCustomField.new(:field_format => 'string')
25 custom_value = CustomValue.new(:custom_field => field, :customized => Issue.new, :value => "*foo*")
29 custom_value = CustomValue.new(:custom_field => field, :customized => Issue.new, :value => "*foo*")
@@ -52,6 +56,17 class Redmine::FieldFormatTest < ActionView::TestCase
52 assert_include "<strong>foo</strong>", field.format.formatted_custom_value(self, custom_value, true)
56 assert_include "<strong>foo</strong>", field.format.formatted_custom_value(self, custom_value, true)
53 end
57 end
54
58
59 def test_should_validate_url_pattern_with_safe_scheme
60 field = IssueCustomField.new(:field_format => 'string', :name => 'URL', :url_pattern => 'http://foo/%value%')
61 assert_save field
62 end
63
64 def test_should_not_validate_url_pattern_with_unsafe_scheme
65 field = IssueCustomField.new(:field_format => 'string', :name => 'URL', :url_pattern => 'foo://foo/%value%')
66 assert !field.save
67 assert_include "URL is invalid", field.errors.full_messages
68 end
69
55 def test_text_field_with_url_pattern_should_format_as_link
70 def test_text_field_with_url_pattern_should_format_as_link
56 field = IssueCustomField.new(:field_format => 'string', :url_pattern => 'http://foo/%value%')
71 field = IssueCustomField.new(:field_format => 'string', :url_pattern => 'http://foo/%value%')
57 custom_value = CustomValue.new(:custom_field => field, :customized => Issue.new, :value => "bar")
72 custom_value = CustomValue.new(:custom_field => field, :customized => Issue.new, :value => "bar")
General Comments 0
You need to be logged in to leave comments. Login now