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
@@ -33,7 +35,7 @@ const stripGitHubFromGitPath = (url: string): string | undefined => {
33
35
* @return {boolean } If true, this is a valid and expected git request. Otherwise, false.
34
36
*/
35
37
const validGitRequest = ( url : string , headers : any ) : boolean => {
36
- const { 'user-agent' : agent , accept } = headers ;
38
+ const { 'user-agent' : agent = '' , accept = '' } = headers ;
37
39
if ( [ '/info/refs?service=git-upload-pack' , '/info/refs?service=git-receive-pack' ] . includes ( url ) ) {
38
40
// https://www.git-scm.com/docs/http-protocol#_discovering_references
39
41
// We can only filter based on User-Agent since the Accept header is not
@@ -47,89 +49,95 @@ const validGitRequest = (url: string, headers: any): boolean => {
47
49
return false ;
48
50
} ;
49
51
52
+ const isPackPost = ( req : Request ) =>
53
+ req . method === 'POST' && / \/ .* \. g i t \/ ( g i t - u p l o a d - p a c k | g i t - r e c e i v e - p a c k ) $ / . test ( req . url ) ;
54
+
55
+ const teeAndValidate = async ( req : Request , res : Response , next : NextFunction ) => {
56
+ if ( ! isPackPost ( req ) ) return next ( ) ;
57
+
58
+ const proxyStream = new PassThrough ( ) ;
59
+ const pluginStream = new PassThrough ( ) ;
60
+
61
+ req . pipe ( proxyStream ) ;
62
+ req . pipe ( pluginStream ) ;
63
+
64
+ try {
65
+ const buf = await getRawBody ( pluginStream , { limit : '1gb' } ) ;
66
+ ( req as any ) . body = buf ;
67
+ const verdict = await executeChain ( req , res ) ;
68
+ console . log ( 'action processed' ) ;
69
+
70
+ if ( verdict . error || verdict . blocked ) {
71
+ const msg = verdict . error ? verdict . errorMessage ! : verdict . blockedMessage ! ;
72
+ res
73
+ . set ( {
74
+ 'content-type' : 'application/x-git-receive-pack-result' ,
75
+ 'transfer-encoding' : 'chunked' ,
76
+ expires : 'Fri, 01 Jan 1980 00:00:00 GMT' ,
77
+ pragma : 'no-cache' ,
78
+ 'cache-control' : 'no-cache, max-age=0, must-revalidate' ,
79
+ vary : 'Accept-Encoding' ,
80
+ 'x-frame-options' : 'DENY' ,
81
+ connection : 'close' ,
82
+ } )
83
+ . status ( 200 )
84
+ . send ( handleMessage ( msg ) ) ;
85
+ return ;
86
+ }
87
+
88
+ ( req as any ) . pipe = ( dest : any , opts : any ) => proxyStream . pipe ( dest , opts ) ;
89
+ next ( ) ;
90
+ } catch ( e ) {
91
+ console . error ( e ) ;
92
+ proxyStream . destroy ( e as Error ) ;
93
+ res . status ( 500 ) . end ( 'Proxy error' ) ;
94
+ }
95
+ } ;
96
+
97
+ router . use ( teeAndValidate ) ;
98
+
50
99
router . use (
51
100
'/' ,
52
101
proxy ( getProxyUrl ( ) , {
102
+ parseReqBody : false ,
53
103
preserveHostHdr : false ,
54
- filter : async function ( req , res ) {
55
- try {
56
- console . log ( 'request url: ' , req . url ) ;
57
- console . log ( 'host: ' , req . headers . host ) ;
58
- console . log ( 'user-agent: ' , req . headers [ 'user-agent' ] ) ;
59
- const gitPath = stripGitHubFromGitPath ( req . url ) ;
60
- if ( gitPath === undefined || ! validGitRequest ( gitPath , req . headers ) ) {
61
- res . status ( 400 ) . send ( 'Invalid request received' ) ;
62
- return false ;
63
- }
64
-
65
- const action = await executeChain ( req , res ) ;
66
- console . log ( 'action processed' ) ;
67
-
68
- if ( action . error || action . blocked ) {
69
- res . set ( 'content-type' , 'application/x-git-receive-pack-result' ) ;
70
- res . set ( 'transfer-encoding' , 'chunked' ) ;
71
- res . set ( 'expires' , 'Fri, 01 Jan 1980 00:00:00 GMT' ) ;
72
- res . set ( 'pragma' , 'no-cache' ) ;
73
- res . set ( 'cache-control' , 'no-cache, max-age=0, must-revalidate' ) ;
74
- res . set ( 'vary' , 'Accept-Encoding' ) ;
75
- res . set ( 'x-frame-options' , 'DENY' ) ;
76
- res . set ( 'connection' , 'close' ) ;
77
-
78
- let message = '' ;
79
-
80
- if ( action . error ) {
81
- message = action . errorMessage ! ;
82
- console . error ( message ) ;
83
- }
84
- if ( action . blocked ) {
85
- message = action . blockedMessage ! ;
86
- }
87
-
88
- const packetMessage = handleMessage ( message ) ;
89
-
90
- console . log ( req . headers ) ;
91
-
92
- res . status ( 200 ) . send ( packetMessage ) ;
93
-
94
- return false ;
95
- }
96
-
97
- return true ;
98
- } catch ( e ) {
99
- console . error ( e ) ;
104
+
105
+ filter : async ( req , res ) => {
106
+ console . log ( 'request url: ' , req . url ) ;
107
+ console . log ( 'host: ' , req . headers . host ) ;
108
+ console . log ( 'user-agent: ' , req . headers [ 'user-agent' ] ) ;
109
+ const gitPath = stripGitHubFromGitPath ( req . url ) ;
110
+ if ( gitPath === undefined || ! validGitRequest ( gitPath , req . headers ) ) {
111
+ res . status ( 400 ) . send ( 'Invalid request received' ) ;
100
112
return false ;
101
113
}
114
+ return true ;
102
115
} ,
116
+
103
117
proxyReqPathResolver : ( req ) => {
104
118
const url = getProxyUrl ( ) + req . originalUrl ;
105
119
console . log ( 'Sending request to ' + url ) ;
106
120
return url ;
107
121
} ,
108
- proxyReqOptDecorator : function ( proxyReqOpts ) {
109
- return proxyReqOpts ;
110
- } ,
111
122
112
- proxyReqBodyDecorator : function ( bodyContent , srcReq ) {
113
- if ( srcReq . method === 'GET' ) {
114
- return '' ;
115
- }
116
- return bodyContent ;
117
- } ,
118
-
119
- proxyErrorHandler : function ( err , res , next ) {
123
+ proxyErrorHandler : ( err , res , next ) => {
120
124
console . log ( `ERROR=${ err } ` ) ;
121
125
next ( err ) ;
122
126
} ,
123
127
} ) ,
124
128
) ;
125
129
126
130
const handleMessage = ( message : string ) : string => {
127
- const errorMessage = `\t${ message } ` ;
128
- const len = 6 + new TextEncoder ( ) . encode ( errorMessage ) . length ;
129
-
130
- const prefix = len . toString ( 16 ) ;
131
- const packetMessage = `${ prefix . padStart ( 4 , '0' ) } \x02${ errorMessage } \n0000` ;
132
- return packetMessage ;
131
+ const body = `\t${ message } ` ;
132
+ const len = ( 6 + Buffer . byteLength ( body ) ) . toString ( 16 ) . padStart ( 4 , '0' ) ;
133
+ return `${ len } \x02${ body } \n0000` ;
133
134
} ;
134
135
135
- export { router , handleMessage , validGitRequest , stripGitHubFromGitPath } ;
136
+ export {
137
+ router ,
138
+ handleMessage ,
139
+ validGitRequest ,
140
+ teeAndValidate ,
141
+ isPackPost ,
142
+ stripGitHubFromGitPath ,
143
+ } ;
0 commit comments