Commit c185db45 authored by jplang's avatar jplang

Files upload restriction by files extensions (#20008).

git-svn-id: https://svn.redmine.org/redmine/trunk@14792 e93f8b46-1217-0410-a6f0-8f06a7374b81
parent cd335f2e
......@@ -26,7 +26,7 @@ class Attachment < ActiveRecord::Base
validates_length_of :filename, :maximum => 255
validates_length_of :disk_filename, :maximum => 255
validates_length_of :description, :maximum => 255
validate :validate_max_file_size
validate :validate_max_file_size, :validate_file_extension
attr_protected :id
acts_as_event :title => :filename,
......@@ -69,6 +69,15 @@ class Attachment < ActiveRecord::Base
end
end
def validate_file_extension
if @temp_file
extension = File.extname(filename)
unless self.class.valid_extension?(extension)
errors.add(:base, l(:error_attachment_extension_not_allowed, :extension => extension))
end
end
end
def file=(incoming_file)
unless incoming_file.nil?
@temp_file = incoming_file
......@@ -333,6 +342,22 @@ class Attachment < ActiveRecord::Base
end
end
# Returns true if the extension is allowed, 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?)
end
if denied.present? && denied.include?(extension)
return false
end
unless allowed.blank? || allowed.include?(extension)
return false
end
true
end
private
# Physically deletes the file from the file system
......
......@@ -3,6 +3,12 @@
<div class="box tabular settings">
<p><%= setting_text_field :attachment_max_size, :size => 6 %> <%= l(:"number.human.storage_units.units.kb") %></p>
<p><%= setting_text_area :attachment_extensions_allowed %>
<em class="info"><%= l(:text_comma_separated) %> <%= l(:label_example) %>: txt, png</em></p>
<p><%= setting_text_area :attachment_extensions_denied %>
<em class="info"><%= l(:text_comma_separated) %> <%= l(:label_example) %>: js, swf</em></p>
<p><%= setting_text_field :file_max_size_displayed, :size => 6 %> <%= l(:"number.human.storage_units.units.kb") %></p>
<p><%= setting_text_field :diff_max_lines_displayed, :size => 6 %></p>
......
......@@ -210,6 +210,7 @@ en:
error_invalid_file_encoding: "The file is not a valid %{encoding} encoded file"
error_invalid_csv_file_or_settings: "The file is not a CSV file or does not match the settings below"
error_can_not_read_import_file: "An error occurred while reading the file to import"
error_attachment_extension_not_allowed: "Attachment extension %{extension} is not allowed"
mail_subject_lost_password: "Your %{value} password"
mail_body_lost_password: 'To change your password, click on the following link:'
......@@ -426,6 +427,8 @@ en:
setting_link_copied_issue: Link issues on copy
setting_max_additional_emails: Maximum number of additional email addresses
setting_search_results_per_page: Search results per page
setting_attachment_extensions_allowed: Allowed extensions
setting_attachment_extensions_denied: Disallowed extensions
permission_add_project: Create project
permission_add_subprojects: Create subprojects
......
......@@ -230,6 +230,7 @@ fr:
error_invalid_file_encoding: "Le fichier n'est pas un fichier %{encoding} valide"
error_invalid_csv_file_or_settings: "Le fichier n'est pas un fichier CSV ou n'est pas conforme aux paramètres sélectionnés"
error_can_not_read_import_file: "Une erreur est survenue lors de la lecture du fichier à importer"
error_attachment_extension_not_allowed: "L'extension %{extension} n'est pas autorisée"
mail_subject_lost_password: "Votre mot de passe %{value}"
mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :'
......@@ -446,6 +447,8 @@ fr:
setting_link_copied_issue: Lier les demandes lors de la copie
setting_max_additional_emails: Nombre maximal d'adresses email additionnelles
setting_search_results_per_page: Résultats de recherche affichés par page
setting_attachment_extensions_allowed: Extensions autorisées
setting_attachment_extensions_denied: Extensions non autorisées
permission_add_project: Créer un projet
permission_add_subprojects: Créer des sous-projets
......
......@@ -55,6 +55,10 @@ session_timeout:
attachment_max_size:
format: int
default: 5120
attachment_extensions_allowed:
default:
attachment_extensions_denied:
default:
issues_export_limit:
format: int
default: 500
......
......@@ -122,6 +122,45 @@ class AttachmentTest < ActiveSupport::TestCase
end
end
def test_extension_should_be_validated_against_allowed_extensions
with_settings :attachment_extensions_allowed => "txt, png" do
a = Attachment.new(:container => Issue.find(1),
:file => mock_file_with_options(:original_filename => "test.png"),
:author => User.find(1))
assert_save a
a = Attachment.new(:container => Issue.find(1),
:file => mock_file_with_options(:original_filename => "test.jpeg"),
:author => User.find(1))
assert !a.save
end
end
def test_extension_should_be_validated_against_denied_extensions
with_settings :attachment_extensions_denied => "txt, png" do
a = Attachment.new(:container => Issue.find(1),
:file => mock_file_with_options(:original_filename => "test.jpeg"),
:author => User.find(1))
assert_save a
a = Attachment.new(:container => Issue.find(1),
:file => mock_file_with_options(:original_filename => "test.png"),
:author => User.find(1))
assert !a.save
end
end
def test_valid_extension_should_be_case_insensitive
with_settings :attachment_extensions_allowed => "txt, Png" do
assert Attachment.valid_extension?(".pnG")
assert !Attachment.valid_extension?(".jpeg")
end
with_settings :attachment_extensions_denied => "txt, Png" do
assert !Attachment.valid_extension?(".pnG")
assert Attachment.valid_extension?(".jpeg")
end
end
def test_description_length_should_be_validated
a = Attachment.new(:description => 'a' * 300)
assert !a.save
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment