Feature #43484 » 0004-Detect-attachment-content-type-from-file-contents-in.patch
| app/assets/javascripts/attachments.js | ||
|---|---|---|
| 122 | 122 |
uploadUrl = uploadUrl + '?attachment_id=' + attachmentId; |
| 123 | 123 |
if (blob instanceof window.Blob) {
|
| 124 | 124 |
uploadUrl += '&filename=' + encodeURIComponent(blob.name); |
| 125 |
uploadUrl += '&content_type=' + encodeURIComponent(blob.type); |
|
| 126 | 125 |
} |
| 127 | 126 | |
| 128 | 127 |
return $.ajax(uploadUrl, {
|
| app/controllers/attachments_controller.rb | ||
|---|---|---|
| 110 | 110 |
@attachment = Attachment.new(:file => raw_request_body) |
| 111 | 111 |
@attachment.author = User.current |
| 112 | 112 |
@attachment.filename = params[:filename].presence || Redmine::Utils.random_hex(16) |
| 113 |
@attachment.content_type = params[:content_type].presence |
|
| 114 | 113 |
saved = @attachment.save |
| 115 | 114 | |
| 116 | 115 |
respond_to do |format| |
| app/models/attachment.rb | ||
|---|---|---|
| 83 | 83 |
cattr_accessor :thumbnails_storage_path |
| 84 | 84 |
@@thumbnails_storage_path = File.join(Rails.root, "tmp", "thumbnails") |
| 85 | 85 | |
| 86 |
before_create :files_to_final_location |
|
| 86 |
before_create :set_content_type, :files_to_final_location
|
|
| 87 | 87 |
after_commit :delete_from_disk, :on => :destroy |
| 88 | 88 |
after_commit :reuse_existing_file_if_possible, :on => :create |
| 89 | 89 |
after_rollback :delete_from_disk, :on => :create |
| 90 | 90 | |
| 91 |
safe_attributes 'filename', 'content_type', 'description'
|
|
| 91 |
safe_attributes 'filename', 'description' |
|
| 92 | 92 | |
| 93 | 93 |
# Returns an unsaved copy of the attachment |
| 94 | 94 |
def copy(attributes=nil) |
| ... | ... | |
| 118 | 118 |
self.filename = @temp_file.original_filename |
| 119 | 119 |
self.filename.force_encoding("UTF-8")
|
| 120 | 120 |
end |
| 121 |
if @temp_file.respond_to?(:content_type) |
|
| 122 |
self.content_type = @temp_file.content_type.to_s.chomp |
|
| 123 |
end |
|
| 124 | 121 |
self.filesize = @temp_file.size |
| 125 | 122 |
end |
| 126 | 123 |
end |
| ... | ... | |
| 134 | 131 |
filename |
| 135 | 132 |
end |
| 136 | 133 | |
| 134 |
def set_content_type |
|
| 135 |
return unless @temp_file |
|
| 136 | ||
| 137 |
io = @temp_file.respond_to?(:read) ? @temp_file : StringIO.new(@temp_file) |
|
| 138 |
self.content_type = Marcel::MimeType.for(io, name: filename) |
|
| 139 |
@temp_file.rewind if @temp_file.respond_to?(:rewind) |
|
| 140 |
end |
|
| 141 | ||
| 137 | 142 |
# Copies the temporary file to its final location |
| 138 | 143 |
# and computes its hash |
| 139 | 144 |
def files_to_final_location |
| ... | ... | |
| 157 | 162 |
self.digest = sha.hexdigest |
| 158 | 163 |
end |
| 159 | 164 |
@temp_file = nil |
| 160 | ||
| 161 |
if content_type.blank? && filename.present? |
|
| 162 |
self.content_type = Redmine::MimeType.of(filename) |
|
| 163 |
end |
|
| 164 |
# Don't save the content type if it's longer than the authorized length |
|
| 165 |
if self.content_type && self.content_type.length > 255 |
|
| 166 |
self.content_type = nil |
|
| 167 |
end |
|
| 168 | 165 |
end |
| 169 | 166 | |
| 170 | 167 |
# Deletes the file from the file system if it's not referenced by other attachments |
| lib/plugins/acts_as_attachable/lib/acts_as_attachable.rb | ||
|---|---|---|
| 127 | 127 |
next |
| 128 | 128 |
end |
| 129 | 129 |
a.filename = attachment['filename'] unless attachment['filename'].blank? |
| 130 |
a.content_type = attachment['content_type'] unless attachment['content_type'].blank? |
|
| 130 |
# Re-evaluate MIME type after rename. Marcel uses the filename as a hint |
|
| 131 |
if a.filename_changed? |
|
| 132 |
File.open(a.diskfile, 'rb') do |io| |
|
| 133 |
a.content_type = Marcel::MimeType.for(io, name: a.filename) |
|
| 134 |
end |
|
| 135 |
end |
|
| 131 | 136 |
end |
| 132 | 137 |
next unless a |
| 133 | 138 |
a.description = attachment['description'].to_s.strip |
| test/integration/api_test/attachments_test.rb | ||
|---|---|---|
| 142 | 142 |
assert_nil attachment.container |
| 143 | 143 |
assert_equal 2, attachment.author_id |
| 144 | 144 |
assert_equal 'File content'.size, attachment.filesize |
| 145 |
assert attachment.content_type.blank?
|
|
| 145 |
assert_equal 'application/octet-stream', attachment.content_type
|
|
| 146 | 146 |
assert attachment.filename.present? |
| 147 | 147 |
assert_match %r{\d+_[0-9a-z]+}, attachment.diskfile
|
| 148 | 148 |
assert File.exist?(attachment.diskfile) |
| test/integration/api_test/files_test.rb | ||
|---|---|---|
| 83 | 83 |
assert_response :bad_request |
| 84 | 84 |
end |
| 85 | 85 | |
| 86 |
test "POST /projects/:project_id/files.json should accept :filename, :description, :content_type as optional parameters" do
|
|
| 86 |
test "POST /projects/:project_id/files.json should accept :filename, :description as optional parameters" do |
|
| 87 | 87 |
set_tmp_attachments_directory |
| 88 | 88 |
post( |
| 89 | 89 |
'/uploads.xml', |
| ... | ... | |
| 94 | 94 |
{ "file": {
|
| 95 | 95 |
"filename": "New filename", |
| 96 | 96 |
"description": "New description", |
| 97 |
"content_type": "application/txt", |
|
| 98 | 97 |
"token": "#{token}"
|
| 99 | 98 |
} |
| 100 | 99 |
} |
| ... | ... | |
| 106 | 105 |
assert_response :success |
| 107 | 106 |
assert_equal "New filename", Attachment.last.filename |
| 108 | 107 |
assert_equal "New description", Attachment.last.description |
| 109 |
assert_equal "application/txt", Attachment.last.content_type |
|
| 110 | 108 |
end |
| 111 | 109 | |
| 112 | 110 |
test "POST /projects/:project_id/files.json should accept :version_id to attach the files to a version" do |
| test/integration/attachments_test.rb | ||
|---|---|---|
| 33 | 33 |
assert_equal 'text/plain', attachment.content_type |
| 34 | 34 |
end |
| 35 | 35 | |
| 36 |
def test_upload_should_accept_content_type_param
|
|
| 36 |
def test_upload_should_detect_file_content_type
|
|
| 37 | 37 |
log_user('jsmith', 'jsmith')
|
| 38 | 38 |
assert_difference 'Attachment.count' do |
| 39 | 39 |
post( |
| 40 |
"/uploads.js?attachment_id=1&filename=foo&content_type=image/jpeg",
|
|
| 41 |
:params => "File content",
|
|
| 40 |
"/uploads.js?attachment_id=1&filename=foo", |
|
| 41 |
:params => "\xCA\xFE\xBA\xBE", # Java class file magic bytes
|
|
| 42 | 42 |
:headers => {"CONTENT_TYPE" => 'application/octet-stream'})
|
| 43 | 43 |
assert_response :success |
| 44 | 44 |
end |
| 45 | 45 |
attachment = Attachment.order(:id => :desc).first |
| 46 |
assert_equal 'image/jpeg', attachment.content_type
|
|
| 46 |
assert_equal 'application/java-vm', attachment.content_type
|
|
| 47 | 47 |
end |
| 48 | 48 | |
| 49 | 49 |
def test_upload_as_js_and_attach_to_an_issue |
| test/unit/attachment_test.rb | ||
|---|---|---|
| 54 | 54 |
assert_equal 59, File.size(a.diskfile) |
| 55 | 55 |
end |
| 56 | 56 | |
| 57 |
def test_create_should_clear_content_type_if_too_long |
|
| 57 |
def test_create_should_not_trust_given_content_type_attribute |
|
| 58 |
# Text file upload with a long bogus content type |
|
| 58 | 59 |
a = Attachment.new(:container => Issue.find(1), |
| 59 |
:file => uploaded_test_file("testfile.txt", "text/plain"),
|
|
| 60 |
:file => uploaded_test_file("testfile.txt", nil),
|
|
| 60 | 61 |
:author => User.find(1), |
| 61 | 62 |
:content_type => 'a'*300) |
| 62 | 63 |
assert a.save |
| 63 | 64 |
a.reload |
| 64 |
assert_nil a.content_type |
|
| 65 |
assert_equal 'text/plain', a.content_type |
|
| 66 |
end |
|
| 67 | ||
| 68 |
def test_create_should_not_trust_client_declared_content_type |
|
| 69 |
# PDF file uploaded with a wrong content type "text/plain" |
|
| 70 |
a = Attachment.new(:container => Issue.find(1), |
|
| 71 |
:file => uploaded_test_file("hello.pdf", "text/plain"),
|
|
| 72 |
:author => User.find(1)) |
|
| 73 |
assert a.save |
|
| 74 |
a.reload |
|
| 75 |
assert_equal 'application/pdf', a.content_type |
|
| 65 | 76 |
end |
| 66 | 77 | |
| 67 | 78 |
def test_shorted_filename_if_too_long |
| ... | ... | |
| 413 | 424 |
assert_equal 'text/plain', attachment.content_type |
| 414 | 425 |
end |
| 415 | 426 | |
| 427 |
test "Attachment.attach_files should recalculate the content_type when filename changes" do |
|
| 428 |
@project = Project.find(1) |
|
| 429 |
attachment = Attachment.create!( |
|
| 430 |
:file => mock_file_with_options(:original_filename => "test.bin", :content => "\x00\x01\x02".b), |
|
| 431 |
:author_id => 1, |
|
| 432 |
:created_on => 2.days.ago |
|
| 433 |
) |
|
| 434 |
assert_equal 'application/octet-stream', attachment.content_type |
|
| 435 | ||
| 436 |
Attachment.attach_files(@project, {'1' => {'token' => attachment.token, 'filename' => 'renamed.txt'}})
|
|
| 437 |
attachment.reload |
|
| 438 |
assert_equal 'text/plain', attachment.content_type |
|
| 439 |
end |
|
| 440 | ||
| 416 | 441 |
def test_update_digest_to_sha256_should_update_digest |
| 417 | 442 |
set_fixtures_attachments_directory |
| 418 | 443 |
attachment = Attachment.find 6 |
- « Previous
- 1
- 2
- 3
- 4
- Next »