2
# Copyright (c) 2008 Canonical
4
# Written by Marc Tardif <marc@interunion.ca>
6
# This file is part of Checkbox.
8
# Checkbox is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
13
# Checkbox is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
35
class ProxyHTTPConnection(httplib.HTTPConnection):
37
_ports = {"http" : httplib.HTTP_PORT, "https" : httplib.HTTPS_PORT}
39
def request(self, method, url, body=None, headers={}):
40
#request is called before connect, so can interpret url and get
41
#real host/port to be used to make CONNECT request to proxy
42
scheme, rest = urllib.splittype(url)
44
raise ValueError, "unknown URL type: %s" % url
46
host, rest = urllib.splithost(rest)
48
host, port = urllib.splitport(host)
49
#if port is not defined try to get from scheme
52
port = self._ports[scheme]
54
raise ValueError, "unknown protocol for: %s" % url
55
self._real_host = host
56
self._real_port = port
57
httplib.HTTPConnection.request(self, method, url, body, headers)
60
httplib.HTTPConnection.connect(self)
61
#send proxy CONNECT request
62
self.send("CONNECT %s:%d HTTP/1.0\r\n\r\n" % (self._real_host, self._real_port))
63
#expect a HTTP/1.0 200 Connection established
64
response = self.response_class(self.sock, strict=self.strict, method=self._method)
65
(version, code, message) = response._read_status()
66
#probably here we can handle auth requests...
68
#proxy returned and error, abort connection, and raise exception
70
raise socket.error, "Proxy connection failed: %d %s" % (code, message.strip())
71
#eat up header block from proxy....
73
#should not use directly fp probablu
74
line = response.fp.readline()
79
class ProxyHTTPSConnection(ProxyHTTPConnection):
81
default_port = httplib.HTTPS_PORT
83
def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None):
84
ProxyHTTPConnection.__init__(self, host, port)
85
self.key_file = key_file
86
self.cert_file = cert_file
89
ProxyHTTPConnection.connect(self)
90
#make the sock ssl-aware
91
ssl = socket.ssl(self.sock, self.key_file, self.cert_file)
92
self.sock = httplib.FakeSocket(self.sock, ssl)
95
class HTTPTransport(object):
96
"""Transport makes a request to exchange message data over HTTP."""
98
def __init__(self, url):
101
proxies = urllib.getproxies()
102
self.http_proxy = proxies.get("http")
103
self.https_proxy = proxies.get("https")
105
def _unpack_host_and_port(self, string):
106
scheme, rest = urllib.splittype(string)
107
host, rest = urllib.splithost(rest)
108
host, port = urllib.splitport(host)
111
def _get_connection(self, timeout=0):
113
socket.setdefaulttimeout(timeout)
115
scheme, rest = urllib.splittype(self.url)
118
host, port = self._unpack_host_and_port(self.http_proxy)
120
host, port = self._unpack_host_and_port(self.url)
122
connection = httplib.HTTPConnection(host, port)
123
elif scheme == "https":
125
host, port = self._unpack_host_and_port(self.https_proxy)
126
connection = ProxyHTTPSConnection(host, port)
128
host, port = self._unpack_host_and_port(self.url)
129
connection = httplib.HTTPSConnection(host, port)
131
raise Exception, "Unknown URL scheme: %s" % scheme
135
def _encode_multipart_formdata(self, fields=[], files=[]):
136
boundary = mimetools.choose_boundary()
139
for (key, value) in fields:
140
lines.append("--" + boundary)
141
lines.append("Content-Disposition: form-data; name=\"%s\"" % key)
145
for (key, file) in files:
146
if hasattr(file, "size"):
149
length = os.fstat(file.fileno())[stat.ST_SIZE]
151
filename = posixpath.basename(file.name)
152
if isinstance(filename, unicode):
153
filename = filename.encode("UTF-8")
155
lines.append("--" + boundary)
156
lines.append("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\""
158
lines.append("Content-Type: %s"
159
% mimetypes.guess_type(filename)[0] or "application/octet-stream")
160
lines.append("Content-Length: %s" % length)
163
if hasattr(file, "seek"):
165
lines.append(file.read())
167
lines.append("--" + boundary + "--")
170
content_type = "multipart/form-data; boundary=%s" % boundary
171
body = "\r\n".join(lines)
173
return content_type, body
175
def _encode_body(self, body=None):
179
content_type = "application/octet-stream"
180
if body is not None and type(body) != str:
181
if hasattr(body, "items"):
185
if len(body) and not isinstance(body[0], tuple):
188
ty, va, tb = sys.exc_info()
190
"Invalid non-string sequence or mapping", tb
192
for key, value in body:
193
if hasattr(value, "read"):
194
files.append((key, value))
196
fields.append((key, value))
199
content_type, body = self._encode_multipart_formdata(fields,
202
content_type = "application/x-www-form-urlencoded"
203
body = urllib.urlencode(fields)
207
return content_type, body
209
def exchange(self, body=None, headers={}, timeout=0):
210
headers = dict(headers)
214
(content_type, body) = self._encode_body(body)
215
if "Content-Type" not in headers:
216
headers["Content-Type"] = content_type
217
if "Content-Length" not in headers:
218
headers["Content-Length"] = len(body)
223
connection = self._get_connection(timeout)
226
connection.request(method, self.url, body, headers)
228
logging.warning("Can't connect to %s", self.url)
230
logging.error("Error connecting to %s", self.url)
231
except socket.timeout:
232
logging.warning("Timeout connecting to %s", self.url)
235
response = connection.getresponse()
236
except httplib.BadStatusLine:
237
logging.warning("Service unavailable on %s", self.url)
239
if response.status == httplib.FOUND:
240
# TODO prevent infinite redirect loop
241
self.url = self._get_location_header(response)
242
response = self.exchange(body, headers, timeout)