##// END OF EJS Templates
https: support tls sni (server name indication) for https urls (issue3090)...
Alex Orange -
r23834:bf07c19b default
parent child Browse files
Show More
@@ -1,186 +1,211 b''
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
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 or any later version.
8 # GNU General Public License version 2 or any later version.
9 import os, sys
9 import os, sys
10
10
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 try:
13 try:
14 # avoid using deprecated/broken FakeSocket in python 2.6
14 # avoid using deprecated/broken FakeSocket in python 2.6
15 import ssl
15 import ssl
16 CERT_REQUIRED = ssl.CERT_REQUIRED
16 CERT_REQUIRED = ssl.CERT_REQUIRED
17 PROTOCOL_TLSv1 = ssl.PROTOCOL_TLSv1
17 PROTOCOL_TLSv1 = ssl.PROTOCOL_TLSv1
18 def ssl_wrap_socket(sock, keyfile, certfile, ssl_version=PROTOCOL_TLSv1,
18 try:
19 cert_reqs=ssl.CERT_NONE, ca_certs=None):
19 ssl_context = ssl.SSLContext
20 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
20
21 cert_reqs=cert_reqs, ca_certs=ca_certs,
21 def ssl_wrap_socket(sock, keyfile, certfile, ssl_version=PROTOCOL_TLSv1,
22 ssl_version=ssl_version)
22 cert_reqs=ssl.CERT_NONE, ca_certs=None,
23 # check if wrap_socket failed silently because socket had been closed
23 serverhostname=None):
24 # - see http://bugs.python.org/issue13721
24 sslcontext = ssl.SSLContext(ssl_version)
25 if not sslsocket.cipher():
25 if certfile is not None:
26 raise util.Abort(_('ssl connection failed'))
26 sslcontext.load_cert_chain(certfile, keyfile)
27 return sslsocket
27 sslcontext.verify_mode = cert_reqs
28 if ca_certs is not None:
29 sslcontext.load_verify_locations(cafile=ca_certs)
30
31 sslsocket = sslcontext.wrap_socket(sock,
32 server_hostname=serverhostname)
33 # check if wrap_socket failed silently because socket had been
34 # closed
35 # - see http://bugs.python.org/issue13721
36 if not sslsocket.cipher():
37 raise util.Abort(_('ssl connection failed'))
38 return sslsocket
39 except AttributeError:
40 def ssl_wrap_socket(sock, keyfile, certfile, ssl_version=PROTOCOL_TLSv1,
41 cert_reqs=ssl.CERT_NONE, ca_certs=None,
42 serverhostname=None):
43 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
44 cert_reqs=cert_reqs, ca_certs=ca_certs,
45 ssl_version=ssl_version)
46 # check if wrap_socket failed silently because socket had been
47 # closed
48 # - see http://bugs.python.org/issue13721
49 if not sslsocket.cipher():
50 raise util.Abort(_('ssl connection failed'))
51 return sslsocket
28 except ImportError:
52 except ImportError:
29 CERT_REQUIRED = 2
53 CERT_REQUIRED = 2
30
54
31 PROTOCOL_TLSv1 = 3
55 PROTOCOL_TLSv1 = 3
32
56
33 import socket, httplib
57 import socket, httplib
34
58
35 def ssl_wrap_socket(sock, keyfile, certfile, ssl_version=PROTOCOL_TLSv1,
59 def ssl_wrap_socket(sock, keyfile, certfile, ssl_version=PROTOCOL_TLSv1,
36 cert_reqs=CERT_REQUIRED, ca_certs=None):
60 cert_reqs=CERT_REQUIRED, ca_certs=None,
61 serverhostname=None):
37 if not util.safehasattr(socket, 'ssl'):
62 if not util.safehasattr(socket, 'ssl'):
38 raise util.Abort(_('Python SSL support not found'))
63 raise util.Abort(_('Python SSL support not found'))
39 if ca_certs:
64 if ca_certs:
40 raise util.Abort(_(
65 raise util.Abort(_(
41 'certificate checking requires Python 2.6'))
66 'certificate checking requires Python 2.6'))
42
67
43 ssl = socket.ssl(sock, keyfile, certfile)
68 ssl = socket.ssl(sock, keyfile, certfile)
44 return httplib.FakeSocket(sock, ssl)
69 return httplib.FakeSocket(sock, ssl)
45
70
46 def _verifycert(cert, hostname):
71 def _verifycert(cert, hostname):
47 '''Verify that cert (in socket.getpeercert() format) matches hostname.
72 '''Verify that cert (in socket.getpeercert() format) matches hostname.
48 CRLs is not handled.
73 CRLs is not handled.
49
74
50 Returns error message if any problems are found and None on success.
75 Returns error message if any problems are found and None on success.
51 '''
76 '''
52 if not cert:
77 if not cert:
53 return _('no certificate received')
78 return _('no certificate received')
54 dnsname = hostname.lower()
79 dnsname = hostname.lower()
55 def matchdnsname(certname):
80 def matchdnsname(certname):
56 return (certname == dnsname or
81 return (certname == dnsname or
57 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
82 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
58
83
59 san = cert.get('subjectAltName', [])
84 san = cert.get('subjectAltName', [])
60 if san:
85 if san:
61 certnames = [value.lower() for key, value in san if key == 'DNS']
86 certnames = [value.lower() for key, value in san if key == 'DNS']
62 for name in certnames:
87 for name in certnames:
63 if matchdnsname(name):
88 if matchdnsname(name):
64 return None
89 return None
65 if certnames:
90 if certnames:
66 return _('certificate is for %s') % ', '.join(certnames)
91 return _('certificate is for %s') % ', '.join(certnames)
67
92
68 # subject is only checked when subjectAltName is empty
93 # subject is only checked when subjectAltName is empty
69 for s in cert.get('subject', []):
94 for s in cert.get('subject', []):
70 key, value = s[0]
95 key, value = s[0]
71 if key == 'commonName':
96 if key == 'commonName':
72 try:
97 try:
73 # 'subject' entries are unicode
98 # 'subject' entries are unicode
74 certname = value.lower().encode('ascii')
99 certname = value.lower().encode('ascii')
75 except UnicodeEncodeError:
100 except UnicodeEncodeError:
76 return _('IDN in certificate not supported')
101 return _('IDN in certificate not supported')
77 if matchdnsname(certname):
102 if matchdnsname(certname):
78 return None
103 return None
79 return _('certificate is for %s') % certname
104 return _('certificate is for %s') % certname
80 return _('no commonName or subjectAltName found in certificate')
105 return _('no commonName or subjectAltName found in certificate')
81
106
82
107
83 # CERT_REQUIRED means fetch the cert from the server all the time AND
108 # CERT_REQUIRED means fetch the cert from the server all the time AND
84 # validate it against the CA store provided in web.cacerts.
109 # validate it against the CA store provided in web.cacerts.
85 #
110 #
86 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
111 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
87 # busted on those versions.
112 # busted on those versions.
88
113
89 def _plainapplepython():
114 def _plainapplepython():
90 """return true if this seems to be a pure Apple Python that
115 """return true if this seems to be a pure Apple Python that
91 * is unfrozen and presumably has the whole mercurial module in the file
116 * is unfrozen and presumably has the whole mercurial module in the file
92 system
117 system
93 * presumably is an Apple Python that uses Apple OpenSSL which has patches
118 * presumably is an Apple Python that uses Apple OpenSSL which has patches
94 for using system certificate store CAs in addition to the provided
119 for using system certificate store CAs in addition to the provided
95 cacerts file
120 cacerts file
96 """
121 """
97 if sys.platform != 'darwin' or util.mainfrozen():
122 if sys.platform != 'darwin' or util.mainfrozen():
98 return False
123 return False
99 exe = (sys.executable or '').lower()
124 exe = (sys.executable or '').lower()
100 return (exe.startswith('/usr/bin/python') or
125 return (exe.startswith('/usr/bin/python') or
101 exe.startswith('/system/library/frameworks/python.framework/'))
126 exe.startswith('/system/library/frameworks/python.framework/'))
102
127
103 def sslkwargs(ui, host):
128 def sslkwargs(ui, host):
104 kws = {'ssl_version': PROTOCOL_TLSv1,
129 kws = {'ssl_version': PROTOCOL_TLSv1,
105 }
130 }
106 hostfingerprint = ui.config('hostfingerprints', host)
131 hostfingerprint = ui.config('hostfingerprints', host)
107 if hostfingerprint:
132 if hostfingerprint:
108 return kws
133 return kws
109 cacerts = ui.config('web', 'cacerts')
134 cacerts = ui.config('web', 'cacerts')
110 if cacerts:
135 if cacerts:
111 cacerts = util.expandpath(cacerts)
136 cacerts = util.expandpath(cacerts)
112 if not os.path.exists(cacerts):
137 if not os.path.exists(cacerts):
113 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
138 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
114 elif cacerts is None and _plainapplepython():
139 elif cacerts is None and _plainapplepython():
115 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
140 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
116 if os.path.exists(dummycert):
141 if os.path.exists(dummycert):
117 ui.debug('using %s to enable OS X system CA\n' % dummycert)
142 ui.debug('using %s to enable OS X system CA\n' % dummycert)
118 ui.setconfig('web', 'cacerts', dummycert, 'dummy')
143 ui.setconfig('web', 'cacerts', dummycert, 'dummy')
119 cacerts = dummycert
144 cacerts = dummycert
120 if cacerts:
145 if cacerts:
121 kws.update({'ca_certs': cacerts,
146 kws.update({'ca_certs': cacerts,
122 'cert_reqs': CERT_REQUIRED,
147 'cert_reqs': CERT_REQUIRED,
123 })
148 })
124 return kws
149 return kws
125
150
126 class validator(object):
151 class validator(object):
127 def __init__(self, ui, host):
152 def __init__(self, ui, host):
128 self.ui = ui
153 self.ui = ui
129 self.host = host
154 self.host = host
130
155
131 def __call__(self, sock, strict=False):
156 def __call__(self, sock, strict=False):
132 host = self.host
157 host = self.host
133 cacerts = self.ui.config('web', 'cacerts')
158 cacerts = self.ui.config('web', 'cacerts')
134 hostfingerprint = self.ui.config('hostfingerprints', host)
159 hostfingerprint = self.ui.config('hostfingerprints', host)
135 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
160 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
136 if hostfingerprint:
161 if hostfingerprint:
137 raise util.Abort(_("host fingerprint for %s can't be "
162 raise util.Abort(_("host fingerprint for %s can't be "
138 "verified (Python too old)") % host)
163 "verified (Python too old)") % host)
139 if strict:
164 if strict:
140 raise util.Abort(_("certificate for %s can't be verified "
165 raise util.Abort(_("certificate for %s can't be verified "
141 "(Python too old)") % host)
166 "(Python too old)") % host)
142 if self.ui.configbool('ui', 'reportoldssl', True):
167 if self.ui.configbool('ui', 'reportoldssl', True):
143 self.ui.warn(_("warning: certificate for %s can't be verified "
168 self.ui.warn(_("warning: certificate for %s can't be verified "
144 "(Python too old)\n") % host)
169 "(Python too old)\n") % host)
145 return
170 return
146
171
147 if not sock.cipher(): # work around http://bugs.python.org/issue13721
172 if not sock.cipher(): # work around http://bugs.python.org/issue13721
148 raise util.Abort(_('%s ssl connection error') % host)
173 raise util.Abort(_('%s ssl connection error') % host)
149 try:
174 try:
150 peercert = sock.getpeercert(True)
175 peercert = sock.getpeercert(True)
151 peercert2 = sock.getpeercert()
176 peercert2 = sock.getpeercert()
152 except AttributeError:
177 except AttributeError:
153 raise util.Abort(_('%s ssl connection error') % host)
178 raise util.Abort(_('%s ssl connection error') % host)
154
179
155 if not peercert:
180 if not peercert:
156 raise util.Abort(_('%s certificate error: '
181 raise util.Abort(_('%s certificate error: '
157 'no certificate received') % host)
182 'no certificate received') % host)
158 peerfingerprint = util.sha1(peercert).hexdigest()
183 peerfingerprint = util.sha1(peercert).hexdigest()
159 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
184 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
160 for x in xrange(0, len(peerfingerprint), 2)])
185 for x in xrange(0, len(peerfingerprint), 2)])
161 if hostfingerprint:
186 if hostfingerprint:
162 if peerfingerprint.lower() != \
187 if peerfingerprint.lower() != \
163 hostfingerprint.replace(':', '').lower():
188 hostfingerprint.replace(':', '').lower():
164 raise util.Abort(_('certificate for %s has unexpected '
189 raise util.Abort(_('certificate for %s has unexpected '
165 'fingerprint %s') % (host, nicefingerprint),
190 'fingerprint %s') % (host, nicefingerprint),
166 hint=_('check hostfingerprint configuration'))
191 hint=_('check hostfingerprint configuration'))
167 self.ui.debug('%s certificate matched fingerprint %s\n' %
192 self.ui.debug('%s certificate matched fingerprint %s\n' %
168 (host, nicefingerprint))
193 (host, nicefingerprint))
169 elif cacerts:
194 elif cacerts:
170 msg = _verifycert(peercert2, host)
195 msg = _verifycert(peercert2, host)
171 if msg:
196 if msg:
172 raise util.Abort(_('%s certificate error: %s') % (host, msg),
197 raise util.Abort(_('%s certificate error: %s') % (host, msg),
173 hint=_('configure hostfingerprint %s or use '
198 hint=_('configure hostfingerprint %s or use '
174 '--insecure to connect insecurely') %
199 '--insecure to connect insecurely') %
175 nicefingerprint)
200 nicefingerprint)
176 self.ui.debug('%s certificate successfully verified\n' % host)
201 self.ui.debug('%s certificate successfully verified\n' % host)
177 elif strict:
202 elif strict:
178 raise util.Abort(_('%s certificate with fingerprint %s not '
203 raise util.Abort(_('%s certificate with fingerprint %s not '
179 'verified') % (host, nicefingerprint),
204 'verified') % (host, nicefingerprint),
180 hint=_('check hostfingerprints or web.cacerts '
205 hint=_('check hostfingerprints or web.cacerts '
181 'config setting'))
206 'config setting'))
182 else:
207 else:
183 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
208 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
184 'verified (check hostfingerprints or web.cacerts '
209 'verified (check hostfingerprints or web.cacerts '
185 'config setting)\n') %
210 'config setting)\n') %
186 (host, nicefingerprint))
211 (host, nicefingerprint))
@@ -1,509 +1,510 b''
1 # url.py - HTTP handling for mercurial
1 # url.py - HTTP handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
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 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 import urllib, urllib2, httplib, os, socket, cStringIO, base64
10 import urllib, urllib2, httplib, os, socket, cStringIO, base64
11 from i18n import _
11 from i18n import _
12 import keepalive, util, sslutil
12 import keepalive, util, sslutil
13 import httpconnection as httpconnectionmod
13 import httpconnection as httpconnectionmod
14
14
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 def __init__(self, ui):
16 def __init__(self, ui):
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 self.ui = ui
18 self.ui = ui
19
19
20 def find_user_password(self, realm, authuri):
20 def find_user_password(self, realm, authuri):
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 self, realm, authuri)
22 self, realm, authuri)
23 user, passwd = authinfo
23 user, passwd = authinfo
24 if user and passwd:
24 if user and passwd:
25 self._writedebug(user, passwd)
25 self._writedebug(user, passwd)
26 return (user, passwd)
26 return (user, passwd)
27
27
28 if not user or not passwd:
28 if not user or not passwd:
29 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
29 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
30 if res:
30 if res:
31 group, auth = res
31 group, auth = res
32 user, passwd = auth.get('username'), auth.get('password')
32 user, passwd = auth.get('username'), auth.get('password')
33 self.ui.debug("using auth.%s.* for authentication\n" % group)
33 self.ui.debug("using auth.%s.* for authentication\n" % group)
34 if not user or not passwd:
34 if not user or not passwd:
35 u = util.url(authuri)
35 u = util.url(authuri)
36 u.query = None
36 u.query = None
37 if not self.ui.interactive():
37 if not self.ui.interactive():
38 raise util.Abort(_('http authorization required for %s') %
38 raise util.Abort(_('http authorization required for %s') %
39 util.hidepassword(str(u)))
39 util.hidepassword(str(u)))
40
40
41 self.ui.write(_("http authorization required for %s\n") %
41 self.ui.write(_("http authorization required for %s\n") %
42 util.hidepassword(str(u)))
42 util.hidepassword(str(u)))
43 self.ui.write(_("realm: %s\n") % realm)
43 self.ui.write(_("realm: %s\n") % realm)
44 if user:
44 if user:
45 self.ui.write(_("user: %s\n") % user)
45 self.ui.write(_("user: %s\n") % user)
46 else:
46 else:
47 user = self.ui.prompt(_("user:"), default=None)
47 user = self.ui.prompt(_("user:"), default=None)
48
48
49 if not passwd:
49 if not passwd:
50 passwd = self.ui.getpass()
50 passwd = self.ui.getpass()
51
51
52 self.add_password(realm, authuri, user, passwd)
52 self.add_password(realm, authuri, user, passwd)
53 self._writedebug(user, passwd)
53 self._writedebug(user, passwd)
54 return (user, passwd)
54 return (user, passwd)
55
55
56 def _writedebug(self, user, passwd):
56 def _writedebug(self, user, passwd):
57 msg = _('http auth: user %s, password %s\n')
57 msg = _('http auth: user %s, password %s\n')
58 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
58 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
59
59
60 def find_stored_password(self, authuri):
60 def find_stored_password(self, authuri):
61 return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
61 return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
62 self, None, authuri)
62 self, None, authuri)
63
63
64 class proxyhandler(urllib2.ProxyHandler):
64 class proxyhandler(urllib2.ProxyHandler):
65 def __init__(self, ui):
65 def __init__(self, ui):
66 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
66 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
67 # XXX proxyauthinfo = None
67 # XXX proxyauthinfo = None
68
68
69 if proxyurl:
69 if proxyurl:
70 # proxy can be proper url or host[:port]
70 # proxy can be proper url or host[:port]
71 if not (proxyurl.startswith('http:') or
71 if not (proxyurl.startswith('http:') or
72 proxyurl.startswith('https:')):
72 proxyurl.startswith('https:')):
73 proxyurl = 'http://' + proxyurl + '/'
73 proxyurl = 'http://' + proxyurl + '/'
74 proxy = util.url(proxyurl)
74 proxy = util.url(proxyurl)
75 if not proxy.user:
75 if not proxy.user:
76 proxy.user = ui.config("http_proxy", "user")
76 proxy.user = ui.config("http_proxy", "user")
77 proxy.passwd = ui.config("http_proxy", "passwd")
77 proxy.passwd = ui.config("http_proxy", "passwd")
78
78
79 # see if we should use a proxy for this url
79 # see if we should use a proxy for this url
80 no_list = ["localhost", "127.0.0.1"]
80 no_list = ["localhost", "127.0.0.1"]
81 no_list.extend([p.lower() for
81 no_list.extend([p.lower() for
82 p in ui.configlist("http_proxy", "no")])
82 p in ui.configlist("http_proxy", "no")])
83 no_list.extend([p.strip().lower() for
83 no_list.extend([p.strip().lower() for
84 p in os.getenv("no_proxy", '').split(',')
84 p in os.getenv("no_proxy", '').split(',')
85 if p.strip()])
85 if p.strip()])
86 # "http_proxy.always" config is for running tests on localhost
86 # "http_proxy.always" config is for running tests on localhost
87 if ui.configbool("http_proxy", "always"):
87 if ui.configbool("http_proxy", "always"):
88 self.no_list = []
88 self.no_list = []
89 else:
89 else:
90 self.no_list = no_list
90 self.no_list = no_list
91
91
92 proxyurl = str(proxy)
92 proxyurl = str(proxy)
93 proxies = {'http': proxyurl, 'https': proxyurl}
93 proxies = {'http': proxyurl, 'https': proxyurl}
94 ui.debug('proxying through http://%s:%s\n' %
94 ui.debug('proxying through http://%s:%s\n' %
95 (proxy.host, proxy.port))
95 (proxy.host, proxy.port))
96 else:
96 else:
97 proxies = {}
97 proxies = {}
98
98
99 # urllib2 takes proxy values from the environment and those
99 # urllib2 takes proxy values from the environment and those
100 # will take precedence if found. So, if there's a config entry
100 # will take precedence if found. So, if there's a config entry
101 # defining a proxy, drop the environment ones
101 # defining a proxy, drop the environment ones
102 if ui.config("http_proxy", "host"):
102 if ui.config("http_proxy", "host"):
103 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
103 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
104 try:
104 try:
105 if env in os.environ:
105 if env in os.environ:
106 del os.environ[env]
106 del os.environ[env]
107 except OSError:
107 except OSError:
108 pass
108 pass
109
109
110 urllib2.ProxyHandler.__init__(self, proxies)
110 urllib2.ProxyHandler.__init__(self, proxies)
111 self.ui = ui
111 self.ui = ui
112
112
113 def proxy_open(self, req, proxy, type_):
113 def proxy_open(self, req, proxy, type_):
114 host = req.get_host().split(':')[0]
114 host = req.get_host().split(':')[0]
115 for e in self.no_list:
115 for e in self.no_list:
116 if host == e:
116 if host == e:
117 return None
117 return None
118 if e.startswith('*.') and host.endswith(e[2:]):
118 if e.startswith('*.') and host.endswith(e[2:]):
119 return None
119 return None
120 if e.startswith('.') and host.endswith(e[1:]):
120 if e.startswith('.') and host.endswith(e[1:]):
121 return None
121 return None
122
122
123 # work around a bug in Python < 2.4.2
123 # work around a bug in Python < 2.4.2
124 # (it leaves a "\n" at the end of Proxy-authorization headers)
124 # (it leaves a "\n" at the end of Proxy-authorization headers)
125 baseclass = req.__class__
125 baseclass = req.__class__
126 class _request(baseclass):
126 class _request(baseclass):
127 def add_header(self, key, val):
127 def add_header(self, key, val):
128 if key.lower() == 'proxy-authorization':
128 if key.lower() == 'proxy-authorization':
129 val = val.strip()
129 val = val.strip()
130 return baseclass.add_header(self, key, val)
130 return baseclass.add_header(self, key, val)
131 req.__class__ = _request
131 req.__class__ = _request
132
132
133 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
133 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
134
134
135 def _gen_sendfile(orgsend):
135 def _gen_sendfile(orgsend):
136 def _sendfile(self, data):
136 def _sendfile(self, data):
137 # send a file
137 # send a file
138 if isinstance(data, httpconnectionmod.httpsendfile):
138 if isinstance(data, httpconnectionmod.httpsendfile):
139 # if auth required, some data sent twice, so rewind here
139 # if auth required, some data sent twice, so rewind here
140 data.seek(0)
140 data.seek(0)
141 for chunk in util.filechunkiter(data):
141 for chunk in util.filechunkiter(data):
142 orgsend(self, chunk)
142 orgsend(self, chunk)
143 else:
143 else:
144 orgsend(self, data)
144 orgsend(self, data)
145 return _sendfile
145 return _sendfile
146
146
147 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
147 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
148 if has_https:
148 if has_https:
149 try:
149 try:
150 _create_connection = socket.create_connection
150 _create_connection = socket.create_connection
151 except AttributeError:
151 except AttributeError:
152 _GLOBAL_DEFAULT_TIMEOUT = object()
152 _GLOBAL_DEFAULT_TIMEOUT = object()
153
153
154 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
154 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
155 source_address=None):
155 source_address=None):
156 # lifted from Python 2.6
156 # lifted from Python 2.6
157
157
158 msg = "getaddrinfo returns an empty list"
158 msg = "getaddrinfo returns an empty list"
159 host, port = address
159 host, port = address
160 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
160 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
161 af, socktype, proto, canonname, sa = res
161 af, socktype, proto, canonname, sa = res
162 sock = None
162 sock = None
163 try:
163 try:
164 sock = socket.socket(af, socktype, proto)
164 sock = socket.socket(af, socktype, proto)
165 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
165 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
166 sock.settimeout(timeout)
166 sock.settimeout(timeout)
167 if source_address:
167 if source_address:
168 sock.bind(source_address)
168 sock.bind(source_address)
169 sock.connect(sa)
169 sock.connect(sa)
170 return sock
170 return sock
171
171
172 except socket.error, msg:
172 except socket.error, msg:
173 if sock is not None:
173 if sock is not None:
174 sock.close()
174 sock.close()
175
175
176 raise socket.error(msg)
176 raise socket.error(msg)
177
177
178 class httpconnection(keepalive.HTTPConnection):
178 class httpconnection(keepalive.HTTPConnection):
179 # must be able to send big bundle as stream.
179 # must be able to send big bundle as stream.
180 send = _gen_sendfile(keepalive.HTTPConnection.send)
180 send = _gen_sendfile(keepalive.HTTPConnection.send)
181
181
182 def connect(self):
182 def connect(self):
183 if has_https and self.realhostport: # use CONNECT proxy
183 if has_https and self.realhostport: # use CONNECT proxy
184 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
184 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
185 self.sock.connect((self.host, self.port))
185 self.sock.connect((self.host, self.port))
186 if _generic_proxytunnel(self):
186 if _generic_proxytunnel(self):
187 # we do not support client X.509 certificates
187 # we do not support client X.509 certificates
188 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None)
188 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None,
189 serverhostname=self.host)
189 else:
190 else:
190 keepalive.HTTPConnection.connect(self)
191 keepalive.HTTPConnection.connect(self)
191
192
192 def getresponse(self):
193 def getresponse(self):
193 proxyres = getattr(self, 'proxyres', None)
194 proxyres = getattr(self, 'proxyres', None)
194 if proxyres:
195 if proxyres:
195 if proxyres.will_close:
196 if proxyres.will_close:
196 self.close()
197 self.close()
197 self.proxyres = None
198 self.proxyres = None
198 return proxyres
199 return proxyres
199 return keepalive.HTTPConnection.getresponse(self)
200 return keepalive.HTTPConnection.getresponse(self)
200
201
201 # general transaction handler to support different ways to handle
202 # general transaction handler to support different ways to handle
202 # HTTPS proxying before and after Python 2.6.3.
203 # HTTPS proxying before and after Python 2.6.3.
203 def _generic_start_transaction(handler, h, req):
204 def _generic_start_transaction(handler, h, req):
204 tunnel_host = getattr(req, '_tunnel_host', None)
205 tunnel_host = getattr(req, '_tunnel_host', None)
205 if tunnel_host:
206 if tunnel_host:
206 if tunnel_host[:7] not in ['http://', 'https:/']:
207 if tunnel_host[:7] not in ['http://', 'https:/']:
207 tunnel_host = 'https://' + tunnel_host
208 tunnel_host = 'https://' + tunnel_host
208 new_tunnel = True
209 new_tunnel = True
209 else:
210 else:
210 tunnel_host = req.get_selector()
211 tunnel_host = req.get_selector()
211 new_tunnel = False
212 new_tunnel = False
212
213
213 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
214 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
214 u = util.url(tunnel_host)
215 u = util.url(tunnel_host)
215 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
216 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
216 h.realhostport = ':'.join([u.host, (u.port or '443')])
217 h.realhostport = ':'.join([u.host, (u.port or '443')])
217 h.headers = req.headers.copy()
218 h.headers = req.headers.copy()
218 h.headers.update(handler.parent.addheaders)
219 h.headers.update(handler.parent.addheaders)
219 return
220 return
220
221
221 h.realhostport = None
222 h.realhostport = None
222 h.headers = None
223 h.headers = None
223
224
224 def _generic_proxytunnel(self):
225 def _generic_proxytunnel(self):
225 proxyheaders = dict(
226 proxyheaders = dict(
226 [(x, self.headers[x]) for x in self.headers
227 [(x, self.headers[x]) for x in self.headers
227 if x.lower().startswith('proxy-')])
228 if x.lower().startswith('proxy-')])
228 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
229 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
229 for header in proxyheaders.iteritems():
230 for header in proxyheaders.iteritems():
230 self.send('%s: %s\r\n' % header)
231 self.send('%s: %s\r\n' % header)
231 self.send('\r\n')
232 self.send('\r\n')
232
233
233 # majority of the following code is duplicated from
234 # majority of the following code is duplicated from
234 # httplib.HTTPConnection as there are no adequate places to
235 # httplib.HTTPConnection as there are no adequate places to
235 # override functions to provide the needed functionality
236 # override functions to provide the needed functionality
236 res = self.response_class(self.sock,
237 res = self.response_class(self.sock,
237 strict=self.strict,
238 strict=self.strict,
238 method=self._method)
239 method=self._method)
239
240
240 while True:
241 while True:
241 version, status, reason = res._read_status()
242 version, status, reason = res._read_status()
242 if status != httplib.CONTINUE:
243 if status != httplib.CONTINUE:
243 break
244 break
244 while True:
245 while True:
245 skip = res.fp.readline().strip()
246 skip = res.fp.readline().strip()
246 if not skip:
247 if not skip:
247 break
248 break
248 res.status = status
249 res.status = status
249 res.reason = reason.strip()
250 res.reason = reason.strip()
250
251
251 if res.status == 200:
252 if res.status == 200:
252 while True:
253 while True:
253 line = res.fp.readline()
254 line = res.fp.readline()
254 if line == '\r\n':
255 if line == '\r\n':
255 break
256 break
256 return True
257 return True
257
258
258 if version == 'HTTP/1.0':
259 if version == 'HTTP/1.0':
259 res.version = 10
260 res.version = 10
260 elif version.startswith('HTTP/1.'):
261 elif version.startswith('HTTP/1.'):
261 res.version = 11
262 res.version = 11
262 elif version == 'HTTP/0.9':
263 elif version == 'HTTP/0.9':
263 res.version = 9
264 res.version = 9
264 else:
265 else:
265 raise httplib.UnknownProtocol(version)
266 raise httplib.UnknownProtocol(version)
266
267
267 if res.version == 9:
268 if res.version == 9:
268 res.length = None
269 res.length = None
269 res.chunked = 0
270 res.chunked = 0
270 res.will_close = 1
271 res.will_close = 1
271 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
272 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
272 return False
273 return False
273
274
274 res.msg = httplib.HTTPMessage(res.fp)
275 res.msg = httplib.HTTPMessage(res.fp)
275 res.msg.fp = None
276 res.msg.fp = None
276
277
277 # are we using the chunked-style of transfer encoding?
278 # are we using the chunked-style of transfer encoding?
278 trenc = res.msg.getheader('transfer-encoding')
279 trenc = res.msg.getheader('transfer-encoding')
279 if trenc and trenc.lower() == "chunked":
280 if trenc and trenc.lower() == "chunked":
280 res.chunked = 1
281 res.chunked = 1
281 res.chunk_left = None
282 res.chunk_left = None
282 else:
283 else:
283 res.chunked = 0
284 res.chunked = 0
284
285
285 # will the connection close at the end of the response?
286 # will the connection close at the end of the response?
286 res.will_close = res._check_close()
287 res.will_close = res._check_close()
287
288
288 # do we have a Content-Length?
289 # do we have a Content-Length?
289 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
290 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
290 # transfer-encoding is "chunked"
291 # transfer-encoding is "chunked"
291 length = res.msg.getheader('content-length')
292 length = res.msg.getheader('content-length')
292 if length and not res.chunked:
293 if length and not res.chunked:
293 try:
294 try:
294 res.length = int(length)
295 res.length = int(length)
295 except ValueError:
296 except ValueError:
296 res.length = None
297 res.length = None
297 else:
298 else:
298 if res.length < 0: # ignore nonsensical negative lengths
299 if res.length < 0: # ignore nonsensical negative lengths
299 res.length = None
300 res.length = None
300 else:
301 else:
301 res.length = None
302 res.length = None
302
303
303 # does the body have a fixed length? (of zero)
304 # does the body have a fixed length? (of zero)
304 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
305 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
305 100 <= status < 200 or # 1xx codes
306 100 <= status < 200 or # 1xx codes
306 res._method == 'HEAD'):
307 res._method == 'HEAD'):
307 res.length = 0
308 res.length = 0
308
309
309 # if the connection remains open, and we aren't using chunked, and
310 # if the connection remains open, and we aren't using chunked, and
310 # a content-length was not provided, then assume that the connection
311 # a content-length was not provided, then assume that the connection
311 # WILL close.
312 # WILL close.
312 if (not res.will_close and
313 if (not res.will_close and
313 not res.chunked and
314 not res.chunked and
314 res.length is None):
315 res.length is None):
315 res.will_close = 1
316 res.will_close = 1
316
317
317 self.proxyres = res
318 self.proxyres = res
318
319
319 return False
320 return False
320
321
321 class httphandler(keepalive.HTTPHandler):
322 class httphandler(keepalive.HTTPHandler):
322 def http_open(self, req):
323 def http_open(self, req):
323 return self.do_open(httpconnection, req)
324 return self.do_open(httpconnection, req)
324
325
325 def _start_transaction(self, h, req):
326 def _start_transaction(self, h, req):
326 _generic_start_transaction(self, h, req)
327 _generic_start_transaction(self, h, req)
327 return keepalive.HTTPHandler._start_transaction(self, h, req)
328 return keepalive.HTTPHandler._start_transaction(self, h, req)
328
329
329 if has_https:
330 if has_https:
330 class httpsconnection(httplib.HTTPSConnection):
331 class httpsconnection(httplib.HTTPSConnection):
331 response_class = keepalive.HTTPResponse
332 response_class = keepalive.HTTPResponse
332 # must be able to send big bundle as stream.
333 # must be able to send big bundle as stream.
333 send = _gen_sendfile(keepalive.safesend)
334 send = _gen_sendfile(keepalive.safesend)
334 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
335 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
335
336
336 def connect(self):
337 def connect(self):
337 self.sock = _create_connection((self.host, self.port))
338 self.sock = _create_connection((self.host, self.port))
338
339
339 host = self.host
340 host = self.host
340 if self.realhostport: # use CONNECT proxy
341 if self.realhostport: # use CONNECT proxy
341 _generic_proxytunnel(self)
342 _generic_proxytunnel(self)
342 host = self.realhostport.rsplit(':', 1)[0]
343 host = self.realhostport.rsplit(':', 1)[0]
343 self.sock = sslutil.ssl_wrap_socket(
344 self.sock = sslutil.ssl_wrap_socket(
344 self.sock, self.key_file, self.cert_file,
345 self.sock, self.key_file, self.cert_file, serverhostname=host,
345 **sslutil.sslkwargs(self.ui, host))
346 **sslutil.sslkwargs(self.ui, host))
346 sslutil.validator(self.ui, host)(self.sock)
347 sslutil.validator(self.ui, host)(self.sock)
347
348
348 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
349 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
349 def __init__(self, ui):
350 def __init__(self, ui):
350 keepalive.KeepAliveHandler.__init__(self)
351 keepalive.KeepAliveHandler.__init__(self)
351 urllib2.HTTPSHandler.__init__(self)
352 urllib2.HTTPSHandler.__init__(self)
352 self.ui = ui
353 self.ui = ui
353 self.pwmgr = passwordmgr(self.ui)
354 self.pwmgr = passwordmgr(self.ui)
354
355
355 def _start_transaction(self, h, req):
356 def _start_transaction(self, h, req):
356 _generic_start_transaction(self, h, req)
357 _generic_start_transaction(self, h, req)
357 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
358 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
358
359
359 def https_open(self, req):
360 def https_open(self, req):
360 # req.get_full_url() does not contain credentials and we may
361 # req.get_full_url() does not contain credentials and we may
361 # need them to match the certificates.
362 # need them to match the certificates.
362 url = req.get_full_url()
363 url = req.get_full_url()
363 user, password = self.pwmgr.find_stored_password(url)
364 user, password = self.pwmgr.find_stored_password(url)
364 res = httpconnectionmod.readauthforuri(self.ui, url, user)
365 res = httpconnectionmod.readauthforuri(self.ui, url, user)
365 if res:
366 if res:
366 group, auth = res
367 group, auth = res
367 self.auth = auth
368 self.auth = auth
368 self.ui.debug("using auth.%s.* for authentication\n" % group)
369 self.ui.debug("using auth.%s.* for authentication\n" % group)
369 else:
370 else:
370 self.auth = None
371 self.auth = None
371 return self.do_open(self._makeconnection, req)
372 return self.do_open(self._makeconnection, req)
372
373
373 def _makeconnection(self, host, port=None, *args, **kwargs):
374 def _makeconnection(self, host, port=None, *args, **kwargs):
374 keyfile = None
375 keyfile = None
375 certfile = None
376 certfile = None
376
377
377 if len(args) >= 1: # key_file
378 if len(args) >= 1: # key_file
378 keyfile = args[0]
379 keyfile = args[0]
379 if len(args) >= 2: # cert_file
380 if len(args) >= 2: # cert_file
380 certfile = args[1]
381 certfile = args[1]
381 args = args[2:]
382 args = args[2:]
382
383
383 # if the user has specified different key/cert files in
384 # if the user has specified different key/cert files in
384 # hgrc, we prefer these
385 # hgrc, we prefer these
385 if self.auth and 'key' in self.auth and 'cert' in self.auth:
386 if self.auth and 'key' in self.auth and 'cert' in self.auth:
386 keyfile = self.auth['key']
387 keyfile = self.auth['key']
387 certfile = self.auth['cert']
388 certfile = self.auth['cert']
388
389
389 conn = httpsconnection(host, port, keyfile, certfile, *args,
390 conn = httpsconnection(host, port, keyfile, certfile, *args,
390 **kwargs)
391 **kwargs)
391 conn.ui = self.ui
392 conn.ui = self.ui
392 return conn
393 return conn
393
394
394 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
395 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
395 def __init__(self, *args, **kwargs):
396 def __init__(self, *args, **kwargs):
396 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
397 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
397 self.retried_req = None
398 self.retried_req = None
398
399
399 def reset_retry_count(self):
400 def reset_retry_count(self):
400 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
401 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
401 # forever. We disable reset_retry_count completely and reset in
402 # forever. We disable reset_retry_count completely and reset in
402 # http_error_auth_reqed instead.
403 # http_error_auth_reqed instead.
403 pass
404 pass
404
405
405 def http_error_auth_reqed(self, auth_header, host, req, headers):
406 def http_error_auth_reqed(self, auth_header, host, req, headers):
406 # Reset the retry counter once for each request.
407 # Reset the retry counter once for each request.
407 if req is not self.retried_req:
408 if req is not self.retried_req:
408 self.retried_req = req
409 self.retried_req = req
409 self.retried = 0
410 self.retried = 0
410 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
411 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
411 # it doesn't know about the auth type requested. This can happen if
412 # it doesn't know about the auth type requested. This can happen if
412 # somebody is using BasicAuth and types a bad password.
413 # somebody is using BasicAuth and types a bad password.
413 try:
414 try:
414 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
415 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
415 self, auth_header, host, req, headers)
416 self, auth_header, host, req, headers)
416 except ValueError, inst:
417 except ValueError, inst:
417 arg = inst.args[0]
418 arg = inst.args[0]
418 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
419 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
419 return
420 return
420 raise
421 raise
421
422
422 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
423 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
423 def __init__(self, *args, **kwargs):
424 def __init__(self, *args, **kwargs):
424 self.auth = None
425 self.auth = None
425 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
426 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
426 self.retried_req = None
427 self.retried_req = None
427
428
428 def http_request(self, request):
429 def http_request(self, request):
429 if self.auth:
430 if self.auth:
430 request.add_unredirected_header(self.auth_header, self.auth)
431 request.add_unredirected_header(self.auth_header, self.auth)
431
432
432 return request
433 return request
433
434
434 def https_request(self, request):
435 def https_request(self, request):
435 if self.auth:
436 if self.auth:
436 request.add_unredirected_header(self.auth_header, self.auth)
437 request.add_unredirected_header(self.auth_header, self.auth)
437
438
438 return request
439 return request
439
440
440 def reset_retry_count(self):
441 def reset_retry_count(self):
441 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
442 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
442 # forever. We disable reset_retry_count completely and reset in
443 # forever. We disable reset_retry_count completely and reset in
443 # http_error_auth_reqed instead.
444 # http_error_auth_reqed instead.
444 pass
445 pass
445
446
446 def http_error_auth_reqed(self, auth_header, host, req, headers):
447 def http_error_auth_reqed(self, auth_header, host, req, headers):
447 # Reset the retry counter once for each request.
448 # Reset the retry counter once for each request.
448 if req is not self.retried_req:
449 if req is not self.retried_req:
449 self.retried_req = req
450 self.retried_req = req
450 self.retried = 0
451 self.retried = 0
451 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
452 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
452 self, auth_header, host, req, headers)
453 self, auth_header, host, req, headers)
453
454
454 def retry_http_basic_auth(self, host, req, realm):
455 def retry_http_basic_auth(self, host, req, realm):
455 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
456 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
456 if pw is not None:
457 if pw is not None:
457 raw = "%s:%s" % (user, pw)
458 raw = "%s:%s" % (user, pw)
458 auth = 'Basic %s' % base64.b64encode(raw).strip()
459 auth = 'Basic %s' % base64.b64encode(raw).strip()
459 if req.headers.get(self.auth_header, None) == auth:
460 if req.headers.get(self.auth_header, None) == auth:
460 return None
461 return None
461 self.auth = auth
462 self.auth = auth
462 req.add_unredirected_header(self.auth_header, auth)
463 req.add_unredirected_header(self.auth_header, auth)
463 return self.parent.open(req)
464 return self.parent.open(req)
464 else:
465 else:
465 return None
466 return None
466
467
467 handlerfuncs = []
468 handlerfuncs = []
468
469
469 def opener(ui, authinfo=None):
470 def opener(ui, authinfo=None):
470 '''
471 '''
471 construct an opener suitable for urllib2
472 construct an opener suitable for urllib2
472 authinfo will be added to the password manager
473 authinfo will be added to the password manager
473 '''
474 '''
474 if ui.configbool('ui', 'usehttp2', False):
475 if ui.configbool('ui', 'usehttp2', False):
475 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
476 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
476 else:
477 else:
477 handlers = [httphandler()]
478 handlers = [httphandler()]
478 if has_https:
479 if has_https:
479 handlers.append(httpshandler(ui))
480 handlers.append(httpshandler(ui))
480
481
481 handlers.append(proxyhandler(ui))
482 handlers.append(proxyhandler(ui))
482
483
483 passmgr = passwordmgr(ui)
484 passmgr = passwordmgr(ui)
484 if authinfo is not None:
485 if authinfo is not None:
485 passmgr.add_password(*authinfo)
486 passmgr.add_password(*authinfo)
486 user, passwd = authinfo[2:4]
487 user, passwd = authinfo[2:4]
487 ui.debug('http auth: user %s, password %s\n' %
488 ui.debug('http auth: user %s, password %s\n' %
488 (user, passwd and '*' * len(passwd) or 'not set'))
489 (user, passwd and '*' * len(passwd) or 'not set'))
489
490
490 handlers.extend((httpbasicauthhandler(passmgr),
491 handlers.extend((httpbasicauthhandler(passmgr),
491 httpdigestauthhandler(passmgr)))
492 httpdigestauthhandler(passmgr)))
492 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
493 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
493 opener = urllib2.build_opener(*handlers)
494 opener = urllib2.build_opener(*handlers)
494
495
495 # 1.0 here is the _protocol_ version
496 # 1.0 here is the _protocol_ version
496 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
497 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
497 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
498 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
498 return opener
499 return opener
499
500
500 def open(ui, url_, data=None):
501 def open(ui, url_, data=None):
501 u = util.url(url_)
502 u = util.url(url_)
502 if u.scheme:
503 if u.scheme:
503 u.scheme = u.scheme.lower()
504 u.scheme = u.scheme.lower()
504 url_, authinfo = u.authinfo()
505 url_, authinfo = u.authinfo()
505 else:
506 else:
506 path = util.normpath(os.path.abspath(url_))
507 path = util.normpath(os.path.abspath(url_))
507 url_ = 'file://' + urllib.pathname2url(path)
508 url_ = 'file://' + urllib.pathname2url(path)
508 authinfo = None
509 authinfo = None
509 return opener(ui, authinfo).open(url_, data)
510 return opener(ui, authinfo).open(url_, data)
General Comments 0
You need to be logged in to leave comments. Login now