lib/http-backend.js - git - Webernet Explorer _X
git 🏠 Home 👤 Sign up Self hosted git, retro shell
★ otgit, code hosting from the year 2000 ★
📄 octuna / lib / http-backend.js
103 lines · 3.3 KB · Raw
1const { spawn, spawnSync } = require('child_process');
2const path = require('path');
3const fs = require('fs');
4const config = require('./config');
5const git = require('./git');
6const log = require('./log');
7const respond = require('./respond');
8 
9const SEP_CRLFCRLF = Buffer.from('\r\n\r\n');
10const SEP_LFLF = Buffer.from('\n\n');
11 
12let _backendPath = null;
13 
14function backendPath() {
15 if (_backendPath) return _backendPath;
16 const configured = config.get('git.httpBackendPath');
17 if (configured && fs.existsSync(configured)) { _backendPath = configured; return _backendPath; }
18 const result = spawnSync(config.get('git.binPath') || 'git', ['--exec-path'],
19 { encoding: 'utf8', timeout: 3000 });
20 if (result.status === 0) {
21 const execDir = result.stdout.trim();
22 const name = process.platform === 'win32' ? 'git-http-backend.exe' : 'git-http-backend';
23 const candidate = path.join(execDir, name);
24 if (fs.existsSync(candidate)) { _backendPath = candidate; return _backendPath; }
25 }
26 throw new Error('git-http-backend not found. Set git.httpBackendPath in admin settings.');
27}
28 
29function findSep(buf) {
30 const i4 = buf.indexOf(SEP_CRLFCRLF);
31 const i2 = buf.indexOf(SEP_LFLF);
32 if (i4 >= 0 && (i2 < 0 || i4 <= i2)) return { at: i4, len: 4 };
33 if (i2 >= 0) return { at: i2, len: 2 };
34 return null;
35}
36 
37function serve(req, res, owner, repo, operation) {
38 let backend;
39 try { backend = backendPath(); }
40 catch (e) { return respond.serverError(res, e.message); }
41 
42 const repoRoot = git.reposRoot();
43 const pathInfo = '/' + owner + '/' + repo + '.git/' + operation;
44 const qs = req.url.includes('?') ? req.url.slice(req.url.indexOf('?') + 1) : '';
45 
46 const env = {
47 GIT_PROJECT_ROOT: repoRoot,
48 GIT_HTTP_EXPORT_ALL: '1',
49 GIT_HTTP_RECEIVE_PACK: '1',
50 PATH_INFO: pathInfo,
51 QUERY_STRING: qs,
52 REQUEST_METHOD: req.method,
53 CONTENT_TYPE: req.headers['content-type'] || '',
54 CONTENT_LENGTH: req.headers['content-length'] || '',
55 REMOTE_ADDR: req.socket.remoteAddress || '',
56 HTTP_GIT_PROTOCOL: req.headers['git-protocol'] || '',
57 PATH: process.env.PATH || ''
58 };
59 
60 const proc = spawn(backend, [], { env });
61 req.pipe(proc.stdin);
62 
63 let headersDone = false;
64 const pending = [];
65 
66 proc.stdout.on('data', chunk => {
67 if (headersDone) { res.write(chunk); return; }
68 pending.push(chunk);
69 const combined = Buffer.concat(pending);
70 const sep = findSep(combined);
71 if (!sep) return;
72 
73 const headerText = combined.slice(0, sep.at).toString('ascii');
74 const body = combined.slice(sep.at + sep.len);
75 
76 let status = 200;
77 const headers = {};
78 for (const line of headerText.split(/\r?\n/)) {
79 const ci = line.indexOf(':');
80 if (ci < 0) continue;
81 const k = line.slice(0, ci).trim().toLowerCase();
82 const v = line.slice(ci + 1).trim();
83 if (k === 'status') status = parseInt(v, 10) || 200;
84 else headers[k] = v;
85 }
86 
87 headersDone = true;
88 pending.length = 0;
89 res.writeHead(status, headers);
90 if (body.length) res.write(body);
91 });
92 
93 proc.stdout.on('end', () => res.end());
94 proc.stderr.on('data', d => log.warn('http-backend:', d.toString().trim()));
95 proc.on('error', e => {
96 log.error('http-backend spawn:', e.message);
97 if (!res.headersSent) respond.serverError(res, 'git http-backend error');
98 else res.end();
99 });
100}
101 
102module.exports = { serve };
103 
Done 🔒 Internet