diff options
Diffstat (limited to 'websdk/mercurial/url.py')
-rw-r--r--[l---------] | websdk/mercurial/url.py | 474 |
1 files changed, 473 insertions, 1 deletions
diff --git a/websdk/mercurial/url.py b/websdk/mercurial/url.py index e2784a9..2430d3a 120000..100644 --- a/websdk/mercurial/url.py +++ b/websdk/mercurial/url.py @@ -1 +1,473 @@ -/usr/share/pyshared/mercurial/url.py
\ No newline at end of file +# url.py - HTTP handling for mercurial +# +# Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com> +# Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +import urllib, urllib2, httplib, os, socket, cStringIO +from i18n import _ +import keepalive, util, sslutil +import httpconnection as httpconnectionmod + +class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): + def __init__(self, ui): + urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self) + self.ui = ui + + def find_user_password(self, realm, authuri): + authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( + self, realm, authuri) + user, passwd = authinfo + if user and passwd: + self._writedebug(user, passwd) + return (user, passwd) + + if not user or not passwd: + res = httpconnectionmod.readauthforuri(self.ui, authuri, user) + if res: + group, auth = res + user, passwd = auth.get('username'), auth.get('password') + self.ui.debug("using auth.%s.* for authentication\n" % group) + if not user or not passwd: + if not self.ui.interactive(): + raise util.Abort(_('http authorization required')) + + self.ui.write(_("http authorization required\n")) + self.ui.write(_("realm: %s\n") % realm) + if user: + self.ui.write(_("user: %s\n") % user) + else: + user = self.ui.prompt(_("user:"), default=None) + + if not passwd: + passwd = self.ui.getpass() + + self.add_password(realm, authuri, user, passwd) + self._writedebug(user, passwd) + return (user, passwd) + + def _writedebug(self, user, passwd): + msg = _('http auth: user %s, password %s\n') + self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set')) + + def find_stored_password(self, authuri): + return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( + self, None, authuri) + +class proxyhandler(urllib2.ProxyHandler): + def __init__(self, ui): + proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy') + # XXX proxyauthinfo = None + + if proxyurl: + # proxy can be proper url or host[:port] + if not (proxyurl.startswith('http:') or + proxyurl.startswith('https:')): + proxyurl = 'http://' + proxyurl + '/' + proxy = util.url(proxyurl) + if not proxy.user: + proxy.user = ui.config("http_proxy", "user") + proxy.passwd = ui.config("http_proxy", "passwd") + + # see if we should use a proxy for this url + no_list = ["localhost", "127.0.0.1"] + no_list.extend([p.lower() for + p in ui.configlist("http_proxy", "no")]) + no_list.extend([p.strip().lower() for + p in os.getenv("no_proxy", '').split(',') + if p.strip()]) + # "http_proxy.always" config is for running tests on localhost + if ui.configbool("http_proxy", "always"): + self.no_list = [] + else: + self.no_list = no_list + + proxyurl = str(proxy) + proxies = {'http': proxyurl, 'https': proxyurl} + ui.debug('proxying through http://%s:%s\n' % + (proxy.host, proxy.port)) + else: + proxies = {} + + # urllib2 takes proxy values from the environment and those + # will take precedence if found. So, if there's a config entry + # defining a proxy, drop the environment ones + if ui.config("http_proxy", "host"): + for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]: + try: + if env in os.environ: + del os.environ[env] + except OSError: + pass + + urllib2.ProxyHandler.__init__(self, proxies) + self.ui = ui + + def proxy_open(self, req, proxy, type_): + host = req.get_host().split(':')[0] + if host in self.no_list: + return None + + # work around a bug in Python < 2.4.2 + # (it leaves a "\n" at the end of Proxy-authorization headers) + baseclass = req.__class__ + class _request(baseclass): + def add_header(self, key, val): + if key.lower() == 'proxy-authorization': + val = val.strip() + return baseclass.add_header(self, key, val) + req.__class__ = _request + + return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_) + +def _gen_sendfile(orgsend): + def _sendfile(self, data): + # send a file + if isinstance(data, httpconnectionmod.httpsendfile): + # if auth required, some data sent twice, so rewind here + data.seek(0) + for chunk in util.filechunkiter(data): + orgsend(self, chunk) + else: + orgsend(self, data) + return _sendfile + +has_https = util.safehasattr(urllib2, 'HTTPSHandler') +if has_https: + try: + _create_connection = socket.create_connection + except AttributeError: + _GLOBAL_DEFAULT_TIMEOUT = object() + + def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, + source_address=None): + # lifted from Python 2.6 + + msg = "getaddrinfo returns an empty list" + host, port = address + for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + if timeout is not _GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except socket.error, msg: + if sock is not None: + sock.close() + + raise socket.error, msg + +class httpconnection(keepalive.HTTPConnection): + # must be able to send big bundle as stream. + send = _gen_sendfile(keepalive.HTTPConnection.send) + + def connect(self): + if has_https and self.realhostport: # use CONNECT proxy + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.host, self.port)) + if _generic_proxytunnel(self): + # we do not support client x509 certificates + self.sock = sslutil.ssl_wrap_socket(self.sock, None, None) + else: + keepalive.HTTPConnection.connect(self) + + def getresponse(self): + proxyres = getattr(self, 'proxyres', None) + if proxyres: + if proxyres.will_close: + self.close() + self.proxyres = None + return proxyres + return keepalive.HTTPConnection.getresponse(self) + +# general transaction handler to support different ways to handle +# HTTPS proxying before and after Python 2.6.3. +def _generic_start_transaction(handler, h, req): + tunnel_host = getattr(req, '_tunnel_host', None) + if tunnel_host: + if tunnel_host[:7] not in ['http://', 'https:/']: + tunnel_host = 'https://' + tunnel_host + new_tunnel = True + else: + tunnel_host = req.get_selector() + new_tunnel = False + + if new_tunnel or tunnel_host == req.get_full_url(): # has proxy + u = util.url(tunnel_host) + if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS + h.realhostport = ':'.join([u.host, (u.port or '443')]) + h.headers = req.headers.copy() + h.headers.update(handler.parent.addheaders) + return + + h.realhostport = None + h.headers = None + +def _generic_proxytunnel(self): + proxyheaders = dict( + [(x, self.headers[x]) for x in self.headers + if x.lower().startswith('proxy-')]) + self._set_hostport(self.host, self.port) + self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport) + for header in proxyheaders.iteritems(): + self.send('%s: %s\r\n' % header) + self.send('\r\n') + + # majority of the following code is duplicated from + # httplib.HTTPConnection as there are no adequate places to + # override functions to provide the needed functionality + res = self.response_class(self.sock, + strict=self.strict, + method=self._method) + + while True: + version, status, reason = res._read_status() + if status != httplib.CONTINUE: + break + while True: + skip = res.fp.readline().strip() + if not skip: + break + res.status = status + res.reason = reason.strip() + + if res.status == 200: + while True: + line = res.fp.readline() + if line == '\r\n': + break + return True + + if version == 'HTTP/1.0': + res.version = 10 + elif version.startswith('HTTP/1.'): + res.version = 11 + elif version == 'HTTP/0.9': + res.version = 9 + else: + raise httplib.UnknownProtocol(version) + + if res.version == 9: + res.length = None + res.chunked = 0 + res.will_close = 1 + res.msg = httplib.HTTPMessage(cStringIO.StringIO()) + return False + + res.msg = httplib.HTTPMessage(res.fp) + res.msg.fp = None + + # are we using the chunked-style of transfer encoding? + trenc = res.msg.getheader('transfer-encoding') + if trenc and trenc.lower() == "chunked": + res.chunked = 1 + res.chunk_left = None + else: + res.chunked = 0 + + # will the connection close at the end of the response? + res.will_close = res._check_close() + + # do we have a Content-Length? + # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked" + length = res.msg.getheader('content-length') + if length and not res.chunked: + try: + res.length = int(length) + except ValueError: + res.length = None + else: + if res.length < 0: # ignore nonsensical negative lengths + res.length = None + else: + res.length = None + + # does the body have a fixed length? (of zero) + if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or + 100 <= status < 200 or # 1xx codes + res._method == 'HEAD'): + res.length = 0 + + # if the connection remains open, and we aren't using chunked, and + # a content-length was not provided, then assume that the connection + # WILL close. + if (not res.will_close and + not res.chunked and + res.length is None): + res.will_close = 1 + + self.proxyres = res + + return False + +class httphandler(keepalive.HTTPHandler): + def http_open(self, req): + return self.do_open(httpconnection, req) + + def _start_transaction(self, h, req): + _generic_start_transaction(self, h, req) + return keepalive.HTTPHandler._start_transaction(self, h, req) + +if has_https: + class httpsconnection(httplib.HTTPSConnection): + response_class = keepalive.HTTPResponse + # must be able to send big bundle as stream. + send = _gen_sendfile(keepalive.safesend) + getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection) + + def connect(self): + self.sock = _create_connection((self.host, self.port)) + + host = self.host + if self.realhostport: # use CONNECT proxy + _generic_proxytunnel(self) + host = self.realhostport.rsplit(':', 1)[0] + self.sock = sslutil.ssl_wrap_socket( + self.sock, self.key_file, self.cert_file, + **sslutil.sslkwargs(self.ui, host)) + sslutil.validator(self.ui, host)(self.sock) + + class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): + def __init__(self, ui): + keepalive.KeepAliveHandler.__init__(self) + urllib2.HTTPSHandler.__init__(self) + self.ui = ui + self.pwmgr = passwordmgr(self.ui) + + def _start_transaction(self, h, req): + _generic_start_transaction(self, h, req) + return keepalive.KeepAliveHandler._start_transaction(self, h, req) + + def https_open(self, req): + # req.get_full_url() does not contain credentials and we may + # need them to match the certificates. + url = req.get_full_url() + user, password = self.pwmgr.find_stored_password(url) + res = httpconnectionmod.readauthforuri(self.ui, url, user) + if res: + group, auth = res + self.auth = auth + self.ui.debug("using auth.%s.* for authentication\n" % group) + else: + self.auth = None + return self.do_open(self._makeconnection, req) + + def _makeconnection(self, host, port=None, *args, **kwargs): + keyfile = None + certfile = None + + if len(args) >= 1: # key_file + keyfile = args[0] + if len(args) >= 2: # cert_file + certfile = args[1] + args = args[2:] + + # if the user has specified different key/cert files in + # hgrc, we prefer these + if self.auth and 'key' in self.auth and 'cert' in self.auth: + keyfile = self.auth['key'] + certfile = self.auth['cert'] + + conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs) + conn.ui = self.ui + return conn + +class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler): + def __init__(self, *args, **kwargs): + urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs) + self.retried_req = None + + def reset_retry_count(self): + # Python 2.6.5 will call this on 401 or 407 errors and thus loop + # forever. We disable reset_retry_count completely and reset in + # http_error_auth_reqed instead. + pass + + def http_error_auth_reqed(self, auth_header, host, req, headers): + # Reset the retry counter once for each request. + if req is not self.retried_req: + self.retried_req = req + self.retried = 0 + # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if + # it doesn't know about the auth type requested. This can happen if + # somebody is using BasicAuth and types a bad password. + try: + return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed( + self, auth_header, host, req, headers) + except ValueError, inst: + arg = inst.args[0] + if arg.startswith("AbstractDigestAuthHandler doesn't know "): + return + raise + +class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler): + def __init__(self, *args, **kwargs): + urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs) + self.retried_req = None + + def reset_retry_count(self): + # Python 2.6.5 will call this on 401 or 407 errors and thus loop + # forever. We disable reset_retry_count completely and reset in + # http_error_auth_reqed instead. + pass + + def http_error_auth_reqed(self, auth_header, host, req, headers): + # Reset the retry counter once for each request. + if req is not self.retried_req: + self.retried_req = req + self.retried = 0 + return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed( + self, auth_header, host, req, headers) + +handlerfuncs = [] + +def opener(ui, authinfo=None): + ''' + construct an opener suitable for urllib2 + authinfo will be added to the password manager + ''' + if ui.configbool('ui', 'usehttp2', False): + handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))] + else: + handlers = [httphandler()] + if has_https: + handlers.append(httpshandler(ui)) + + handlers.append(proxyhandler(ui)) + + passmgr = passwordmgr(ui) + if authinfo is not None: + passmgr.add_password(*authinfo) + user, passwd = authinfo[2:4] + ui.debug('http auth: user %s, password %s\n' % + (user, passwd and '*' * len(passwd) or 'not set')) + + handlers.extend((httpbasicauthhandler(passmgr), + httpdigestauthhandler(passmgr))) + handlers.extend([h(ui, passmgr) for h in handlerfuncs]) + opener = urllib2.build_opener(*handlers) + + # 1.0 here is the _protocol_ version + opener.addheaders = [('User-agent', 'mercurial/proto-1.0')] + opener.addheaders.append(('Accept', 'application/mercurial-0.1')) + return opener + +def open(ui, url_, data=None): + u = util.url(url_) + if u.scheme: + u.scheme = u.scheme.lower() + url_, authinfo = u.authinfo() + else: + path = util.normpath(os.path.abspath(url_)) + url_ = 'file://' + urllib.pathname2url(path) + authinfo = None + return opener(ui, authinfo).open(url_, data) |