From bdfc5d2d092fdce8fb4149ae8acb86b63c14c642 Mon Sep 17 00:00:00 2001 From: HampusM Date: Thu, 27 May 2021 21:46:44 +0200 Subject: Implemented cloning in backend --- src/api/git.js | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/app.js | 47 +++++++++++++++++++++++++++++++------ 2 files changed, 113 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/api/git.js b/src/api/git.js index 756136b..3697f98 100644 --- a/src/api/git.js +++ b/src/api/git.js @@ -1,6 +1,10 @@ const { formatDistance } = require('date-fns'); const fs = require('fs'); const git = require("nodegit"); +const zlib = require("zlib"); +const { spawn } = require('child_process'); +const whatwg = require("whatwg-url"); +const path = require("path"); function addRepoDirSuffix(repo_name) { @@ -244,8 +248,76 @@ async function doesCommitExist(base_dir, repo_name, commit_oid) } } +function connectToGitHTTPBackend(base_dir, req, reply) +{ + const url_path = req.url.replace(req.params.repo, req.params.repo + ".git"); + const repo = req.params.repo + ".git"; + const repo_path = path.join(base_dir, repo); + + req = req.headers['Content-Encoding'] == 'gzip' ? req.pipe(zlib.createGunzip()) : req; + + const parsed_url = new whatwg.URL(`${req.protocol}://${req.hostname}${url_path}`); + const url_path_parts = parsed_url.pathname.split('/'); + + let service; + let info = false; + + if(/\/info\/refs$/.test(parsed_url.pathname)) { + service = parsed_url.searchParams.get("service"); + info = true; + } + else { + service = url_path_parts[url_path_parts.length-1]; + } + + const content_type = `application/x-${service}-${info ? "advertisement" : "result"}`; + + if(/\.\/|\.\./.test(parsed_url.pathname)) { + reply.header("Content-Type", content_type); + reply.code(404).send("Git repository not found!\n"); + return; + } + + if(service !== 'git-upload-pack') { + reply.header("Content-Type", content_type); + reply.code(403).send("Access denied!\n"); + return; + } + + reply.raw.writeHead(200, { "Content-Type": content_type }); + + const spawn_args = [ "--stateless-rpc", repo_path ]; + + if(info) { + spawn_args.push("--advertise-refs"); + } + + const git_pack = spawn(service, spawn_args); + + if(info) { + const s = '# service=' + service + '\n'; + const n = (4 + s.length).toString(16); + reply.raw.write(Buffer.from((Array(4 - n.length + 1).join('0') + n + s) + '0000')); + } + else { + req.body.on("data", (data) => git_pack.stdin.write(data)); + req.body.on("close", () => git_pack.stdin.end()); + } + + git_pack.on("error", (err) => console.log(err)) + git_pack.stderr.on("data", (stderr) => console.log(stderr)); + + git_pack.stdout.on("data", (data) => + { + reply.raw.write(data); + }); + + git_pack.on("close", () => reply.raw.end()); +} + module.exports.getLog = getLog; module.exports.getRepos = getRepos; module.exports.getRepoFile = getRepoFile; module.exports.getCommit = getCommit; -module.exports.doesCommitExist = doesCommitExist; \ No newline at end of file +module.exports.doesCommitExist = doesCommitExist; +module.exports.connectToGitHTTPBackend = connectToGitHTTPBackend; \ No newline at end of file diff --git a/src/app.js b/src/app.js index 14b8188..57f0ca1 100644 --- a/src/app.js +++ b/src/app.js @@ -6,6 +6,7 @@ const fs = require('fs'); const { exit } = require("process"); const path = require("path"); const util = require("./api/util"); +const git = require("./api/git"); const settings = yaml.load(fs.readFileSync(__dirname + "/../settings.yml", 'utf8')); const settings_keys = Object.keys(settings); @@ -44,12 +45,9 @@ fastify.setNotFoundHandler({ reply.send("404: Not found"); }); -fastify.route({ - method: "GET", - path: "/app.html", - handler: (req, reply) => reply.redirect("/") -}); +fastify.addContentTypeParser("application/x-git-upload-pack-request", (req, payload, done) => done(null, payload)); + fastify.register(fastify_static, { root: dist_dir }) fastify.register(api, { prefix: "/api/v1", config: { settings: settings } }); @@ -64,6 +62,12 @@ fastify.route({ } }); +fastify.route({ + method: "GET", + path: "/app.html", + handler: (req, reply) => reply.redirect("/") +}); + fastify.register((fastify_repo, opts, done) => { fastify_repo.setNotFoundHandler({ @@ -79,10 +83,10 @@ fastify.register((fastify_repo, opts, done) => const repo_verification = await util.verifyRepoName(req.params.repo, settings.base_dir); if(repo_verification !== true) { if(repo_verification === "ERR_REPO_REGEX") { - reply.code(400).send("Error: Unacceptable git repository name!"); + reply.code(400).send("Unacceptable git repository name!\n"); } else if(repo_verification === "ERR_REPO_NOT_FOUND") { - reply.code(404).send("Error: Git repository not found!"); + reply.code(404).send("Git repository not found!\n"); } } }); @@ -113,6 +117,35 @@ fastify.register((fastify_repo, opts, done) => } }); + fastify_repo.route({ + method: "GET", + path: "/info/refs", + handler: (req, reply) => + { + if(!req.query.service) { + reply.code(403).send("Missing service query parameter\n"); + return + } + else if(req.query.service !== "git-upload-pack") { + reply.code(403).send("Access denied!\n"); + return; + } + else if(Object.keys(req.query).length !== 1) { + reply.header("Content-Type", "application/x-git-upload-pack-advertisement"); + reply.code(403).send("Too many query parameters!\n"); + return; + } + + git.connectToGitHTTPBackend(settings["base_dir"], req, reply); + } + }); + + fastify_repo.route({ + method: "POST", + path: "/git-upload-pack", + handler: (req, reply) => git.connectToGitHTTPBackend(settings["base_dir"], req, reply) + }); + done(); }, { prefix: "/:repo" }); -- cgit v1.2.3-18-g5258