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.

90 lines
2.1 KiB

  1. # frozen_string_literal: true
  2. class Request
  3. REQUEST_TARGET = '(request-target)'
  4. include RoutingHelper
  5. def initialize(verb, url, **options)
  6. @verb = verb
  7. @url = Addressable::URI.parse(url).normalize
  8. @options = options
  9. @headers = {}
  10. set_common_headers!
  11. set_digest! if options.key?(:body)
  12. end
  13. def on_behalf_of(account, key_id_format = :acct)
  14. raise ArgumentError unless account.local?
  15. @account = account
  16. @key_id_format = key_id_format
  17. self
  18. end
  19. def add_headers(new_headers)
  20. @headers.merge!(new_headers)
  21. self
  22. end
  23. def perform
  24. http_client.headers(headers).public_send(@verb, @url.to_s, @options)
  25. rescue => e
  26. raise e.class, "#{e.message} on #{@url}", e.backtrace[0]
  27. end
  28. def headers
  29. (@account ? @headers.merge('Signature' => signature) : @headers).without(REQUEST_TARGET)
  30. end
  31. private
  32. def set_common_headers!
  33. @headers[REQUEST_TARGET] = "#{@verb} #{@url.path}"
  34. @headers['User-Agent'] = user_agent
  35. @headers['Host'] = @url.host
  36. @headers['Date'] = Time.now.utc.httpdate
  37. end
  38. def set_digest!
  39. @headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(@options[:body])}"
  40. end
  41. def signature
  42. algorithm = 'rsa-sha256'
  43. signature = Base64.strict_encode64(@account.keypair.sign(OpenSSL::Digest::SHA256.new, signed_string))
  44. "keyId=\"#{key_id}\",algorithm=\"#{algorithm}\",headers=\"#{signed_headers}\",signature=\"#{signature}\""
  45. end
  46. def signed_string
  47. @headers.map { |key, value| "#{key.downcase}: #{value}" }.join("\n")
  48. end
  49. def signed_headers
  50. @headers.keys.join(' ').downcase
  51. end
  52. def user_agent
  53. @user_agent ||= "#{HTTP::Request::USER_AGENT} (Mastodon/#{Mastodon::Version}; +#{root_url})"
  54. end
  55. def key_id
  56. case @key_id_format
  57. when :acct
  58. @account.to_webfinger_s
  59. when :uri
  60. [ActivityPub::TagManager.instance.uri_for(@account), '#main-key'].join
  61. end
  62. end
  63. def timeout
  64. { write: 10, connect: 10, read: 10 }
  65. end
  66. def http_client
  67. HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
  68. end
  69. end