File: socks4.rb

package info (click to toggle)
ruby-httpx 1.7.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,816 kB
  • sloc: ruby: 12,209; makefile: 4
file content (135 lines) | stat: -rw-r--r-- 3,542 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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
# frozen_string_literal: true

require "resolv"
require "ipaddr"

module HTTPX
  class Socks4Error < ProxyError; end

  module Plugins
    module Proxy
      module Socks4
        VERSION = 4
        CONNECT = 1
        GRANTED = 0x5A
        PROTOCOLS = %w[socks4 socks4a].freeze

        Error = Socks4Error

        class << self
          def extra_options(options)
            options.merge(supported_proxy_protocols: options.supported_proxy_protocols + PROTOCOLS)
          end
        end

        module ConnectionMethods
          def interests
            if @state == :connecting
              return @write_buffer.empty? ? :r : :w
            end

            super
          end

          private

          def handle_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 HTTPX::Callbacks

          def initialize(buffer, options)
            @buffer = buffer
            @options = options
          end

          def close; end

          def consume(*); end

          def empty?
            true
          end

          def <<(packet)
            emit(:packet, packet)
          end
        end

        module Packet
          module_function

          def connect(parameters, uri)
            packet = [VERSION, CONNECT, uri.port].pack("CCn")

            case parameters.uri.scheme
            when "socks4"
              socks_host = uri.host
              begin
                ip = IPAddr.new(socks_host)
                packet << ip.hton
              rescue IPAddr::InvalidAddressError
                socks_host = Resolv.getaddress(socks_host)
                retry
              end
              packet << [parameters.username].pack("Z*")
            when "socks4a"
              packet << "\x0\x0\x0\x1" << [parameters.username].pack("Z*") << uri.host << "\x0"
            end
            packet
          end
        end
      end
    end
    register_plugin :"proxy/socks4", Proxy::Socks4
  end
end