#!/usr/bin/env python3 """HTTP proxy for Gitea LFS - rewrites Host header to bypass ICP domain block. Acts as both: - Forward proxy: receives "GET http://git.zz.com:3000/..." and rewrites Host to IP - Reverse proxy: receives "GET /..." directly and forwards to upstream with IP Host Usage: python3 lfs-proxy.py [port] """ import http.client import http.server import sys from urllib.parse import urlparse LOCAL_PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 13000 REMOTE_DOMAIN = "git.zz.com" UPSTREAM_IP = "62.234.191.215" UPSTREAM_PORT = 3000 class ProxyHandler(http.server.BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" def _proxy_request(self, target_host, target_port, target_path, body=None): body_len = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(body_len) if body_len else None # Always rewrite Host to IP for upstream conn = http.client.HTTPConnection(target_host, target_port, timeout=120) try: headers = {} skip = {"host", "transfer-encoding", "connection"} for k in self.headers: if k.lower() not in skip: headers[k] = self.headers[k] headers["Host"] = f"{UPSTREAM_IP}:{UPSTREAM_PORT}" if body_len: headers["Content-Length"] = str(body_len) conn.request(self.command, target_path, body=body, headers=headers) resp = conn.getresponse() resp_body = resp.read() self.send_response(resp.status) sent_cl = False for k, v in resp.getheaders(): kl = k.lower() if kl in ("transfer-encoding", "connection"): continue if kl == "content-length": self.send_header(k, len(resp_body)) sent_cl = True continue self.send_header(k, v) if not sent_cl: self.send_header("Content-Length", len(resp_body)) self.end_headers() self.wfile.write(resp_body) finally: conn.close() def _do(self): raw_path = self.path parsed = urlparse(raw_path) if parsed.hostname and parsed.hostname != "127.0.0.1" and parsed.hostname != "localhost": # Forward proxy mode: full URL like "http://git.zz.com:3000/path" target_host = UPSTREAM_IP if parsed.hostname == REMOTE_DOMAIN else parsed.hostname target_port = parsed.port or 80 target_path = parsed.path + ("?" + parsed.query if parsed.query else "") self._proxy_request(target_host, target_port, target_path) else: # Direct/reverse proxy mode: path like "/Mustapha/lfs-test.git/..." # This happens when LFS downloads from URLs rewritten to point at us self._proxy_request(UPSTREAM_IP, UPSTREAM_PORT, raw_path) do_GET = do_POST = do_PUT = do_DELETE = do_HEAD = do_PATCH = _do def log_message(self, fmt, *args): print(f"[proxy] {self.address_string()} - {fmt % args}", file=sys.stderr, flush=True) if __name__ == "__main__": with http.server.ThreadingHTTPServer(("127.0.0.1", LOCAL_PORT), ProxyHandler) as s: print(f"Proxy: 127.0.0.1:{LOCAL_PORT} -> {UPSTREAM_IP}:{UPSTREAM_PORT}", file=sys.stderr, flush=True) s.serve_forever()