diff --git a/run.py b/run.py index 1a4417d..a86e500 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,97 @@ +import logging +import os.path +import re +import subprocess +from srht.config import cfg +from werkzeug.exceptions import BadRequest +from werkzeug.wrappers import Request, Response +from werkzeug.wsgi import wrap_file + +def configure_git_arguments(parser): + parser.add_argument('--http-serve', action='store_true', + help="Also serve the Git repositories for HTTP cloning.") + +def configure_git_app(app, args): + if not args.http_serve: + return + + gitreposdir = cfg('git.sr.ht', 'repos') + print("Serving git repos from {}".format(gitreposdir)) + app.wsgi_app = HttpGitRepos(app.wsgi_app, gitreposdir) + +re_git1 = re.compile( + r"^.*/objects/([0-9a-f]+/[0-9a-f]+|pack/pack-[0-9a-f]+.(pack|idx)).*$") +re_git2 = re.compile( + r"^.*/(HEAD|info/refs|objects/info/.*|git-(upload|receive)-pack).*$") + +logger = logging.getLogger('werkzeug') + +class HttpGitRepos: + def __init__(self, app, reposdir, ssl=None): + self._app = app + self._reposdir = reposdir + self._ssl = None + + def __call__(self, environ, start_response): + request = Request(environ) + + if re_git1.search(request.path): + path = os.path.join(self._reposdir, request.path.lstrip('/')) + if os.path.exists(path): + f = wrap_file(environ, open(path)) + return Response(f, direct_passthrough=True) + + if re_git2.search(request.path): + subenv = environ.copy() + for k in list(subenv.keys()): + if (k.startswith('wsgi') or k.startswith('werkzeug') or + type(subenv[k]) is not str): + del subenv[k] + + subenv['GIT_PROJECT_ROOT'] = self._reposdir + subenv['GIT_HTTP_EXPORT_ALL'] = "1" + p = subprocess.Popen(['git', 'http-backend'], + cwd=self._reposdir, env=subenv, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + try: + stdin = request.data + stdout, stderr = p.communicate(input=stdin, timeout=30) + except subprocess.TimeoutExpired: + logger.warning("Git HTTP backend timed out:") + logger.warning(stderr.decode()) + return BadRequest()(environ, start_response) + + sep = stdout.find(b'\r\n\r\n') + headers = [] + body_start = 0 + if sep > 0: + body_start = sep + 4 + raw_headers = stdout[:sep].decode() + for i, line in enumerate(raw_headers.split('\r\n')): + sepidx = line.find(':') + if sepidx > 0: + headers.append((line[:sepidx], line[sepidx+1:].lstrip())) + else: + logger.warning("Skipping malformed header: %s" % line) + + if stderr: + logger.warning("Errors while serving Git repo:") + logger.warning(stderr.decode()) + body = stdout[body_start:] + r = Response(body, headers=headers) + return r(environ, start_response) + + return self._app(environ, start_response) + if __name__ == '__main__': - from srht.debug import run_service + from srht.debug import configure_static_folder, configure_static_serving + from srht.debug import configure_static_arguments, build_parser, run_app from gitsrht.app import app - run_service(app) + configure_static_folder(app) + parser = build_parser(app) + configure_static_arguments(parser) + configure_git_arguments(parser) + args = parser.parse_args() + configure_static_serving(app, args) + configure_git_app(app, args) + run_app(app)