changeset 8684:89f11587b2dc

config: move WSGI middleware apps from lib to config These middlewares are full WSGI applications - that is not so lib-ish. The middleware is referenced from the application in config - that seems like a good place for them to live.
author Mads Kiilerich <mads@kiilerich.com>
date Sat, 10 Oct 2020 23:38:20 +0200
parents e410c43aec42
children dff9658bdd98
files kallithea/config/application.py kallithea/config/middleware/__init__.py kallithea/config/middleware/appenlight.py kallithea/config/middleware/https_fixup.py kallithea/config/middleware/permanent_repo_url.py kallithea/config/middleware/pygrack.py kallithea/config/middleware/simplegit.py kallithea/config/middleware/simplehg.py kallithea/config/middleware/wrapper.py kallithea/lib/middleware/__init__.py kallithea/lib/middleware/appenlight.py kallithea/lib/middleware/https_fixup.py kallithea/lib/middleware/permanent_repo_url.py kallithea/lib/middleware/pygrack.py kallithea/lib/middleware/simplegit.py kallithea/lib/middleware/simplehg.py kallithea/lib/middleware/wrapper.py
diffstat 17 files changed, 744 insertions(+), 744 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/config/application.py	Tue Oct 13 19:07:59 2020 +0200
+++ b/kallithea/config/application.py	Sat Oct 10 23:38:20 2020 +0200
@@ -14,11 +14,11 @@
 """WSGI middleware initialization for the Kallithea application."""
 
 from kallithea.config.app_cfg import base_config
