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.

93 lines
2.2 KiB

  1. # frozen_string_literal: true
  2. class Webfinger
  3. class Error < StandardError; end
  4. class Response
  5. def initialize(body)
  6. @json = Oj.load(body, mode: :strict)
  7. end
  8. def subject
  9. @json['subject']
  10. end
  11. def link(rel, attribute)
  12. links.dig(rel, attribute)
  13. end
  14. private
  15. def links
  16. @links ||= @json['links'].map { |link| [link['rel'], link] }.to_h
  17. end
  18. end
  19. def initialize(uri)
  20. _, @domain = uri.split('@')
  21. raise ArgumentError, 'Webfinger requested for local account' if @domain.nil?
  22. @uri = uri
  23. end
  24. def perform
  25. Response.new(body_from_webfinger)
  26. rescue Oj::ParseError
  27. raise Webfinger::Error, "Invalid JSON in response for #{@uri}"
  28. rescue Addressable::URI::InvalidURIError
  29. raise Webfinger::Error, "Invalid URI for #{@uri}"
  30. end
  31. private
  32. def body_from_webfinger(url = standard_url, use_fallback = true)
  33. webfinger_request(url).perform do |res|
  34. if res.code == 200
  35. res.body_with_limit
  36. elsif res.code == 404 && use_fallback
  37. body_from_host_meta
  38. else
  39. raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}"
  40. end
  41. end
  42. end
  43. def body_from_host_meta
  44. host_meta_request.perform do |res|
  45. if res.code == 200
  46. body_from_webfinger(url_from_template(res.body_with_limit), false)
  47. else
  48. raise Webfinger::Error, "Request for #{@uri} returned HTTP #{res.code}"
  49. end
  50. end
  51. end
  52. def url_from_template(str)
  53. link = Nokogiri::XML(str).at_xpath('//xmlns:Link[@rel="lrdd"]')
  54. if link.present?
  55. link['template'].gsub('{uri}', @uri)
  56. else
  57. raise Webfinger::Error, "Request for #{@uri} returned host-meta without link to Webfinger"
  58. end
  59. rescue Nokogiri::XML::XPath::SyntaxError
  60. raise Webfinger::Error, "Invalid XML encountered in host-meta for #{@uri}"
  61. end
  62. def host_meta_request
  63. Request.new(:get, host_meta_url).add_headers('Accept' => 'application/xrd+xml, application/xml, text/xml')
  64. end
  65. def webfinger_request(url)
  66. Request.new(:get, url).add_headers('Accept' => 'application/jrd+json, application/json')
  67. end
  68. def standard_url
  69. "https://#{@domain}/.well-known/webfinger?resource=#{@uri}"
  70. end
  71. def host_meta_url
  72. "https://#{@domain}/.well-known/host-meta"
  73. end
  74. end