discourse/app/controllers/static_controller.rb

254 lines
8.3 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require_dependency 'distributed_memoizer'
require_dependency 'file_helper'
2013-02-06 03:16:51 +08:00
class StaticController < ApplicationController
skip_before_action :check_xhr, :redirect_to_login_if_required
skip_before_action :verify_authenticity_token, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset]
skip_before_action :preload_json, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset]
skip_before_action :handle_theme, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset]
2013-02-06 03:16:51 +08:00
PAGES_WITH_EMAIL_PARAM = ['login', 'password_reset', 'signup']
MODAL_PAGES = ['password_reset', 'signup']
2013-02-06 03:16:51 +08:00
def show
return redirect_to(path '/') if current_user && (params[:id] == 'login' || params[:id] == 'signup')
if SiteSetting.login_required? && current_user.nil? && ['faq', 'guidelines'].include?(params[:id])
return redirect_to path('/login')
end
map = {
2017-07-28 09:20:09 +08:00
"faq" => { redirect: "faq_url", topic_id: "guidelines_topic_id" },
"tos" => { redirect: "tos_url", topic_id: "tos_topic_id" },
"privacy" => { redirect: "privacy_policy_url", topic_id: "privacy_topic_id" }
}
@page = params[:id]
2013-02-06 03:16:51 +08:00
if map.has_key?(@page)
site_setting_key = map[@page][:redirect]
url = SiteSetting.get(site_setting_key)
return redirect_to(url) unless url.blank?
end
# The /guidelines route ALWAYS shows our FAQ, ignoring the faq_url site setting.
@page = 'faq' if @page == 'guidelines'
2013-02-06 03:16:51 +08:00
# Don't allow paths like ".." or "/" or anything hacky like that
@page = @page.gsub(/[^a-z0-9\_\-]/, '')
2013-02-06 03:16:51 +08:00
if map.has_key?(@page)
2019-05-07 09:27:05 +08:00
@topic = Topic.find_by_id(SiteSetting.get(map[@page][:topic_id]))
raise Discourse::NotFound unless @topic
title_prefix = if I18n.exists?("js.#{@page}")
I18n.t("js.#{@page}")
else
@topic.title
end
@title = "#{title_prefix} - #{SiteSetting.title}"
@body = @topic.posts.first.cooked
@faq_overriden = !SiteSetting.faq_url.blank?
render :show, layout: !request.xhr?, formats: [:html]
2013-02-06 03:16:51 +08:00
return
end
2014-10-18 11:27:33 +08:00
if I18n.exists?("static.#{@page}")
render html: I18n.t("static.#{@page}"), layout: !request.xhr?, formats: [:html]
2014-10-18 11:27:33 +08:00
return
end
if PAGES_WITH_EMAIL_PARAM.include?(@page) && params[:email]
cookies[:email] = { value: params[:email], expires: 1.day.from_now }
end
file = "static/#{@page}.#{I18n.locale}"
file = "static/#{@page}.en" if lookup_context.find_all("#{file}.html").empty?
file = "static/#{@page}" if lookup_context.find_all("#{file}.html").empty?
if lookup_context.find_all("#{file}.html").any?
render file, layout: !request.xhr?, formats: [:html]
return
end
if MODAL_PAGES.include?(@page)
render html: nil, layout: true
return
end
raise Discourse::NotFound
2013-02-06 03:16:51 +08:00
end
# This method just redirects to a given url.
# It's used when an ajax login was successful but we want the browser to see
# a post of a login form so that it offers to remember your password.
def enter
params.delete(:username)
params.delete(:password)
destination = path("/")
redirect_location = params[:redirect]
if redirect_location.present? && !redirect_location.is_a?(String)
raise Discourse::InvalidParameters.new(:redirect)
elsif redirect_location.present? && !redirect_location.match(login_path)
begin
forum_uri = URI(Discourse.base_url)
2019-06-11 19:40:16 +08:00
uri = URI(redirect_location)
if uri.path.present? &&
(uri.host.blank? || uri.host == forum_uri.host) &&
uri.path !~ /\./
destination = uri.path
destination = "#{uri.path}?#{uri.query}" if uri.path =~ /new-topic/ || uri.path =~ /new-message/ || uri.path =~ /user-api-key/
end
rescue URI::Error
# Do nothing if the URI is invalid
end
end
redirect_to destination
end
FAVICON ||= -"favicon"
# We need to be able to draw our favicon on a canvas, this happens when you enable the feature
# that draws the notification count on top of favicon (per user default off)
#
# With s3 the original upload is going to be stored at s3, we don't have a local copy of the favicon.
# To allow canvas to work with s3 we are going to need to add special CORS headers and use
# a special crossorigin hint on the original, this is not easily workable.
#
# Forcing all consumers to set magic CORS headers on a CDN is also not workable for us.
#
# So we cache the favicon in redis and serve it out real quick with
# a huge expiry, we also cache these assets in nginx so it is bypassed if needed
def favicon
is_asset_path
2017-11-27 11:50:57 +08:00
hijack do
FEATURE: Automatically generate optimized site metadata icons (#7372) This change automatically resizes icons for various purposes. Admins can now upload `logo` and `logo_small`, and everything else will be auto-generated. Specific icons can still be uploaded separately if required. ## Core - Adds an SiteIconManager module which manages automatic resizing and fallback - Icons are looked up in the OptimizedImage table at runtime, and then cached in Redis. If the resized version is missing for some reason, then most icons will fall back to the original files. Some icons (e.g. PWA Manifest) will return `nil` (because an incorrectly sized icon is worse than a missing icon). - `SiteSetting.site_large_icon_url` will return the optimized version, including any fallback. `SiteSetting.large_icon` continues to return the upload object. This means that (almost) no changes are required in core/plugins to support this new system. - Icons are resized whenever a relevant site setting is changed, and during post-deploy migrations ## Wizard - Allows `requiresRefresh` wizard steps to reload data via AJAX instead of a full page reload - Add placeholders to the **icons** step of the wizard, which automatically update from the "Square Logo" - Various copy updates to support the changes - Remove the "upload-time" resizing for `large_icon`. This is no longer required. ## Site Settings UX - Move logo/icon settings under a new "Branding" tab - Various copy changes to support the changes - Adds placeholder support to the `image-uploader` component - Automatically reloads site settings after saving. This allows setting placeholders to change based on changes to other settings - Upload site settings will be assigned a placeholder if SiteIconManager `responds_to?` an icon of the same name ## Dashboard Warnings - Remove PWA icon and PWA title warnings. Both are now handled automatically. ## Bonus - Updated the sketch logos to use @awesomerobot's new high-res designs
2019-05-01 21:44:45 +08:00
data = DistributedMemoizer.memoize("FAVICON#{SiteIconManager.favicon_url}", 60 * 30) do
favicon = SiteIconManager.favicon
next "" unless favicon
if Discourse.store.external?
begin
file = FileHelper.download(
UrlHelper.absolute(favicon.url),
max_file_size: favicon.filesize,
tmp_file_name: FAVICON,
follow_redirect: true
)
file&.read || ""
rescue => e
AdminDashboardData.add_problem_message('dashboard.bad_favicon_url', 1800)
2019-06-05 15:46:07 +08:00
Rails.logger.warn("Failed to fetch favicon #{favicon.url}: #{e}\n#{e.backtrace}")
""
ensure
file&.unlink
end
else
File.read(Rails.root.join("public", favicon.url[1..-1]))
2017-11-27 11:50:57 +08:00
end
2015-08-25 10:05:15 +08:00
end
2017-11-27 11:50:57 +08:00
if data.bytesize == 0
@@default_favicon ||= File.read(Rails.root + "public/images/default-favicon.png")
response.headers["Content-Length"] = @@default_favicon.bytesize.to_s
render body: @@default_favicon, content_type: "image/png"
else
immutable_for 1.year
response.headers["Expires"] = 1.year.from_now.httpdate
response.headers["Content-Length"] = data.bytesize.to_s
response.headers["Last-Modified"] = Time.new('2000-01-01').httpdate
render body: data, content_type: "image/png"
end
end
end
def brotli_asset
is_asset_path
serve_asset(".br") do
response.headers["Content-Encoding"] = 'br'
end
end
def cdn_asset
is_asset_path
serve_asset
end
def service_worker_asset
is_asset_path
respond_to do |format|
format.js do
# https://github.com/w3c/ServiceWorker/blob/master/explainer.md#updating-a-service-worker
# Maximum cache that the service worker will respect is 24 hours.
# However, ensure that these may be cached and served for longer on servers.
immutable_for 1.year
2017-12-28 05:31:01 +08:00
if Rails.application.assets_manifest.assets['service-worker.js']
path = File.expand_path(Rails.root + "public/assets/#{Rails.application.assets_manifest.assets['service-worker.js']}")
response.headers["Last-Modified"] = File.ctime(path).httpdate
end
render(
plain: Rails.application.assets_manifest.find_sources('service-worker.js').first,
content_type: 'application/javascript'
)
end
end
end
protected
2017-07-28 09:20:09 +08:00
def serve_asset(suffix = nil)
path = File.expand_path(Rails.root + "public/assets/#{params[:path]}#{suffix}")
# SECURITY what if path has /../
raise Discourse::NotFound unless path.start_with?(Rails.root.to_s + "/public/assets")
response.headers["Expires"] = 1.year.from_now.httpdate
response.headers["Access-Control-Allow-Origin"] = params[:origin] if params[:origin]
begin
response.headers["Last-Modified"] = File.ctime(path).httpdate
rescue Errno::ENOENT
begin
if GlobalSetting.fallback_assets_path.present?
path = File.expand_path("#{GlobalSetting.fallback_assets_path}/#{params[:path]}#{suffix}")
response.headers["Last-Modified"] = File.ctime(path).httpdate
else
raise
end
rescue Errno::ENOENT
expires_in 1.second, public: true, must_revalidate: false
render plain: "can not find #{params[:path]}", status: 404
return
end
end
response.headers["Content-Length"] = File.size(path).to_s
yield if block_given?
immutable_for 1.year
2017-02-24 07:26:40 +08:00
# disable NGINX mucking with transfer
request.env['sendfile.type'] = ''
opts = { disposition: nil }
2017-03-21 05:07:32 +08:00
opts[:type] = "application/javascript" if params[:path] =~ /\.js$/
send_file(path, opts)
end
2013-02-07 23:45:24 +08:00
end