Repository: urllib3/urllib3

Issue:
Differentiate socks5h from socks5 and socks4a from socks4 when handling proxy string
In a proxy string, socks5h:// and socks4a:// mean that the hostname is
resolved by the SOCKS server.  socks5:// and socks4:// mean that the
hostname is resolved locally.  socks4a:// means to use SOCKS4a, which is
an extension of SOCKS4.  Let's make urllib3 honor it.


Discussion:

Resolving patch:
diff --git a/dummyserver/testcase.py b/dummyserver/testcase.py
--- a/dummyserver/testcase.py
+++ b/dummyserver/testcase.py
@@ -1,4 +1,4 @@
-import unittest
+import sys
 import socket
 import threading
 from nose.plugins.skip import SkipTest
@@ -13,6 +13,11 @@
 from dummyserver.handlers import TestingApp
 from dummyserver.proxy import ProxyHandler
 
+if sys.version_info >= (2, 7):
+    import unittest
+else:
+    import unittest2 as unittest
+
 
 def consume_socket(sock, chunks=65536):
     while not sock.recv(chunks).endswith(b'\r\n\r\n'):
diff --git a/urllib3/contrib/socks.py b/urllib3/contrib/socks.py
--- a/urllib3/contrib/socks.py
+++ b/urllib3/contrib/socks.py
@@ -83,6 +83,7 @@ def _new_conn(self):
                 proxy_port=self._socks_options['proxy_port'],
                 proxy_username=self._socks_options['username'],
                 proxy_password=self._socks_options['password'],
+                proxy_rdns=self._socks_options['rdns'],
                 timeout=self.timeout,
                 **extra_kw
             )
