##// END OF EJS Templates
Fixed: wrong digest for text files under Windows (#2264)....
Jean-Philippe Lang -
r2083:93c04f2666b6
parent child
Show More
@@ -1,136 +1,143
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, :filename => o.filename}}
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}}
30
30
31 acts_as_activity_provider :type => 'files',
31 acts_as_activity_provider :type => 'files',
32 :permission => :view_files,
32 :permission => :view_files,
33 :author_key => :author_id,
33 :author_key => :author_id,
34 :find_options => {:select => "#{Attachment.table_name}.*",
34 :find_options => {:select => "#{Attachment.table_name}.*",
35 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
35 :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " +
36 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id"}
36 "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id"}
37
37
38 acts_as_activity_provider :type => 'documents',
38 acts_as_activity_provider :type => 'documents',
39 :permission => :view_documents,
39 :permission => :view_documents,
40 :author_key => :author_id,
40 :author_key => :author_id,
41 :find_options => {:select => "#{Attachment.table_name}.*",
41 :find_options => {:select => "#{Attachment.table_name}.*",
42 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
42 :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " +
43 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
43 "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"}
44
44
45 cattr_accessor :storage_path
45 cattr_accessor :storage_path
46 @@storage_path = "#{RAILS_ROOT}/files"
46 @@storage_path = "#{RAILS_ROOT}/files"
47
47
48 def validate
48 def validate
49 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
49 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
50 end
50 end
51
51
52 def file=(incoming_file)
52 def file=(incoming_file)
53 unless incoming_file.nil?
53 unless incoming_file.nil?
54 @temp_file = incoming_file
54 @temp_file = incoming_file
55 if @temp_file.size > 0
55 if @temp_file.size > 0
56 self.filename = sanitize_filename(@temp_file.original_filename)
56 self.filename = sanitize_filename(@temp_file.original_filename)
57 self.disk_filename = Attachment.disk_filename(filename)
57 self.disk_filename = Attachment.disk_filename(filename)
58 self.content_type = @temp_file.content_type.to_s.chomp
58 self.content_type = @temp_file.content_type.to_s.chomp
59 self.filesize = @temp_file.size
59 self.filesize = @temp_file.size
60 end
60 end
61 end
61 end
62 end
62 end
63
63
64 def file
64 def file
65 nil
65 nil
66 end
66 end
67
67
68 # Copy temp file to its final location
68 # Copy temp file to its final location
69 def before_save
69 def before_save
70 if @temp_file && (@temp_file.size > 0)
70 if @temp_file && (@temp_file.size > 0)
71 logger.debug("saving '#{self.diskfile}'")
71 logger.debug("saving '#{self.diskfile}'")
72 File.open(diskfile, "wb") do |f|
72 File.open(diskfile, "wb") do |f|
73 f.write(@temp_file.read)
73 f.write(@temp_file.read)
74 end
74 end
75 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
75 self.digest = self.class.digest(diskfile)
76 end
76 end
77 # Don't save the content type if it's longer than the authorized length
77 # Don't save the content type if it's longer than the authorized length
78 if self.content_type && self.content_type.length > 255
78 if self.content_type && self.content_type.length > 255
79 self.content_type = nil
79 self.content_type = nil
80 end
80 end
81 end
81 end
82
82
83 # Deletes file on the disk
83 # Deletes file on the disk
84 def after_destroy
84 def after_destroy
85 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
85 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
86 end
86 end
87
87
88 # Returns file's location on disk
88 # Returns file's location on disk
89 def diskfile
89 def diskfile
90 "#{@@storage_path}/#{self.disk_filename}"
90 "#{@@storage_path}/#{self.disk_filename}"
91 end
91 end
92
92
93 def increment_download
93 def increment_download
94 increment!(:downloads)
94 increment!(:downloads)
95 end
95 end
96
96
97 def project
97 def project
98 container.project
98 container.project
99 end
99 end
100
100
101 def image?
101 def image?
102 self.filename =~ /\.(jpe?g|gif|png)$/i
102 self.filename =~ /\.(jpe?g|gif|png)$/i
103 end
103 end
104
104
105 def is_text?
105 def is_text?
106 Redmine::MimeType.is_type?('text', filename)
106 Redmine::MimeType.is_type?('text', filename)
107 end
107 end
108
108
109 def is_diff?
109 def is_diff?
110 self.filename =~ /\.(patch|diff)$/i
110 self.filename =~ /\.(patch|diff)$/i
111 end
111 end
112
112
113 private
113 private
114 def sanitize_filename(value)
114 def sanitize_filename(value)
115 # get only the filename, not the whole path
115 # get only the filename, not the whole path
116 just_filename = value.gsub(/^.*(\\|\/)/, '')
116 just_filename = value.gsub(/^.*(\\|\/)/, '')
117 # NOTE: File.basename doesn't work right with Windows paths on Unix
117 # NOTE: File.basename doesn't work right with Windows paths on Unix
118 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
118 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
119
119
120 # Finally, replace all non alphanumeric, hyphens or periods with underscore
120 # Finally, replace all non alphanumeric, hyphens or periods with underscore
121 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
121 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
122 end
122 end
123
123
124 # Returns an ASCII or hashed filename
124 # Returns an ASCII or hashed filename
125 def self.disk_filename(filename)
125 def self.disk_filename(filename)
126 df = DateTime.now.strftime("%y%m%d%H%M%S") + "_"
126 df = DateTime.now.strftime("%y%m%d%H%M%S") + "_"
127 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
127 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
128 df << filename
128 df << filename
129 else
129 else
130 df << Digest::MD5.hexdigest(filename)
130 df << Digest::MD5.hexdigest(filename)
131 # keep the extension if any
131 # keep the extension if any
132 df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
132 df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
133 end
133 end
134 df
134 df
135 end
135 end
136
137 # Returns the MD5 digest of the file at given path
138 def self.digest(filename)
139 File.open(filename, 'rb') do |f|
140 Digest::MD5.hexdigest(f.read)
141 end
142 end
136 end
143 end
@@ -1,1 +1,2
1 this is a text file for upload tests No newline at end of file
1 this is a text file for upload tests
2 with multiple lines
@@ -1,32 +1,37
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 File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class AttachmentTest < Test::Unit::TestCase
20 class AttachmentTest < Test::Unit::TestCase
21
21 fixtures :issues, :users
22
22 def setup
23 def setup
23 end
24 end
24
25
25 def test_diskfilename
26 def test_diskfilename
26 assert Attachment.disk_filename("test_file.txt") =~ /^\d{12}_test_file.txt$/
27 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 '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 '770c509475505f37c2b8fb6030434d6b.txt', Attachment.disk_filename("test_accentué.txt")[13..-1]
29 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentué")[13..-1]
30 assert_equal 'f8139524ebb8f32e51976982cd20a85d', Attachment.disk_filename("test_accentué")[13..-1]
30 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentué.ça")[13..-1]
31 assert_equal 'cbb5b0f30978ba03731d61f9f6d10011', Attachment.disk_filename("test_accentué.ça")[13..-1]
31 end
32 end
33
34 def test_digest
35 assert_equal '1478adae0d4eb06d35897518540e25d6', Attachment.digest(Test::Unit::TestCase.fixture_path + "/files/testfile.txt")
36 end
32 end
37 end
General Comments 0
You need to be logged in to leave comments. Login now