masto-fe/app/services/resolve_url_service.rb
ThibG b1feb47055
Improve searching for private toots from URL (#14856)
* Improve searching for private toots from URL

Most of the time, when sharing toots, people use the toot URL rather than
the toot URI, which makes sense since it is the user-facing URL.

In Mastodon's case, the URL and URI are different, and Mastodon does not
have an index on URL, which means searching a private toot by URL is done
with a slow query that will only succeed for very recent toots.

This change gets rid of the slow query, and attempts to guess the URI from
URL instead, as Mastodon's are predictable.

* Add tests

* Only return status with guessed uri if url matches

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
2020-12-17 06:51:49 +01:00

103 lines
2.7 KiB
Ruby

# frozen_string_literal: true
class ResolveURLService < BaseService
include JsonLdHelper
include Authorization
def call(url, on_behalf_of: nil)
@url = url
@on_behalf_of = on_behalf_of
if local_url?
process_local_url
elsif !fetched_resource.nil?
process_url
else
process_url_from_db
end
end
private
def process_url
if equals_or_includes_any?(type, ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES)
ActivityPub::FetchRemoteAccountService.new.call(resource_url, prefetched_body: body)
elsif equals_or_includes_any?(type, ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES)
status = FetchRemoteStatusService.new.call(resource_url, body)
authorize_with @on_behalf_of, status, :show? unless status.nil?
status
end
end
def process_url_from_db
return unless @on_behalf_of.present? && [401, 403, 404].include?(fetch_resource_service.response_code)
# It may happen that the resource is a private toot, and thus not fetchable,
# but we can return the toot if we already know about it.
scope = Status.where(uri: @url)
# We don't have an index on `url`, so try guessing the `uri` from `url`
parsed_url = Addressable::URI.parse(@url)
parsed_url.path.match(%r{/@(?<username>#{Account::USERNAME_RE})/(?<status_id>[0-9]+)\Z}) do |matched|
parsed_url.path = "/users/#{matched[:username]}/statuses/#{matched[:status_id]}"
scope = scope.or(Status.where(uri: parsed_url.to_s, url: @url))
end
status = scope.first
authorize_with @on_behalf_of, status, :show? unless status.nil?
status
rescue Mastodon::NotPermittedError
nil
end
def fetched_resource
@fetched_resource ||= fetch_resource_service.call(@url)
end
def fetch_resource_service
@_fetch_resource_service ||= FetchResourceService.new
end
def resource_url
fetched_resource.first
end
def body
fetched_resource.second[:prefetched_body]
end
def type
json_data['type']
end
def json_data
@json_data ||= body_to_json(body)
end
def local_url?
TagManager.instance.local_url?(@url)
end
def process_local_url
recognized_params = Rails.application.routes.recognize_path(@url)
return unless recognized_params[:action] == 'show'
if recognized_params[:controller] == 'statuses'
status = Status.find_by(id: recognized_params[:id])
check_local_status(status)
elsif recognized_params[:controller] == 'accounts'
Account.find_local(recognized_params[:username])
end
end
def check_local_status(status)
return if status.nil?
authorize_with @on_behalf_of, status, :show?
status
rescue Mastodon::NotPermittedError
nil
end
end