diff --git a/pyproject.toml b/pyproject.toml index c5b1920..a5160bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] -dependencies = ["dulwich", "flask", "markdown2[all]"] +dependencies = ["dulwich", "flask", "markdown2[all]", "requests"] [project.scripts] gitcloak = "gitcloak.app:main" diff --git a/src/gitcloak/app.py b/src/gitcloak/app.py index 0d0de66..8956ff0 100644 --- a/src/gitcloak/app.py +++ b/src/gitcloak/app.py @@ -1,4 +1,13 @@ -from flask import Flask, render_template, abort, send_from_directory, Response +from flask import ( + Flask, + render_template, + abort, + send_from_directory, + Response, + request, + redirect, +) +import requests from .classes.git import Git from .classes.markdown import RelativeURLRewriter import logging @@ -206,6 +215,74 @@ def preview_file(owner: str, repo: str, file_path: str): abort(404, description=str(e)) +@app.route("/.git", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) +@app.route( + "/.git/", + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], +) +def proxy_git(subpath, extra=None): + """Route for proxying Git client requests to GitHub. + + Args: + subpath (str): The subpath of the request. + extra (str): An optional extra path after .git. + + Returns: + Response: A response from the repository on GitHub. + """ + path = f"{subpath}.git" + if extra: + path += f"/{extra}" + github_url = f"https://github.com/{path}" + + logger.debug(f"Proxying Git request to {github_url}") + + resp = requests.request( + method=request.method, + url=github_url, + headers={ + key: value + for (key, value) in request.headers.items() + if key.lower() != "host" + and not key.lower().startswith("x-forwarded") + and not key.lower().startswith("forwarded") + }, + data=request.get_data(), + cookies=request.cookies, + params=request.args, + allow_redirects=False, + stream=True, + ) + + # Those headers shouldn't be passed directly to the client + excluded_headers = [ + "content-length", + "content-encoding", + "transfer-encoding", + "connection", + ] + + headers = [ + (name, value) + for (name, value) in resp.raw.headers.items() + if name.lower() not in excluded_headers + ] + + return Response(resp.raw, status=resp.status_code, headers=dict(headers)) + + +@app.before_request +def catch_git_requests(): + if "git/" in request.headers.get("User-Agent", ""): + try: + path = request.path.lstrip("/") + path = path.split("/") + return proxy_git("/".join(path[:2]), "/".join(path[2:]) or None) + except Exception as e: + logger.error(f"Error proxying Git request: {e}") + abort(404, description=str(e)) + + def main(): """Main function to run the Flask app.""" port = os.environ.get("PORT", 8107)