Skip to content

Commit fc07a28

Browse files
authored
feat: expose a connect-style middleware (#5)
1 parent 972d6a1 commit fc07a28

File tree

6 files changed

+200
-183
lines changed

6 files changed

+200
-183
lines changed

allow-request.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
1-
const url = require('url')
2-
3-
function isPreflight (req, u) {
4-
return req.method === 'OPTIONS'
1+
function isPreflightInfoRefs (req, u) {
2+
return req.method === 'OPTIONS' && u.pathname.endsWith('/info/refs') && (u.query.service === 'git-upload-pack' || u.query.service === 'git-receive-pack')
53
}
64

75
function isInfoRefs (req, u) {
86
return req.method === 'GET' && u.pathname.endsWith('/info/refs') && (u.query.service === 'git-upload-pack' || u.query.service === 'git-receive-pack')
97
}
108

9+
function isPreflightPull (req, u) {
10+
return req.method === 'OPTIONS' && req.headers['access-control-request-headers'].includes('content-type') && u.pathname.endsWith('git-upload-pack')
11+
}
12+
1113
function isPull (req, u) {
1214
return req.method === 'POST' && req.headers['content-type'] === 'application/x-git-upload-pack-request' && u.pathname.endsWith('git-upload-pack')
1315
}
1416

17+
function isPreflightPush (req, u) {
18+
return req.method === 'OPTIONS' && req.headers['access-control-request-headers'].includes('content-type') && u.pathname.endsWith('git-receive-pack')
19+
}
20+
1521
function isPush (req, u) {
1622
return req.method === 'POST' && req.headers['content-type'] === 'application/x-git-receive-pack-request' && u.pathname.endsWith('git-receive-pack')
1723
}
1824

1925
module.exports = function allow (req, u) {
20-
return (isPreflight(req, u) || isInfoRefs(req, u) || isPull(req, u) || isPush(req, u))
26+
return (
27+
isPreflightInfoRefs(req, u) ||
28+
isInfoRefs(req, u) ||
29+
isPreflightPull(req, u) ||
30+
isPull(req, u) ||
31+
isPreflightPush(req, u) ||
32+
isPush(req, u)
33+
)
2134
}

index.js

Lines changed: 32 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -4,125 +4,42 @@ const pkg = require('./package.json')
44
const {send} = require('micro')
55
const origin = process.env.ALLOW_ORIGIN
66
const insecure_origins = (process.env.INSECURE_HTTP_ORIGINS || '').split(',')
7-
const allowHeaders = [
8-
'accept-encoding',
9-
'accept-language',
10-
'accept',
11-
'access-control-allow-origin',
12-
'authorization',
13-
'cache-control',
14-
'connection',
15-
'content-length',
16-
'content-type',
17-
'dnt',
18-
'pragma',
19-
'range',
20-
'referer',
21-
'user-agent',
22-
'x-http-method-override',
23-
'x-requested-with',
24-
]
25-
const exposeHeaders = [
26-
'accept-ranges',
27-
'age',
28-
'cache-control',
29-
'content-length',
30-
'content-language',
31-
'content-type',
32-
'date',
33-
'etag',
34-
'expires',
35-
'last-modified',
36-
'pragma',
37-
'server',
38-
'transfer-encoding',
39-
'vary',
40-
'x-github-request-id',
41-
]
42-
const allowMethods = [
43-
'POST',
44-
'GET',
45-
'OPTIONS'
46-
]
47-
const fetch = require('node-fetch')
48-
const cors = require('./micro-cors.js')({
49-
allowHeaders,
50-
exposeHeaders,
51-
allowMethods,
52-
allowCredentials: false,
53-
origin
54-
})
55-
const allow = require('./allow-request.js')
7+
const middleware = require('./middleware.js')({ origin, insecure_origins })
568

579
async function service (req, res) {
58-
let u = url.parse(req.url, true)
59-
60-
if (u.pathname === '/') {
61-
res.setHeader('content-type', 'text/html')
62-
let html = `<!DOCTYPE html>
63-
<html>
64-
<title>@isomorphic-git/cors-proxy</title>
65-
<h1>@isomorphic-git/cors-proxy</h1>
66-
<p>This is the server software that runs on <a href="https://cors.isomorphic-git.org">https://cors.isomorphic-git.org</a>
67-
&ndash; a free service (generously sponsored by <a href="https://www.clever-cloud.com/?utm_source=ref&utm_medium=link&utm_campaign=isomorphic-git">Clever Cloud</a>)
68-
for users of <a href="https://isomorphic-git.org">isomorphic-git</a> that enables cloning and pushing repos in the browser.</p>
69-
<p>The source code is hosted on Github at <a href="https://github.com/isomorphic-git/cors-proxy">https://github.com/isomorphic-git/cors-proxy</a></p>
70-
<p>It can also be installed from npm with <code>npm install <a href="https://npmjs.org/package/${pkg.name}">@isomorphic-git/cors-proxy</a></code></p>
71-
72-
<h2>Terms of Use</h2>
73-
<p><b>This free service is provided to you AS IS with no guarantees.
74-
By using this free service, you promise not to use excessive amounts of bandwidth.
75-
</b></p>
76-
77-
<p><b>If you are cloning or pushing large amounts of data your IP address may be banned.
78-
Please run your own instance of the software if you need to make heavy use this service.</b></p>
79-
80-
<h2>Allowed Origins</h2>
81-
This proxy allows git clone / fetch / push / getRemoteInfo requests from these domains: <code>${process.env.ALLOW_ORIGIN || '*'}</code>
82-
</html>
83-
`
84-
return send(res, 400, html)
85-
}
10+
middleware(req, res, () => {
11+
let u = url.parse(req.url, true)
12+
13+
if (u.pathname === '/') {
14+
res.setHeader('content-type', 'text/html')
15+
let html = `<!DOCTYPE html>
16+
<html>
17+
<title>@isomorphic-git/cors-proxy</title>
18+
<h1>@isomorphic-git/cors-proxy</h1>
19+
<p>This is the server software that runs on <a href="https://cors.isomorphic-git.org">https://cors.isomorphic-git.org</a>
20+
&ndash; a free service (generously sponsored by <a href="https://www.clever-cloud.com/?utm_source=ref&utm_medium=link&utm_campaign=isomorphic-git">Clever Cloud</a>)
21+
for users of <a href="https://isomorphic-git.org">isomorphic-git</a> that enables cloning and pushing repos in the browser.</p>
22+
<p>The source code is hosted on Github at <a href="https://github.com/isomorphic-git/cors-proxy">https://github.com/isomorphic-git/cors-proxy</a></p>
23+
<p>It can also be installed from npm with <code>npm install <a href="https://npmjs.org/package/${pkg.name}">@isomorphic-git/cors-proxy</a></code></p>
24+
25+
<h2>Terms of Use</h2>
26+
<p><b>This free service is provided to you AS IS with no guarantees.
27+
By using this free service, you promise not to use excessive amounts of bandwidth.
28+
</b></p>
29+
30+
<p><b>If you are cloning or pushing large amounts of data your IP address may be banned.
31+
Please run your own instance of the software if you need to make heavy use this service.</b></p>
32+
33+
<h2>Allowed Origins</h2>
34+
This proxy allows git clone / fetch / push / getRemoteInfo requests from these domains: <code>${process.env.ALLOW_ORIGIN || '*'}</code>
35+
</html>
36+
`
37+
return send(res, 400, html)
38+
}
8639

87-
if (!allow(req, u)) {
8840
// Don't waste my precious bandwidth
8941
return send(res, 403, '')
90-
}
91-
92-
// Handle CORS preflight request
93-
if (req.method === 'OPTIONS') {
94-
return send(res, 200, '')
95-
}
96-
97-
let headers = {}
98-
for (let h of allowHeaders) {
99-
if (req.headers[h]) {
100-
headers[h] = req.headers[h]
101-
}
102-
}
103-
104-
let p = u.path
105-
let parts = p.match(/\/([^\/]*)\/(.*)/)
106-
let pathdomain = parts[1]
107-
let remainingpath = parts[2]
108-
let protocol = insecure_origins.includes(pathdomain) ? 'http' : 'https'
109-
console.log(`${protocol}://${pathdomain}/${remainingpath}`)
110-
let f = await fetch(
111-
`${protocol}://${pathdomain}/${remainingpath}`,
112-
{
113-
method: req.method,
114-
headers,
115-
body: (req.method !== 'GET' && req.method !== 'HEAD') ? req : undefined
116-
}
117-
)
118-
res.statusCode = f.status
119-
for (let h of exposeHeaders) {
120-
if (h === 'content-length') continue
121-
if (f.headers.has(h)) {
122-
res.setHeader(h, f.headers.get(h))
123-
}
124-
}
125-
f.body.pipe(res)
42+
})
12643
}
12744

128-
module.exports = cors(service)
45+
module.exports = service

micro-cors.js

Lines changed: 0 additions & 52 deletions
This file was deleted.

middleware.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
'use strict'
2+
const url = require('url')
3+
const {send} = require('micro')
4+
const microCors = require('micro-cors')
5+
const fetch = require('node-fetch')
6+
7+
const allowHeaders = [
8+
'accept-encoding',
9+
'accept-language',
10+
'accept',
11+
'access-control-allow-origin',
12+
'authorization',
13+
'cache-control',
14+
'connection',
15+
'content-length',
16+
'content-type',
17+
'dnt',
18+
'pragma',
19+
'range',
20+
'referer',
21+
'user-agent',
22+
'x-http-method-override',
23+
'x-requested-with',
24+
]
25+
const exposeHeaders = [
26+
'accept-ranges',
27+
'age',
28+
'cache-control',
29+
'content-length',
30+
'content-language',
31+
'content-type',
32+
'date',
33+
'etag',
34+
'expires',
35+
'last-modified',
36+
'pragma',
37+
'server',
38+
'transfer-encoding',
39+
'vary',
40+
'x-github-request-id',
41+
]
42+
const allowMethods = [
43+
'POST',
44+
'GET',
45+
'OPTIONS'
46+
]
47+
48+
const allow = require('./allow-request.js')
49+
50+
const filter = (predicate, middleware) => {
51+
function corsProxyMiddleware (req, res, next) {
52+
if (predicate(req, res)) {
53+
middleware(req, res, next)
54+
} else {
55+
next()
56+
}
57+
}
58+
return corsProxyMiddleware
59+
}
60+
61+
module.exports = ({ origin, insecure_origins = [] } = {}) => {
62+
function predicate (req) {
63+
let u = url.parse(req.url, true)
64+
// Not a git request, skip
65+
return allow(req, u)
66+
}
67+
function middleware (req, res) {
68+
let u = url.parse(req.url, true)
69+
70+
// Handle CORS preflight request
71+
if (req.method === 'OPTIONS') {
72+
return send(res, 200, '')
73+
}
74+
75+
let headers = {}
76+
for (let h of allowHeaders) {
77+
if (req.headers[h]) {
78+
headers[h] = req.headers[h]
79+
}
80+
}
81+
82+
let p = u.path
83+
let parts = p.match(/\/([^\/]*)\/(.*)/)
84+
let pathdomain = parts[1]
85+
let remainingpath = parts[2]
86+
let protocol = insecure_origins.includes(pathdomain) ? 'http' : 'https'
87+
88+
fetch(
89+
`${protocol}://${pathdomain}/${remainingpath}`,
90+
{
91+
method: req.method,
92+
headers,
93+
body: (req.method !== 'GET' && req.method !== 'HEAD') ? req : undefined
94+
}
95+
).then(f => {
96+
res.statusCode = f.status
97+
for (let h of exposeHeaders) {
98+
if (h === 'content-length') continue
99+
if (f.headers.has(h)) {
100+
res.setHeader(h, f.headers.get(h))
101+
}
102+
}
103+
f.body.pipe(res)
104+
})
105+
}
106+
const cors = microCors({
107+
allowHeaders,
108+
exposeHeaders,
109+
allowMethods,
110+
allowCredentials: false,
111+
origin
112+
})
113+
return filter(predicate, cors(middleware))
114+
}

0 commit comments

Comments
 (0)