|
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