2020-10-07 22:34:57 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class Webfinger
|
|
|
|
class Error < StandardError; end
|
2020-11-07 23:28:39 +00:00
|
|
|
class GoneError < Error; end
|
|
|
|
class RedirectError < StandardError; end
|
2020-10-07 22:34:57 +00:00
|
|
|
|
|
|
|
class Response
|
|
|
|
def initialize(body)
|
|
|
|
@json = Oj.load(body, mode: :strict)
|
|
|
|
end
|
|
|
|
|
|
|
|
def subject
|
|
|
|
@json['subject']
|
|
|
|
end
|
|
|
|
|
|
|
|
def link(rel, attribute)
|
|
|
|
links.dig(rel, attribute)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def links
|
2021-01-12 08:27:38 +00:00
|
|
|
@links ||= @json['links'].index_by { |link| link['rel'] }
|
2020-10-07 22:34:57 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(uri)
|
|
|
|
_, @domain = uri.split('@')
|
|
|
|
|
|
|
|
raise ArgumentError, 'Webfinger requested for local account' if @domain.nil?
|
|
|
|
|
|
|
|
@uri = uri
|
|
|
|
end
|
|
|
|
|
|
|
|
def perform
|
|
|
|
Response.new(body_from_webfinger)
|
|
|
|
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 20:55:40 +00:00
|
|
|
body = res.body_with_limit
|
|
|
|
raise Webfinger::Error, "Request for #{@uri} returned empty response" if body.empty?
|
|
|
|
body
|
2020-10-07 22:34:57 +00:00
|
|
|
elsif res.code == 404 && use_fallback
|
|
|
|
body_from_host_meta
|
2020-11-07 23:28:39 +00:00
|
|
|
elsif res.code == 410
|
|
|
|
raise Webfinger::GoneError, "#{@uri} is gone from the server"
|
2020-10-07 22:34:57 +00: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
|
2021-02-19 08:56:14 +00:00
|
|
|
if @domain.end_with? ".onion"
|
2021-02-11 03:40:13 +00:00
|
|
|
"http://#{@domain}/.well-known/webfinger?resource=#{@uri}"
|
|
|
|
else
|
|
|
|
"https://#{@domain}/.well-known/webfinger?resource=#{@uri}"
|
|
|
|
end
|
2020-10-07 22:34:57 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def host_meta_url
|
2021-02-19 08:56:14 +00:00
|
|
|
if @domain.end_with? ".onion"
|
2021-02-11 03:40:13 +00:00
|
|
|
"http://#{@domain}/.well-known/host-meta"
|
|
|
|
else
|
|
|
|
"https://#{@domain}/.well-known/host-meta"
|
|
|
|
end
|
2020-10-07 22:34:57 +00:00
|
|
|
end
|
|
|
|
end
|