Merge lp:~mvo/piston-mini-client/proxy_type_http_no_tunnel into lp:piston-mini-client

Proposed by Michael Vogt
Status: Merged
Merged at revision: 48
Proposed branch: lp:~mvo/piston-mini-client/proxy_type_http_no_tunnel
Merge into: lp:piston-mini-client
Diff against target: 170 lines (+82/-10)
3 files modified
piston_mini_client/__init__.py (+17/-6)
piston_mini_client/socks.py (+59/-4)
piston_mini_client/tests/test_proxy.py (+6/-0)
To merge this branch: bzr merge lp:~mvo/piston-mini-client/proxy_type_http_no_tunnel
Reviewer Review Type Date Requested Status
Anthony Lenton Approve
Review via email: mp+78094@code.launchpad.net

Description of the change

This adds support for a new type of http proxy that is sufficient for plain http tunneling to the internals version of socks.py. It will also patch httplib2 to use the internal version of socks.py over the one bundled with httplib2 that has no support for this. See http://code.google.com/p/httplib2/issues/detail?id=38 for some more details

To post a comment you must log in.
53. By Michael Vogt

piston_mini_client/__init__.py: always import httplib2 first

54. By Michael Vogt

use logging.warn() instead of print

Revision history for this message
Anthony Lenton (elachuni) wrote :

