2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-02-06 03:16:51 +08:00
|
|
|
require 'ipaddr'
|
2015-02-23 03:47:18 +08:00
|
|
|
require 'url_helper'
|
|
|
|
|
2013-02-06 03:16:51 +08:00
|
|
|
class TopicLinkClick < ActiveRecord::Base
|
|
|
|
belongs_to :topic_link, counter_cache: :clicks
|
|
|
|
belongs_to :user
|
|
|
|
|
|
|
|
validates_presence_of :topic_link_id
|
|
|
|
|
2020-09-28 11:52:05 +08:00
|
|
|
ALLOWED_REDIRECT_HOSTNAMES = Set.new(%W{www.youtube.com youtu.be})
|
|
|
|
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
|
|
|
deprecate_constant 'WHITELISTED_REDIRECT_HOSTNAMES', 'TopicLinkClick::ALLOWED_REDIRECT_HOSTNAMES'
|
2015-04-02 04:59:25 +08:00
|
|
|
|
2013-02-06 03:16:51 +08:00
|
|
|
# Create a click from a URL and post_id
|
|
|
|
def self.create_from(args = {})
|
2015-09-26 02:07:04 +08:00
|
|
|
url = args[:url][0...TopicLink.max_url_length]
|
2015-02-23 03:47:18 +08:00
|
|
|
return nil if url.blank?
|
|
|
|
|
2018-12-11 15:03:13 +08:00
|
|
|
uri = UrlHelper.relaxed_parse(url)
|
2015-02-23 03:47:18 +08:00
|
|
|
urls = Set.new
|
|
|
|
urls << url
|
|
|
|
if url =~ /^http/
|
|
|
|
urls << url.sub(/^https/, 'http')
|
|
|
|
urls << url.sub(/^http:/, 'https:')
|
2015-06-12 18:02:36 +08:00
|
|
|
urls << UrlHelper.schemaless(url)
|
2014-01-15 03:59:51 +08:00
|
|
|
end
|
2015-06-12 18:02:36 +08:00
|
|
|
urls << UrlHelper.absolute_without_cdn(url)
|
2015-02-23 03:47:18 +08:00
|
|
|
urls << uri.path if uri.try(:host) == Discourse.current_hostname
|
2016-08-24 00:08:37 +08:00
|
|
|
|
|
|
|
query = url.index('?')
|
|
|
|
unless query.nil?
|
|
|
|
endpos = url.index('#') || url.size
|
|
|
|
urls << url[0..query - 1] + url[endpos..-1]
|
|
|
|
end
|
2015-02-23 03:47:18 +08:00
|
|
|
|
2017-12-14 02:58:36 +08:00
|
|
|
# link can have query params, and analytics can add more to the end:
|
|
|
|
i = url.length
|
2017-12-14 04:47:42 +08:00
|
|
|
while i = url.rindex('&', i - 1)
|
2017-12-14 02:58:36 +08:00
|
|
|
urls << url[0...i]
|
|
|
|
end
|
|
|
|
|
2015-08-05 10:15:08 +08:00
|
|
|
# add a cdn link
|
2016-06-29 03:52:38 +08:00
|
|
|
if uri
|
|
|
|
if Discourse.asset_host.present?
|
2018-03-28 16:20:08 +08:00
|
|
|
cdn_uri = begin
|
|
|
|
URI.parse(Discourse.asset_host)
|
2018-08-14 18:23:32 +08:00
|
|
|
rescue URI::Error
|
2018-03-28 16:20:08 +08:00
|
|
|
end
|
|
|
|
|
2016-06-29 03:52:38 +08:00
|
|
|
if cdn_uri && cdn_uri.hostname == uri.hostname && uri.path.starts_with?(cdn_uri.path)
|
|
|
|
is_cdn_link = true
|
2016-06-30 22:55:01 +08:00
|
|
|
urls << uri.path[cdn_uri.path.length..-1]
|
2016-06-29 03:52:38 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-10-06 13:20:01 +08:00
|
|
|
if SiteSetting.Upload.s3_cdn_url.present?
|
2018-03-28 16:20:08 +08:00
|
|
|
cdn_uri = begin
|
|
|
|
URI.parse(SiteSetting.Upload.s3_cdn_url)
|
2018-08-14 18:23:32 +08:00
|
|
|
rescue URI::Error
|
2018-03-28 16:20:08 +08:00
|
|
|
end
|
|
|
|
|
2016-06-29 03:52:38 +08:00
|
|
|
if cdn_uri && cdn_uri.hostname == uri.hostname && uri.path.starts_with?(cdn_uri.path)
|
|
|
|
is_cdn_link = true
|
2016-06-30 22:55:01 +08:00
|
|
|
path = uri.path[cdn_uri.path.length..-1]
|
2016-06-29 03:52:38 +08:00
|
|
|
urls << path
|
|
|
|
urls << "#{Discourse.store.absolute_base_url}#{path}"
|
|
|
|
end
|
2015-08-05 10:15:08 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-02-23 03:47:18 +08:00
|
|
|
link = TopicLink.select([:id, :user_id])
|
|
|
|
|
|
|
|
# test for all possible URLs
|
|
|
|
link = link.where(Array.new(urls.count, "url = ?").join(" OR "), *urls)
|
2014-01-15 03:59:51 +08:00
|
|
|
|
2013-02-06 03:16:51 +08:00
|
|
|
# Find the forum topic link
|
|
|
|
link = link.where(post_id: args[:post_id]) if args[:post_id].present?
|
|
|
|
|
|
|
|
# If we don't have a post, just find the first occurance of the link
|
|
|
|
link = link.where(topic_id: args[:topic_id]) if args[:topic_id].present?
|
|
|
|
link = link.first
|
|
|
|
|
2015-02-23 03:47:18 +08:00
|
|
|
# If no link is found...
|
2013-07-28 01:18:37 +08:00
|
|
|
unless link.present?
|
2015-02-23 03:47:18 +08:00
|
|
|
# ... return the url for relative links or when using the same host
|
2016-06-30 22:55:01 +08:00
|
|
|
return url if url =~ /^\/[^\/]/ || uri.try(:host) == Discourse.current_hostname
|
2014-11-21 03:01:48 +08:00
|
|
|
|
2015-02-23 03:47:18 +08:00
|
|
|
# If we have it somewhere else on the site, just allow the redirect.
|
|
|
|
# This is likely due to a onebox of another topic.
|
|
|
|
link = TopicLink.find_by(url: url)
|
2015-04-02 04:59:25 +08:00
|
|
|
return link.url if link.present?
|
|
|
|
|
2015-05-06 09:22:53 +08:00
|
|
|
return nil unless uri
|
|
|
|
|
2020-07-27 08:23:54 +08:00
|
|
|
# Only redirect to allowlisted hostnames
|
2020-09-28 11:52:05 +08:00
|
|
|
return url if ALLOWED_REDIRECT_HOSTNAMES.include?(uri.hostname) || is_cdn_link
|
2015-08-05 09:49:11 +08:00
|
|
|
|
|
|
|
return nil
|
2013-07-28 01:18:37 +08:00
|
|
|
end
|
|
|
|
|
2015-02-23 03:47:18 +08:00
|
|
|
return url if args[:user_id] && link.user_id == args[:user_id]
|
2013-07-27 05:29:43 +08:00
|
|
|
|
2013-02-06 03:16:51 +08:00
|
|
|
# Rate limit the click counts to once in 24 hours
|
|
|
|
rate_key = "link-clicks:#{link.id}:#{args[:user_id] || args[:ip]}"
|
2019-12-03 17:05:53 +08:00
|
|
|
if Discourse.redis.setnx(rate_key, "1")
|
|
|
|
Discourse.redis.expire(rate_key, 1.day.to_i)
|
2018-05-22 03:04:55 +08:00
|
|
|
args[:ip] = nil if args[:user_id]
|
2013-06-25 06:30:32 +08:00
|
|
|
create!(topic_link_id: link.id, user_id: args[:user_id], ip_address: args[:ip])
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
2015-02-23 03:47:18 +08:00
|
|
|
url
|
2013-02-06 03:16:51 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
2013-05-24 10:48:32 +08:00
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: topic_link_clicks
|
|
|
|
#
|
|
|
|
# id :integer not null, primary key
|
|
|
|
# topic_link_id :integer not null
|
|
|
|
# user_id :integer
|
2014-08-27 13:19:25 +08:00
|
|
|
# created_at :datetime not null
|
|
|
|
# updated_at :datetime not null
|
2018-05-22 03:04:55 +08:00
|
|
|
# ip_address :inet
|
2013-05-24 10:48:32 +08:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
2020-10-28 02:12:33 +08:00
|
|
|
# index_forum_thread_link_clicks_on_forum_thread_link_id (topic_link_id)
|
2013-05-24 10:48:32 +08:00
|
|
|
#
|