diff --git a/app/models/attachment.rb b/app/models/attachment.rb index c9283ae..19f30a6 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -354,22 +354,37 @@ class Attachment < ActiveRecord::Base end end - # Returns true if the extension is allowed, otherwise false + # Returns true if the extension is allowed regarding allowed/denied + # extensions defined in application settings, otherwise false def self.valid_extension?(extension) - extension = extension.downcase.sub(/\A\.+/, '') - denied, allowed = [:attachment_extensions_denied, :attachment_extensions_allowed].map do |setting| - Setting.send(setting).to_s.split(",").map {|s| s.strip.downcase.sub(/\A\.+/, '')}.reject(&:blank?) + Setting.send(setting) end - if denied.present? && denied.include?(extension) + if denied.present? && extension_in?(extension, denied) return false end - unless allowed.blank? || allowed.include?(extension) + if allowed.present? && !extension_in?(extension, allowed) return false end true end + # Returns true if extension belongs to extensions list. + def self.extension_in?(extension, extensions) + extension = extension.downcase.sub(/\A\.+/, '') + + unless extensions.is_a?(Array) + extensions = extensions.to_s.split(",").map(&:strip) + end + extensions = extensions.map {|s| s.downcase.sub(/\A\.+/, '')}.reject(&:blank?) + extensions.include?(extension) + end + + # Returns true if attachment's extension belongs to extensions list. + def extension_in?(extensions) + self.class.extension_in?(File.extname(filename), extensions) + end + private # Physically deletes the file from the file system diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 7f37ec1..46d4581 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -87,7 +87,8 @@ class CustomField < ActiveRecord::Base 'text_formatting', 'edit_tag_style', 'user_role', - 'version_status' + 'version_status', + 'extensions_allowed' def format @format ||= Redmine::FieldFormat.find(field_format) diff --git a/app/views/custom_fields/formats/_attachment.html.erb b/app/views/custom_fields/formats/_attachment.html.erb index e69de29..263238a 100644 --- a/app/views/custom_fields/formats/_attachment.html.erb +++ b/app/views/custom_fields/formats/_attachment.html.erb @@ -0,0 +1,4 @@ +

+ <%= f.text_field :extensions_allowed, :size => 50, :label => :setting_attachment_extensions_allowed %> + <%= l(:text_comma_separated) %> <%= l(:label_example) %>: txt, png +

diff --git a/lib/redmine/field_format.rb b/lib/redmine/field_format.rb index c2a02d0..7d6502b 100644 --- a/lib/redmine/field_format.rb +++ b/lib/redmine/field_format.rb @@ -861,6 +861,7 @@ module Redmine self.form_partial = 'custom_fields/formats/attachment' self.is_filter_supported = false self.change_no_details = true + field_attributes :extensions_allowed def set_custom_field_value(custom_field, custom_field_value, value) attachment_present = false @@ -917,8 +918,18 @@ module Redmine def validate_custom_value(custom_value) errors = [] - if custom_value.instance_variable_get("@attachment_present") && custom_value.value.blank? - errors << ::I18n.t('activerecord.errors.messages.invalid') + if custom_value.value.blank? + if custom_value.instance_variable_get("@attachment_present") + errors << ::I18n.t('activerecord.errors.messages.invalid') + end + else + if custom_value.value.present? + attachment = Attachment.where(:id => custom_value.value.to_s).first + extensions = custom_value.custom_field.extensions_allowed + if attachment && extensions.present? && !attachment.extension_in?(extensions) + errors << "#{::I18n.t('activerecord.errors.messages.invalid')} (#{l(:setting_attachment_extensions_allowed)}: #{extensions})" + end + end end errors.uniq diff --git a/test/integration/lib/redmine/field_format/attachment_format_test.rb b/test/integration/lib/redmine/field_format/attachment_format_test.rb index 447ba68..98aa005 100644 --- a/test/integration/lib/redmine/field_format/attachment_format_test.rb +++ b/test/integration/lib/redmine/field_format/attachment_format_test.rb @@ -153,4 +153,42 @@ class AttachmentFieldFormatTest < Redmine::IntegrationTest assert_equal attachment.id.to_s, custom_value.value assert_equal custom_value, attachment.reload.container end + + def test_create_with_valid_extension + @field.extensions_allowed = "txt, log" + @field.save! + + attachment = new_record(Attachment) do + assert_difference 'Issue.count' do + post '/projects/ecookbook/issues', { + :issue => { + :subject => "Blank", + :custom_field_values => { + @field.id => {:file => uploaded_test_file("testfile.txt", "text/plain")} + } + } + } + assert_response 302 + end + end + end + + def test_create_with_invalid_extension_should_fail + @field.extensions_allowed = "png, jpeg" + @field.save! + + attachment = new_record(Attachment) do + assert_no_difference 'Issue.count' do + post '/projects/ecookbook/issues', { + :issue => { + :subject => "Blank", + :custom_field_values => { + @field.id => {:file => uploaded_test_file("testfile.txt", "text/plain")} + } + } + } + assert_response :success + end + end + end end