~cytrogen/masto-fe

a0b614f10acef03ade34cdae498b254059e1ba1d — ThibG 6 years ago 7039dca
Fix support for HTTP proxies (#11245)

* Disable incorrect check for hidden services in Socket

Hidden services can only be accessed with an HTTP proxy, in which
case the host seen by the Socket class will be the proxy, not the
target host.

Hidden services are already filtered in `Request#initialize`.

* Use our Socket class to connect to HTTP proxies

Avoid the timeout logic being bypassed

* Add support for IP addresses in Request::Socket

* Refactor a bit, no need to keep the DNS resolver around
1 files changed, 48 insertions(+), 35 deletions(-)

M app/lib/request.rb
M app/lib/request.rb => app/lib/request.rb +48 -35
@@ 30,7 30,8 @@ class Request
    @verb        = verb
    @url         = Addressable::URI.parse(url).normalize
    @http_client = options.delete(:http_client)
    @options     = options.merge(use_proxy? ? Rails.configuration.x.http_client_proxy : { socket_class: Socket })
    @options     = options.merge(socket_class: use_proxy? ? ProxySocket : Socket)
    @options     = @options.merge(Rails.configuration.x.http_client_proxy) if use_proxy?
    @headers     = {}

    raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service?


@@ 177,47 178,49 @@ class Request
  class Socket < TCPSocket
    class << self
      def open(host, *args)
        return super(host, *args) if thru_hidden_service?(host)

        outer_e = nil
        port    = args.first

        Resolv::DNS.open do |dns|
          dns.timeouts = 5
        addresses = []
        begin
          addresses = [IPAddr.new(host)]
        rescue IPAddr::InvalidAddressError
          Resolv::DNS.open do |dns|
            dns.timeouts = 5
            addresses = dns.getaddresses(host).take(2)
          end
        end

          addresses = dns.getaddresses(host).take(2)
        addresses.each do |address|
          begin
            check_private_address(address)

            sock     = ::Socket.new(address.is_a?(Resolv::IPv6) ? ::Socket::AF_INET6 : ::Socket::AF_INET, ::Socket::SOCK_STREAM, 0)
            sockaddr = ::Socket.pack_sockaddr_in(port, address.to_s)

            sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)

          addresses.each do |address|
            begin
              raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s))

              sock     = ::Socket.new(address.is_a?(Resolv::IPv6) ? ::Socket::AF_INET6 : ::Socket::AF_INET, ::Socket::SOCK_STREAM, 0)
              sockaddr = ::Socket.pack_sockaddr_in(port, address.to_s)

              sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)

              begin
                sock.connect_nonblock(sockaddr)
              rescue IO::WaitWritable
                if IO.select(nil, [sock], nil, Request::TIMEOUT[:connect])
                  begin
                    sock.connect_nonblock(sockaddr)
                  rescue Errno::EISCONN
                    # Yippee!
                  rescue
                    sock.close
                    raise
                  end
                else
              sock.connect_nonblock(sockaddr)
            rescue IO::WaitWritable
              if IO.select(nil, [sock], nil, Request::TIMEOUT[:connect])
                begin
                  sock.connect_nonblock(sockaddr)
                rescue Errno::EISCONN
                  # Yippee!
                rescue
                  sock.close
                  raise HTTP::TimeoutError, "Connect timed out after #{Request::TIMEOUT[:connect]} seconds"
                  raise
                end
              else
                sock.close
                raise HTTP::TimeoutError, "Connect timed out after #{Request::TIMEOUT[:connect]} seconds"
              end

              return sock
            rescue => e
              outer_e = e
            end

            return sock
          rescue => e
            outer_e = e
          end
        end



@@ 230,11 233,21 @@ class Request

      alias new open

      def thru_hidden_service?(host)
        Rails.configuration.x.access_to_hidden_service && /\.(onion|i2p)$/.match(host)
      def check_private_address(address)
        raise Mastodon::HostValidationError if PrivateAddressCheck.private_address?(IPAddr.new(address.to_s))
      end
    end
  end

  class ProxySocket < Socket
    class << self
      def check_private_address(_address)
        # Accept connections to private addresses as HTTP proxies will usually
        # be on local addresses
        nil
      end
    end
  end

  private_constant :ClientLimit, :Socket
  private_constant :ClientLimit, :Socket, :ProxySocket
end