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