0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-05 17:25:34 +08:00
discourse/lib/middleware/crawler_hooks.rb
Natalie Tay 16919c4fd4
UX: Prevent incomplete crawler localization settings (#41297)
This PR makes sure crawler localization URLs only work when anonymous
users can resolve the locale from the URL param. This blocks enabling
the crawler param without that prerequisite and keeps hreflang,
canonical URLs, and crawler link rewriting behind the same guard.

/406195/4
2026-07-01 12:13:22 +08:00

65 lines
1.8 KiB
Ruby
Vendored

# frozen_string_literal: true
module Middleware
class CrawlerHooks
NON_LOCALIZABLE_PATH_PREFIXES = %w[/uploads/ /secure-uploads/ /secure-media-uploads/].freeze
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
status, headers, response = @app.call(env)
if status == 200 && headers["X-Discourse-Crawler-View"] &&
headers["Content-Type"]&.include?("text/html") &&
!non_localizable_path?(request_path_without_base_path(request)) &&
ContentLocalization.crawler_locale_param_enabled?
response = transform_response(request:, response:)
end
[status, headers, response]
end
private
def transform_response(request:, response:)
locale = request.params[Discourse::LOCALE_PARAM]
if ContentLocalization.crawler_locale_param_enabled? && locale.present?
html_fragment = Nokogiri::HTML5.parse(response.body)
html_fragment
.css("a[href^='/'], a[href^='#{Discourse.base_url}']")
.each do |link|
uri = Addressable::URI.parse(link["href"])
next if non_localizable_path?(uri.path)
uri.query_values = (uri.query_values || {}).merge(Discourse::LOCALE_PARAM => locale)
link["href"] = uri.to_s
end
transformed_html = html_fragment.to_html
return [transformed_html || response]
end
response
end
def non_localizable_path?(path)
return false if path.blank?
NON_LOCALIZABLE_PATH_PREFIXES.any? { |prefix| path.start_with?(prefix) }
end
def request_path_without_base_path(request)
path = request.path
base_path = Discourse.base_path
if base_path.present? && path.start_with?(base_path)
path.delete_prefix(base_path)
else
path
end
end
end
end