1 # frozen_string_literal: true
7 REQUEST_TARGET
= '(request-target)'
11 def initialize(verb
, url
, **options
)
12 raise ArgumentError
if url
.blank
?
15 @url = Addressable
::URI.parse(url
).normalize
16 @options = options
.merge(use_proxy
? ? Rails
.configuration
.x
.http_client_proxy
: { socket_class
: Socket
})
19 raise Mastodon
::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service
?
22 set_digest!
if options
.key
?(:body)
25 def on_behalf_of(account
, key_id_format
= :acct)
26 raise ArgumentError
unless account
.local
?
29 @key_id_format = key_id_format
34 def add_headers(new_headers
)
35 @headers.merge!
(new_headers
)
41 response
= http_client
.headers(headers
).public_send(@verb, @url.to_s
, @options)
43 raise e
.class, "#{e.message} on #{@url}", e
.backtrace
[0]
47 yield response
.extend(ClientLimit
)
54 (@account ? @headers.merge('Signature' => signature
) : @headers).without(REQUEST_TARGET
)
59 def set_common_headers!
60 @headers[REQUEST_TARGET
] = "#{@verb} #{@url.path}"
61 @headers['User-Agent'] = user_agent
62 @headers['Host'] = @url.host
63 @headers['Date'] = Time
.now
.utc
.httpdate
67 @headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(@options[:body])}"
71 algorithm
= 'rsa-sha256'
72 signature
= Base64
.strict_encode64(@account.keypair
.sign(OpenSSL
::Digest::SHA256.new
, signed_string
))
74 "keyId=\"#{key_id}\",algorithm=\"#{algorithm}\",headers=\"#{signed_headers}\",signature=\"#{signature}\""
78 @headers.map
{ |key
, value
| "#{key.downcase}: #{value}" }.join("\n")
82 @headers.keys
.join(' ').downcase
86 @user_agent ||= "#{HTTP::Request::USER_AGENT} (Mastodon/#{Mastodon::Version}; +#{root_url})"
92 @account.to_webfinger_s
94 [ActivityPub::TagManager.instance.uri_for(@account), '#main-key'].join
99 { write: 10, connect: 10, read: 10 }
103 @http_client ||= HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
107 Rails.configuration.x.http_client_proxy.present?
110 def block_hidden_service?
111 !Rails
.configuration
.x
.access_to_hidden_service
&& /\.(onion|i2p)$/.match(@url.host
)
115 def body_with_limit(limit
= 1.megabyte
)
116 raise Mastodon
::LengthValidationError if content_length
.present
? && content_length
> limit
119 encoding
= Encoding
::BINARY
122 encoding
= Encoding
.find(charset
)
124 encoding
= Encoding
::BINARY
128 contents
= String
.new(encoding
: encoding
)
130 while (chunk
= readpartial
)
134 raise Mastodon
::LengthValidationError if contents
.bytesize
> limit
141 class Socket
< TCPSocket
143 def open(host
, *args
)
144 return super host
, *args
if thru_hidden_service
? host
146 Addrinfo
.foreach(host
, nil, nil, :SOCK_STREAM) do |address
|
148 raise Mastodon
::HostValidationError if PrivateAddressCheck
.private_address
? IPAddr
.new(address
.ip_address
)
149 return super address
.ip_address
, *args
154 raise outer_e
if outer_e
159 def thru_hidden_service
?(host
)
160 Rails
.configuration
.x
.hidden_service_via_transparent_proxy
&& /\.(onion|i2p)$/.match(host
)
165 private_constant
:ClientLimit, :Socket