Fixed bug where PackageKit downloaded the wrong architecture
[zeroinstall/solver.git] / zeroinstall / injector / _download_child.py
blob8c983dd657de69dd5df942d576128e5783dcec11
1 # Copyright (C) 2011, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
4 import sys, os, socket, ssl
6 from zeroinstall import _
7 from zeroinstall.injector import download
8 from zeroinstall.support import ssl_match_hostname
10 if sys.version_info[0] > 2:
11 from urllib import request as urllib2
12 from http.client import HTTPSConnection, HTTPException
13 else:
14 import urllib2
15 from httplib import HTTPSConnection, HTTPException
17 try:
18 # http://pypi.python.org/pypi/certifi
19 import certifi
20 _fallback_ca_bundle = certifi.where()
21 except:
22 # Final fallback (last known signer of keylookup)
23 _fallback_ca_bundle = os.path.join(os.path.dirname(__file__), "EquifaxSecureCA.crt")
25 # Note: on MacOS X at least, it will also look in the system keychain provided that you supply *some* CAs.
26 # (if you don't specify any trusted CAs, Python trusts everything!)
27 # So, the "fallback" option doesn't necessarily mean that other sites won't work.
28 for ca_bundle in [
29 "/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Arch Linux
30 "/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL
31 "/etc/ssl/ca-bundle.pem", # openSUSE/SLE (claimed)
32 "/var/lib/ca-certificates/ca-bundle.pem.new", # openSUSE (actual)
33 _fallback_ca_bundle]:
34 if os.path.exists(ca_bundle):
35 class ValidatingHTTPSConnection(HTTPSConnection):
36 def connect(self):
37 sock = socket.create_connection((self.host, self.port), self.timeout)
38 if hasattr(self, '_tunnel_host') and self._tunnel_host:
39 self.sock = sock
40 self._tunnel()
41 sock = ssl.wrap_socket(sock, cert_reqs = ssl.CERT_REQUIRED, ca_certs = ca_bundle)
42 ssl_match_hostname.match_hostname(sock.getpeercert(), self.host)
43 self.sock = sock
45 class ValidatingHTTPSHandler(urllib2.HTTPSHandler):
46 def https_open(self, req):
47 return self.do_open(self.getConnection, req)
49 def getConnection(self, host, timeout=300):
50 return ValidatingHTTPSConnection(host)
51 MyHTTPSHandler = ValidatingHTTPSHandler
52 break
53 else:
54 raise Exception("No root CA's found (not even the built-in one!); security of HTTPS connections cannot be verified")
56 class Redirect(Exception):
57 def __init__(self, req):
58 Exception.__init__(self, "Redirect")
59 self.req = req
61 class MyRedirectHandler(urllib2.HTTPRedirectHandler):
62 """Throw an exception on redirects instead of continuing. The redirect will be handled in the main thread
63 so it can work with connection pooling."""
64 def redirect_request(self, req, fp, code, msg, headers, newurl):
65 new_req = urllib2.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
66 if new_req:
67 raise Redirect(new_req)
69 # Our handler differs from the Python default in that:
70 # - we don't support file:// URLs
71 # - we don't follow HTTP redirects
72 _my_urlopen = urllib2.OpenerDirector()
73 for klass in [urllib2.ProxyHandler, urllib2.UnknownHandler, urllib2.HTTPHandler,
74 urllib2.HTTPDefaultErrorHandler, MyRedirectHandler,
75 urllib2.FTPHandler, urllib2.HTTPErrorProcessor, MyHTTPSHandler]:
76 _my_urlopen.add_handler(klass())
78 def download_in_thread(url, target_file, if_modified_since, notify_done):
79 src = None
80 try:
81 #print "Child downloading", url
82 if url.startswith('http:') or url.startswith('https:') or url.startswith('ftp:'):
83 req = urllib2.Request(url)
84 if url.startswith('http:') and if_modified_since:
85 req.add_header('If-Modified-Since', if_modified_since)
86 src = _my_urlopen.open(req)
87 else:
88 raise Exception(_('Unsupported URL protocol in: %s') % url)
90 if sys.version_info[0] > 2:
91 sock_recv = src.fp.read1 # Python 3
92 else:
93 try:
94 sock_recv = src.fp._sock.recv # Python 2
95 except AttributeError:
96 sock_recv = src.fp.fp._sock.recv # Python 2.5 on FreeBSD
97 while True:
98 data = sock_recv(256)
99 if not data: break
100 target_file.write(data)
101 target_file.flush()
103 notify_done(download.RESULT_OK)
104 except (urllib2.HTTPError, urllib2.URLError, HTTPException, socket.error) as ex:
105 if isinstance(ex, urllib2.HTTPError) and ex.code == 304: # Not modified
106 notify_done(download.RESULT_NOT_MODIFIED)
107 else:
108 #print >>sys.stderr, "Error downloading '" + url + "': " + (str(ex) or str(ex.__class__.__name__))
109 __, ex, tb = sys.exc_info()
110 notify_done(download.RESULT_FAILED, (download.DownloadError(_('Error downloading {url}: {ex}').format(url = url, ex = ex)), tb))
111 except Redirect as ex:
112 notify_done(download.RESULT_REDIRECT, redirect = ex.req.get_full_url())
113 except Exception as ex:
114 __, ex, tb = sys.exc_info()
115 notify_done(download.RESULT_FAILED, (ex, tb))
116 finally:
117 if src is not None:
118 src.close()