FEATURE: support email attachments
This commit is contained in:
parent
ed6e2b1d79
commit
2505d18aa9
|
@ -261,15 +261,16 @@ Discourse.Utilities = {
|
||||||
switch (data.jqXHR.status) {
|
switch (data.jqXHR.status) {
|
||||||
// cancel from the user
|
// cancel from the user
|
||||||
case 0: return;
|
case 0: return;
|
||||||
|
|
||||||
// entity too large, usually returned from the web server
|
// entity too large, usually returned from the web server
|
||||||
case 413:
|
case 413:
|
||||||
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
|
var maxSizeKB = Discourse.SiteSettings.max_image_size_kb;
|
||||||
bootbox.alert(I18n.t('post.errors.image_too_large', { max_size_kb: maxSizeKB }));
|
bootbox.alert(I18n.t('post.errors.image_too_large', { max_size_kb: maxSizeKB }));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// the error message is provided by the server
|
// the error message is provided by the server
|
||||||
case 415: // media type not authorized
|
case 422:
|
||||||
case 422: // there has been an error on the server (mostly due to FastImage)
|
bootbox.alert(data.jqXHR.responseJSON.join("\n"));
|
||||||
bootbox.alert(data.jqXHR.responseText);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,45 +5,29 @@ class UploadsController < ApplicationController
|
||||||
def create
|
def create
|
||||||
file = params[:file] || params[:files].first
|
file = params[:file] || params[:files].first
|
||||||
|
|
||||||
# check if the extension is allowed
|
|
||||||
unless SiteSetting.authorized_upload?(file)
|
|
||||||
text = I18n.t("upload.unauthorized", authorized_extensions: SiteSetting.authorized_extensions.gsub("|", ", "))
|
|
||||||
return render status: 415, text: text
|
|
||||||
end
|
|
||||||
|
|
||||||
# check the file size (note: this might also be done in the web server)
|
|
||||||
filesize = File.size(file.tempfile)
|
filesize = File.size(file.tempfile)
|
||||||
type = SiteSetting.authorized_image?(file) ? "image" : "attachment"
|
upload = Upload.create_for(current_user.id, file.tempfile, file.original_filename, filesize)
|
||||||
max_size_kb = SiteSetting.send("max_#{type}_size_kb").kilobytes
|
|
||||||
return render status: 413, text: I18n.t("upload.#{type}s.too_large", max_size_kb: max_size_kb) if filesize > max_size_kb
|
|
||||||
|
|
||||||
upload = Upload.create_for(current_user.id, file, filesize)
|
if upload.errors.empty?
|
||||||
|
render_serialized(upload, UploadSerializer, root: false)
|
||||||
render_serialized(upload, UploadSerializer, root: false)
|
else
|
||||||
|
render status: 422, text: upload.errors.full_messages
|
||||||
rescue FastImage::ImageFetchFailure
|
end
|
||||||
render status: 422, text: I18n.t("upload.images.fetch_failure")
|
|
||||||
rescue FastImage::UnknownImageType
|
|
||||||
render status: 422, text: I18n.t("upload.images.unknown_image_type")
|
|
||||||
rescue FastImage::SizeNotFound
|
|
||||||
render status: 422, text: I18n.t("upload.images.size_not_found")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
RailsMultisite::ConnectionManagement.with_connection(params[:site]) do |db|
|
RailsMultisite::ConnectionManagement.with_connection(params[:site]) do |db|
|
||||||
|
|
||||||
return render nothing: true, status: 404 unless Discourse.store.internal?
|
return render nothing: true, status: 404 unless Discourse.store.internal?
|
||||||
|
|
||||||
id = params[:id].to_i
|
id = params[:id].to_i
|
||||||
url = request.fullpath
|
url = request.fullpath
|
||||||
|
|
||||||
# the "url" parameter is here to prevent people from scanning the uploads using the id
|
# the "url" parameter is here to prevent people from scanning the uploads using the id
|
||||||
upload = Upload.where(id: id, url: url).first
|
if upload = Upload.where(id: id, url: url).first
|
||||||
|
send_file(Discourse.store.path_for(upload), filename: upload.original_filename)
|
||||||
return render nothing: true, status: 404 unless upload
|
else
|
||||||
|
render nothing: true, status: 404
|
||||||
send_file(Discourse.store.path_for(upload), filename: upload.original_filename)
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -307,14 +307,13 @@ class UsersController < ApplicationController
|
||||||
size = 128 if size > 128
|
size = 128 if size > 128
|
||||||
size
|
size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# LEGACY: used by the API
|
||||||
def upload_avatar
|
def upload_avatar
|
||||||
params[:user_image_type] = "avatar"
|
params[:user_image_type] = "avatar"
|
||||||
|
|
||||||
upload_user_image
|
upload_user_image
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_user_image
|
def upload_user_image
|
||||||
params.require(:user_image_type)
|
params.require(:user_image_type)
|
||||||
user = fetch_user_from_params
|
user = fetch_user_from_params
|
||||||
|
@ -322,39 +321,24 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
file = params[:file] || params[:files].first
|
file = params[:file] || params[:files].first
|
||||||
|
|
||||||
# Only allow url uploading for API users
|
begin
|
||||||
# TODO: Does not protect from huge uploads
|
image = build_user_image_from(file)
|
||||||
# https://github.com/discourse/discourse/pull/1512
|
rescue Discourse::InvalidParameters
|
||||||
# check the file size (note: this might also be done in the web server)
|
return render status: 422, text: I18n.t("upload.images.unknown_image_type")
|
||||||
img = build_user_image_from(file)
|
|
||||||
upload_policy = AvatarUploadPolicy.new(img)
|
|
||||||
|
|
||||||
if upload_policy.too_big?
|
|
||||||
return render status: 413, text: I18n.t("upload.images.too_large",
|
|
||||||
max_size_kb: upload_policy.max_size_kb)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
raise FastImage::UnknownImageType unless SiteSetting.authorized_image?(img.file)
|
upload = Upload.create_for(user.id, image.file, image.filename, image.filesize)
|
||||||
|
|
||||||
upload_type = params[:user_image_type]
|
if upload.errors.empty?
|
||||||
|
case params[:user_image_type]
|
||||||
if upload_type == "avatar"
|
when "avatar"
|
||||||
upload_avatar_for(user, img)
|
upload_avatar_for(user, upload)
|
||||||
elsif upload_type == "profile_background"
|
when "profile_background"
|
||||||
upload_profile_background_for(user, img)
|
upload_profile_background_for(user, upload)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
render status: 422, text: ""
|
render status: 422, text: upload.errors.full_messages
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
rescue Discourse::InvalidParameters
|
|
||||||
render status: 422, text: I18n.t("upload.images.unknown_image_type")
|
|
||||||
rescue FastImage::ImageFetchFailure
|
|
||||||
render status: 422, text: I18n.t("upload.images.fetch_failure")
|
|
||||||
rescue FastImage::UnknownImageType
|
|
||||||
render status: 422, text: I18n.t("upload.images.unknown_image_type")
|
|
||||||
rescue FastImage::SizeNotFound
|
|
||||||
render status: 422, text: I18n.t("upload.images.size_not_found")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def toggle_avatar
|
def toggle_avatar
|
||||||
|
@ -367,21 +351,23 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_profile_background
|
def clear_profile_background
|
||||||
user = fetch_user_from_params
|
user = fetch_user_from_params
|
||||||
guardian.ensure_can_edit!(user)
|
guardian.ensure_can_edit!(user)
|
||||||
|
|
||||||
user.profile_background = ""
|
user.profile_background = ""
|
||||||
user.save!
|
user.save!
|
||||||
|
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@user = fetch_user_from_params
|
@user = fetch_user_from_params
|
||||||
guardian.ensure_can_delete_user!(@user)
|
guardian.ensure_can_delete_user!(@user)
|
||||||
|
|
||||||
UserDestroyer.new(current_user).destroy(@user, {delete_posts: true, context: params[:context]})
|
UserDestroyer.new(current_user).destroy(@user, {delete_posts: true, context: params[:context]})
|
||||||
|
|
||||||
render json: success_json
|
render json: success_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -403,31 +389,28 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
def build_user_image_from(file)
|
def build_user_image_from(file)
|
||||||
source = if file.is_a?(String)
|
source = if file.is_a?(String)
|
||||||
is_api? ? :url : (raise FastImage::UnknownImageType)
|
is_api? ? :url : (raise Discourse::InvalidParameters)
|
||||||
else
|
else
|
||||||
:image
|
:image
|
||||||
end
|
end
|
||||||
|
|
||||||
AvatarUploadService.new(file, source)
|
AvatarUploadService.new(file, source)
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_avatar_for(user, avatar)
|
def upload_avatar_for(user, upload)
|
||||||
upload = Upload.create_for(user.id, avatar.file, avatar.filesize)
|
|
||||||
user.upload_avatar(upload)
|
user.upload_avatar(upload)
|
||||||
|
|
||||||
Jobs.enqueue(:generate_avatars, user_id: user.id, upload_id: upload.id)
|
Jobs.enqueue(:generate_avatars, user_id: user.id, upload_id: upload.id)
|
||||||
|
|
||||||
render json: { url: upload.url, width: upload.width, height: upload.height }
|
render json: { url: upload.url, width: upload.width, height: upload.height }
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_profile_background_for(user, background)
|
def upload_profile_background_for(user, upload)
|
||||||
upload = Upload.create_for(user.id, background.file, background.filesize)
|
user.upload_profile_background(upload)
|
||||||
user.profile_background = upload.url
|
# TODO: add a resize job here
|
||||||
user.save!
|
|
||||||
|
|
||||||
# TODO: maybe add a resize job here
|
|
||||||
|
|
||||||
render json: { url: upload.url, width: upload.width, height: upload.height }
|
render json: { url: upload.url, width: upload.width, height: upload.height }
|
||||||
end
|
end
|
||||||
|
|
||||||
def respond_to_suspicious_request
|
def respond_to_suspicious_request
|
||||||
if suspicious?(params)
|
if suspicious?(params)
|
||||||
render(
|
render(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require_dependency 'url_helper'
|
require_dependency 'url_helper'
|
||||||
|
require_dependency 'file_helper'
|
||||||
|
|
||||||
module Jobs
|
module Jobs
|
||||||
|
|
||||||
|
@ -30,14 +31,13 @@ module Jobs
|
||||||
begin
|
begin
|
||||||
# have we already downloaded that file?
|
# have we already downloaded that file?
|
||||||
if !downloaded_urls.include?(src)
|
if !downloaded_urls.include?(src)
|
||||||
hotlinked = download(src)
|
hotlinked = FileHelper.download(src, @max_size, "discourse-hotlinked") rescue Discourse::InvalidParameters
|
||||||
if hotlinked.try(:size) <= @max_size
|
if hotlinked.try(:size) <= @max_size
|
||||||
filename = File.basename(URI.parse(src).path)
|
filename = File.basename(URI.parse(src).path)
|
||||||
file = ActionDispatch::Http::UploadedFile.new(tempfile: hotlinked, filename: filename)
|
upload = Upload.create_for(post.user_id, hotlinked, filename, hotlinked.size, src)
|
||||||
upload = Upload.create_for(post.user_id, file, hotlinked.size, src)
|
|
||||||
downloaded_urls[src] = upload.url
|
downloaded_urls[src] = upload.url
|
||||||
else
|
else
|
||||||
puts "Failed to pull hotlinked image: #{src} - Image is bigger than #{@max_size}"
|
Rails.logger.error("Failed to pull hotlinked image: #{src} - Image is bigger than #{@max_size}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# have we successfully downloaded that file?
|
# have we successfully downloaded that file?
|
||||||
|
@ -59,7 +59,7 @@ module Jobs
|
||||||
raw.gsub!(src, "<img src='#{url}'>")
|
raw.gsub!(src, "<img src='#{url}'>")
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
puts "Failed to pull hotlinked image: #{src}\n" + e.message + "\n" + e.backtrace.join("\n")
|
Rails.logger.error("Failed to pull hotlinked image: #{src}\n" + e.message + "\n" + e.backtrace.join("\n"))
|
||||||
ensure
|
ensure
|
||||||
# close & delete the temp file
|
# close & delete the temp file
|
||||||
hotlinked && hotlinked.close!
|
hotlinked && hotlinked.close!
|
||||||
|
@ -87,22 +87,6 @@ module Jobs
|
||||||
!src.start_with?(Discourse.asset_host || Discourse.base_url_no_prefix)
|
!src.start_with?(Discourse.asset_host || Discourse.base_url_no_prefix)
|
||||||
end
|
end
|
||||||
|
|
||||||
def download(url)
|
|
||||||
return if @max_size <= 0
|
|
||||||
extension = File.extname(URI.parse(url).path)
|
|
||||||
tmp = Tempfile.new(["discourse-hotlinked", extension])
|
|
||||||
|
|
||||||
File.open(tmp.path, "wb") do |f|
|
|
||||||
hotlinked = open(url, "rb", read_timeout: 5)
|
|
||||||
while f.size <= @max_size && data = hotlinked.read(@max_size)
|
|
||||||
f.write(data)
|
|
||||||
end
|
|
||||||
hotlinked.close!
|
|
||||||
end
|
|
||||||
|
|
||||||
tmp
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -49,6 +49,7 @@ module Jobs
|
||||||
handle_mail(mail)
|
handle_mail(mail)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
pop.finish
|
||||||
end
|
end
|
||||||
rescue Net::POPAuthenticationError => e
|
rescue Net::POPAuthenticationError => e
|
||||||
# inform admins about the error (1 message per hour to prevent too much SPAM)
|
# inform admins about the error (1 message per hour to prevent too much SPAM)
|
||||||
|
|
|
@ -72,28 +72,6 @@ class SiteSetting < ActiveRecord::Base
|
||||||
.first
|
.first
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.authorized_uploads
|
|
||||||
authorized_extensions.tr(" ", "")
|
|
||||||
.split("|")
|
|
||||||
.map { |extension| (extension.start_with?(".") ? extension[1..-1] : extension).gsub(".", "\.") }
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.authorized_upload?(file)
|
|
||||||
authorized_uploads.count > 0 && file.original_filename =~ /\.(#{authorized_uploads.join("|")})$/i
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.images
|
|
||||||
@images ||= Set.new ["jpg", "jpeg", "png", "gif", "tif", "tiff", "bmp"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.authorized_images
|
|
||||||
authorized_uploads.select { |extension| images.include?(extension) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.authorized_image?(file)
|
|
||||||
authorized_images.count > 0 && file.original_filename =~ /\.(#{authorized_images.join("|")})$/i
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.scheme
|
def self.scheme
|
||||||
use_https? ? "https" : "http"
|
use_https? ? "https" : "http"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
require "digest/sha1"
|
require "digest/sha1"
|
||||||
require "image_sizer"
|
require_dependency "image_sizer"
|
||||||
|
require_dependency "file_helper"
|
||||||
|
require_dependency "validators/upload_validator"
|
||||||
|
|
||||||
class Upload < ActiveRecord::Base
|
class Upload < ActiveRecord::Base
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
@ -12,6 +14,8 @@ class Upload < ActiveRecord::Base
|
||||||
validates_presence_of :filesize
|
validates_presence_of :filesize
|
||||||
validates_presence_of :original_filename
|
validates_presence_of :original_filename
|
||||||
|
|
||||||
|
validates_with ::Validators::UploadValidator
|
||||||
|
|
||||||
def thumbnail(width = self.width, height = self.height)
|
def thumbnail(width = self.width, height = self.height)
|
||||||
optimized_images.where(width: width, height: height).first
|
optimized_images.where(width: width, height: height).first
|
||||||
end
|
end
|
||||||
|
@ -42,9 +46,9 @@ class Upload < ActiveRecord::Base
|
||||||
File.extname(original_filename)
|
File.extname(original_filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create_for(user_id, file, filesize, origin = nil)
|
def self.create_for(user_id, file, filename, filesize, origin = nil)
|
||||||
# compute the sha
|
# compute the sha
|
||||||
sha1 = Digest::SHA1.file(file.tempfile).hexdigest
|
sha1 = Digest::SHA1.file(file).hexdigest
|
||||||
# check if the file has already been uploaded
|
# check if the file has already been uploaded
|
||||||
upload = Upload.where(sha1: sha1).first
|
upload = Upload.where(sha1: sha1).first
|
||||||
# delete the previously uploaded file if there's been an error
|
# delete the previously uploaded file if there's been an error
|
||||||
|
@ -54,37 +58,50 @@ class Upload < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
# create the upload
|
# create the upload
|
||||||
unless upload
|
unless upload
|
||||||
# deal with width & height for images
|
# initialize a new upload
|
||||||
if SiteSetting.authorized_image?(file)
|
upload = Upload.new(
|
||||||
# retrieve image info
|
|
||||||
image_info = FastImage.new(file.tempfile, raise_on_failure: true)
|
|
||||||
# compute image aspect ratio
|
|
||||||
width, height = ImageSizer.resize(*image_info.size)
|
|
||||||
# make sure we're at the beginning of the file (FastImage is moving the pointer)
|
|
||||||
file.rewind
|
|
||||||
end
|
|
||||||
# trim the origin if any
|
|
||||||
origin = origin[0...1000] if origin
|
|
||||||
# create a db record (so we can use the id)
|
|
||||||
upload = Upload.create!(
|
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
original_filename: file.original_filename,
|
original_filename: filename,
|
||||||
filesize: filesize,
|
filesize: filesize,
|
||||||
sha1: sha1,
|
sha1: sha1,
|
||||||
url: "",
|
url: ""
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
origin: origin,
|
|
||||||
)
|
)
|
||||||
|
# trim the origin if any
|
||||||
|
upload.origin = origin[0...1000] if origin
|
||||||
|
|
||||||
|
# deal with width & height for images
|
||||||
|
if FileHelper.is_image?(filename)
|
||||||
|
begin
|
||||||
|
# retrieve image info
|
||||||
|
image_info = FastImage.new(file, 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 moves the pointer)
|
||||||
|
file.rewind
|
||||||
|
rescue FastImage::ImageFetchFailure
|
||||||
|
upload.errors.add(:base, I18n.t("upload.images.fetch_failure"))
|
||||||
|
rescue FastImage::UnknownImageType
|
||||||
|
upload.errors.add(:base, I18n.t("upload.images.unknown_image_type"))
|
||||||
|
rescue FastImage::SizeNotFound
|
||||||
|
upload.errors.add(:base, I18n.t("upload.images.size_not_found"))
|
||||||
|
end
|
||||||
|
|
||||||
|
return upload unless upload.errors.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
# create a db record (so we can use the id)
|
||||||
|
return upload unless upload.save
|
||||||
|
|
||||||
# store the file and update its url
|
# store the file and update its url
|
||||||
url = Discourse.store.store_upload(file, upload)
|
url = Discourse.store.store_upload(file, upload)
|
||||||
if url.present?
|
if url.present?
|
||||||
upload.url = url
|
upload.url = url
|
||||||
upload.save
|
upload.save
|
||||||
else
|
else
|
||||||
Rails.logger.error("Failed to store upload ##{upload.id} for user ##{user_id}")
|
upload.errors.add(:url, I18n.t("upload.store_failure", { upload_id: upload.id, user_id: user_id }))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# return the uploaded file
|
# return the uploaded file
|
||||||
upload
|
upload
|
||||||
end
|
end
|
||||||
|
|
|
@ -527,13 +527,18 @@ class User < ActiveRecord::Base
|
||||||
created_at > 1.day.ago
|
created_at > 1.day.ago
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_avatar(avatar)
|
def upload_avatar(upload)
|
||||||
self.uploaded_avatar_template = nil
|
self.uploaded_avatar_template = nil
|
||||||
self.uploaded_avatar = avatar
|
self.uploaded_avatar = upload
|
||||||
self.use_uploaded_avatar = true
|
self.use_uploaded_avatar = true
|
||||||
self.save!
|
self.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def upload_profile_background(upload)
|
||||||
|
self.profile_background = upload.url
|
||||||
|
self.save!
|
||||||
|
end
|
||||||
|
|
||||||
def generate_api_key(created_by)
|
def generate_api_key(created_by)
|
||||||
if api_key.present?
|
if api_key.present?
|
||||||
api_key.regenerate!(created_by)
|
api_key.regenerate!(created_by)
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
# For converting urls to files
|
|
||||||
class UriAdapter
|
|
||||||
|
|
||||||
attr_reader :target, :content, :tempfile, :original_filename
|
|
||||||
|
|
||||||
def initialize(target)
|
|
||||||
raise Discourse::InvalidParameters unless target =~ /^https?:\/\//
|
|
||||||
|
|
||||||
@target = Addressable::URI.parse(target)
|
|
||||||
@original_filename = ::File.basename(@target.path)
|
|
||||||
@content = download_content
|
|
||||||
@tempfile = TempfileFactory.new.generate(@original_filename)
|
|
||||||
end
|
|
||||||
|
|
||||||
def download_content
|
|
||||||
open(target.normalize)
|
|
||||||
end
|
|
||||||
|
|
||||||
def copy_to_tempfile(src)
|
|
||||||
while data = src.read(16.kilobytes)
|
|
||||||
tempfile.write(data)
|
|
||||||
end
|
|
||||||
src.close
|
|
||||||
tempfile.rewind
|
|
||||||
tempfile
|
|
||||||
end
|
|
||||||
|
|
||||||
def file_size
|
|
||||||
content.size
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_uploaded_file
|
|
||||||
return if SiteSetting.max_image_size_kb.kilobytes < file_size
|
|
||||||
|
|
||||||
copy_to_tempfile(content)
|
|
||||||
content_type = content.content_type if content.respond_to?(:content_type)
|
|
||||||
content_type ||= "text/html"
|
|
||||||
|
|
||||||
ActionDispatch::Http::UploadedFile.new( tempfile: tempfile,
|
|
||||||
filename: original_filename,
|
|
||||||
type: content_type
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# From https://github.com/thoughtbot/paperclip/blob/master/lib/paperclip/tempfile_factory.rb
|
|
||||||
class TempfileFactory
|
|
||||||
ILLEGAL_FILENAME_CHARACTERS = /^~/
|
|
||||||
|
|
||||||
def generate(name)
|
|
||||||
@name = name
|
|
||||||
file = Tempfile.new([basename, extension])
|
|
||||||
file.binmode
|
|
||||||
file
|
|
||||||
end
|
|
||||||
|
|
||||||
def extension
|
|
||||||
File.extname(@name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def basename
|
|
||||||
File.basename(@name, extension).gsub(ILLEGAL_FILENAME_CHARACTERS, '_')
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1444,6 +1444,7 @@ en:
|
||||||
edit_reason: "We have downloaded copies of the remote images"
|
edit_reason: "We have downloaded copies of the remote images"
|
||||||
unauthorized: "Sorry, the file you are trying to upload is not authorized (authorized extensions: %{authorized_extensions})."
|
unauthorized: "Sorry, the file you are trying to upload is not authorized (authorized extensions: %{authorized_extensions})."
|
||||||
pasted_image_filename: "Pasted image"
|
pasted_image_filename: "Pasted image"
|
||||||
|
store_failure: "Failed to store upload #%{upload_id} for user #%{user_id}."
|
||||||
attachments:
|
attachments:
|
||||||
too_large: "Sorry, the file you are trying to upload is too big (maximum size is %{max_size_kb}%kb)."
|
too_large: "Sorry, the file you are trying to upload is too big (maximum size is %{max_size_kb}%kb)."
|
||||||
images:
|
images:
|
||||||
|
|
|
@ -187,7 +187,7 @@ Discourse::Application.routes.draw do
|
||||||
get "users/:username/preferences/username" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT}
|
get "users/:username/preferences/username" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
put "users/:username/preferences/username" => "users#username", constraints: {username: USERNAME_ROUTE_FORMAT}
|
put "users/:username/preferences/username" => "users#username", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
get "users/:username/avatar(/:size)" => "users#avatar", constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE
|
get "users/:username/avatar(/:size)" => "users#avatar", constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE
|
||||||
post "users/:username/preferences/avatar" => "users#upload_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
|
post "users/:username/preferences/avatar" => "users#upload_avatar", constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE
|
||||||
post "users/:username/preferences/user_image" => "users#upload_user_image", constraints: {username: USERNAME_ROUTE_FORMAT}
|
post "users/:username/preferences/user_image" => "users#upload_user_image", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
put "users/:username/preferences/avatar/toggle" => "users#toggle_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
|
put "users/:username/preferences/avatar/toggle" => "users#toggle_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
put "users/:username/preferences/profile_background/clear" => "users#clear_profile_background", constraints: {username: USERNAME_ROUTE_FORMAT}
|
put "users/:username/preferences/profile_background/clear" => "users#clear_profile_background", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
|
|
|
@ -1,43 +1,23 @@
|
||||||
|
require_dependency "file_helper"
|
||||||
|
|
||||||
class AvatarUploadService
|
class AvatarUploadService
|
||||||
|
|
||||||
attr_accessor :source
|
attr_accessor :source
|
||||||
attr_reader :filesize, :file
|
attr_reader :filesize, :filename, :file
|
||||||
|
|
||||||
def initialize(file, source)
|
def initialize(file, source)
|
||||||
@source = source
|
@source = source
|
||||||
@file , @filesize = construct(file)
|
@file, @filename, @filesize = construct(file)
|
||||||
end
|
end
|
||||||
|
|
||||||
def construct(file)
|
def construct(file)
|
||||||
case source
|
case source
|
||||||
when :url
|
when :url
|
||||||
build_from_url(file)
|
tmp = FileHelper.download(file, SiteSetting.max_image_size_kb.kilobytes, "discourse-avatar")
|
||||||
|
[tmp, File.basename(URI.parse(file).path), File.size(tmp)]
|
||||||
when :image
|
when :image
|
||||||
[file, File.size(file.tempfile)]
|
[file.tempfile, file.original_filename, File.size(file.tempfile)]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def build_from_url(url)
|
|
||||||
temp = ::UriAdapter.new(url)
|
|
||||||
return temp.build_uploaded_file, temp.file_size
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
class AvatarUploadPolicy
|
|
||||||
|
|
||||||
def initialize(avatar)
|
|
||||||
@avatar = avatar
|
|
||||||
end
|
|
||||||
|
|
||||||
def max_size_kb
|
|
||||||
SiteSetting.max_image_size_kb.kilobytes
|
|
||||||
end
|
|
||||||
|
|
||||||
def too_big?
|
|
||||||
@avatar.filesize > max_size_kb
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,8 +3,11 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
module Email
|
module Email
|
||||||
|
|
||||||
class Receiver
|
class Receiver
|
||||||
|
|
||||||
|
include ActionView::Helpers::NumberHelper
|
||||||
|
|
||||||
class ProcessingError < StandardError; end
|
class ProcessingError < StandardError; end
|
||||||
class EmailUnparsableError < ProcessingError; end
|
class EmailUnparsableError < ProcessingError; end
|
||||||
class EmptyEmailError < ProcessingError; end
|
class EmptyEmailError < ProcessingError; end
|
||||||
|
@ -18,28 +21,11 @@ module Email
|
||||||
@raw = raw
|
@raw = raw
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_in_email?
|
|
||||||
@allow_strangers = false
|
|
||||||
if SiteSetting.email_in and SiteSetting.email_in_address == @message.to.first
|
|
||||||
@category_id = SiteSetting.email_in_category.to_i
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
category = Category.find_by_email(@message.to.first)
|
|
||||||
return false if not category
|
|
||||||
|
|
||||||
@category_id = category.id
|
|
||||||
@allow_strangers = category.email_in_allow_strangers
|
|
||||||
return true
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def process
|
def process
|
||||||
raise EmptyEmailError if @raw.blank?
|
raise EmptyEmailError if @raw.blank?
|
||||||
|
|
||||||
@message = Mail.new(@raw)
|
@message = Mail.new(@raw)
|
||||||
|
|
||||||
|
|
||||||
# First remove the known discourse stuff.
|
# First remove the known discourse stuff.
|
||||||
parse_body
|
parse_body
|
||||||
raise EmptyEmailError if @body.blank?
|
raise EmptyEmailError if @body.blank?
|
||||||
|
@ -48,18 +34,17 @@ module Email
|
||||||
@body = EmailReplyParser.read(@body).visible_text.force_encoding('UTF-8')
|
@body = EmailReplyParser.read(@body).visible_text.force_encoding('UTF-8')
|
||||||
|
|
||||||
discourse_email_parser
|
discourse_email_parser
|
||||||
|
|
||||||
raise EmailUnparsableError if @body.blank?
|
raise EmailUnparsableError if @body.blank?
|
||||||
|
|
||||||
if is_in_email?
|
if is_in_email?
|
||||||
@user = User.find_by_email(@message.from.first)
|
@user = User.find_by_email(@message.from.first)
|
||||||
if @user.blank? and @allow_strangers
|
if @user.blank? && @allow_strangers
|
||||||
wrap_body_in_quote
|
wrap_body_in_quote
|
||||||
@user = Discourse.system_user
|
@user = Discourse.system_user
|
||||||
end
|
end
|
||||||
|
|
||||||
raise UserNotFoundError if @user.blank?
|
raise UserNotFoundError if @user.blank?
|
||||||
raise UserNotSufficientTrustLevelError.new @user if not @user.has_trust_level?(TrustLevel.levels[SiteSetting.email_in_min_trust.to_i])
|
raise UserNotSufficientTrustLevelError.new @user unless @user.has_trust_level?(TrustLevel.levels[SiteSetting.email_in_min_trust.to_i])
|
||||||
|
|
||||||
create_new_topic
|
create_new_topic
|
||||||
else
|
else
|
||||||
|
@ -81,12 +66,6 @@ module Email
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def wrap_body_in_quote
|
|
||||||
@body = "[quote=\"#{@message.from.first}\"]
|
|
||||||
#{@body}
|
|
||||||
[/quote]"
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_body
|
def parse_body
|
||||||
html = nil
|
html = nil
|
||||||
|
|
||||||
|
@ -102,7 +81,7 @@ module Email
|
||||||
|
|
||||||
if @message.content_type =~ /text\/html/
|
if @message.content_type =~ /text\/html/
|
||||||
if defined? @message.charset
|
if defined? @message.charset
|
||||||
html = @message.body.decoded.force_encoding(@message.charset).encode("UTF-8").to_s
|
html = @message.body.decoded.force_encoding(@message.charset).encode("UTF-8").to_s
|
||||||
else
|
else
|
||||||
html = @message.body.to_s
|
html = @message.body.to_s
|
||||||
end
|
end
|
||||||
|
@ -127,12 +106,11 @@ module Email
|
||||||
# If we have an HTML message, strip the markup
|
# If we have an HTML message, strip the markup
|
||||||
doc = Nokogiri::HTML(html)
|
doc = Nokogiri::HTML(html)
|
||||||
|
|
||||||
# Blackberry is annoying in that it only provides HTML. We can easily
|
# Blackberry is annoying in that it only provides HTML. We can easily extract it though
|
||||||
# extract it though
|
|
||||||
content = doc.at("#BB10_response_div")
|
content = doc.at("#BB10_response_div")
|
||||||
return content.text if content.present?
|
return content.text if content.present?
|
||||||
|
|
||||||
return doc.xpath("//text()").text
|
doc.xpath("//text()").text
|
||||||
end
|
end
|
||||||
|
|
||||||
def discourse_email_parser
|
def discourse_email_parser
|
||||||
|
@ -154,35 +132,91 @@ module Email
|
||||||
@body.strip!
|
@body.strip!
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_reply
|
def is_in_email?
|
||||||
# Try to post the body as a reply
|
@allow_strangers = false
|
||||||
creator = PostCreator.new(email_log.user,
|
|
||||||
raw: @body,
|
|
||||||
topic_id: @email_log.topic_id,
|
|
||||||
reply_to_post_number: @email_log.post.post_number,
|
|
||||||
cooking_options: {traditional_markdown_linebreaks: true})
|
|
||||||
|
|
||||||
creator.create
|
if SiteSetting.email_in && SiteSetting.email_in_address == @message.to.first
|
||||||
|
@category_id = SiteSetting.email_in_category.to_i
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
category = Category.find_by_email(@message.to.first)
|
||||||
|
return false unless category
|
||||||
|
|
||||||
|
@category_id = category.id
|
||||||
|
@allow_strangers = category.email_in_allow_strangers
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def wrap_body_in_quote
|
||||||
|
@body = "[quote=\"#{@message.from.first}\"]
|
||||||
|
#{@body}
|
||||||
|
[/quote]"
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_reply
|
||||||
|
create_post_with_attachments(email_log.user, @body, @email_log.topic_id, @email_log.post.post_number)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_new_topic
|
def create_new_topic
|
||||||
# Try to post the body as a reply
|
topic = TopicCreator.new(
|
||||||
topic_creator = TopicCreator.new(@user,
|
@user,
|
||||||
Guardian.new(@user),
|
Guardian.new(@user),
|
||||||
category: @category_id,
|
category: @category_id,
|
||||||
title: @message.subject)
|
title: @message.subject,
|
||||||
|
).create
|
||||||
|
|
||||||
topic = topic_creator.create
|
post = create_post_with_attachments(@user, @body, topic.id)
|
||||||
post_creator = PostCreator.new(@user,
|
|
||||||
raw: @body,
|
|
||||||
topic_id: topic.id,
|
|
||||||
cooking_options: {traditional_markdown_linebreaks: true})
|
|
||||||
|
|
||||||
post_creator.create
|
EmailLog.create(
|
||||||
EmailLog.create(email_type: "topic_via_incoming_email",
|
email_type: "topic_via_incoming_email",
|
||||||
to_address: @message.to.first,
|
to_address: @message.to.first,
|
||||||
topic_id: topic.id, user_id: @user.id)
|
topic_id: topic.id,
|
||||||
topic
|
user_id: @user.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
post
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_post_with_attachments(user, raw, topic_id, reply_to_post_number=nil)
|
||||||
|
options = {
|
||||||
|
raw: raw,
|
||||||
|
topic_id: topic_id,
|
||||||
|
cooking_options: { traditional_markdown_linebreaks: true },
|
||||||
|
}
|
||||||
|
options[:reply_to_post_number] = reply_to_post_number if reply_to_post_number
|
||||||
|
|
||||||
|
# deal with attachments
|
||||||
|
@message.attachments.each do |attachment|
|
||||||
|
tmp = Tempfile.new("discourse-email-attachment")
|
||||||
|
begin
|
||||||
|
# read attachment
|
||||||
|
File.open(tmp.path, "w+b") { |f| f.write attachment.body.decoded }
|
||||||
|
# create the upload for the user
|
||||||
|
upload = Upload.create_for(user.id, tmp, attachment.filename, File.size(tmp))
|
||||||
|
if upload && upload.errors.empty?
|
||||||
|
# TODO: should use the same code as the client to insert attachments
|
||||||
|
raw << "\n#{attachment_markdown(upload)}\n"
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
tmp.close!
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
create_post(user, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def attachment_markdown(upload)if FileHelper.is_image?(upload.original_filename)
|
||||||
|
"<img src='#{upload.url}' width='#{upload.width}' height='#{upload.height}'>"
|
||||||
|
else
|
||||||
|
"<a class='attachment' href='#{upload.url}'>#{upload.original_filename}</a> (#{number_to_human_size(upload.filesize)})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_post(user, options)
|
||||||
|
PostCreator.new(user, options).create
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
class FileHelper
|
||||||
|
|
||||||
|
def self.is_image?(filename)
|
||||||
|
filename =~ images_regexp
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.download(url, max_file_size, tmp_file_name)
|
||||||
|
raise Discourse::InvalidParameters unless url =~ /^https?:\/\//
|
||||||
|
|
||||||
|
extension = File.extname(URI.parse(url).path)
|
||||||
|
tmp = Tempfile.new([tmp_file_name, extension])
|
||||||
|
|
||||||
|
File.open(tmp.path, "wb") do |f|
|
||||||
|
avatar = open(url, "rb", read_timeout: 5)
|
||||||
|
while f.size <= max_file_size && data = avatar.read(max_file_size)
|
||||||
|
f.write(data)
|
||||||
|
end
|
||||||
|
avatar.close!
|
||||||
|
end
|
||||||
|
|
||||||
|
tmp
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def self.images
|
||||||
|
@@images ||= Set.new ["jpg", "jpeg", "png", "gif", "tif", "tiff", "bmp"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.images_regexp
|
||||||
|
@@images_regexp ||= /\.(#{images.to_a.join("|").gsub(".", "\.")})$/i
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -62,8 +62,8 @@ module FileStore
|
||||||
private
|
private
|
||||||
|
|
||||||
def get_path_for_upload(file, upload)
|
def get_path_for_upload(file, upload)
|
||||||
unique_sha1 = Digest::SHA1.hexdigest("#{Time.now.to_s}#{file.original_filename}")[0..15]
|
unique_sha1 = Digest::SHA1.hexdigest("#{Time.now.to_s}#{upload.original_filename}")[0..15]
|
||||||
extension = File.extname(file.original_filename)
|
extension = File.extname(upload.original_filename)
|
||||||
clean_name = "#{unique_sha1}#{extension}"
|
clean_name = "#{unique_sha1}#{extension}"
|
||||||
# path
|
# path
|
||||||
"#{relative_base_url}/#{upload.id}/#{clean_name}"
|
"#{relative_base_url}/#{upload.id}/#{clean_name}"
|
||||||
|
|
|
@ -10,8 +10,7 @@ module PrettyText
|
||||||
def t(key, opts)
|
def t(key, opts)
|
||||||
str = I18n.t("js." + key)
|
str = I18n.t("js." + key)
|
||||||
if opts
|
if opts
|
||||||
# TODO: server localisation has no parity with client
|
# TODO: server localisation has no parity with client should be fixed
|
||||||
# should be fixed
|
|
||||||
str = str.dup
|
str = str.dup
|
||||||
opts.each do |k,v|
|
opts.each do |k,v|
|
||||||
str.gsub!("{{#{k}}}", v)
|
str.gsub!("{{#{k}}}", v)
|
||||||
|
@ -31,7 +30,7 @@ module PrettyText
|
||||||
def is_username_valid(username)
|
def is_username_valid(username)
|
||||||
return false unless username
|
return false unless username
|
||||||
username = username.downcase
|
username = username.downcase
|
||||||
return User.exec_sql('select 1 from users where username_lower = ?', username).values.length == 1
|
return User.exec_sql('SELECT 1 FROM users WHERE username_lower = ?', username).values.length == 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -53,12 +52,13 @@ module PrettyText
|
||||||
ctx["helpers"] = Helpers.new
|
ctx["helpers"] = Helpers.new
|
||||||
|
|
||||||
ctx_load(ctx,
|
ctx_load(ctx,
|
||||||
"vendor/assets/javascripts/md5.js",
|
"vendor/assets/javascripts/md5.js",
|
||||||
"vendor/assets/javascripts/lodash.js",
|
"vendor/assets/javascripts/lodash.js",
|
||||||
"vendor/assets/javascripts/Markdown.Converter.js",
|
"vendor/assets/javascripts/Markdown.Converter.js",
|
||||||
"lib/headless-ember.js",
|
"lib/headless-ember.js",
|
||||||
"vendor/assets/javascripts/rsvp.js",
|
"vendor/assets/javascripts/rsvp.js",
|
||||||
Rails.configuration.ember.handlebars_location)
|
Rails.configuration.ember.handlebars_location
|
||||||
|
)
|
||||||
|
|
||||||
ctx.eval("var Discourse = {}; Discourse.SiteSettings = {};")
|
ctx.eval("var Discourse = {}; Discourse.SiteSettings = {};")
|
||||||
ctx.eval("var window = {}; window.devicePixelRatio = 2;") # hack to make code think stuff is retina
|
ctx.eval("var window = {}; window.devicePixelRatio = 2;") # hack to make code think stuff is retina
|
||||||
|
@ -67,12 +67,13 @@ module PrettyText
|
||||||
decorate_context(ctx)
|
decorate_context(ctx)
|
||||||
|
|
||||||
ctx_load(ctx,
|
ctx_load(ctx,
|
||||||
"vendor/assets/javascripts/better_markdown.js",
|
"vendor/assets/javascripts/better_markdown.js",
|
||||||
"app/assets/javascripts/defer/html-sanitizer-bundle.js",
|
"app/assets/javascripts/defer/html-sanitizer-bundle.js",
|
||||||
"app/assets/javascripts/discourse/dialects/dialect.js",
|
"app/assets/javascripts/discourse/dialects/dialect.js",
|
||||||
"app/assets/javascripts/discourse/lib/utilities.js",
|
"app/assets/javascripts/discourse/lib/utilities.js",
|
||||||
"app/assets/javascripts/discourse/lib/html.js",
|
"app/assets/javascripts/discourse/lib/html.js",
|
||||||
"app/assets/javascripts/discourse/lib/markdown.js")
|
"app/assets/javascripts/discourse/lib/markdown.js"
|
||||||
|
)
|
||||||
|
|
||||||
Dir["#{Rails.root}/app/assets/javascripts/discourse/dialects/**.js"].each do |dialect|
|
Dir["#{Rails.root}/app/assets/javascripts/discourse/dialects/**.js"].each do |dialect|
|
||||||
unless dialect =~ /\/dialect\.js$/
|
unless dialect =~ /\/dialect\.js$/
|
||||||
|
@ -111,6 +112,7 @@ module PrettyText
|
||||||
return @ctx if @ctx
|
return @ctx if @ctx
|
||||||
@ctx = create_new_context
|
@ctx = create_new_context
|
||||||
end
|
end
|
||||||
|
|
||||||
@ctx
|
@ctx
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
require_dependency "file_helper"
|
||||||
|
|
||||||
|
module Validators; end
|
||||||
|
|
||||||
|
class Validators::UploadValidator < ActiveModel::Validator
|
||||||
|
|
||||||
|
def validate(upload)
|
||||||
|
extension = File.extname(upload.original_filename)[1..-1]
|
||||||
|
|
||||||
|
if is_authorized?(upload, extension)
|
||||||
|
if FileHelper.is_image?(upload.original_filename)
|
||||||
|
authorized_image_extension(upload, extension)
|
||||||
|
maximum_image_file_size(upload)
|
||||||
|
else
|
||||||
|
authorized_attachment_extension(upload, extension)
|
||||||
|
maximum_attachment_file_size(upload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_authorized?(upload, extension)
|
||||||
|
authorized_extensions(upload, extension, authorized_uploads)
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorized_image_extension(upload, extension)
|
||||||
|
authorized_extensions(upload, extension, authorized_images)
|
||||||
|
end
|
||||||
|
|
||||||
|
def maximum_image_file_size(upload)
|
||||||
|
maximum_file_size(upload, "image")
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorized_attachment_extension(upload, extension)
|
||||||
|
authorized_extensions(upload, extension, authorized_attachments)
|
||||||
|
end
|
||||||
|
|
||||||
|
def maximum_attachment_file_size(upload)
|
||||||
|
maximum_file_size(upload, "attachment")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def authorized_uploads
|
||||||
|
authorized_uploads = Set.new
|
||||||
|
|
||||||
|
SiteSetting.authorized_extensions
|
||||||
|
.tr(" ", "")
|
||||||
|
.split("|")
|
||||||
|
.each do |extension|
|
||||||
|
authorized_uploads << (extension.start_with?(".") ? extension[1..-1] : extension)
|
||||||
|
end
|
||||||
|
|
||||||
|
authorized_uploads
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorized_images
|
||||||
|
@authorized_images ||= (authorized_uploads & FileHelper.images)
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorized_attachments
|
||||||
|
@authorized_attachments ||= (authorized_uploads - FileHelper.images)
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorized_extensions(upload, extension, extensions)
|
||||||
|
unless authorized = extensions.include?(extension)
|
||||||
|
message = I18n.t("upload.unauthorized", authorized_extensions: extensions.to_a.join(", "))
|
||||||
|
upload.errors.add(:original_filename, message)
|
||||||
|
end
|
||||||
|
authorized
|
||||||
|
end
|
||||||
|
|
||||||
|
def maximum_file_size(upload, type)
|
||||||
|
max_size_kb = SiteSetting.send("max_#{type}_size_kb").kilobytes
|
||||||
|
if upload.filesize > max_size_kb
|
||||||
|
message = I18n.t("upload.#{type}s.too_large", max_size_kb: max_size_kb)
|
||||||
|
upload.errors.add(:filesize, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -2,11 +2,11 @@ require "spec_helper"
|
||||||
require "avatar_upload_service"
|
require "avatar_upload_service"
|
||||||
|
|
||||||
describe AvatarUploadService do
|
describe AvatarUploadService do
|
||||||
|
|
||||||
|
let(:logo) { File.new("#{Rails.root}/spec/fixtures/images/logo.png") }
|
||||||
|
|
||||||
let(:file) do
|
let(:file) do
|
||||||
ActionDispatch::Http::UploadedFile.new({
|
ActionDispatch::Http::UploadedFile.new({ filename: 'logo.png', tempfile: logo })
|
||||||
filename: 'logo.png',
|
|
||||||
tempfile: File.new("#{Rails.root}/spec/fixtures/images/logo.png")
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:url) { "http://cdn.discourse.org/assets/logo.png" }
|
let(:url) { "http://cdn.discourse.org/assets/logo.png" }
|
||||||
|
@ -16,49 +16,41 @@ describe AvatarUploadService do
|
||||||
let(:avatar_file) { AvatarUploadService.new(file, :image) }
|
let(:avatar_file) { AvatarUploadService.new(file, :image) }
|
||||||
|
|
||||||
it "should have a filesize" do
|
it "should have a filesize" do
|
||||||
expect(avatar_file.filesize).to eq(2290)
|
avatar_file.filesize.should == 2290
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have a filename" do
|
||||||
|
avatar_file.filename.should == "logo.png"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have a file" do
|
||||||
|
avatar_file.file.should == file.tempfile
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have a source as 'image'" do
|
it "should have a source as 'image'" do
|
||||||
expect(avatar_file.source).to eq(:image)
|
avatar_file.source.should == :image
|
||||||
end
|
|
||||||
|
|
||||||
it "is an instance of File class" do
|
|
||||||
file = avatar_file.file
|
|
||||||
expect(file.tempfile).to be_instance_of File
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the file object built from File" do
|
|
||||||
file = avatar_file.file
|
|
||||||
file.should be_instance_of(ActionDispatch::Http::UploadedFile)
|
|
||||||
file.original_filename.should == "logo.png"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when file is in the form of a URL" do
|
context "when file is in the form of a URL" do
|
||||||
let(:avatar_file) { AvatarUploadService.new(url, :url) }
|
let(:avatar_file) { AvatarUploadService.new(url, :url) }
|
||||||
|
|
||||||
before :each do
|
before { FileHelper.stubs(:download).returns(logo) }
|
||||||
UriAdapter.any_instance.stubs(:open).returns StringIO.new(fixture_file("images/logo.png"))
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have a filesize" do
|
it "should have a filesize" do
|
||||||
expect(avatar_file.filesize).to eq(2290)
|
avatar_file.filesize.should == 2290
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have a filename" do
|
||||||
|
avatar_file.filename.should == "logo.png"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should have a file" do
|
||||||
|
avatar_file.file.should == logo
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should have a source as 'url'" do
|
it "should have a source as 'url'" do
|
||||||
expect(avatar_file.source).to eq(:url)
|
avatar_file.source.should == :url
|
||||||
end
|
|
||||||
|
|
||||||
it "is an instance of Tempfile class" do
|
|
||||||
file = avatar_file.file
|
|
||||||
expect(file.tempfile).to be_instance_of Tempfile
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns the file object built from URL" do
|
|
||||||
file = avatar_file.file
|
|
||||||
file.should be_instance_of(ActionDispatch::Http::UploadedFile)
|
|
||||||
file.original_filename.should == "logo.png"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -94,8 +94,8 @@ describe CookedPostProcessor do
|
||||||
|
|
||||||
it "generates overlay information" do
|
it "generates overlay information" do
|
||||||
cpp.post_process_images
|
cpp.post_process_images
|
||||||
cpp.html.should match_html '<div class="lightbox-wrapper"><a href="/uploads/default/1/1234567890123456.jpg" class="lightbox" title="uploaded.jpg"><img src="/uploads/default/_optimized/da3/9a3/ee5e6b4b0d_690x1380.jpg" width="690" height="1380"><div class="meta">
|
cpp.html.should match_html '<div class="lightbox-wrapper"><a href="/uploads/default/1/1234567890123456.jpg" class="lightbox" title="logo.png"><img src="/uploads/default/_optimized/da3/9a3/ee5e6b4b0d_690x1380.png" width="690" height="1380"><div class="meta">
|
||||||
<span class="filename">uploaded.jpg</span><span class="informations">1000x2000 1.21 KB</span><span class="expand"></span>
|
<span class="filename">logo.png</span><span class="informations">1000x2000 1.21 KB</span><span class="expand"></span>
|
||||||
</div></a></div>'
|
</div></a></div>'
|
||||||
cpp.should be_dirty
|
cpp.should be_dirty
|
||||||
end
|
end
|
||||||
|
|
|
@ -178,15 +178,18 @@ greatest show ever created. Everyone should watch it.
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "email with attachments" do
|
describe "email with attachments" do
|
||||||
|
|
||||||
it "can find the message and create a post" do
|
it "can find the message and create a post" do
|
||||||
|
user.id = -1
|
||||||
User.stubs(:find_by_email).returns(user)
|
User.stubs(:find_by_email).returns(user)
|
||||||
EmailLog.stubs(:for).returns(email_log)
|
EmailLog.stubs(:for).returns(email_log)
|
||||||
attachment_email = File.read("#{Rails.root}/spec/fixtures/emails/attachment.eml")
|
attachment_email = File.read("#{Rails.root}/spec/fixtures/emails/attachment.eml")
|
||||||
r = Email::Receiver.new(attachment_email)
|
r = Email::Receiver.new(attachment_email)
|
||||||
r.expects(:create_reply)
|
r.expects(:create_post)
|
||||||
expect { r.process }.to_not raise_error
|
expect { r.process }.to_not raise_error
|
||||||
expect(r.body).to eq("here is an image attachment")
|
expect(r.body).to match(/here is an image attachment\n<img src='\/uploads\/default\/\d+\/\w{16}\.png' width='289' height='126'>\n/)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,12 +6,7 @@ describe FileStore::LocalStore do
|
||||||
let(:store) { FileStore::LocalStore.new }
|
let(:store) { FileStore::LocalStore.new }
|
||||||
|
|
||||||
let(:upload) { build(:upload) }
|
let(:upload) { build(:upload) }
|
||||||
let(:uploaded_file) do
|
let(:uploaded_file) { File.new("#{Rails.root}/spec/fixtures/images/logo.png") }
|
||||||
ActionDispatch::Http::UploadedFile.new({
|
|
||||||
filename: 'logo.png',
|
|
||||||
tempfile: File.new("#{Rails.root}/spec/fixtures/images/logo.png")
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:optimized_image) { build(:optimized_image) }
|
let(:optimized_image) { build(:optimized_image) }
|
||||||
let(:avatar) { build(:upload) }
|
let(:avatar) { build(:upload) }
|
||||||
|
@ -40,7 +35,7 @@ describe FileStore::LocalStore do
|
||||||
|
|
||||||
it "returns a relative url" do
|
it "returns a relative url" do
|
||||||
store.expects(:copy_file)
|
store.expects(:copy_file)
|
||||||
store.store_avatar({}, upload, 100).should == "/uploads/default/avatars/e9d/71f/5ee7c92d6d/100.jpg"
|
store.store_avatar({}, upload, 100).should == "/uploads/default/avatars/e9d/71f/5ee7c92d6d/100.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -125,7 +120,7 @@ describe FileStore::LocalStore do
|
||||||
describe ".avatar_template" do
|
describe ".avatar_template" do
|
||||||
|
|
||||||
it "is present" do
|
it "is present" do
|
||||||
store.avatar_template(avatar).should == "/uploads/default/avatars/e9d/71f/5ee7c92d6d/{size}.jpg"
|
store.avatar_template(avatar).should == "/uploads/default/avatars/e9d/71f/5ee7c92d6d/{size}.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -64,7 +64,7 @@ describe FileStore::S3Store do
|
||||||
|
|
||||||
it "returns an absolute schemaless url" do
|
it "returns an absolute schemaless url" do
|
||||||
avatar.stubs(:id).returns(42)
|
avatar.stubs(:id).returns(42)
|
||||||
store.store_avatar(avatar_file, avatar, 100).should == "//s3_upload_bucket.s3.amazonaws.com/avatars/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98/100.jpg"
|
store.store_avatar(avatar_file, avatar, 100).should == "//s3_upload_bucket.s3.amazonaws.com/avatars/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98/100.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -116,7 +116,7 @@ describe FileStore::S3Store do
|
||||||
describe ".avatar_template" do
|
describe ".avatar_template" do
|
||||||
|
|
||||||
it "is present" do
|
it "is present" do
|
||||||
store.avatar_template(avatar).should == "//s3_upload_bucket.s3.amazonaws.com/avatars/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98/{size}.jpg"
|
store.avatar_template(avatar).should == "//s3_upload_bucket.s3.amazonaws.com/avatars/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98/{size}.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -57,7 +57,7 @@ describe UploadsController do
|
||||||
|
|
||||||
it 'rejects the upload' do
|
it 'rejects the upload' do
|
||||||
xhr :post, :create, file: text_file
|
xhr :post, :create, file: text_file
|
||||||
response.status.should eq 413
|
response.status.should eq 422
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -70,7 +70,7 @@ describe UploadsController do
|
||||||
|
|
||||||
it 'rejects the upload' do
|
it 'rejects the upload' do
|
||||||
xhr :post, :create, file: text_file
|
xhr :post, :create, file: text_file
|
||||||
response.status.should eq 415
|
response.status.should eq 422
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -112,9 +112,12 @@ describe UploadsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'uses send_file' do
|
it 'uses send_file' do
|
||||||
Fabricate(:attachment)
|
upload = build(:upload)
|
||||||
|
Upload.expects(:where).with(id: 42, url: "/uploads/default/42/66b3ed1503efc936.zip").returns([upload])
|
||||||
|
|
||||||
controller.stubs(:render)
|
controller.stubs(:render)
|
||||||
controller.expects(:send_file)
|
controller.expects(:send_file)
|
||||||
|
|
||||||
get :show, site: "default", id: 42, sha: "66b3ed1503efc936", extension: "zip"
|
get :show, site: "default", id: 42, sha: "66b3ed1503efc936", extension: "zip"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1099,25 +1099,23 @@ describe UsersController do
|
||||||
it 'raises an error when not logged in' do
|
it 'raises an error when not logged in' do
|
||||||
lambda { xhr :put, :upload_user_image, username: 'asdf' }.should raise_error(Discourse::NotLoggedIn)
|
lambda { xhr :put, :upload_user_image, username: 'asdf' }.should raise_error(Discourse::NotLoggedIn)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
context 'while logged in' do
|
context 'while logged in' do
|
||||||
|
|
||||||
let!(:user) { log_in }
|
let!(:user) { log_in }
|
||||||
|
|
||||||
|
let(:logo) { File.new("#{Rails.root}/spec/fixtures/images/logo.png") }
|
||||||
|
|
||||||
let(:user_image) do
|
let(:user_image) do
|
||||||
ActionDispatch::Http::UploadedFile.new({
|
ActionDispatch::Http::UploadedFile.new({ filename: 'logo.png', tempfile: logo })
|
||||||
filename: 'logo.png',
|
|
||||||
tempfile: File.new("#{Rails.root}/spec/fixtures/images/logo.png")
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'raises an error without a user_image_type param' do
|
it 'raises an error without a user_image_type param' do
|
||||||
lambda { xhr :put, :upload_user_image, username: user.username }.should raise_error(ActionController::ParameterMissing)
|
lambda { xhr :put, :upload_user_image, username: user.username }.should raise_error(ActionController::ParameterMissing)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with uploaded file" do
|
describe "with uploaded file" do
|
||||||
|
|
||||||
it 'raises an error when you don\'t have permission to upload an user image' do
|
it 'raises an error when you don\'t have permission to upload an user image' do
|
||||||
Guardian.any_instance.expects(:can_edit?).with(user).returns(false)
|
Guardian.any_instance.expects(:can_edit?).with(user).returns(false)
|
||||||
xhr :post, :upload_user_image, username: user.username, user_image_type: "avatar"
|
xhr :post, :upload_user_image, username: user.username, user_image_type: "avatar"
|
||||||
|
@ -1125,19 +1123,14 @@ describe UsersController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects large images' do
|
it 'rejects large images' do
|
||||||
AvatarUploadPolicy.any_instance.stubs(:too_big?).returns(true)
|
SiteSetting.stubs(:max_image_size_kb).returns(1)
|
||||||
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "avatar"
|
|
||||||
response.status.should eq 413
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'rejects unauthorized images' do
|
|
||||||
SiteSetting.stubs(:authorized_image?).returns(false)
|
|
||||||
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "avatar"
|
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "avatar"
|
||||||
response.status.should eq 422
|
response.status.should eq 422
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects requests with unknown user_image_type' do
|
it 'rejects unauthorized images' do
|
||||||
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "asdf"
|
SiteSetting.stubs(:authorized_extensions).returns(".txt")
|
||||||
|
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "avatar"
|
||||||
response.status.should eq 422
|
response.status.should eq 422
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1156,54 +1149,46 @@ describe UsersController do
|
||||||
user.use_uploaded_avatar.should == true
|
user.use_uploaded_avatar.should == true
|
||||||
# returns the url, width and height of the uploaded image
|
# returns the url, width and height of the uploaded image
|
||||||
json = JSON.parse(response.body)
|
json = JSON.parse(response.body)
|
||||||
json['url'].should == "/uploads/default/1/1234567890123456.jpg"
|
json['url'].should == "/uploads/default/1/1234567890123456.png"
|
||||||
json['width'].should == 100
|
json['width'].should == 100
|
||||||
json['height'].should == 200
|
json['height'].should == 200
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'is successful for profile backgrounds' do
|
it 'is successful for profile backgrounds' do
|
||||||
upload = Fabricate(:upload)
|
upload = Fabricate(:upload)
|
||||||
Upload.expects(:create_for).returns(upload)
|
Upload.expects(:create_for).returns(upload)
|
||||||
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "profile_background"
|
xhr :post, :upload_user_image, username: user.username, file: user_image, user_image_type: "profile_background"
|
||||||
user.reload
|
user.reload
|
||||||
|
|
||||||
user.profile_background.should == "/uploads/default/1/1234567890123456.jpg"
|
user.profile_background.should == "/uploads/default/1/1234567890123456.png"
|
||||||
|
|
||||||
# returns the url, width and height of the uploaded image
|
# returns the url, width and height of the uploaded image
|
||||||
json = JSON.parse(response.body)
|
json = JSON.parse(response.body)
|
||||||
json['url'].should == "/uploads/default/1/1234567890123456.jpg"
|
json['url'].should == "/uploads/default/1/1234567890123456.png"
|
||||||
json['width'].should == 100
|
json['width'].should == 100
|
||||||
json['height'].should == 200
|
json['height'].should == 200
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with url" do
|
describe "with url" do
|
||||||
let(:user_image_url) { "http://cdn.discourse.org/assets/logo.png" }
|
let(:user_image_url) { "http://cdn.discourse.org/assets/logo.png" }
|
||||||
|
|
||||||
before :each do
|
before { UsersController.any_instance.stubs(:is_api?).returns(true) }
|
||||||
UsersController.any_instance.stubs(:is_api?).returns(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "correct urls" do
|
describe "correct urls" do
|
||||||
before :each do
|
|
||||||
UriAdapter.any_instance.stubs(:open).returns StringIO.new(fixture_file("images/logo.png"))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'rejects large images' do
|
|
||||||
AvatarUploadPolicy.any_instance.stubs(:too_big?).returns(true)
|
|
||||||
xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "profile_background"
|
|
||||||
response.status.should eq 413
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'rejects unauthorized images' do
|
before { FileHelper.stubs(:download).returns(logo) }
|
||||||
SiteSetting.stubs(:authorized_image?).returns(false)
|
|
||||||
|
it 'rejects large images' do
|
||||||
|
SiteSetting.stubs(:max_image_size_kb).returns(1)
|
||||||
xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "profile_background"
|
xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "profile_background"
|
||||||
response.status.should eq 422
|
response.status.should eq 422
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects requests with unknown user_image_type' do
|
it 'rejects unauthorized images' do
|
||||||
xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "asdf"
|
SiteSetting.stubs(:authorized_extensions).returns(".txt")
|
||||||
|
xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "profile_background"
|
||||||
response.status.should eq 422
|
response.status.should eq 422
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1222,7 +1207,7 @@ describe UsersController do
|
||||||
user.use_uploaded_avatar.should == true
|
user.use_uploaded_avatar.should == true
|
||||||
# returns the url, width and height of the uploaded image
|
# returns the url, width and height of the uploaded image
|
||||||
json = JSON.parse(response.body)
|
json = JSON.parse(response.body)
|
||||||
json['url'].should == "/uploads/default/1/1234567890123456.jpg"
|
json['url'].should == "/uploads/default/1/1234567890123456.png"
|
||||||
json['width'].should == 100
|
json['width'].should == 100
|
||||||
json['height'].should == 200
|
json['height'].should == 200
|
||||||
end
|
end
|
||||||
|
@ -1232,11 +1217,11 @@ describe UsersController do
|
||||||
Upload.expects(:create_for).returns(upload)
|
Upload.expects(:create_for).returns(upload)
|
||||||
xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "profile_background"
|
xhr :post, :upload_user_image, username: user.username, file: user_image_url, user_image_type: "profile_background"
|
||||||
user.reload
|
user.reload
|
||||||
user.profile_background.should == "/uploads/default/1/1234567890123456.jpg"
|
user.profile_background.should == "/uploads/default/1/1234567890123456.png"
|
||||||
|
|
||||||
# returns the url, width and height of the uploaded image
|
# returns the url, width and height of the uploaded image
|
||||||
json = JSON.parse(response.body)
|
json = JSON.parse(response.body)
|
||||||
json['url'].should == "/uploads/default/1/1234567890123456.jpg"
|
json['url'].should == "/uploads/default/1/1234567890123456.png"
|
||||||
json['width'].should == 100
|
json['width'].should == 100
|
||||||
json['height'].should == 200
|
json['height'].should == 200
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
Fabricator(:upload) do
|
Fabricator(:upload) do
|
||||||
user
|
user
|
||||||
sha1 "e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98"
|
sha1 "e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98"
|
||||||
original_filename "uploaded.jpg"
|
original_filename "logo.png"
|
||||||
filesize 1234
|
filesize 1234
|
||||||
width 100
|
width 100
|
||||||
height 200
|
height 200
|
||||||
url "/uploads/default/1/1234567890123456.jpg"
|
url "/uploads/default/1/1234567890123456.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
Fabricator(:attachment, from: :upload) do
|
Fabricator(:attachment, from: :upload) do
|
||||||
|
|
|
@ -42,10 +42,10 @@ describe OptimizedImage do
|
||||||
it "works" do
|
it "works" do
|
||||||
oi = OptimizedImage.create_for(upload, 100, 200)
|
oi = OptimizedImage.create_for(upload, 100, 200)
|
||||||
oi.sha1.should == "da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
oi.sha1.should == "da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
||||||
oi.extension.should == ".jpg"
|
oi.extension.should == ".png"
|
||||||
oi.width.should == 100
|
oi.width.should == 100
|
||||||
oi.height.should == 200
|
oi.height.should == 200
|
||||||
oi.url.should == "/internally/stored/optimized/image.jpg"
|
oi.url.should == "/internally/stored/optimized/image.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -73,17 +73,17 @@ describe OptimizedImage do
|
||||||
|
|
||||||
it "downloads a copy of the original image" do
|
it "downloads a copy of the original image" do
|
||||||
Tempfile.any_instance.expects(:close!).twice
|
Tempfile.any_instance.expects(:close!).twice
|
||||||
store.expects(:download).with(upload).returns(Tempfile.new(["discourse-external", ".jpg"]))
|
store.expects(:download).with(upload).returns(Tempfile.new(["discourse-external", ".png"]))
|
||||||
OptimizedImage.create_for(upload, 100, 200)
|
OptimizedImage.create_for(upload, 100, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "works" do
|
it "works" do
|
||||||
oi = OptimizedImage.create_for(upload, 100, 200)
|
oi = OptimizedImage.create_for(upload, 100, 200)
|
||||||
oi.sha1.should == "da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
oi.sha1.should == "da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
||||||
oi.extension.should == ".jpg"
|
oi.extension.should == ".png"
|
||||||
oi.width.should == 100
|
oi.width.should == 100
|
||||||
oi.height.should == 200
|
oi.height.should == 200
|
||||||
oi.url.should == "/externally/stored/optimized/image.jpg"
|
oi.url.should == "/externally/stored/optimized/image.png"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -105,28 +105,6 @@ describe SiteSetting do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "authorized extensions" do
|
|
||||||
|
|
||||||
describe "authorized_uploads" do
|
|
||||||
|
|
||||||
it "trims spaces and leading dots" do
|
|
||||||
SiteSetting.stubs(:authorized_extensions).returns(" png | .jpeg|txt|bmp | .tar.gz")
|
|
||||||
SiteSetting.authorized_uploads.should == ["png", "jpeg", "txt", "bmp", "tar.gz"]
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "authorized_images" do
|
|
||||||
|
|
||||||
it "filters non-image out" do
|
|
||||||
SiteSetting.stubs(:authorized_extensions).returns(" png | .jpeg|txt|bmp")
|
|
||||||
SiteSetting.authorized_images.should == ["png", "jpeg", "bmp"]
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "scheme" do
|
describe "scheme" do
|
||||||
|
|
||||||
it "returns http when ssl is disabled" do
|
it "returns http when ssl is disabled" do
|
||||||
|
|
|
@ -2,7 +2,6 @@ require 'spec_helper'
|
||||||
require 'digest/sha1'
|
require 'digest/sha1'
|
||||||
|
|
||||||
describe Upload do
|
describe Upload do
|
||||||
|
|
||||||
it { should belong_to :user }
|
it { should belong_to :user }
|
||||||
|
|
||||||
it { should have_many :post_uploads }
|
it { should have_many :post_uploads }
|
||||||
|
@ -10,33 +9,22 @@ describe Upload do
|
||||||
|
|
||||||
it { should have_many :optimized_images }
|
it { should have_many :optimized_images }
|
||||||
|
|
||||||
it { should validate_presence_of :original_filename }
|
|
||||||
it { should validate_presence_of :filesize }
|
|
||||||
|
|
||||||
let(:upload) { build(:upload) }
|
let(:upload) { build(:upload) }
|
||||||
let(:thumbnail) { build(:optimized_image, upload: upload) }
|
let(:thumbnail) { build(:optimized_image, upload: upload) }
|
||||||
|
|
||||||
let(:user_id) { 1 }
|
let(:user_id) { 1 }
|
||||||
let(:url) { "http://domain.com" }
|
let(:url) { "http://domain.com" }
|
||||||
|
|
||||||
let(:image) do
|
let(:image_path) { "#{Rails.root}/spec/fixtures/images/logo.png" }
|
||||||
ActionDispatch::Http::UploadedFile.new({
|
let(:image) { File.new(image_path) }
|
||||||
filename: 'logo.png',
|
let(:image_filename) { File.basename(image_path) }
|
||||||
tempfile: File.new("#{Rails.root}/spec/fixtures/images/logo.png")
|
let(:image_filesize) { File.size(image_path) }
|
||||||
})
|
let(:image_sha1) { Digest::SHA1.file(image).hexdigest }
|
||||||
end
|
|
||||||
|
|
||||||
let(:image_sha1) { Digest::SHA1.file(image.tempfile).hexdigest }
|
let(:attachment_path) { __FILE__ }
|
||||||
let(:image_filesize) { File.size(image.tempfile) }
|
let(:attachment) { File.new(attachment_path) }
|
||||||
|
let(:attachment_filename) { File.basename(attachment_path) }
|
||||||
let(:attachment) do
|
let(:attachment_filesize) { File.size(attachment_path) }
|
||||||
ActionDispatch::Http::UploadedFile.new({
|
|
||||||
filename: File.basename(__FILE__),
|
|
||||||
tempfile: File.new(__FILE__)
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:attachment_filesize) { File.size(attachment.tempfile) }
|
|
||||||
|
|
||||||
context ".create_thumbnail!" do
|
context ".create_thumbnail!" do
|
||||||
|
|
||||||
|
@ -62,39 +50,41 @@ describe Upload do
|
||||||
|
|
||||||
it "does not create another upload if it already exists" do
|
it "does not create another upload if it already exists" do
|
||||||
Upload.expects(:where).with(sha1: image_sha1).returns([upload])
|
Upload.expects(:where).with(sha1: image_sha1).returns([upload])
|
||||||
Upload.expects(:create!).never
|
Upload.expects(:save).never
|
||||||
Upload.create_for(user_id, image, image_filesize).should == upload
|
Upload.create_for(user_id, image, image_filename, image_filesize).should == upload
|
||||||
end
|
end
|
||||||
|
|
||||||
it "computes width & height for images" do
|
it "computes width & height for images" do
|
||||||
SiteSetting.expects(:authorized_image?).returns(true)
|
|
||||||
FastImage.any_instance.expects(:size).returns([100, 200])
|
FastImage.any_instance.expects(:size).returns([100, 200])
|
||||||
ImageSizer.expects(:resize)
|
ImageSizer.expects(:resize)
|
||||||
ActionDispatch::Http::UploadedFile.any_instance.expects(:rewind)
|
image.expects(:rewind).twice
|
||||||
Upload.create_for(user_id, image, image_filesize)
|
Upload.create_for(user_id, image, image_filename, image_filesize)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not create an upload when there is an error with FastImage" do
|
it "does not create an upload when there is an error with FastImage" do
|
||||||
SiteSetting.expects(:authorized_image?).returns(true)
|
FileHelper.expects(:is_image?).returns(true)
|
||||||
Upload.expects(:create!).never
|
Upload.expects(:save).never
|
||||||
expect { Upload.create_for(user_id, attachment, attachment_filesize) }.to raise_error(FastImage::UnknownImageType)
|
upload = Upload.create_for(user_id, attachment, attachment_filename, attachment_filesize)
|
||||||
|
upload.errors.size.should > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not compute width & height for non-image" do
|
it "does not compute width & height for non-image" do
|
||||||
SiteSetting.expects(:authorized_image?).returns(false)
|
|
||||||
FastImage.any_instance.expects(:size).never
|
FastImage.any_instance.expects(:size).never
|
||||||
Upload.create_for(user_id, image, image_filesize)
|
upload = Upload.create_for(user_id, attachment, attachment_filename, attachment_filesize)
|
||||||
|
upload.errors.size.should > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
it "saves proper information" do
|
it "saves proper information" do
|
||||||
store = {}
|
store = {}
|
||||||
Discourse.expects(:store).returns(store)
|
Discourse.expects(:store).returns(store)
|
||||||
store.expects(:store_upload).returns(url)
|
store.expects(:store_upload).returns(url)
|
||||||
upload = Upload.create_for(user_id, image, image_filesize)
|
|
||||||
|
upload = Upload.create_for(user_id, image, image_filename, image_filesize)
|
||||||
|
|
||||||
upload.user_id.should == user_id
|
upload.user_id.should == user_id
|
||||||
upload.original_filename.should == image.original_filename
|
upload.original_filename.should == image_filename
|
||||||
upload.filesize.should == File.size(image.tempfile)
|
upload.filesize.should == image_filesize
|
||||||
upload.sha1.should == Digest::SHA1.file(image.tempfile).hexdigest
|
upload.sha1.should == image_sha1
|
||||||
upload.width.should == 244
|
upload.width.should == 244
|
||||||
upload.height.should == 66
|
upload.height.should == 66
|
||||||
upload.url.should == url
|
upload.url.should == url
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
describe UriAdapter do
|
|
||||||
let(:target) { "http://cdn.discourse.org/assets/logo.png" }
|
|
||||||
let(:response) { StringIO.new(fixture_file("images/logo.png")) }
|
|
||||||
|
|
||||||
before :each do
|
|
||||||
response.stubs(:content_type).returns("image/png")
|
|
||||||
UriAdapter.any_instance.stubs(:open).returns(response)
|
|
||||||
end
|
|
||||||
|
|
||||||
subject { UriAdapter.new(target) }
|
|
||||||
|
|
||||||
describe "#initialize" do
|
|
||||||
|
|
||||||
it "has a target" do
|
|
||||||
subject.target.should be_instance_of(Addressable::URI)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has content" do
|
|
||||||
subject.content.should == response
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has an original_filename" do
|
|
||||||
subject.original_filename.should == "logo.png"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has a tempfile" do
|
|
||||||
subject.tempfile.should be_instance_of Tempfile
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "it handles ugly targets" do
|
|
||||||
let(:ugly_target) { "http://cdn.discourse.org/assets/logo with spaces.png" }
|
|
||||||
subject { UriAdapter.new(ugly_target) }
|
|
||||||
|
|
||||||
it "handles targets" do
|
|
||||||
subject.target.should be_instance_of(Addressable::URI)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has content" do
|
|
||||||
subject.content.should == response
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has an original_filename" do
|
|
||||||
subject.original_filename.should == "logo with spaces.png"
|
|
||||||
end
|
|
||||||
|
|
||||||
it "has a tempfile" do
|
|
||||||
subject.tempfile.should be_instance_of Tempfile
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#copy_to_tempfile" do
|
|
||||||
it "does not allow files bigger then max_image_size_kb" do
|
|
||||||
SiteSetting.stubs(:max_image_size_kb).returns(1)
|
|
||||||
subject.build_uploaded_file.should == nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#build_uploaded_file" do
|
|
||||||
it "returns an uploaded file" do
|
|
||||||
file = subject.build_uploaded_file
|
|
||||||
file.should be_instance_of(ActionDispatch::Http::UploadedFile)
|
|
||||||
file.content_type.should == "image/png"
|
|
||||||
file.original_filename.should == "logo.png"
|
|
||||||
file.tempfile.should be_instance_of Tempfile
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
Loading…
Reference in New Issue