Show More
@@ -0,0 +1,302 b'' | |||||
|
1 | # url.py - HTTP handling for mercurial | |||
|
2 | # | |||
|
3 | # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com> | |||
|
4 | # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> | |||
|
5 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> | |||
|
6 | # | |||
|
7 | # This software may be used and distributed according to the terms | |||
|
8 | # of the GNU General Public License, incorporated herein by reference. | |||
|
9 | ||||
|
10 | import urllib, urllib2, urlparse, httplib, os, re | |||
|
11 | from i18n import _ | |||
|
12 | import keepalive, util | |||
|
13 | ||||
|
14 | def hidepassword(url): | |||
|
15 | '''hide user credential in a url string''' | |||
|
16 | scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) | |||
|
17 | netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc) | |||
|
18 | return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) | |||
|
19 | ||||
|
20 | def removeauth(url): | |||
|
21 | '''remove all authentication information from a url string''' | |||
|
22 | scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) | |||
|
23 | netloc = netloc[netloc.find('@')+1:] | |||
|
24 | return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) | |||
|
25 | ||||
|
26 | def netlocsplit(netloc): | |||
|
27 | '''split [user[:passwd]@]host[:port] into 4-tuple.''' | |||
|
28 | ||||
|
29 | a = netloc.find('@') | |||
|
30 | if a == -1: | |||
|
31 | user, passwd = None, None | |||
|
32 | else: | |||
|
33 | userpass, netloc = netloc[:a], netloc[a+1:] | |||
|
34 | c = userpass.find(':') | |||
|
35 | if c == -1: | |||
|
36 | user, passwd = urllib.unquote(userpass), None | |||
|
37 | else: | |||
|
38 | user = urllib.unquote(userpass[:c]) | |||
|
39 | passwd = urllib.unquote(userpass[c+1:]) | |||
|
40 | c = netloc.find(':') | |||
|
41 | if c == -1: | |||
|
42 | host, port = netloc, None | |||
|
43 | else: | |||
|
44 | host, port = netloc[:c], netloc[c+1:] | |||
|
45 | return host, port, user, passwd | |||
|
46 | ||||
|
47 | def netlocunsplit(host, port, user=None, passwd=None): | |||
|
48 | '''turn host, port, user, passwd into [user[:passwd]@]host[:port].''' | |||
|
49 | if port: | |||
|
50 | hostport = host + ':' + port | |||
|
51 | else: | |||
|
52 | hostport = host | |||
|
53 | if user: | |||
|
54 | if passwd: | |||
|
55 | userpass = urllib.quote(user) + ':' + urllib.quote(passwd) | |||
|
56 | else: | |||
|
57 | userpass = urllib.quote(user) | |||
|
58 | return userpass + '@' + hostport | |||
|
59 | return hostport | |||
|
60 | ||||
|
61 | _safe = ('abcdefghijklmnopqrstuvwxyz' | |||
|
62 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | |||
|
63 | '0123456789' '_.-/') | |||
|
64 | _safeset = None | |||
|
65 | _hex = None | |||
|
66 | def quotepath(path): | |||
|
67 | '''quote the path part of a URL | |||
|
68 | ||||
|
69 | This is similar to urllib.quote, but it also tries to avoid | |||
|
70 | quoting things twice (inspired by wget): | |||
|
71 | ||||
|
72 | >>> quotepath('abc def') | |||
|
73 | 'abc%20def' | |||
|
74 | >>> quotepath('abc%20def') | |||
|
75 | 'abc%20def' | |||
|
76 | >>> quotepath('abc%20 def') | |||
|
77 | 'abc%20%20def' | |||
|
78 | >>> quotepath('abc def%20') | |||
|
79 | 'abc%20def%20' | |||
|
80 | >>> quotepath('abc def%2') | |||
|
81 | 'abc%20def%252' | |||
|
82 | >>> quotepath('abc def%') | |||
|
83 | 'abc%20def%25' | |||
|
84 | ''' | |||
|
85 | global _safeset, _hex | |||
|
86 | if _safeset is None: | |||
|
87 | _safeset = util.set(_safe) | |||
|
88 | _hex = util.set('abcdefABCDEF0123456789') | |||
|
89 | l = list(path) | |||
|
90 | for i in xrange(len(l)): | |||
|
91 | c = l[i] | |||
|
92 | if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex): | |||
|
93 | pass | |||
|
94 | elif c not in _safeset: | |||
|
95 | l[i] = '%%%02X' % ord(c) | |||
|
96 | return ''.join(l) | |||
|
97 | ||||
|
98 | class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): | |||
|
99 | def __init__(self, ui): | |||
|
100 | urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self) | |||
|
101 | self.ui = ui | |||
|
102 | ||||
|
103 | def find_user_password(self, realm, authuri): | |||
|
104 | authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( | |||
|
105 | self, realm, authuri) | |||
|
106 | user, passwd = authinfo | |||
|
107 | if user and passwd: | |||
|
108 | return (user, passwd) | |||
|
109 | ||||
|
110 | if not self.ui.interactive: | |||
|
111 | raise util.Abort(_('http authorization required')) | |||
|
112 | ||||
|
113 | self.ui.write(_("http authorization required\n")) | |||
|
114 | self.ui.status(_("realm: %s\n") % realm) | |||
|
115 | if user: | |||
|
116 | self.ui.status(_("user: %s\n") % user) | |||
|
117 | else: | |||
|
118 | user = self.ui.prompt(_("user:"), default=None) | |||
|
119 | ||||
|
120 | if not passwd: | |||
|
121 | passwd = self.ui.getpass() | |||
|
122 | ||||
|
123 | self.add_password(realm, authuri, user, passwd) | |||
|
124 | return (user, passwd) | |||
|
125 | ||||
|
126 | class proxyhandler(urllib2.ProxyHandler): | |||
|
127 | def __init__(self, ui): | |||
|
128 | proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy') | |||
|
129 | # XXX proxyauthinfo = None | |||
|
130 | ||||
|
131 | if proxyurl: | |||
|
132 | # proxy can be proper url or host[:port] | |||
|
133 | if not (proxyurl.startswith('http:') or | |||
|
134 | proxyurl.startswith('https:')): | |||
|
135 | proxyurl = 'http://' + proxyurl + '/' | |||
|
136 | snpqf = urlparse.urlsplit(proxyurl) | |||
|
137 | proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf | |||
|
138 | hpup = netlocsplit(proxynetloc) | |||
|
139 | ||||
|
140 | proxyhost, proxyport, proxyuser, proxypasswd = hpup | |||
|
141 | if not proxyuser: | |||
|
142 | proxyuser = ui.config("http_proxy", "user") | |||
|
143 | proxypasswd = ui.config("http_proxy", "passwd") | |||
|
144 | ||||
|
145 | # see if we should use a proxy for this url | |||
|
146 | no_list = [ "localhost", "127.0.0.1" ] | |||
|
147 | no_list.extend([p.lower() for | |||
|
148 | p in ui.configlist("http_proxy", "no")]) | |||
|
149 | no_list.extend([p.strip().lower() for | |||
|
150 | p in os.getenv("no_proxy", '').split(',') | |||
|
151 | if p.strip()]) | |||
|
152 | # "http_proxy.always" config is for running tests on localhost | |||
|
153 | if ui.configbool("http_proxy", "always"): | |||
|
154 | self.no_list = [] | |||
|
155 | else: | |||
|
156 | self.no_list = no_list | |||
|
157 | ||||
|
158 | proxyurl = urlparse.urlunsplit(( | |||
|
159 | proxyscheme, netlocunsplit(proxyhost, proxyport, | |||
|
160 | proxyuser, proxypasswd or ''), | |||
|
161 | proxypath, proxyquery, proxyfrag)) | |||
|
162 | proxies = {'http': proxyurl, 'https': proxyurl} | |||
|
163 | ui.debug(_('proxying through http://%s:%s\n') % | |||
|
164 | (proxyhost, proxyport)) | |||
|
165 | else: | |||
|
166 | proxies = {} | |||
|
167 | ||||
|
168 | # urllib2 takes proxy values from the environment and those | |||
|
169 | # will take precedence if found, so drop them | |||
|
170 | for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]: | |||
|
171 | try: | |||
|
172 | if env in os.environ: | |||
|
173 | del os.environ[env] | |||
|
174 | except OSError: | |||
|
175 | pass | |||
|
176 | ||||
|
177 | urllib2.ProxyHandler.__init__(self, proxies) | |||
|
178 | self.ui = ui | |||
|
179 | ||||
|
180 | def proxy_open(self, req, proxy, type_): | |||
|
181 | host = req.get_host().split(':')[0] | |||
|
182 | if host in self.no_list: | |||
|
183 | return None | |||
|
184 | ||||
|
185 | # work around a bug in Python < 2.4.2 | |||
|
186 | # (it leaves a "\n" at the end of Proxy-authorization headers) | |||
|
187 | baseclass = req.__class__ | |||
|
188 | class _request(baseclass): | |||
|
189 | def add_header(self, key, val): | |||
|
190 | if key.lower() == 'proxy-authorization': | |||
|
191 | val = val.strip() | |||
|
192 | return baseclass.add_header(self, key, val) | |||
|
193 | req.__class__ = _request | |||
|
194 | ||||
|
195 | return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_) | |||
|
196 | ||||
|
197 | class httpsendfile(file): | |||
|
198 | def __len__(self): | |||
|
199 | return os.fstat(self.fileno()).st_size | |||
|
200 | ||||
|
201 | def _gen_sendfile(connection): | |||
|
202 | def _sendfile(self, data): | |||
|
203 | # send a file | |||
|
204 | if isinstance(data, httpsendfile): | |||
|
205 | # if auth required, some data sent twice, so rewind here | |||
|
206 | data.seek(0) | |||
|
207 | for chunk in util.filechunkiter(data): | |||
|
208 | connection.send(self, chunk) | |||
|
209 | else: | |||
|
210 | connection.send(self, data) | |||
|
211 | return _sendfile | |||
|
212 | ||||
|
213 | class httpconnection(keepalive.HTTPConnection): | |||
|
214 | # must be able to send big bundle as stream. | |||
|
215 | send = _gen_sendfile(keepalive.HTTPConnection) | |||
|
216 | ||||
|
217 | class httphandler(keepalive.HTTPHandler): | |||
|
218 | def http_open(self, req): | |||
|
219 | return self.do_open(httpconnection, req) | |||
|
220 | ||||
|
221 | def __del__(self): | |||
|
222 | self.close_all() | |||
|
223 | ||||
|
224 | has_https = hasattr(urllib2, 'HTTPSHandler') | |||
|
225 | if has_https: | |||
|
226 | class httpsconnection(httplib.HTTPSConnection): | |||
|
227 | response_class = keepalive.HTTPResponse | |||
|
228 | # must be able to send big bundle as stream. | |||
|
229 | send = _gen_sendfile(httplib.HTTPSConnection) | |||
|
230 | ||||
|
231 | class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): | |||
|
232 | def https_open(self, req): | |||
|
233 | return self.do_open(httpsconnection, req) | |||
|
234 | ||||
|
235 | # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if | |||
|
236 | # it doesn't know about the auth type requested. This can happen if | |||
|
237 | # somebody is using BasicAuth and types a bad password. | |||
|
238 | class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler): | |||
|
239 | def http_error_auth_reqed(self, auth_header, host, req, headers): | |||
|
240 | try: | |||
|
241 | return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed( | |||
|
242 | self, auth_header, host, req, headers) | |||
|
243 | except ValueError, inst: | |||
|
244 | arg = inst.args[0] | |||
|
245 | if arg.startswith("AbstractDigestAuthHandler doesn't know "): | |||
|
246 | return | |||
|
247 | raise | |||
|
248 | ||||
|
249 | def getauthinfo(path): | |||
|
250 | scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path) | |||
|
251 | if not urlpath: | |||
|
252 | urlpath = '/' | |||
|
253 | urlpath = quotepath(urlpath) | |||
|
254 | host, port, user, passwd = netlocsplit(netloc) | |||
|
255 | ||||
|
256 | # urllib cannot handle URLs with embedded user or passwd | |||
|
257 | url = urlparse.urlunsplit((scheme, netlocunsplit(host, port), | |||
|
258 | urlpath, query, frag)) | |||
|
259 | if user: | |||
|
260 | netloc = host | |||
|
261 | if port: | |||
|
262 | netloc += ':' + port | |||
|
263 | # Python < 2.4.3 uses only the netloc to search for a password | |||
|
264 | authinfo = (None, (url, netloc), user, passwd or '') | |||
|
265 | else: | |||
|
266 | authinfo = None | |||
|
267 | return url, authinfo | |||
|
268 | ||||
|
269 | def opener(ui, authinfo=None): | |||
|
270 | ''' | |||
|
271 | construct an opener suitable for urllib2 | |||
|
272 | authinfo will be added to the password manager | |||
|
273 | ''' | |||
|
274 | handlers = [httphandler()] | |||
|
275 | if has_https: | |||
|
276 | handlers.append(httpshandler()) | |||
|
277 | ||||
|
278 | handlers.append(proxyhandler(ui)) | |||
|
279 | ||||
|
280 | passmgr = passwordmgr(ui) | |||
|
281 | if authinfo is not None: | |||
|
282 | passmgr.add_password(*authinfo) | |||
|
283 | user, passwd = authinfo[2:4] | |||
|
284 | ui.debug(_('http auth: user %s, password %s\n') % | |||
|
285 | (user, passwd and '*' * len(passwd) or 'not set')) | |||
|
286 | ||||
|
287 | handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr), | |||
|
288 | httpdigestauthhandler(passmgr))) | |||
|
289 | opener = urllib2.build_opener(*handlers) | |||
|
290 | ||||
|
291 | # 1.0 here is the _protocol_ version | |||
|
292 | opener.addheaders = [('User-agent', 'mercurial/proto-1.0')] | |||
|
293 | opener.addheaders.append(('Accept', 'application/mercurial-0.1')) | |||
|
294 | return opener | |||
|
295 | ||||
|
296 | def open(ui, url, data=None): | |||
|
297 | scheme = urlparse.urlsplit(url)[0] | |||
|
298 | if not scheme: | |||
|
299 | url, authinfo = 'file://' + util.normpath(os.path.abspath(url)), None | |||
|
300 | else: | |||
|
301 | url, authinfo = getauthinfo(url) | |||
|
302 | return opener(ui, authinfo).open(url, data) |
@@ -8,7 +8,7 b'' | |||||
8 |
|
8 | |||
9 | from mercurial.i18n import _ |
|
9 | from mercurial.i18n import _ | |
10 | from mercurial.node import nullid, short |
|
10 | from mercurial.node import nullid, short | |
11 | from mercurial import commands, cmdutil, hg, util |
|
11 | from mercurial import commands, cmdutil, hg, util, url | |
12 |
|
12 | |||
13 | def fetch(ui, repo, source='default', **opts): |
|
13 | def fetch(ui, repo, source='default', **opts): | |
14 | '''Pull changes from a remote repository, merge new changes if needed. |
|
14 | '''Pull changes from a remote repository, merge new changes if needed. | |
@@ -60,7 +60,7 b" def fetch(ui, repo, source='default', **" | |||||
60 |
|
60 | |||
61 | other = hg.repository(ui, ui.expandpath(source)) |
|
61 | other = hg.repository(ui, ui.expandpath(source)) | |
62 | ui.status(_('pulling from %s\n') % |
|
62 | ui.status(_('pulling from %s\n') % | |
63 |
u |
|
63 | url.hidepassword(ui.expandpath(source))) | |
64 | revs = None |
|
64 | revs = None | |
65 | if opts['rev']: |
|
65 | if opts['rev']: | |
66 | if not other.local(): |
|
66 | if not other.local(): | |
@@ -118,7 +118,7 b" def fetch(ui, repo, source='default', **" | |||||
118 | mod, add, rem = repo.status()[:3] |
|
118 | mod, add, rem = repo.status()[:3] | |
119 | message = (cmdutil.logmessage(opts) or |
|
119 | message = (cmdutil.logmessage(opts) or | |
120 | (_('Automated merge with %s') % |
|
120 | (_('Automated merge with %s') % | |
121 |
u |
|
121 | url.removeauth(other.url()))) | |
122 | force_editor = opts.get('force_editor') or opts.get('edit') |
|
122 | force_editor = opts.get('force_editor') or opts.get('edit') | |
123 | n = repo.commit(mod + add + rem, message, |
|
123 | n = repo.commit(mod + add + rem, message, | |
124 | opts['user'], opts['date'], force=True, |
|
124 | opts['user'], opts['date'], force=True, |
@@ -10,7 +10,7 b' from repo import RepoError, NoCapability' | |||||
10 | from i18n import _, gettext |
|
10 | from i18n import _, gettext | |
11 | import os, re, sys, urllib |
|
11 | import os, re, sys, urllib | |
12 | import hg, util, revlog, bundlerepo, extensions, copies |
|
12 | import hg, util, revlog, bundlerepo, extensions, copies | |
13 | import difflib, patch, time, help, mdiff, tempfile |
|
13 | import difflib, patch, time, help, mdiff, tempfile, url | |
14 | import version, socket |
|
14 | import version, socket | |
15 | import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect |
|
15 | import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect | |
16 | import merge as merge_ |
|
16 | import merge as merge_ | |
@@ -1675,7 +1675,7 b' def incoming(ui, repo, source="default",' | |||||
1675 | cmdutil.setremoteconfig(ui, opts) |
|
1675 | cmdutil.setremoteconfig(ui, opts) | |
1676 |
|
1676 | |||
1677 | other = hg.repository(ui, source) |
|
1677 | other = hg.repository(ui, source) | |
1678 |
ui.status(_('comparing with %s\n') % u |
|
1678 | ui.status(_('comparing with %s\n') % url.hidepassword(source)) | |
1679 | if revs: |
|
1679 | if revs: | |
1680 | revs = [other.lookup(rev) for rev in revs] |
|
1680 | revs = [other.lookup(rev) for rev in revs] | |
1681 | incoming = repo.findincoming(other, heads=revs, force=opts["force"]) |
|
1681 | incoming = repo.findincoming(other, heads=revs, force=opts["force"]) | |
@@ -1997,7 +1997,7 b' def outgoing(ui, repo, dest=None, **opts' | |||||
1997 | revs = [repo.lookup(rev) for rev in revs] |
|
1997 | revs = [repo.lookup(rev) for rev in revs] | |
1998 |
|
1998 | |||
1999 | other = hg.repository(ui, dest) |
|
1999 | other = hg.repository(ui, dest) | |
2000 |
ui.status(_('comparing with %s\n') % u |
|
2000 | ui.status(_('comparing with %s\n') % url.hidepassword(dest)) | |
2001 | o = repo.findoutgoing(other, force=opts.get('force')) |
|
2001 | o = repo.findoutgoing(other, force=opts.get('force')) | |
2002 | if not o: |
|
2002 | if not o: | |
2003 | ui.status(_("no changes found\n")) |
|
2003 | ui.status(_("no changes found\n")) | |
@@ -2068,13 +2068,13 b' def paths(ui, repo, search=None):' | |||||
2068 | if search: |
|
2068 | if search: | |
2069 | for name, path in ui.configitems("paths"): |
|
2069 | for name, path in ui.configitems("paths"): | |
2070 | if name == search: |
|
2070 | if name == search: | |
2071 |
ui.write("%s\n" % u |
|
2071 | ui.write("%s\n" % url.hidepassword(path)) | |
2072 | return |
|
2072 | return | |
2073 | ui.warn(_("not found!\n")) |
|
2073 | ui.warn(_("not found!\n")) | |
2074 | return 1 |
|
2074 | return 1 | |
2075 | else: |
|
2075 | else: | |
2076 | for name, path in ui.configitems("paths"): |
|
2076 | for name, path in ui.configitems("paths"): | |
2077 |
ui.write("%s = %s\n" % (name, u |
|
2077 | ui.write("%s = %s\n" % (name, url.hidepassword(path))) | |
2078 |
|
2078 | |||
2079 | def postincoming(ui, repo, modheads, optupdate, checkout): |
|
2079 | def postincoming(ui, repo, modheads, optupdate, checkout): | |
2080 | if modheads == 0: |
|
2080 | if modheads == 0: | |
@@ -2131,7 +2131,7 b' def pull(ui, repo, source="default", **o' | |||||
2131 | cmdutil.setremoteconfig(ui, opts) |
|
2131 | cmdutil.setremoteconfig(ui, opts) | |
2132 |
|
2132 | |||
2133 | other = hg.repository(ui, source) |
|
2133 | other = hg.repository(ui, source) | |
2134 |
ui.status(_('pulling from %s\n') % u |
|
2134 | ui.status(_('pulling from %s\n') % url.hidepassword(source)) | |
2135 | if revs: |
|
2135 | if revs: | |
2136 | try: |
|
2136 | try: | |
2137 | revs = [other.lookup(rev) for rev in revs] |
|
2137 | revs = [other.lookup(rev) for rev in revs] | |
@@ -2179,7 +2179,7 b' def push(ui, repo, dest=None, **opts):' | |||||
2179 | cmdutil.setremoteconfig(ui, opts) |
|
2179 | cmdutil.setremoteconfig(ui, opts) | |
2180 |
|
2180 | |||
2181 | other = hg.repository(ui, dest) |
|
2181 | other = hg.repository(ui, dest) | |
2182 |
ui.status(_('pushing to %s\n') % u |
|
2182 | ui.status(_('pushing to %s\n') % url.hidepassword(dest)) | |
2183 | if revs: |
|
2183 | if revs: | |
2184 | revs = [repo.lookup(rev) for rev in revs] |
|
2184 | revs = [repo.lookup(rev) for rev in revs] | |
2185 | r = repo.push(other, opts.get('force'), revs=revs) |
|
2185 | r = repo.push(other, opts.get('force'), revs=revs) |
@@ -10,189 +10,7 b' from node import bin, hex, nullid' | |||||
10 | from i18n import _ |
|
10 | from i18n import _ | |
11 | import repo, os, urllib, urllib2, urlparse, zlib, util, httplib |
|
11 | import repo, os, urllib, urllib2, urlparse, zlib, util, httplib | |
12 | import errno, keepalive, socket, changegroup, statichttprepo |
|
12 | import errno, keepalive, socket, changegroup, statichttprepo | |
13 |
|
13 | import url | ||
14 | class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): |
|
|||
15 | def __init__(self, ui): |
|
|||
16 | urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self) |
|
|||
17 | self.ui = ui |
|
|||
18 |
|
||||
19 | def find_user_password(self, realm, authuri): |
|
|||
20 | authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( |
|
|||
21 | self, realm, authuri) |
|
|||
22 | user, passwd = authinfo |
|
|||
23 | if user and passwd: |
|
|||
24 | return (user, passwd) |
|
|||
25 |
|
||||
26 | if not self.ui.interactive: |
|
|||
27 | raise util.Abort(_('http authorization required')) |
|
|||
28 |
|
||||
29 | self.ui.write(_("http authorization required\n")) |
|
|||
30 | self.ui.status(_("realm: %s\n") % realm) |
|
|||
31 | if user: |
|
|||
32 | self.ui.status(_("user: %s\n") % user) |
|
|||
33 | else: |
|
|||
34 | user = self.ui.prompt(_("user:"), default=None) |
|
|||
35 |
|
||||
36 | if not passwd: |
|
|||
37 | passwd = self.ui.getpass() |
|
|||
38 |
|
||||
39 | self.add_password(realm, authuri, user, passwd) |
|
|||
40 | return (user, passwd) |
|
|||
41 |
|
||||
42 | class proxyhandler(urllib2.ProxyHandler): |
|
|||
43 | def __init__(self, ui): |
|
|||
44 | proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy') |
|
|||
45 | # XXX proxyauthinfo = None |
|
|||
46 |
|
||||
47 | if proxyurl: |
|
|||
48 | # proxy can be proper url or host[:port] |
|
|||
49 | if not (proxyurl.startswith('http:') or |
|
|||
50 | proxyurl.startswith('https:')): |
|
|||
51 | proxyurl = 'http://' + proxyurl + '/' |
|
|||
52 | snpqf = urlparse.urlsplit(proxyurl) |
|
|||
53 | proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf |
|
|||
54 | hpup = netlocsplit(proxynetloc) |
|
|||
55 |
|
||||
56 | proxyhost, proxyport, proxyuser, proxypasswd = hpup |
|
|||
57 | if not proxyuser: |
|
|||
58 | proxyuser = ui.config("http_proxy", "user") |
|
|||
59 | proxypasswd = ui.config("http_proxy", "passwd") |
|
|||
60 |
|
||||
61 | # see if we should use a proxy for this url |
|
|||
62 | no_list = [ "localhost", "127.0.0.1" ] |
|
|||
63 | no_list.extend([p.lower() for |
|
|||
64 | p in ui.configlist("http_proxy", "no")]) |
|
|||
65 | no_list.extend([p.strip().lower() for |
|
|||
66 | p in os.getenv("no_proxy", '').split(',') |
|
|||
67 | if p.strip()]) |
|
|||
68 | # "http_proxy.always" config is for running tests on localhost |
|
|||
69 | if ui.configbool("http_proxy", "always"): |
|
|||
70 | self.no_list = [] |
|
|||
71 | else: |
|
|||
72 | self.no_list = no_list |
|
|||
73 |
|
||||
74 | proxyurl = urlparse.urlunsplit(( |
|
|||
75 | proxyscheme, netlocunsplit(proxyhost, proxyport, |
|
|||
76 | proxyuser, proxypasswd or ''), |
|
|||
77 | proxypath, proxyquery, proxyfrag)) |
|
|||
78 | proxies = {'http': proxyurl, 'https': proxyurl} |
|
|||
79 | ui.debug(_('proxying through http://%s:%s\n') % |
|
|||
80 | (proxyhost, proxyport)) |
|
|||
81 | else: |
|
|||
82 | proxies = {} |
|
|||
83 |
|
||||
84 | # urllib2 takes proxy values from the environment and those |
|
|||
85 | # will take precedence if found, so drop them |
|
|||
86 | for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]: |
|
|||
87 | try: |
|
|||
88 | if env in os.environ: |
|
|||
89 | del os.environ[env] |
|
|||
90 | except OSError: |
|
|||
91 | pass |
|
|||
92 |
|
||||
93 | urllib2.ProxyHandler.__init__(self, proxies) |
|
|||
94 | self.ui = ui |
|
|||
95 |
|
||||
96 | def proxy_open(self, req, proxy, type): |
|
|||
97 | host = req.get_host().split(':')[0] |
|
|||
98 | if host in self.no_list: |
|
|||
99 | return None |
|
|||
100 | return urllib2.ProxyHandler.proxy_open(self, req, proxy, type) |
|
|||
101 |
|
||||
102 | def netlocsplit(netloc): |
|
|||
103 | '''split [user[:passwd]@]host[:port] into 4-tuple.''' |
|
|||
104 |
|
||||
105 | a = netloc.find('@') |
|
|||
106 | if a == -1: |
|
|||
107 | user, passwd = None, None |
|
|||
108 | else: |
|
|||
109 | userpass, netloc = netloc[:a], netloc[a+1:] |
|
|||
110 | c = userpass.find(':') |
|
|||
111 | if c == -1: |
|
|||
112 | user, passwd = urllib.unquote(userpass), None |
|
|||
113 | else: |
|
|||
114 | user = urllib.unquote(userpass[:c]) |
|
|||
115 | passwd = urllib.unquote(userpass[c+1:]) |
|
|||
116 | c = netloc.find(':') |
|
|||
117 | if c == -1: |
|
|||
118 | host, port = netloc, None |
|
|||
119 | else: |
|
|||
120 | host, port = netloc[:c], netloc[c+1:] |
|
|||
121 | return host, port, user, passwd |
|
|||
122 |
|
||||
123 | def netlocunsplit(host, port, user=None, passwd=None): |
|
|||
124 | '''turn host, port, user, passwd into [user[:passwd]@]host[:port].''' |
|
|||
125 | if port: |
|
|||
126 | hostport = host + ':' + port |
|
|||
127 | else: |
|
|||
128 | hostport = host |
|
|||
129 | if user: |
|
|||
130 | if passwd: |
|
|||
131 | userpass = urllib.quote(user) + ':' + urllib.quote(passwd) |
|
|||
132 | else: |
|
|||
133 | userpass = urllib.quote(user) |
|
|||
134 | return userpass + '@' + hostport |
|
|||
135 | return hostport |
|
|||
136 |
|
||||
137 | # work around a bug in Python < 2.4.2 |
|
|||
138 | # (it leaves a "\n" at the end of Proxy-authorization headers) |
|
|||
139 | class request(urllib2.Request): |
|
|||
140 | def add_header(self, key, val): |
|
|||
141 | if key.lower() == 'proxy-authorization': |
|
|||
142 | val = val.strip() |
|
|||
143 | return urllib2.Request.add_header(self, key, val) |
|
|||
144 |
|
||||
145 | class httpsendfile(file): |
|
|||
146 | def __len__(self): |
|
|||
147 | return os.fstat(self.fileno()).st_size |
|
|||
148 |
|
||||
149 | def _gen_sendfile(connection): |
|
|||
150 | def _sendfile(self, data): |
|
|||
151 | # send a file |
|
|||
152 | if isinstance(data, httpsendfile): |
|
|||
153 | # if auth required, some data sent twice, so rewind here |
|
|||
154 | data.seek(0) |
|
|||
155 | for chunk in util.filechunkiter(data): |
|
|||
156 | connection.send(self, chunk) |
|
|||
157 | else: |
|
|||
158 | connection.send(self, data) |
|
|||
159 | return _sendfile |
|
|||
160 |
|
||||
161 | class httpconnection(keepalive.HTTPConnection): |
|
|||
162 | # must be able to send big bundle as stream. |
|
|||
163 | send = _gen_sendfile(keepalive.HTTPConnection) |
|
|||
164 |
|
||||
165 | class httphandler(keepalive.HTTPHandler): |
|
|||
166 | def http_open(self, req): |
|
|||
167 | return self.do_open(httpconnection, req) |
|
|||
168 |
|
||||
169 | def __del__(self): |
|
|||
170 | self.close_all() |
|
|||
171 |
|
||||
172 | has_https = hasattr(urllib2, 'HTTPSHandler') |
|
|||
173 | if has_https: |
|
|||
174 | class httpsconnection(httplib.HTTPSConnection): |
|
|||
175 | response_class = keepalive.HTTPResponse |
|
|||
176 | # must be able to send big bundle as stream. |
|
|||
177 | send = _gen_sendfile(httplib.HTTPSConnection) |
|
|||
178 |
|
||||
179 | class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): |
|
|||
180 | def https_open(self, req): |
|
|||
181 | return self.do_open(httpsconnection, req) |
|
|||
182 |
|
||||
183 | # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if |
|
|||
184 | # it doesn't know about the auth type requested. This can happen if |
|
|||
185 | # somebody is using BasicAuth and types a bad password. |
|
|||
186 | class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler): |
|
|||
187 | def http_error_auth_reqed(self, auth_header, host, req, headers): |
|
|||
188 | try: |
|
|||
189 | return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed( |
|
|||
190 | self, auth_header, host, req, headers) |
|
|||
191 | except ValueError, inst: |
|
|||
192 | arg = inst.args[0] |
|
|||
193 | if arg.startswith("AbstractDigestAuthHandler doesn't know "): |
|
|||
194 | return |
|
|||
195 | raise |
|
|||
196 |
|
14 | |||
197 | def zgenerator(f): |
|
15 | def zgenerator(f): | |
198 | zd = zlib.decompressobj() |
|
16 | zd = zlib.decompressobj() | |
@@ -203,43 +21,6 b' def zgenerator(f):' | |||||
203 | raise IOError(None, _('connection ended unexpectedly')) |
|
21 | raise IOError(None, _('connection ended unexpectedly')) | |
204 | yield zd.flush() |
|
22 | yield zd.flush() | |
205 |
|
23 | |||
206 | _safe = ('abcdefghijklmnopqrstuvwxyz' |
|
|||
207 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' |
|
|||
208 | '0123456789' '_.-/') |
|
|||
209 | _safeset = None |
|
|||
210 | _hex = None |
|
|||
211 | def quotepath(path): |
|
|||
212 | '''quote the path part of a URL |
|
|||
213 |
|
||||
214 | This is similar to urllib.quote, but it also tries to avoid |
|
|||
215 | quoting things twice (inspired by wget): |
|
|||
216 |
|
||||
217 | >>> quotepath('abc def') |
|
|||
218 | 'abc%20def' |
|
|||
219 | >>> quotepath('abc%20def') |
|
|||
220 | 'abc%20def' |
|
|||
221 | >>> quotepath('abc%20 def') |
|
|||
222 | 'abc%20%20def' |
|
|||
223 | >>> quotepath('abc def%20') |
|
|||
224 | 'abc%20def%20' |
|
|||
225 | >>> quotepath('abc def%2') |
|
|||
226 | 'abc%20def%252' |
|
|||
227 | >>> quotepath('abc def%') |
|
|||
228 | 'abc%20def%25' |
|
|||
229 | ''' |
|
|||
230 | global _safeset, _hex |
|
|||
231 | if _safeset is None: |
|
|||
232 | _safeset = util.set(_safe) |
|
|||
233 | _hex = util.set('abcdefABCDEF0123456789') |
|
|||
234 | l = list(path) |
|
|||
235 | for i in xrange(len(l)): |
|
|||
236 | c = l[i] |
|
|||
237 | if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex): |
|
|||
238 | pass |
|
|||
239 | elif c not in _safeset: |
|
|||
240 | l[i] = '%%%02X' % ord(c) |
|
|||
241 | return ''.join(l) |
|
|||
242 |
|
||||
243 | class httprepository(repo.repository): |
|
24 | class httprepository(repo.repository): | |
244 | def __init__(self, ui, path): |
|
25 | def __init__(self, ui, path): | |
245 | self.path = path |
|
26 | self.path = path | |
@@ -249,41 +30,14 b' class httprepository(repo.repository):' | |||||
249 | if query or frag: |
|
30 | if query or frag: | |
250 | raise util.Abort(_('unsupported URL component: "%s"') % |
|
31 | raise util.Abort(_('unsupported URL component: "%s"') % | |
251 | (query or frag)) |
|
32 | (query or frag)) | |
252 | if not urlpath: |
|
|||
253 | urlpath = '/' |
|
|||
254 | urlpath = quotepath(urlpath) |
|
|||
255 | host, port, user, passwd = netlocsplit(netloc) |
|
|||
256 |
|
33 | |||
257 | # urllib cannot handle URLs with embedded user or passwd |
|
34 | # urllib cannot handle URLs with embedded user or passwd | |
258 | self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port), |
|
35 | self._url, authinfo = url.getauthinfo(path) | |
259 | urlpath, '', '')) |
|
36 | ||
260 | self.ui = ui |
|
37 | self.ui = ui | |
261 | self.ui.debug(_('using %s\n') % self._url) |
|
38 | self.ui.debug(_('using %s\n') % self._url) | |
262 |
|
39 | |||
263 | handlers = [httphandler()] |
|
40 | self.urlopener = url.opener(ui, authinfo) | |
264 | if has_https: |
|
|||
265 | handlers.append(httpshandler()) |
|
|||
266 |
|
||||
267 | handlers.append(proxyhandler(ui)) |
|
|||
268 |
|
||||
269 | passmgr = passwordmgr(ui) |
|
|||
270 | if user: |
|
|||
271 | ui.debug(_('http auth: user %s, password %s\n') % |
|
|||
272 | (user, passwd and '*' * len(passwd) or 'not set')) |
|
|||
273 | netloc = host |
|
|||
274 | if port: |
|
|||
275 | netloc += ':' + port |
|
|||
276 | # Python < 2.4.3 uses only the netloc to search for a password |
|
|||
277 | passmgr.add_password(None, (self._url, netloc), user, passwd or '') |
|
|||
278 |
|
||||
279 | handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr), |
|
|||
280 | httpdigestauthhandler(passmgr))) |
|
|||
281 | opener = urllib2.build_opener(*handlers) |
|
|||
282 |
|
||||
283 | # 1.0 here is the _protocol_ version |
|
|||
284 | opener.addheaders = [('User-agent', 'mercurial/proto-1.0')] |
|
|||
285 | opener.addheaders.append(('Accept', 'application/mercurial-0.1')) |
|
|||
286 | urllib2.install_opener(opener) |
|
|||
287 |
|
41 | |||
288 | def url(self): |
|
42 | def url(self): | |
289 | return self.path |
|
43 | return self.path | |
@@ -316,7 +70,7 b' class httprepository(repo.repository):' | |||||
316 | try: |
|
70 | try: | |
317 | if data: |
|
71 | if data: | |
318 | self.ui.debug(_("sending %s bytes\n") % len(data)) |
|
72 | self.ui.debug(_("sending %s bytes\n") % len(data)) | |
319 |
resp = urllib2. |
|
73 | resp = self.urlopener.open(urllib2.Request(cu, data, headers)) | |
320 | except urllib2.HTTPError, inst: |
|
74 | except urllib2.HTTPError, inst: | |
321 | if inst.code == 401: |
|
75 | if inst.code == 401: | |
322 | raise util.Abort(_('authorization failed')) |
|
76 | raise util.Abort(_('authorization failed')) | |
@@ -433,7 +187,7 b' class httprepository(repo.repository):' | |||||
433 | break |
|
187 | break | |
434 |
|
188 | |||
435 | tempname = changegroup.writebundle(cg, None, type) |
|
189 | tempname = changegroup.writebundle(cg, None, type) | |
436 | fp = httpsendfile(tempname, "rb") |
|
190 | fp = url.httpsendfile(tempname, "rb") | |
437 | try: |
|
191 | try: | |
438 | try: |
|
192 | try: | |
439 | resp = self.do_read( |
|
193 | resp = self.do_read( |
@@ -15,7 +15,7 b' platform-specific details from the core.' | |||||
15 | from i18n import _ |
|
15 | from i18n import _ | |
16 | import cStringIO, errno, getpass, re, shutil, sys, tempfile |
|
16 | import cStringIO, errno, getpass, re, shutil, sys, tempfile | |
17 | import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil |
|
17 | import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil | |
18 |
import imp |
|
18 | import imp | |
19 |
|
19 | |||
20 | # Python compatibility |
|
20 | # Python compatibility | |
21 |
|
21 | |||
@@ -1932,15 +1932,3 b' def drop_scheme(scheme, path):' | |||||
1932 | def uirepr(s): |
|
1932 | def uirepr(s): | |
1933 | # Avoid double backslash in Windows path repr() |
|
1933 | # Avoid double backslash in Windows path repr() | |
1934 | return repr(s).replace('\\\\', '\\') |
|
1934 | return repr(s).replace('\\\\', '\\') | |
1935 |
|
||||
1936 | def hidepassword(url): |
|
|||
1937 | '''hide user credential in a url string''' |
|
|||
1938 | scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) |
|
|||
1939 | netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc) |
|
|||
1940 | return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) |
|
|||
1941 |
|
||||
1942 | def removeauth(url): |
|
|||
1943 | '''remove all authentication information from a url string''' |
|
|||
1944 | scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) |
|
|||
1945 | netloc = netloc[netloc.find('@')+1:] |
|
|||
1946 | return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) |
|
General Comments 0
You need to be logged in to leave comments.
Login now