2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-05-26 05:46:43 -04:00
|
|
|
class UserAvatarsController < ApplicationController
|
2017-08-31 00:06:56 -04:00
|
|
|
skip_before_action :preload_json,
|
|
|
|
:redirect_to_login_if_required,
|
2024-06-25 07:32:18 -04:00
|
|
|
:redirect_to_profile_if_required,
|
2017-08-31 00:06:56 -04:00
|
|
|
:check_xhr,
|
|
|
|
:verify_authenticity_token,
|
|
|
|
only: %i[show show_letter show_proxy_letter]
|
2014-05-27 08:29:27 -04:00
|
|
|
|
2021-01-28 21:14:49 -05:00
|
|
|
before_action :apply_cdn_headers, only: %i[show show_letter show_proxy_letter]
|
|
|
|
|
2014-05-26 05:46:43 -04:00
|
|
|
def refresh_gravatar
|
|
|
|
user = User.find_by(username_lower: params[:username].downcase)
|
|
|
|
guardian.ensure_can_edit!(user)
|
|
|
|
|
|
|
|
if user
|
2017-11-27 01:43:24 -05:00
|
|
|
hijack do
|
|
|
|
user.create_user_avatar(user_id: user.id) unless user.user_avatar
|
|
|
|
user.user_avatar.update_gravatar!
|
|
|
|
|
2017-11-29 12:09:44 -05:00
|
|
|
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
|
2017-11-27 01:43:24 -05:00
|
|
|
end
|
2014-05-26 05:46:43 -04:00
|
|
|
else
|
|
|
|
raise Discourse::NotFound
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-11-24 22:42:46 -05:00
|
|
|
def show_proxy_letter
|
2018-03-05 23:20:39 -05:00
|
|
|
is_asset_path
|
|
|
|
|
2023-01-20 13:52:49 -05:00
|
|
|
if SiteSetting.external_system_avatars_url !~ %r{\A/letter_avatar_proxy}
|
2016-07-27 18:59:58 -04:00
|
|
|
raise Discourse::NotFound
|
|
|
|
end
|
|
|
|
|
2015-11-24 22:42:46 -05:00
|
|
|
params.require(:letter)
|
|
|
|
params.require(:color)
|
|
|
|
params.require(:version)
|
|
|
|
params.require(:size)
|
2019-04-24 17:03:33 -04:00
|
|
|
|
2017-11-27 01:43:24 -05:00
|
|
|
hijack do
|
2019-04-24 17:03:33 -04:00
|
|
|
begin
|
2022-12-13 14:03:53 -05:00
|
|
|
proxy_avatar(
|
|
|
|
"https://avatars.discourse-cdn.com/#{params[:version]}/letter/#{params[:letter]}/#{params[:color]}/#{params[:size]}.png",
|
|
|
|
Time.new(1990, 01, 01),
|
|
|
|
)
|
2019-04-24 17:03:33 -04:00
|
|
|
rescue OpenURI::HTTPError
|
|
|
|
render_blank
|
|
|
|
end
|
2017-11-27 01:43:24 -05:00
|
|
|
end
|
2015-11-24 22:42:46 -05:00
|
|
|
end
|
|
|
|
|
2014-05-30 00:17:35 -04:00
|
|
|
def show_letter
|
2018-03-05 23:20:39 -05:00
|
|
|
is_asset_path
|
|
|
|
|
2014-05-30 00:17:35 -04:00
|
|
|
params.require(:username)
|
|
|
|
params.require(:version)
|
|
|
|
params.require(:size)
|
|
|
|
|
2015-05-22 02:15:46 -04:00
|
|
|
no_cookies
|
|
|
|
|
2015-12-15 22:02:09 -05:00
|
|
|
return render_blank if params[:version] != LetterAvatar.version
|
2014-05-30 00:17:35 -04:00
|
|
|
|
2017-11-27 01:43:24 -05:00
|
|
|
hijack do
|
|
|
|
image = LetterAvatar.generate(params[:username].to_s, params[:size].to_i)
|
2014-10-22 09:39:51 -04:00
|
|
|
|
2017-11-27 01:43:24 -05:00
|
|
|
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
|
2014-05-30 00:17:35 -04:00
|
|
|
end
|
|
|
|
|
2014-05-22 03:37:02 -04:00
|
|
|
def show
|
2018-03-05 23:20:39 -05:00
|
|
|
is_asset_path
|
|
|
|
|
2014-05-27 09:13:42 -04:00
|
|
|
# we need multisite support to keep a single origin pull for CDNs
|
|
|
|
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
|
2017-11-27 01:43:24 -05:00
|
|
|
hijack { show_in_site(params[:hostname]) }
|
2014-05-27 09:13:42 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
2014-05-27 10:15:09 -04:00
|
|
|
def show_in_site(hostname)
|
2014-05-22 03:37:02 -04:00
|
|
|
username = params[:username].to_s
|
2015-12-15 22:02:09 -05:00
|
|
|
return render_blank unless user = User.find_by(username_lower: username.downcase)
|
2014-05-22 03:37:02 -04:00
|
|
|
|
2015-05-29 13:19:41 -04:00
|
|
|
upload_id, version = params[:version].split("_")
|
|
|
|
|
|
|
|
version = (version || OptimizedImage::VERSION).to_i
|
2019-01-08 03:51:33 -05:00
|
|
|
|
|
|
|
# old versions simply get new avatar
|
|
|
|
return render_blank if version > OptimizedImage::VERSION
|
2015-05-29 13:19:41 -04:00
|
|
|
|
|
|
|
upload_id = upload_id.to_i
|
2023-02-16 04:40:11 -05:00
|
|
|
return render_blank if upload_id <= 0
|
2014-05-22 03:37:02 -04:00
|
|
|
|
2015-05-26 09:54:25 -04:00
|
|
|
size = params[:size].to_i
|
2016-07-05 12:49:33 -04:00
|
|
|
return render_blank if size < 8 || size > 1000
|
2015-05-26 01:41:50 -04:00
|
|
|
|
|
|
|
if !Discourse.avatar_sizes.include?(size) && Discourse.store.external?
|
2015-05-29 03:57:54 -04:00
|
|
|
closest = Discourse.avatar_sizes.to_a.min { |a, b| (size - a).abs <=> (size - b).abs }
|
2020-06-05 12:31:58 -04:00
|
|
|
avatar_url =
|
|
|
|
UserAvatar.local_avatar_url(
|
|
|
|
hostname,
|
|
|
|
user.encoded_username(lower: true),
|
|
|
|
upload_id,
|
|
|
|
closest,
|
|
|
|
)
|
2022-03-21 10:28:52 -04:00
|
|
|
return redirect_to cdn_path(avatar_url), allow_other_host: true
|
2015-05-26 01:41:50 -04:00
|
|
|
end
|
|
|
|
|
2017-12-14 00:20:58 -05:00
|
|
|
upload = Upload.find_by(id: upload_id) if user&.user_avatar&.contains_upload?(upload_id)
|
2015-05-29 13:19:41 -04:00
|
|
|
upload ||= user.uploaded_avatar if user.uploaded_avatar_id == upload_id
|
2014-05-22 03:37:02 -04:00
|
|
|
|
|
|
|
if user.uploaded_avatar && !upload
|
2020-06-05 12:31:58 -04:00
|
|
|
avatar_url =
|
|
|
|
UserAvatar.local_avatar_url(
|
|
|
|
hostname,
|
|
|
|
user.encoded_username(lower: true),
|
|
|
|
user.uploaded_avatar_id,
|
|
|
|
size,
|
|
|
|
)
|
2022-03-21 10:28:52 -04:00
|
|
|
return redirect_to cdn_path(avatar_url), allow_other_host: true
|
2015-06-01 11:49:58 -04:00
|
|
|
elsif upload && optimized = get_optimized_image(upload, size)
|
|
|
|
if optimized.local?
|
|
|
|
optimized_path = Discourse.store.path_for(optimized)
|
2022-01-05 12:45:08 -05:00
|
|
|
image = optimized_path if File.exist?(optimized_path)
|
2022-12-02 05:07:25 -05:00
|
|
|
elsif GlobalSetting.redirect_avatar_requests
|
|
|
|
return redirect_s3_avatar(Discourse.store.cdn_url(optimized.url))
|
2015-06-01 11:49:58 -04:00
|
|
|
else
|
2018-08-23 19:36:11 -04:00
|
|
|
return proxy_avatar(Discourse.store.cdn_url(optimized.url), upload.created_at)
|
2014-05-22 03:37:02 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if image
|
2014-07-08 03:16:07 -04:00
|
|
|
response.headers["Last-Modified"] = File.ctime(image).httpdate
|
2014-10-22 09:39:51 -04:00
|
|
|
response.headers["Content-Length"] = File.size(image).to_s
|
2017-02-23 13:05:00 -05:00
|
|
|
immutable_for 1.year
|
2014-05-22 03:37:02 -04:00
|
|
|
send_file image, disposition: nil
|
|
|
|
else
|
2015-12-15 22:02:09 -05:00
|
|
|
render_blank
|
2014-05-22 03:37:02 -04:00
|
|
|
end
|
2017-05-04 12:42:46 -04:00
|
|
|
rescue OpenURI::HTTPError
|
|
|
|
render_blank
|
2014-05-22 03:37:02 -04:00
|
|
|
end
|
2014-05-27 00:40:46 -04:00
|
|
|
|
2022-06-01 20:12:06 -04:00
|
|
|
# Allow plugins to overwrite max file size value
|
|
|
|
def max_file_size
|
|
|
|
1.megabyte
|
|
|
|
end
|
|
|
|
|
2015-12-15 21:18:12 -05:00
|
|
|
PROXY_PATH = Rails.root + "tmp/avatar_proxy"
|
2018-08-23 19:36:11 -04:00
|
|
|
def proxy_avatar(url, last_modified)
|
2016-06-27 05:26:43 -04:00
|
|
|
url = (SiteSetting.force_https ? "https:" : "http:") + url if url[0..1] == "//"
|
2015-12-16 21:21:09 -05:00
|
|
|
|
2015-12-15 21:18:12 -05:00
|
|
|
sha = Digest::SHA1.hexdigest(url)
|
|
|
|
filename = "#{sha}#{File.extname(url)}"
|
|
|
|
path = "#{PROXY_PATH}/#{filename}"
|
|
|
|
|
2015-12-15 21:40:34 -05:00
|
|
|
unless File.exist? path
|
2015-12-15 21:18:12 -05:00
|
|
|
FileUtils.mkdir_p PROXY_PATH
|
2017-05-24 13:42:52 -04:00
|
|
|
tmp =
|
|
|
|
FileHelper.download(
|
|
|
|
url,
|
2022-06-01 20:12:06 -04:00
|
|
|
max_file_size: max_file_size,
|
2017-05-24 13:42:52 -04:00
|
|
|
tmp_file_name: filename,
|
|
|
|
follow_redirect: true,
|
|
|
|
read_timeout: 10,
|
|
|
|
)
|
2019-10-22 11:05:36 -04:00
|
|
|
|
|
|
|
return render_blank if tmp.nil?
|
|
|
|
|
2015-12-15 21:18:12 -05:00
|
|
|
FileUtils.mv tmp.path, path
|
|
|
|
end
|
|
|
|
|
2018-08-23 19:36:11 -04:00
|
|
|
response.headers["Last-Modified"] = last_modified.httpdate
|
2015-12-15 21:18:12 -05:00
|
|
|
response.headers["Content-Length"] = File.size(path).to_s
|
2017-02-23 13:05:00 -05:00
|
|
|
immutable_for(1.year)
|
2015-12-15 21:18:12 -05:00
|
|
|
send_file path, disposition: nil
|
|
|
|
end
|
|
|
|
|
2022-12-02 05:07:25 -05:00
|
|
|
def redirect_s3_avatar(url)
|
2023-02-15 04:13:19 -05:00
|
|
|
response.cache_control[:max_age] = 1.hour.to_i
|
|
|
|
response.cache_control[:public] = true
|
|
|
|
response.cache_control[:extras] = ["immutable", "stale-while-revalidate=#{1.day.to_i}"]
|
2022-12-02 05:07:25 -05:00
|
|
|
redirect_to url, allow_other_host: true
|
|
|
|
end
|
|
|
|
|
2014-05-27 08:29:27 -04:00
|
|
|
# this protects us from a DoS
|
2015-12-15 22:02:09 -05:00
|
|
|
def render_blank
|
|
|
|
path = Rails.root + "public/images/avatar.png"
|
2014-05-27 08:29:27 -04:00
|
|
|
expires_in 10.minutes, public: true
|
2022-12-13 14:03:53 -05:00
|
|
|
response.headers["Last-Modified"] = Time.new(1990, 01, 01).httpdate
|
2015-12-15 22:02:09 -05:00
|
|
|
response.headers["Content-Length"] = File.size(path).to_s
|
|
|
|
send_file path, disposition: nil
|
2014-05-27 08:29:27 -04:00
|
|
|
end
|
|
|
|
|
2018-08-16 02:32:36 -04:00
|
|
|
protected
|
|
|
|
|
|
|
|
# consider removal of hacks some time in 2019
|
|
|
|
|
2014-05-27 00:40:46 -04:00
|
|
|
def get_optimized_image(upload, size)
|
2018-08-17 00:00:27 -04:00
|
|
|
return if !upload
|
2018-11-06 23:29:14 -05:00
|
|
|
return upload if upload.extension == "svg"
|
2018-08-16 02:32:36 -04:00
|
|
|
|
2020-10-16 06:41:27 -04:00
|
|
|
upload.get_optimized_image(size, size)
|
2018-08-17 00:00:27 -04:00
|
|
|
# TODO decide if we want to detach here
|
2014-05-27 00:40:46 -04:00
|
|
|
end
|
2014-05-22 03:37:02 -04:00
|
|
|
end
|