discourse/app/controllers/user_avatars_controller.rb
Régis Hanol 85d4d3223c FIX: crop avatars on the server instead of the client
FIX: support for dots in S3 bucket names
2015-05-26 15:54:25 +02:00

110 lines
3.4 KiB
Ruby

require_dependency 'letter_avatar'
class UserAvatarsController < ApplicationController
DOT = Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==")
skip_before_filter :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :show_letter]
def refresh_gravatar
user = User.find_by(username_lower: params[:username].downcase)
guardian.ensure_can_edit!(user)
if user
user.create_user_avatar(user_id: user.id) unless user.user_avatar
user.user_avatar.update_gravatar!
render json: { upload_id: user.user_avatar.gravatar_upload_id }
else
raise Discourse::NotFound
end
end
def show_letter
params.require(:username)
params.require(:version)
params.require(:size)
no_cookies
return render_dot if params[:version] != LetterAvatar.version
image = LetterAvatar.generate(params[:username].to_s, params[:size].to_i)
response.headers["Last-Modified"] = File.ctime(image).httpdate
response.headers["Content-Length"] = File.size(image).to_s
expires_in 1.year, public: true
send_file image, disposition: nil
end
def show
no_cookies
# we need multisite support to keep a single origin pull for CDNs
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
show_in_site(RailsMultisite::ConnectionManagement.current_hostname)
end
end
protected
def show_in_site(hostname)
username = params[:username].to_s
return render_dot unless user = User.find_by(username_lower: username.downcase)
version = params[:version].to_i
return render_dot unless version > 0 && user_avatar = user.user_avatar
size = params[:size].to_i
return render_dot if size < 8 || size > 500
if !Discourse.avatar_sizes.include?(size) && Discourse.store.external?
closest = Discourse.avatar_sizes.to_a.min{|a,b| (size-a).abs <=> (size-b).abs}
return redirect_to cdn_path("/user_avatar/#{params[:hostname]}/#{user.username_lower}/#{closest}/#{version}.png")
end
upload = Upload.find_by(id: version) if user_avatar.contains_upload?(version)
upload ||= user.uploaded_avatar if user.uploaded_avatar_id == version
if user.uploaded_avatar && !upload
return redirect_to cdn_path("/user_avatar/#{hostname}/#{user.username_lower}/#{size}/#{user.uploaded_avatar_id}.png")
elsif upload
original = Discourse.store.path_for(upload)
if Discourse.store.external? || File.exists?(original)
if optimized = get_optimized_image(upload, size)
unless optimized.local?
expires_in 1.day, public: true
return redirect_to optimized.url
end
image = Discourse.store.path_for(optimized)
end
end
end
if image
response.headers["Last-Modified"] = File.ctime(image).httpdate
response.headers["Content-Length"] = File.size(image).to_s
expires_in 1.year, public: true
send_file image, disposition: nil
else
render_dot
end
end
# this protects us from a DoS
def render_dot
expires_in 10.minutes, public: true
render text: DOT, content_type: "image/png"
end
def get_optimized_image(upload, size)
OptimizedImage.create_for(
upload,
size,
size,
allow_animation: SiteSetting.allow_animated_avatars
)
end
end