Changeset - 7e06657be365
[Not reviewed]
default
0 2 0
Mads Kiilerich - 7 years ago 2019-01-11 02:02:01
mads@kiilerich.com
middleware: also parse action in BaseVCSController parse_request

Some of the parsing in parse_request can be reused.
2 files changed with 113 insertions and 126 deletions:
0 comments (0 inline, 0 general)
kallithea/lib/middleware/simplegit.py
Show inline comments
 
@@ -49,6 +49,12 @@ log = logging.getLogger(__name__)
 
GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)$')
 

	
 

	
 
cmd_mapping = {
 
    'git-receive-pack': 'push',
 
    'git-upload-pack': 'pull',
 
}
 

	
 

	
 
class SimpleGit(BaseVCSController):
 

	
 
    @classmethod
 
@@ -59,9 +65,18 @@ class SimpleGit(BaseVCSController):
 
            return None
 

	
 
        class parsed_request(object):
 
            # See https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols#_the_smart_protocol
 
            repo_name = safe_unicode(m.group(1).rstrip('/'))
 
            cmd = m.group(2)
 

	
 
            query_string = environ['QUERY_STRING']
 
            if cmd == 'info/refs' and query_string.startswith('service='):
 
                service = query_string.split('=', 1)[1]
 
                action = cmd_mapping.get(service)
 
            else:
 
                service = None
 
                action = cmd_mapping.get(cmd)
 

	
 
        return parsed_request
 

	
 
    def _handle_request(self, parsed_request, environ, start_response):
 
@@ -73,15 +88,14 @@ class SimpleGit(BaseVCSController):
 
        if not is_valid_repo(parsed_request.repo_name, self.basepath, 'git'):
 
            raise HTTPNotFound()
 

	
 
        #======================================================================
 
        # GET ACTION PULL or PUSH
 
        #======================================================================
 
        action = self.__get_action(environ)
 
        if parsed_request.action is None:
 
            # Note: the client doesn't get the helpful error message
 
            raise HTTPBadRequest('Unable to detect pull/push action for %r! Are you using a nonstandard command or client?' % parsed_request.repo_name)
 

	
 
        #======================================================================
 
        # CHECK PERMISSIONS
 
        #======================================================================
 
        user, response_app = self._authorize(environ, start_response, action, parsed_request.repo_name, ip_addr)
 
        user, response_app = self._authorize(environ, start_response, parsed_request.action, parsed_request.repo_name, ip_addr)
 
        if response_app is not None:
 
            return response_app(environ, start_response)
 

	
 
@@ -92,7 +106,7 @@ class SimpleGit(BaseVCSController):
 
        extras = {
 
            'ip': ip_addr,
 
            'username': user.username,
 
            'action': action,
 
            'action': parsed_request.action,
 
            'repository': parsed_request.repo_name,
 
            'scm': 'git',
 
            'config': CONFIG['__file__'],
 
@@ -107,9 +121,9 @@ class SimpleGit(BaseVCSController):
 
        _set_extras(extras or {})
 

	
 
        try:
 
            self._handle_githooks(parsed_request.repo_name, action, baseui, environ)
 
            self._handle_githooks(parsed_request.repo_name, parsed_request.action, baseui, environ)
 
            log.info('%s action on Git repo "%s" by "%s" from %s',
 
                     action, parsed_request.repo_name, safe_str(user.username), ip_addr)
 
                     parsed_request.action, parsed_request.repo_name, safe_str(user.username), ip_addr)
 
            app = self.__make_app(parsed_request.repo_name)
 
            return app(environ, start_response)
 
        except Exception:
 
@@ -122,32 +136,6 @@ class SimpleGit(BaseVCSController):
 
        """
 
        return make_wsgi_app(repo_name, safe_str(self.basepath)) # FIXME: safe_str???
 

	
 
    def __get_action(self, environ):
 
        """
 
        Maps Git request commands into 'pull' or 'push'.
 

	
 
        Raises HTTPBadRequest if the request environment doesn't look like a git client.
 
        """
 
        mapping = {
 
            'git-receive-pack': 'push',
 
            'git-upload-pack': 'pull',
 
        }
 
        path_info = environ.get('PATH_INFO', '')
 
        m = GIT_PROTO_PAT.match(path_info)
 
        if m is None:
 
            action = None
 
        else:
 
            cmd = m.group(2)
 
            query_string = environ['QUERY_STRING']
 
            if cmd == 'info/refs' and query_string.startswith('service='):
 
                service = query_string.split('=', 1)[1]
 
                action = mapping.get(service)
 
            else:
 
                action = mapping.get(cmd)
 
        if action is None:
 
            raise HTTPBadRequest('Unable to detect pull/push action for %r! Are you using a nonstandard command or client?' % path_info)
 
        return action
 

	
 
    def _handle_githooks(self, repo_name, action, baseui, environ):
 
        """
 
        Handles pull action, push is handled by post-receive hook
