##// END OF EJS Templates
url: replace uses of hasattr with safehasattr or getattr
Augie Fackler -
r14964:376c32a5 default
parent child Browse files
Show More
@@ -1,463 +1,463
1 1 # url.py - HTTP handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 import urllib, urllib2, httplib, os, socket, cStringIO
11 11 from i18n import _
12 12 import keepalive, util, sslutil
13 13 import httpconnection as httpconnectionmod
14 14
15 15 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
16 16 def __init__(self, ui):
17 17 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
18 18 self.ui = ui
19 19
20 20 def find_user_password(self, realm, authuri):
21 21 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
22 22 self, realm, authuri)
23 23 user, passwd = authinfo
24 24 if user and passwd:
25 25 self._writedebug(user, passwd)
26 26 return (user, passwd)
27 27
28 28 if not user:
29 29 res = httpconnectionmod.readauthforuri(self.ui, authuri)
30 30 if res:
31 31 group, auth = res
32 32 user, passwd = auth.get('username'), auth.get('password')
33 33 self.ui.debug("using auth.%s.* for authentication\n" % group)
34 34 if not user or not passwd:
35 35 if not self.ui.interactive():
36 36 raise util.Abort(_('http authorization required'))
37 37
38 38 self.ui.write(_("http authorization required\n"))
39 39 self.ui.write(_("realm: %s\n") % realm)
40 40 if user:
41 41 self.ui.write(_("user: %s\n") % user)
42 42 else:
43 43 user = self.ui.prompt(_("user:"), default=None)
44 44
45 45 if not passwd:
46 46 passwd = self.ui.getpass()
47 47
48 48 self.add_password(realm, authuri, user, passwd)
49 49 self._writedebug(user, passwd)
50 50 return (user, passwd)
51 51
52 52 def _writedebug(self, user, passwd):
53 53 msg = _('http auth: user %s, password %s\n')
54 54 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
55 55
56 56 class proxyhandler(urllib2.ProxyHandler):
57 57 def __init__(self, ui):
58 58 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
59 59 # XXX proxyauthinfo = None
60 60
61 61 if proxyurl:
62 62 # proxy can be proper url or host[:port]
63 63 if not (proxyurl.startswith('http:') or
64 64 proxyurl.startswith('https:')):
65 65 proxyurl = 'http://' + proxyurl + '/'
66 66 proxy = util.url(proxyurl)
67 67 if not proxy.user:
68 68 proxy.user = ui.config("http_proxy", "user")
69 69 proxy.passwd = ui.config("http_proxy", "passwd")
70 70
71 71 # see if we should use a proxy for this url
72 72 no_list = ["localhost", "127.0.0.1"]
73 73 no_list.extend([p.lower() for
74 74 p in ui.configlist("http_proxy", "no")])
75 75 no_list.extend([p.strip().lower() for
76 76 p in os.getenv("no_proxy", '').split(',')
77 77 if p.strip()])
78 78 # "http_proxy.always" config is for running tests on localhost
79 79 if ui.configbool("http_proxy", "always"):
80 80 self.no_list = []
81 81 else:
82 82 self.no_list = no_list
83 83
84 84 proxyurl = str(proxy)
85 85 proxies = {'http': proxyurl, 'https': proxyurl}
86 86 ui.debug('proxying through http://%s:%s\n' %
87 87 (proxy.host, proxy.port))
88 88 else:
89 89 proxies = {}
90 90
91 91 # urllib2 takes proxy values from the environment and those
92 92 # will take precedence if found, so drop them
93 93 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
94 94 try:
95 95 if env in os.environ:
96 96 del os.environ[env]
97 97 except OSError:
98 98 pass
99 99
100 100 urllib2.ProxyHandler.__init__(self, proxies)
101 101 self.ui = ui
102 102
103 103 def proxy_open(self, req, proxy, type_):
104 104 host = req.get_host().split(':')[0]
105 105 if host in self.no_list:
106 106 return None
107 107
108 108 # work around a bug in Python < 2.4.2
109 109 # (it leaves a "\n" at the end of Proxy-authorization headers)
110 110 baseclass = req.__class__
111 111 class _request(baseclass):
112 112 def add_header(self, key, val):
113 113 if key.lower() == 'proxy-authorization':
114 114 val = val.strip()
115 115 return baseclass.add_header(self, key, val)
116 116 req.__class__ = _request
117 117
118 118 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
119 119
120 120 def _gen_sendfile(orgsend):
121 121 def _sendfile(self, data):
122 122 # send a file
123 123 if isinstance(data, httpconnectionmod.httpsendfile):
124 124 # if auth required, some data sent twice, so rewind here
125 125 data.seek(0)
126 126 for chunk in util.filechunkiter(data):
127 127 orgsend(self, chunk)
128 128 else:
129 129 orgsend(self, data)
130 130 return _sendfile
131 131
132 has_https = hasattr(urllib2, 'HTTPSHandler')
132 has_https = util.safehasattr(urllib2, 'HTTPSHandler')
133 133 if has_https:
134 134 try:
135 135 _create_connection = socket.create_connection
136 136 except AttributeError:
137 137 _GLOBAL_DEFAULT_TIMEOUT = object()
138 138
139 139 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
140 140 source_address=None):
141 141 # lifted from Python 2.6
142 142
143 143 msg = "getaddrinfo returns an empty list"
144 144 host, port = address
145 145 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
146 146 af, socktype, proto, canonname, sa = res
147 147 sock = None
148 148 try:
149 149 sock = socket.socket(af, socktype, proto)
150 150 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
151 151 sock.settimeout(timeout)
152 152 if source_address:
153 153 sock.bind(source_address)
154 154 sock.connect(sa)
155 155 return sock
156 156
157 157 except socket.error, msg:
158 158 if sock is not None:
159 159 sock.close()
160 160
161 161 raise socket.error, msg
162 162
163 163 class httpconnection(keepalive.HTTPConnection):
164 164 # must be able to send big bundle as stream.
165 165 send = _gen_sendfile(keepalive.HTTPConnection.send)
166 166
167 167 def connect(self):
168 168 if has_https and self.realhostport: # use CONNECT proxy
169 169 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
170 170 self.sock.connect((self.host, self.port))
171 171 if _generic_proxytunnel(self):
172 172 # we do not support client x509 certificates
173 173 self.sock = sslutil.ssl_wrap_socket(self.sock, None, None)
174 174 else:
175 175 keepalive.HTTPConnection.connect(self)
176 176
177 177 def getresponse(self):
178 178 proxyres = getattr(self, 'proxyres', None)
179 179 if proxyres:
180 180 if proxyres.will_close:
181 181 self.close()
182 182 self.proxyres = None
183 183 return proxyres
184 184 return keepalive.HTTPConnection.getresponse(self)
185 185
186 186 # general transaction handler to support different ways to handle
187 187 # HTTPS proxying before and after Python 2.6.3.
188 188 def _generic_start_transaction(handler, h, req):
189 if hasattr(req, '_tunnel_host') and req._tunnel_host:
190 tunnel_host = req._tunnel_host
189 tunnel_host = getattr(req, '_tunnel_host', None)
190 if tunnel_host:
191 191 if tunnel_host[:7] not in ['http://', 'https:/']:
192 192 tunnel_host = 'https://' + tunnel_host
193 193 new_tunnel = True
194 194 else:
195 195 tunnel_host = req.get_selector()
196 196 new_tunnel = False
197 197
198 198 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
199 199 u = util.url(tunnel_host)
200 200 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
201 201 h.realhostport = ':'.join([u.host, (u.port or '443')])
202 202 h.headers = req.headers.copy()
203 203 h.headers.update(handler.parent.addheaders)
204 204 return
205 205
206 206 h.realhostport = None
207 207 h.headers = None
208 208
209 209 def _generic_proxytunnel(self):
210 210 proxyheaders = dict(
211 211 [(x, self.headers[x]) for x in self.headers
212 212 if x.lower().startswith('proxy-')])
213 213 self._set_hostport(self.host, self.port)
214 214 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
215 215 for header in proxyheaders.iteritems():
216 216 self.send('%s: %s\r\n' % header)
217 217 self.send('\r\n')
218 218
219 219 # majority of the following code is duplicated from
220 220 # httplib.HTTPConnection as there are no adequate places to
221 221 # override functions to provide the needed functionality
222 222 res = self.response_class(self.sock,
223 223 strict=self.strict,
224 224 method=self._method)
225 225
226 226 while True:
227 227 version, status, reason = res._read_status()
228 228 if status != httplib.CONTINUE:
229 229 break
230 230 while True:
231 231 skip = res.fp.readline().strip()
232 232 if not skip:
233 233 break
234 234 res.status = status
235 235 res.reason = reason.strip()
236 236
237 237 if res.status == 200:
238 238 while True:
239 239 line = res.fp.readline()
240 240 if line == '\r\n':
241 241 break
242 242 return True
243 243
244 244 if version == 'HTTP/1.0':
245 245 res.version = 10
246 246 elif version.startswith('HTTP/1.'):
247 247 res.version = 11
248 248 elif version == 'HTTP/0.9':
249 249 res.version = 9
250 250 else:
251 251 raise httplib.UnknownProtocol(version)
252 252
253 253 if res.version == 9:
254 254 res.length = None
255 255 res.chunked = 0
256 256 res.will_close = 1
257 257 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
258 258 return False
259 259
260 260 res.msg = httplib.HTTPMessage(res.fp)
261 261 res.msg.fp = None
262 262
263 263 # are we using the chunked-style of transfer encoding?
264 264 trenc = res.msg.getheader('transfer-encoding')
265 265 if trenc and trenc.lower() == "chunked":
266 266 res.chunked = 1
267 267 res.chunk_left = None
268 268 else:
269 269 res.chunked = 0
270 270
271 271 # will the connection close at the end of the response?
272 272 res.will_close = res._check_close()
273 273
274 274 # do we have a Content-Length?
275 275 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
276 276 length = res.msg.getheader('content-length')
277 277 if length and not res.chunked:
278 278 try:
279 279 res.length = int(length)
280 280 except ValueError:
281 281 res.length = None
282 282 else:
283 283 if res.length < 0: # ignore nonsensical negative lengths
284 284 res.length = None
285 285 else:
286 286 res.length = None
287 287
288 288 # does the body have a fixed length? (of zero)
289 289 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
290 290 100 <= status < 200 or # 1xx codes
291 291 res._method == 'HEAD'):
292 292 res.length = 0
293 293
294 294 # if the connection remains open, and we aren't using chunked, and
295 295 # a content-length was not provided, then assume that the connection
296 296 # WILL close.
297 297 if (not res.will_close and
298 298 not res.chunked and
299 299 res.length is None):
300 300 res.will_close = 1
301 301
302 302 self.proxyres = res
303 303
304 304 return False
305 305
306 306 class httphandler(keepalive.HTTPHandler):
307 307 def http_open(self, req):
308 308 return self.do_open(httpconnection, req)
309 309
310 310 def _start_transaction(self, h, req):
311 311 _generic_start_transaction(self, h, req)
312 312 return keepalive.HTTPHandler._start_transaction(self, h, req)
313 313
314 314 if has_https:
315 315 class httpsconnection(httplib.HTTPSConnection):
316 316 response_class = keepalive.HTTPResponse
317 317 # must be able to send big bundle as stream.
318 318 send = _gen_sendfile(keepalive.safesend)
319 319 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
320 320
321 321 def connect(self):
322 322 self.sock = _create_connection((self.host, self.port))
323 323
324 324 host = self.host
325 325 if self.realhostport: # use CONNECT proxy
326 326 _generic_proxytunnel(self)
327 327 host = self.realhostport.rsplit(':', 1)[0]
328 328 self.sock = sslutil.ssl_wrap_socket(
329 329 self.sock, self.key_file, self.cert_file,
330 330 **sslutil.sslkwargs(self.ui, host))
331 331 sslutil.validator(self.ui, host)(self.sock)
332 332
333 333 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
334 334 def __init__(self, ui):
335 335 keepalive.KeepAliveHandler.__init__(self)
336 336 urllib2.HTTPSHandler.__init__(self)
337 337 self.ui = ui
338 338 self.pwmgr = passwordmgr(self.ui)
339 339
340 340 def _start_transaction(self, h, req):
341 341 _generic_start_transaction(self, h, req)
342 342 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
343 343
344 344 def https_open(self, req):
345 345 res = httpconnectionmod.readauthforuri(self.ui, req.get_full_url())
346 346 if res:
347 347 group, auth = res
348 348 self.auth = auth
349 349 self.ui.debug("using auth.%s.* for authentication\n" % group)
350 350 else:
351 351 self.auth = None
352 352 return self.do_open(self._makeconnection, req)
353 353
354 354 def _makeconnection(self, host, port=None, *args, **kwargs):
355 355 keyfile = None
356 356 certfile = None
357 357
358 358 if len(args) >= 1: # key_file
359 359 keyfile = args[0]
360 360 if len(args) >= 2: # cert_file
361 361 certfile = args[1]
362 362 args = args[2:]
363 363
364 364 # if the user has specified different key/cert files in
365 365 # hgrc, we prefer these
366 366 if self.auth and 'key' in self.auth and 'cert' in self.auth:
367 367 keyfile = self.auth['key']
368 368 certfile = self.auth['cert']
369 369
370 370 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
371 371 conn.ui = self.ui
372 372 return conn
373 373
374 374 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
375 375 def __init__(self, *args, **kwargs):
376 376 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
377 377 self.retried_req = None
378 378
379 379 def reset_retry_count(self):
380 380 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
381 381 # forever. We disable reset_retry_count completely and reset in
382 382 # http_error_auth_reqed instead.
383 383 pass
384 384
385 385 def http_error_auth_reqed(self, auth_header, host, req, headers):
386 386 # Reset the retry counter once for each request.
387 387 if req is not self.retried_req:
388 388 self.retried_req = req
389 389 self.retried = 0
390 390 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
391 391 # it doesn't know about the auth type requested. This can happen if
392 392 # somebody is using BasicAuth and types a bad password.
393 393 try:
394 394 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
395 395 self, auth_header, host, req, headers)
396 396 except ValueError, inst:
397 397 arg = inst.args[0]
398 398 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
399 399 return
400 400 raise
401 401
402 402 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
403 403 def __init__(self, *args, **kwargs):
404 404 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
405 405 self.retried_req = None
406 406
407 407 def reset_retry_count(self):
408 408 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
409 409 # forever. We disable reset_retry_count completely and reset in
410 410 # http_error_auth_reqed instead.
411 411 pass
412 412
413 413 def http_error_auth_reqed(self, auth_header, host, req, headers):
414 414 # Reset the retry counter once for each request.
415 415 if req is not self.retried_req:
416 416 self.retried_req = req
417 417 self.retried = 0
418 418 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
419 419 self, auth_header, host, req, headers)
420 420
421 421 handlerfuncs = []
422 422
423 423 def opener(ui, authinfo=None):
424 424 '''
425 425 construct an opener suitable for urllib2
426 426 authinfo will be added to the password manager
427 427 '''
428 428 if ui.configbool('ui', 'usehttp2', False):
429 429 handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))]
430 430 else:
431 431 handlers = [httphandler()]
432 432 if has_https:
433 433 handlers.append(httpshandler(ui))
434 434
435 435 handlers.append(proxyhandler(ui))
436 436
437 437 passmgr = passwordmgr(ui)
438 438 if authinfo is not None:
439 439 passmgr.add_password(*authinfo)
440 440 user, passwd = authinfo[2:4]
441 441 ui.debug('http auth: user %s, password %s\n' %
442 442 (user, passwd and '*' * len(passwd) or 'not set'))
443 443
444 444 handlers.extend((httpbasicauthhandler(passmgr),
445 445 httpdigestauthhandler(passmgr)))
446 446 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
447 447 opener = urllib2.build_opener(*handlers)
448 448
449 449 # 1.0 here is the _protocol_ version
450 450 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
451 451 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
452 452 return opener
453 453
454 454 def open(ui, url_, data=None):
455 455 u = util.url(url_)
456 456 if u.scheme:
457 457 u.scheme = u.scheme.lower()
458 458 url_, authinfo = u.authinfo()
459 459 else:
460 460 path = util.normpath(os.path.abspath(url_))
461 461 url_ = 'file://' + urllib.pathname2url(path)
462 462 authinfo = None
463 463 return opener(ui, authinfo).open(url_, data)
General Comments 0
You need to be logged in to leave comments. Login now