##// END OF EJS Templates
ssl: rename ssl_wrap_socket() to conform to our naming convention...
Yuya Nishihara -
r25429:9d1c6171 default
parent child Browse files
Show More
@@ -1,283 +1,283 b''
1 # httpconnection.py - urllib2 handler for new http support
1 # httpconnection.py - urllib2 handler for new http support
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 # Copyright 2011 Google, Inc.
6 # Copyright 2011 Google, Inc.
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10 import logging
10 import logging
11 import socket
11 import socket
12 import urllib
12 import urllib
13 import urllib2
13 import urllib2
14 import os
14 import os
15
15
16 from mercurial import httpclient
16 from mercurial import httpclient
17 from mercurial import sslutil
17 from mercurial import sslutil
18 from mercurial import util
18 from mercurial import util
19 from mercurial.i18n import _
19 from mercurial.i18n import _
20
20
21 # moved here from url.py to avoid a cycle
21 # moved here from url.py to avoid a cycle
22 class httpsendfile(object):
22 class httpsendfile(object):
23 """This is a wrapper around the objects returned by python's "open".
23 """This is a wrapper around the objects returned by python's "open".
24
24
25 Its purpose is to send file-like objects via HTTP.
25 Its purpose is to send file-like objects via HTTP.
26 It do however not define a __len__ attribute because the length
26 It do however not define a __len__ attribute because the length
27 might be more than Py_ssize_t can handle.
27 might be more than Py_ssize_t can handle.
28 """
28 """
29
29
30 def __init__(self, ui, *args, **kwargs):
30 def __init__(self, ui, *args, **kwargs):
31 # We can't just "self._data = open(*args, **kwargs)" here because there
31 # We can't just "self._data = open(*args, **kwargs)" here because there
32 # is an "open" function defined in this module that shadows the global
32 # is an "open" function defined in this module that shadows the global
33 # one
33 # one
34 self.ui = ui
34 self.ui = ui
35 self._data = open(*args, **kwargs)
35 self._data = open(*args, **kwargs)
36 self.seek = self._data.seek
36 self.seek = self._data.seek
37 self.close = self._data.close
37 self.close = self._data.close
38 self.write = self._data.write
38 self.write = self._data.write
39 self.length = os.fstat(self._data.fileno()).st_size
39 self.length = os.fstat(self._data.fileno()).st_size
40 self._pos = 0
40 self._pos = 0
41 self._total = self.length // 1024 * 2
41 self._total = self.length // 1024 * 2
42
42
43 def read(self, *args, **kwargs):
43 def read(self, *args, **kwargs):
44 try:
44 try:
45 ret = self._data.read(*args, **kwargs)
45 ret = self._data.read(*args, **kwargs)
46 except EOFError:
46 except EOFError:
47 self.ui.progress(_('sending'), None)
47 self.ui.progress(_('sending'), None)
48 self._pos += len(ret)
48 self._pos += len(ret)
49 # We pass double the max for total because we currently have
49 # We pass double the max for total because we currently have
50 # to send the bundle twice in the case of a server that
50 # to send the bundle twice in the case of a server that
51 # requires authentication. Since we can't know until we try
51 # requires authentication. Since we can't know until we try
52 # once whether authentication will be required, just lie to
52 # once whether authentication will be required, just lie to
53 # the user and maybe the push succeeds suddenly at 50%.
53 # the user and maybe the push succeeds suddenly at 50%.
54 self.ui.progress(_('sending'), self._pos // 1024,
54 self.ui.progress(_('sending'), self._pos // 1024,
55 unit=_('kb'), total=self._total)
55 unit=_('kb'), total=self._total)
56 return ret
56 return ret
57
57
58 # moved here from url.py to avoid a cycle
58 # moved here from url.py to avoid a cycle
59 def readauthforuri(ui, uri, user):
59 def readauthforuri(ui, uri, user):
60 # Read configuration
60 # Read configuration
61 config = dict()
61 config = dict()
62 for key, val in ui.configitems('auth'):
62 for key, val in ui.configitems('auth'):
63 if '.' not in key:
63 if '.' not in key:
64 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
64 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
65 continue
65 continue
66 group, setting = key.rsplit('.', 1)
66 group, setting = key.rsplit('.', 1)
67 gdict = config.setdefault(group, dict())
67 gdict = config.setdefault(group, dict())
68 if setting in ('username', 'cert', 'key'):
68 if setting in ('username', 'cert', 'key'):
69 val = util.expandpath(val)
69 val = util.expandpath(val)
70 gdict[setting] = val
70 gdict[setting] = val
71
71
72 # Find the best match
72 # Find the best match
73 scheme, hostpath = uri.split('://', 1)
73 scheme, hostpath = uri.split('://', 1)
74 bestuser = None
74 bestuser = None
75 bestlen = 0
75 bestlen = 0
76 bestauth = None
76 bestauth = None
77 for group, auth in config.iteritems():
77 for group, auth in config.iteritems():
78 if user and user != auth.get('username', user):
78 if user and user != auth.get('username', user):
79 # If a username was set in the URI, the entry username
79 # If a username was set in the URI, the entry username
80 # must either match it or be unset
80 # must either match it or be unset
81 continue
81 continue
82 prefix = auth.get('prefix')
82 prefix = auth.get('prefix')
83 if not prefix:
83 if not prefix:
84 continue
84 continue
85 p = prefix.split('://', 1)
85 p = prefix.split('://', 1)
86 if len(p) > 1:
86 if len(p) > 1:
87 schemes, prefix = [p[0]], p[1]
87 schemes, prefix = [p[0]], p[1]
88 else:
88 else:
89 schemes = (auth.get('schemes') or 'https').split()
89 schemes = (auth.get('schemes') or 'https').split()
90 if (prefix == '*' or hostpath.startswith(prefix)) and \
90 if (prefix == '*' or hostpath.startswith(prefix)) and \
91 (len(prefix) > bestlen or (len(prefix) == bestlen and \
91 (len(prefix) > bestlen or (len(prefix) == bestlen and \
92 not bestuser and 'username' in auth)) \
92 not bestuser and 'username' in auth)) \
93 and scheme in schemes:
93 and scheme in schemes:
94 bestlen = len(prefix)
94 bestlen = len(prefix)
95 bestauth = group, auth
95 bestauth = group, auth
96 bestuser = auth.get('username')
96 bestuser = auth.get('username')
97 if user and not bestuser:
97 if user and not bestuser:
98 auth['username'] = user
98 auth['username'] = user
99 return bestauth
99 return bestauth
100
100
101 # Mercurial (at least until we can remove the old codepath) requires
101 # Mercurial (at least until we can remove the old codepath) requires
102 # that the http response object be sufficiently file-like, so we
102 # that the http response object be sufficiently file-like, so we
103 # provide a close() method here.
103 # provide a close() method here.
104 class HTTPResponse(httpclient.HTTPResponse):
104 class HTTPResponse(httpclient.HTTPResponse):
105 def close(self):
105 def close(self):
106 pass
106 pass
107
107
108 class HTTPConnection(httpclient.HTTPConnection):
108 class HTTPConnection(httpclient.HTTPConnection):
109 response_class = HTTPResponse
109 response_class = HTTPResponse
110 def request(self, method, uri, body=None, headers={}):
110 def request(self, method, uri, body=None, headers={}):
111 if isinstance(body, httpsendfile):
111 if isinstance(body, httpsendfile):
112 body.seek(0)
112 body.seek(0)
113 httpclient.HTTPConnection.request(self, method, uri, body=body,
113 httpclient.HTTPConnection.request(self, method, uri, body=body,
114 headers=headers)
114 headers=headers)
115
115
116
116
117 _configuredlogging = False
117 _configuredlogging = False
118 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
118 LOGFMT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'
119 # Subclass BOTH of these because otherwise urllib2 "helpfully"
119 # Subclass BOTH of these because otherwise urllib2 "helpfully"
120 # reinserts them since it notices we don't include any subclasses of
120 # reinserts them since it notices we don't include any subclasses of
121 # them.
121 # them.
122 class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
122 class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler):
123 def __init__(self, ui, pwmgr):
123 def __init__(self, ui, pwmgr):
124 global _configuredlogging
124 global _configuredlogging
125 urllib2.AbstractHTTPHandler.__init__(self)
125 urllib2.AbstractHTTPHandler.__init__(self)
126 self.ui = ui
126 self.ui = ui
127 self.pwmgr = pwmgr
127 self.pwmgr = pwmgr
128 self._connections = {}
128 self._connections = {}
129 loglevel = ui.config('ui', 'http2debuglevel', default=None)
129 loglevel = ui.config('ui', 'http2debuglevel', default=None)
130 if loglevel and not _configuredlogging:
130 if loglevel and not _configuredlogging:
131 _configuredlogging = True
131 _configuredlogging = True
132 logger = logging.getLogger('mercurial.httpclient')
132 logger = logging.getLogger('mercurial.httpclient')
133 logger.setLevel(getattr(logging, loglevel.upper()))
133 logger.setLevel(getattr(logging, loglevel.upper()))
134 handler = logging.StreamHandler()
134 handler = logging.StreamHandler()
135 handler.setFormatter(logging.Formatter(LOGFMT))
135 handler.setFormatter(logging.Formatter(LOGFMT))
136 logger.addHandler(handler)
136 logger.addHandler(handler)
137
137
138 def close_all(self):
138 def close_all(self):
139 """Close and remove all connection objects being kept for reuse."""
139 """Close and remove all connection objects being kept for reuse."""
140 for openconns in self._connections.values():
140 for openconns in self._connections.values():
141 for conn in openconns:
141 for conn in openconns:
142 conn.close()
142 conn.close()
143 self._connections = {}
143 self._connections = {}
144
144
145 # shamelessly borrowed from urllib2.AbstractHTTPHandler
145 # shamelessly borrowed from urllib2.AbstractHTTPHandler
146 def do_open(self, http_class, req, use_ssl):
146 def do_open(self, http_class, req, use_ssl):
147 """Return an addinfourl object for the request, using http_class.
147 """Return an addinfourl object for the request, using http_class.
148
148
149 http_class must implement the HTTPConnection API from httplib.
149 http_class must implement the HTTPConnection API from httplib.
150 The addinfourl return value is a file-like object. It also
150 The addinfourl return value is a file-like object. It also
151 has methods and attributes including:
151 has methods and attributes including:
152 - info(): return a mimetools.Message object for the headers
152 - info(): return a mimetools.Message object for the headers
153 - geturl(): return the original request URL
153 - geturl(): return the original request URL
154 - code: HTTP status code
154 - code: HTTP status code
155 """
155 """
156 # If using a proxy, the host returned by get_host() is
156 # If using a proxy, the host returned by get_host() is
157 # actually the proxy. On Python 2.6.1, the real destination
157 # actually the proxy. On Python 2.6.1, the real destination
158 # hostname is encoded in the URI in the urllib2 request
158 # hostname is encoded in the URI in the urllib2 request
159 # object. On Python 2.6.5, it's stored in the _tunnel_host
159 # object. On Python 2.6.5, it's stored in the _tunnel_host
160 # attribute which has no accessor.
160 # attribute which has no accessor.
161 tunhost = getattr(req, '_tunnel_host', None)
161 tunhost = getattr(req, '_tunnel_host', None)
162 host = req.get_host()
162 host = req.get_host()
163 if tunhost:
163 if tunhost:
164 proxyhost = host
164 proxyhost = host
165 host = tunhost
165 host = tunhost
166 elif req.has_proxy():
166 elif req.has_proxy():
167 proxyhost = req.get_host()
167 proxyhost = req.get_host()
168 host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
168 host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
169 else:
169 else:
170 proxyhost = None
170 proxyhost = None
171
171
172 if proxyhost:
172 if proxyhost:
173 if ':' in proxyhost:
173 if ':' in proxyhost:
174 # Note: this means we'll explode if we try and use an
174 # Note: this means we'll explode if we try and use an
175 # IPv6 http proxy. This isn't a regression, so we
175 # IPv6 http proxy. This isn't a regression, so we
176 # won't worry about it for now.
176 # won't worry about it for now.
177 proxyhost, proxyport = proxyhost.rsplit(':', 1)
177 proxyhost, proxyport = proxyhost.rsplit(':', 1)
178 else:
178 else:
179 proxyport = 3128 # squid default
179 proxyport = 3128 # squid default
180 proxy = (proxyhost, proxyport)
180 proxy = (proxyhost, proxyport)
181 else:
181 else:
182 proxy = None
182 proxy = None
183
183
184 if not host:
184 if not host:
185 raise urllib2.URLError('no host given')
185 raise urllib2.URLError('no host given')
186
186
187 connkey = use_ssl, host, proxy
187 connkey = use_ssl, host, proxy
188 allconns = self._connections.get(connkey, [])
188 allconns = self._connections.get(connkey, [])
189 conns = [c for c in allconns if not c.busy()]
189 conns = [c for c in allconns if not c.busy()]
190 if conns:
190 if conns:
191 h = conns[0]
191 h = conns[0]
192 else:
192 else:
193 if allconns:
193 if allconns:
194 self.ui.debug('all connections for %s busy, making a new '
194 self.ui.debug('all connections for %s busy, making a new '
195 'one\n' % host)
195 'one\n' % host)
196 timeout = None
196 timeout = None
197 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
197 if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT:
198 timeout = req.timeout
198 timeout = req.timeout
199 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
199 h = http_class(host, timeout=timeout, proxy_hostport=proxy)
200 self._connections.setdefault(connkey, []).append(h)
200 self._connections.setdefault(connkey, []).append(h)
201
201
202 headers = dict(req.headers)
202 headers = dict(req.headers)
203 headers.update(req.unredirected_hdrs)
203 headers.update(req.unredirected_hdrs)
204 headers = dict(
204 headers = dict(
205 (name.title(), val) for name, val in headers.items())
205 (name.title(), val) for name, val in headers.items())
206 try:
206 try:
207 path = req.get_selector()
207 path = req.get_selector()
208 if '://' in path:
208 if '://' in path:
209 path = path.split('://', 1)[1].split('/', 1)[1]
209 path = path.split('://', 1)[1].split('/', 1)[1]
210 if path[0] != '/':
210 if path[0] != '/':
211 path = '/' + path
211 path = '/' + path
212 h.request(req.get_method(), path, req.data, headers)
212 h.request(req.get_method(), path, req.data, headers)
213 r = h.getresponse()
213 r = h.getresponse()
214 except socket.error, err: # XXX what error?
214 except socket.error, err: # XXX what error?
215 raise urllib2.URLError(err)
215 raise urllib2.URLError(err)
216
216
217 # Pick apart the HTTPResponse object to get the addinfourl
217 # Pick apart the HTTPResponse object to get the addinfourl
218 # object initialized properly.
218 # object initialized properly.
219 r.recv = r.read
219 r.recv = r.read
220
220
221 resp = urllib.addinfourl(r, r.headers, req.get_full_url())
221 resp = urllib.addinfourl(r, r.headers, req.get_full_url())
222 resp.code = r.status
222 resp.code = r.status
223 resp.msg = r.reason
223 resp.msg = r.reason
224 return resp
224 return resp
225
225
226 # httplib always uses the given host/port as the socket connect
226 # httplib always uses the given host/port as the socket connect
227 # target, and then allows full URIs in the request path, which it
227 # target, and then allows full URIs in the request path, which it
228 # then observes and treats as a signal to do proxying instead.
228 # then observes and treats as a signal to do proxying instead.
229 def http_open(self, req):
229 def http_open(self, req):
230 if req.get_full_url().startswith('https'):
230 if req.get_full_url().startswith('https'):
231 return self.https_open(req)
231 return self.https_open(req)
232 def makehttpcon(*args, **kwargs):
232 def makehttpcon(*args, **kwargs):
233 k2 = dict(kwargs)
233 k2 = dict(kwargs)
234 k2['use_ssl'] = False
234 k2['use_ssl'] = False
235 return HTTPConnection(*args, **k2)
235 return HTTPConnection(*args, **k2)
236 return self.do_open(makehttpcon, req, False)
236 return self.do_open(makehttpcon, req, False)
237
237
238 def https_open(self, req):
238 def https_open(self, req):
239 # req.get_full_url() does not contain credentials and we may
239 # req.get_full_url() does not contain credentials and we may
240 # need them to match the certificates.
240 # need them to match the certificates.
241 url = req.get_full_url()
241 url = req.get_full_url()
242 user, password = self.pwmgr.find_stored_password(url)
242 user, password = self.pwmgr.find_stored_password(url)
243 res = readauthforuri(self.ui, url, user)
243 res = readauthforuri(self.ui, url, user)
244 if res:
244 if res:
245 group, auth = res
245 group, auth = res
246 self.auth = auth
246 self.auth = auth
247 self.ui.debug("using auth.%s.* for authentication\n" % group)
247 self.ui.debug("using auth.%s.* for authentication\n" % group)
248 else:
248 else:
249 self.auth = None
249 self.auth = None
250 return self.do_open(self._makesslconnection, req, True)
250 return self.do_open(self._makesslconnection, req, True)
251
251
252 def _makesslconnection(self, host, port=443, *args, **kwargs):
252 def _makesslconnection(self, host, port=443, *args, **kwargs):
253 keyfile = None
253 keyfile = None
254 certfile = None
254 certfile = None
255
255
256 if args: # key_file
256 if args: # key_file
257 keyfile = args.pop(0)
257 keyfile = args.pop(0)
258 if args: # cert_file
258 if args: # cert_file
259 certfile = args.pop(0)
259 certfile = args.pop(0)
260
260
261 # if the user has specified different key/cert files in
261 # if the user has specified different key/cert files in
262 # hgrc, we prefer these
262 # hgrc, we prefer these
263 if self.auth and 'key' in self.auth and 'cert' in self.auth:
263 if self.auth and 'key' in self.auth and 'cert' in self.auth:
264 keyfile = self.auth['key']
264 keyfile = self.auth['key']
265 certfile = self.auth['cert']
265 certfile = self.auth['cert']
266
266
267 # let host port take precedence
267 # let host port take precedence
268 if ':' in host and '[' not in host or ']:' in host:
268 if ':' in host and '[' not in host or ']:' in host:
269 host, port = host.rsplit(':', 1)
269 host, port = host.rsplit(':', 1)
270 port = int(port)
270 port = int(port)
271 if '[' in host:
271 if '[' in host:
272 host = host[1:-1]
272 host = host[1:-1]
273
273
274 kwargs['keyfile'] = keyfile
274 kwargs['keyfile'] = keyfile
275 kwargs['certfile'] = certfile
275 kwargs['certfile'] = certfile
276
276
277 kwargs.update(sslutil.sslkwargs(self.ui, host))
277 kwargs.update(sslutil.sslkwargs(self.ui, host))
278
278
279 con = HTTPConnection(host, port, use_ssl=True,
279 con = HTTPConnection(host, port, use_ssl=True,
280 ssl_wrap_socket=sslutil.ssl_wrap_socket,
280 ssl_wrap_socket=sslutil.wrapsocket,
281 ssl_validator=sslutil.validator(self.ui, host),
281 ssl_validator=sslutil.validator(self.ui, host),
282 **kwargs)
282 **kwargs)
283 return con
283 return con
@@ -1,327 +1,327 b''
1 # mail.py - mail sending bits for mercurial
1 # mail.py - mail sending bits for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import util, encoding, sslutil
9 import util, encoding, sslutil
10 import os, smtplib, socket, quopri, time, sys
10 import os, smtplib, socket, quopri, time, sys
11 import email
11 import email
12
12
13 _oldheaderinit = email.Header.Header.__init__
13 _oldheaderinit = email.Header.Header.__init__
14 def _unifiedheaderinit(self, *args, **kw):
14 def _unifiedheaderinit(self, *args, **kw):
15 """
15 """
16 Python 2.7 introduces a backwards incompatible change
16 Python 2.7 introduces a backwards incompatible change
17 (Python issue1974, r70772) in email.Generator.Generator code:
17 (Python issue1974, r70772) in email.Generator.Generator code:
18 pre-2.7 code passed "continuation_ws='\t'" to the Header
18 pre-2.7 code passed "continuation_ws='\t'" to the Header
19 constructor, and 2.7 removed this parameter.
19 constructor, and 2.7 removed this parameter.
20
20
21 Default argument is continuation_ws=' ', which means that the
21 Default argument is continuation_ws=' ', which means that the
22 behaviour is different in <2.7 and 2.7
22 behaviour is different in <2.7 and 2.7
23
23
24 We consider the 2.7 behaviour to be preferable, but need
24 We consider the 2.7 behaviour to be preferable, but need
25 to have an unified behaviour for versions 2.4 to 2.7
25 to have an unified behaviour for versions 2.4 to 2.7
26 """
26 """
27 # override continuation_ws
27 # override continuation_ws
28 kw['continuation_ws'] = ' '
28 kw['continuation_ws'] = ' '
29 _oldheaderinit(self, *args, **kw)
29 _oldheaderinit(self, *args, **kw)
30
30
31 email.Header.Header.__dict__['__init__'] = _unifiedheaderinit
31 email.Header.Header.__dict__['__init__'] = _unifiedheaderinit
32
32
33 class STARTTLS(smtplib.SMTP):
33 class STARTTLS(smtplib.SMTP):
34 '''Derived class to verify the peer certificate for STARTTLS.
34 '''Derived class to verify the peer certificate for STARTTLS.
35
35
36 This class allows to pass any keyword arguments to SSL socket creation.
36 This class allows to pass any keyword arguments to SSL socket creation.
37 '''
37 '''
38 def __init__(self, sslkwargs, **kwargs):
38 def __init__(self, sslkwargs, **kwargs):
39 smtplib.SMTP.__init__(self, **kwargs)
39 smtplib.SMTP.__init__(self, **kwargs)
40 self._sslkwargs = sslkwargs
40 self._sslkwargs = sslkwargs
41
41
42 def starttls(self, keyfile=None, certfile=None):
42 def starttls(self, keyfile=None, certfile=None):
43 if not self.has_extn("starttls"):
43 if not self.has_extn("starttls"):
44 msg = "STARTTLS extension not supported by server"
44 msg = "STARTTLS extension not supported by server"
45 raise smtplib.SMTPException(msg)
45 raise smtplib.SMTPException(msg)
46 (resp, reply) = self.docmd("STARTTLS")
46 (resp, reply) = self.docmd("STARTTLS")
47 if resp == 220:
47 if resp == 220:
48 self.sock = sslutil.ssl_wrap_socket(self.sock, keyfile, certfile,
48 self.sock = sslutil.wrapsocket(self.sock, keyfile, certfile,
49 **self._sslkwargs)
49 **self._sslkwargs)
50 if not util.safehasattr(self.sock, "read"):
50 if not util.safehasattr(self.sock, "read"):
51 # using httplib.FakeSocket with Python 2.5.x or earlier
51 # using httplib.FakeSocket with Python 2.5.x or earlier
52 self.sock.read = self.sock.recv
52 self.sock.read = self.sock.recv
53 self.file = smtplib.SSLFakeFile(self.sock)
53 self.file = smtplib.SSLFakeFile(self.sock)
54 self.helo_resp = None
54 self.helo_resp = None
55 self.ehlo_resp = None
55 self.ehlo_resp = None
56 self.esmtp_features = {}
56 self.esmtp_features = {}
57 self.does_esmtp = 0
57 self.does_esmtp = 0
58 return (resp, reply)
58 return (resp, reply)
59
59
60 if util.safehasattr(smtplib.SMTP, '_get_socket'):
60 if util.safehasattr(smtplib.SMTP, '_get_socket'):
61 class SMTPS(smtplib.SMTP):
61 class SMTPS(smtplib.SMTP):
62 '''Derived class to verify the peer certificate for SMTPS.
62 '''Derived class to verify the peer certificate for SMTPS.
63
63
64 This class allows to pass any keyword arguments to SSL socket creation.
64 This class allows to pass any keyword arguments to SSL socket creation.
65 '''
65 '''
66 def __init__(self, sslkwargs, keyfile=None, certfile=None, **kwargs):
66 def __init__(self, sslkwargs, keyfile=None, certfile=None, **kwargs):
67 self.keyfile = keyfile
67 self.keyfile = keyfile
68 self.certfile = certfile
68 self.certfile = certfile
69 smtplib.SMTP.__init__(self, **kwargs)
69 smtplib.SMTP.__init__(self, **kwargs)
70 self.default_port = smtplib.SMTP_SSL_PORT
70 self.default_port = smtplib.SMTP_SSL_PORT
71 self._sslkwargs = sslkwargs
71 self._sslkwargs = sslkwargs
72
72
73 def _get_socket(self, host, port, timeout):
73 def _get_socket(self, host, port, timeout):
74 if self.debuglevel > 0:
74 if self.debuglevel > 0:
75 print >> sys.stderr, 'connect:', (host, port)
75 print >> sys.stderr, 'connect:', (host, port)
76 new_socket = socket.create_connection((host, port), timeout)
76 new_socket = socket.create_connection((host, port), timeout)
77 new_socket = sslutil.ssl_wrap_socket(new_socket,
77 new_socket = sslutil.wrapsocket(new_socket,
78 self.keyfile, self.certfile,
78 self.keyfile, self.certfile,
79 **self._sslkwargs)
79 **self._sslkwargs)
80 self.file = smtplib.SSLFakeFile(new_socket)
80 self.file = smtplib.SSLFakeFile(new_socket)
81 return new_socket
81 return new_socket
82 else:
82 else:
83 def SMTPS(sslkwargs, keyfile=None, certfile=None, **kwargs):
83 def SMTPS(sslkwargs, keyfile=None, certfile=None, **kwargs):
84 raise util.Abort(_('SMTPS requires Python 2.6 or later'))
84 raise util.Abort(_('SMTPS requires Python 2.6 or later'))
85
85
86 def _smtp(ui):
86 def _smtp(ui):
87 '''build an smtp connection and return a function to send mail'''
87 '''build an smtp connection and return a function to send mail'''
88 local_hostname = ui.config('smtp', 'local_hostname')
88 local_hostname = ui.config('smtp', 'local_hostname')
89 tls = ui.config('smtp', 'tls', 'none')
89 tls = ui.config('smtp', 'tls', 'none')
90 # backward compatible: when tls = true, we use starttls.
90 # backward compatible: when tls = true, we use starttls.
91 starttls = tls == 'starttls' or util.parsebool(tls)
91 starttls = tls == 'starttls' or util.parsebool(tls)
92 smtps = tls == 'smtps'
92 smtps = tls == 'smtps'
93 if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
93 if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
94 raise util.Abort(_("can't use TLS: Python SSL support not installed"))
94 raise util.Abort(_("can't use TLS: Python SSL support not installed"))
95 mailhost = ui.config('smtp', 'host')
95 mailhost = ui.config('smtp', 'host')
96 if not mailhost:
96 if not mailhost:
97 raise util.Abort(_('smtp.host not configured - cannot send mail'))
97 raise util.Abort(_('smtp.host not configured - cannot send mail'))
98 verifycert = ui.config('smtp', 'verifycert', 'strict')
98 verifycert = ui.config('smtp', 'verifycert', 'strict')
99 if verifycert not in ['strict', 'loose']:
99 if verifycert not in ['strict', 'loose']:
100 if util.parsebool(verifycert) is not False:
100 if util.parsebool(verifycert) is not False:
101 raise util.Abort(_('invalid smtp.verifycert configuration: %s')
101 raise util.Abort(_('invalid smtp.verifycert configuration: %s')
102 % (verifycert))
102 % (verifycert))
103 verifycert = False
103 verifycert = False
104 if (starttls or smtps) and verifycert:
104 if (starttls or smtps) and verifycert:
105 sslkwargs = sslutil.sslkwargs(ui, mailhost)
105 sslkwargs = sslutil.sslkwargs(ui, mailhost)
106 else:
106 else:
107 sslkwargs = {}
107 sslkwargs = {}
108 if smtps:
108 if smtps:
109 ui.note(_('(using smtps)\n'))
109 ui.note(_('(using smtps)\n'))
110 s = SMTPS(sslkwargs, local_hostname=local_hostname)
110 s = SMTPS(sslkwargs, local_hostname=local_hostname)
111 elif starttls:
111 elif starttls:
112 s = STARTTLS(sslkwargs, local_hostname=local_hostname)
112 s = STARTTLS(sslkwargs, local_hostname=local_hostname)
113 else:
113 else:
114 s = smtplib.SMTP(local_hostname=local_hostname)
114 s = smtplib.SMTP(local_hostname=local_hostname)
115 if smtps:
115 if smtps:
116 defaultport = 465
116 defaultport = 465
117 else:
117 else:
118 defaultport = 25
118 defaultport = 25
119 mailport = util.getport(ui.config('smtp', 'port', defaultport))
119 mailport = util.getport(ui.config('smtp', 'port', defaultport))
120 ui.note(_('sending mail: smtp host %s, port %s\n') %
120 ui.note(_('sending mail: smtp host %s, port %s\n') %
121 (mailhost, mailport))
121 (mailhost, mailport))
122 s.connect(host=mailhost, port=mailport)
122 s.connect(host=mailhost, port=mailport)
123 if starttls:
123 if starttls:
124 ui.note(_('(using starttls)\n'))
124 ui.note(_('(using starttls)\n'))
125 s.ehlo()
125 s.ehlo()
126 s.starttls()
126 s.starttls()
127 s.ehlo()
127 s.ehlo()
128 if (starttls or smtps) and verifycert:
128 if (starttls or smtps) and verifycert:
129 ui.note(_('(verifying remote certificate)\n'))
129 ui.note(_('(verifying remote certificate)\n'))
130 sslutil.validator(ui, mailhost)(s.sock, verifycert == 'strict')
130 sslutil.validator(ui, mailhost)(s.sock, verifycert == 'strict')
131 username = ui.config('smtp', 'username')
131 username = ui.config('smtp', 'username')
132 password = ui.config('smtp', 'password')
132 password = ui.config('smtp', 'password')
133 if username and not password:
133 if username and not password:
134 password = ui.getpass()
134 password = ui.getpass()
135 if username and password:
135 if username and password:
136 ui.note(_('(authenticating to mail server as %s)\n') %
136 ui.note(_('(authenticating to mail server as %s)\n') %
137 (username))
137 (username))
138 try:
138 try:
139 s.login(username, password)
139 s.login(username, password)
140 except smtplib.SMTPException, inst:
140 except smtplib.SMTPException, inst:
141 raise util.Abort(inst)
141 raise util.Abort(inst)
142
142
143 def send(sender, recipients, msg):
143 def send(sender, recipients, msg):
144 try:
144 try:
145 return s.sendmail(sender, recipients, msg)
145 return s.sendmail(sender, recipients, msg)
146 except smtplib.SMTPRecipientsRefused, inst:
146 except smtplib.SMTPRecipientsRefused, inst:
147 recipients = [r[1] for r in inst.recipients.values()]
147 recipients = [r[1] for r in inst.recipients.values()]
148 raise util.Abort('\n' + '\n'.join(recipients))
148 raise util.Abort('\n' + '\n'.join(recipients))
149 except smtplib.SMTPException, inst:
149 except smtplib.SMTPException, inst:
150 raise util.Abort(inst)
150 raise util.Abort(inst)
151
151
152 return send
152 return send
153
153
154 def _sendmail(ui, sender, recipients, msg):
154 def _sendmail(ui, sender, recipients, msg):
155 '''send mail using sendmail.'''
155 '''send mail using sendmail.'''
156 program = ui.config('email', 'method')
156 program = ui.config('email', 'method')
157 cmdline = '%s -f %s %s' % (program, util.email(sender),
157 cmdline = '%s -f %s %s' % (program, util.email(sender),
158 ' '.join(map(util.email, recipients)))
158 ' '.join(map(util.email, recipients)))
159 ui.note(_('sending mail: %s\n') % cmdline)
159 ui.note(_('sending mail: %s\n') % cmdline)
160 fp = util.popen(cmdline, 'w')
160 fp = util.popen(cmdline, 'w')
161 fp.write(msg)
161 fp.write(msg)
162 ret = fp.close()
162 ret = fp.close()
163 if ret:
163 if ret:
164 raise util.Abort('%s %s' % (
164 raise util.Abort('%s %s' % (
165 os.path.basename(program.split(None, 1)[0]),
165 os.path.basename(program.split(None, 1)[0]),
166 util.explainexit(ret)[0]))
166 util.explainexit(ret)[0]))
167
167
168 def _mbox(mbox, sender, recipients, msg):
168 def _mbox(mbox, sender, recipients, msg):
169 '''write mails to mbox'''
169 '''write mails to mbox'''
170 fp = open(mbox, 'ab+')
170 fp = open(mbox, 'ab+')
171 # Should be time.asctime(), but Windows prints 2-characters day
171 # Should be time.asctime(), but Windows prints 2-characters day
172 # of month instead of one. Make them print the same thing.
172 # of month instead of one. Make them print the same thing.
173 date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime())
173 date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime())
174 fp.write('From %s %s\n' % (sender, date))
174 fp.write('From %s %s\n' % (sender, date))
175 fp.write(msg)
175 fp.write(msg)
176 fp.write('\n\n')
176 fp.write('\n\n')
177 fp.close()
177 fp.close()
178
178
179 def connect(ui, mbox=None):
179 def connect(ui, mbox=None):
180 '''make a mail connection. return a function to send mail.
180 '''make a mail connection. return a function to send mail.
181 call as sendmail(sender, list-of-recipients, msg).'''
181 call as sendmail(sender, list-of-recipients, msg).'''
182 if mbox:
182 if mbox:
183 open(mbox, 'wb').close()
183 open(mbox, 'wb').close()
184 return lambda s, r, m: _mbox(mbox, s, r, m)
184 return lambda s, r, m: _mbox(mbox, s, r, m)
185 if ui.config('email', 'method', 'smtp') == 'smtp':
185 if ui.config('email', 'method', 'smtp') == 'smtp':
186 return _smtp(ui)
186 return _smtp(ui)
187 return lambda s, r, m: _sendmail(ui, s, r, m)
187 return lambda s, r, m: _sendmail(ui, s, r, m)
188
188
189 def sendmail(ui, sender, recipients, msg, mbox=None):
189 def sendmail(ui, sender, recipients, msg, mbox=None):
190 send = connect(ui, mbox=mbox)
190 send = connect(ui, mbox=mbox)
191 return send(sender, recipients, msg)
191 return send(sender, recipients, msg)
192
192
193 def validateconfig(ui):
193 def validateconfig(ui):
194 '''determine if we have enough config data to try sending email.'''
194 '''determine if we have enough config data to try sending email.'''
195 method = ui.config('email', 'method', 'smtp')
195 method = ui.config('email', 'method', 'smtp')
196 if method == 'smtp':
196 if method == 'smtp':
197 if not ui.config('smtp', 'host'):
197 if not ui.config('smtp', 'host'):
198 raise util.Abort(_('smtp specified as email transport, '
198 raise util.Abort(_('smtp specified as email transport, '
199 'but no smtp host configured'))
199 'but no smtp host configured'))
200 else:
200 else:
201 if not util.findexe(method):
201 if not util.findexe(method):
202 raise util.Abort(_('%r specified as email transport, '
202 raise util.Abort(_('%r specified as email transport, '
203 'but not in PATH') % method)
203 'but not in PATH') % method)
204
204
205 def mimetextpatch(s, subtype='plain', display=False):
205 def mimetextpatch(s, subtype='plain', display=False):
206 '''Return MIME message suitable for a patch.
206 '''Return MIME message suitable for a patch.
207 Charset will be detected as utf-8 or (possibly fake) us-ascii.
207 Charset will be detected as utf-8 or (possibly fake) us-ascii.
208 Transfer encodings will be used if necessary.'''
208 Transfer encodings will be used if necessary.'''
209
209
210 cs = 'us-ascii'
210 cs = 'us-ascii'
211 if not display:
211 if not display:
212 try:
212 try:
213 s.decode('us-ascii')
213 s.decode('us-ascii')
214 except UnicodeDecodeError:
214 except UnicodeDecodeError:
215 try:
215 try:
216 s.decode('utf-8')
216 s.decode('utf-8')
217 cs = 'utf-8'
217 cs = 'utf-8'
218 except UnicodeDecodeError:
218 except UnicodeDecodeError:
219 # We'll go with us-ascii as a fallback.
219 # We'll go with us-ascii as a fallback.
220 pass
220 pass
221
221
222 return mimetextqp(s, subtype, cs)
222 return mimetextqp(s, subtype, cs)
223
223
224 def mimetextqp(body, subtype, charset):
224 def mimetextqp(body, subtype, charset):
225 '''Return MIME message.
225 '''Return MIME message.
226 Quoted-printable transfer encoding will be used if necessary.
226 Quoted-printable transfer encoding will be used if necessary.
227 '''
227 '''
228 enc = None
228 enc = None
229 for line in body.splitlines():
229 for line in body.splitlines():
230 if len(line) > 950:
230 if len(line) > 950:
231 body = quopri.encodestring(body)
231 body = quopri.encodestring(body)
232 enc = "quoted-printable"
232 enc = "quoted-printable"
233 break
233 break
234
234
235 msg = email.MIMEText.MIMEText(body, subtype, charset)
235 msg = email.MIMEText.MIMEText(body, subtype, charset)
236 if enc:
236 if enc:
237 del msg['Content-Transfer-Encoding']
237 del msg['Content-Transfer-Encoding']
238 msg['Content-Transfer-Encoding'] = enc
238 msg['Content-Transfer-Encoding'] = enc
239 return msg
239 return msg
240
240
241 def _charsets(ui):
241 def _charsets(ui):
242 '''Obtains charsets to send mail parts not containing patches.'''
242 '''Obtains charsets to send mail parts not containing patches.'''
243 charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')]
243 charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')]
244 fallbacks = [encoding.fallbackencoding.lower(),
244 fallbacks = [encoding.fallbackencoding.lower(),
245 encoding.encoding.lower(), 'utf-8']
245 encoding.encoding.lower(), 'utf-8']
246 for cs in fallbacks: # find unique charsets while keeping order
246 for cs in fallbacks: # find unique charsets while keeping order
247 if cs not in charsets:
247 if cs not in charsets:
248 charsets.append(cs)
248 charsets.append(cs)
249 return [cs for cs in charsets if not cs.endswith('ascii')]
249 return [cs for cs in charsets if not cs.endswith('ascii')]
250
250
251 def _encode(ui, s, charsets):
251 def _encode(ui, s, charsets):
252 '''Returns (converted) string, charset tuple.
252 '''Returns (converted) string, charset tuple.
253 Finds out best charset by cycling through sendcharsets in descending
253 Finds out best charset by cycling through sendcharsets in descending
254 order. Tries both encoding and fallbackencoding for input. Only as
254 order. Tries both encoding and fallbackencoding for input. Only as
255 last resort send as is in fake ascii.
255 last resort send as is in fake ascii.
256 Caveat: Do not use for mail parts containing patches!'''
256 Caveat: Do not use for mail parts containing patches!'''
257 try:
257 try:
258 s.decode('ascii')
258 s.decode('ascii')
259 except UnicodeDecodeError:
259 except UnicodeDecodeError:
260 sendcharsets = charsets or _charsets(ui)
260 sendcharsets = charsets or _charsets(ui)
261 for ics in (encoding.encoding, encoding.fallbackencoding):
261 for ics in (encoding.encoding, encoding.fallbackencoding):
262 try:
262 try:
263 u = s.decode(ics)
263 u = s.decode(ics)
264 except UnicodeDecodeError:
264 except UnicodeDecodeError:
265 continue
265 continue
266 for ocs in sendcharsets:
266 for ocs in sendcharsets:
267 try:
267 try:
268 return u.encode(ocs), ocs
268 return u.encode(ocs), ocs
269 except UnicodeEncodeError:
269 except UnicodeEncodeError:
270 pass
270 pass
271 except LookupError:
271 except LookupError:
272 ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs)
272 ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs)
273 # if ascii, or all conversion attempts fail, send (broken) ascii
273 # if ascii, or all conversion attempts fail, send (broken) ascii
274 return s, 'us-ascii'
274 return s, 'us-ascii'
275
275
276 def headencode(ui, s, charsets=None, display=False):
276 def headencode(ui, s, charsets=None, display=False):
277 '''Returns RFC-2047 compliant header from given string.'''
277 '''Returns RFC-2047 compliant header from given string.'''
278 if not display:
278 if not display:
279 # split into words?
279 # split into words?
280 s, cs = _encode(ui, s, charsets)
280 s, cs = _encode(ui, s, charsets)
281 return str(email.Header.Header(s, cs))
281 return str(email.Header.Header(s, cs))
282 return s
282 return s
283
283
284 def _addressencode(ui, name, addr, charsets=None):
284 def _addressencode(ui, name, addr, charsets=None):
285 name = headencode(ui, name, charsets)
285 name = headencode(ui, name, charsets)
286 try:
286 try:
287 acc, dom = addr.split('@')
287 acc, dom = addr.split('@')
288 acc = acc.encode('ascii')
288 acc = acc.encode('ascii')
289 dom = dom.decode(encoding.encoding).encode('idna')
289 dom = dom.decode(encoding.encoding).encode('idna')
290 addr = '%s@%s' % (acc, dom)
290 addr = '%s@%s' % (acc, dom)
291 except UnicodeDecodeError:
291 except UnicodeDecodeError:
292 raise util.Abort(_('invalid email address: %s') % addr)
292 raise util.Abort(_('invalid email address: %s') % addr)
293 except ValueError:
293 except ValueError:
294 try:
294 try:
295 # too strict?
295 # too strict?
296 addr = addr.encode('ascii')
296 addr = addr.encode('ascii')
297 except UnicodeDecodeError:
297 except UnicodeDecodeError:
298 raise util.Abort(_('invalid local address: %s') % addr)
298 raise util.Abort(_('invalid local address: %s') % addr)
299 return email.Utils.formataddr((name, addr))
299 return email.Utils.formataddr((name, addr))
300
300
301 def addressencode(ui, address, charsets=None, display=False):
301 def addressencode(ui, address, charsets=None, display=False):
302 '''Turns address into RFC-2047 compliant header.'''
302 '''Turns address into RFC-2047 compliant header.'''
303 if display or not address:
303 if display or not address:
304 return address or ''
304 return address or ''
305 name, addr = email.Utils.parseaddr(address)
305 name, addr = email.Utils.parseaddr(address)
306 return _addressencode(ui, name, addr, charsets)
306 return _addressencode(ui, name, addr, charsets)
307
307
308 def addrlistencode(ui, addrs, charsets=None, display=False):
308 def addrlistencode(ui, addrs, charsets=None, display=False):
309 '''Turns a list of addresses into a list of RFC-2047 compliant headers.
309 '''Turns a list of addresses into a list of RFC-2047 compliant headers.
310 A single element of input list may contain multiple addresses, but output
310 A single element of input list may contain multiple addresses, but output
311 always has one address per item'''
311 always has one address per item'''
312 if display:
312 if display:
313 return [a.strip() for a in addrs if a.strip()]
313 return [a.strip() for a in addrs if a.strip()]
314
314
315 result = []
315 result = []
316 for name, addr in email.Utils.getaddresses(addrs):
316 for name, addr in email.Utils.getaddresses(addrs):
317 if name or addr:
317 if name or addr:
318 result.append(_addressencode(ui, name, addr, charsets))
318 result.append(_addressencode(ui, name, addr, charsets))
319 return result
319 return result
320
320
321 def mimeencode(ui, s, charsets=None, display=False):
321 def mimeencode(ui, s, charsets=None, display=False):
322 '''creates mime text object, encodes it if needed, and sets
322 '''creates mime text object, encodes it if needed, and sets
323 charset and transfer-encoding accordingly.'''
323 charset and transfer-encoding accordingly.'''
324 cs = 'us-ascii'
324 cs = 'us-ascii'
325 if not display:
325 if not display:
326 s, cs = _encode(ui, s, charsets)
326 s, cs = _encode(ui, s, charsets)
327 return mimetextqp(s, 'plain', cs)
327 return mimetextqp(s, 'plain', cs)
@@ -1,237 +1,237 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
13
14 _canloaddefaultcerts = False
14 _canloaddefaultcerts = False
15 try:
15 try:
16 # avoid using deprecated/broken FakeSocket in python 2.6
16 # avoid using deprecated/broken FakeSocket in python 2.6
17 import ssl
17 import ssl
18 CERT_REQUIRED = ssl.CERT_REQUIRED
18 CERT_REQUIRED = ssl.CERT_REQUIRED
19 try:
19 try:
20 ssl_context = ssl.SSLContext
20 ssl_context = ssl.SSLContext
21 _canloaddefaultcerts = util.safehasattr(ssl_context,
21 _canloaddefaultcerts = util.safehasattr(ssl_context,
22 'load_default_certs')
22 'load_default_certs')
23
23
24 def ssl_wrap_socket(sock, keyfile, certfile, ui,
24 def wrapsocket(sock, keyfile, certfile, ui,
25 cert_reqs=ssl.CERT_NONE,
25 cert_reqs=ssl.CERT_NONE,
26 ca_certs=None, serverhostname=None):
26 ca_certs=None, serverhostname=None):
27 # Allow any version of SSL starting with TLSv1 and
27 # Allow any version of SSL starting with TLSv1 and
28 # up. Note that specifying TLSv1 here prohibits use of
28 # up. Note that specifying TLSv1 here prohibits use of
29 # newer standards (like TLSv1_2), so this is the right way
29 # newer standards (like TLSv1_2), so this is the right way
30 # to do this. Note that in the future it'd be better to
30 # to do this. Note that in the future it'd be better to
31 # support using ssl.create_default_context(), which sets
31 # support using ssl.create_default_context(), which sets
32 # up a bunch of things in smart ways (strong ciphers,
32 # up a bunch of things in smart ways (strong ciphers,
33 # protocol versions, etc) and is upgraded by Python
33 # protocol versions, etc) and is upgraded by Python
34 # maintainers for us, but that breaks too many things to
34 # maintainers for us, but that breaks too many things to
35 # do it in a hurry.
35 # do it in a hurry.
36 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
36 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
37 sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
37 sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
38 if certfile is not None:
38 if certfile is not None:
39 def password():
39 def password():
40 f = keyfile or certfile
40 f = keyfile or certfile
41 return ui.getpass(_('passphrase for %s: ') % f, '')
41 return ui.getpass(_('passphrase for %s: ') % f, '')
42 sslcontext.load_cert_chain(certfile, keyfile, password)
42 sslcontext.load_cert_chain(certfile, keyfile, password)
43 sslcontext.verify_mode = cert_reqs
43 sslcontext.verify_mode = cert_reqs
44 if ca_certs is not None:
44 if ca_certs is not None:
45 sslcontext.load_verify_locations(cafile=ca_certs)
45 sslcontext.load_verify_locations(cafile=ca_certs)
46 elif _canloaddefaultcerts:
46 elif _canloaddefaultcerts:
47 sslcontext.load_default_certs()
47 sslcontext.load_default_certs()
48
48
49 sslsocket = sslcontext.wrap_socket(sock,
49 sslsocket = sslcontext.wrap_socket(sock,
50 server_hostname=serverhostname)
50 server_hostname=serverhostname)
51 # check if wrap_socket failed silently because socket had been
51 # check if wrap_socket failed silently because socket had been
52 # closed
52 # closed
53 # - see http://bugs.python.org/issue13721
53 # - see http://bugs.python.org/issue13721
54 if not sslsocket.cipher():
54 if not sslsocket.cipher():
55 raise util.Abort(_('ssl connection failed'))
55 raise util.Abort(_('ssl connection failed'))
56 return sslsocket
56 return sslsocket
57 except AttributeError:
57 except AttributeError:
58 def ssl_wrap_socket(sock, keyfile, certfile, ui,
58 def wrapsocket(sock, keyfile, certfile, ui,
59 cert_reqs=ssl.CERT_NONE,
59 cert_reqs=ssl.CERT_NONE,
60 ca_certs=None, serverhostname=None):
60 ca_certs=None, serverhostname=None):
61 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
61 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
62 cert_reqs=cert_reqs, ca_certs=ca_certs,
62 cert_reqs=cert_reqs, ca_certs=ca_certs,
63 ssl_version=ssl.PROTOCOL_TLSv1)
63 ssl_version=ssl.PROTOCOL_TLSv1)
64 # check if wrap_socket failed silently because socket had been
64 # check if wrap_socket failed silently because socket had been
65 # closed
65 # closed
66 # - see http://bugs.python.org/issue13721
66 # - see http://bugs.python.org/issue13721
67 if not sslsocket.cipher():
67 if not sslsocket.cipher():
68 raise util.Abort(_('ssl connection failed'))
68 raise util.Abort(_('ssl connection failed'))
69 return sslsocket
69 return sslsocket
70 except ImportError:
70 except ImportError:
71 CERT_REQUIRED = 2
71 CERT_REQUIRED = 2
72
72
73 import socket, httplib
73 import socket, httplib
74
74
75 def ssl_wrap_socket(sock, keyfile, certfile, ui,
75 def wrapsocket(sock, keyfile, certfile, ui,
76 cert_reqs=CERT_REQUIRED,
76 cert_reqs=CERT_REQUIRED,
77 ca_certs=None, serverhostname=None):
77 ca_certs=None, serverhostname=None):
78 if not util.safehasattr(socket, 'ssl'):
78 if not util.safehasattr(socket, 'ssl'):
79 raise util.Abort(_('Python SSL support not found'))
79 raise util.Abort(_('Python SSL support not found'))
80 if ca_certs:
80 if ca_certs:
81 raise util.Abort(_(
81 raise util.Abort(_(
82 'certificate checking requires Python 2.6'))
82 'certificate checking requires Python 2.6'))
83
83
84 ssl = socket.ssl(sock, keyfile, certfile)
84 ssl = socket.ssl(sock, keyfile, certfile)
85 return httplib.FakeSocket(sock, ssl)
85 return httplib.FakeSocket(sock, ssl)
86
86
87 def _verifycert(cert, hostname):
87 def _verifycert(cert, hostname):
88 '''Verify that cert (in socket.getpeercert() format) matches hostname.
88 '''Verify that cert (in socket.getpeercert() format) matches hostname.
89 CRLs is not handled.
89 CRLs is not handled.
90
90
91 Returns error message if any problems are found and None on success.
91 Returns error message if any problems are found and None on success.
92 '''
92 '''
93 if not cert:
93 if not cert:
94 return _('no certificate received')
94 return _('no certificate received')
95 dnsname = hostname.lower()
95 dnsname = hostname.lower()
96 def matchdnsname(certname):
96 def matchdnsname(certname):
97 return (certname == dnsname or
97 return (certname == dnsname or
98 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
98 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
99
99
100 san = cert.get('subjectAltName', [])
100 san = cert.get('subjectAltName', [])
101 if san:
101 if san:
102 certnames = [value.lower() for key, value in san if key == 'DNS']
102 certnames = [value.lower() for key, value in san if key == 'DNS']
103 for name in certnames:
103 for name in certnames:
104 if matchdnsname(name):
104 if matchdnsname(name):
105 return None
105 return None
106 if certnames:
106 if certnames:
107 return _('certificate is for %s') % ', '.join(certnames)
107 return _('certificate is for %s') % ', '.join(certnames)
108
108
109 # subject is only checked when subjectAltName is empty
109 # subject is only checked when subjectAltName is empty
110 for s in cert.get('subject', []):
110 for s in cert.get('subject', []):
111 key, value = s[0]
111 key, value = s[0]
112 if key == 'commonName':
112 if key == 'commonName':
113 try:
113 try:
114 # 'subject' entries are unicode
114 # 'subject' entries are unicode
115 certname = value.lower().encode('ascii')
115 certname = value.lower().encode('ascii')
116 except UnicodeEncodeError:
116 except UnicodeEncodeError:
117 return _('IDN in certificate not supported')
117 return _('IDN in certificate not supported')
118 if matchdnsname(certname):
118 if matchdnsname(certname):
119 return None
119 return None
120 return _('certificate is for %s') % certname
120 return _('certificate is for %s') % certname
121 return _('no commonName or subjectAltName found in certificate')
121 return _('no commonName or subjectAltName found in certificate')
122
122
123
123
124 # CERT_REQUIRED means fetch the cert from the server all the time AND
124 # CERT_REQUIRED means fetch the cert from the server all the time AND
125 # validate it against the CA store provided in web.cacerts.
125 # validate it against the CA store provided in web.cacerts.
126 #
126 #
127 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
127 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
128 # busted on those versions.
128 # busted on those versions.
129
129
130 def _plainapplepython():
130 def _plainapplepython():
131 """return true if this seems to be a pure Apple Python that
131 """return true if this seems to be a pure Apple Python that
132 * is unfrozen and presumably has the whole mercurial module in the file
132 * is unfrozen and presumably has the whole mercurial module in the file
133 system
133 system
134 * presumably is an Apple Python that uses Apple OpenSSL which has patches
134 * presumably is an Apple Python that uses Apple OpenSSL which has patches
135 for using system certificate store CAs in addition to the provided
135 for using system certificate store CAs in addition to the provided
136 cacerts file
136 cacerts file
137 """
137 """
138 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
138 if sys.platform != 'darwin' or util.mainfrozen() or not sys.executable:
139 return False
139 return False
140 exe = os.path.realpath(sys.executable).lower()
140 exe = os.path.realpath(sys.executable).lower()
141 return (exe.startswith('/usr/bin/python') or
141 return (exe.startswith('/usr/bin/python') or
142 exe.startswith('/system/library/frameworks/python.framework/'))
142 exe.startswith('/system/library/frameworks/python.framework/'))
143
143
144 def _defaultcacerts():
144 def _defaultcacerts():
145 """return path to CA certificates; None for system's store; ! to disable"""
145 """return path to CA certificates; None for system's store; ! to disable"""
146 if _plainapplepython():
146 if _plainapplepython():
147 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
147 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
148 if os.path.exists(dummycert):
148 if os.path.exists(dummycert):
149 return dummycert
149 return dummycert
150 if _canloaddefaultcerts:
150 if _canloaddefaultcerts:
151 return None
151 return None
152 return '!'
152 return '!'
153
153
154 def sslkwargs(ui, host):
154 def sslkwargs(ui, host):
155 kws = {'ui': ui}
155 kws = {'ui': ui}
156 hostfingerprint = ui.config('hostfingerprints', host)
156 hostfingerprint = ui.config('hostfingerprints', host)
157 if hostfingerprint:
157 if hostfingerprint:
158 return kws
158 return kws
159 cacerts = ui.config('web', 'cacerts')
159 cacerts = ui.config('web', 'cacerts')
160 if cacerts == '!':
160 if cacerts == '!':
161 pass
161 pass
162 elif cacerts:
162 elif cacerts:
163 cacerts = util.expandpath(cacerts)
163 cacerts = util.expandpath(cacerts)
164 if not os.path.exists(cacerts):
164 if not os.path.exists(cacerts):
165 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
165 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
166 else:
166 else:
167 cacerts = _defaultcacerts()
167 cacerts = _defaultcacerts()
168 if cacerts and cacerts != '!':
168 if cacerts and cacerts != '!':
169 ui.debug('using %s to enable OS X system CA\n' % cacerts)
169 ui.debug('using %s to enable OS X system CA\n' % cacerts)
170 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
170 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
171 if cacerts != '!':
171 if cacerts != '!':
172 kws.update({'ca_certs': cacerts,
172 kws.update({'ca_certs': cacerts,
173 'cert_reqs': CERT_REQUIRED,
173 'cert_reqs': CERT_REQUIRED,
174 })
174 })
175 return kws
175 return kws
176
176
177 class validator(object):
177 class validator(object):
178 def __init__(self, ui, host):
178 def __init__(self, ui, host):
179 self.ui = ui
179 self.ui = ui
180 self.host = host
180 self.host = host
181
181
182 def __call__(self, sock, strict=False):
182 def __call__(self, sock, strict=False):
183 host = self.host
183 host = self.host
184 cacerts = self.ui.config('web', 'cacerts')
184 cacerts = self.ui.config('web', 'cacerts')
185 hostfingerprint = self.ui.config('hostfingerprints', host)
185 hostfingerprint = self.ui.config('hostfingerprints', host)
186 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
186 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
187 if hostfingerprint:
187 if hostfingerprint:
188 raise util.Abort(_("host fingerprint for %s can't be "
188 raise util.Abort(_("host fingerprint for %s can't be "
189 "verified (Python too old)") % host)
189 "verified (Python too old)") % host)
190 if strict:
190 if strict:
191 raise util.Abort(_("certificate for %s can't be verified "
191 raise util.Abort(_("certificate for %s can't be verified "
192 "(Python too old)") % host)
192 "(Python too old)") % host)
193 if self.ui.configbool('ui', 'reportoldssl', True):
193 if self.ui.configbool('ui', 'reportoldssl', True):
194 self.ui.warn(_("warning: certificate for %s can't be verified "
194 self.ui.warn(_("warning: certificate for %s can't be verified "
195 "(Python too old)\n") % host)
195 "(Python too old)\n") % host)
196 return
196 return
197
197
198 if not sock.cipher(): # work around http://bugs.python.org/issue13721
198 if not sock.cipher(): # work around http://bugs.python.org/issue13721
199 raise util.Abort(_('%s ssl connection error') % host)
199 raise util.Abort(_('%s ssl connection error') % host)
200 try:
200 try:
201 peercert = sock.getpeercert(True)
201 peercert = sock.getpeercert(True)
202 peercert2 = sock.getpeercert()
202 peercert2 = sock.getpeercert()
203 except AttributeError:
203 except AttributeError:
204 raise util.Abort(_('%s ssl connection error') % host)
204 raise util.Abort(_('%s ssl connection error') % host)
205
205
206 if not peercert:
206 if not peercert:
207 raise util.Abort(_('%s certificate error: '
207 raise util.Abort(_('%s certificate error: '
208 'no certificate received') % host)
208 'no certificate received') % host)
209 peerfingerprint = util.sha1(peercert).hexdigest()
209 peerfingerprint = util.sha1(peercert).hexdigest()
210 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
210 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
211 for x in xrange(0, len(peerfingerprint), 2)])
211 for x in xrange(0, len(peerfingerprint), 2)])
212 if hostfingerprint:
212 if hostfingerprint:
213 if peerfingerprint.lower() != \
213 if peerfingerprint.lower() != \
214 hostfingerprint.replace(':', '').lower():
214 hostfingerprint.replace(':', '').lower():
215 raise util.Abort(_('certificate for %s has unexpected '
215 raise util.Abort(_('certificate for %s has unexpected '
216 'fingerprint %s') % (host, nicefingerprint),
216 'fingerprint %s') % (host, nicefingerprint),
217 hint=_('check hostfingerprint configuration'))
217 hint=_('check hostfingerprint configuration'))
218 self.ui.debug('%s certificate matched fingerprint %s\n' %
218 self.ui.debug('%s certificate matched fingerprint %s\n' %
219 (host, nicefingerprint))
219 (host, nicefingerprint))
220 elif cacerts != '!':
220 elif cacerts != '!':
221 msg = _verifycert(peercert2, host)
221 msg = _verifycert(peercert2, host)
222 if msg:
222 if msg:
223 raise util.Abort(_('%s certificate error: %s') % (host, msg),
223 raise util.Abort(_('%s certificate error: %s') % (host, msg),
224 hint=_('configure hostfingerprint %s or use '
224 hint=_('configure hostfingerprint %s or use '
225 '--insecure to connect insecurely') %
225 '--insecure to connect insecurely') %
226 nicefingerprint)
226 nicefingerprint)
227 self.ui.debug('%s certificate successfully verified\n' % host)
227 self.ui.debug('%s certificate successfully verified\n' % host)
228 elif strict:
228 elif strict:
229 raise util.Abort(_('%s certificate with fingerprint %s not '
229 raise util.Abort(_('%s certificate with fingerprint %s not '
230 'verified') % (host, nicefingerprint),
230 'verified') % (host, nicefingerprint),
231 hint=_('check hostfingerprints or web.cacerts '
231 hint=_('check hostfingerprints or web.cacerts '
232 'config setting'))
232 'config setting'))
233 else:
233 else:
234 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
234 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
235 'verified (check hostfingerprints or web.cacerts '
235 'verified (check hostfingerprints or web.cacerts '
236 'config setting)\n') %
236 'config setting)\n') %
237 (host, nicefingerprint))
237 (host, nicefingerprint))
@@ -1,507 +1,507 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 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
123 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
124
124
125 def _gen_sendfile(orgsend):
125 def _gen_sendfile(orgsend):
126 def _sendfile(self, data):
126 def _sendfile(self, data):
127 # send a file
127 # send a file
128 if isinstance(data, httpconnectionmod.httpsendfile):
128 if isinstance(data, httpconnectionmod.httpsendfile):
129 # if auth required, some data sent twice, so rewind here
129 # if auth required, some data sent twice, so rewind here
130 data.seek(0)
130 data.seek(0)
131 for chunk in util.filechunkiter(data):
131 for chunk in util.filechunkiter(data):
132 orgsend(self, chunk)
132 orgsend(self, chunk)
133 else:
133 else:
134 orgsend(self, data)
134 orgsend(self, data)
135 return _sendfile
135 return _sendfile
136
136
137 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
137 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
138 if has_https:
138 if has_https:
139 try:
139 try:
140 _create_connection = socket.create_connection
140 _create_connection = socket.create_connection
141 except AttributeError:
141 except AttributeError:
142 _GLOBAL_DEFAULT_TIMEOUT = object()
142 _GLOBAL_DEFAULT_TIMEOUT = object()
143
143
144 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
144 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
145 source_address=None):
145 source_address=None):
146 # lifted from Python 2.6
146 # lifted from Python 2.6
147
147
148 msg = "getaddrinfo returns an empty list"
148 msg = "getaddrinfo returns an empty list"
149 host, port = address
149 host, port = address
150 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
150 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
151 af, socktype, proto, canonname, sa = res
151 af, socktype, proto, canonname, sa = res
152 sock = None
152 sock = None
153 try:
153 try:
154 sock = socket.socket(af, socktype, proto)
154 sock = socket.socket(af, socktype, proto)
155 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
155 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
156 sock.settimeout(timeout)
156 sock.settimeout(timeout)
157 if source_address:
157 if source_address:
158 sock.bind(source_address)
158 sock.bind(source_address)
159 sock.connect(sa)
159 sock.connect(sa)
160 return sock
160 return sock
161
161
162 except socket.error, msg:
162 except socket.error, msg:
163 if sock is not None:
163 if sock is not None:
164 sock.close()
164 sock.close()
165
165
166 raise socket.error(msg)
166 raise socket.error(msg)
167
167
168 class httpconnection(keepalive.HTTPConnection):
168 class httpconnection(keepalive.HTTPConnection):
169 # must be able to send big bundle as stream.
169 # must be able to send big bundle as stream.
170 send = _gen_sendfile(keepalive.HTTPConnection.send)
170 send = _gen_sendfile(keepalive.HTTPConnection.send)
171
171
172 def connect(self):
172 def connect(self):
173 if has_https and self.realhostport: # use CONNECT proxy
173 if has_https and self.realhostport: # use CONNECT proxy
174 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
174 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
175 self.sock.connect((self.host, self.port))
175 self.sock.connect((self.host, self.port))
176 if _generic_proxytunnel(self):
176 if _generic_proxytunnel(self):
177 # we do not support client X.509 certificates
177 # we do not support client X.509 certificates
178 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None, None,
178 self.sock = sslutil.wrapsocket(self.sock, None, None, None,
179 serverhostname=self.host)
179 serverhostname=self.host)
180 else:
180 else:
181 keepalive.HTTPConnection.connect(self)
181 keepalive.HTTPConnection.connect(self)
182
182
183 def getresponse(self):
183 def getresponse(self):
184 proxyres = getattr(self, 'proxyres', None)
184 proxyres = getattr(self, 'proxyres', None)
185 if proxyres:
185 if proxyres:
186 if proxyres.will_close:
186 if proxyres.will_close:
187 self.close()
187 self.close()
188 self.proxyres = None
188 self.proxyres = None
189 return proxyres
189 return proxyres
190 return keepalive.HTTPConnection.getresponse(self)
190 return keepalive.HTTPConnection.getresponse(self)
191
191
192 # general transaction handler to support different ways to handle
192 # general transaction handler to support different ways to handle
193 # HTTPS proxying before and after Python 2.6.3.
193 # HTTPS proxying before and after Python 2.6.3.
194 def _generic_start_transaction(handler, h, req):
194 def _generic_start_transaction(handler, h, req):
195 tunnel_host = getattr(req, '_tunnel_host', None)
195 tunnel_host = getattr(req, '_tunnel_host', None)
196 if tunnel_host:
196 if tunnel_host:
197 if tunnel_host[:7] not in ['http://', 'https:/']:
197 if tunnel_host[:7] not in ['http://', 'https:/']:
198 tunnel_host = 'https://' + tunnel_host
198 tunnel_host = 'https://' + tunnel_host
199 new_tunnel = True
199 new_tunnel = True
200 else:
200 else:
201 tunnel_host = req.get_selector()
201 tunnel_host = req.get_selector()
202 new_tunnel = False
202 new_tunnel = False
203
203
204 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
204 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
205 u = util.url(tunnel_host)
205 u = util.url(tunnel_host)
206 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
206 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
207 h.realhostport = ':'.join([u.host, (u.port or '443')])
207 h.realhostport = ':'.join([u.host, (u.port or '443')])
208 h.headers = req.headers.copy()
208 h.headers = req.headers.copy()
209 h.headers.update(handler.parent.addheaders)
209 h.headers.update(handler.parent.addheaders)
210 return
210 return
211
211
212 h.realhostport = None
212 h.realhostport = None
213 h.headers = None
213 h.headers = None
214
214
215 def _generic_proxytunnel(self):
215 def _generic_proxytunnel(self):
216 proxyheaders = dict(
216 proxyheaders = dict(
217 [(x, self.headers[x]) for x in self.headers
217 [(x, self.headers[x]) for x in self.headers
218 if x.lower().startswith('proxy-')])
218 if x.lower().startswith('proxy-')])
219 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
219 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
220 for header in proxyheaders.iteritems():
220 for header in proxyheaders.iteritems():
221 self.send('%s: %s\r\n' % header)
221 self.send('%s: %s\r\n' % header)
222 self.send('\r\n')
222 self.send('\r\n')
223
223
224 # majority of the following code is duplicated from
224 # majority of the following code is duplicated from
225 # httplib.HTTPConnection as there are no adequate places to
225 # httplib.HTTPConnection as there are no adequate places to
226 # override functions to provide the needed functionality
226 # override functions to provide the needed functionality
227 res = self.response_class(self.sock,
227 res = self.response_class(self.sock,
228 strict=self.strict,
228 strict=self.strict,
229 method=self._method)
229 method=self._method)
230
230
231 while True:
231 while True:
232 version, status, reason = res._read_status()
232 version, status, reason = res._read_status()
233 if status != httplib.CONTINUE:
233 if status != httplib.CONTINUE:
234 break
234 break
235 while True:
235 while True:
236 skip = res.fp.readline().strip()
236 skip = res.fp.readline().strip()
237 if not skip:
237 if not skip:
238 break
238 break
239 res.status = status
239 res.status = status
240 res.reason = reason.strip()
240 res.reason = reason.strip()
241
241
242 if res.status == 200:
242 if res.status == 200:
243 while True:
243 while True:
244 line = res.fp.readline()
244 line = res.fp.readline()
245 if line == '\r\n':
245 if line == '\r\n':
246 break
246 break
247 return True
247 return True
248
248
249 if version == 'HTTP/1.0':
249 if version == 'HTTP/1.0':
250 res.version = 10
250 res.version = 10
251 elif version.startswith('HTTP/1.'):
251 elif version.startswith('HTTP/1.'):
252 res.version = 11
252 res.version = 11
253 elif version == 'HTTP/0.9':
253 elif version == 'HTTP/0.9':
254 res.version = 9
254 res.version = 9
255 else:
255 else:
256 raise httplib.UnknownProtocol(version)
256 raise httplib.UnknownProtocol(version)
257
257
258 if res.version == 9:
258 if res.version == 9:
259 res.length = None
259 res.length = None
260 res.chunked = 0
260 res.chunked = 0
261 res.will_close = 1
261 res.will_close = 1
262 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
262 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
263 return False
263 return False
264
264
265 res.msg = httplib.HTTPMessage(res.fp)
265 res.msg = httplib.HTTPMessage(res.fp)
266 res.msg.fp = None
266 res.msg.fp = None
267
267
268 # are we using the chunked-style of transfer encoding?
268 # are we using the chunked-style of transfer encoding?
269 trenc = res.msg.getheader('transfer-encoding')
269 trenc = res.msg.getheader('transfer-encoding')
270 if trenc and trenc.lower() == "chunked":
270 if trenc and trenc.lower() == "chunked":
271 res.chunked = 1
271 res.chunked = 1
272 res.chunk_left = None
272 res.chunk_left = None
273 else:
273 else:
274 res.chunked = 0
274 res.chunked = 0
275
275
276 # will the connection close at the end of the response?
276 # will the connection close at the end of the response?
277 res.will_close = res._check_close()
277 res.will_close = res._check_close()
278
278
279 # do we have a Content-Length?
279 # do we have a Content-Length?
280 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
280 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
281 # transfer-encoding is "chunked"
281 # transfer-encoding is "chunked"
282 length = res.msg.getheader('content-length')
282 length = res.msg.getheader('content-length')
283 if length and not res.chunked:
283 if length and not res.chunked:
284 try:
284 try:
285 res.length = int(length)
285 res.length = int(length)
286 except ValueError:
286 except ValueError:
287 res.length = None
287 res.length = None
288 else:
288 else:
289 if res.length < 0: # ignore nonsensical negative lengths
289 if res.length < 0: # ignore nonsensical negative lengths
290 res.length = None
290 res.length = None
291 else:
291 else:
292 res.length = None
292 res.length = None
293
293
294 # does the body have a fixed length? (of zero)
294 # does the body have a fixed length? (of zero)
295 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
295 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
296 100 <= status < 200 or # 1xx codes
296 100 <= status < 200 or # 1xx codes
297 res._method == 'HEAD'):
297 res._method == 'HEAD'):
298 res.length = 0
298 res.length = 0
299
299
300 # if the connection remains open, and we aren't using chunked, and
300 # if the connection remains open, and we aren't using chunked, and
301 # a content-length was not provided, then assume that the connection
301 # a content-length was not provided, then assume that the connection
302 # WILL close.
302 # WILL close.
303 if (not res.will_close and
303 if (not res.will_close and
304 not res.chunked and
304 not res.chunked and
305 res.length is None):
305 res.length is None):
306 res.will_close = 1
306 res.will_close = 1
307
307
308 self.proxyres = res
308 self.proxyres = res
309
309
310 return False
310 return False
311
311
312 class httphandler(keepalive.HTTPHandler):
312 class httphandler(keepalive.HTTPHandler):
313 def http_open(self, req):
313 def http_open(self, req):
314 return self.do_open(httpconnection, req)
314 return self.do_open(httpconnection, req)
315
315
316 def _start_transaction(self, h, req):
316 def _start_transaction(self, h, req):
317 _generic_start_transaction(self, h, req)
317 _generic_start_transaction(self, h, req)
318 return keepalive.HTTPHandler._start_transaction(self, h, req)
318 return keepalive.HTTPHandler._start_transaction(self, h, req)
319
319
320 if has_https:
320 if has_https:
321 class httpsconnection(httplib.HTTPConnection):
321 class httpsconnection(httplib.HTTPConnection):
322 response_class = keepalive.HTTPResponse
322 response_class = keepalive.HTTPResponse
323 default_port = httplib.HTTPS_PORT
323 default_port = httplib.HTTPS_PORT
324 # must be able to send big bundle as stream.
324 # must be able to send big bundle as stream.
325 send = _gen_sendfile(keepalive.safesend)
325 send = _gen_sendfile(keepalive.safesend)
326 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
326 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
327
327
328 def __init__(self, host, port=None, key_file=None, cert_file=None,
328 def __init__(self, host, port=None, key_file=None, cert_file=None,
329 *args, **kwargs):
329 *args, **kwargs):
330 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
330 httplib.HTTPConnection.__init__(self, host, port, *args, **kwargs)
331 self.key_file = key_file
331 self.key_file = key_file
332 self.cert_file = cert_file
332 self.cert_file = cert_file
333
333
334 def connect(self):
334 def connect(self):
335 self.sock = _create_connection((self.host, self.port))
335 self.sock = _create_connection((self.host, self.port))
336
336
337 host = self.host
337 host = self.host
338 if self.realhostport: # use CONNECT proxy
338 if self.realhostport: # use CONNECT proxy
339 _generic_proxytunnel(self)
339 _generic_proxytunnel(self)
340 host = self.realhostport.rsplit(':', 1)[0]
340 host = self.realhostport.rsplit(':', 1)[0]
341 self.sock = sslutil.ssl_wrap_socket(
341 self.sock = sslutil.wrapsocket(
342 self.sock, self.key_file, self.cert_file, serverhostname=host,
342 self.sock, self.key_file, self.cert_file, serverhostname=host,
343 **sslutil.sslkwargs(self.ui, host))
343 **sslutil.sslkwargs(self.ui, host))
344 sslutil.validator(self.ui, host)(self.sock)
344 sslutil.validator(self.ui, host)(self.sock)
345
345
346 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
346 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
347 def __init__(self, ui):
347 def __init__(self, ui):
348 keepalive.KeepAliveHandler.__init__(self)
348 keepalive.KeepAliveHandler.__init__(self)
349 urllib2.HTTPSHandler.__init__(self)
349 urllib2.HTTPSHandler.__init__(self)
350 self.ui = ui
350 self.ui = ui
351 self.pwmgr = passwordmgr(self.ui)
351 self.pwmgr = passwordmgr(self.ui)
352
352
353 def _start_transaction(self, h, req):
353 def _start_transaction(self, h, req):
354 _generic_start_transaction(self, h, req)
354 _generic_start_transaction(self, h, req)
355 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
355 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
356
356
357 def https_open(self, req):
357 def https_open(self, req):
358 # req.get_full_url() does not contain credentials and we may
358 # req.get_full_url() does not contain credentials and we may
359 # need them to match the certificates.
359 # need them to match the certificates.
360 url = req.get_full_url()
360 url = req.get_full_url()
361 user, password = self.pwmgr.find_stored_password(url)
361 user, password = self.pwmgr.find_stored_password(url)
362 res = httpconnectionmod.readauthforuri(self.ui, url, user)
362 res = httpconnectionmod.readauthforuri(self.ui, url, user)
363 if res:
363 if res:
364 group, auth = res
364 group, auth = res
365 self.auth = auth
365 self.auth = auth
366 self.ui.debug("using auth.%s.* for authentication\n" % group)
366 self.ui.debug("using auth.%s.* for authentication\n" % group)
367 else:
367 else:
368 self.auth = None
368 self.auth = None
369 return self.do_open(self._makeconnection, req)
369 return self.do_open(self._makeconnection, req)
370
370
371 def _makeconnection(self, host, port=None, *args, **kwargs):
371 def _makeconnection(self, host, port=None, *args, **kwargs):
372 keyfile = None
372 keyfile = None
373 certfile = None
373 certfile = None
374
374
375 if len(args) >= 1: # key_file
375 if len(args) >= 1: # key_file
376 keyfile = args[0]
376 keyfile = args[0]
377 if len(args) >= 2: # cert_file
377 if len(args) >= 2: # cert_file
378 certfile = args[1]
378 certfile = args[1]
379 args = args[2:]
379 args = args[2:]
380
380
381 # if the user has specified different key/cert files in
381 # if the user has specified different key/cert files in
382 # hgrc, we prefer these
382 # hgrc, we prefer these
383 if self.auth and 'key' in self.auth and 'cert' in self.auth:
383 if self.auth and 'key' in self.auth and 'cert' in self.auth:
384 keyfile = self.auth['key']
384 keyfile = self.auth['key']
385 certfile = self.auth['cert']
385 certfile = self.auth['cert']
386
386
387 conn = httpsconnection(host, port, keyfile, certfile, *args,
387 conn = httpsconnection(host, port, keyfile, certfile, *args,
388 **kwargs)
388 **kwargs)
389 conn.ui = self.ui
389 conn.ui = self.ui
390 return conn
390 return conn
391
391
392 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
392 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
393 def __init__(self, *args, **kwargs):
393 def __init__(self, *args, **kwargs):
394 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
394 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
395 self.retried_req = None
395 self.retried_req = None
396
396
397 def reset_retry_count(self):
397 def reset_retry_count(self):
398 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
398 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
399 # forever. We disable reset_retry_count completely and reset in
399 # forever. We disable reset_retry_count completely and reset in
400 # http_error_auth_reqed instead.
400 # http_error_auth_reqed instead.
401 pass
401 pass
402
402
403 def http_error_auth_reqed(self, auth_header, host, req, headers):
403 def http_error_auth_reqed(self, auth_header, host, req, headers):
404 # Reset the retry counter once for each request.
404 # Reset the retry counter once for each request.
405 if req is not self.retried_req:
405 if req is not self.retried_req:
406 self.retried_req = req
406 self.retried_req = req
407 self.retried = 0
407 self.retried = 0
408 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
408 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
409 # it doesn't know about the auth type requested. This can happen if
409 # it doesn't know about the auth type requested. This can happen if
410 # somebody is using BasicAuth and types a bad password.
410 # somebody is using BasicAuth and types a bad password.
411 try:
411 try:
412 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
412 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
413 self, auth_header, host, req, headers)
413 self, auth_header, host, req, headers)
414 except ValueError, inst:
414 except ValueError, inst:
415 arg = inst.args[0]
415 arg = inst.args[0]
416 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
416 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
417 return
417 return
418 raise
418 raise
419
419
420 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
420 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
421 def __init__(self, *args, **kwargs):
421 def __init__(self, *args, **kwargs):
422 self.auth = None
422 self.auth = None
423 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
423 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
424 self.retried_req = None
424 self.retried_req = None
425
425
426 def http_request(self, request):
426 def http_request(self, request):
427 if self.auth:
427 if self.auth:
428 request.add_unredirected_header(self.auth_header, self.auth)
428 request.add_unredirected_header(self.auth_header, self.auth)
429
429
430 return request
430 return request
431
431
432 def https_request(self, request):
432 def https_request(self, request):
433 if self.auth:
433 if self.auth:
434 request.add_unredirected_header(self.auth_header, self.auth)
434 request.add_unredirected_header(self.auth_header, self.auth)
435
435
436 return request
436 return request
437
437
438 def reset_retry_count(self):
438 def reset_retry_count(self):
439 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
439 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
440 # forever. We disable reset_retry_count completely and reset in
440 # forever. We disable reset_retry_count completely and reset in
441 # http_error_auth_reqed instead.
441 # http_error_auth_reqed instead.
442 pass
442 pass
443
443
444 def http_error_auth_reqed(self, auth_header, host, req, headers):
444 def http_error_auth_reqed(self, auth_header, host, req, headers):
445 # Reset the retry counter once for each request.
445 # Reset the retry counter once for each request.
446 if req is not self.retried_req:
446 if req is not self.retried_req:
447 self.retried_req = req
447 self.retried_req = req
448 self.retried = 0
448 self.retried = 0
449 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
449 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
450 self, auth_header, host, req, headers)
450 self, auth_header, host, req, headers)
451
451
452 def retry_http_basic_auth(self, host, req, realm):
452 def retry_http_basic_auth(self, host, req, realm):
453 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
453 user, pw = self.passwd.find_user_password(realm, req.get_full_url())
454 if pw is not None:
454 if pw is not None:
455 raw = "%s:%s" % (user, pw)
455 raw = "%s:%s" % (user, pw)
456 auth = 'Basic %s' % base64.b64encode(raw).strip()
456 auth = 'Basic %s' % base64.b64encode(raw).strip()
457 if req.headers.get(self.auth_header, None) == auth:
457 if req.headers.get(self.auth_header, None) == auth:
458 return None
458 return None
459 self.auth = auth
459 self.auth = auth
460 req.add_unredirected_header(self.auth_header, auth)
460 req.add_unredirected_header(self.auth_header, auth)
461 return self.parent.open(req)
461 return self.parent.open(req)
462 else:
462 else:
463 return None
463 return None
464
464
465 handlerfuncs = []
465 handlerfuncs = []
466
466
467 def opener(ui, authinfo=None):
467 def opener(ui, authinfo=None):
468 '''
468 '''
469 construct an opener suitable for urllib2
469 construct an opener suitable for urllib2
470 authinfo will be added to the password manager
470 authinfo will be added to the password manager
471 '''
471 '''
472 if ui.configbool('ui', 'usehttp2', False):
472 if ui.configbool('ui', 'usehttp2', False):
473 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
473 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
474 else:
474 else:
475 handlers = [httphandler()]
475 handlers = [httphandler()]
476 if has_https:
476 if has_https:
477 handlers.append(httpshandler(ui))
477 handlers.append(httpshandler(ui))
478
478
479 handlers.append(proxyhandler(ui))
479 handlers.append(proxyhandler(ui))
480
480
481 passmgr = passwordmgr(ui)
481 passmgr = passwordmgr(ui)
482 if authinfo is not None:
482 if authinfo is not None:
483 passmgr.add_password(*authinfo)
483 passmgr.add_password(*authinfo)
484 user, passwd = authinfo[2:4]
484 user, passwd = authinfo[2:4]
485 ui.debug('http auth: user %s, password %s\n' %
485 ui.debug('http auth: user %s, password %s\n' %
486 (user, passwd and '*' * len(passwd) or 'not set'))
486 (user, passwd and '*' * len(passwd) or 'not set'))
487
487
488 handlers.extend((httpbasicauthhandler(passmgr),
488 handlers.extend((httpbasicauthhandler(passmgr),
489 httpdigestauthhandler(passmgr)))
489 httpdigestauthhandler(passmgr)))
490 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
490 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
491 opener = urllib2.build_opener(*handlers)
491 opener = urllib2.build_opener(*handlers)
492
492
493 # 1.0 here is the _protocol_ version
493 # 1.0 here is the _protocol_ version
494 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
494 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
495 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
495 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
496 return opener
496 return opener
497
497
498 def open(ui, url_, data=None):
498 def open(ui, url_, data=None):
499 u = util.url(url_)
499 u = util.url(url_)
500 if u.scheme:
500 if u.scheme:
501 u.scheme = u.scheme.lower()
501 u.scheme = u.scheme.lower()
502 url_, authinfo = u.authinfo()
502 url_, authinfo = u.authinfo()
503 else:
503 else:
504 path = util.normpath(os.path.abspath(url_))
504 path = util.normpath(os.path.abspath(url_))
505 url_ = 'file://' + urllib.pathname2url(path)
505 url_ = 'file://' + urllib.pathname2url(path)
506 authinfo = None
506 authinfo = None
507 return opener(ui, authinfo).open(url_, data)
507 return opener(ui, authinfo).open(url_, data)
General Comments 0
You need to be logged in to leave comments. Login now