changeset 7720:7e06657be365

middleware: also parse action in BaseVCSController parse_request Some of the parsing in parse_request can be reused.
author Mads Kiilerich <mads@kiilerich.com>
date Fri, 11 Jan 2019 02:02:01 +0100
parents 04ace15a511e
children 96e26544d037
files kallithea/lib/middleware/simplegit.py kallithea/lib/middleware/simplehg.py
diffstat 2 files changed, 83 insertions(+), 96 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/lib/middleware/simplegit.py	Tue Jan 08 13:02:44 2019 +0100
+++ b/kallithea/lib/middleware/simplegit.py	Fri Jan 11 02:02:01 2019 +0100
@@ -49,6 +49,12 @@
 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 @@
             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 @@
         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 @@
         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 @@
         _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 @@
         """
         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
--- a/kallithea/lib/middleware/simplehg.py	Tue Jan 08 13:02:44 2019 +0100
+++ b/kallithea/lib/middleware/simplehg.py	Fri Jan 11 02:02:01 2019 +0100
@@ -63,6 +63,37 @@
     return ''.join(chunks)
 
 
+cmd_mapping = {
+    # 'batch' is not in this list - it is handled explicitly
+    'between': 'pull',
+    'branches': 'pull',
+    'branchmap': 'pull',
+    'capabilities': 'pull',
+    'changegroup': 'pull',
+    'changegroupsubset': 'pull',
+    'changesetdata': 'pull',
+    'clonebundles': 'pull',
+    'debugwireargs': 'pull',
+    'filedata': 'pull',
+    'getbundle': 'pull',
+    'getlfile': 'pull',
+    'heads': 'pull',
+    'hello': 'pull',
+    'known': 'pull',
+    'lheads': 'pull',
+    'listkeys': 'pull',
+    'lookup': 'pull',
+    'manifestdata': 'pull',
+    'narrow_widen': 'pull',
+    'protocaps': 'pull',
+    'statlfile': 'pull',
+    'stream_out': 'pull',
+    'pushkey': 'push',
+    'putlfile': 'push',
+    'unbundle': 'push',
+    }
+
+
 class SimpleHg(BaseVCSController):
 
     @classmethod
@@ -77,6 +108,30 @@
         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='):
+                            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 = cmd_mapping.get(cmd, 'push')
+                            if op != 'pull':
+                                assert op == '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):
@@ -88,15 +143,14 @@
         if not is_valid_repo(parsed_request.repo_name, self.basepath, 'hg'):
             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)
 
@@ -107,7 +161,7 @@
         extras = {
             'ip': ip_addr,
             'username': user.username,
-            'action': action,
+            'action': parsed_request.action,
             'repository': parsed_request.repo_name,
             'scm': 'hg',
             'config': CONFIG['__file__'],
@@ -126,7 +180,7 @@
 
         try:
             log.info('%s action on Mercurial 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)
             environ['REPO_NAME'] = str_repo_name # used by hgweb_mod.hgweb
             app = self.__make_app(repo_path, baseui)
             return app(environ, start_response)
@@ -139,58 +193,3 @@
         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 = {
-            # 'batch' is not in this list - it is handled explicitly
-            'between': 'pull',
-            'branches': 'pull',
-            'branchmap': 'pull',
-            'capabilities': 'pull',
-            'changegroup': 'pull',
-            'changegroupsubset': 'pull',
-            'changesetdata': 'pull',
-            'clonebundles': 'pull',
-            'debugwireargs': 'pull',
-            'filedata': 'pull',
-            'getbundle': 'pull',
-            'getlfile': 'pull',
-            'heads': 'pull',
-            'hello': 'pull',
-            'known': 'pull',
-            'lheads': 'pull',
-            'listkeys': 'pull',
-            'lookup': 'pull',
-            'manifestdata': 'pull',
-            'narrow_widen': 'pull',
-            'protocaps': 'pull',
-            'statlfile': 'pull',
-            'stream_out': 'pull',
-            'pushkey': 'push',
-            'putlfile': 'push',
-            'unbundle': 'push',
-            }
-        for qry in environ['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
-                    for cmd_arg in hgarg[5:].split(';'):
-                        cmd, _args = urllib.unquote_plus(cmd_arg).split(' ', 1)
-                        op = mapping.get(cmd, 'push')
-                        if op != 'pull':
-                            assert op == 'push'
-                            return 'push'
-                    return 'pull'
-                return mapping.get(cmd, 'push')
-
-        # 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?')