http{/,s} git server
git clone http://git.nthia.dev/qwgit
const path = require('path')
const { pipeline } = require('stream')
const parseParams = require('./parse-params.js')
const checkHeaders = ['origin','referrer']
module.exports = function (qwgit, req, res, ids) {
let m = null
if (!req.url.startsWith('/api/v1/')) return error(res, 404, 'not found')
let u = req.url.slice('/api/v1'.length)
let ct = req.headers['content-type']?.trim()?.toLowerCase()
if (req.method === 'POST' && ct !== 'application/x-www-form-urlencoded') {
return error(res, 400, `expected content-type: application/x-www-form-urlencoded, found: ${ct}`)
}
// don't allow api access originating from other domains
let host = req.headers.host
if (host === undefined) return error(res, 400, `host header required for api access`)
for (let i = 0; i < checkHeaders.length; i++) {
let key = checkHeaders[i]
if (Object.hasOwnProperty.call(req.headers,key) && req.headers[key] !== host) {
let u = null
try { u = new URL(req.headers[key]) }
catch (err) {
return error(res, 400, `${key} header is not a valid url`)
}
if (host !== u.host) {
return error(res, 400, `host header does not match ${key} header`)
}
}
}
if (req.method === 'POST' && u === '/repo/create') {
if (ids.length === 0) return error(res, 401, 'must authenticate to create repos')
pipeline(req, parseParams(4096, (err,params) => {
if (err) return error(res, 400, err)
let r = {
paths: params.getAll('path'),
list: params.getAll('list'),
push: params.getAll('push'),
pull: params.getAll('pull'),
name: params.get('name'),
description: params.get('description'),
tags: params.getAll('tag'),
}
if (r.paths.length === 0) return error(res, 400, 'a new repo must have one or more paths')
for (let i = 0; i < r.paths.length; i++) {
if (!qwgit.repos.isAuthorized('create', r.paths[i], ids)) {
return error(res, 403, 'not authorized to create a repo at path: ' + r.paths[i])
}
}
qwgit.repos.create(r, (err,repo) => {
if (err) return error(res, 400, `failed to create repo directory: ${err}`)
else json(res, 200, { id: repo.id })
})
}), err => {})
} else if (req.method === 'POST' && (m = /^\/repo\/([^\/]+)\/update$/.exec(u))) {
let rid = m[1]
pipeline(req, parseParams(4096, (err,params) => {
if (err) return error(res, 400, err)
let repo = qwgit.repos.get(rid)
if (repo === null) error(res, 404, 'repo not found')
else {
try { handleUpdate(qwgit, res, rid, repo, params) }
catch (err) { error(res, 500, err) }
}
}), err => {})
} else if (req.method === 'POST' && (m = /^\/repo-from-path\/(.+)\/update$/.exec(u))) {
let rpath = m[1]
pipeline(req, parseParams(4096, (err,params) => {
if (err) return error(res, 400, err)
let repo = qwgit.repos.getFromPath(rpath)
if (repo === null) error(res, 404, 'repo not found')
else {
try { handleUpdate(qwgit, res, repo.id, repo, params) }
catch (err) { error(res, 500, err) }
}
}), err => {})
} else if (req.method === 'POST' && (m = /^\/repo\/([^\/]+)\/remove$/.exec(u))) {
let rid = m[1]
pipeline(req, parseParams(4096, (err,params) => {
if (err) return error(res, 400, err)
qwgit.repos.remove(rid, err => {
if (err) error(res, 500, err)
else json(res, 200, { status: 'ok', info: `successfully removed git repo ${rid}` })
})
}), err => {})
} else if (req.method === 'POST' && (m = /^\/repo-from-path\/([^\/]+)\/remove$/.exec(u))) {
let rpath = m[1]
pipeline(req, parseParams(4096, (err,params) => {
if (err) return error(res, 400, err)
let repo = qwgit.repos.getFromPath(rpath)
if (repo === null) error(res, 404, 'repo not found')
qwgit.repos.remove(repo.id, err => {
if (err) error(res, 500, err)
else json(res, 200, { status: 'ok', info: `successfully removed git repo ${repo.id}` })
})
}), err => {})
} else if (req.method === 'GET' && (m = /^\/repo\/([^\/]+)\/info$/.exec(u))) {
let repo = qwgit.repos.get(m[1])
if (repo === null) error(res, 404, 'repo not found')
else json(res, 200, repo)
} else if (req.method === 'GET' && (m = /^\/repo-from-path\/([^\/]+)\/info$/.exec(u))) {
let repo = qwgit.repos.getFromPath(m[1])
if (repo === null) error(res, 404, 'repo not found')
else json(res, 200, repo)
} else if (req.method === 'GET' && u === '/repo/list') {
let list = qwgit.repos.listAuthorized(ids)
json(res, 200, list)
} else if (req.method === 'POST' && u === '/config/reload') {
pipeline(req, parseParams(4096, (err,params) => {
if (err) return error(res, 400, err)
qwgit.emit('reloadConfig')
json(res, 200, {})
}), (err) => {})
} else {
error(res, 404, 'not found')
}
}
function error(res, code, err) {
res.writeHead(code, { 'content-type': 'text/plain; charset=utf-8' })
return res.end(String(err?.message ?? err) + '\n')
}
function json(res, code, obj) {
res.writeHead(code, { 'content-type': 'application/json; charset=utf-8' })
res.end(JSON.stringify(obj,null,2)+'\n')
}
function handleUpdate(qwgit, res, rid, repo, params) {
let r = Object.assign({}, repo, {
id: params.get('id') ?? rid,
})
console.log('handle r=',r)
let plural = { list: 'lists', push: 'pushes', pull: 'pulls', tag: 'tags' }
let mods = [['paths','path'],['list'],['push'],['pull'],['tag']]
mods.forEach(ms => {
if (!Array.isArray(r[ms[0]])) r[ms[0]] = []
let xs = ms.flatMap(m => params.getAll(m) ?? [])
if (xs.length === 1 && xs[0] === '') r[xs[0]] = []
else if (xs.length > 0) r[ms[0]] = xs
let rs = ms.flatMap(m => params.getAll(m + ':remove') ?? [])
if (rs.length > 0 && Array.isArray(r[ms[0]])) {
r[ms[0]] = r[ms[0]].filter(x => !rs.includes(x))
}
let as = ms.flatMap(m => params.getAll(m + ':add') ?? [])
if (as.length > 0 && Array.isArray(r[ms[0]])) {
r[ms[0]] = Array.from(new Set(r[ms[0]].concat(as)))
}
if (params.has(ms[0] + ':clear')) r[ms[0]] = []
if (plural.hasOwnProperty(ms[0]) && params.has(plural[ms[0]] + ':clear')) r[ms[0]] = []
})
let name = params.get('name')
if (name !== undefined) r.name = name
let description = params.get('description')
if (description !== undefined) r.description = description
qwgit.repos.update(rid, r, (err,repo) => {
if (err) return error(res, 400, err)
if (r.id !== undefined && r.id !== rid) {
json(res, 200, { status: 'ok', info: `successfully updated git repo ${rid} => ${r.id}` })
} else {
json(res, 200, { status: 'ok', info: `successfully updated git repo ${rid}` })
}
})
}