diff --git a/README.md b/README.md index b2641908..a6f083fc 100644 --- a/README.md +++ b/README.md @@ -89,27 +89,32 @@ proxy requests. The following options are supported: * function `getProxyForUrl` - If set, specifies which intermediate proxy to use for a given URL. If the return value is void, a direct request is sent. The default implementation is [`proxy-from-env`](https://github.com/Rob--W/proxy-from-env), which respects the standard proxy - environment variables (e.g. `https_proxy`, `no_proxy`, etc.). -* array of strings `originBlacklist` - If set, requests whose origin is listed are blocked. + environment variables (e.g. `https_proxy`, `no_proxy`, etc.). +* array of strings `originBlacklist` - If set, requests whose origin is listed are blocked. Example: `['https://bad.example.com', 'http://bad.example.com']` -* array of strings `originWhitelist` - If set, requests whose origin is not listed are blocked. +* array of strings `originWhitelist` - If set, requests whose origin is not listed are blocked. If this list is empty, all origins are allowed. Example: `['https://good.example.com', 'http://good.example.com']` +* array of strings `targetBlacklist` - If set, requests whose target is listed are blocked. + Example: `['https://bad.example.com', 'http://bad.example.com']` +* array of strings `targetWhitelist` - If set, requests whose target is not listed are blocked. + If this list is empty, all targets are allowed. + Example: `['https://good.example.com', 'http://good.example.com']` * function `checkRateLimit` - If set, it is called with the origin (string) of the request. If this function returns a non-empty string, the request is rejected and the string is send to the client. * boolean `redirectSameOrigin` - If true, requests to URLs from the same origin will not be proxied but redirected. The primary purpose for this option is to save server resources by delegating the request to the client (since same-origin requests should always succeed, even without proxying). -* array of strings `requireHeader` - If set, the request must include this header or the API will refuse to proxy. - Recommended if you want to prevent users from using the proxy for normal browsing. +* array of strings `requireHeader` - If set, the request must include this header or the API will refuse to proxy. + Recommended if you want to prevent users from using the proxy for normal browsing. Example: `['Origin', 'X-Requested-With']`. -* array of lowercase strings `removeHeaders` - Exclude certain headers from being included in the request. +* array of lowercase strings `removeHeaders` - Exclude certain headers from being included in the request. Example: `["cookie"]` -* dictionary of lowercase strings `setHeaders` - Set headers for the request (overwrites existing ones). +* dictionary of lowercase strings `setHeaders` - Set headers for the request (overwrites existing ones). Example: `{"x-powered-by": "CORS Anywhere"}` -* number `corsMaxAge` - If set, an Access-Control-Max-Age request header with this value (in seconds) will be added. +* number `corsMaxAge` - If set, an Access-Control-Max-Age request header with this value (in seconds) will be added. Example: `600` - Allow CORS preflight request to be cached by the browser for 10 minutes. -* string `helpFile` - Set the help file (shown at the homepage). +* string `helpFile` - Set the help file (shown at the homepage). Example: `"myCustomHelpText.txt"` For advanced users, the following options are also provided. @@ -142,6 +147,13 @@ export CORSANYWHERE_WHITELIST=https://example.com,http://example.com,http://exam node server.js ``` +Similarly, to run a CORS Anywhere server that proxies only to example.com sites on port 8080, use: +``` +export PORT=8080 +export CORSANYWHERE_WHITELIST_TARGET=https://example.com,http://example.com,http://example.com:8080 +node server.js +``` + This application can immediately be run on Heroku, see https://devcenter.heroku.com/articles/nodejs for instructions. Note that their [Acceptable Use Policy](https://www.heroku.com/policy/aup) forbids the use of Heroku for operating an open proxy, so make sure that you either enforce a whitelist as @@ -157,6 +169,12 @@ export CORSANYWHERE_RATELIMIT='50 3 my.example.com my2.example.com' node server.js ``` +To enable proxying to all sites except to example.com, use: +``` +export PORT=8080 +export CORSANYWHERE_BLACKLIST_TARGET=https://example.com,http://example.com +node server.js +``` ## License diff --git a/lib/cors-anywhere.js b/lib/cors-anywhere.js index 7a82400d..e3a40197 100644 --- a/lib/cors-anywhere.js +++ b/lib/cors-anywhere.js @@ -244,6 +244,8 @@ function getHandler(options, proxy) { maxRedirects: 5, // Maximum number of redirects to be followed. originBlacklist: [], // Requests from these origins will be blocked. originWhitelist: [], // If non-empty, requests not from an origin in this list will be blocked. + targetBlacklist: [], // Requests to these targets will be blocked. + targetWhitelist: [], // If non-empty, requests not to an target in this list will be blocked. checkRateLimit: null, // Function that may enforce a rate-limit by returning a non-empty string. redirectSameOrigin: false, // Redirect the client to the requested URL for same-origin requests. requireHeader: null, // Require a header to be set? @@ -342,6 +344,19 @@ function getHandler(options, proxy) { return; } + var target = location.protocol + '//' + location.hostname; + if (corsAnywhere.targetBlacklist.indexOf(target) >= 0) { + res.writeHead(403, 'Forbidden', cors_headers); + res.end('The target "' + target + '" was blacklisted by the operator of this proxy.'); + return; + } + + if (corsAnywhere.targetWhitelist.length && corsAnywhere.targetWhitelist.indexOf(target) === -1) { + res.writeHead(403, 'Forbidden', cors_headers); + res.end('The target "' + target + '" was not whitelisted by the operator of this proxy.'); + return; + } + var rateLimitMessage = corsAnywhere.checkRateLimit && corsAnywhere.checkRateLimit(origin); if (rateLimitMessage) { res.writeHead(429, 'Too Many Requests', cors_headers); diff --git a/server.js b/server.js index fb3f84d3..f8164e7a 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,8 @@ var port = process.env.PORT || 8080; // use originWhitelist instead. var originBlacklist = parseEnvList(process.env.CORSANYWHERE_BLACKLIST); var originWhitelist = parseEnvList(process.env.CORSANYWHERE_WHITELIST); +var targetBlacklist = parseEnvList(process.env.CORSANYWHERE_TARGET_BLACKLIST); +var targetWhitelist = parseEnvList(process.env.CORSANYWHERE_TARGET_WHITELIST); function parseEnvList(env) { if (!env) { return []; @@ -23,6 +25,8 @@ var cors_proxy = require('./lib/cors-anywhere'); cors_proxy.createServer({ originBlacklist: originBlacklist, originWhitelist: originWhitelist, + targetBlacklist: targetBlacklist, + targetWhitelist: targetWhitelist, requireHeader: ['origin', 'x-requested-with'], checkRateLimit: checkRateLimit, removeHeaders: [ @@ -38,7 +42,7 @@ cors_proxy.createServer({ httpProxyOptions: { // Do not add X-Forwarded-For, etc. headers, because Heroku already adds it. xfwd: false, - }, + } }).listen(port, host, function() { console.log('Running CORS Anywhere on ' + host + ':' + port); }); diff --git a/test/test.js b/test/test.js index 367a01ab..623f6208 100644 --- a/test/test.js +++ b/test/test.js @@ -548,6 +548,54 @@ describe('originWhitelist', function() { }); }); +describe('targetBlacklist', function() { + before(function() { + cors_anywhere = createServer({ + targetBlacklist: ['http://example.com'], + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /http://example.com with denied target http://example.com', function(done) { + request(cors_anywhere) + .get('/http://example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(403, done); + }); + + it('GET /https://example.com with denied target http://example.com', function(done) { + request(cors_anywhere) + .get('/https://example.com/') // Note: different scheme! + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); +}); + +describe('targetWhitelist', function() { + before(function() { + cors_anywhere = createServer({ + targetWhitelist: ['http://example.com'], + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /http://example.com with permitted target http://example.com', function(done) { + request(cors_anywhere) + .get('/http://example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); + + it('GET /https://example.com with permitted target http://example.com', function(done) { + request(cors_anywhere) + .get('/https://example.com') // Note: different scheme! + .expect('Access-Control-Allow-Origin', '*') + .expect(403, done); + }); +}); + describe('checkRateLimit', function() { afterEach(stopServer);