mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-10-05 00:02:38 -04:00
123 lines
3.3 KiB
Ruby
123 lines
3.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "resolv"
|
|
require "ipaddr"
|
|
|
|
module HTTPX
|
|
Socks4Error = Class.new(Error)
|
|
module Plugins
|
|
module Proxy
|
|
module Socks4
|
|
VERSION = 4
|
|
CONNECT = 1
|
|
GRANTED = 90
|
|
PROTOCOLS = %w[socks4 socks4a].freeze
|
|
|
|
Error = Socks4Error
|
|
|
|
module ConnectionMethods
|
|
private
|
|
|
|
def transition(nextstate)
|
|
return super unless @options.proxy && PROTOCOLS.include?(@options.proxy.uri.scheme)
|
|
|
|
case nextstate
|
|
when :connecting
|
|
return unless @state == :idle
|
|
|
|
@io.connect
|
|
return unless @io.connected?
|
|
|
|
req = @pending.first
|
|
return unless req
|
|
|
|
request_uri = req.uri
|
|
@write_buffer << Packet.connect(@options.proxy, request_uri)
|
|
__socks4_proxy_connect
|
|
when :connected
|
|
return unless @state == :connecting
|
|
|
|
@parser = nil
|
|
end
|
|
log(level: 1) { "SOCKS4: #{nextstate}: #{@write_buffer.to_s.inspect}" } unless nextstate == :open
|
|
super
|
|
end
|
|
|
|
def __socks4_proxy_connect
|
|
@parser = SocksParser.new(@write_buffer, @options)
|
|
@parser.once(:packet, &method(:__socks4_on_packet))
|
|
end
|
|
|
|
def __socks4_on_packet(packet)
|
|
_version, status, _port, _ip = packet.unpack("CCnN")
|
|
if status == GRANTED
|
|
req = @pending.first
|
|
request_uri = req.uri
|
|
@io = ProxySSL.new(@io, request_uri, @options) if request_uri.scheme == "https"
|
|
transition(:connected)
|
|
throw(:called)
|
|
else
|
|
on_socks4_error("socks error: #{status}")
|
|
end
|
|
end
|
|
|
|
def on_socks4_error(message)
|
|
ex = Error.new(message)
|
|
ex.set_backtrace(caller)
|
|
on_error(ex)
|
|
throw(:called)
|
|
end
|
|
end
|
|
|
|
class SocksParser
|
|
include Callbacks
|
|
|
|
def initialize(buffer, options)
|
|
@buffer = buffer
|
|
@options = Options.new(options)
|
|
end
|
|
|
|
def close; end
|
|
|
|
def consume(*); end
|
|
|
|
def empty?
|
|
true
|
|
end
|
|
|
|
def <<(packet)
|
|
emit(:packet, packet)
|
|
end
|
|
end
|
|
|
|
module Packet
|
|
using(RegexpExtensions) unless Regexp.method_defined?(:match?)
|
|
|
|
module_function
|
|
|
|
def connect(parameters, uri)
|
|
packet = [VERSION, CONNECT, uri.port].pack("CCn")
|
|
begin
|
|
ip = IPAddr.new(uri.host)
|
|
raise Error, "Socks4 connection to #{ip} not supported" unless ip.ipv4?
|
|
|
|
packet << [ip.to_i].pack("N")
|
|
rescue IPAddr::InvalidAddressError
|
|
if /^socks4a?$/.match?(parameters.uri.scheme)
|
|
# resolv defaults to IPv4, and socks4 doesn't support IPv6 otherwise
|
|
ip = IPAddr.new(Resolv.getaddress(uri.host))
|
|
packet << [ip.to_i].pack("N")
|
|
else
|
|
packet << "\x0\x0\x0\x1" << "\x7\x0" << uri.host
|
|
end
|
|
end
|
|
packet << [parameters.username].pack("Z*")
|
|
packet
|
|
end
|
|
end
|
|
end
|
|
end
|
|
register_plugin :"proxy/socks4", Proxy::Socks4
|
|
end
|
|
end
|