2020-10-07 18:34:57 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class Webfinger
|
|
|
|
class Error < StandardError; end
|
2020-11-07 18:28:39 -05:00
|
|
|
class GoneError < Error; end
|
2022-09-20 17:30:26 -04:00
|
|
|
class RedirectError < Error; end
|
2020-10-07 18:34:57 -04:00
|
|
|
|
|
|
|
class Response
|
2024-07-23 10:42:31 -04:00
|
|
|
ACTIVITYPUB_READY_TYPE = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].freeze
|
|
|
|
|
2022-04-30 18:37:46 -04:00
|
|
|
attr_reader :uri
|
|
|
|
|
|
|
|
def initialize(uri, body)
|
|
|
|
@uri = uri
|
2020-10-07 18:34:57 -04:00
|
|
|
@json = Oj.load(body, mode: :strict)
|
2022-04-30 18:37:46 -04:00
|
|
|
|
|
|
|
validate_response!
|
2020-10-07 18:34:57 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def subject
|
|
|
|
@json['subject']
|
|
|
|
end
|
|
|
|
|
|
|
|
def link(rel, attribute)
|
2024-07-23 10:42:31 -04:00
|
|
|
links.dig(rel, 0, attribute)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self_link_href
|
|
|
|
self_link.fetch('href')
|
2020-10-07 18:34:57 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def links
|
2024-07-23 10:42:31 -04:00
|
|
|
@links ||= @json.fetch('links', []).group_by { |link| link['rel'] }
|
|
|
|
end
|
|
|
|
|
|
|
|
def self_link
|
|
|
|
links.fetch('self', []).find do |link|
|
|
|
|
ACTIVITYPUB_READY_TYPE.include?(link['type'])
|
|
|
|
end
|
2020-10-07 18:34:57 -04:00
|
|
|
end
|
2022-04-30 18:37:46 -04:00
|
|
|
|
|
|
|
def validate_response!
|
|
|
|
raise Webfinger::Error, "Missing subject in response for #{@uri}" if subject.blank?
|
2024-07-23 10:42:31 -04:00
|
|
|
raise Webfinger::Error, "Missing self link in response for #{@uri}" if self_link.blank?
|
2022-04-30 18:37:46 -04:00
|
|
|
end
|
2020-10-07 18:34:57 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(uri)
|
|
|
|
_, @domain = uri.split('@')
|
|
|
|
|
|
|
|
raise ArgumentError, 'Webfinger requested for local account' if @domain.nil?
|
|
|
|
|
|
|
|
@uri = uri
|
|
|
|
end
|
|
|
|
|
|
|
|
def perform
|
2022-04-30 18:37:46 -04:00
|
|
|
Response.new(@uri, body_from_webfinger)
|
2020-10-07 18:34:57 -04:00
|
|
|
rescue Oj::ParseError
|
|
|
|
raise Webfinger::Error, "Invalid JSON in response for #{@uri}"
|
|
|
|
rescue Addressable::URI::InvalidURIError
|
|
|
|
raise Webfinger::Error, "Invalid URI for #{@uri}"
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def body_from_webfinger(url = standard_url, use_fallback = true)
|
|
|
|
webfinger_request(url).perform do |res|
|
|
|
|
if res.code == 200
|
2021-11-14 15:55:40 -05:00
|
|
|
body = res.body_with_limit
|
|
|
|
raise Webfinger::Error, "Request for #{@uri} returned empty response" if body.empty?
|
2023-02-20 00:58:28 -05:00
|
|
|
|
2021-11-14 15:55:40 -05:00
|
|
|
body
|
2020-10-07 18:34:57 -04:00
|
|
|
elsif res.code == 404 && use_fallback
|
|
|
|
body_from_host_meta
|
2020-11-07 18:28:39 -05:00
|
|
|
elsif res.code == 410
|
|
|
|
raise Webfinger::GoneError, "#{@uri} is gone from the server"
|
2020-10-07 18:34:57 -04:00
|
|
|
else
|
|
|
|
raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def body_from_host_meta
|
|
|
|
host_meta_request.perform do |res|
|
|
|
|
if res.code == 200
|
|
|
|
body_from_webfinger(url_from_template(res.body_with_limit), false)
|
|
|
|
else
|
|
|
|
raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def url_from_template(str)
|
|
|
|
link = Nokogiri::XML(str).at_xpath('//xmlns:Link[@rel="lrdd"]')
|
|
|
|
|
|
|
|
if link.present?
|
|
|
|
link['template'].gsub('{uri}', @uri)
|
|
|
|
else
|
|
|
|
raise Webfinger::Error, "Request for #{@uri} returned host-meta without link to Webfinger"
|
|
|
|
end
|
|
|
|
rescue Nokogiri::XML::XPath::SyntaxError
|
|
|
|
raise Webfinger::Error, "Invalid XML encountered in host-meta for #{@uri}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def host_meta_request
|
|
|
|
Request.new(:get, host_meta_url).add_headers('Accept' => 'application/xrd+xml, application/xml, text/xml')
|
|
|
|
end
|
|
|
|
|
|
|
|
def webfinger_request(url)
|
|
|
|
Request.new(:get, url).add_headers('Accept' => 'application/jrd+json, application/json')
|
|
|
|
end
|
|
|
|
|
|
|
|
def standard_url
|
2023-02-18 17:38:14 -05:00
|
|
|
if @domain.end_with? '.onion'
|
2021-02-10 22:40:13 -05:00
|
|
|
"http://#{@domain}/.well-known/webfinger?resource=#{@uri}"
|
|
|
|
else
|
|
|
|
"https://#{@domain}/.well-known/webfinger?resource=#{@uri}"
|
|
|
|
end
|
2020-10-07 18:34:57 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def host_meta_url
|
2023-02-18 17:38:14 -05:00
|
|
|
if @domain.end_with? '.onion'
|
2021-02-10 22:40:13 -05:00
|
|
|
"http://#{@domain}/.well-known/host-meta"
|
|
|
|
else
|
|
|
|
"https://#{@domain}/.well-known/host-meta"
|
|
|
|
end
|
2020-10-07 18:34:57 -04:00
|
|
|
end
|
|
|
|
end
|