|
1 |
| -import { Router } from 'express'; |
| 1 | +import { Router, Request, Response, NextFunction } from 'express'; |
2 | 2 | import proxy from 'express-http-proxy';
|
| 3 | +import { PassThrough } from 'stream'; |
| 4 | +import getRawBody from 'raw-body'; |
3 | 5 | import { executeChain } from '../chain';
|
4 | 6 | import { getProxyUrl } from '../../config';
|
5 | 7 |
|
@@ -48,93 +50,109 @@ const validGitRequest = (url: string, headers: any): boolean => {
|
48 | 50 | return false;
|
49 | 51 | }
|
50 | 52 | // https://www.git-scm.com/docs/http-protocol#_uploading_data
|
51 |
| - return agent.startsWith('git/') && accept.startsWith('application/x-git-') ; |
| 53 | + return agent.startsWith('git/') && accept.startsWith('application/x-git-'); |
52 | 54 | }
|
53 | 55 | return false;
|
54 | 56 | };
|
55 | 57 |
|
| 58 | +const isPackPost = (req: Request) => |
| 59 | + req.method === 'POST' && |
| 60 | + // eslint-disable-next-line no-useless-escape |
| 61 | + /^\/[^\/]+\/[^\/]+\.git\/(?:git-upload-pack|git-receive-pack)$/.test(req.url); |
| 62 | + |
| 63 | +const teeAndValidate = async (req: Request, res: Response, next: NextFunction) => { |
| 64 | + if (!isPackPost(req)) return next(); |
| 65 | + |
| 66 | + const proxyStream = new PassThrough(); |
| 67 | + const pluginStream = new PassThrough(); |
| 68 | + |
| 69 | + req.pipe(proxyStream); |
| 70 | + req.pipe(pluginStream); |
| 71 | + |
| 72 | + try { |
| 73 | + const buf = await getRawBody(pluginStream, { limit: '1gb' }); |
| 74 | + (req as any).body = buf; |
| 75 | + const verdict = await executeChain(req, res); |
| 76 | + console.log('action processed'); |
| 77 | + if (verdict.error || verdict.blocked) { |
| 78 | + let msg = ''; |
| 79 | + |
| 80 | + if (verdict.error) { |
| 81 | + msg = verdict.errorMessage!; |
| 82 | + console.error(msg); |
| 83 | + } |
| 84 | + if (verdict.blocked) { |
| 85 | + msg = verdict.blockedMessage!; |
| 86 | + } |
| 87 | + |
| 88 | + res |
| 89 | + .set({ |
| 90 | + 'content-type': 'application/x-git-receive-pack-result', |
| 91 | + expires: 'Fri, 01 Jan 1980 00:00:00 GMT', |
| 92 | + pragma: 'no-cache', |
| 93 | + 'cache-control': 'no-cache, max-age=0, must-revalidate', |
| 94 | + vary: 'Accept-Encoding', |
| 95 | + 'x-frame-options': 'DENY', |
| 96 | + connection: 'close', |
| 97 | + }) |
| 98 | + .status(200) |
| 99 | + .send(handleMessage(msg)); |
| 100 | + return; |
| 101 | + } |
| 102 | + |
| 103 | + (req as any).pipe = (dest: any, opts: any) => proxyStream.pipe(dest, opts); |
| 104 | + next(); |
| 105 | + } catch (e) { |
| 106 | + console.error(e); |
| 107 | + proxyStream.destroy(e as Error); |
| 108 | + res.status(500).end('Proxy error'); |
| 109 | + } |
| 110 | +}; |
| 111 | + |
| 112 | +router.use(teeAndValidate); |
| 113 | + |
56 | 114 | router.use(
|
57 | 115 | '/',
|
58 | 116 | proxy(getProxyUrl(), {
|
| 117 | + parseReqBody: false, |
59 | 118 | preserveHostHdr: false,
|
60 |
| - filter: async function (req, res) { |
61 |
| - try { |
62 |
| - console.log('request url: ', req.url); |
63 |
| - console.log('host: ', req.headers.host); |
64 |
| - console.log('user-agent: ', req.headers['user-agent']); |
65 |
| - const gitPath = stripGitHubFromGitPath(req.url); |
66 |
| - if (gitPath === undefined || !validGitRequest(gitPath, req.headers)) { |
67 |
| - res.status(400).send('Invalid request received'); |
68 |
| - return false; |
69 |
| - } |
70 |
| - |
71 |
| - const action = await executeChain(req, res); |
72 |
| - console.log('action processed'); |
73 |
| - |
74 |
| - if (action.error || action.blocked) { |
75 |
| - res.set('content-type', 'application/x-git-receive-pack-result'); |
76 |
| - res.set('expires', 'Fri, 01 Jan 1980 00:00:00 GMT'); |
77 |
| - res.set('pragma', 'no-cache'); |
78 |
| - res.set('cache-control', 'no-cache, max-age=0, must-revalidate'); |
79 |
| - res.set('vary', 'Accept-Encoding'); |
80 |
| - res.set('x-frame-options', 'DENY'); |
81 |
| - res.set('connection', 'close'); |
82 |
| - |
83 |
| - let message = ''; |
84 |
| - |
85 |
| - if (action.error) { |
86 |
| - message = action.errorMessage!; |
87 |
| - console.error(message); |
88 |
| - } |
89 |
| - if (action.blocked) { |
90 |
| - message = action.blockedMessage!; |
91 |
| - } |
92 |
| - |
93 |
| - const packetMessage = handleMessage(message); |
94 |
| - |
95 |
| - console.log(req.headers); |
96 |
| - |
97 |
| - res.status(200).send(packetMessage); |
98 |
| - |
99 |
| - return false; |
100 |
| - } |
101 |
| - |
102 |
| - return true; |
103 |
| - } catch (e) { |
104 |
| - console.error(e); |
| 119 | + |
| 120 | + filter: async (req, res) => { |
| 121 | + console.log('request url: ', req.url); |
| 122 | + console.log('host: ', req.headers.host); |
| 123 | + console.log('user-agent: ', req.headers['user-agent']); |
| 124 | + const gitPath = stripGitHubFromGitPath(req.url); |
| 125 | + if (gitPath === undefined || !validGitRequest(gitPath, req.headers)) { |
| 126 | + res.status(400).send('Invalid request received'); |
105 | 127 | return false;
|
106 | 128 | }
|
| 129 | + return true; |
107 | 130 | },
|
| 131 | + |
108 | 132 | proxyReqPathResolver: (req) => {
|
109 | 133 | const url = getProxyUrl() + req.originalUrl;
|
110 | 134 | console.log('Sending request to ' + url);
|
111 | 135 | return url;
|
112 | 136 | },
|
113 |
| - proxyReqOptDecorator: function (proxyReqOpts) { |
114 |
| - return proxyReqOpts; |
115 |
| - }, |
116 | 137 |
|
117 |
| - proxyReqBodyDecorator: function (bodyContent, srcReq) { |
118 |
| - if (srcReq.method === 'GET') { |
119 |
| - return ''; |
120 |
| - } |
121 |
| - return bodyContent; |
122 |
| - }, |
123 |
| - |
124 |
| - proxyErrorHandler: function (err, res, next) { |
| 138 | + proxyErrorHandler: (err, res, next) => { |
125 | 139 | console.log(`ERROR=${err}`);
|
126 | 140 | next(err);
|
127 | 141 | },
|
128 | 142 | }),
|
129 | 143 | );
|
130 | 144 |
|
131 | 145 | const handleMessage = (message: string): string => {
|
132 |
| - const errorMessage = `\t${message}`; |
133 |
| - const len = 6 + new TextEncoder().encode(errorMessage).length; |
134 |
| - |
135 |
| - const prefix = len.toString(16); |
136 |
| - const packetMessage = `${prefix.padStart(4, '0')}\x02${errorMessage}\n0000`; |
137 |
| - return packetMessage; |
| 146 | + const body = `\t${message}`; |
| 147 | + const len = (6 + Buffer.byteLength(body)).toString(16).padStart(4, '0'); |
| 148 | + return `${len}\x02${body}\n0000`; |
138 | 149 | };
|
139 | 150 |
|
140 |
| -export { router, handleMessage, validGitRequest, stripGitHubFromGitPath }; |
| 151 | +export { |
| 152 | + router, |
| 153 | + handleMessage, |
| 154 | + validGitRequest, |
| 155 | + teeAndValidate, |
| 156 | + isPackPost, |
| 157 | + stripGitHubFromGitPath, |
| 158 | +}; |
0 commit comments