|
|
|
|
|
|
|
|
|
|
|
'use strict'; |
|
|
|
|
|
var httpProxy = require('http-proxy'); |
|
|
var net = require('net'); |
|
|
var url = require('url'); |
|
|
var getProxyForUrl = require('proxy-from-env').getProxyForUrl; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isValidHostName(hostname) { |
|
|
return !!( |
|
|
hostname.indexOf('.') > 0 || |
|
|
net.isIPv4(hostname) || |
|
|
net.isIPv6(hostname) |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function withCORS(headers, request) { |
|
|
headers['access-control-allow-origin'] = '*'; |
|
|
var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge; |
|
|
if (corsMaxAge) { |
|
|
headers['access-control-max-age'] = corsMaxAge; |
|
|
} |
|
|
if (request.headers['access-control-request-method']) { |
|
|
headers['access-control-allow-methods'] = request.headers['access-control-request-method']; |
|
|
delete request.headers['access-control-request-method']; |
|
|
} |
|
|
if (request.headers['access-control-request-headers']) { |
|
|
headers['access-control-allow-headers'] = request.headers['access-control-request-headers']; |
|
|
delete request.headers['access-control-request-headers']; |
|
|
} |
|
|
|
|
|
headers['access-control-expose-headers'] = Object.keys(headers).join(','); |
|
|
|
|
|
return headers; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function proxyRequest(req, res, proxy) { |
|
|
var location = req.corsAnywhereRequestState.location; |
|
|
req.url = location.path; |
|
|
|
|
|
var proxyOptions = { |
|
|
changeOrigin: false, |
|
|
prependPath: false, |
|
|
target: location, |
|
|
headers: { |
|
|
host: location.host, |
|
|
}, |
|
|
|
|
|
|
|
|
buffer: { |
|
|
pipe: function (proxyReq) { |
|
|
var proxyReqOn = proxyReq.on; |
|
|
|
|
|
|
|
|
proxyReq.on = function (eventName, listener) { |
|
|
if (eventName !== 'response') { |
|
|
return proxyReqOn.call(this, eventName, listener); |
|
|
} |
|
|
return proxyReqOn.call(this, 'response', function (proxyRes) { |
|
|
if (onProxyResponse(proxy, proxyReq, proxyRes, req, res)) { |
|
|
try { |
|
|
listener(proxyRes); |
|
|
} catch (err) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proxyReq.emit('error', err); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}; |
|
|
return req.pipe(proxyReq); |
|
|
}, |
|
|
}, |
|
|
}; |
|
|
|
|
|
var proxyThroughUrl = req.corsAnywhereRequestState.getProxyForUrl(location.href); |
|
|
if (proxyThroughUrl) { |
|
|
proxyOptions.target = proxyThroughUrl; |
|
|
proxyOptions.toProxy = true; |
|
|
|
|
|
|
|
|
req.url = location.href; |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
proxy.web(req, res, proxyOptions); |
|
|
} catch (err) { |
|
|
proxy.emit('error', err, req, res); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onProxyResponse(proxy, proxyReq, proxyRes, req, res) { |
|
|
var requestState = req.corsAnywhereRequestState; |
|
|
|
|
|
var statusCode = proxyRes.statusCode; |
|
|
|
|
|
if (!requestState.redirectCount_) { |
|
|
res.setHeader('x-request-url', requestState.location.href); |
|
|
} |
|
|
|
|
|
if (statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308) { |
|
|
var locationHeader = proxyRes.headers.location; |
|
|
var parsedLocation; |
|
|
if (locationHeader) { |
|
|
locationHeader = url.resolve(requestState.location.href, locationHeader); |
|
|
parsedLocation = parseURL(locationHeader); |
|
|
} |
|
|
if (parsedLocation) { |
|
|
if (statusCode === 301 || statusCode === 302 || statusCode === 303) { |
|
|
|
|
|
requestState.redirectCount_ = requestState.redirectCount_ + 1 || 1; |
|
|
if (requestState.redirectCount_ <= requestState.maxRedirects) { |
|
|
|
|
|
|
|
|
|
|
|
res.setHeader('X-CORS-Redirect-' + requestState.redirectCount_, statusCode + ' ' + locationHeader); |
|
|
|
|
|
req.method = 'GET'; |
|
|
req.headers['content-length'] = '0'; |
|
|
delete req.headers['content-type']; |
|
|
requestState.location = parsedLocation; |
|
|
|
|
|
|
|
|
req.removeAllListeners(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proxyReq.removeAllListeners('error'); |
|
|
proxyReq.once('error', function catchAndIgnoreError() { }); |
|
|
proxyReq.abort(); |
|
|
|
|
|
|
|
|
proxyRequest(req, res, proxy); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
proxyRes.headers.location = requestState.proxyBaseUrl + '/' + locationHeader; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
delete proxyRes.headers['set-cookie']; |
|
|
delete proxyRes.headers['set-cookie2']; |
|
|
|
|
|
proxyRes.headers['x-final-url'] = requestState.location.href; |
|
|
withCORS(proxyRes.headers, req); |
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseURL(req_url) { |
|
|
|
|
|
var host = req_url.slice(0, 8); |
|
|
|
|
|
if (host.startsWith('/')){ |
|
|
return null; |
|
|
} else if (host.includes("://")) { |
|
|
|
|
|
} else if (host.includes(':/')) { |
|
|
req_url = new URL(req_url).href; |
|
|
} else { |
|
|
req_url = "http://" + req_url; |
|
|
} |
|
|
|
|
|
return url.parse(req_url); |
|
|
} |
|
|
|
|
|
|
|
|
function getHandler(options, proxy) { |
|
|
var corsAnywhere = { |
|
|
getProxyForUrl: getProxyForUrl, |
|
|
maxRedirects: 5, |
|
|
keywordBlacklist: [], |
|
|
originBlacklist: [], |
|
|
originWhitelist: [], |
|
|
checkRateLimit: null, |
|
|
redirectSameOrigin: false, |
|
|
requireHeader: null, |
|
|
removeHeaders: [], |
|
|
setHeaders: {}, |
|
|
corsMaxAge: 0 |
|
|
}; |
|
|
|
|
|
Object.keys(corsAnywhere).forEach(function (option) { |
|
|
if (Object.prototype.hasOwnProperty.call(options, option)) { |
|
|
corsAnywhere[option] = options[option]; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
if (corsAnywhere.requireHeader) { |
|
|
if (typeof corsAnywhere.requireHeader === 'string') { |
|
|
corsAnywhere.requireHeader = [corsAnywhere.requireHeader.toLowerCase()]; |
|
|
} else if (!Array.isArray(corsAnywhere.requireHeader) || corsAnywhere.requireHeader.length === 0) { |
|
|
corsAnywhere.requireHeader = null; |
|
|
} else { |
|
|
corsAnywhere.requireHeader = corsAnywhere.requireHeader.map(function (headerName) { |
|
|
return headerName.toLowerCase(); |
|
|
}); |
|
|
} |
|
|
} |
|
|
var hasRequiredHeaders = function (headers) { |
|
|
return !corsAnywhere.requireHeader || corsAnywhere.requireHeader.some(function (headerName) { |
|
|
return Object.hasOwnProperty.call(headers, headerName); |
|
|
}); |
|
|
}; |
|
|
|
|
|
return function (req, res) { |
|
|
req.corsAnywhereRequestState = { |
|
|
getProxyForUrl: corsAnywhere.getProxyForUrl, |
|
|
maxRedirects: corsAnywhere.maxRedirects, |
|
|
corsMaxAge: corsAnywhere.corsMaxAge, |
|
|
}; |
|
|
|
|
|
let clientip = req.headers['x-forwarded-for'] || |
|
|
req.connection.remoteAddress || |
|
|
req.socket.remoteAddress || |
|
|
req.connection.socket.remoteAddress; |
|
|
|
|
|
console.log(JSON.stringify([ |
|
|
new Date().toISOString(), |
|
|
req.method, |
|
|
req.url, |
|
|
clientip, |
|
|
req.headers["user-agent"] |
|
|
])); |
|
|
|
|
|
var cors_headers = withCORS({}, req); |
|
|
if (req.method === 'OPTIONS') { |
|
|
|
|
|
res.writeHead(200, cors_headers); |
|
|
res.end(); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
var raw = req.url; |
|
|
var idx = raw.indexOf('/', 1); |
|
|
var url = raw.slice(idx + 1); |
|
|
var location = parseURL(url); |
|
|
}catch(err){ |
|
|
console.log(err.message); |
|
|
} |
|
|
|
|
|
if (!location) { |
|
|
|
|
|
res.writeHead(200, { 'Content-Type': 'text/plain' }); |
|
|
res.end('404'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (location.host === 'iscorsneeded') { |
|
|
|
|
|
|
|
|
|
|
|
res.writeHead(200, { 'Content-Type': 'text/plain' }); |
|
|
res.end('no'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (location.port > 65535) { |
|
|
|
|
|
res.writeHead(400, 'Invalid port', cors_headers); |
|
|
res.end('Port number too large: ' + location.port); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (!/^\/https?:/.test(req.url) && !isValidHostName(location.hostname)) { |
|
|
|
|
|
res.writeHead(404, 'Invalid host', cors_headers); |
|
|
res.end('Invalid host: ' + location.hostname); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (!hasRequiredHeaders(req.headers)) { |
|
|
res.writeHead(400, 'Header required', cors_headers); |
|
|
res.end('Missing required request header. Must specify one of: ' + corsAnywhere.requireHeader); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (corsAnywhere.keywordBlacklist.filter(x => location.href.indexOf(x) >= 0).length > 0) { |
|
|
res.writeHead(403, 'Forbidden', cors_headers); |
|
|
res.end('The keyword "' + corsAnywhere.keywordBlacklist.join(" ") + '" was blacklisted by the operator of this proxy.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (corsAnywhere.originBlacklist.indexOf(location.hostname) >= 0) { |
|
|
res.writeHead(403, 'Forbidden', cors_headers); |
|
|
res.end('The origin "' + location.hostname + '" was blacklisted by the operator of this proxy.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (corsAnywhere.originWhitelist.length && corsAnywhere.originWhitelist.indexOf(location.hostname) === -1) { |
|
|
res.writeHead(403, 'Forbidden', cors_headers); |
|
|
res.end('The origin "' + location.hostname + '" was not whitelisted by the operator of this proxy.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
var origin = req.headers.origin || ''; |
|
|
|
|
|
if (corsAnywhere.redirectSameOrigin && origin && location.href[origin.length] === '/' && |
|
|
location.href.lastIndexOf(origin, 0) === 0) { |
|
|
|
|
|
cors_headers.vary = 'origin'; |
|
|
cors_headers['cache-control'] = 'private'; |
|
|
cors_headers.location = location.href; |
|
|
res.writeHead(301, 'Please use a direct request', cors_headers); |
|
|
res.end(); |
|
|
return; |
|
|
} |
|
|
|
|
|
var isRequestedOverHttps = req.connection.encrypted || /^\s*https/.test(req.headers['x-forwarded-proto']); |
|
|
var proxyBaseUrl = (isRequestedOverHttps ? 'https://' : 'http://') + req.headers.host; |
|
|
|
|
|
corsAnywhere.removeHeaders.forEach(function (header) { |
|
|
delete req.headers[header]; |
|
|
}); |
|
|
|
|
|
Object.keys(corsAnywhere.setHeaders).forEach(function (header) { |
|
|
req.headers[header] = corsAnywhere.setHeaders[header]; |
|
|
}); |
|
|
|
|
|
var host = raw.slice(1, idx); |
|
|
var referer = host.indexOf('.') != -1 ? 'https://' + host : location.href; |
|
|
console.log('网址' + location.href + ' 来路' + referer) |
|
|
req.headers.referer = referer; |
|
|
|
|
|
req.corsAnywhereRequestState.location = location; |
|
|
req.corsAnywhereRequestState.proxyBaseUrl = proxyBaseUrl; |
|
|
|
|
|
proxyRequest(req, res, proxy); |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function createServer(options) { |
|
|
options = options || {}; |
|
|
|
|
|
|
|
|
var httpProxyOptions = { |
|
|
xfwd: true, |
|
|
}; |
|
|
|
|
|
if (options.httpProxyOptions) { |
|
|
Object.keys(options.httpProxyOptions).forEach(function (option) { |
|
|
httpProxyOptions[option] = options.httpProxyOptions[option]; |
|
|
}); |
|
|
} |
|
|
|
|
|
var proxy = httpProxy.createServer(httpProxyOptions); |
|
|
var requestHandler = getHandler(options, proxy); |
|
|
var server; |
|
|
if (options.httpsOptions) { |
|
|
server = require('https').createServer(options.httpsOptions, requestHandler); |
|
|
} else { |
|
|
server = require('http').createServer(requestHandler); |
|
|
} |
|
|
|
|
|
|
|
|
proxy.on('error', function (err, req, res) { |
|
|
if (res.headersSent) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (res.writableEnded === false) { |
|
|
res.end(); |
|
|
} |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var headerNames = res.getHeaderNames ? res.getHeaderNames() : Object.keys(res._headers || {}); |
|
|
headerNames.forEach(function (name) { |
|
|
res.removeHeader(name); |
|
|
}); |
|
|
|
|
|
res.writeHead(404, { 'Access-Control-Allow-Origin': '*' }); |
|
|
res.end('Not found because of proxy error: ' + err); |
|
|
}); |
|
|
|
|
|
return server; |
|
|
}; |
|
|
|
|
|
process.on('uncaughtException', function (exception) { |
|
|
console.log(exception); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var host = process.env.HOST || '0.0.0.0'; |
|
|
|
|
|
var port = process.env.PORT || 3000; |
|
|
|
|
|
createServer({ |
|
|
|
|
|
removeHeaders: [ |
|
|
'x-heroku-queue-wait-time', |
|
|
'x-heroku-queue-depth', |
|
|
'x-heroku-dynos-in-use', |
|
|
'x-request-start', |
|
|
], |
|
|
keywordBlacklist: [".mpd", ".m4v"], |
|
|
originBlacklist: [], |
|
|
originWhitelist: [], |
|
|
redirectSameOrigin: true, |
|
|
|
|
|
httpProxyOptions: { |
|
|
xfwd: false, |
|
|
secure: false |
|
|
}, |
|
|
}).listen(port, host, function () { |
|
|
console.log(new Date().toISOString() + ' Running CORS Anywhere on ' + host + ':' + port); |
|
|
}); |