8 |
8 |
9 from node import bin, hex, nullid |
9 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() |
199 try: |
17 try: |
200 for chunk in util.filechunkiter(f): |
18 for chunk in util.filechunkiter(f): |
201 yield zd.decompress(chunk) |
19 yield zd.decompress(chunk) |
202 except httplib.HTTPException, inst: |
20 except httplib.HTTPException, inst: |
203 raise IOError(None, _('connection ended unexpectedly')) |
21 raise IOError(None, _('connection ended unexpectedly')) |
204 yield zd.flush() |
22 yield zd.flush() |
205 |
|
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 |
23 |
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 |
246 self.caps = None |
27 self.caps = None |
247 self.handler = None |
28 self.handler = None |
248 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path) |
29 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path) |
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 |
290 |
44 |
291 # look up capabilities only when needed |
45 # look up capabilities only when needed |