##// END OF EJS Templates
httpsendfile: record progress information during read()...
Augie Fackler -
r13115:bda5f35f default
parent child Browse files
Show More
@@ -1,203 +1,203 b''
1 # httprepo.py - HTTP repository proxy classes for mercurial
1 # httprepo.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from node import nullid
9 from node import nullid
10 from i18n import _
10 from i18n import _
11 import changegroup, statichttprepo, error, url, util, wireproto
11 import changegroup, statichttprepo, error, url, util, wireproto
12 import os, urllib, urllib2, urlparse, zlib, httplib
12 import os, urllib, urllib2, urlparse, zlib, httplib
13 import errno, socket
13 import errno, socket
14
14
15 def zgenerator(f):
15 def zgenerator(f):
16 zd = zlib.decompressobj()
16 zd = zlib.decompressobj()
17 try:
17 try:
18 for chunk in util.filechunkiter(f):
18 for chunk in util.filechunkiter(f):
19 while chunk:
19 while chunk:
20 yield zd.decompress(chunk, 2**18)
20 yield zd.decompress(chunk, 2**18)
21 chunk = zd.unconsumed_tail
21 chunk = zd.unconsumed_tail
22 except httplib.HTTPException:
22 except httplib.HTTPException:
23 raise IOError(None, _('connection ended unexpectedly'))
23 raise IOError(None, _('connection ended unexpectedly'))
24 yield zd.flush()
24 yield zd.flush()
25
25
26 class httprepository(wireproto.wirerepository):
26 class httprepository(wireproto.wirerepository):
27 def __init__(self, ui, path):
27 def __init__(self, ui, path):
28 self.path = path
28 self.path = path
29 self.caps = None
29 self.caps = None
30 self.handler = None
30 self.handler = None
31 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
31 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
32 if query or frag:
32 if query or frag:
33 raise util.Abort(_('unsupported URL component: "%s"') %
33 raise util.Abort(_('unsupported URL component: "%s"') %
34 (query or frag))
34 (query or frag))
35
35
36 # urllib cannot handle URLs with embedded user or passwd
36 # urllib cannot handle URLs with embedded user or passwd
37 self._url, authinfo = url.getauthinfo(path)
37 self._url, authinfo = url.getauthinfo(path)
38
38
39 self.ui = ui
39 self.ui = ui
40 self.ui.debug('using %s\n' % self._url)
40 self.ui.debug('using %s\n' % self._url)
41
41
42 self.urlopener = url.opener(ui, authinfo)
42 self.urlopener = url.opener(ui, authinfo)
43
43
44 def __del__(self):
44 def __del__(self):
45 for h in self.urlopener.handlers:
45 for h in self.urlopener.handlers:
46 h.close()
46 h.close()
47 if hasattr(h, "close_all"):
47 if hasattr(h, "close_all"):
48 h.close_all()
48 h.close_all()
49
49
50 def url(self):
50 def url(self):
51 return self.path
51 return self.path
52
52
53 # look up capabilities only when needed
53 # look up capabilities only when needed
54
54
55 def get_caps(self):
55 def get_caps(self):
56 if self.caps is None:
56 if self.caps is None:
57 try:
57 try:
58 self.caps = set(self._call('capabilities').split())
58 self.caps = set(self._call('capabilities').split())
59 except error.RepoError:
59 except error.RepoError:
60 self.caps = set()
60 self.caps = set()
61 self.ui.debug('capabilities: %s\n' %
61 self.ui.debug('capabilities: %s\n' %
62 (' '.join(self.caps or ['none'])))
62 (' '.join(self.caps or ['none'])))
63 return self.caps
63 return self.caps
64
64
65 capabilities = property(get_caps)
65 capabilities = property(get_caps)
66
66
67 def lock(self):
67 def lock(self):
68 raise util.Abort(_('operation not supported over http'))
68 raise util.Abort(_('operation not supported over http'))
69
69
70 def _callstream(self, cmd, **args):
70 def _callstream(self, cmd, **args):
71 if cmd == 'pushkey':
71 if cmd == 'pushkey':
72 args['data'] = ''
72 args['data'] = ''
73 data = args.pop('data', None)
73 data = args.pop('data', None)
74 headers = args.pop('headers', {})
74 headers = args.pop('headers', {})
75 self.ui.debug("sending %s command\n" % cmd)
75 self.ui.debug("sending %s command\n" % cmd)
76 q = {"cmd": cmd}
76 q = {"cmd": cmd}
77 q.update(args)
77 q.update(args)
78 qs = '?%s' % urllib.urlencode(q)
78 qs = '?%s' % urllib.urlencode(q)
79 cu = "%s%s" % (self._url, qs)
79 cu = "%s%s" % (self._url, qs)
80 req = urllib2.Request(cu, data, headers)
80 req = urllib2.Request(cu, data, headers)
81 if data is not None:
81 if data is not None:
82 # len(data) is broken if data doesn't fit into Py_ssize_t
82 # len(data) is broken if data doesn't fit into Py_ssize_t
83 # add the header ourself to avoid OverflowError
83 # add the header ourself to avoid OverflowError
84 size = data.__len__()
84 size = data.__len__()
85 self.ui.debug("sending %s bytes\n" % size)
85 self.ui.debug("sending %s bytes\n" % size)
86 req.add_unredirected_header('Content-Length', '%d' % size)
86 req.add_unredirected_header('Content-Length', '%d' % size)
87 try:
87 try:
88 resp = self.urlopener.open(req)
88 resp = self.urlopener.open(req)
89 except urllib2.HTTPError, inst:
89 except urllib2.HTTPError, inst:
90 if inst.code == 401:
90 if inst.code == 401:
91 raise util.Abort(_('authorization failed'))
91 raise util.Abort(_('authorization failed'))
92 raise
92 raise
93 except httplib.HTTPException, inst:
93 except httplib.HTTPException, inst:
94 self.ui.debug('http error while sending %s command\n' % cmd)
94 self.ui.debug('http error while sending %s command\n' % cmd)
95 self.ui.traceback()
95 self.ui.traceback()
96 raise IOError(None, inst)
96 raise IOError(None, inst)
97 except IndexError:
97 except IndexError:
98 # this only happens with Python 2.3, later versions raise URLError
98 # this only happens with Python 2.3, later versions raise URLError
99 raise util.Abort(_('http error, possibly caused by proxy setting'))
99 raise util.Abort(_('http error, possibly caused by proxy setting'))
100 # record the url we got redirected to
100 # record the url we got redirected to
101 resp_url = resp.geturl()
101 resp_url = resp.geturl()
102 if resp_url.endswith(qs):
102 if resp_url.endswith(qs):
103 resp_url = resp_url[:-len(qs)]
103 resp_url = resp_url[:-len(qs)]
104 if self._url.rstrip('/') != resp_url.rstrip('/'):
104 if self._url.rstrip('/') != resp_url.rstrip('/'):
105 self.ui.status(_('real URL is %s\n') % resp_url)
105 self.ui.status(_('real URL is %s\n') % resp_url)
106 self._url = resp_url
106 self._url = resp_url
107 try:
107 try:
108 proto = resp.getheader('content-type')
108 proto = resp.getheader('content-type')
109 except AttributeError:
109 except AttributeError:
110 proto = resp.headers['content-type']
110 proto = resp.headers['content-type']
111
111
112 safeurl = url.hidepassword(self._url)
112 safeurl = url.hidepassword(self._url)
113 # accept old "text/plain" and "application/hg-changegroup" for now
113 # accept old "text/plain" and "application/hg-changegroup" for now
114 if not (proto.startswith('application/mercurial-') or
114 if not (proto.startswith('application/mercurial-') or
115 proto.startswith('text/plain') or
115 proto.startswith('text/plain') or
116 proto.startswith('application/hg-changegroup')):
116 proto.startswith('application/hg-changegroup')):
117 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
117 self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
118 raise error.RepoError(
118 raise error.RepoError(
119 _("'%s' does not appear to be an hg repository:\n"
119 _("'%s' does not appear to be an hg repository:\n"
120 "---%%<--- (%s)\n%s\n---%%<---\n")
120 "---%%<--- (%s)\n%s\n---%%<---\n")
121 % (safeurl, proto, resp.read()))
121 % (safeurl, proto, resp.read()))
122
122
123 if proto.startswith('application/mercurial-'):
123 if proto.startswith('application/mercurial-'):
124 try:
124 try:
125 version = proto.split('-', 1)[1]
125 version = proto.split('-', 1)[1]
126 version_info = tuple([int(n) for n in version.split('.')])
126 version_info = tuple([int(n) for n in version.split('.')])
127 except ValueError:
127 except ValueError:
128 raise error.RepoError(_("'%s' sent a broken Content-Type "
128 raise error.RepoError(_("'%s' sent a broken Content-Type "
129 "header (%s)") % (safeurl, proto))
129 "header (%s)") % (safeurl, proto))
130 if version_info > (0, 1):
130 if version_info > (0, 1):
131 raise error.RepoError(_("'%s' uses newer protocol %s") %
131 raise error.RepoError(_("'%s' uses newer protocol %s") %
132 (safeurl, version))
132 (safeurl, version))
133
133
134 return resp
134 return resp
135
135
136 def _call(self, cmd, **args):
136 def _call(self, cmd, **args):
137 fp = self._callstream(cmd, **args)
137 fp = self._callstream(cmd, **args)
138 try:
138 try:
139 return fp.read()
139 return fp.read()
140 finally:
140 finally:
141 # if using keepalive, allow connection to be reused
141 # if using keepalive, allow connection to be reused
142 fp.close()
142 fp.close()
143
143
144 def _callpush(self, cmd, cg, **args):
144 def _callpush(self, cmd, cg, **args):
145 # have to stream bundle to a temp file because we do not have
145 # have to stream bundle to a temp file because we do not have
146 # http 1.1 chunked transfer.
146 # http 1.1 chunked transfer.
147
147
148 type = ""
148 type = ""
149 types = self.capable('unbundle')
149 types = self.capable('unbundle')
150 # servers older than d1b16a746db6 will send 'unbundle' as a
150 # servers older than d1b16a746db6 will send 'unbundle' as a
151 # boolean capability
151 # boolean capability
152 try:
152 try:
153 types = types.split(',')
153 types = types.split(',')
154 except AttributeError:
154 except AttributeError:
155 types = [""]
155 types = [""]
156 if types:
156 if types:
157 for x in types:
157 for x in types:
158 if x in changegroup.bundletypes:
158 if x in changegroup.bundletypes:
159 type = x
159 type = x
160 break
160 break
161
161
162 tempname = changegroup.writebundle(cg, None, type)
162 tempname = changegroup.writebundle(cg, None, type)
163 fp = url.httpsendfile(tempname, "rb")
163 fp = url.httpsendfile(self.ui, tempname, "rb")
164 headers = {'Content-Type': 'application/mercurial-0.1'}
164 headers = {'Content-Type': 'application/mercurial-0.1'}
165
165
166 try:
166 try:
167 try:
167 try:
168 r = self._call(cmd, data=fp, headers=headers, **args)
168 r = self._call(cmd, data=fp, headers=headers, **args)
169 return r.split('\n', 1)
169 return r.split('\n', 1)
170 except socket.error, err:
170 except socket.error, err:
171 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
171 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
172 raise util.Abort(_('push failed: %s') % err.args[1])
172 raise util.Abort(_('push failed: %s') % err.args[1])
173 raise util.Abort(err.args[1])
173 raise util.Abort(err.args[1])
174 finally:
174 finally:
175 fp.close()
175 fp.close()
176 os.unlink(tempname)
176 os.unlink(tempname)
177
177
178 def _abort(self, exception):
178 def _abort(self, exception):
179 raise exception
179 raise exception
180
180
181 def _decompress(self, stream):
181 def _decompress(self, stream):
182 return util.chunkbuffer(zgenerator(stream))
182 return util.chunkbuffer(zgenerator(stream))
183
183
184 class httpsrepository(httprepository):
184 class httpsrepository(httprepository):
185 def __init__(self, ui, path):
185 def __init__(self, ui, path):
186 if not url.has_https:
186 if not url.has_https:
187 raise util.Abort(_('Python support for SSL and HTTPS '
187 raise util.Abort(_('Python support for SSL and HTTPS '
188 'is not installed'))
188 'is not installed'))
189 httprepository.__init__(self, ui, path)
189 httprepository.__init__(self, ui, path)
190
190
191 def instance(ui, path, create):
191 def instance(ui, path, create):
192 if create:
192 if create:
193 raise util.Abort(_('cannot create new http repository'))
193 raise util.Abort(_('cannot create new http repository'))
194 try:
194 try:
195 if path.startswith('https:'):
195 if path.startswith('https:'):
196 inst = httpsrepository(ui, path)
196 inst = httpsrepository(ui, path)
197 else:
197 else:
198 inst = httprepository(ui, path)
198 inst = httprepository(ui, path)
199 inst.between([(nullid, nullid)])
199 inst.between([(nullid, nullid)])
200 return inst
200 return inst
201 except error.RepoError:
201 except error.RepoError:
202 ui.note('(falling back to static-http)\n')
202 ui.note('(falling back to static-http)\n')
203 return statichttprepo.instance(ui, "static-" + path, create)
203 return statichttprepo.instance(ui, "static-" + path, create)
@@ -1,698 +1,716 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, urlparse, httplib, os, re, socket, cStringIO
10 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
11 import __builtin__
11 import __builtin__
12 from i18n import _
12 from i18n import _
13 import keepalive, util
13 import keepalive, util
14
14
15 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
15 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
16 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
16 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
17 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
17 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
18 if (scheme and
18 if (scheme and
19 result.startswith(scheme + ':') and
19 result.startswith(scheme + ':') and
20 not result.startswith(scheme + '://') and
20 not result.startswith(scheme + '://') and
21 url.startswith(scheme + '://')
21 url.startswith(scheme + '://')
22 ):
22 ):
23 result = scheme + '://' + result[len(scheme + ':'):]
23 result = scheme + '://' + result[len(scheme + ':'):]
24 return result
24 return result
25
25
26 def hidepassword(url):
26 def hidepassword(url):
27 '''hide user credential in a url string'''
27 '''hide user credential in a url string'''
28 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
28 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
29 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
29 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
30 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
30 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
31
31
32 def removeauth(url):
32 def removeauth(url):
33 '''remove all authentication information from a url string'''
33 '''remove all authentication information from a url string'''
34 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
34 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
35 netloc = netloc[netloc.find('@')+1:]
35 netloc = netloc[netloc.find('@')+1:]
36 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
36 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
37
37
38 def netlocsplit(netloc):
38 def netlocsplit(netloc):
39 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
39 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
40
40
41 a = netloc.find('@')
41 a = netloc.find('@')
42 if a == -1:
42 if a == -1:
43 user, passwd = None, None
43 user, passwd = None, None
44 else:
44 else:
45 userpass, netloc = netloc[:a], netloc[a + 1:]
45 userpass, netloc = netloc[:a], netloc[a + 1:]
46 c = userpass.find(':')
46 c = userpass.find(':')
47 if c == -1:
47 if c == -1:
48 user, passwd = urllib.unquote(userpass), None
48 user, passwd = urllib.unquote(userpass), None
49 else:
49 else:
50 user = urllib.unquote(userpass[:c])
50 user = urllib.unquote(userpass[:c])
51 passwd = urllib.unquote(userpass[c + 1:])
51 passwd = urllib.unquote(userpass[c + 1:])
52 c = netloc.find(':')
52 c = netloc.find(':')
53 if c == -1:
53 if c == -1:
54 host, port = netloc, None
54 host, port = netloc, None
55 else:
55 else:
56 host, port = netloc[:c], netloc[c + 1:]
56 host, port = netloc[:c], netloc[c + 1:]
57 return host, port, user, passwd
57 return host, port, user, passwd
58
58
59 def netlocunsplit(host, port, user=None, passwd=None):
59 def netlocunsplit(host, port, user=None, passwd=None):
60 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
60 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
61 if port:
61 if port:
62 hostport = host + ':' + port
62 hostport = host + ':' + port
63 else:
63 else:
64 hostport = host
64 hostport = host
65 if user:
65 if user:
66 quote = lambda s: urllib.quote(s, safe='')
66 quote = lambda s: urllib.quote(s, safe='')
67 if passwd:
67 if passwd:
68 userpass = quote(user) + ':' + quote(passwd)
68 userpass = quote(user) + ':' + quote(passwd)
69 else:
69 else:
70 userpass = quote(user)
70 userpass = quote(user)
71 return userpass + '@' + hostport
71 return userpass + '@' + hostport
72 return hostport
72 return hostport
73
73
74 _safe = ('abcdefghijklmnopqrstuvwxyz'
74 _safe = ('abcdefghijklmnopqrstuvwxyz'
75 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
75 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
76 '0123456789' '_.-/')
76 '0123456789' '_.-/')
77 _safeset = None
77 _safeset = None
78 _hex = None
78 _hex = None
79 def quotepath(path):
79 def quotepath(path):
80 '''quote the path part of a URL
80 '''quote the path part of a URL
81
81
82 This is similar to urllib.quote, but it also tries to avoid
82 This is similar to urllib.quote, but it also tries to avoid
83 quoting things twice (inspired by wget):
83 quoting things twice (inspired by wget):
84
84
85 >>> quotepath('abc def')
85 >>> quotepath('abc def')
86 'abc%20def'
86 'abc%20def'
87 >>> quotepath('abc%20def')
87 >>> quotepath('abc%20def')
88 'abc%20def'
88 'abc%20def'
89 >>> quotepath('abc%20 def')
89 >>> quotepath('abc%20 def')
90 'abc%20%20def'
90 'abc%20%20def'
91 >>> quotepath('abc def%20')
91 >>> quotepath('abc def%20')
92 'abc%20def%20'
92 'abc%20def%20'
93 >>> quotepath('abc def%2')
93 >>> quotepath('abc def%2')
94 'abc%20def%252'
94 'abc%20def%252'
95 >>> quotepath('abc def%')
95 >>> quotepath('abc def%')
96 'abc%20def%25'
96 'abc%20def%25'
97 '''
97 '''
98 global _safeset, _hex
98 global _safeset, _hex
99 if _safeset is None:
99 if _safeset is None:
100 _safeset = set(_safe)
100 _safeset = set(_safe)
101 _hex = set('abcdefABCDEF0123456789')
101 _hex = set('abcdefABCDEF0123456789')
102 l = list(path)
102 l = list(path)
103 for i in xrange(len(l)):
103 for i in xrange(len(l)):
104 c = l[i]
104 c = l[i]
105 if (c == '%' and i + 2 < len(l) and
105 if (c == '%' and i + 2 < len(l) and
106 l[i + 1] in _hex and l[i + 2] in _hex):
106 l[i + 1] in _hex and l[i + 2] in _hex):
107 pass
107 pass
108 elif c not in _safeset:
108 elif c not in _safeset:
109 l[i] = '%%%02X' % ord(c)
109 l[i] = '%%%02X' % ord(c)
110 return ''.join(l)
110 return ''.join(l)
111
111
112 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
112 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
113 def __init__(self, ui):
113 def __init__(self, ui):
114 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
114 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
115 self.ui = ui
115 self.ui = ui
116
116
117 def find_user_password(self, realm, authuri):
117 def find_user_password(self, realm, authuri):
118 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
118 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
119 self, realm, authuri)
119 self, realm, authuri)
120 user, passwd = authinfo
120 user, passwd = authinfo
121 if user and passwd:
121 if user and passwd:
122 self._writedebug(user, passwd)
122 self._writedebug(user, passwd)
123 return (user, passwd)
123 return (user, passwd)
124
124
125 if not user:
125 if not user:
126 auth = self.readauthtoken(authuri)
126 auth = self.readauthtoken(authuri)
127 if auth:
127 if auth:
128 user, passwd = auth.get('username'), auth.get('password')
128 user, passwd = auth.get('username'), auth.get('password')
129 if not user or not passwd:
129 if not user or not passwd:
130 if not self.ui.interactive():
130 if not self.ui.interactive():
131 raise util.Abort(_('http authorization required'))
131 raise util.Abort(_('http authorization required'))
132
132
133 self.ui.write(_("http authorization required\n"))
133 self.ui.write(_("http authorization required\n"))
134 self.ui.write(_("realm: %s\n") % realm)
134 self.ui.write(_("realm: %s\n") % realm)
135 if user:
135 if user:
136 self.ui.write(_("user: %s\n") % user)
136 self.ui.write(_("user: %s\n") % user)
137 else:
137 else:
138 user = self.ui.prompt(_("user:"), default=None)
138 user = self.ui.prompt(_("user:"), default=None)
139
139
140 if not passwd:
140 if not passwd:
141 passwd = self.ui.getpass()
141 passwd = self.ui.getpass()
142
142
143 self.add_password(realm, authuri, user, passwd)
143 self.add_password(realm, authuri, user, passwd)
144 self._writedebug(user, passwd)
144 self._writedebug(user, passwd)
145 return (user, passwd)
145 return (user, passwd)
146
146
147 def _writedebug(self, user, passwd):
147 def _writedebug(self, user, passwd):
148 msg = _('http auth: user %s, password %s\n')
148 msg = _('http auth: user %s, password %s\n')
149 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
149 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
150
150
151 def readauthtoken(self, uri):
151 def readauthtoken(self, uri):
152 # Read configuration
152 # Read configuration
153 config = dict()
153 config = dict()
154 for key, val in self.ui.configitems('auth'):
154 for key, val in self.ui.configitems('auth'):
155 if '.' not in key:
155 if '.' not in key:
156 self.ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
156 self.ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
157 continue
157 continue
158 group, setting = key.split('.', 1)
158 group, setting = key.split('.', 1)
159 gdict = config.setdefault(group, dict())
159 gdict = config.setdefault(group, dict())
160 if setting in ('username', 'cert', 'key'):
160 if setting in ('username', 'cert', 'key'):
161 val = util.expandpath(val)
161 val = util.expandpath(val)
162 gdict[setting] = val
162 gdict[setting] = val
163
163
164 # Find the best match
164 # Find the best match
165 scheme, hostpath = uri.split('://', 1)
165 scheme, hostpath = uri.split('://', 1)
166 bestlen = 0
166 bestlen = 0
167 bestauth = None
167 bestauth = None
168 for auth in config.itervalues():
168 for auth in config.itervalues():
169 prefix = auth.get('prefix')
169 prefix = auth.get('prefix')
170 if not prefix:
170 if not prefix:
171 continue
171 continue
172 p = prefix.split('://', 1)
172 p = prefix.split('://', 1)
173 if len(p) > 1:
173 if len(p) > 1:
174 schemes, prefix = [p[0]], p[1]
174 schemes, prefix = [p[0]], p[1]
175 else:
175 else:
176 schemes = (auth.get('schemes') or 'https').split()
176 schemes = (auth.get('schemes') or 'https').split()
177 if (prefix == '*' or hostpath.startswith(prefix)) and \
177 if (prefix == '*' or hostpath.startswith(prefix)) and \
178 len(prefix) > bestlen and scheme in schemes:
178 len(prefix) > bestlen and scheme in schemes:
179 bestlen = len(prefix)
179 bestlen = len(prefix)
180 bestauth = auth
180 bestauth = auth
181 return bestauth
181 return bestauth
182
182
183 class proxyhandler(urllib2.ProxyHandler):
183 class proxyhandler(urllib2.ProxyHandler):
184 def __init__(self, ui):
184 def __init__(self, ui):
185 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
185 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
186 # XXX proxyauthinfo = None
186 # XXX proxyauthinfo = None
187
187
188 if proxyurl:
188 if proxyurl:
189 # proxy can be proper url or host[:port]
189 # proxy can be proper url or host[:port]
190 if not (proxyurl.startswith('http:') or
190 if not (proxyurl.startswith('http:') or
191 proxyurl.startswith('https:')):
191 proxyurl.startswith('https:')):
192 proxyurl = 'http://' + proxyurl + '/'
192 proxyurl = 'http://' + proxyurl + '/'
193 snpqf = urlparse.urlsplit(proxyurl)
193 snpqf = urlparse.urlsplit(proxyurl)
194 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
194 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
195 hpup = netlocsplit(proxynetloc)
195 hpup = netlocsplit(proxynetloc)
196
196
197 proxyhost, proxyport, proxyuser, proxypasswd = hpup
197 proxyhost, proxyport, proxyuser, proxypasswd = hpup
198 if not proxyuser:
198 if not proxyuser:
199 proxyuser = ui.config("http_proxy", "user")
199 proxyuser = ui.config("http_proxy", "user")
200 proxypasswd = ui.config("http_proxy", "passwd")
200 proxypasswd = ui.config("http_proxy", "passwd")
201
201
202 # see if we should use a proxy for this url
202 # see if we should use a proxy for this url
203 no_list = ["localhost", "127.0.0.1"]
203 no_list = ["localhost", "127.0.0.1"]
204 no_list.extend([p.lower() for
204 no_list.extend([p.lower() for
205 p in ui.configlist("http_proxy", "no")])
205 p in ui.configlist("http_proxy", "no")])
206 no_list.extend([p.strip().lower() for
206 no_list.extend([p.strip().lower() for
207 p in os.getenv("no_proxy", '').split(',')
207 p in os.getenv("no_proxy", '').split(',')
208 if p.strip()])
208 if p.strip()])
209 # "http_proxy.always" config is for running tests on localhost
209 # "http_proxy.always" config is for running tests on localhost
210 if ui.configbool("http_proxy", "always"):
210 if ui.configbool("http_proxy", "always"):
211 self.no_list = []
211 self.no_list = []
212 else:
212 else:
213 self.no_list = no_list
213 self.no_list = no_list
214
214
215 proxyurl = urlparse.urlunsplit((
215 proxyurl = urlparse.urlunsplit((
216 proxyscheme, netlocunsplit(proxyhost, proxyport,
216 proxyscheme, netlocunsplit(proxyhost, proxyport,
217 proxyuser, proxypasswd or ''),
217 proxyuser, proxypasswd or ''),
218 proxypath, proxyquery, proxyfrag))
218 proxypath, proxyquery, proxyfrag))
219 proxies = {'http': proxyurl, 'https': proxyurl}
219 proxies = {'http': proxyurl, 'https': proxyurl}
220 ui.debug('proxying through http://%s:%s\n' %
220 ui.debug('proxying through http://%s:%s\n' %
221 (proxyhost, proxyport))
221 (proxyhost, proxyport))
222 else:
222 else:
223 proxies = {}
223 proxies = {}
224
224
225 # urllib2 takes proxy values from the environment and those
225 # urllib2 takes proxy values from the environment and those
226 # will take precedence if found, so drop them
226 # will take precedence if found, so drop them
227 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
227 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
228 try:
228 try:
229 if env in os.environ:
229 if env in os.environ:
230 del os.environ[env]
230 del os.environ[env]
231 except OSError:
231 except OSError:
232 pass
232 pass
233
233
234 urllib2.ProxyHandler.__init__(self, proxies)
234 urllib2.ProxyHandler.__init__(self, proxies)
235 self.ui = ui
235 self.ui = ui
236
236
237 def proxy_open(self, req, proxy, type_):
237 def proxy_open(self, req, proxy, type_):
238 host = req.get_host().split(':')[0]
238 host = req.get_host().split(':')[0]
239 if host in self.no_list:
239 if host in self.no_list:
240 return None
240 return None
241
241
242 # work around a bug in Python < 2.4.2
242 # work around a bug in Python < 2.4.2
243 # (it leaves a "\n" at the end of Proxy-authorization headers)
243 # (it leaves a "\n" at the end of Proxy-authorization headers)
244 baseclass = req.__class__
244 baseclass = req.__class__
245 class _request(baseclass):
245 class _request(baseclass):
246 def add_header(self, key, val):
246 def add_header(self, key, val):
247 if key.lower() == 'proxy-authorization':
247 if key.lower() == 'proxy-authorization':
248 val = val.strip()
248 val = val.strip()
249 return baseclass.add_header(self, key, val)
249 return baseclass.add_header(self, key, val)
250 req.__class__ = _request
250 req.__class__ = _request
251
251
252 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
252 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
253
253
254 class httpsendfile(object):
254 class httpsendfile(object):
255 """This is a wrapper around the objects returned by python's "open".
255 """This is a wrapper around the objects returned by python's "open".
256
256
257 Its purpose is to send file-like objects via HTTP and, to do so, it
257 Its purpose is to send file-like objects via HTTP and, to do so, it
258 defines a __len__ attribute to feed the Content-Length header.
258 defines a __len__ attribute to feed the Content-Length header.
259 """
259 """
260
260
261 def __init__(self, *args, **kwargs):
261 def __init__(self, ui, *args, **kwargs):
262 # We can't just "self._data = open(*args, **kwargs)" here because there
262 # We can't just "self._data = open(*args, **kwargs)" here because there
263 # is an "open" function defined in this module that shadows the global
263 # is an "open" function defined in this module that shadows the global
264 # one
264 # one
265 self.ui = ui
265 self._data = __builtin__.open(*args, **kwargs)
266 self._data = __builtin__.open(*args, **kwargs)
266 self.read = self._data.read
267 self.seek = self._data.seek
267 self.seek = self._data.seek
268 self.close = self._data.close
268 self.close = self._data.close
269 self.write = self._data.write
269 self.write = self._data.write
270 self._len = os.fstat(self._data.fileno()).st_size
271 self._pos = 0
272 self._total = len(self) / 1024 * 2
273
274 def read(self, *args, **kwargs):
275 try:
276 ret = self._data.read(*args, **kwargs)
277 except EOFError:
278 self.ui.progress(_('sending'), None)
279 self._pos += len(ret)
280 # We pass double the max for total because we currently have
281 # to send the bundle twice in the case of a server that
282 # requires authentication. Since we can't know until we try
283 # once whether authentication will be required, just lie to
284 # the user and maybe the push succeeds suddenly at 50%.
285 self.ui.progress(_('sending'), self._pos / 1024,
286 unit=_('kb'), total=self._total)
287 return ret
270
288
271 def __len__(self):
289 def __len__(self):
272 return os.fstat(self._data.fileno()).st_size
290 return self._len
273
291
274 def _gen_sendfile(connection):
292 def _gen_sendfile(connection):
275 def _sendfile(self, data):
293 def _sendfile(self, data):
276 # send a file
294 # send a file
277 if isinstance(data, httpsendfile):
295 if isinstance(data, httpsendfile):
278 # if auth required, some data sent twice, so rewind here
296 # if auth required, some data sent twice, so rewind here
279 data.seek(0)
297 data.seek(0)
280 for chunk in util.filechunkiter(data):
298 for chunk in util.filechunkiter(data):
281 connection.send(self, chunk)
299 connection.send(self, chunk)
282 else:
300 else:
283 connection.send(self, data)
301 connection.send(self, data)
284 return _sendfile
302 return _sendfile
285
303
286 has_https = hasattr(urllib2, 'HTTPSHandler')
304 has_https = hasattr(urllib2, 'HTTPSHandler')
287 if has_https:
305 if has_https:
288 try:
306 try:
289 # avoid using deprecated/broken FakeSocket in python 2.6
307 # avoid using deprecated/broken FakeSocket in python 2.6
290 import ssl
308 import ssl
291 _ssl_wrap_socket = ssl.wrap_socket
309 _ssl_wrap_socket = ssl.wrap_socket
292 CERT_REQUIRED = ssl.CERT_REQUIRED
310 CERT_REQUIRED = ssl.CERT_REQUIRED
293 except ImportError:
311 except ImportError:
294 CERT_REQUIRED = 2
312 CERT_REQUIRED = 2
295
313
296 def _ssl_wrap_socket(sock, key_file, cert_file,
314 def _ssl_wrap_socket(sock, key_file, cert_file,
297 cert_reqs=CERT_REQUIRED, ca_certs=None):
315 cert_reqs=CERT_REQUIRED, ca_certs=None):
298 if ca_certs:
316 if ca_certs:
299 raise util.Abort(_(
317 raise util.Abort(_(
300 'certificate checking requires Python 2.6'))
318 'certificate checking requires Python 2.6'))
301
319
302 ssl = socket.ssl(sock, key_file, cert_file)
320 ssl = socket.ssl(sock, key_file, cert_file)
303 return httplib.FakeSocket(sock, ssl)
321 return httplib.FakeSocket(sock, ssl)
304
322
305 try:
323 try:
306 _create_connection = socket.create_connection
324 _create_connection = socket.create_connection
307 except AttributeError:
325 except AttributeError:
308 _GLOBAL_DEFAULT_TIMEOUT = object()
326 _GLOBAL_DEFAULT_TIMEOUT = object()
309
327
310 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
328 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
311 source_address=None):
329 source_address=None):
312 # lifted from Python 2.6
330 # lifted from Python 2.6
313
331
314 msg = "getaddrinfo returns an empty list"
332 msg = "getaddrinfo returns an empty list"
315 host, port = address
333 host, port = address
316 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
334 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
317 af, socktype, proto, canonname, sa = res
335 af, socktype, proto, canonname, sa = res
318 sock = None
336 sock = None
319 try:
337 try:
320 sock = socket.socket(af, socktype, proto)
338 sock = socket.socket(af, socktype, proto)
321 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
339 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
322 sock.settimeout(timeout)
340 sock.settimeout(timeout)
323 if source_address:
341 if source_address:
324 sock.bind(source_address)
342 sock.bind(source_address)
325 sock.connect(sa)
343 sock.connect(sa)
326 return sock
344 return sock
327
345
328 except socket.error, msg:
346 except socket.error, msg:
329 if sock is not None:
347 if sock is not None:
330 sock.close()
348 sock.close()
331
349
332 raise socket.error, msg
350 raise socket.error, msg
333
351
334 class httpconnection(keepalive.HTTPConnection):
352 class httpconnection(keepalive.HTTPConnection):
335 # must be able to send big bundle as stream.
353 # must be able to send big bundle as stream.
336 send = _gen_sendfile(keepalive.HTTPConnection)
354 send = _gen_sendfile(keepalive.HTTPConnection)
337
355
338 def connect(self):
356 def connect(self):
339 if has_https and self.realhostport: # use CONNECT proxy
357 if has_https and self.realhostport: # use CONNECT proxy
340 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
358 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
341 self.sock.connect((self.host, self.port))
359 self.sock.connect((self.host, self.port))
342 if _generic_proxytunnel(self):
360 if _generic_proxytunnel(self):
343 # we do not support client x509 certificates
361 # we do not support client x509 certificates
344 self.sock = _ssl_wrap_socket(self.sock, None, None)
362 self.sock = _ssl_wrap_socket(self.sock, None, None)
345 else:
363 else:
346 keepalive.HTTPConnection.connect(self)
364 keepalive.HTTPConnection.connect(self)
347
365
348 def getresponse(self):
366 def getresponse(self):
349 proxyres = getattr(self, 'proxyres', None)
367 proxyres = getattr(self, 'proxyres', None)
350 if proxyres:
368 if proxyres:
351 if proxyres.will_close:
369 if proxyres.will_close:
352 self.close()
370 self.close()
353 self.proxyres = None
371 self.proxyres = None
354 return proxyres
372 return proxyres
355 return keepalive.HTTPConnection.getresponse(self)
373 return keepalive.HTTPConnection.getresponse(self)
356
374
357 # general transaction handler to support different ways to handle
375 # general transaction handler to support different ways to handle
358 # HTTPS proxying before and after Python 2.6.3.
376 # HTTPS proxying before and after Python 2.6.3.
359 def _generic_start_transaction(handler, h, req):
377 def _generic_start_transaction(handler, h, req):
360 if hasattr(req, '_tunnel_host') and req._tunnel_host:
378 if hasattr(req, '_tunnel_host') and req._tunnel_host:
361 tunnel_host = req._tunnel_host
379 tunnel_host = req._tunnel_host
362 if tunnel_host[:7] not in ['http://', 'https:/']:
380 if tunnel_host[:7] not in ['http://', 'https:/']:
363 tunnel_host = 'https://' + tunnel_host
381 tunnel_host = 'https://' + tunnel_host
364 new_tunnel = True
382 new_tunnel = True
365 else:
383 else:
366 tunnel_host = req.get_selector()
384 tunnel_host = req.get_selector()
367 new_tunnel = False
385 new_tunnel = False
368
386
369 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
387 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
370 urlparts = urlparse.urlparse(tunnel_host)
388 urlparts = urlparse.urlparse(tunnel_host)
371 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
389 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
372 realhostport = urlparts[1]
390 realhostport = urlparts[1]
373 if realhostport[-1] == ']' or ':' not in realhostport:
391 if realhostport[-1] == ']' or ':' not in realhostport:
374 realhostport += ':443'
392 realhostport += ':443'
375
393
376 h.realhostport = realhostport
394 h.realhostport = realhostport
377 h.headers = req.headers.copy()
395 h.headers = req.headers.copy()
378 h.headers.update(handler.parent.addheaders)
396 h.headers.update(handler.parent.addheaders)
379 return
397 return
380
398
381 h.realhostport = None
399 h.realhostport = None
382 h.headers = None
400 h.headers = None
383
401
384 def _generic_proxytunnel(self):
402 def _generic_proxytunnel(self):
385 proxyheaders = dict(
403 proxyheaders = dict(
386 [(x, self.headers[x]) for x in self.headers
404 [(x, self.headers[x]) for x in self.headers
387 if x.lower().startswith('proxy-')])
405 if x.lower().startswith('proxy-')])
388 self._set_hostport(self.host, self.port)
406 self._set_hostport(self.host, self.port)
389 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
407 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
390 for header in proxyheaders.iteritems():
408 for header in proxyheaders.iteritems():
391 self.send('%s: %s\r\n' % header)
409 self.send('%s: %s\r\n' % header)
392 self.send('\r\n')
410 self.send('\r\n')
393
411
394 # majority of the following code is duplicated from
412 # majority of the following code is duplicated from
395 # httplib.HTTPConnection as there are no adequate places to
413 # httplib.HTTPConnection as there are no adequate places to
396 # override functions to provide the needed functionality
414 # override functions to provide the needed functionality
397 res = self.response_class(self.sock,
415 res = self.response_class(self.sock,
398 strict=self.strict,
416 strict=self.strict,
399 method=self._method)
417 method=self._method)
400
418
401 while True:
419 while True:
402 version, status, reason = res._read_status()
420 version, status, reason = res._read_status()
403 if status != httplib.CONTINUE:
421 if status != httplib.CONTINUE:
404 break
422 break
405 while True:
423 while True:
406 skip = res.fp.readline().strip()
424 skip = res.fp.readline().strip()
407 if not skip:
425 if not skip:
408 break
426 break
409 res.status = status
427 res.status = status
410 res.reason = reason.strip()
428 res.reason = reason.strip()
411
429
412 if res.status == 200:
430 if res.status == 200:
413 while True:
431 while True:
414 line = res.fp.readline()
432 line = res.fp.readline()
415 if line == '\r\n':
433 if line == '\r\n':
416 break
434 break
417 return True
435 return True
418
436
419 if version == 'HTTP/1.0':
437 if version == 'HTTP/1.0':
420 res.version = 10
438 res.version = 10
421 elif version.startswith('HTTP/1.'):
439 elif version.startswith('HTTP/1.'):
422 res.version = 11
440 res.version = 11
423 elif version == 'HTTP/0.9':
441 elif version == 'HTTP/0.9':
424 res.version = 9
442 res.version = 9
425 else:
443 else:
426 raise httplib.UnknownProtocol(version)
444 raise httplib.UnknownProtocol(version)
427
445
428 if res.version == 9:
446 if res.version == 9:
429 res.length = None
447 res.length = None
430 res.chunked = 0
448 res.chunked = 0
431 res.will_close = 1
449 res.will_close = 1
432 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
450 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
433 return False
451 return False
434
452
435 res.msg = httplib.HTTPMessage(res.fp)
453 res.msg = httplib.HTTPMessage(res.fp)
436 res.msg.fp = None
454 res.msg.fp = None
437
455
438 # are we using the chunked-style of transfer encoding?
456 # are we using the chunked-style of transfer encoding?
439 trenc = res.msg.getheader('transfer-encoding')
457 trenc = res.msg.getheader('transfer-encoding')
440 if trenc and trenc.lower() == "chunked":
458 if trenc and trenc.lower() == "chunked":
441 res.chunked = 1
459 res.chunked = 1
442 res.chunk_left = None
460 res.chunk_left = None
443 else:
461 else:
444 res.chunked = 0
462 res.chunked = 0
445
463
446 # will the connection close at the end of the response?
464 # will the connection close at the end of the response?
447 res.will_close = res._check_close()
465 res.will_close = res._check_close()
448
466
449 # do we have a Content-Length?
467 # do we have a Content-Length?
450 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
468 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
451 length = res.msg.getheader('content-length')
469 length = res.msg.getheader('content-length')
452 if length and not res.chunked:
470 if length and not res.chunked:
453 try:
471 try:
454 res.length = int(length)
472 res.length = int(length)
455 except ValueError:
473 except ValueError:
456 res.length = None
474 res.length = None
457 else:
475 else:
458 if res.length < 0: # ignore nonsensical negative lengths
476 if res.length < 0: # ignore nonsensical negative lengths
459 res.length = None
477 res.length = None
460 else:
478 else:
461 res.length = None
479 res.length = None
462
480
463 # does the body have a fixed length? (of zero)
481 # does the body have a fixed length? (of zero)
464 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
482 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
465 100 <= status < 200 or # 1xx codes
483 100 <= status < 200 or # 1xx codes
466 res._method == 'HEAD'):
484 res._method == 'HEAD'):
467 res.length = 0
485 res.length = 0
468
486
469 # if the connection remains open, and we aren't using chunked, and
487 # if the connection remains open, and we aren't using chunked, and
470 # a content-length was not provided, then assume that the connection
488 # a content-length was not provided, then assume that the connection
471 # WILL close.
489 # WILL close.
472 if (not res.will_close and
490 if (not res.will_close and
473 not res.chunked and
491 not res.chunked and
474 res.length is None):
492 res.length is None):
475 res.will_close = 1
493 res.will_close = 1
476
494
477 self.proxyres = res
495 self.proxyres = res
478
496
479 return False
497 return False
480
498
481 class httphandler(keepalive.HTTPHandler):
499 class httphandler(keepalive.HTTPHandler):
482 def http_open(self, req):
500 def http_open(self, req):
483 return self.do_open(httpconnection, req)
501 return self.do_open(httpconnection, req)
484
502
485 def _start_transaction(self, h, req):
503 def _start_transaction(self, h, req):
486 _generic_start_transaction(self, h, req)
504 _generic_start_transaction(self, h, req)
487 return keepalive.HTTPHandler._start_transaction(self, h, req)
505 return keepalive.HTTPHandler._start_transaction(self, h, req)
488
506
489 def _verifycert(cert, hostname):
507 def _verifycert(cert, hostname):
490 '''Verify that cert (in socket.getpeercert() format) matches hostname.
508 '''Verify that cert (in socket.getpeercert() format) matches hostname.
491 CRLs and subjectAltName are not handled.
509 CRLs and subjectAltName are not handled.
492
510
493 Returns error message if any problems are found and None on success.
511 Returns error message if any problems are found and None on success.
494 '''
512 '''
495 if not cert:
513 if not cert:
496 return _('no certificate received')
514 return _('no certificate received')
497 dnsname = hostname.lower()
515 dnsname = hostname.lower()
498 for s in cert.get('subject', []):
516 for s in cert.get('subject', []):
499 key, value = s[0]
517 key, value = s[0]
500 if key == 'commonName':
518 if key == 'commonName':
501 certname = value.lower()
519 certname = value.lower()
502 if (certname == dnsname or
520 if (certname == dnsname or
503 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
521 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
504 return None
522 return None
505 return _('certificate is for %s') % certname
523 return _('certificate is for %s') % certname
506 return _('no commonName found in certificate')
524 return _('no commonName found in certificate')
507
525
508 if has_https:
526 if has_https:
509 class BetterHTTPS(httplib.HTTPSConnection):
527 class BetterHTTPS(httplib.HTTPSConnection):
510 send = keepalive.safesend
528 send = keepalive.safesend
511
529
512 def connect(self):
530 def connect(self):
513 if hasattr(self, 'ui'):
531 if hasattr(self, 'ui'):
514 cacerts = self.ui.config('web', 'cacerts')
532 cacerts = self.ui.config('web', 'cacerts')
515 else:
533 else:
516 cacerts = None
534 cacerts = None
517
535
518 if cacerts:
536 if cacerts:
519 sock = _create_connection((self.host, self.port))
537 sock = _create_connection((self.host, self.port))
520 self.sock = _ssl_wrap_socket(sock, self.key_file,
538 self.sock = _ssl_wrap_socket(sock, self.key_file,
521 self.cert_file, cert_reqs=CERT_REQUIRED,
539 self.cert_file, cert_reqs=CERT_REQUIRED,
522 ca_certs=cacerts)
540 ca_certs=cacerts)
523 msg = _verifycert(self.sock.getpeercert(), self.host)
541 msg = _verifycert(self.sock.getpeercert(), self.host)
524 if msg:
542 if msg:
525 raise util.Abort(_('%s certificate error: %s') %
543 raise util.Abort(_('%s certificate error: %s') %
526 (self.host, msg))
544 (self.host, msg))
527 self.ui.debug('%s certificate successfully verified\n' %
545 self.ui.debug('%s certificate successfully verified\n' %
528 self.host)
546 self.host)
529 else:
547 else:
530 httplib.HTTPSConnection.connect(self)
548 httplib.HTTPSConnection.connect(self)
531
549
532 class httpsconnection(BetterHTTPS):
550 class httpsconnection(BetterHTTPS):
533 response_class = keepalive.HTTPResponse
551 response_class = keepalive.HTTPResponse
534 # must be able to send big bundle as stream.
552 # must be able to send big bundle as stream.
535 send = _gen_sendfile(BetterHTTPS)
553 send = _gen_sendfile(BetterHTTPS)
536 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
554 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
537
555
538 def connect(self):
556 def connect(self):
539 if self.realhostport: # use CONNECT proxy
557 if self.realhostport: # use CONNECT proxy
540 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
558 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
541 self.sock.connect((self.host, self.port))
559 self.sock.connect((self.host, self.port))
542 if _generic_proxytunnel(self):
560 if _generic_proxytunnel(self):
543 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
561 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
544 self.cert_file)
562 self.cert_file)
545 else:
563 else:
546 BetterHTTPS.connect(self)
564 BetterHTTPS.connect(self)
547
565
548 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
566 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
549 def __init__(self, ui):
567 def __init__(self, ui):
550 keepalive.KeepAliveHandler.__init__(self)
568 keepalive.KeepAliveHandler.__init__(self)
551 urllib2.HTTPSHandler.__init__(self)
569 urllib2.HTTPSHandler.__init__(self)
552 self.ui = ui
570 self.ui = ui
553 self.pwmgr = passwordmgr(self.ui)
571 self.pwmgr = passwordmgr(self.ui)
554
572
555 def _start_transaction(self, h, req):
573 def _start_transaction(self, h, req):
556 _generic_start_transaction(self, h, req)
574 _generic_start_transaction(self, h, req)
557 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
575 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
558
576
559 def https_open(self, req):
577 def https_open(self, req):
560 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
578 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
561 return self.do_open(self._makeconnection, req)
579 return self.do_open(self._makeconnection, req)
562
580
563 def _makeconnection(self, host, port=None, *args, **kwargs):
581 def _makeconnection(self, host, port=None, *args, **kwargs):
564 keyfile = None
582 keyfile = None
565 certfile = None
583 certfile = None
566
584
567 if len(args) >= 1: # key_file
585 if len(args) >= 1: # key_file
568 keyfile = args[0]
586 keyfile = args[0]
569 if len(args) >= 2: # cert_file
587 if len(args) >= 2: # cert_file
570 certfile = args[1]
588 certfile = args[1]
571 args = args[2:]
589 args = args[2:]
572
590
573 # if the user has specified different key/cert files in
591 # if the user has specified different key/cert files in
574 # hgrc, we prefer these
592 # hgrc, we prefer these
575 if self.auth and 'key' in self.auth and 'cert' in self.auth:
593 if self.auth and 'key' in self.auth and 'cert' in self.auth:
576 keyfile = self.auth['key']
594 keyfile = self.auth['key']
577 certfile = self.auth['cert']
595 certfile = self.auth['cert']
578
596
579 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
597 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
580 conn.ui = self.ui
598 conn.ui = self.ui
581 return conn
599 return conn
582
600
583 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
601 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
584 def __init__(self, *args, **kwargs):
602 def __init__(self, *args, **kwargs):
585 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
603 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
586 self.retried_req = None
604 self.retried_req = None
587
605
588 def reset_retry_count(self):
606 def reset_retry_count(self):
589 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
607 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
590 # forever. We disable reset_retry_count completely and reset in
608 # forever. We disable reset_retry_count completely and reset in
591 # http_error_auth_reqed instead.
609 # http_error_auth_reqed instead.
592 pass
610 pass
593
611
594 def http_error_auth_reqed(self, auth_header, host, req, headers):
612 def http_error_auth_reqed(self, auth_header, host, req, headers):
595 # Reset the retry counter once for each request.
613 # Reset the retry counter once for each request.
596 if req is not self.retried_req:
614 if req is not self.retried_req:
597 self.retried_req = req
615 self.retried_req = req
598 self.retried = 0
616 self.retried = 0
599 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
617 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
600 # it doesn't know about the auth type requested. This can happen if
618 # it doesn't know about the auth type requested. This can happen if
601 # somebody is using BasicAuth and types a bad password.
619 # somebody is using BasicAuth and types a bad password.
602 try:
620 try:
603 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
621 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
604 self, auth_header, host, req, headers)
622 self, auth_header, host, req, headers)
605 except ValueError, inst:
623 except ValueError, inst:
606 arg = inst.args[0]
624 arg = inst.args[0]
607 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
625 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
608 return
626 return
609 raise
627 raise
610
628
611 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
629 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
612 def __init__(self, *args, **kwargs):
630 def __init__(self, *args, **kwargs):
613 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
631 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
614 self.retried_req = None
632 self.retried_req = None
615
633
616 def reset_retry_count(self):
634 def reset_retry_count(self):
617 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
635 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
618 # forever. We disable reset_retry_count completely and reset in
636 # forever. We disable reset_retry_count completely and reset in
619 # http_error_auth_reqed instead.
637 # http_error_auth_reqed instead.
620 pass
638 pass
621
639
622 def http_error_auth_reqed(self, auth_header, host, req, headers):
640 def http_error_auth_reqed(self, auth_header, host, req, headers):
623 # Reset the retry counter once for each request.
641 # Reset the retry counter once for each request.
624 if req is not self.retried_req:
642 if req is not self.retried_req:
625 self.retried_req = req
643 self.retried_req = req
626 self.retried = 0
644 self.retried = 0
627 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
645 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
628 self, auth_header, host, req, headers)
646 self, auth_header, host, req, headers)
629
647
630 def getauthinfo(path):
648 def getauthinfo(path):
631 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
649 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
632 if not urlpath:
650 if not urlpath:
633 urlpath = '/'
651 urlpath = '/'
634 if scheme != 'file':
652 if scheme != 'file':
635 # XXX: why are we quoting the path again with some smart
653 # XXX: why are we quoting the path again with some smart
636 # heuristic here? Anyway, it cannot be done with file://
654 # heuristic here? Anyway, it cannot be done with file://
637 # urls since path encoding is os/fs dependent (see
655 # urls since path encoding is os/fs dependent (see
638 # urllib.pathname2url() for details).
656 # urllib.pathname2url() for details).
639 urlpath = quotepath(urlpath)
657 urlpath = quotepath(urlpath)
640 host, port, user, passwd = netlocsplit(netloc)
658 host, port, user, passwd = netlocsplit(netloc)
641
659
642 # urllib cannot handle URLs with embedded user or passwd
660 # urllib cannot handle URLs with embedded user or passwd
643 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
661 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
644 urlpath, query, frag))
662 urlpath, query, frag))
645 if user:
663 if user:
646 netloc = host
664 netloc = host
647 if port:
665 if port:
648 netloc += ':' + port
666 netloc += ':' + port
649 # Python < 2.4.3 uses only the netloc to search for a password
667 # Python < 2.4.3 uses only the netloc to search for a password
650 authinfo = (None, (url, netloc), user, passwd or '')
668 authinfo = (None, (url, netloc), user, passwd or '')
651 else:
669 else:
652 authinfo = None
670 authinfo = None
653 return url, authinfo
671 return url, authinfo
654
672
655 handlerfuncs = []
673 handlerfuncs = []
656
674
657 def opener(ui, authinfo=None):
675 def opener(ui, authinfo=None):
658 '''
676 '''
659 construct an opener suitable for urllib2
677 construct an opener suitable for urllib2
660 authinfo will be added to the password manager
678 authinfo will be added to the password manager
661 '''
679 '''
662 handlers = [httphandler()]
680 handlers = [httphandler()]
663 if has_https:
681 if has_https:
664 handlers.append(httpshandler(ui))
682 handlers.append(httpshandler(ui))
665
683
666 handlers.append(proxyhandler(ui))
684 handlers.append(proxyhandler(ui))
667
685
668 passmgr = passwordmgr(ui)
686 passmgr = passwordmgr(ui)
669 if authinfo is not None:
687 if authinfo is not None:
670 passmgr.add_password(*authinfo)
688 passmgr.add_password(*authinfo)
671 user, passwd = authinfo[2:4]
689 user, passwd = authinfo[2:4]
672 ui.debug('http auth: user %s, password %s\n' %
690 ui.debug('http auth: user %s, password %s\n' %
673 (user, passwd and '*' * len(passwd) or 'not set'))
691 (user, passwd and '*' * len(passwd) or 'not set'))
674
692
675 handlers.extend((httpbasicauthhandler(passmgr),
693 handlers.extend((httpbasicauthhandler(passmgr),
676 httpdigestauthhandler(passmgr)))
694 httpdigestauthhandler(passmgr)))
677 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
695 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
678 opener = urllib2.build_opener(*handlers)
696 opener = urllib2.build_opener(*handlers)
679
697
680 # 1.0 here is the _protocol_ version
698 # 1.0 here is the _protocol_ version
681 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
699 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
682 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
700 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
683 return opener
701 return opener
684
702
685 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
703 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
686
704
687 def open(ui, url, data=None):
705 def open(ui, url, data=None):
688 scheme = None
706 scheme = None
689 m = scheme_re.search(url)
707 m = scheme_re.search(url)
690 if m:
708 if m:
691 scheme = m.group(1).lower()
709 scheme = m.group(1).lower()
692 if not scheme:
710 if not scheme:
693 path = util.normpath(os.path.abspath(url))
711 path = util.normpath(os.path.abspath(url))
694 url = 'file://' + urllib.pathname2url(path)
712 url = 'file://' + urllib.pathname2url(path)
695 authinfo = None
713 authinfo = None
696 else:
714 else:
697 url, authinfo = getauthinfo(url)
715 url, authinfo = getauthinfo(url)
698 return opener(ui, authinfo).open(url, data)
716 return opener(ui, authinfo).open(url, data)
General Comments 0
You need to be logged in to leave comments. Login now