summaryrefslogtreecommitdiffstats
path: root/run.py
blob: a86e50099f9d7f249e3021c4f247b8e1d691ef54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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 configure_static_folder, configure_static_serving
    from srht.debug import configure_static_arguments, build_parser, run_app
    from gitsrht.app import 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)