1#!/usr/bin/env python
2"""
3SocksiPy + urllib2 handler
4
5version: 0.3
6author: e<e@tr0ll.in>
7
8This module provides a Handler which you can use with urllib2 to allow it to tunnel your connection through a socks.sockssocket socket, with out monkey patching the original socket...
9"""
10import socket
11import ssl
12
13try:
14    import urllib2
15    import httplib
16except ImportError: # Python 3
17    import urllib.request as urllib2
18    import http.client as httplib
19
20import socks # $ pip install PySocks
21
22def merge_dict(a, b):
23    d = a.copy()
24    d.update(b)
25    return d
26
27def is_ip(s):
28    try:
29        if ':' in s:
30            socket.inet_pton(socket.AF_INET6, s)
31        elif '.' in s:
32            socket.inet_aton(s)
33        else:
34            return False
35    except:
36        return False
37    else:
38        return True
39
40socks4_no_rdns = set()
41
42class SocksiPyConnection(httplib.HTTPConnection):
43    def __init__(self, proxytype, proxyaddr, proxyport=None, rdns=True, username=None, password=None, *args, **kwargs):
44        self.proxyargs = (proxytype, proxyaddr, proxyport, rdns, username, password)
45        httplib.HTTPConnection.__init__(self, *args, **kwargs)
46
47    def connect(self):
48        (proxytype, proxyaddr, proxyport, rdns, username, password) = self.proxyargs
49        rdns = rdns and proxyaddr not in socks4_no_rdns
50        while True:
51            try:
52                sock = socks.create_connection(
53                    (self.host, self.port), self.timeout, None,
54                    proxytype, proxyaddr, proxyport, rdns, username, password,
55                    ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),))
56                break
57            except socks.SOCKS4Error as e:
58                if rdns and "0x5b" in str(e) and not is_ip(self.host):
59                    # Maybe a SOCKS4 server that doesn't support remote resolving
60                    # Let's try again
61                    rdns = False
62                    socks4_no_rdns.add(proxyaddr)
63                else:
64                    raise
65        self.sock = sock
66
67class SocksiPyConnectionS(httplib.HTTPSConnection):
68    def __init__(self, proxytype, proxyaddr, proxyport=None, rdns=True, username=None, password=None, *args, **kwargs):
69        self.proxyargs = (proxytype, proxyaddr, proxyport, rdns, username, password)
70        httplib.HTTPSConnection.__init__(self, *args, **kwargs)
71
72    def connect(self):
73        SocksiPyConnection.connect(self)
74        self.sock = self._context.wrap_socket(self.sock, server_hostname=self.host)
75        if not self._context.check_hostname and self._check_hostname:
76            try:
77                ssl.match_hostname(self.sock.getpeercert(), self.host)
78            except Exception:
79                self.sock.shutdown(socket.SHUT_RDWR)
80                self.sock.close()
81                raise
82
83class SocksiPyHandler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
84    def __init__(self, *args, **kwargs):
85        self.args = args
86        self.kw = kwargs
87        urllib2.HTTPHandler.__init__(self)
88
89    def http_open(self, req):
90        def build(host, port=None, timeout=0, **kwargs):
91            kw = merge_dict(self.kw, kwargs)
92            conn = SocksiPyConnection(*self.args, host=host, port=port, timeout=timeout, **kw)
93            return conn
94        return self.do_open(build, req)
95
96    def https_open(self, req):
97        def build(host, port=None, timeout=0, **kwargs):
98            kw = merge_dict(self.kw, kwargs)
99            conn = SocksiPyConnectionS(*self.args, host=host, port=port, timeout=timeout, **kw)
100            return conn
101        return self.do_open(build, req)
102
103if __name__ == "__main__":
104    import sys
105    try:
106        port = int(sys.argv[1])
107    except (ValueError, IndexError):
108        port = 9050
109    opener = urllib2.build_opener(SocksiPyHandler(socks.PROXY_TYPE_SOCKS5, "localhost", port))
110    print("HTTP: " + opener.open("http://httpbin.org/ip").read().decode())
111    print("HTTPS: " + opener.open("https://httpbin.org/ip").read().decode())
112