mirror of
https://github.com/discourse/discourse.git
synced 2024-11-27 17:53:40 +08:00
f04471e422
Co-authored-by: Sam Saffron <sam.saffron@gmail.com> Co-authored-by: David Taylor <david@taylorhq.com> This gives more control over the request. In particular we can easily lookup DNS dynamically, instead of only upon NGINX startup. Previously, NGINX was looking up IP for the letter avatar service and caching the CDN IP address, this caused issues if CDN changed IP, in which letter avatars would be broken till a container restarted. NGINX config has been updated to add caching. This change will require a container rebuild. The proxy will now function in development environments, so the patch for `letter_avatar_proxy` has been removed.
189 lines
5.4 KiB
Ruby
189 lines
5.4 KiB
Ruby
require_dependency 'letter_avatar'
|
|
|
|
class UserAvatarsController < ApplicationController
|
|
|
|
skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :show_letter, :show_proxy_letter]
|
|
|
|
def refresh_gravatar
|
|
user = User.find_by(username_lower: params[:username].downcase)
|
|
guardian.ensure_can_edit!(user)
|
|
|
|
if user
|
|
hijack do
|
|
user.create_user_avatar(user_id: user.id) unless user.user_avatar
|
|
user.user_avatar.update_gravatar!
|
|
|
|
gravatar = if user.user_avatar.gravatar_upload_id
|
|
{
|
|
gravatar_upload_id: user.user_avatar.gravatar_upload_id,
|
|
gravatar_avatar_template: User.avatar_template(user.username, user.user_avatar.gravatar_upload_id)
|
|
}
|
|
else
|
|
{
|
|
gravatar_upload_id: nil,
|
|
gravatar_avatar_template: nil
|
|
}
|
|
end
|
|
|
|
render json: gravatar
|
|
end
|
|
else
|
|
raise Discourse::NotFound
|
|
end
|
|
end
|
|
|
|
def show_proxy_letter
|
|
is_asset_path
|
|
|
|
if SiteSetting.external_system_avatars_url !~ /^\/letter(_avatar)?_proxy/
|
|
raise Discourse::NotFound
|
|
end
|
|
|
|
params.require(:letter)
|
|
params.require(:color)
|
|
params.require(:version)
|
|
params.require(:size)
|
|
hijack do
|
|
proxy_avatar("https://avatars.discourse.org/#{params[:version]}/letter/#{params[:letter]}/#{params[:color]}/#{params[:size]}.png", Time.new('1990-01-01'))
|
|
end
|
|
end
|
|
|
|
def show_letter
|
|
is_asset_path
|
|
|
|
params.require(:username)
|
|
params.require(:version)
|
|
params.require(:size)
|
|
|
|
no_cookies
|
|
|
|
return render_blank if params[:version] != LetterAvatar.version
|
|
|
|
hijack do
|
|
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
|
|
immutable_for(1.year)
|
|
send_file image, disposition: nil
|
|
end
|
|
end
|
|
|
|
def show
|
|
is_asset_path
|
|
|
|
# we need multisite support to keep a single origin pull for CDNs
|
|
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
|
|
hijack do
|
|
show_in_site(params[:hostname])
|
|
end
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
def show_in_site(hostname)
|
|
|
|
username = params[:username].to_s
|
|
return render_blank unless user = User.find_by(username_lower: username.downcase)
|
|
|
|
upload_id, version = params[:version].split("_")
|
|
|
|
version = (version || OptimizedImage::VERSION).to_i
|
|
|
|
# old versions simply get new avatar
|
|
if version > OptimizedImage::VERSION
|
|
return render_blank
|
|
end
|
|
|
|
upload_id = upload_id.to_i
|
|
return render_blank unless upload_id > 0
|
|
|
|
size = params[:size].to_i
|
|
return render_blank if size < 8 || size > 1000
|
|
|
|
if !Discourse.avatar_sizes.include?(size) && Discourse.store.external?
|
|
closest = Discourse.avatar_sizes.to_a.min { |a, b| (size - a).abs <=> (size - b).abs }
|
|
avatar_url = UserAvatar.local_avatar_url(hostname, user.username_lower, upload_id, closest)
|
|
return redirect_to cdn_path(avatar_url)
|
|
end
|
|
|
|
upload = Upload.find_by(id: upload_id) if user&.user_avatar&.contains_upload?(upload_id)
|
|
upload ||= user.uploaded_avatar if user.uploaded_avatar_id == upload_id
|
|
|
|
if user.uploaded_avatar && !upload
|
|
avatar_url = UserAvatar.local_avatar_url(hostname, user.username_lower, user.uploaded_avatar_id, size)
|
|
return redirect_to cdn_path(avatar_url)
|
|
elsif upload && optimized = get_optimized_image(upload, size)
|
|
if optimized.local?
|
|
optimized_path = Discourse.store.path_for(optimized)
|
|
image = optimized_path if File.exists?(optimized_path)
|
|
else
|
|
return proxy_avatar(Discourse.store.cdn_url(optimized.url), upload.created_at)
|
|
end
|
|
end
|
|
|
|
if image
|
|
response.headers["Last-Modified"] = File.ctime(image).httpdate
|
|
response.headers["Content-Length"] = File.size(image).to_s
|
|
immutable_for 1.year
|
|
send_file image, disposition: nil
|
|
else
|
|
render_blank
|
|
end
|
|
rescue OpenURI::HTTPError
|
|
render_blank
|
|
end
|
|
|
|
PROXY_PATH = Rails.root + "tmp/avatar_proxy"
|
|
def proxy_avatar(url, last_modified)
|
|
|
|
if url[0..1] == "//"
|
|
url = (SiteSetting.force_https ? "https:" : "http:") + url
|
|
end
|
|
|
|
sha = Digest::SHA1.hexdigest(url)
|
|
filename = "#{sha}#{File.extname(url)}"
|
|
path = "#{PROXY_PATH}/#{filename}"
|
|
|
|
unless File.exist? path
|
|
FileUtils.mkdir_p PROXY_PATH
|
|
tmp = FileHelper.download(
|
|
url,
|
|
max_file_size: 1.megabyte,
|
|
tmp_file_name: filename,
|
|
follow_redirect: true,
|
|
read_timeout: 10
|
|
)
|
|
FileUtils.mv tmp.path, path
|
|
end
|
|
|
|
response.headers["Last-Modified"] = last_modified.httpdate
|
|
response.headers["Content-Length"] = File.size(path).to_s
|
|
immutable_for(1.year)
|
|
send_file path, disposition: nil
|
|
end
|
|
|
|
# this protects us from a DoS
|
|
def render_blank
|
|
path = Rails.root + "public/images/avatar.png"
|
|
expires_in 10.minutes, public: true
|
|
response.headers["Last-Modified"] = Time.new('1990-01-01').httpdate
|
|
response.headers["Content-Length"] = File.size(path).to_s
|
|
send_file path, disposition: nil
|
|
end
|
|
|
|
protected
|
|
|
|
# consider removal of hacks some time in 2019
|
|
|
|
def get_optimized_image(upload, size)
|
|
return if !upload
|
|
return upload if upload.extension == "svg"
|
|
|
|
upload.get_optimized_image(size, size, allow_animation: SiteSetting.allow_animated_avatars)
|
|
# TODO decide if we want to detach here
|
|
end
|
|
|
|
end
|