2017-05-22 16:42:19 -04:00
|
|
|
require_dependency "onebox/discourse_onebox_sanitize_config"
|
|
|
|
require_dependency 'final_destination'
|
|
|
|
|
2016-10-24 19:25:44 -04:00
|
|
|
Dir["#{Rails.root}/lib/onebox/engine/*_onebox.rb"].sort.each { |f| require f }
|
2014-02-25 13:35:08 -05:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
module Oneboxer
|
2013-04-29 22:43:21 -04:00
|
|
|
# keep reloaders happy
|
|
|
|
unless defined? Oneboxer::Result
|
|
|
|
Result = Struct.new(:doc, :changed) do
|
|
|
|
def to_html
|
|
|
|
doc.to_html
|
|
|
|
end
|
2013-04-10 03:52:38 -04:00
|
|
|
|
2013-04-29 22:43:21 -04:00
|
|
|
def changed?
|
|
|
|
changed
|
|
|
|
end
|
2013-04-10 03:52:38 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-26 15:38:23 -04:00
|
|
|
def self.ignore_redirects
|
2017-07-27 21:20:09 -04:00
|
|
|
@ignore_redirects ||= ['http://www.dropbox.com', 'http://store.steampowered.com', Discourse.base_url]
|
2017-06-26 15:38:23 -04:00
|
|
|
end
|
|
|
|
|
2017-08-08 05:44:27 -04:00
|
|
|
def self.force_get_hosts
|
|
|
|
@force_get_hosts ||= ['http://us.battle.net']
|
|
|
|
end
|
|
|
|
|
2017-07-27 21:20:09 -04:00
|
|
|
def self.preview(url, options = nil)
|
2014-01-27 15:09:09 -05:00
|
|
|
options ||= {}
|
2016-12-19 18:31:10 -05:00
|
|
|
invalidate(url) if options[:invalidate_oneboxes]
|
2018-02-13 18:39:44 -05:00
|
|
|
onebox_raw(url, options)[:preview]
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2017-07-27 21:20:09 -04:00
|
|
|
def self.onebox(url, options = nil)
|
2014-01-27 15:09:09 -05:00
|
|
|
options ||= {}
|
2016-12-19 18:31:10 -05:00
|
|
|
invalidate(url) if options[:invalidate_oneboxes]
|
2018-02-13 18:39:44 -05:00
|
|
|
onebox_raw(url, options)[:onebox]
|
2014-03-17 22:12:58 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.cached_onebox(url)
|
2014-04-01 00:29:14 -04:00
|
|
|
if c = Rails.cache.read(onebox_cache_key(url))
|
|
|
|
c[:onebox]
|
|
|
|
end
|
2014-05-28 03:15:10 -04:00
|
|
|
rescue => e
|
|
|
|
invalidate(url)
|
|
|
|
Rails.logger.warn("invalid cached onebox for #{url} #{e}")
|
|
|
|
""
|
2014-03-17 22:12:58 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.cached_preview(url)
|
2014-04-01 00:29:14 -04:00
|
|
|
if c = Rails.cache.read(onebox_cache_key(url))
|
|
|
|
c[:preview]
|
|
|
|
end
|
2014-05-28 03:15:10 -04:00
|
|
|
rescue => e
|
|
|
|
invalidate(url)
|
|
|
|
Rails.logger.warn("invalid cached preview for #{url} #{e}")
|
|
|
|
""
|
2013-08-14 11:05:53 -04:00
|
|
|
end
|
|
|
|
|
2014-01-28 13:18:19 -05:00
|
|
|
def self.invalidate(url)
|
2014-03-17 22:12:58 -04:00
|
|
|
Rails.cache.delete(onebox_cache_key(url))
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
2014-01-28 13:18:19 -05:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
# Parse URLs out of HTML, returning the document when finished.
|
|
|
|
def self.each_onebox_link(string_or_doc)
|
|
|
|
doc = string_or_doc
|
2013-04-10 03:52:38 -04:00
|
|
|
doc = Nokogiri::HTML::fragment(doc) if doc.is_a?(String)
|
2013-02-05 14:16:51 -05:00
|
|
|
|
|
|
|
onebox_links = doc.search("a.onebox")
|
|
|
|
if onebox_links.present?
|
|
|
|
onebox_links.each do |link|
|
2016-11-03 17:48:32 -04:00
|
|
|
yield(link['href'], link) if link['href'].present?
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
doc
|
|
|
|
end
|
|
|
|
|
2017-07-27 21:20:09 -04:00
|
|
|
def self.apply(string_or_doc, args = nil)
|
2013-04-10 03:52:38 -04:00
|
|
|
doc = string_or_doc
|
|
|
|
doc = Nokogiri::HTML::fragment(doc) if doc.is_a?(String)
|
|
|
|
changed = false
|
|
|
|
|
2016-12-19 18:31:10 -05:00
|
|
|
each_onebox_link(doc) do |url, element|
|
2018-02-13 18:39:44 -05:00
|
|
|
onebox, _ = yield(url, element)
|
2013-04-10 03:52:38 -04:00
|
|
|
if onebox
|
|
|
|
parsed_onebox = Nokogiri::HTML::fragment(onebox)
|
2013-05-01 02:37:27 -04:00
|
|
|
next unless parsed_onebox.children.count > 0
|
2013-04-10 03:52:38 -04:00
|
|
|
|
|
|
|
# special logic to strip empty p elements
|
2018-02-13 18:39:44 -05:00
|
|
|
if element&.parent&.node_name&.downcase == "p" && element&.parent&.children&.count == 1
|
2013-05-01 02:37:27 -04:00
|
|
|
element = element.parent
|
2013-04-10 03:52:38 -04:00
|
|
|
end
|
2018-02-13 18:39:44 -05:00
|
|
|
|
2013-04-10 03:52:38 -04:00
|
|
|
changed = true
|
|
|
|
element.swap parsed_onebox.to_html
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
Result.new(doc, changed)
|
|
|
|
end
|
|
|
|
|
2016-12-19 18:31:10 -05:00
|
|
|
def self.is_previewing?(user_id)
|
|
|
|
$redis.get(preview_key(user_id)) == "1"
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.preview_onebox!(user_id)
|
|
|
|
$redis.setex(preview_key(user_id), 1.minute, "1")
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.onebox_previewed!(user_id)
|
|
|
|
$redis.del(preview_key(user_id))
|
|
|
|
end
|
|
|
|
|
2017-01-05 21:01:14 -05:00
|
|
|
def self.engine(url)
|
|
|
|
Onebox::Matcher.new(url).oneboxed
|
|
|
|
end
|
|
|
|
|
2014-03-17 22:12:58 -04:00
|
|
|
private
|
|
|
|
|
2016-12-19 18:31:10 -05:00
|
|
|
def self.preview_key(user_id)
|
2016-12-20 05:18:47 -05:00
|
|
|
"onebox:preview:#{user_id}"
|
2016-12-19 18:31:10 -05:00
|
|
|
end
|
|
|
|
|
2016-10-24 06:46:22 -04:00
|
|
|
def self.blank_onebox
|
|
|
|
{ preview: "", onebox: "" }
|
2014-04-09 16:57:45 -04:00
|
|
|
end
|
|
|
|
|
2016-10-24 06:46:22 -04:00
|
|
|
def self.onebox_cache_key(url)
|
|
|
|
"onebox__#{url}"
|
|
|
|
end
|
2015-08-23 20:43:07 -04:00
|
|
|
|
2018-02-13 18:39:44 -05:00
|
|
|
def self.onebox_raw(url, opts = {})
|
|
|
|
local_onebox(url, opts) || external_onebox(url)
|
|
|
|
rescue => e
|
|
|
|
# no point warning here, just cause we have an issue oneboxing a url
|
|
|
|
# we can later hunt for failed oneboxes by searching logs if needed
|
|
|
|
Rails.logger.info("Failed to onebox #{url} #{e} #{e.backtrace}")
|
|
|
|
# return a blank hash, so rest of the code works
|
|
|
|
blank_onebox
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.local_onebox(url, opts = {})
|
|
|
|
return unless route = Discourse.route_for(url)
|
|
|
|
|
|
|
|
html =
|
|
|
|
case route[:controller]
|
|
|
|
when "uploads" then local_upload_html(url)
|
|
|
|
when "topics" then local_topic_html(url, route, opts)
|
|
|
|
when "users" then local_user_html(url, route)
|
|
|
|
end
|
|
|
|
|
|
|
|
html = html.presence || "<a href='#{url}'>#{url}</a>"
|
|
|
|
{ onebox: html, preview: html }
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.local_upload_html(url)
|
|
|
|
case File.extname(URI(url).path || "")
|
|
|
|
when /^\.(mov|mp4|webm|ogv)$/i
|
|
|
|
"<video width='100%' height='100%' controls><source src='#{url}'><a href='#{url}'>#{url}</a></video>"
|
|
|
|
when /^\.(mp3|ogg|wav|m4a)$/i
|
|
|
|
"<audio controls><source src='#{url}'><a href='#{url}'>#{url}</a></audio>"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.local_topic_html(url, route, opts)
|
|
|
|
return unless current_user = User.find_by(id: opts[:user_id])
|
2018-02-15 12:13:57 -05:00
|
|
|
|
|
|
|
if current_category = Category.find_by(id: opts[:category_id])
|
|
|
|
return unless Guardian.new(current_user).can_see_category?(current_category)
|
|
|
|
end
|
2018-02-13 18:39:44 -05:00
|
|
|
|
2018-02-19 16:40:14 -05:00
|
|
|
if current_topic = Topic.find_by(id: opts[:topic_id])
|
|
|
|
return unless Guardian.new(current_user).can_see_topic?(current_topic)
|
|
|
|
end
|
|
|
|
|
2018-02-15 16:56:13 -05:00
|
|
|
topic = Topic.find_by(id: route[:topic_id])
|
2018-02-13 18:39:44 -05:00
|
|
|
|
2018-02-15 16:56:13 -05:00
|
|
|
return unless topic
|
|
|
|
return if topic.private_message?
|
2018-02-15 16:00:06 -05:00
|
|
|
|
2018-02-15 16:56:13 -05:00
|
|
|
if current_category&.id != topic.category_id
|
|
|
|
return unless Guardian.new.can_see_topic?(topic)
|
|
|
|
end
|
|
|
|
|
|
|
|
post_number = route[:post_number].to_i
|
2018-02-16 05:21:11 -05:00
|
|
|
|
|
|
|
post = post_number > 1 ?
|
|
|
|
topic.posts.where(post_number: post_number).first :
|
|
|
|
topic.ordered_posts.first
|
2018-02-15 16:56:13 -05:00
|
|
|
|
|
|
|
return if !post || post.hidden || post.post_type != Post.types[:regular]
|
2018-02-13 18:39:44 -05:00
|
|
|
|
2018-02-19 16:40:14 -05:00
|
|
|
if post_number > 1 && current_topic&.id == topic.id
|
2018-02-13 18:39:44 -05:00
|
|
|
excerpt = post.excerpt(SiteSetting.post_onebox_maxlength)
|
|
|
|
excerpt.gsub!(/[\r\n]+/, " ")
|
|
|
|
excerpt.gsub!("[/quote]", "[quote]") # don't break my quote
|
|
|
|
|
|
|
|
quote = "[quote=\"#{post.user.username}, topic:#{topic.id}, post:#{post.post_number}\"]\n#{excerpt}\n[/quote]"
|
|
|
|
|
|
|
|
PrettyText.cook(quote)
|
|
|
|
else
|
|
|
|
args = {
|
|
|
|
topic_id: topic.id,
|
2018-02-20 13:49:39 -05:00
|
|
|
avatar: PrettyText.avatar_img(post.user.avatar_template, "tiny"),
|
2018-02-13 18:39:44 -05:00
|
|
|
original_url: url,
|
|
|
|
title: PrettyText.unescape_emoji(CGI::escapeHTML(topic.title)),
|
|
|
|
category_html: CategoryBadge.html_for(topic.category),
|
2018-02-15 16:56:13 -05:00
|
|
|
quote: post.excerpt(SiteSetting.post_onebox_maxlength),
|
2018-02-13 18:39:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
template = File.read("#{Rails.root}/lib/onebox/templates/discourse_topic_onebox.hbs")
|
|
|
|
Mustache.render(template, args)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.local_user_html(url, route)
|
|
|
|
username = route[:username] || ""
|
|
|
|
|
|
|
|
if user = User.find_by(username_lower: username.downcase)
|
|
|
|
args = {
|
|
|
|
user_id: user.id,
|
|
|
|
username: user.username,
|
|
|
|
avatar: PrettyText.avatar_img(user.avatar_template, "extra_large"),
|
|
|
|
name: user.name,
|
|
|
|
bio: user.user_profile.bio_excerpt(230),
|
|
|
|
location: user.user_profile.location,
|
|
|
|
joined: I18n.t('joined'),
|
|
|
|
created_at: user.created_at.strftime(I18n.t('datetime_formats.formats.date_only')),
|
|
|
|
website: user.user_profile.website,
|
|
|
|
website_name: UserSerializer.new(user).website_name,
|
|
|
|
original_url: url
|
|
|
|
}
|
|
|
|
|
|
|
|
template = File.read("#{Rails.root}/lib/onebox/templates/discourse_user_onebox.hbs")
|
|
|
|
Mustache.render(template, args)
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.external_onebox(url)
|
2016-10-24 06:46:22 -04:00
|
|
|
Rails.cache.fetch(onebox_cache_key(url), expires_in: 1.day) do
|
2017-08-08 05:44:27 -04:00
|
|
|
fd = FinalDestination.new(url, ignore_redirects: ignore_redirects, force_get_hosts: force_get_hosts)
|
2017-06-06 15:02:11 -04:00
|
|
|
uri = fd.resolve
|
2016-10-24 06:46:22 -04:00
|
|
|
return blank_onebox if uri.blank? || SiteSetting.onebox_domains_blacklist.include?(uri.hostname)
|
2017-12-18 12:31:41 -05:00
|
|
|
|
2017-06-06 15:02:11 -04:00
|
|
|
options = {
|
|
|
|
cache: {},
|
|
|
|
max_width: 695,
|
|
|
|
sanitize_config: Sanitize::Config::DISCOURSE_ONEBOX
|
|
|
|
}
|
|
|
|
|
|
|
|
options[:cookie] = fd.cookie if fd.cookie
|
|
|
|
|
2017-12-18 12:31:41 -05:00
|
|
|
if Rails.env.development? && SiteSetting.port.to_i > 0
|
|
|
|
Onebox.options = { allowed_ports: [80, 443, SiteSetting.port.to_i] }
|
|
|
|
end
|
|
|
|
|
2017-06-06 16:39:15 -04:00
|
|
|
r = Onebox.preview(uri.to_s, options)
|
2017-12-18 12:31:41 -05:00
|
|
|
|
2018-02-13 18:39:44 -05:00
|
|
|
{ onebox: r.to_s, preview: r&.placeholder_html.to_s }
|
2016-10-24 06:46:22 -04:00
|
|
|
end
|
|
|
|
end
|
2014-03-17 22:12:58 -04:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|