##// END OF EJS Templates
url: use CONNECT for HTTPS connections through HTTP proxy (issue967)...
Henrik Stuart -
r8590:59acb9c7 default
parent child Browse files
Show More
@@ -7,7 +7,7 b''
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2, incorporated herein by reference.
8 # GNU General Public License version 2, incorporated herein by reference.
9
9
10 import urllib, urllib2, urlparse, httplib, os, re
10 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
11 from i18n import _
11 from i18n import _
12 import keepalive, util
12 import keepalive, util
13
13
@@ -245,18 +245,165 b' def _gen_sendfile(connection):'
245 connection.send(self, data)
245 connection.send(self, data)
246 return _sendfile
246 return _sendfile
247
247
248 has_https = hasattr(urllib2, 'HTTPSHandler')
249 if has_https:
250 try:
251 # avoid using deprecated/broken FakeSocket in python 2.6
252 import ssl
253 _ssl_wrap_socket = ssl.wrap_socket
254 except ImportError:
255 def _ssl_wrap_socket(sock, key_file, cert_file):
256 ssl = socket.ssl(sock, key_file, cert_file)
257 return httplib.FakeSocket(sock, ssl)
258
248 class httpconnection(keepalive.HTTPConnection):
259 class httpconnection(keepalive.HTTPConnection):
249 # must be able to send big bundle as stream.
260 # must be able to send big bundle as stream.
250 send = _gen_sendfile(keepalive.HTTPConnection)
261 send = _gen_sendfile(keepalive.HTTPConnection)
251
262
263 def _proxytunnel(self):
264 proxyheaders = dict(
265 [(x, self.headers[x]) for x in self.headers
266 if x.lower().startswith('proxy-')])
267 self._set_hostport(self.host, self.port)
268 self.send('CONNECT %s:%d HTTP/1.0\r\n' % (self.realhost, self.realport))
269 for header in proxyheaders.iteritems():
270 self.send('%s: %s\r\n' % header)
271 self.send('\r\n')
272
273 # majority of the following code is duplicated from
274 # httplib.HTTPConnection as there are no adequate places to
275 # override functions to provide the needed functionality
276 res = self.response_class(self.sock,
277 strict=self.strict,
278 method=self._method)
279
280 while True:
281 version, status, reason = res._read_status()
282 if status != httplib.CONTINUE:
283 break
284 while True:
285 skip = res.fp.readline().strip()
286 if not skip:
287 break
288 res.status = status
289 res.reason = reason.strip()
290
291 if res.status == 200:
292 while True:
293 line = res.fp.readline()
294 if line == '\r\n':
295 break
296 return True
297
298 if version == 'HTTP/1.0':
299 res.version = 10
300 elif version.startswith('HTTP/1.'):
301 res.version = 11
302 elif version == 'HTTP/0.9':
303 res.version = 9
304 else:
305 raise httplib.UnknownProtocol(version)
306
307 if res.version == 9:
308 res.length = None
309 res.chunked = 0
310 res.will_close = 1
311 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
312 return False
313
314 res.msg = httplib.HTTPMessage(res.fp)
315 res.msg.fp = None
316
317 # are we using the chunked-style of transfer encoding?
318 trenc = res.msg.getheader('transfer-encoding')
319 if trenc and trenc.lower() == "chunked":
320 res.chunked = 1
321 res.chunk_left = None
322 else:
323 res.chunked = 0
324
325 # will the connection close at the end of the response?
326 res.will_close = res._check_close()
327
328 # do we have a Content-Length?
329 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
330 length = res.msg.getheader('content-length')
331 if length and not res.chunked:
332 try:
333 res.length = int(length)
334 except ValueError:
335 res.length = None
336 else:
337 if res.length < 0: # ignore nonsensical negative lengths
338 res.length = None
339 else:
340 res.length = None
341
342 # does the body have a fixed length? (of zero)
343 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
344 100 <= status < 200 or # 1xx codes
345 res._method == 'HEAD'):
346 res.length = 0
347
348 # if the connection remains open, and we aren't using chunked, and
349 # a content-length was not provided, then assume that the connection
350 # WILL close.
351 if (not res.will_close and
352 not res.chunked and
353 res.length is None):
354 res.will_close = 1
355
356 self.proxyres = res
357
358 return False
359
360 def connect(self):
361 if has_https and self.realhost: # use CONNECT proxy
362 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
363 self.sock.connect((self.host, self.port))
364 if self._proxytunnel():
365 # we do not support client x509 certificates
366 self.sock = _ssl_wrap_socket(self.sock, None, None)
367 else:
368 keepalive.HTTPConnection.connect(self)
369
370 def getresponse(self):
371 proxyres = getattr(self, 'proxyres', None)
372 if proxyres:
373 if proxyres.will_close:
374 self.close()
375 self.proxyres = None
376 return proxyres
377 return keepalive.HTTPConnection.getresponse(self)
378
252 class httphandler(keepalive.HTTPHandler):
379 class httphandler(keepalive.HTTPHandler):
253 def http_open(self, req):
380 def http_open(self, req):
254 return self.do_open(httpconnection, req)
381 return self.do_open(httpconnection, req)
255
382
383 def _start_transaction(self, h, req):
384 if req.get_selector() == req.get_full_url(): # has proxy
385 urlparts = urlparse.urlparse(req.get_selector())
386 if urlparts[0] == 'https': # only use CONNECT for HTTPS
387 if ':' in urlparts[1]:
388 realhost, realport = urlparts[1].split(':')
389 else:
390 realhost = urlparts[1]
391 realport = 443
392
393 h.realhost = realhost
394 h.realport = realport
395 h.headers = req.headers.copy()
396 h.headers.update(self.parent.addheaders)
397 return keepalive.HTTPHandler._start_transaction(self, h, req)
398
399 h.realhost = None
400 h.realport = None
401 h.headers = None
402 return keepalive.HTTPHandler._start_transaction(self, h, req)
403
256 def __del__(self):
404 def __del__(self):
257 self.close_all()
405 self.close_all()
258
406
259 has_https = hasattr(urllib2, 'HTTPSHandler')
260 if has_https:
407 if has_https:
261 class httpsconnection(httplib.HTTPSConnection):
408 class httpsconnection(httplib.HTTPSConnection):
262 response_class = keepalive.HTTPResponse
409 response_class = keepalive.HTTPResponse
General Comments 0
You need to be logged in to leave comments. Login now