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
(@account ? @headers.merge('Signature' => signature) : @headers).without(REQUEST_TARGET)
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}"
@headers['User-Agent'] = Mastodon::Version.user_agent
@headers['Host'] = @url.host
@headers['Date'] = Time.now.utc.httpdate
@headers['Accept-Encoding'] = 'gzip' if @verb != :head
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 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.use(:auto_inflate).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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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)
MIYAGI Hikaru
a validé
Rails.configuration.x.access_to_hidden_service && /\.(onion|i2p)$/.match(host)
MIYAGI Hikaru
a validé
end
Akihiko Odaki
a validé
end
end
private_constant :ClientLimit, :Socket