-from kallithea.lib.middleware.https_fixup import HttpsFixup
-from kallithea.lib.middleware.permanent_repo_url import PermanentRepoUrl
-from kallithea.lib.middleware.simplegit import SimpleGit
-from kallithea.lib.middleware.simplehg import SimpleHg
-from kallithea.lib.middleware.wrapper import RequestWrapper
+from kallithea.config.middleware.https_fixup import HttpsFixup
+from kallithea.config.middleware.permanent_repo_url import PermanentRepoUrl
+from kallithea.config.middleware.simplegit import SimpleGit
+from kallithea.config.middleware.simplehg import SimpleHg
+from kallithea.config.middleware.wrapper import RequestWrapper
 from kallithea.lib.utils2 import asbool
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/config/middleware/__init__.py	Sat Oct 10 23:38:20 2020 +0200
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/config/middleware/appenlight.py	Sat Oct 10 23:38:20 2020 +0200
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.appenlight
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+middleware to handle appenlight publishing of errors
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: October 18, 2012
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+
+
+try:
+    from appenlight_client import make_appenlight_middleware
+except ImportError:
+    AppEnlight = None
+else:
+    AppEnlight = make_appenlight_middleware
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/config/middleware/https_fixup.py	Sat Oct 10 23:38:20 2020 +0200
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.https_fixup
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+middleware to handle https correctly
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: May 23, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+
+
+from kallithea.lib.utils2 import asbool
+
+
+class HttpsFixup(object):
+
+    def __init__(self, app, config):
+        self.application = app
+        self.config = config
+
+    def __call__(self, environ, start_response):
+        self.__fixup(environ)
+        debug = asbool(self.config.get('debug'))
+        is_ssl = environ['wsgi.url_scheme'] == 'https'
+
+        def custom_start_response(status, headers, exc_info=None):
+            if is_ssl and asbool(self.config.get('use_htsts')) and not debug:
+                headers.append(('Strict-Transport-Security',
+                                'max-age=8640000; includeSubDomains'))
+            return start_response(status, headers, exc_info)
+
+        return self.application(environ, custom_start_response)
+
+    def __fixup(self, environ):
+        """
+        Function to fixup the environ as needed. In order to use this
+        middleware you should set this header inside your
+        proxy ie. nginx, apache etc.
+        """
+        # DETECT PROTOCOL !
+        if 'HTTP_X_URL_SCHEME' in environ:
+            proto = environ.get('HTTP_X_URL_SCHEME')
+        elif 'HTTP_X_FORWARDED_SCHEME' in environ:
+            proto = environ.get('HTTP_X_FORWARDED_SCHEME')
+        elif 'HTTP_X_FORWARDED_PROTO' in environ:
+            proto = environ.get('HTTP_X_FORWARDED_PROTO')
+        else:
+            proto = 'http'
+        org_proto = proto
+
+        # if we have force, just override
+        if asbool(self.config.get('force_https')):
+            proto = 'https'
+
+        environ['wsgi.url_scheme'] = proto
+        environ['wsgi._org_proto'] = org_proto
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/config/middleware/permanent_repo_url.py	Sat Oct 10 23:38:20 2020 +0200
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.permanent_repo_url
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+middleware to handle permanent repo URLs, replacing PATH_INFO '/_123/yada' with
+'/name/of/repo/yada' after looking 123 up in the database.
+"""
+
+
+from kallithea.lib.utils import fix_repo_id_name
+from kallithea.lib.utils2 import safe_bytes, safe_str
+
+
+class PermanentRepoUrl(object):
+
+    def __init__(self, app, config):
+        self.application = app
+        self.config = config
+
+    def __call__(self, environ, start_response):
+        # Extract path_info as get_path_info does, but do it explicitly because
+        # we also have to do the reverse operation when patching it back in
+        path_info = safe_str(environ['PATH_INFO'].encode('latin1'))
+        if path_info.startswith('/'): # it must
+            path_info = '/' + fix_repo_id_name(path_info[1:])
+            environ['PATH_INFO'] = safe_bytes(path_info).decode('latin1')
+
+        return self.application(environ, start_response)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/config/middleware/pygrack.py	Sat Oct 10 23:38:20 2020 +0200
@@ -0,0 +1,229 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.pygrack
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Python implementation of git-http-backend's Smart HTTP protocol
+
+Based on original code from git_http_backend.py project.
+
+Copyright (c) 2010 Daniel Dotsenko <dotsa@hotmail.com>
+Copyright (c) 2012 Marcin Kuzminski <marcin@python-works.com>
+
+This file was forked by the Kallithea project in July 2014.
+"""
+
+import logging
+import os
+import socket
+import traceback
+
+from dulwich.server import update_server_info
+from dulwich.web import GunzipFilter, LimitedInputFilter
+from webob import Request, Response, exc
+
+import kallithea
+from kallithea.lib.utils2 import ascii_bytes
+from kallithea.lib.vcs import subprocessio
+
+
+log = logging.getLogger(__name__)
+
+
+class FileWrapper(object):
+
+    def __init__(self, fd, content_length):
+        self.fd = fd
+        self.content_length = content_length
+        self.remain = content_length
+
+    def read(self, size):
+        if size <= self.remain:
+            try:
+                data = self.fd.read(size)
+            except socket.error:
+                raise IOError(self)
+            self.remain -= size
+        elif self.remain:
+            data = self.fd.read(self.remain)
+            self.remain = 0
+        else:
+            data = None
+        return data
+
+    def __repr__(self):
+        return '<FileWrapper %s len: %s, read: %s>' % (
+            self.fd, self.content_length, self.content_length - self.remain
+        )
+
+
+class GitRepository(object):
+    git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
+    commands = ['git-upload-pack', 'git-receive-pack']
+
+    def __init__(self, repo_name, content_path):
+        files = set([f.lower() for f in os.listdir(content_path)])
+        if not (self.git_folder_signature.intersection(files)
+                == self.git_folder_signature):
+            raise OSError('%s missing git signature' % content_path)
+        self.content_path = content_path
+        self.valid_accepts = ['application/x-%s-result' %
+                              c for c in self.commands]
+        self.repo_name = repo_name
+
+    def _get_fixedpath(self, path):
+        """
+        Small fix for repo_path
+
+        :param path:
+        """
+        assert path.startswith('/' + self.repo_name + '/')
+        return path[len(self.repo_name) + 2:].strip('/')
+
+    def inforefs(self, req, environ):
+        """
+        WSGI Response producer for HTTP GET Git Smart
+        HTTP /info/refs request.
+        """
+
+        git_command = req.GET.get('service')
+        if git_command not in self.commands:
+            log.debug('command %s not allowed', git_command)
+            return exc.HTTPMethodNotAllowed()
+
+        # From Documentation/technical/http-protocol.txt shipped with Git:
+        #
+        # Clients MUST verify the first pkt-line is `# service=$servicename`.
+        # Servers MUST set $servicename to be the request parameter value.
+        # Servers SHOULD include an LF at the end of this line.
+        # Clients MUST ignore an LF at the end of the line.
+        #
+        #  smart_reply     =  PKT-LINE("# service=$servicename" LF)
+        #                     ref_list
+        #                     "0000"
+        server_advert = '# service=%s\n' % git_command
+        packet_len = hex(len(server_advert) + 4)[2:].rjust(4, '0').lower()
+        _git_path = kallithea.CONFIG.get('git_path', 'git')
+        cmd = [_git_path, git_command[4:],
+               '--stateless-rpc', '--advertise-refs', self.content_path]
+        log.debug('handling cmd %s', cmd)
+        try:
+            out = subprocessio.SubprocessIOChunker(cmd,
+                starting_values=[ascii_bytes(packet_len + server_advert + '0000')]
+            )
+        except EnvironmentError as e:
+            log.error(traceback.format_exc())
+            raise exc.HTTPExpectationFailed()
+        resp = Response()
+        resp.content_type = 'application/x-%s-advertisement' % str(git_command)
+        resp.charset = None
+        resp.app_iter = out
+        return resp
+
+    def backend(self, req, environ):
+        """
+        WSGI Response producer for HTTP POST Git Smart HTTP requests.
+        Reads commands and data from HTTP POST's body.
+        returns an iterator obj with contents of git command's
+        response to stdout
+        """
+        _git_path = kallithea.CONFIG.get('git_path', 'git')
+        git_command = self._get_fixedpath(req.path_info)
+        if git_command not in self.commands:
+            log.debug('command %s not allowed', git_command)
+            return exc.HTTPMethodNotAllowed()
+
+        if 'CONTENT_LENGTH' in environ:
+            inputstream = FileWrapper(environ['wsgi.input'],
+                                      req.content_length)
+        else:
+            inputstream = environ['wsgi.input']
+
+        gitenv = dict(os.environ)
+        # forget all configs
+        gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
+        cmd = [_git_path, git_command[4:], '--stateless-rpc', self.content_path]
+        log.debug('handling cmd %s', cmd)
+        try:
+            out = subprocessio.SubprocessIOChunker(
+                cmd,
+                inputstream=inputstream,
+                env=gitenv,
+                cwd=self.content_path,
+            )
+        except EnvironmentError as e:
+            log.error(traceback.format_exc())
+            raise exc.HTTPExpectationFailed()
+
+        if git_command in ['git-receive-pack']:
+            # updating refs manually after each push.
+            # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
+
+            from kallithea.lib.vcs import get_repo
+            repo = get_repo(self.content_path)
+            if repo:
+                update_server_info(repo._repo)
+
+        resp = Response()
+        resp.content_type = 'application/x-%s-result' % git_command.encode('utf-8')
+        resp.charset = None
+        resp.app_iter = out
+        return resp
+
+    def __call__(self, environ, start_response):
+        req = Request(environ)
+        _path = self._get_fixedpath(req.path_info)
+        if _path.startswith('info/refs'):
+            app = self.inforefs
+        elif req.accept.acceptable_offers(self.valid_accepts):
+            app = self.backend
+        try:
+            resp = app(req, environ)
+        except exc.HTTPException as e:
+            resp = e
+            log.error(traceback.format_exc())
+        except Exception as e:
+            log.error(traceback.format_exc())
+            resp = exc.HTTPInternalServerError()
+        return resp(environ, start_response)
+
+
+class GitDirectory(object):
+
+    def __init__(self, repo_root, repo_name):
+        repo_location = os.path.join(repo_root, repo_name)
+        if not os.path.isdir(repo_location):
+            raise OSError(repo_location)
+
+        self.content_path = repo_location
+        self.repo_name = repo_name
+        self.repo_location = repo_location
+
+    def __call__(self, environ, start_response):
+        content_path = self.content_path
+        try:
+            app = GitRepository(self.repo_name, content_path)
+        except (AssertionError, OSError):
+            content_path = os.path.join(content_path, '.git')
+            if os.path.isdir(content_path):
+                app = GitRepository(self.repo_name, content_path)
+            else:
+                return exc.HTTPNotFound()(environ, start_response)
+        return app(environ, start_response)
+
+
+def make_wsgi_app(repo_name, repo_root):
+    app = GitDirectory(repo_root, repo_name)
+    return GunzipFilter(LimitedInputFilter(app))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/config/middleware/simplegit.py	Sat Oct 10 23:38:20 2020 +0200
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.simplegit
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+SimpleGit middleware for handling Git protocol requests (push/clone etc.)
+It's implemented with basic auth function
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Apr 28, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+
+"""
+
+
+import logging
+import re
+
+from kallithea.config.middleware.pygrack import make_wsgi_app
+from kallithea.lib.base import BaseVCSController, get_path_info
+from kallithea.lib.hooks import log_pull_action
+from kallithea.lib.utils import make_ui
+from kallithea.model.db import Repository
+
+
+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):
+
+    scm_alias = 'git'
+
+    @classmethod
+    def parse_request(cls, environ):
+        path_info = get_path_info(environ)
+        m = GIT_PROTO_PAT.match(path_info)
+        if m is None:
+            return None
+
+        class parsed_request(object):
+            # See https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols#_the_smart_protocol
+            repo_name = 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 _make_app(self, parsed_request):
+        """
+        Return a pygrack wsgi application.
+        """
+        pygrack_app = make_wsgi_app(parsed_request.repo_name, self.basepath)
+
+        def wrapper_app(environ, start_response):
+            if (parsed_request.cmd == 'info/refs' and
+                parsed_request.service == 'git-upload-pack'
+            ):
+                baseui = make_ui()
+                repo = Repository.get_by_repo_name(parsed_request.repo_name)
+                scm_repo = repo.scm_instance
+                # Run hooks, like Mercurial outgoing.pull_logger does
+                log_pull_action(ui=baseui, repo=scm_repo._repo)
+            # Note: push hooks are handled by post-receive hook
+
+            return pygrack_app(environ, start_response)
+
+        return wrapper_app
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/config/middleware/simplehg.py	Sat Oct 10 23:38:20 2020 +0200
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.simplehg
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+SimpleHg middleware for handling Mercurial protocol requests (push/clone etc.).
+It's implemented with basic auth function
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: Apr 28, 2010
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+
+"""
+
+
+import logging
+import os
+import urllib.parse
+
+import mercurial.hgweb
+
+from kallithea.lib.base import BaseVCSController, get_path_info
+from kallithea.lib.utils import make_ui
+from kallithea.lib.utils2 import safe_bytes
+
+
+log = logging.getLogger(__name__)
+
+
+def get_header_hgarg(environ):
+    """Decode the special Mercurial encoding of big requests over multiple headers.
+    >>> get_header_hgarg({})
+    ''
+    >>> get_header_hgarg({'HTTP_X_HGARG_0': ' ', 'HTTP_X_HGARG_1': 'a','HTTP_X_HGARG_2': '','HTTP_X_HGARG_3': 'b+c %20'})
+    'ab+c %20'
+    """
+    chunks = []
+    i = 1
+    while True:
+        v = environ.get('HTTP_X_HGARG_%d' % i)
+        if v is None:
+            break
+        chunks.append(v)
+        i += 1
+    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):
+
+    scm_alias = 'hg'
+
+    @classmethod
+    def parse_request(cls, environ):
+        http_accept = environ.get('HTTP_ACCEPT', '')
+        if not http_accept.startswith('application/mercurial'):
+            return None
+        path_info = get_path_info(environ)
+        if not path_info.startswith('/'): # it must!
+            return None
+
+        class parsed_request(object):
+            repo_name = 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.parse.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 _make_app(self, parsed_request):
+        """
+        Make an hgweb wsgi application.
+        """
+        repo_name = parsed_request.repo_name
+        repo_path = os.path.join(self.basepath, repo_name)
+        baseui = make_ui(repo_path=repo_path)
+        hgweb_app = mercurial.hgweb.hgweb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
+
+        def wrapper_app(environ, start_response):
+            environ['REPO_NAME'] = repo_name # used by mercurial.hgweb.hgweb
+            return hgweb_app(environ, start_response)
+
+        return wrapper_app
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kallithea/config/middleware/wrapper.py	Sat Oct 10 23:38:20 2020 +0200
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+kallithea.lib.middleware.wrapper
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Wrap app to measure request and response time ... all the way to the response
+WSGI iterator has been closed.
+
+This file was forked by the Kallithea project in July 2014.
+Original author and date, and relevant copyright and licensing information is below:
+:created_on: May 23, 2013
+:author: marcink
+:copyright: (c) 2013 RhodeCode GmbH, and others.
+:license: GPLv3, see LICENSE.md for more details.
+"""
+
+import logging
+import time
+
+from kallithea.lib.base import _get_ip_addr, get_path_info
+
+
+log = logging.getLogger(__name__)
+
+
+class Meter:
+
+    def __init__(self, start_response):
+        self._start_response = start_response
+        self._start = time.time()
+        self.status = None
+        self._size = 0
+
+    def duration(self):
+        return time.time() - self._start
+
+    def start_response(self, status, response_headers, exc_info=None):
+        self.status = status
+        write = self._start_response(status, response_headers, exc_info)
+        def metered_write(s):
+            self.measure(s)
+            write(s)
+        return metered_write
+
+    def measure(self, chunk):
+        self._size += len(chunk)
+
+    def size(self):
+        return self._size
+
+
+class ResultIter:
+
+    def __init__(self, result, meter, description):
+        self._result_close = getattr(result, 'close', None) or (lambda: None)
+        self._next = iter(result).__next__
+        self._meter = meter
+        self._description = description
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        chunk = self._next()
+        self._meter.measure(chunk)
+        return chunk
+
+    def close(self):
+        self._result_close()
+        log.info("%s responded %r after %.3fs with %s bytes", self._description, self._meter.status, self._meter.duration(), self._meter.size())
+
+
+class RequestWrapper(object):
+
+    def __init__(self, app, config):
+        self.application = app
+        self.config = config
+
+    def __call__(self, environ, start_response):
+        meter = Meter(start_response)
+        description = "Request from %s for %s" % (
+            _get_ip_addr(environ),
+            get_path_info(environ),
+        )
+        log.info("%s received", description)
+        try:
+            result = self.application(environ, meter.start_response)
+        finally:
+            log.info("%s responding %r after %.3fs", description, meter.status, meter.duration())
+        return ResultIter(result, meter, description)
--- a/kallithea/lib/middleware/__init__.py	Tue Oct 13 19:07:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
--- a/kallithea/lib/middleware/appenlight.py	Tue Oct 13 19:07:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.lib.middleware.appenlight
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-middleware to handle appenlight publishing of errors
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: October 18, 2012
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-try:
-    from appenlight_client import make_appenlight_middleware
-except ImportError:
-    AppEnlight = None
-else:
-    AppEnlight = make_appenlight_middleware
--- a/kallithea/lib/middleware/https_fixup.py	Tue Oct 13 19:07:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.lib.middleware.https_fixup
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-middleware to handle https correctly
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: May 23, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-
-from kallithea.lib.utils2 import asbool
-
-
-class HttpsFixup(object):
-
-    def __init__(self, app, config):
-        self.application = app
-        self.config = config
-
-    def __call__(self, environ, start_response):
-        self.__fixup(environ)
-        debug = asbool(self.config.get('debug'))
-        is_ssl = environ['wsgi.url_scheme'] == 'https'
-
-        def custom_start_response(status, headers, exc_info=None):
-            if is_ssl and asbool(self.config.get('use_htsts')) and not debug:
-                headers.append(('Strict-Transport-Security',
-                                'max-age=8640000; includeSubDomains'))
-            return start_response(status, headers, exc_info)
-
-        return self.application(environ, custom_start_response)
-
-    def __fixup(self, environ):
-        """
-        Function to fixup the environ as needed. In order to use this
-        middleware you should set this header inside your
-        proxy ie. nginx, apache etc.
-        """
-        # DETECT PROTOCOL !
-        if 'HTTP_X_URL_SCHEME' in environ:
-            proto = environ.get('HTTP_X_URL_SCHEME')
-        elif 'HTTP_X_FORWARDED_SCHEME' in environ:
-            proto = environ.get('HTTP_X_FORWARDED_SCHEME')
-        elif 'HTTP_X_FORWARDED_PROTO' in environ:
-            proto = environ.get('HTTP_X_FORWARDED_PROTO')
-        else:
-            proto = 'http'
-        org_proto = proto
-
-        # if we have force, just override
-        if asbool(self.config.get('force_https')):
-            proto = 'https'
-
-        environ['wsgi.url_scheme'] = proto
-        environ['wsgi._org_proto'] = org_proto
--- a/kallithea/lib/middleware/permanent_repo_url.py	Tue Oct 13 19:07:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.lib.middleware.permanent_repo_url
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-middleware to handle permanent repo URLs, replacing PATH_INFO '/_123/yada' with
-'/name/of/repo/yada' after looking 123 up in the database.
-"""
-
-
-from kallithea.lib.utils import fix_repo_id_name
-from kallithea.lib.utils2 import safe_bytes, safe_str
-
-
-class PermanentRepoUrl(object):
-
-    def __init__(self, app, config):
-        self.application = app
-        self.config = config
-
-    def __call__(self, environ, start_response):
-        # Extract path_info as get_path_info does, but do it explicitly because
-        # we also have to do the reverse operation when patching it back in
-        path_info = safe_str(environ['PATH_INFO'].encode('latin1'))
-        if path_info.startswith('/'): # it must
-            path_info = '/' + fix_repo_id_name(path_info[1:])
-            environ['PATH_INFO'] = safe_bytes(path_info).decode('latin1')
-
-        return self.application(environ, start_response)
--- a/kallithea/lib/middleware/pygrack.py	Tue Oct 13 19:07:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,229 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.lib.middleware.pygrack
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Python implementation of git-http-backend's Smart HTTP protocol
-
-Based on original code from git_http_backend.py project.
-
-Copyright (c) 2010 Daniel Dotsenko <dotsa@hotmail.com>
-Copyright (c) 2012 Marcin Kuzminski <marcin@python-works.com>
-
-This file was forked by the Kallithea project in July 2014.
-"""
-
-import logging
-import os
-import socket
-import traceback
-
-from dulwich.server import update_server_info
-from dulwich.web import GunzipFilter, LimitedInputFilter
-from webob import Request, Response, exc
-
-import kallithea
-from kallithea.lib.utils2 import ascii_bytes
-from kallithea.lib.vcs import subprocessio
-
-
-log = logging.getLogger(__name__)
-
-
-class FileWrapper(object):
-
-    def __init__(self, fd, content_length):
-        self.fd = fd
-        self.content_length = content_length
-        self.remain = content_length
-
-    def read(self, size):
-        if size <= self.remain:
-            try:
-                data = self.fd.read(size)
-            except socket.error:
-                raise IOError(self)
-            self.remain -= size
-        elif self.remain:
-            data = self.fd.read(self.remain)
-            self.remain = 0
-        else:
-            data = None
-        return data
-
-    def __repr__(self):
-        return '<FileWrapper %s len: %s, read: %s>' % (
-            self.fd, self.content_length, self.content_length - self.remain
-        )
-
-
-class GitRepository(object):
-    git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
-    commands = ['git-upload-pack', 'git-receive-pack']
-
-    def __init__(self, repo_name, content_path):
-        files = set([f.lower() for f in os.listdir(content_path)])
-        if not (self.git_folder_signature.intersection(files)
-                == self.git_folder_signature):
-            raise OSError('%s missing git signature' % content_path)
-        self.content_path = content_path
-        self.valid_accepts = ['application/x-%s-result' %
-                              c for c in self.commands]
-        self.repo_name = repo_name
-
-    def _get_fixedpath(self, path):
-        """
-        Small fix for repo_path
-
-        :param path:
-        """
-        assert path.startswith('/' + self.repo_name + '/')
-        return path[len(self.repo_name) + 2:].strip('/')
-
-    def inforefs(self, req, environ):
-        """
-        WSGI Response producer for HTTP GET Git Smart
-        HTTP /info/refs request.
-        """
-
-        git_command = req.GET.get('service')
-        if git_command not in self.commands:
-            log.debug('command %s not allowed', git_command)
-            return exc.HTTPMethodNotAllowed()
-
-        # From Documentation/technical/http-protocol.txt shipped with Git:
-        #
-        # Clients MUST verify the first pkt-line is `# service=$servicename`.
-        # Servers MUST set $servicename to be the request parameter value.
-        # Servers SHOULD include an LF at the end of this line.
-        # Clients MUST ignore an LF at the end of the line.
-        #
-        #  smart_reply     =  PKT-LINE("# service=$servicename" LF)
-        #                     ref_list
-        #                     "0000"
-        server_advert = '# service=%s\n' % git_command
-        packet_len = hex(len(server_advert) + 4)[2:].rjust(4, '0').lower()
-        _git_path = kallithea.CONFIG.get('git_path', 'git')
-        cmd = [_git_path, git_command[4:],
-               '--stateless-rpc', '--advertise-refs', self.content_path]
-        log.debug('handling cmd %s', cmd)
-        try:
-            out = subprocessio.SubprocessIOChunker(cmd,
-                starting_values=[ascii_bytes(packet_len + server_advert + '0000')]
-            )
-        except EnvironmentError as e:
-            log.error(traceback.format_exc())
-            raise exc.HTTPExpectationFailed()
-        resp = Response()
-        resp.content_type = 'application/x-%s-advertisement' % str(git_command)
-        resp.charset = None
-        resp.app_iter = out
-        return resp
-
-    def backend(self, req, environ):
-        """
-        WSGI Response producer for HTTP POST Git Smart HTTP requests.
-        Reads commands and data from HTTP POST's body.
-        returns an iterator obj with contents of git command's
-        response to stdout
-        """
-        _git_path = kallithea.CONFIG.get('git_path', 'git')
-        git_command = self._get_fixedpath(req.path_info)
-        if git_command not in self.commands:
-            log.debug('command %s not allowed', git_command)
-            return exc.HTTPMethodNotAllowed()
-
-        if 'CONTENT_LENGTH' in environ:
-            inputstream = FileWrapper(environ['wsgi.input'],
-                                      req.content_length)
-        else:
-            inputstream = environ['wsgi.input']
-
-        gitenv = dict(os.environ)
-        # forget all configs
-        gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
-        cmd = [_git_path, git_command[4:], '--stateless-rpc', self.content_path]
-        log.debug('handling cmd %s', cmd)
-        try:
-            out = subprocessio.SubprocessIOChunker(
-                cmd,
-                inputstream=inputstream,
-                env=gitenv,
-                cwd=self.content_path,
-            )
-        except EnvironmentError as e:
-            log.error(traceback.format_exc())
-            raise exc.HTTPExpectationFailed()
-
-        if git_command in ['git-receive-pack']:
-            # updating refs manually after each push.
-            # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
-
-            from kallithea.lib.vcs import get_repo
-            repo = get_repo(self.content_path)
-            if repo:
-                update_server_info(repo._repo)
-
-        resp = Response()
-        resp.content_type = 'application/x-%s-result' % git_command.encode('utf-8')
-        resp.charset = None
-        resp.app_iter = out
-        return resp
-
-    def __call__(self, environ, start_response):
-        req = Request(environ)
-        _path = self._get_fixedpath(req.path_info)
-        if _path.startswith('info/refs'):
-            app = self.inforefs
-        elif req.accept.acceptable_offers(self.valid_accepts):
-            app = self.backend
-        try:
-            resp = app(req, environ)
-        except exc.HTTPException as e:
-            resp = e
-            log.error(traceback.format_exc())
-        except Exception as e:
-            log.error(traceback.format_exc())
-            resp = exc.HTTPInternalServerError()
-        return resp(environ, start_response)
-
-
-class GitDirectory(object):
-
-    def __init__(self, repo_root, repo_name):
-        repo_location = os.path.join(repo_root, repo_name)
-        if not os.path.isdir(repo_location):
-            raise OSError(repo_location)
-
-        self.content_path = repo_location
-        self.repo_name = repo_name
-        self.repo_location = repo_location
-
-    def __call__(self, environ, start_response):
-        content_path = self.content_path
-        try:
-            app = GitRepository(self.repo_name, content_path)
-        except (AssertionError, OSError):
-            content_path = os.path.join(content_path, '.git')
-            if os.path.isdir(content_path):
-                app = GitRepository(self.repo_name, content_path)
-            else:
-                return exc.HTTPNotFound()(environ, start_response)
-        return app(environ, start_response)
-
-
-def make_wsgi_app(repo_name, repo_root):
-    app = GitDirectory(repo_root, repo_name)
-    return GunzipFilter(LimitedInputFilter(app))
--- a/kallithea/lib/middleware/simplegit.py	Tue Oct 13 19:07:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.lib.middleware.simplegit
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-SimpleGit middleware for handling Git protocol requests (push/clone etc.)
-It's implemented with basic auth function
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 28, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-
-"""
-
-
-import logging
-import re
-
-from kallithea.lib.base import BaseVCSController, get_path_info
-from kallithea.lib.hooks import log_pull_action
-from kallithea.lib.middleware.pygrack import make_wsgi_app
-from kallithea.lib.utils import make_ui
-from kallithea.model.db import Repository
-
-
-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):
-
-    scm_alias = 'git'
-
-    @classmethod
-    def parse_request(cls, environ):
-        path_info = get_path_info(environ)
-        m = GIT_PROTO_PAT.match(path_info)
-        if m is None:
-            return None
-
-        class parsed_request(object):
-            # See https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols#_the_smart_protocol
-            repo_name = 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 _make_app(self, parsed_request):
-        """
-        Return a pygrack wsgi application.
-        """
-        pygrack_app = make_wsgi_app(parsed_request.repo_name, self.basepath)
-
-        def wrapper_app(environ, start_response):
-            if (parsed_request.cmd == 'info/refs' and
-                parsed_request.service == 'git-upload-pack'
-            ):
-                baseui = make_ui()
-                repo = Repository.get_by_repo_name(parsed_request.repo_name)
-                scm_repo = repo.scm_instance
-                # Run hooks, like Mercurial outgoing.pull_logger does
-                log_pull_action(ui=baseui, repo=scm_repo._repo)
-            # Note: push hooks are handled by post-receive hook
-
-            return pygrack_app(environ, start_response)
-
-        return wrapper_app
--- a/kallithea/lib/middleware/simplehg.py	Tue Oct 13 19:07:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.lib.middleware.simplehg
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-SimpleHg middleware for handling Mercurial protocol requests (push/clone etc.).
-It's implemented with basic auth function
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: Apr 28, 2010
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-
-"""
-
-
-import logging
-import os
-import urllib.parse
-
-import mercurial.hgweb
-
-from kallithea.lib.base import BaseVCSController, get_path_info
-from kallithea.lib.utils import make_ui
-from kallithea.lib.utils2 import safe_bytes
-
-
-log = logging.getLogger(__name__)
-
-
-def get_header_hgarg(environ):
-    """Decode the special Mercurial encoding of big requests over multiple headers.
-    >>> get_header_hgarg({})
-    ''
-    >>> get_header_hgarg({'HTTP_X_HGARG_0': ' ', 'HTTP_X_HGARG_1': 'a','HTTP_X_HGARG_2': '','HTTP_X_HGARG_3': 'b+c %20'})
-    'ab+c %20'
-    """
-    chunks = []
-    i = 1
-    while True:
-        v = environ.get('HTTP_X_HGARG_%d' % i)
-        if v is None:
-            break
-        chunks.append(v)
-        i += 1
-    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):
-
-    scm_alias = 'hg'
-
-    @classmethod
-    def parse_request(cls, environ):
-        http_accept = environ.get('HTTP_ACCEPT', '')
-        if not http_accept.startswith('application/mercurial'):
-            return None
-        path_info = get_path_info(environ)
-        if not path_info.startswith('/'): # it must!
-            return None
-
-        class parsed_request(object):
-            repo_name = 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.parse.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 _make_app(self, parsed_request):
-        """
-        Make an hgweb wsgi application.
-        """
-        repo_name = parsed_request.repo_name
-        repo_path = os.path.join(self.basepath, repo_name)
-        baseui = make_ui(repo_path=repo_path)
-        hgweb_app = mercurial.hgweb.hgweb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
-
-        def wrapper_app(environ, start_response):
-            environ['REPO_NAME'] = repo_name # used by mercurial.hgweb.hgweb
-            return hgweb_app(environ, start_response)
-
-        return wrapper_app
--- a/kallithea/lib/middleware/wrapper.py	Tue Oct 13 19:07:59 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-# -*- coding: utf-8 -*-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-kallithea.lib.middleware.wrapper
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Wrap app to measure request and response time ... all the way to the response
-WSGI iterator has been closed.
-
-This file was forked by the Kallithea project in July 2014.
-Original author and date, and relevant copyright and licensing information is below:
-:created_on: May 23, 2013
-:author: marcink
-:copyright: (c) 2013 RhodeCode GmbH, and others.
-:license: GPLv3, see LICENSE.md for more details.
-"""
-
-import logging
-import time
-
-from kallithea.lib.base import _get_ip_addr, get_path_info
-
-
-log = logging.getLogger(__name__)
-
-
-class Meter:
-
-    def __init__(self, start_response):
-        self._start_response = start_response
-        self._start = time.time()
-        self.status = None
-        self._size = 0
-
-    def duration(self):
-        return time.time() - self._start
-
-    def start_response(self, status, response_headers, exc_info=None):
-        self.status = status
-        write = self._start_response(status, response_headers, exc_info)
-        def metered_write(s):
-            self.measure(s)
-            write(s)
-        return metered_write
-
-    def measure(self, chunk):
-        self._size += len(chunk)
-
-    def size(self):
-        return self._size
-
-
-class ResultIter:
-
-    def __init__(self, result, meter, description):
-        self._result_close = getattr(result, 'close', None) or (lambda: None)
-        self._next = iter(result).__next__
-        self._meter = meter
-        self._description = description
-
-    def __iter__(self):
-        return self
-
-    def __next__(self):
-        chunk = self._next()
-        self._meter.measure(chunk)
-        return chunk
-
-    def close(self):
-        self._result_close()
-        log.info("%s responded %r after %.3fs with %s bytes", self._description, self._meter.status, self._meter.duration(), self._meter.size())
-
-
-class RequestWrapper(object):
-
-    def __init__(self, app, config):
-        self.application = app
-        self.config = config
-
-    def __call__(self, environ, start_response):
-        meter = Meter(start_response)
-        description = "Request from %s for %s" % (
-            _get_ip_addr(environ),
-            get_path_info(environ),
-        )
-        log.info("%s received", description)
-        try:
-            result = self.application(environ, meter.start_response)
-        finally:
-            log.info("%s responding %r after %.3fs", description, meter.status, meter.duration())
-        return ResultIter(result, meter, description)