You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
126 lines
3.3 KiB
126 lines
3.3 KiB
# frozen_string_literal: true |
|
|
|
class Webfinger |
|
class Error < StandardError; end |
|
class GoneError < Error; end |
|
class RedirectError < Error; end |
|
|
|
class Response |
|
ACTIVITYPUB_READY_TYPE = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].freeze |
|
|
|
attr_reader :uri |
|
|
|
def initialize(uri, body) |
|
@uri = uri |
|
@json = Oj.load(body, mode: :strict) |
|
|
|
validate_response! |
|
end |
|
|
|
def subject |
|
@json['subject'] |
|
end |
|
|
|
def link(rel, attribute) |
|
links.dig(rel, 0, attribute) |
|
end |
|
|
|
def self_link_href |
|
self_link.fetch('href') |
|
end |
|
|
|
private |
|
|
|
def links |
|
@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 |
|
end |
|
|
|
def validate_response! |
|
raise Webfinger::Error, "Missing subject in response for #{@uri}" if subject.blank? |
|
raise Webfinger::Error, "Missing self link in response for #{@uri}" if self_link.blank? |
|
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(@uri, 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 |
|
body = res.body_with_limit |
|
raise Webfinger::Error, "Request for #{@uri} returned empty response" if body.empty? |
|
|
|
body |
|
elsif res.code == 404 && use_fallback |
|
body_from_host_meta |
|
elsif res.code == 410 |
|
raise Webfinger::GoneError, "#{@uri} is gone from the server" |
|
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| |
|
raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}" unless res.code == 200 |
|
|
|
body_from_webfinger(url_from_template(res.body_with_limit), use_fallback: false) |
|
end |
|
end |
|
|
|
def url_from_template(str) |
|
link = Nokogiri::XML(str).at_xpath('//xmlns:Link[@rel="lrdd"]') |
|
|
|
raise Webfinger::Error, "Request for #{@uri} returned host-meta without link to Webfinger" if link.blank? |
|
|
|
link['template'].gsub('{uri}', @uri) |
|
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 |
|
if @domain.end_with? '.onion' |
|
"http://#{@domain}/.well-known/webfinger?resource=#{@uri}" |
|
else |
|
"https://#{@domain}/.well-known/webfinger?resource=#{@uri}" |
|
end |
|
end |
|
|
|
def host_meta_url |
|
if @domain.end_with? '.onion' |
|
"http://#{@domain}/.well-known/host-meta" |
|
else |
|
"https://#{@domain}/.well-known/host-meta" |
|
end |
|
end |
|
end
|
|
|