support arbitrary attachments

This commit is contained in:
Régis Hanol 2013-07-10 22:54:05 +02:00
parent e8c1a81e87
commit 27ab5f471c
5 changed files with 61 additions and 38 deletions

View File

@ -4,8 +4,7 @@ class UploadsController < ApplicationController
def create def create
file = params[:file] || params[:files].first file = params[:file] || params[:files].first
# only supports images for now return render status: 415, json: failed_json unless SiteSetting.authorized_file?(file)
return render status: 415, json: failed_json unless file.content_type =~ /^image\/.+/
upload = Upload.create_for(current_user.id, file) upload = Upload.create_for(current_user.id, file)

View File

@ -274,6 +274,23 @@ class SiteSetting < ActiveRecord::Base
top_menu_items.map { |item| item.name }.select{ |item| list.include?(item) }.first top_menu_items.map { |item| item.name }.select{ |item| list.include?(item) }.first
end end
def self.authorized_file?(file)
file.original_filename =~ /\.(#{authorized_extensions.tr(". ", "")})$/i
end
def self.images
@images ||= ["jpg", "jpeg", "png", "gif", "tif", "tiff", "bmp"]
end
def self.authorized_image?(file)
authorized_images = authorized_extensions
.tr(". ", "")
.split("|")
.select { |extension| images.include?(extension) }
.join("|")
file.original_filename =~ /\.(#{authorized_images})$/i
end
end end
# == Schema Information # == Schema Information

View File

@ -1,9 +1,9 @@
require 'digest/sha1' require 'digest/sha1'
require 'image_sizer' require 'image_sizer'
require 's3'
require 'local_store'
require 'tempfile' require 'tempfile'
require 'pathname' require 'pathname'
require_dependency 's3'
require_dependency 'local_store'
class Upload < ActiveRecord::Base class Upload < ActiveRecord::Base
belongs_to :user belongs_to :user
@ -48,24 +48,25 @@ class Upload < ActiveRecord::Base
sha1 = Digest::SHA1.file(file.tempfile).hexdigest sha1 = Digest::SHA1.file(file.tempfile).hexdigest
# check if the file has already been uploaded # check if the file has already been uploaded
unless upload = Upload.where(sha1: sha1).first unless upload = Upload.where(sha1: sha1).first
# retrieve image info
image_info = FastImage.new(file.tempfile, raise_on_failure: true)
# compute image aspect ratio
width, height = ImageSizer.resize(*image_info.size)
# create a db record (so we can use the id) # create a db record (so we can use the id)
upload = Upload.create!({ upload = Upload.create!({
user_id: user_id, user_id: user_id,
original_filename: file.original_filename, original_filename: file.original_filename,
filesize: File.size(file.tempfile), filesize: File.size(file.tempfile),
sha1: sha1, sha1: sha1,
width: width,
height: height,
url: "" url: ""
}) })
# make sure we're at the beginning of the file (FastImage is moving the pointer) # deal with width & heights for images
file.rewind if SiteSetting.authorized_image?(file)
# retrieve image info
image_info = FastImage.new(file.tempfile, raise_on_failure: true)
# compute image aspect ratio
upload.width, upload.height = ImageSizer.resize(*image_info.size)
# make sure we're at the beginning of the file (FastImage is moving the pointer)
file.rewind
end
# store the file and update its url # store the file and update its url
upload.url = Upload.store_file(file, sha1, image_info, upload.id) upload.url = Upload.store_file(file, sha1, upload.id)
# save the url # save the url
upload.save upload.save
end end
@ -73,9 +74,9 @@ class Upload < ActiveRecord::Base
upload upload
end end
def self.store_file(file, sha1, image_info, upload_id) def self.store_file(file, sha1, upload_id)
return S3.store_file(file, sha1, image_info, upload_id) if SiteSetting.enable_s3_uploads? return S3.store_file(file, sha1, upload_id) if SiteSetting.enable_s3_uploads?
return LocalStore.store_file(file, sha1, image_info, upload_id) return LocalStore.store_file(file, sha1, upload_id)
end end
def self.remove_file(url) def self.remove_file(url)
@ -92,29 +93,15 @@ class Upload < ActiveRecord::Base
end end
def self.is_local?(url) def self.is_local?(url)
url.start_with?(base_url) !SiteSetting.enable_s3_uploads? && url.start_with?(LocalStore.base_url)
end end
def self.is_on_s3?(url) def self.is_on_s3?(url)
SiteSetting.enable_s3_uploads? && url.start_with?(S3.base_url) SiteSetting.enable_s3_uploads? && url.start_with?(S3.base_url)
end end
def self.base_url
asset_host.present? ? asset_host : Discourse.base_url_no_prefix
end
def self.asset_host
ActionController::Base.asset_host
end
def self.get_from_url(url) def self.get_from_url(url)
if has_been_uploaded?(url) Upload.where(url: url).first if has_been_uploaded?(url)
if m = LocalStore.uploaded_regex.match(url)
Upload.where(id: m[:upload_id]).first
elsif is_on_s3?(url)
Upload.where(url: url).first
end
end
end end
end end

View File

@ -1,18 +1,21 @@
module LocalStore module LocalStore
def self.store_file(file, sha1, image_info, upload_id) def self.store_file(file, sha1, upload_id)
clean_name = Digest::SHA1.hexdigest("#{Time.now.to_s}#{file.original_filename}")[0,16] + ".#{image_info.type}" unique_sha1 = Digest::SHA1.hexdigest("#{Time.now.to_s}#{file.original_filename}")[0,16]
extension = File.extname(file.original_filename)
clean_name = "#{unique_sha1}#{extension}"
url_root = "/uploads/#{RailsMultisite::ConnectionManagement.current_db}/#{upload_id}" url_root = "/uploads/#{RailsMultisite::ConnectionManagement.current_db}/#{upload_id}"
path = "#{Rails.root}/public#{url_root}" path = "#{Rails.root}/public#{url_root}"
FileUtils.mkdir_p path FileUtils.mkdir_p path
# not using cause mv, cause permissions are no good on move # not using cause mv, cause permissions are no good on move
File.open("#{path}/#{clean_name}", "wb") do |f| File.open("#{path}/#{clean_name}", "wb") do |f|
f.write File.read(file.tempfile) f.write File.read(file.tempfile)
end end
# url # url
return Discourse::base_uri + "#{url_root}/#{clean_name}" Discourse::base_uri + "#{url_root}/#{clean_name}"
end end
def self.remove_file(url) def self.remove_file(url)
@ -24,4 +27,21 @@ module LocalStore
/\/uploads\/#{RailsMultisite::ConnectionManagement.current_db}\/(?<upload_id>\d+)\/[0-9a-f]{16}\.(png|jpg|jpeg|gif|tif|tiff|bmp)/ /\/uploads\/#{RailsMultisite::ConnectionManagement.current_db}\/(?<upload_id>\d+)\/[0-9a-f]{16}\.(png|jpg|jpeg|gif|tif|tiff|bmp)/
end end
def self.base_url
url = asset_host.present? ? asset_host : Discourse.base_url_no_prefix
"#{url}#{directory}"
end
def self.base_path
"#{Rails.root}/public#{directory}"
end
def self.directory
"/uploads/#{RailsMultisite::ConnectionManagement.current_db}"
end
def self.asset_host
ActionController::Base.asset_host
end
end end

View File

@ -1,11 +1,11 @@
module S3 module S3
def self.store_file(file, sha1, image_info, upload_id) def self.store_file(file, sha1, upload_id)
S3.check_missing_site_settings S3.check_missing_site_settings
directory = S3.get_or_create_directory(SiteSetting.s3_upload_bucket) directory = S3.get_or_create_directory(SiteSetting.s3_upload_bucket)
extension = File.extname(file.original_filename)
remote_filename = "#{upload_id}#{sha1}.#{image_info.type}" remote_filename = "#{upload_id}#{sha1}#{extension}"
# if this fails, it will throw an exception # if this fails, it will throw an exception
file = S3.upload(file, remote_filename, directory) file = S3.upload(file, remote_filename, directory)