Thanks for the fix mvo!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'piston_mini_client/__init__.py'
2--- piston_mini_client/__init__.py 2011-09-28 08:32:48 +0000
3+++ piston_mini_client/__init__.py 2011-10-04 13:34:17 +0000
4@@ -3,6 +3,17 @@
5 # GNU Lesser General Public License version 3 (see the file LICENSE).
6
7 import httplib2
8+
9+try:
10+ # ensure we have a version with a fix for
11+ # http://code.google.com/p/httplib2/issues/detail?id=38
12+ # and if not, patch in our own socks with the fix
13+ from httplib2.socks import PROXY_TYPE_HTTP_NO_TUNNEL
14+ from httplib2 import socks
15+except ImportError:
16+ from piston_mini_client import socks
17+ httplib2.socks = socks
18+
19 import json
20 import os
21 import urllib
22@@ -262,19 +273,19 @@
23 def _get_proxy_info(self, scheme):
24 envvar = "%s_proxy" % scheme
25 if envvar in os.environ:
26- try:
27- # In Oneiric socks is already packaged as part of httplib2.
28- from httplib2 import socks
29- except ImportError:
30- from piston_mini_client import socks
31 url = urlparse(os.environ[envvar])
32 user_pass, sep, host_and_port = url.netloc.rpartition("@")
33 user, sep, passw = user_pass.partition(":")
34 host, sep, port = host_and_port.partition(":")
35 if port:
36 port = int(port)
37+ proxy_type = socks.PROXY_TYPE_HTTP
38+ if scheme == "http":
39+ # this will not require the CONNECT acl from squid and
40+ # is good enough for http connections
41+ proxy_type = socks.PROXY_TYPE_HTTP_NO_TUNNEL
42 proxy_info = httplib2.ProxyInfo(
43- proxy_type=socks.PROXY_TYPE_HTTP,
44+ proxy_type=proxy_type,
45 proxy_host=host,
46 proxy_port=port or 8080,
47 proxy_user=user or None,
48
49=== modified file 'piston_mini_client/socks.py'
50--- piston_mini_client/socks.py 2011-09-27 19:28:32 +0000
51+++ piston_mini_client/socks.py 2011-10-04 13:34:17 +0000
52@@ -43,13 +43,13 @@
53 import socket
54 import struct
55 import sys
56-
57-if getattr(socket, 'socket', None) is None:
58- raise ImportError('socket.socket missing, proxy support unusable')
59+import base64
60+import logging
61
62 PROXY_TYPE_SOCKS4 = 1
63 PROXY_TYPE_SOCKS5 = 2
64 PROXY_TYPE_HTTP = 3
65+PROXY_TYPE_HTTP_NO_TUNNEL = 4
66
67 _defaultproxy = None
68 _orgsocket = socket.socket
69@@ -127,6 +127,8 @@
70 self.__proxysockname = None
71 self.__proxypeername = None
72
73+ self.__httptunnel = True
74+
75 def __recvall(self, count):
76 """__recvall(count) -> data
77 Receive EXACTLY the number of bytes requested from the socket.
78@@ -139,6 +141,43 @@
79 data = data + d
80 return data
81
82+ def sendall(self, content, *args):
83+ """ override socket.socket.sendall method to rewrite the header
84+ for non-tunneling proxies if needed
85+ """
86+ if not self.__httptunnel:
87+ content = self.__rewriteproxy(content)
88+
89+ return super(socksocket, self).sendall(content, *args)
90+
91+ def __rewriteproxy(self, header):
92+ """ rewrite HTTP request headers to support non-tunneling proxies
93+ (i.e. thos which do not support the CONNECT method).
94+ This only works for HTTP (not HTTPS) since HTTPS requires tunneling.
95+ """
96+ host, endpt = None, None
97+ hdrs = header.split("\r\n")
98+ for hdr in hdrs:
99+ if hdr.lower().startswith("host:"):
100+ host = hdr
101+ elif hdr.lower().startswith("get") or hdr.lower().startswith("post"):
102+ endpt = hdr
103+ if host and endpt:
104+ hdrs.remove(host)
105+ hdrs.remove(endpt)
106+ host = host.split(" ")[1]
107+ endpt = endpt.split(" ")
108+ if (self.__proxy[4] != None and self.__proxy[5] != None):
109+ hdrs.insert(0, self.__getauthheader())
110+ hdrs.insert(0, "Host: %s" % host)
111+ hdrs.insert(0, "%s http://%s%s %s" % (endpt[0], host, endpt[1], endpt[2]))
112+
113+ return "\r\n".join(hdrs)
114+
115+ def __getauthheader(self):
116+ auth = self.__proxy[4] + ":" + self.__proxy[5]
117+ return "Proxy-Authorization: Basic " + base64.b64encode(auth)
118+
119 def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
120 """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
121 Sets the proxy to be used.
122@@ -326,7 +365,12 @@
123 addr = socket.gethostbyname(destaddr)
124 else:
125 addr = destaddr
126- self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode())
127+ headers = "CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n"
128+ headers += "Host: " + destaddr + "\r\n"
129+ if (self.__proxy[4] != None and self.__proxy[5] != None):
130+ headers += self.__getauthheader() + "\r\n"
131+ headers += "\r\n"
132+ self.sendall(headers.encode())
133 # We read the response until we get the string "\r\n\r\n"
134 resp = self.recv(1)
135 while resp.find("\r\n\r\n".encode()) == -1:
136@@ -379,6 +423,17 @@
137 portnum = 8080
138 _orgsocket.connect(self,(self.__proxy[1], portnum))
139 self.__negotiatehttp(destpair[0], destpair[1])
140+ elif self.__proxy[0] == PROXY_TYPE_HTTP_NO_TUNNEL:
141+ if self.__proxy[2] != None:
142+ portnum = self.__proxy[2]
143+ else:
144+ portnum = 8080
145+ _orgsocket.connect(self,(self.__proxy[1],portnum))
146+ if destpair[1] == 443:
147+ logging.warn("SSL connections (generally on port 443) require the use of tunneling - failing back to PROXY_TYPE_HTTP")
148+ self.__negotiatehttp(destpair[0],destpair[1])
149+ else:
150+ self.__httptunnel = False
151 elif self.__proxy[0] == None:
152 _orgsocket.connect(self, (destpair[0], destpair[1]))
153 else:
154
155=== modified file 'piston_mini_client/tests/test_proxy.py'
156--- piston_mini_client/tests/test_proxy.py 2011-09-27 16:09:08 +0000
157+++ piston_mini_client/tests/test_proxy.py 2011-10-04 13:34:17 +0000
158@@ -53,6 +53,12 @@
159 proxy_info = api._http["http"].proxy_info
160 self.assertEqual(proxy_info, None)
161
162+ def test_httplib2_proxy_type_http_no_tunnel(self):
163+ # test that patching httplib2 with our own socks.py works
164+ import httplib2
165+ import piston_mini_client
166+ self.assertEqual(piston_mini_client.socks.PROXY_TYPE_HTTP_NO_TUNNEL,
167+ httplib2.socks.PROXY_TYPE_HTTP_NO_TUNNEL)
168
169 if __name__ == "__main__":
170 import unittest

Subscribers

People subscribed via source and target branches