kallithea/lib/middleware/simplehg.py
Show inline comments
 
@@ -63,90 +63,7 @@ def get_header_hgarg(environ):
 
    return ''.join(chunks)
 

	
 

	
 
class SimpleHg(BaseVCSController):
 

	
 
    @classmethod
 
    def parse_request(cls, environ):
 
        http_accept = environ.get('HTTP_ACCEPT', '')
 
        if not http_accept.startswith('application/mercurial'):
 
            return None
 
        path_info = environ.get('PATH_INFO', '')
 
        if not path_info.startswith('/'): # it must!
 
            return None
 

	
 
        class parsed_request(object):
 
            repo_name = safe_unicode(path_info[1:].rstrip('/'))
 

	
 
        return parsed_request
 

	
 
    def _handle_request(self, parsed_request, environ, start_response):
 
        ip_addr = self._get_ip_addr(environ)
 
        # skip passing error to error controller
 
        environ['pylons.status_code_redirect'] = True
 

	
 
        # quick check if repo exists...
 
        if not is_valid_repo(parsed_request.repo_name, self.basepath, 'hg'):
 
            raise HTTPNotFound()
 

	
 
        #======================================================================
 
        # GET ACTION PULL or PUSH
 
        #======================================================================
 
        action = self.__get_action(environ)
 

	
 
        #======================================================================
 
        # CHECK PERMISSIONS
 
        #======================================================================
 
        user, response_app = self._authorize(environ, start_response, action, parsed_request.repo_name, ip_addr)
 
        if response_app is not None:
 
            return response_app(environ, start_response)
 

	
 
        # extras are injected into Mercurial UI object and later available
 
        # in hooks executed by Kallithea
 
        from kallithea import CONFIG
 
        server_url = get_server_url(environ)
 
        extras = {
 
            'ip': ip_addr,
 
            'username': user.username,
 
            'action': action,
 
            'repository': parsed_request.repo_name,
 
            'scm': 'hg',
 
            'config': CONFIG['__file__'],
 
            'server_url': server_url,
 
        }
 
        #======================================================================
 
        # MERCURIAL REQUEST HANDLING
 
        #======================================================================
 
        str_repo_name = safe_str(parsed_request.repo_name)
 
        repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
 
        log.debug('Repository path is %s', repo_path)
 

	
 
        log.debug('HOOKS extras is %s', extras)
 
        baseui = make_ui(repo_path=repo_path)
 
        _set_extras(extras or {})
 

	
 
        try:
 
            log.info('%s action on Mercurial repo "%s" by "%s" from %s',
 
                     action, parsed_request.repo_name, safe_str(user.username), ip_addr)
 
            environ['REPO_NAME'] = str_repo_name # used by hgweb_mod.hgweb
 
            app = self.__make_app(repo_path, baseui)
 
            return app(environ, start_response)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise HTTPInternalServerError()
 

	
 
    def __make_app(self, repo_name, baseui):
 
        """
 
        Make an hgweb wsgi application using baseui.
 
        """
 
        return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
 

	
 
    def __get_action(self, environ):
 
        """
 
        Maps Mercurial request commands into 'pull' or 'push'.
 

	
 
        Raises HTTPBadRequest if the request environment doesn't look like a hg client.
 
        """
 
        mapping = {
 
cmd_mapping = {
 
            # 'batch' is not in this list - it is handled explicitly
 
            'between': 'pull',
 
            'branches': 'pull',
 
@@ -175,22 +92,104 @@ class SimpleHg(BaseVCSController):
 
            'putlfile': 'push',
 
            'unbundle': 'push',
 
            }
 
        for qry in environ['QUERY_STRING'].split('&'):
 

	
 

	
 
class SimpleHg(BaseVCSController):
 

	
 
    @classmethod
 
    def parse_request(cls, environ):
 
        http_accept = environ.get('HTTP_ACCEPT', '')
 
        if not http_accept.startswith('application/mercurial'):
 
            return None
 
        path_info = environ.get('PATH_INFO', '')
 
        if not path_info.startswith('/'): # it must!
 
            return None
 

	
 
        class parsed_request(object):
 
            repo_name = safe_unicode(path_info[1:].rstrip('/'))
 

	
 
            query_string = environ['QUERY_STRING']
 

	
 
            action = None
 
            for qry in query_string.split('&'):
 
            parts = qry.split('=', 1)
 
            if len(parts) == 2 and parts[0] == 'cmd':
 
                cmd = parts[1]
 
                if cmd == 'batch':
 
                    hgarg = get_header_hgarg(environ)
 
                    if not hgarg.startswith('cmds='):
 
                        return 'push' # paranoid and safe
 
                            action = 'push' # paranoid and safe
 
                            break
 
                        action = 'pull'
 
                    for cmd_arg in hgarg[5:].split(';'):
 
                        cmd, _args = urllib.unquote_plus(cmd_arg).split(' ', 1)
 
                        op = mapping.get(cmd, 'push')
 
                            op = cmd_mapping.get(cmd, 'push')
 
                        if op != 'pull':
 
                            assert op == 'push'
 
                            return 'push'
 
                    return 'pull'
 
                return mapping.get(cmd, 'push')
 
                                action = 'push'
 
                                break
 
                    else:
 
                        action = cmd_mapping.get(cmd, 'push')
 
                    break # only process one cmd
 

	
 
        return parsed_request
 

	
 
    def _handle_request(self, parsed_request, environ, start_response):
 
        ip_addr = self._get_ip_addr(environ)
 
        # skip passing error to error controller
 
        environ['pylons.status_code_redirect'] = True
 

	
 
        # quick check if repo exists...
 
        if not is_valid_repo(parsed_request.repo_name, self.basepath, 'hg'):
 
            raise HTTPNotFound()
 

	
 
        if parsed_request.action is None:
 
            # Note: the client doesn't get the helpful error message
 
            raise HTTPBadRequest('Unable to detect pull/push action for %r! Are you using a nonstandard command or client?' % parsed_request.repo_name)
 

	
 
        #======================================================================
 
        # CHECK PERMISSIONS
 
        #======================================================================
 
        user, response_app = self._authorize(environ, start_response, parsed_request.action, parsed_request.repo_name, ip_addr)
 
        if response_app is not None:
 
            return response_app(environ, start_response)
 

	
 
        # Note: the client doesn't get the helpful error message
 
        raise HTTPBadRequest('Unable to detect pull/push action! Are you using non standard command or client?')
 
        # extras are injected into Mercurial UI object and later available
 
        # in hooks executed by Kallithea
 
        from kallithea import CONFIG
 
        server_url = get_server_url(environ)
 
        extras = {
 
            'ip': ip_addr,
 
            'username': user.username,
 
            'action': parsed_request.action,
 
            'repository': parsed_request.repo_name,
 
            'scm': 'hg',
 
            'config': CONFIG['__file__'],
 
            'server_url': server_url,
 
        }
 
        #======================================================================
 
        # MERCURIAL REQUEST HANDLING
 
        #======================================================================
 
        str_repo_name = safe_str(parsed_request.repo_name)
 
        repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
 
        log.debug('Repository path is %s', repo_path)
 

	
 
        log.debug('HOOKS extras is %s', extras)
 
        baseui = make_ui(repo_path=repo_path)
 
        _set_extras(extras or {})
 

	
 
        try:
 
            log.info('%s action on Mercurial repo "%s" by "%s" from %s',
 
                     parsed_request.action, parsed_request.repo_name, safe_str(user.username), ip_addr)
 
            environ['REPO_NAME'] = str_repo_name # used by hgweb_mod.hgweb
 
            app = self.__make_app(repo_path, baseui)
 
            return app(environ, start_response)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise HTTPInternalServerError()
 

	
 
    def __make_app(self, repo_name, baseui):
 
        """
 
        Make an hgweb wsgi application using baseui.
 
        """
 
        return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
0 comments (0 inline, 0 general)