Newer
Older
Akihiko Odaki
a validé
require 'ipaddr'
require 'socket'
class Request
REQUEST_TARGET = '(request-target)'
include RoutingHelper
def initialize(verb, url, **options)
raise ArgumentError if url.blank?
@verb = verb
@url = Addressable::URI.parse(url).normalize
MIYAGI Hikaru
a validé
@options = options.merge(use_proxy? ? Rails.configuration.x.http_client_proxy : { socket_class: Socket })
MIYAGI Hikaru
a validé
raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service?
set_digest! if options.key?(:body)
def on_behalf_of(account, key_id_format = :acct)
@account = account
@key_id_format = key_id_format
self
end
def add_headers(new_headers)
@headers.merge!(new_headers)
self
begin
response = http_client.headers(headers).public_send(@verb, @url.to_s, @options)
rescue => e
raise e.class, "#{e.message} on #{@url}", e.backtrace[0]
end
begin
yield response.extend(ClientLimit)
ensure
http_client.close
end
end
def headers
(@account ? @headers.merge('Signature' => signature) : @headers).without(REQUEST_TARGET)
end
private
def set_common_headers!
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}"
@headers['User-Agent'] = user_agent
@headers['Host'] = @url.host
@headers['Date'] = Time.now.utc.httpdate
end
def set_digest!
@headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(@options[:body])}"
end
def signature
algorithm = 'rsa-sha256'
signature = Base64.strict_encode64(@account.keypair.sign(OpenSSL::Digest::SHA256.new, signed_string))
"keyId=\"#{key_id}\",algorithm=\"#{algorithm}\",headers=\"#{signed_headers}\",signature=\"#{signature}\""
end
def signed_string
@headers.map { |key, value| "#{key.downcase}: #{value}" }.join("\n")
end
def signed_headers
@headers.keys.join(' ').downcase
end
def user_agent
@user_agent ||= "#{HTTP::Request::USER_AGENT} (Mastodon/#{Mastodon::Version}; +#{root_url})"
end
def key_id
case @key_id_format
when :acct
@account.to_webfinger_s
when :uri
[ActivityPub::TagManager.instance.uri_for(@account), '#main-key'].join
end
end
def timeout
{ write: 10, connect: 10, read: 10 }
end
def http_client
@http_client ||= HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
Akihiko Odaki
a validé
MIYAGI Hikaru
a validé
def use_proxy?
Rails.configuration.x.http_client_proxy.present?
end
def block_hidden_service?
!Rails.configuration.x.access_to_hidden_service && /\.(onion|i2p)$/.match(@url.host)
end
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
module ClientLimit
def body_with_limit(limit = 1.megabyte)
raise Mastodon::LengthValidationError if content_length.present? && content_length > limit
if charset.nil?
encoding = Encoding::BINARY
else
begin
encoding = Encoding.find(charset)
rescue ArgumentError
encoding = Encoding::BINARY
end
end
contents = String.new(encoding: encoding)
while (chunk = readpartial)
contents << chunk
chunk.clear
raise Mastodon::LengthValidationError if contents.bytesize > limit
end
contents
end
end
Akihiko Odaki
a validé
class Socket < TCPSocket
class << self
def open(host, *args)
MIYAGI Hikaru
a validé
return super host, *args if thru_hidden_service? host
outer_e = nil
Addrinfo.foreach(host, nil, nil, :SOCK_STREAM) do |address|
begin
raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address.ip_address)
return super address.ip_address, *args
rescue => e
outer_e = e
end
end
raise outer_e if outer_e
Akihiko Odaki
a validé
end
alias new open
MIYAGI Hikaru
a validé
def thru_hidden_service?(host)
Rails.configuration.x.hidden_service_via_transparent_proxy && /\.(onion|i2p)$/.match(host)
end
Akihiko Odaki
a validé
end
end
private_constant :ClientLimit, :Socket