Remove dependency on goldfinger gem (#14919)
There are edge cases where requests to certain hosts timeout when using the vanilla HTTP.rb gem, which the goldfinger gem uses. Now that we no longer need to support OStatus servers, webfinger logic is so simple that there is no point encapsulating it in a gem, so we can just use our own Request class. With that, we benefit from more robust timeout code and IPv4/IPv6 resolution. Fix #14091main
parent
a37732ef33
commit
7d985f2aac
@ -1,38 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Monkey-patch on monkey-patch.
|
||||
# Because it conflicts with the request.rb patch.
|
||||
class HTTP::Timeout::PerOperationOriginal < HTTP::Timeout::PerOperation
|
||||
def connect(socket_class, host, port, nodelay = false)
|
||||
::Timeout.timeout(@connect_timeout, HTTP::TimeoutError) do
|
||||
@socket = socket_class.open(host, port)
|
||||
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module WebfingerHelper
|
||||
def webfinger!(uri)
|
||||
hidden_service_uri = /\.(onion|i2p)(:\d+)?$/.match(uri)
|
||||
|
||||
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && hidden_service_uri
|
||||
|
||||
opts = {
|
||||
ssl: !hidden_service_uri,
|
||||
|
||||
headers: {
|
||||
'User-Agent': Mastodon::Version.user_agent,
|
||||
},
|
||||
|
||||
timeout_class: HTTP::Timeout::PerOperationOriginal,
|
||||
|
||||
timeout_options: {
|
||||
write_timeout: 10,
|
||||
connect_timeout: 5,
|
||||
read_timeout: 10,
|
||||
},
|
||||
}
|
||||
|
||||
Goldfinger::Client.new(uri, opts.merge(Rails.configuration.x.http_client_proxy)).finger
|
||||
Webfinger.new(uri).perform
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,93 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Webfinger
|
||||
class Error < StandardError; end
|
||||
|
||||
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
|
||||
@links ||= @json['links'].map { |link| [link['rel'], link] }.to_h
|
||||
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
|
||||
res.body_with_limit
|
||||
elsif res.code == 404 && use_fallback
|
||||
body_from_host_meta
|
||||
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
|
||||
"https://#{@domain}/.well-known/webfinger?resource=#{@uri}"
|
||||
end
|
||||
|
||||
def host_meta_url
|
||||
"https://#{@domain}/.well-known/host-meta"
|
||||
end
|
||||
end
|
Loading…
Reference in new issue