@@ -153,8 +154,16 @@ def __init__(self, proxy_url, username=None, password=None,
 
         if parsed.scheme == 'socks5':
             socks_version = socks.PROXY_TYPE_SOCKS5
+            rdns = False
+        elif parsed.scheme == 'socks5h':
+            socks_version = socks.PROXY_TYPE_SOCKS5
+            rdns = True
         elif parsed.scheme == 'socks4':
             socks_version = socks.PROXY_TYPE_SOCKS4
+            rdns = False
+        elif parsed.scheme == 'socks4a':
+            socks_version = socks.PROXY_TYPE_SOCKS4
+            rdns = True
         else:
             raise ValueError(
                 "Unable to determine SOCKS version from %s" % proxy_url
@@ -168,6 +177,7 @@ def __init__(self, proxy_url, username=None, password=None,
             'proxy_port': parsed.port,
             'username': username,
             'password': password,
+            'rdns': rdns
         }
         connection_pool_kw['_socks_options'] = socks_options
 


Test patch:
diff --git a/test/contrib/test_socks.py b/test/contrib/test_socks.py
--- a/test/contrib/test_socks.py
+++ b/test/contrib/test_socks.py
@@ -229,6 +229,37 @@ def request_handler(listener):
         self.assertEqual(response.data, b'')
         self.assertEqual(response.headers['Server'], 'SocksTestServer')
 
+    def test_local_dns(self):
+        def request_handler(listener):
+            sock = listener.accept()[0]
+
+            handler = handle_socks5_negotiation(sock, negotiate=False)
+            addr, port = next(handler)
+
+            self.assertIn(addr, ['127.0.0.1', '::1'])
+            self.assertTrue(port, 80)
+            handler.send(True)
+
+            while True:
+                buf = sock.recv(65535)
+                if buf.endswith(b'\r\n\r\n'):
+                    break
+
+            sock.sendall(b'HTTP/1.1 200 OK\r\n'
+                         b'Server: SocksTestServer\r\n'
+                         b'Content-Length: 0\r\n'
+                         b'\r\n')
+            sock.close()
+
+        self._start_server(request_handler)
+        proxy_url = "socks5://%s:%s" % (self.host, self.port)
+        pm = socks.SOCKSProxyManager(proxy_url)
+        response = pm.request('GET', 'http://localhost')
+
+        self.assertEqual(response.status, 200)
+        self.assertEqual(response.data, b'')
+        self.assertEqual(response.headers['Server'], 'SocksTestServer')
+
     def test_correct_header_line(self):
         def request_handler(listener):
             sock = listener.accept()[0]
@@ -256,7 +287,7 @@ def request_handler(listener):
             sock.close()
 
         self._start_server(request_handler)
-        proxy_url = "socks5://%s:%s" % (self.host, self.port)
+        proxy_url = "socks5h://%s:%s" % (self.host, self.port)
         pm = socks.SOCKSProxyManager(proxy_url)
         response = pm.request('GET', 'http://example.com')
         self.assertEqual(response.status, 200)
@@ -268,7 +299,7 @@ def request_handler(listener):
             event.wait()
 
         self._start_server(request_handler)
-        proxy_url = "socks5://%s:%s" % (self.host, self.port)
+        proxy_url = "socks5h://%s:%s" % (self.host, self.port)
         pm = socks.SOCKSProxyManager(proxy_url)
 
         self.assertRaises(
@@ -285,7 +316,7 @@ def request_handler(listener):
             event.set()
 
         self._start_server(request_handler)
-        proxy_url = "socks5://%s:%s" % (self.host, self.port)
+        proxy_url = "socks5h://%s:%s" % (self.host, self.port)
         pm = socks.SOCKSProxyManager(proxy_url)
 
         event.wait()
@@ -308,7 +339,7 @@ def request_handler(listener):
             sock.close()
 
         self._start_server(request_handler)
-        proxy_url = "socks5://%s:%s" % (self.host, self.port)
+        proxy_url = "socks5h://%s:%s" % (self.host, self.port)
         pm = socks.SOCKSProxyManager(proxy_url)
 
         self.assertRaises(
@@ -361,7 +392,7 @@ def request_handler(listener):
             next(handler)
 
         self._start_server(request_handler)
-        proxy_url = "socks5://%s:%s" % (self.host, self.port)
+        proxy_url = "socks5h://%s:%s" % (self.host, self.port)
         pm = socks.SOCKSProxyManager(proxy_url, username='user',
                                            password='badpass')
 
@@ -445,6 +476,37 @@ def request_handler(listener):
         self.assertEqual(response.headers['Server'], 'SocksTestServer')
         self.assertEqual(response.data, b'')
 
+    def test_local_dns(self):
+        def request_handler(listener):
+            sock = listener.accept()[0]
+
+            handler = handle_socks4_negotiation(sock)
+            addr, port = next(handler)
+
+            self.assertEqual(addr, '127.0.0.1')
+            self.assertTrue(port, 80)
+            handler.send(True)
+
+            while True:
+                buf = sock.recv(65535)
+                if buf.endswith(b'\r\n\r\n'):
+                    break
+
+            sock.sendall(b'HTTP/1.1 200 OK\r\n'
+                         b'Server: SocksTestServer\r\n'
+                         b'Content-Length: 0\r\n'
+                         b'\r\n')
+            sock.close()
+
+        self._start_server(request_handler)
+        proxy_url = "socks4://%s:%s" % (self.host, self.port)
+        pm = socks.SOCKSProxyManager(proxy_url)
+        response = pm.request('GET', 'http://localhost')
+
+        self.assertEqual(response.status, 200)
+        self.assertEqual(response.headers['Server'], 'SocksTestServer')
+        self.assertEqual(response.data, b'')
+
     def test_correct_header_line(self):
         def request_handler(listener):
             sock = listener.accept()[0]
@@ -472,7 +534,7 @@ def request_handler(listener):
             sock.close()
 
         self._start_server(request_handler)
-        proxy_url = "socks4://%s:%s" % (self.host, self.port)
+        proxy_url = "socks4a://%s:%s" % (self.host, self.port)
         pm = socks.SOCKSProxyManager(proxy_url)
         response = pm.request('GET', 'http://example.com')
         self.assertEqual(response.status, 200)
@@ -491,7 +553,7 @@ def request_handler(listener):
             sock.close()
 
         self._start_server(request_handler)
-        proxy_url = "socks4://%s:%s" % (self.host, self.port)
+        proxy_url = "socks4a://%s:%s" % (self.host, self.port)
         pm = socks.SOCKSProxyManager(proxy_url)
 
         self.assertRaises(
@@ -539,7 +601,7 @@ def request_handler(listener):
             next(handler)
 
         self._start_server(request_handler)
-        proxy_url = "socks4://%s:%s" % (self.host, self.port)
+        proxy_url = "socks4a://%s:%s" % (self.host, self.port)
         pm = socks.SOCKSProxyManager(proxy_url, username='baduser')
 
         try:
@@ -590,7 +652,7 @@ def request_handler(listener):
             tls.close()
 
         self._start_server(request_handler)
-        proxy_url = "socks5://%s:%s" % (self.host, self.port)
+        proxy_url = "socks5h://%s:%s" % (self.host, self.port)
         pm = socks.SOCKSProxyManager(proxy_url)
         response = pm.request('GET', 'https://localhost')