##// END OF EJS Templates
Fixed: possible error when attachment's filename is non-ASCII (#747, #1243, #1089)....
Jean-Philippe Lang -
r1418:439c69723722
parent child
Show More
@@ -0,0 +1,32
1 # redMine - project management software
2 # Copyright (C) 2006-2007 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 File.dirname(__FILE__) + '/../test_helper'
19
20 class AttachmentTest < Test::Unit::TestCase
21
22 def setup
23 end
24
25 def test_diskfilename
26 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
27 assert_equal 'test_file.txt', Attachment.disk_filename("test_file.txt")[13..-1]
28 assert_equal '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentuΓ©.txt")[13..-1]
29 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentuΓ©")[13..-1]
30 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentuΓ©.Γ§a")[13..-1]
31 end
32 end
@@ -1,103 +1,116
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 "digest/md5"
18 require "digest/md5"
19
19
20 class Attachment < ActiveRecord::Base
20 class Attachment < ActiveRecord::Base
21 belongs_to :container, :polymorphic => true
21 belongs_to :container, :polymorphic => true
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23
23
24 validates_presence_of :container, :filename, :author
24 validates_presence_of :container, :filename, :author
25 validates_length_of :filename, :maximum => 255
25 validates_length_of :filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
27
27
28 acts_as_event :title => :filename,
28 acts_as_event :title => :filename,
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}}
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}}
30
30
31 cattr_accessor :storage_path
31 cattr_accessor :storage_path
32 @@storage_path = "#{RAILS_ROOT}/files"
32 @@storage_path = "#{RAILS_ROOT}/files"
33
33
34 def validate
34 def validate
35 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
35 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
36 end
36 end
37
37
38 def file=(incoming_file)
38 def file=(incoming_file)
39 unless incoming_file.nil?
39 unless incoming_file.nil?
40 @temp_file = incoming_file
40 @temp_file = incoming_file
41 if @temp_file.size > 0
41 if @temp_file.size > 0
42 self.filename = sanitize_filename(@temp_file.original_filename)
42 self.filename = sanitize_filename(@temp_file.original_filename)
43 self.disk_filename = DateTime.now.strftime("%y%m%d%H%M%S") + "_" + self.filename
43 self.disk_filename = Attachment.disk_filename(filename)
44 self.content_type = @temp_file.content_type.to_s.chomp
44 self.content_type = @temp_file.content_type.to_s.chomp
45 self.filesize = @temp_file.size
45 self.filesize = @temp_file.size
46 end
46 end
47 end
47 end
48 end
48 end
49
49
50 def file
50 def file
51 nil
51 nil
52 end
52 end
53
53
54 # Copy temp file to its final location
54 # Copy temp file to its final location
55 def before_save
55 def before_save
56 if @temp_file && (@temp_file.size > 0)
56 if @temp_file && (@temp_file.size > 0)
57 logger.debug("saving '#{self.diskfile}'")
57 logger.debug("saving '#{self.diskfile}'")
58 File.open(diskfile, "wb") do |f|
58 File.open(diskfile, "wb") do |f|
59 f.write(@temp_file.read)
59 f.write(@temp_file.read)
60 end
60 end
61 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
61 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
62 end
62 end
63 # Don't save the content type if it's longer than the authorized length
63 # Don't save the content type if it's longer than the authorized length
64 if self.content_type && self.content_type.length > 255
64 if self.content_type && self.content_type.length > 255
65 self.content_type = nil
65 self.content_type = nil
66 end
66 end
67 end
67 end
68
68
69 # Deletes file on the disk
69 # Deletes file on the disk
70 def after_destroy
70 def after_destroy
71 if self.filename?
71 if self.filename?
72 File.delete(diskfile) if File.exist?(diskfile)
72 File.delete(diskfile) if File.exist?(diskfile)
73 end
73 end
74 end
74 end
75
75
76 # Returns file's location on disk
76 # Returns file's location on disk
77 def diskfile
77 def diskfile
78 "#{@@storage_path}/#{self.disk_filename}"
78 "#{@@storage_path}/#{self.disk_filename}"
79 end
79 end
80
80
81 def increment_download
81 def increment_download
82 increment!(:downloads)
82 increment!(:downloads)
83 end
83 end
84
84
85 def project
85 def project
86 container.project
86 container.project
87 end
87 end
88
88
89 def image?
89 def image?
90 self.filename =~ /\.(jpe?g|gif|png)$/i
90 self.filename =~ /\.(jpe?g|gif|png)$/i
91 end
91 end
92
92
93 private
93 private
94 def sanitize_filename(value)
94 def sanitize_filename(value)
95 # get only the filename, not the whole path
95 # get only the filename, not the whole path
96 just_filename = value.gsub(/^.*(\\|\/)/, '')
96 just_filename = value.gsub(/^.*(\\|\/)/, '')
97 # NOTE: File.basename doesn't work right with Windows paths on Unix
97 # NOTE: File.basename doesn't work right with Windows paths on Unix
98 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
98 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
99
99
100 # Finally, replace all non alphanumeric, hyphens or periods with underscore
100 # Finally, replace all non alphanumeric, hyphens or periods with underscore
101 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
101 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
102 end
102 end
103
104 # Returns an ASCII or hashed filename
105 def self.disk_filename(filename)
106 df = DateTime.now.strftime("%y%m%d%H%M%S") + "_"
107 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
108 df << filename
109 else
110 df << Digest::MD5.hexdigest(filename)
111 # keep the extension if any
112 df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
113 end
114 df
115 end
103 end
116 end
General Comments 0
You need to be logged in to leave comments. Login now