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 --- package-lock.json | 115 +++--------------------------------------------------- package.json | 3 +- src/api/git.js | 74 ++++++++++++++++++++++++++++++++++- src/app.js | 47 ++++++++++++++++++---- 4 files changed, 121 insertions(+), 118 deletions(-) diff --git a/package-lock.json b/package-lock.json index c73238e..2f28bfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "nodegit": "^0.27.0", "vue": "^3.0.11", "vue-loading-overlay": "^4.0.2", - "vue-router": "^4.0.8" + "vue-router": "^4.0.8", + "whatwg-url": "^7.1.0" }, "devDependencies": { "@parcel/transformer-sass": "^2.0.0-beta.2", @@ -4585,15 +4586,6 @@ "node": ">= 0.10.0" } }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/console-browserify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", @@ -6680,15 +6672,6 @@ "node": ">=0.10.0" } }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/expand-brackets/node_modules/define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -7124,15 +7107,6 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/find-my-way": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-4.1.0.tgz", @@ -8844,8 +8818,7 @@ "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, "node_modules/lodash.truncate": { "version": "4.4.2", @@ -15244,15 +15217,6 @@ "node": ">=0.10.0" } }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/snapdragon/node_modules/define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -16322,7 +16286,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -16561,15 +16524,6 @@ "debug": "^2.2.0" } }, - "node_modules/undefsafe/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -17042,8 +16996,7 @@ "node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" }, "node_modules/whatwg-encoding": { "version": "1.0.5", @@ -17064,7 +17017,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -21375,17 +21327,6 @@ "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, "console-browserify": { @@ -23105,15 +23046,6 @@ "to-regex": "^3.0.1" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -23480,17 +23412,6 @@ "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, "find-my-way": { @@ -24901,8 +24822,7 @@ "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" }, "lodash.truncate": { "version": "4.4.2", @@ -30139,15 +30059,6 @@ "use": "^3.1.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -31086,7 +30997,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -31282,17 +31192,6 @@ "dev": true, "requires": { "debug": "^2.2.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, "unicode-canonical-property-names-ecmascript": { @@ -31695,8 +31594,7 @@ "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" }, "whatwg-encoding": { "version": "1.0.5", @@ -31717,7 +31615,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, "requires": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", diff --git a/package.json b/package.json index 0e206ff..e69341c 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "nodegit": "^0.27.0", "vue": "^3.0.11", "vue-loading-overlay": "^4.0.2", - "vue-router": "^4.0.8" + "vue-router": "^4.0.8", + "whatwg-url": "^7.1.0" }, "devDependencies": { "@parcel/transformer-sass": "^2.0.0-beta.2", 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