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 |
10 import urllib, urllib2, httplib, os, socket, cStringIO |
11 import __builtin__ |
|
12 from i18n import _ |
11 from i18n import _ |
13 import keepalive, util, sslutil |
12 import keepalive, util, sslutil |
14 |
13 import httpconnection as httpconnectionmod |
15 def readauthforuri(ui, uri): |
|
16 # Read configuration |
|
17 config = dict() |
|
18 for key, val in ui.configitems('auth'): |
|
19 if '.' not in key: |
|
20 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key) |
|
21 continue |
|
22 group, setting = key.rsplit('.', 1) |
|
23 gdict = config.setdefault(group, dict()) |
|
24 if setting in ('username', 'cert', 'key'): |
|
25 val = util.expandpath(val) |
|
26 gdict[setting] = val |
|
27 |
|
28 # Find the best match |
|
29 scheme, hostpath = uri.split('://', 1) |
|
30 bestlen = 0 |
|
31 bestauth = None |
|
32 for group, auth in config.iteritems(): |
|
33 prefix = auth.get('prefix') |
|
34 if not prefix: |
|
35 continue |
|
36 p = prefix.split('://', 1) |
|
37 if len(p) > 1: |
|
38 schemes, prefix = [p[0]], p[1] |
|
39 else: |
|
40 schemes = (auth.get('schemes') or 'https').split() |
|
41 if (prefix == '*' or hostpath.startswith(prefix)) and \ |
|
42 len(prefix) > bestlen and scheme in schemes: |
|
43 bestlen = len(prefix) |
|
44 bestauth = group, auth |
|
45 return bestauth |
|
46 |
14 |
47 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): |
15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): |
48 def __init__(self, ui): |
16 def __init__(self, ui): |
49 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self) |
17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self) |
50 self.ui = ui |
18 self.ui = ui |
56 if user and passwd: |
24 if user and passwd: |
57 self._writedebug(user, passwd) |
25 self._writedebug(user, passwd) |
58 return (user, passwd) |
26 return (user, passwd) |
59 |
27 |
60 if not user: |
28 if not user: |
61 res = readauthforuri(self.ui, authuri) |
29 res = httpconnectionmod.readauthforuri(self.ui, authuri) |
62 if res: |
30 if res: |
63 group, auth = res |
31 group, auth = res |
64 user, passwd = auth.get('username'), auth.get('password') |
32 user, passwd = auth.get('username'), auth.get('password') |
65 self.ui.debug("using auth.%s.* for authentication\n" % group) |
33 self.ui.debug("using auth.%s.* for authentication\n" % group) |
66 if not user or not passwd: |
34 if not user or not passwd: |
147 return baseclass.add_header(self, key, val) |
115 return baseclass.add_header(self, key, val) |
148 req.__class__ = _request |
116 req.__class__ = _request |
149 |
117 |
150 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_) |
118 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_) |
151 |
119 |
152 class httpsendfile(object): |
|
153 """This is a wrapper around the objects returned by python's "open". |
|
154 |
|
155 Its purpose is to send file-like objects via HTTP and, to do so, it |
|
156 defines a __len__ attribute to feed the Content-Length header. |
|
157 """ |
|
158 |
|
159 def __init__(self, ui, *args, **kwargs): |
|
160 # We can't just "self._data = open(*args, **kwargs)" here because there |
|
161 # is an "open" function defined in this module that shadows the global |
|
162 # one |
|
163 self.ui = ui |
|
164 self._data = __builtin__.open(*args, **kwargs) |
|
165 self.seek = self._data.seek |
|
166 self.close = self._data.close |
|
167 self.write = self._data.write |
|
168 self._len = os.fstat(self._data.fileno()).st_size |
|
169 self._pos = 0 |
|
170 self._total = len(self) / 1024 * 2 |
|
171 |
|
172 def read(self, *args, **kwargs): |
|
173 try: |
|
174 ret = self._data.read(*args, **kwargs) |
|
175 except EOFError: |
|
176 self.ui.progress(_('sending'), None) |
|
177 self._pos += len(ret) |
|
178 # We pass double the max for total because we currently have |
|
179 # to send the bundle twice in the case of a server that |
|
180 # requires authentication. Since we can't know until we try |
|
181 # once whether authentication will be required, just lie to |
|
182 # the user and maybe the push succeeds suddenly at 50%. |
|
183 self.ui.progress(_('sending'), self._pos / 1024, |
|
184 unit=_('kb'), total=self._total) |
|
185 return ret |
|
186 |
|
187 def __len__(self): |
|
188 return self._len |
|
189 |
|
190 def _gen_sendfile(orgsend): |
120 def _gen_sendfile(orgsend): |
191 def _sendfile(self, data): |
121 def _sendfile(self, data): |
192 # send a file |
122 # send a file |
193 if isinstance(data, httpsendfile): |
123 if isinstance(data, httpconnectionmod.httpsendfile): |
194 # if auth required, some data sent twice, so rewind here |
124 # if auth required, some data sent twice, so rewind here |
195 data.seek(0) |
125 data.seek(0) |
196 for chunk in util.filechunkiter(data): |
126 for chunk in util.filechunkiter(data): |
197 orgsend(self, chunk) |
127 orgsend(self, chunk) |
198 else: |
128 else: |
410 def _start_transaction(self, h, req): |
340 def _start_transaction(self, h, req): |
411 _generic_start_transaction(self, h, req) |
341 _generic_start_transaction(self, h, req) |
412 return keepalive.KeepAliveHandler._start_transaction(self, h, req) |
342 return keepalive.KeepAliveHandler._start_transaction(self, h, req) |
413 |
343 |
414 def https_open(self, req): |
344 def https_open(self, req): |
415 res = readauthforuri(self.ui, req.get_full_url()) |
345 res = httpconnectionmod.readauthforuri(self.ui, req.get_full_url()) |
416 if res: |
346 if res: |
417 group, auth = res |
347 group, auth = res |
418 self.auth = auth |
348 self.auth = auth |
419 self.ui.debug("using auth.%s.* for authentication\n" % group) |
349 self.ui.debug("using auth.%s.* for authentication\n" % group) |
420 else: |
350 else: |
493 def opener(ui, authinfo=None): |
423 def opener(ui, authinfo=None): |
494 ''' |
424 ''' |
495 construct an opener suitable for urllib2 |
425 construct an opener suitable for urllib2 |
496 authinfo will be added to the password manager |
426 authinfo will be added to the password manager |
497 ''' |
427 ''' |
498 handlers = [httphandler()] |
428 if ui.configbool('ui', 'usehttp2', False): |
499 if has_https: |
429 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))] |
500 handlers.append(httpshandler(ui)) |
430 else: |
|
431 handlers = [httphandler()] |
|
432 if has_https: |
|
433 handlers.append(httpshandler(ui)) |
501 |
434 |
502 handlers.append(proxyhandler(ui)) |
435 handlers.append(proxyhandler(ui)) |
503 |
436 |
504 passmgr = passwordmgr(ui) |
437 passmgr = passwordmgr(ui) |
505 if authinfo is not None: |
438 if authinfo is not None: |