discourse/lib/inline_oneboxer.rb
Bianca Nenciu 7328a2bfb0
FIX: Apply censored words to inline onebox (#16873)
Censored watched words were not censored inside the title of an inline
oneboxes. Malicious users could exploit this behaviour to insert bad
words. The same issue has been fixed for regular Oneboxes in commit
d184fe59ca.
2022-05-25 14:51:47 +03:00

129 lines
3.4 KiB
Ruby

# frozen_string_literal: true
class InlineOneboxer
MIN_TITLE_LENGTH = 2
def initialize(urls, opts = nil)
@urls = urls
@opts = opts || {}
end
def process
@urls.map { |url| InlineOneboxer.lookup(url, @opts) }.compact
end
def self.invalidate(url)
Discourse.cache.delete(cache_key(url))
end
def self.cache_lookup(url)
Discourse.cache.read(cache_key(url))
end
def self.local_handlers
@local_handlers ||= {}
end
def self.register_local_handler(controller, &handler)
local_handlers[controller] = handler
end
def self.lookup(url, opts = nil)
opts ||= {}
opts = opts.with_indifferent_access
unless opts[:skip_cache] || opts[:invalidate]
cached = cache_lookup(url)
return cached if cached.present?
end
return unless url
if route = Discourse.route_for(url)
if route[:controller] == "topics"
if topic = Oneboxer.local_topic(url, route, opts)
opts[:skip_cache] = true
post_number = [route[:post_number].to_i, topic.highest_post_number].min
if post_number > 1
opts[:post_number] = post_number
opts[:post_author] = post_author_for_title(topic, post_number)
end
return onebox_for(url, topic.title, opts)
else
# not permitted to see topic
return nil
end
elsif handler = local_handlers[route[:controller]]
return handler.call(url, route)
end
end
always_allow = SiteSetting.enable_inline_onebox_on_all_domains
allowed_domains = SiteSetting.allowed_inline_onebox_domains&.split('|') unless always_allow
if always_allow || allowed_domains
uri = begin
URI(url)
rescue URI::Error
end
if uri.present? &&
uri.hostname.present? &&
(always_allow || allowed_domains.include?(uri.hostname)) &&
!Onebox::DomainChecker.is_blocked?(uri.hostname)
if SiteSetting.block_onebox_on_redirect
max_redirects = 0
end
title = RetrieveTitle.crawl(
url,
max_redirects: max_redirects,
initial_https_redirect_ignore_limit: SiteSetting.block_onebox_on_redirect
)
title = nil if title && title.length < MIN_TITLE_LENGTH
return onebox_for(url, title, opts)
end
end
nil
end
private
def self.onebox_for(url, title, opts)
title = title && Emoji.gsub_emoji_to_unicode(title)
if title && opts[:post_number]
title += " - "
if opts[:post_author]
title += I18n.t(
"inline_oneboxer.topic_page_title_post_number_by_user",
post_number: opts[:post_number],
username: opts[:post_author]
)
else
title += I18n.t(
"inline_oneboxer.topic_page_title_post_number",
post_number: opts[:post_number]
)
end
end
onebox = { url: url, title: title && Emoji.gsub_emoji_to_unicode(title) }
onebox[:title] = WordWatcher.censor_text(onebox[:title])
Discourse.cache.write(cache_key(url), onebox, expires_in: 1.day) if !opts[:skip_cache]
onebox
end
def self.cache_key(url)
"inline_onebox:#{url}"
end
def self.post_author_for_title(topic, post_number)
guardian = Guardian.new
post = topic.posts.find_by(post_number: post_number)
author = post&.user
if author && guardian.can_see_post?(post) && post.post_type == Post.types[:regular]
author.username
end
end
end