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.

105 lines
2.6 KiB

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