2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
class StaticController < ApplicationController
|
2024-06-25 07:32:18 -04:00
|
|
|
skip_before_action :check_xhr, :redirect_to_login_if_required, :redirect_to_profile_if_required
|
2017-11-22 20:02:01 -05:00
|
|
|
skip_before_action :verify_authenticity_token,
|
2023-11-06 10:57:00 -05:00
|
|
|
only: %i[cdn_asset enter favicon service_worker_asset]
|
|
|
|
skip_before_action :preload_json, only: %i[cdn_asset enter favicon service_worker_asset]
|
|
|
|
skip_before_action :handle_theme, only: %i[cdn_asset enter favicon service_worker_asset]
|
2023-01-09 07:20:10 -05:00
|
|
|
|
2023-11-06 10:57:00 -05:00
|
|
|
before_action :apply_cdn_headers, only: %i[cdn_asset enter favicon service_worker_asset]
|
2023-01-09 07:20:10 -05:00
|
|
|
|
2016-01-19 16:53:46 -05:00
|
|
|
PAGES_WITH_EMAIL_PARAM = %w[login password_reset signup]
|
2019-03-04 04:34:48 -05:00
|
|
|
MODAL_PAGES = %w[password_reset signup]
|
2021-12-15 22:24:11 -05:00
|
|
|
DEFAULT_PAGES = {
|
|
|
|
"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",
|
2023-01-09 07:20:10 -05:00
|
|
|
},
|
2021-12-15 22:24:11 -05:00
|
|
|
}
|
|
|
|
CUSTOM_PAGES = {} # Add via `#add_topic_static_page` in plugin API
|
2016-01-19 16:53:46 -05:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
def show
|
2016-01-19 16:53:46 -05:00
|
|
|
if current_user && (params[:id] == "login" || params[:id] == "signup")
|
|
|
|
return redirect_to(path "/")
|
2023-01-09 07:20:10 -05:00
|
|
|
end
|
2021-12-15 22:24:11 -05:00
|
|
|
|
2018-12-18 16:40:05 -05:00
|
|
|
if SiteSetting.login_required? && current_user.nil? && %w[faq guidelines].include?(params[:id])
|
2018-07-26 15:37:56 -04:00
|
|
|
return redirect_to path("/login")
|
|
|
|
end
|
2013-10-30 16:37:22 -04:00
|
|
|
|
2024-06-25 07:11:54 -04:00
|
|
|
rename_faq = SiteSetting.experimental_rename_faq_to_guidelines
|
|
|
|
|
|
|
|
if rename_faq
|
|
|
|
redirect_paths = %w[/rules /conduct]
|
|
|
|
redirect_paths << "/faq" if SiteSetting.faq_url.blank?
|
|
|
|
return redirect_to(path("/guidelines")) if redirect_paths.include?(request.path)
|
|
|
|
end
|
|
|
|
|
2021-12-15 22:24:11 -05:00
|
|
|
map = DEFAULT_PAGES.deep_merge(CUSTOM_PAGES)
|
2014-07-24 14:27:34 -04:00
|
|
|
@page = params[:id]
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2014-07-24 14:27:34 -04:00
|
|
|
if map.has_key?(@page)
|
|
|
|
site_setting_key = map[@page][:redirect]
|
2021-12-15 22:24:11 -05:00
|
|
|
url = SiteSetting.get(site_setting_key) if site_setting_key
|
2022-03-21 10:28:52 -04:00
|
|
|
return redirect_to(url, allow_other_host: true) if url.present?
|
2013-06-27 03:15:59 -04:00
|
|
|
end
|
2013-06-18 10:52:04 -04:00
|
|
|
|
2014-07-10 12:58:34 -04:00
|
|
|
# The /guidelines route ALWAYS shows our FAQ, ignoring the faq_url site setting.
|
2018-12-18 16:40:05 -05:00
|
|
|
@page = "faq" if @page == "guidelines"
|
2014-07-10 12:58:34 -04:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
# Don't allow paths like ".." or "/" or anything hacky like that
|
2019-05-02 18:17:27 -04:00
|
|
|
@page = @page.gsub(/[^a-z0-9\_\-]/, "")
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2014-07-24 14:27:34 -04:00
|
|
|
if map.has_key?(@page)
|
2021-12-15 22:24:11 -05:00
|
|
|
topic_id = map[@page][:topic_id]
|
|
|
|
topic_id = instance_exec(&topic_id) if topic_id.is_a?(Proc)
|
|
|
|
|
|
|
|
@topic = Topic.find_by_id(SiteSetting.get(topic_id))
|
2014-07-24 14:27:34 -04:00
|
|
|
raise Discourse::NotFound unless @topic
|
2021-12-15 22:24:11 -05:00
|
|
|
|
2024-06-25 07:11:54 -04:00
|
|
|
page_name =
|
|
|
|
if @page == "faq"
|
|
|
|
rename_faq ? "guidelines" : "faq"
|
|
|
|
else
|
|
|
|
@page
|
|
|
|
end
|
|
|
|
|
2018-11-27 11:19:59 -05:00
|
|
|
title_prefix =
|
2024-06-25 07:11:54 -04:00
|
|
|
if I18n.exists?("js.#{page_name}")
|
|
|
|
I18n.t("js.#{page_name}")
|
2018-11-27 11:19:59 -05:00
|
|
|
else
|
|
|
|
@topic.title
|
|
|
|
end
|
|
|
|
@title = "#{title_prefix} - #{SiteSetting.title}"
|
2014-07-24 14:27:34 -04:00
|
|
|
@body = @topic.posts.first.cooked
|
2021-12-15 22:24:11 -05:00
|
|
|
@faq_overridden = !SiteSetting.faq_url.blank?
|
2024-06-25 07:11:54 -04:00
|
|
|
@experimental_rename_faq_to_guidelines = rename_faq
|
2021-12-15 22:24:11 -05:00
|
|
|
|
2014-07-24 14:27:34 -04:00
|
|
|
render :show, layout: !request.xhr?, formats: [:html]
|
2013-02-05 14:16:51 -05:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2021-12-21 14:51:18 -05:00
|
|
|
@title = SiteSetting.title.dup
|
2021-12-15 22:24:11 -05:00
|
|
|
|
|
|
|
if SiteSetting.short_site_description.present?
|
|
|
|
@title << " - #{SiteSetting.short_site_description}"
|
2019-10-14 02:09:14 -04:00
|
|
|
end
|
|
|
|
|
2014-10-17 23:27:33 -04:00
|
|
|
if I18n.exists?("static.#{@page}")
|
2017-04-10 08:01:25 -04:00
|
|
|
render html: I18n.t("static.#{@page}"), layout: !request.xhr?, formats: [:html]
|
2014-10-17 23:27:33 -04:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2016-01-19 16:53:46 -05:00
|
|
|
if PAGES_WITH_EMAIL_PARAM.include?(@page) && params[:email]
|
|
|
|
cookies[:email] = { value: params[:email], expires: 1.day.from_now }
|
|
|
|
end
|
|
|
|
|
2021-04-21 05:36:32 -04:00
|
|
|
if lookup_context.find_all("static/#{@page}").any?
|
|
|
|
render "static/#{@page}", layout: !request.xhr?, formats: [:html]
|
2014-07-26 17:16:08 -04:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2019-03-04 04:34:48 -05:00
|
|
|
if MODAL_PAGES.include?(@page)
|
|
|
|
render html: nil, layout: true
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2013-05-19 20:29:49 -04:00
|
|
|
raise Discourse::NotFound
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2013-03-13 10:22:56 -04:00
|
|
|
# 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)
|
|
|
|
|
2015-03-08 20:45:36 -04:00
|
|
|
destination = path("/")
|
2014-08-28 17:45:13 -04:00
|
|
|
|
2019-06-17 07:14:30 -04:00
|
|
|
redirect_location = params[:redirect]
|
|
|
|
if redirect_location.present? && !redirect_location.is_a?(String)
|
|
|
|
raise Discourse::InvalidParameters.new(:redirect)
|
2024-11-27 11:22:45 -05:00
|
|
|
elsif redirect_location.present? &&
|
|
|
|
begin
|
|
|
|
forum_uri = URI(Discourse.base_url)
|
|
|
|
uri = URI(redirect_location)
|
|
|
|
|
|
|
|
if uri.path.present? && !uri.path.starts_with?(login_path) &&
|
|
|
|
(uri.host.blank? || uri.host == forum_uri.host) &&
|
|
|
|
uri.path =~ %r{\A\/{1}[^\.\s]*\z}
|
|
|
|
destination = "#{uri.path}#{uri.query ? "?#{uri.query}" : ""}"
|
|
|
|
end
|
|
|
|
rescue URI::Error
|
|
|
|
# Do nothing if the URI is invalid
|
|
|
|
end
|
2014-08-28 17:45:13 -04:00
|
|
|
end
|
|
|
|
|
2024-07-15 02:39:37 -04:00
|
|
|
redirect_to(destination, allow_other_host: false)
|
2013-06-04 18:34:54 -04:00
|
|
|
end
|
2014-05-18 18:46:09 -04:00
|
|
|
|
2024-10-15 22:09:07 -04:00
|
|
|
FAVICON = -"favicon"
|
2018-02-24 06:35:57 -05:00
|
|
|
|
2019-01-01 21:15:20 -05:00
|
|
|
# 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
|
2015-08-24 21:54:23 -04:00
|
|
|
def favicon
|
2018-03-05 23:20:39 -05:00
|
|
|
is_asset_path
|
|
|
|
|
2017-11-26 22:50:57 -05:00
|
|
|
hijack do
|
2019-05-01 09:44:45 -04:00
|
|
|
data =
|
|
|
|
DistributedMemoizer.memoize("FAVICON#{SiteIconManager.favicon_url}", 60 * 30) do
|
|
|
|
favicon = SiteIconManager.favicon
|
2019-03-13 16:17:36 -04:00
|
|
|
next "" unless favicon
|
2023-01-09 07:20:10 -05:00
|
|
|
|
2019-03-13 16:17:36 -04:00
|
|
|
if Discourse.store.external?
|
|
|
|
begin
|
|
|
|
file =
|
|
|
|
FileHelper.download(
|
2019-09-04 04:20:55 -04:00
|
|
|
Discourse.store.cdn_url(favicon.url),
|
2019-03-13 16:17:36 -04:00
|
|
|
max_file_size: favicon.filesize,
|
|
|
|
tmp_file_name: FAVICON,
|
|
|
|
follow_redirect: true,
|
|
|
|
)
|
2023-01-09 07:20:10 -05:00
|
|
|
|
2019-03-13 16:17:36 -04:00
|
|
|
file&.read || ""
|
|
|
|
rescue => e
|
2024-05-22 21:29:08 -04:00
|
|
|
ProblemCheckTracker[:bad_favicon_url].problem!
|
2020-07-28 21:42:55 -04:00
|
|
|
Rails.logger.debug("Failed to fetch favicon #{favicon.url}: #{e}\n#{e.backtrace}")
|
2019-06-05 02:43:40 -04:00
|
|
|
""
|
2019-03-13 16:17:36 -04:00
|
|
|
ensure
|
|
|
|
file&.unlink
|
2023-01-09 07:20:10 -05:00
|
|
|
end
|
|
|
|
else
|
2019-03-13 16:17:36 -04:00
|
|
|
File.read(Rails.root.join("public", favicon.url[1..-1]))
|
|
|
|
end
|
2017-11-26 22:50:57 -05:00
|
|
|
end
|
2015-08-24 22:05:15 -04:00
|
|
|
|
2017-11-26 22:50:57 -05: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
|
2022-12-13 14:03:53 -05:00
|
|
|
response.headers["Last-Modified"] = Time.new(2000, 01, 01).httpdate
|
2017-11-26 22:50:57 -05:00
|
|
|
render body: data, content_type: "image/png"
|
|
|
|
end
|
2015-08-24 21:54:23 -04:00
|
|
|
end
|
2016-12-04 21:57:09 -05:00
|
|
|
end
|
|
|
|
|
2017-03-20 15:59:06 -04:00
|
|
|
def cdn_asset
|
2018-03-05 23:20:39 -05:00
|
|
|
is_asset_path
|
|
|
|
|
2017-03-20 15:59:06 -04:00
|
|
|
serve_asset
|
2015-08-24 21:54:23 -04:00
|
|
|
end
|
|
|
|
|
2017-11-22 20:02:01 -05:00
|
|
|
def service_worker_asset
|
2018-03-05 23:20:39 -05:00
|
|
|
is_asset_path
|
|
|
|
|
2017-11-22 20:02:01 -05:00
|
|
|
respond_to do |format|
|
|
|
|
format.js do
|
2018-02-14 21:24:39 -05:00
|
|
|
# https://github.com/w3c/ServiceWorker/blob/master/explainer.md#updating-a-service-worker
|
|
|
|
# Maximum cache that the service worker will respect is 24 hours.
|
2018-05-14 15:23:54 -04:00
|
|
|
# However, ensure that these may be cached and served for longer on servers.
|
|
|
|
immutable_for 1.year
|
2017-12-27 16:31:01 -05:00
|
|
|
|
2018-06-11 14:18:34 -04: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
|
2022-02-14 07:47:56 -05:00
|
|
|
content = Rails.application.assets_manifest.find_sources("service-worker.js").first
|
2023-01-09 07:20:10 -05:00
|
|
|
|
2022-05-11 08:42:34 -04:00
|
|
|
base_url = File.dirname(helpers.script_asset_path("service-worker"))
|
2022-02-14 07:47:56 -05:00
|
|
|
content =
|
|
|
|
content.sub(%r{^//# sourceMappingURL=(service-worker-.+\.map)$}) do
|
2022-05-11 08:42:34 -04:00
|
|
|
"//# sourceMappingURL=#{base_url}/#{Regexp.last_match(1)}"
|
2017-11-28 02:38:58 -05:00
|
|
|
end
|
|
|
|
render(plain: content, content_type: "application/javascript")
|
2017-11-22 20:02:01 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-03-20 15:59:06 -04:00
|
|
|
protected
|
2014-10-22 09:39:51 -04:00
|
|
|
|
2017-03-20 15:59:06 -04:00
|
|
|
def serve_asset(suffix = nil)
|
|
|
|
path = File.expand_path(Rails.root + "public/assets/#{params[:path]}#{suffix}")
|
2014-07-10 03:29:38 -04:00
|
|
|
|
|
|
|
# SECURITY what if path has /../
|
2014-10-22 09:39:51 -04:00
|
|
|
raise Discourse::NotFound unless path.start_with?(Rails.root.to_s + "/public/assets")
|
2014-07-10 03:29:38 -04:00
|
|
|
|
2014-10-21 00:59:16 -04:00
|
|
|
response.headers["Expires"] = 1.year.from_now.httpdate
|
2015-02-16 17:54:45 -05:00
|
|
|
response.headers["Access-Control-Allow-Origin"] = params[:origin] if params[:origin]
|
2014-10-21 00:59:16 -04:00
|
|
|
|
2014-07-08 00:48:20 -04:00
|
|
|
begin
|
|
|
|
response.headers["Last-Modified"] = File.ctime(path).httpdate
|
|
|
|
rescue Errno::ENOENT
|
2017-03-20 15:59:06 -04:00
|
|
|
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
|
|
|
|
|
2017-04-10 08:01:25 -04:00
|
|
|
render plain: "can not find #{params[:path]}", status: 404
|
2017-03-20 15:59:06 -04:00
|
|
|
return
|
|
|
|
end
|
2014-07-08 00:48:20 -04:00
|
|
|
end
|
2014-10-21 00:59:16 -04:00
|
|
|
|
2017-03-20 15:59:06 -04:00
|
|
|
response.headers["Content-Length"] = File.size(path).to_s
|
|
|
|
|
|
|
|
yield if block_given?
|
2014-07-10 02:32:06 -04:00
|
|
|
|
2017-03-20 15:59:06 -04:00
|
|
|
immutable_for 1.year
|
2017-02-23 18:26:40 -05:00
|
|
|
|
2017-03-20 15:59:06 -04:00
|
|
|
# disable NGINX mucking with transfer
|
2014-07-10 03:01:21 -04:00
|
|
|
request.env["sendfile.type"] = ""
|
2017-03-20 15:59:06 -04:00
|
|
|
|
|
|
|
opts = { disposition: nil }
|
2017-03-20 17:07:32 -04:00
|
|
|
opts[:type] = "application/javascript" if params[:path] =~ /\.js$/
|
2014-05-18 18:46:09 -04:00
|
|
|
send_file(path, opts)
|
|
|
|
end
|
2013-02-07 10:45:24 -05:00
|
|
|
end
|