├── .eslintignore ├── Procfile ├── test ├── dummy.txt ├── customHelp.txt ├── customHelp.html ├── cert.pem ├── key.pem ├── child.js ├── setup.js ├── test-memory.js ├── test-examples.js ├── test-ratelimit.js └── test.js ├── .gitignore ├── .travis.yml ├── .eslintrc ├── LICENSE ├── package.json ├── lib ├── help.txt ├── rate-limit.js ├── regexp-top-level-domain.js └── cors-anywhere.js ├── server.js ├── demo.html └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node server.js 2 | -------------------------------------------------------------------------------- /test/dummy.txt: -------------------------------------------------------------------------------- 1 | dummy content 2 | -------------------------------------------------------------------------------- /test/customHelp.txt: -------------------------------------------------------------------------------- 1 | Server is OK!! 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.tmp 3 | *.log 4 | 5 | coverage/ 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /test/customHelp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Custom HTML help!!

6 | 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | - 4 5 | - 6 6 | - 7 7 | - 8 8 | - 9 9 | - 11 10 | - 12 11 | - 13 12 | - 13 13 | - 14 14 | - 15 15 | script: 16 | - npm run lint 17 | - npm run test 18 | - npm run test-coverage && cat coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf coverage 19 | -------------------------------------------------------------------------------- /test/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBsTCCARoCCQDp0DuED0RAJzANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJj 3 | b3JzLWFueXdoZXJlIHRlc3QwHhcNMTUwNTA2MDcyOTM1WhcNMTUwNjA1MDcyOTM1 4 | WjAdMRswGQYDVQQDDBJjb3JzLWFueXdoZXJlIHRlc3QwgZ8wDQYJKoZIhvcNAQEB 5 | BQADgY0AMIGJAoGBALzTF5ClJKvkB6h9h7kLORV+mMV3ySDs+oGZn0NgXM+yb9Zh 6 | 69r5e95zZJl/V432LFdy0hkEcVteUkC2REWG8D4COGfiwWsXyZdaP1qqLpDpPAMm 7 | v6xFHjW6rVuxzfr4GUjE0Zh9Fg2R2SbtCOcHS/LZoDVOqOvn6+urP6XFY4aFAgMB 8 | AAEwDQYJKoZIhvcNAQELBQADgYEAYXMhS8ouff/c8lSUUs/CLh010cj5RPk/ivS7 9 | aN2PArzQ6pZvhpgJKf7XAQksBtLYYZMzIpG6W8zhPSbqzly7lELAdE+sxcbbfu8A 10 | FMjNVFQ2Fm1c8ImX8qpE3nhVrPAiwfPjGBqKHTl730gvbh1XH9TC4O4dZcbEomX3 11 | 5MsxQfc= 12 | -----END CERTIFICATE----- 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "rules": { 6 | "array-bracket-spacing": [2, "never"], 7 | "block-scoped-var": 2, 8 | "brace-style": [2, "1tbs", {"allowSingleLine": true}], 9 | "comma-dangle": [2, "always-multiline"], 10 | "computed-property-spacing": [2, "never"], 11 | "curly": 2, 12 | "eol-last": 2, 13 | "eqeqeq": [2, "smart"], 14 | "max-len": [1, 125], 15 | "new-cap": 1, 16 | "no-extend-native": 2, 17 | "no-mixed-spaces-and-tabs": 2, 18 | "no-trailing-spaces": 2, 19 | "no-undef": 2, 20 | "no-unused-vars": 1, 21 | "no-use-before-define": [2, "nofunc"], 22 | "object-curly-spacing": [2, "never"], 23 | "quotes": [2, "single", "avoid-escape"], 24 | "semi": [2, "always"], 25 | "keyword-spacing": 2, 26 | "space-unary-ops": 2 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQC80xeQpSSr5AeofYe5CzkVfpjFd8kg7PqBmZ9DYFzPsm/WYeva 3 | +Xvec2SZf1eN9ixXctIZBHFbXlJAtkRFhvA+Ajhn4sFrF8mXWj9aqi6Q6TwDJr+s 4 | RR41uq1bsc36+BlIxNGYfRYNkdkm7QjnB0vy2aA1Tqjr5+vrqz+lxWOGhQIDAQAB 5 | AoGBAISy8OelN01Zlowxk/VWTsqtSl3UHdP21uHHfWaTTQZlxzTpYiBknkmp3LQH 6 | CxfoPidCuSX9ulBUzAdQUFBwUVp8wyPIRjpNyRiD58dLNxG0G+OACqnLxNWqIf6F 7 | vS3UqrRGIA5u+GSz+0g3DAeVA5JmsAyHQGkJsh3pcuD8/7wNAkEA7MScGfySy9td 8 | dDBekVU5/GaVg4DA4ELtDNfa99ARB89XP0ps/XrOPEL9yxTjWIHH+qxuhpfG6zGN 9 | ouxZlvBT9wJBAMwpig4A4JE8M8pBDwMY4213gud8B1grQTbhz5bv51aTaIEQFcxw 10 | sGfEmAfVToI+kVTrdFggy42YCSMSvwuF4mMCQQDZHkqPwf/TlSwT2i8+UstD28aL 11 | uswkWvsKZf9UdKbJZKd7UIK1x6HLvRsC2frJNOnvw6PvJMuy7dQWbWqScXxtAkBv 12 | /5msdO68vbnriiUiHdUliBpXwsKEq7Xq1ZV7x7+wzszVgG106ZzcUAzWvz2CVbCE 13 | VWZNsi/4TR82DmKff6LhAkBA/xceWaZjxh5dkWkIrMFWd2GFhGlpfwYw7oELwRL8 14 | RYXzc1Mr2fDdZDgwgjg67JQqIhOQ3E4RGKPgZ+E7Pk3/ 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (C) 2013 - 2021 Rob Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cors-anywhere", 3 | "version": "0.4.11", 4 | "description": "CORS Anywhere is a reverse proxy which adds CORS headers to the proxied request. Request URL is taken from the path", 5 | "license": "MIT", 6 | "author": "Rob Wu ", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Rob--W/cors-anywhere.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/Rob--W/cors-anywhere/issues/", 13 | "email": "rob@robwu.nl" 14 | }, 15 | "keywords": [ 16 | "cors", 17 | "cross-domain", 18 | "http-proxy", 19 | "proxy", 20 | "heroku" 21 | ], 22 | "main": "./lib/cors-anywhere.js", 23 | "files": [ 24 | "lib/", 25 | "test/", 26 | "Procfile", 27 | "demo.html", 28 | "server.js" 29 | ], 30 | "dependencies": { 31 | "http-proxy": "1.11.1", 32 | "proxy-from-env": "0.0.1" 33 | }, 34 | "devDependencies": { 35 | "coveralls": "^2.11.6", 36 | "eslint": "^2.2.0", 37 | "istanbul": "^0.4.2", 38 | "lolex": "^1.5.0", 39 | "mocha": "^3.4.2", 40 | "nock": "^8.2.1", 41 | "supertest": "^2.0.1" 42 | }, 43 | "scripts": { 44 | "lint": "eslint .", 45 | "test": "mocha ./test/test*.js --reporter spec", 46 | "test-coverage": "istanbul cover ./node_modules/.bin/_mocha -- test/test.js test/test-ratelimit.js --reporter spec" 47 | }, 48 | "engines": { 49 | "node": ">=0.10.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/help.txt: -------------------------------------------------------------------------------- 1 | This API enables cross-origin requests to anywhere. 2 | 3 | Usage: 4 | 5 | / Shows help 6 | /iscorsneeded This is the only resource on this host which is served without CORS headers. 7 | / Create a request to , and includes CORS headers in the response. 8 | 9 | If the protocol is omitted, it defaults to http (https if port 443 is specified). 10 | 11 | Cookies are disabled and stripped from requests. 12 | 13 | Redirects are automatically followed. For debugging purposes, each followed redirect results 14 | in the addition of a X-CORS-Redirect-n header, where n starts at 1. These headers are not 15 | accessible by the XMLHttpRequest API. 16 | After 5 redirects, redirects are not followed any more. The redirect response is sent back 17 | to the browser, which can choose to follow the redirect (handled automatically by the browser). 18 | 19 | The requested URL is available in the X-Request-URL response header. 20 | The final URL, after following all redirects, is available in the X-Final-URL response header. 21 | 22 | 23 | To prevent the use of the proxy for casual browsing, the API requires either the Origin 24 | or the X-Requested-With header to be set. To avoid unnecessary preflight (OPTIONS) requests, 25 | it's recommended to not manually set these headers in your code. 26 | 27 | 28 | Demo : https://robwu.nl/cors-anywhere.html 29 | Source code : https://github.com/Rob--W/cors-anywhere/ 30 | Documentation : https://github.com/Rob--W/cors-anywhere/#documentation 31 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // Listen on a specific host via the HOST environment variable 2 | var host = process.env.HOST || '0.0.0.0'; 3 | // Listen on a specific port via the PORT environment variable 4 | var port = process.env.PORT || 8080; 5 | 6 | // Grab the blacklist from the command-line so that we can update the blacklist without deploying 7 | // again. CORS Anywhere is open by design, and this blacklist is not used, except for countering 8 | // immediate abuse (e.g. denial of service). If you want to block all origins except for some, 9 | // use originWhitelist instead. 10 | var originBlacklist = parseEnvList(process.env.CORSANYWHERE_BLACKLIST); 11 | var originWhitelist = parseEnvList(process.env.CORSANYWHERE_WHITELIST); 12 | function parseEnvList(env) { 13 | if (!env) { 14 | return []; 15 | } 16 | return env.split(','); 17 | } 18 | 19 | // Set up rate-limiting to avoid abuse of the public CORS Anywhere server. 20 | var checkRateLimit = require('./lib/rate-limit')(process.env.CORSANYWHERE_RATELIMIT); 21 | 22 | var cors_proxy = require('./lib/cors-anywhere'); 23 | cors_proxy.createServer({ 24 | originBlacklist: originBlacklist, 25 | originWhitelist: originWhitelist, 26 | requireHeader: ['origin', 'x-requested-with'], 27 | checkRateLimit: checkRateLimit, 28 | removeHeaders: [ 29 | 'cookie', 30 | 'cookie2', 31 | // Strip Heroku-specific headers 32 | 'x-request-start', 33 | 'x-request-id', 34 | 'via', 35 | 'connect-time', 36 | 'total-route-time', 37 | // Other Heroku added debug headers 38 | // 'x-forwarded-for', 39 | // 'x-forwarded-proto', 40 | // 'x-forwarded-port', 41 | ], 42 | redirectSameOrigin: true, 43 | httpProxyOptions: { 44 | // Do not add X-Forwarded-For, etc. headers, because Heroku already adds it. 45 | xfwd: false, 46 | }, 47 | }).listen(port, host, function() { 48 | console.log('Running CORS Anywhere on ' + host + ':' + port); 49 | }); 50 | -------------------------------------------------------------------------------- /test/child.js: -------------------------------------------------------------------------------- 1 | // When this module is loaded, CORS Anywhere is started. 2 | // Then, a request is generated to warm up the server (just in case). 3 | // Then the base URL of CORS Anywhere is sent to the parent process. 4 | // ... 5 | // When the parent process is done, it sends an empty message to this child 6 | // process, which in turn records the change in used heap space. 7 | // The difference in heap space is finally sent back to the parent process. 8 | // ... 9 | // The parent process should then kill this child. 10 | 11 | process.on('uncaughtException', function(e) { 12 | console.error('Uncaught exception in child process: ' + e); 13 | console.error(e.stack); 14 | process.exit(-1); 15 | }); 16 | 17 | // Invoke memoryUsage() without using its result to make sure that any internal 18 | // datastructures that supports memoryUsage() is initialized and won't pollute 19 | // the memory usage measurement later on. 20 | process.memoryUsage(); 21 | 22 | var heapUsedStart = 0; 23 | function getMemoryUsage(callback) { 24 | // Note: Requires --expose-gc 25 | // 6 is the minimum amount of gc() calls before calling gc() again does not 26 | // reduce memory any more. 27 | for (var i = 0; i < 6; ++i) { 28 | global.gc(); 29 | } 30 | callback(process.memoryUsage().heapUsed); 31 | } 32 | 33 | var server; 34 | if (process.argv.indexOf('use-http-instead-of-cors-anywhere') >= 0) { 35 | server = require('http').createServer(function(req, res) { res.end(); }); 36 | } else { 37 | server = require('../').createServer(); 38 | } 39 | 40 | server.listen(0, function() { 41 | // Perform 1 request to warm up. 42 | require('http').get({ 43 | hostname: '127.0.0.1', 44 | port: server.address().port, 45 | path: '/http://invalid:99999', 46 | agent: false, 47 | }, function() { 48 | notifyParent(); 49 | }); 50 | 51 | function notifyParent() { 52 | getMemoryUsage(function(usage) { 53 | heapUsedStart = usage; 54 | process.send('http://127.0.0.1:' + server.address().port + '/'); 55 | }); 56 | } 57 | }); 58 | 59 | process.once('message', function() { 60 | getMemoryUsage(function(heapUsedEnd) { 61 | var delta = heapUsedEnd - heapUsedStart; 62 | process.send(delta); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo of CORS Anywhere 6 | 7 | 46 | 47 | 48 |
49 | CORS Anywhere demo • GithubLive server. 50 | 54 | 58 | 61 |
62 |
63 | 64 |
65 | 66 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /lib/rate-limit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function createRateLimitChecker(CORSANYWHERE_RATELIMIT) { 3 | // Configure rate limit. The following format is accepted for CORSANYWHERE_RATELIMIT: 4 | // 5 | // where is a space-separated list of strings or regexes (/.../) that 6 | // matches the whole host (ports have to be listed explicitly if applicable). 7 | // cannot be zero. 8 | // 9 | // Examples: 10 | // - Allow any origin to make one request per 5 minutes: 11 | // 1 5 12 | // 13 | // - Allow example.com to make an unlimited number of requests, and the others 1 per 5 minutes. 14 | // 1 5 example.com 15 | // 16 | // - Allow example.com, or any subdomain to make any number of requests and block the rest: 17 | // 0 1 /(.*\.)?example\.com/ 18 | // 19 | // - Allow example.com and www.example.com, and block the rest: 20 | // 0 1 example.com www.example.com 21 | var rateLimitConfig = /^(\d+) (\d+)(?:\s*$|\s+(.+)$)/.exec(CORSANYWHERE_RATELIMIT); 22 | if (!rateLimitConfig) { 23 | // No rate limit by default. 24 | return function checkRateLimit() {}; 25 | } 26 | var maxRequestsPerPeriod = parseInt(rateLimitConfig[1]); 27 | var periodInMinutes = parseInt(rateLimitConfig[2]); 28 | var unlimitedPattern = rateLimitConfig[3]; // Will become a RegExp or void. 29 | if (unlimitedPattern) { 30 | var unlimitedPatternParts = []; 31 | unlimitedPattern.trim().split(/\s+/).forEach(function(unlimitedHost, i) { 32 | var startsWithSlash = unlimitedHost.charAt(0) === '/'; 33 | var endsWithSlash = unlimitedHost.slice(-1) === '/'; 34 | if (startsWithSlash || endsWithSlash) { 35 | if (unlimitedHost.length === 1 || !startsWithSlash || !endsWithSlash) { 36 | throw new Error('Invalid CORSANYWHERE_RATELIMIT. Regex at index ' + i + 37 | ' must start and end with a slash ("/").'); 38 | } 39 | unlimitedHost = unlimitedHost.slice(1, -1); 40 | // Throws if the pattern is invalid. 41 | new RegExp(unlimitedHost); 42 | } else { 43 | // Just escape RegExp characters even though they cannot appear in a host name. 44 | // The only actual important escape is the dot. 45 | unlimitedHost = unlimitedHost.replace(/[$()*+.?[\\\]^{|}]/g, '\\$&'); 46 | } 47 | unlimitedPatternParts.push(unlimitedHost); 48 | }); 49 | unlimitedPattern = new RegExp('^(?:' + unlimitedPatternParts.join('|') + ')$', 'i'); 50 | } 51 | 52 | var accessedHosts = Object.create(null); 53 | setInterval(function() { 54 | accessedHosts = Object.create(null); 55 | }, periodInMinutes * 60000); 56 | 57 | var rateLimitMessage = 'The number of requests is limited to ' + maxRequestsPerPeriod + 58 | (periodInMinutes === 1 ? ' per minute' : ' per ' + periodInMinutes + ' minutes') + '. ' + 59 | 'Please self-host CORS Anywhere if you need more quota. ' + 60 | 'See https://github.com/Rob--W/cors-anywhere#demo-server'; 61 | 62 | return function checkRateLimit(origin) { 63 | var host = origin.replace(/^[\w\-]+:\/\//i, ''); 64 | if (unlimitedPattern && unlimitedPattern.test(host)) { 65 | return; 66 | } 67 | var count = accessedHosts[host] || 0; 68 | ++count; 69 | if (count > maxRequestsPerPeriod) { 70 | return rateLimitMessage; 71 | } 72 | accessedHosts[host] = count; 73 | }; 74 | }; 75 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | var nock = require('nock'); 2 | if (parseInt(process.versions.node, 10) >= 8) { 3 | // See DEP0066 at https://nodejs.org/api/deprecations.html. 4 | // _headers and _headerNames have been removed from Node v8, which causes 5 | // nock <= 9.0.13 to fail. The snippet below monkey-patches the library, see 6 | // https://github.com/node-nock/nock/pull/929/commits/f6369d0edd2a172024124f 7 | // for the equivalent logic without proxies. 8 | Object.defineProperty(require('http').ClientRequest.prototype, '_headers', { 9 | get: function() { 10 | var request = this; 11 | // eslint-disable-next-line no-undef 12 | return new Proxy(request.getHeaders(), { 13 | set: function(target, property, value) { 14 | request.setHeader(property, value); 15 | return true; 16 | }, 17 | }); 18 | }, 19 | set: function() { 20 | // Ignore. 21 | }, 22 | }); 23 | } 24 | 25 | nock.enableNetConnect('127.0.0.1'); 26 | 27 | function echoheaders(origin) { 28 | nock(origin) 29 | .persist() 30 | .get('/echoheaders') 31 | .reply(function() { 32 | var headers = this.req.headers; 33 | var excluded_headers = [ 34 | 'accept-encoding', 35 | 'user-agent', 36 | 'connection', 37 | // Remove this header since its value is platform-specific. 38 | 'x-forwarded-for', 39 | 'test-include-xfwd', 40 | ]; 41 | if (!('test-include-xfwd' in headers)) { 42 | excluded_headers.push('x-forwarded-port'); 43 | excluded_headers.push('x-forwarded-proto'); 44 | } 45 | var response = {}; 46 | Object.keys(headers).forEach(function(name) { 47 | if (excluded_headers.indexOf(name) === -1) { 48 | response[name] = headers[name]; 49 | } 50 | }); 51 | return response; 52 | }); 53 | } 54 | 55 | nock('http://example.com') 56 | .persist() 57 | .get('/') 58 | .reply(200, 'Response from example.com') 59 | 60 | .post('/echopost') 61 | .reply(200, function(uri, requestBody) { 62 | return requestBody; 63 | }) 64 | 65 | .get('/setcookie') 66 | .reply(200, '', { 67 | 'Set-Cookie': 'x', 68 | 'Set-Cookie2': 'y', 69 | 'Set-Cookie3': 'z', // This is not a special cookie setting header. 70 | }) 71 | 72 | .get('/redirecttarget') 73 | .reply(200, 'redirect target', { 74 | 'Some-header': 'value', 75 | }) 76 | 77 | .head('/redirect') 78 | .reply(302, '', { 79 | Location: '/redirecttarget', 80 | }) 81 | 82 | .get('/redirect') 83 | .reply(302, 'redirecting...', { 84 | 'header at redirect': 'should not be here', 85 | Location: '/redirecttarget', 86 | }) 87 | 88 | .get('/redirectposttarget') 89 | .reply(200, 'post target') 90 | 91 | .post('/redirectposttarget') 92 | .reply(200, 'post target (POST)') 93 | 94 | .post('/redirectpost') 95 | .reply(302, 'redirecting...', { 96 | Location: '/redirectposttarget', 97 | }) 98 | 99 | .post('/redirect307') 100 | .reply(307, 'redirecting...', { 101 | Location: '/redirectposttarget', 102 | }) 103 | 104 | .get('/redirect2redirect') 105 | .reply(302, 'redirecting to redirect...', { 106 | Location: '/redirect', 107 | }) 108 | 109 | .get('/redirectloop') 110 | .reply(302, 'redirecting ad infinitum...', { 111 | Location: '/redirectloop', 112 | }) 113 | 114 | .get('/redirectwithoutlocation') 115 | .reply(302, 'maybe found') 116 | 117 | .get('/redirectinvalidlocation') 118 | .reply(302, 'redirecting to junk...', { 119 | Location: 'http:///', 120 | }) 121 | 122 | .get('/proxyerror') 123 | .replyWithError('throw node') 124 | ; 125 | 126 | nock('https://example.com') 127 | .persist() 128 | .get('/') 129 | .reply(200, 'Response from https://example.com') 130 | ; 131 | 132 | nock('http://example.com.com') 133 | .persist() 134 | .get('/') 135 | .reply(200, 'Response from example.com.com') 136 | ; 137 | 138 | nock('http://example.com:1234') 139 | .persist() 140 | .get('/') 141 | .reply(200, 'Response from example.com:1234') 142 | ; 143 | 144 | nock('http://prefix.example.com') 145 | .persist() 146 | .get('/') 147 | .reply(200, 'Response from prefix.example.com') 148 | ; 149 | 150 | echoheaders('http://example.com'); 151 | echoheaders('http://example.com:1337'); 152 | echoheaders('https://example.com'); 153 | echoheaders('https://example.com:1337'); 154 | 155 | nock('http://robots.txt') 156 | .get('/') 157 | .reply(200, 'this is http://robots.txt'); 158 | -------------------------------------------------------------------------------- /test/test-memory.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | // Run this specific test using: 3 | // npm test -- -f memory 4 | var http = require('http'); 5 | var path = require('path'); 6 | var url = require('url'); 7 | var fork = require('child_process').fork; 8 | 9 | describe('memory usage', function() { 10 | var cors_api_url; 11 | 12 | var server; 13 | var cors_anywhere_child; 14 | before(function(done) { 15 | server = http.createServer(function(req, res) { 16 | res.writeHead(200); 17 | res.end(); 18 | }).listen(0, function() { 19 | done(); 20 | }); 21 | }); 22 | 23 | after(function(done) { 24 | server.close(function() { 25 | done(); 26 | }); 27 | }); 28 | 29 | beforeEach(function(done) { 30 | var cors_module_path = path.join(__dirname, 'child'); 31 | var args = []; 32 | // Uncomment this if you want to compare the performance of CORS Anywhere 33 | // with the standard no-op http module. 34 | // args.push('use-http-instead-of-cors-anywhere'); 35 | var nodeOptionsArgs = ['--expose-gc']; 36 | var nodeMajorV = parseInt(process.versions.node, 10); 37 | // Node 11.3.0+, 10.14.0+, 8.14.0+, 6.15.0+ restrict header sizes 38 | // (CVE-2018-12121), and need to be passed the --max-http-header-size flag 39 | // to not reject large headers. 40 | if (nodeMajorV >= 11 || 41 | nodeMajorV === 10 || 42 | nodeMajorV === 8 || 43 | nodeMajorV === 6) { 44 | nodeOptionsArgs.push('--max-http-header-size=60000'); 45 | } 46 | cors_anywhere_child = fork(cors_module_path, args, { 47 | execArgv: nodeOptionsArgs, 48 | }); 49 | cors_anywhere_child.once('message', function(cors_url) { 50 | cors_api_url = cors_url; 51 | done(); 52 | }); 53 | }); 54 | 55 | afterEach(function() { 56 | cors_anywhere_child.kill(); 57 | }); 58 | 59 | /** 60 | * Perform N CORS Anywhere proxy requests to a simple test server. 61 | * 62 | * @param {number} n - number of repetitions. 63 | * @param {number} requestSize - Approximate size of request in kilobytes. 64 | * @param {number} memMax - Expected maximum memory usage in kilobytes. 65 | * @param {function} done - Upon success, called without arguments. 66 | * Upon failure, called with the error as parameter. 67 | */ 68 | function performNRequests(n, requestSize, memMax, done) { 69 | var remaining = n; 70 | var request = url.parse( 71 | cors_api_url + 'http://127.0.0.1:' + server.address().port); 72 | request.agent = false; // Force Connection: Close 73 | request.headers = { 74 | 'Long-header': new Array(requestSize * 1e3).join('x'), 75 | }; 76 | (function requestAgain() { 77 | if (remaining-- === 0) { 78 | cors_anywhere_child.once('message', function(memory_usage_delta) { 79 | console.log('Memory usage delta: ' + memory_usage_delta + 80 | ' (' + n + ' requests of ' + requestSize + ' kb each)'); 81 | if (memory_usage_delta > memMax * 1e3) { 82 | // Note: Even if this error is reached, always profile (e.g. using 83 | // node-inspector) whether it is a true leak, and not e.g. noise 84 | // caused by the implementation of V8/Node.js. 85 | // Uncomment args.push('use-http-instead-of-cors-anywhere') at the 86 | // fork() call to get a sense of what's normal. 87 | throw new Error('Possible memory leak: ' + memory_usage_delta + 88 | ' bytes was not released, which exceeds the ' + memMax + 89 | ' kb limit by ' + 90 | Math.round(memory_usage_delta / memMax / 10 - 100) + '%.'); 91 | } 92 | done(); 93 | }); 94 | cors_anywhere_child.send(null); 95 | return; 96 | } 97 | http.request(request, function() { 98 | requestAgain(); 99 | }).on('error', function(error) { 100 | done(error); 101 | }).end(); 102 | })(); 103 | } 104 | 105 | it('100 GET requests 50k', function(done) { 106 | // This test is just for comparison with the following tests. 107 | performNRequests(100, 50, 1200, done); 108 | }); 109 | 110 | // 100x 1k and 100x 50k for comparison. 111 | // Both should use about the same amount of memory if there is no leak. 112 | it('1000 GET requests 1k', function(done) { 113 | // Every request should complete within 10ms. 114 | this.timeout(1000 * 10); 115 | performNRequests(1000, 1, 2000, done); 116 | }); 117 | it('1000 GET requests 50k', function(done) { 118 | // Every request should complete within 10ms. 119 | this.timeout(1000 * 10); 120 | performNRequests(1000, 50, 2000, done); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/test-examples.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CORS Anywhere is designed for use as a standalone server. Sometimes you want 3 | * to have extra functionality on top of the default CORS server. If it may be 4 | * useful to others, please open a feature request on the issue tracker at 5 | * https://github.com/Rob--W/cors-anywhere/issues. 6 | * 7 | * If it is only useful to your application, look below for some examples. 8 | * These examples are provided as-is without guarantees. Use at your own risk. 9 | */ 10 | 11 | /* eslint-env mocha */ 12 | require('./setup'); 13 | 14 | var createServer = require('../').createServer; 15 | var assert = require('assert'); 16 | var request = require('supertest'); 17 | 18 | var http = require('http'); 19 | 20 | describe('Examples', function() { 21 | // Note: In the examples below we don't listen on any port after calling 22 | // createServer() because it is not needed to start listening on a port if the 23 | // CORS Anywhere is only used internally. 24 | 25 | // And normally you have to listen on some port, like this: 26 | // 27 | // http_server.listen(port_number); 28 | // 29 | // But in these test, the call to request() automatically handles that part so 30 | // the examples don't have an explicit .listen() call. 31 | 32 | it('Rewrite proxy URL', function(done) { 33 | var cors_anywhere = createServer(); 34 | 35 | var http_server = http.createServer(function(req, res) { 36 | // For testing, check whether req.url is the same as what we input below. 37 | assert.strictEqual(req.url, '/dummy-for-testing'); 38 | 39 | // Basic example: Always proxy example.com. 40 | req.url = '/http://example.com'; 41 | 42 | cors_anywhere.emit('request', req, res); 43 | }); 44 | 45 | request(http_server) 46 | .get('/dummy-for-testing') 47 | .expect('Access-Control-Allow-Origin', '*') 48 | .expect('x-request-url', 'http://example.com/') 49 | .expect(200, 'Response from example.com', done); 50 | }); 51 | 52 | it('Transform response to uppercase (streaming)', function(done) { 53 | var cors_anywhere = createServer(); 54 | 55 | var http_server = http.createServer(function(req, res) { 56 | var originalWrite = res.write; 57 | 58 | res.write = function(data, encoding, callback) { 59 | if (Buffer.isBuffer(data)) { 60 | data = data.toString(); 61 | } 62 | 63 | assert.strictEqual(typeof data, 'string'); 64 | 65 | // This example shows how to transform the response to upper case. 66 | data = data.toUpperCase(); 67 | 68 | originalWrite.call(this, data, encoding, callback); 69 | }; 70 | 71 | cors_anywhere.emit('request', req, res); 72 | }); 73 | 74 | request(http_server) 75 | .get('/example.com') 76 | .expect('Access-Control-Allow-Origin', '*') 77 | .expect('x-request-url', 'http://example.com/') 78 | .expect(200, 'RESPONSE FROM EXAMPLE.COM', done); 79 | }); 80 | 81 | it('Transform response to uppercase (buffered)', function(done) { 82 | var cors_anywhere = createServer(); 83 | 84 | var http_server = http.createServer(function(req, res) { 85 | var originalWrite = res.write; 86 | var originalEnd = res.end; 87 | 88 | var buffers = []; 89 | 90 | res.write = function(data, encoding, callback) { 91 | assert.ok(Buffer.isBuffer(data) || typeof data === 'string'); 92 | 93 | buffers.push(data); 94 | if (callback) { 95 | process.nextTick(callback, null); 96 | } 97 | }; 98 | res.end = function(data, encoding, callback) { 99 | if (data) { 100 | this.write(data, encoding); 101 | } 102 | 103 | // After calling .end(), .write shouldn't be called any more. So let's 104 | // restore it so that the default error handling for writing to closed 105 | // streams would occur. 106 | this.write = originalWrite; 107 | 108 | // Combine all chunks. Note that we're assuming that all chunks are 109 | // utf8 strings or buffers whose content is utf8-encoded. If this 110 | // assumption is not true, then you have to update the .write method 111 | // above. 112 | data = buffers.join(''); 113 | 114 | // This example shows how to transform the response to upper case. 115 | data = data.toUpperCase(); 116 | 117 | // .end should be called once, so let's restore it so that any default 118 | // error handling occurs if it occurs again. 119 | this.end = originalEnd; 120 | this.end(data, 'utf8', callback); 121 | }; 122 | 123 | cors_anywhere.emit('request', req, res); 124 | }); 125 | 126 | request(http_server) 127 | .get('/example.com') 128 | .expect('Access-Control-Allow-Origin', '*') 129 | .expect('x-request-url', 'http://example.com/') 130 | .expect(200, 'RESPONSE FROM EXAMPLE.COM', done); 131 | }); 132 | }); 133 | 134 | -------------------------------------------------------------------------------- /lib/regexp-top-level-domain.js: -------------------------------------------------------------------------------- 1 | // Based on http://data.iana.org/TLD/tlds-alpha-by-domain.txt 2 | // '/\\.(?:' + document.body.firstChild.textContent.trim().split('\n').slice(1).join('|') + ')$/i'; 3 | 4 | /* eslint max-len:0 */ 5 | 6 | // # Version 2021031700, Last Updated Wed Mar 17 07:07:01 2021 UTC 7 | var regexp = /\.(?:AAA|AARP|ABARTH|ABB|ABBOTT|ABBVIE|ABC|ABLE|ABOGADO|ABUDHABI|AC|ACADEMY|ACCENTURE|ACCOUNTANT|ACCOUNTANTS|ACO|ACTOR|AD|ADAC|ADS|ADULT|AE|AEG|AERO|AETNA|AF|AFAMILYCOMPANY|AFL|AFRICA|AG|AGAKHAN|AGENCY|AI|AIG|AIRBUS|AIRFORCE|AIRTEL|AKDN|AL|ALFAROMEO|ALIBABA|ALIPAY|ALLFINANZ|ALLSTATE|ALLY|ALSACE|ALSTOM|AM|AMAZON|AMERICANEXPRESS|AMERICANFAMILY|AMEX|AMFAM|AMICA|AMSTERDAM|ANALYTICS|ANDROID|ANQUAN|ANZ|AO|AOL|APARTMENTS|APP|APPLE|AQ|AQUARELLE|AR|ARAB|ARAMCO|ARCHI|ARMY|ARPA|ART|ARTE|AS|ASDA|ASIA|ASSOCIATES|AT|ATHLETA|ATTORNEY|AU|AUCTION|AUDI|AUDIBLE|AUDIO|AUSPOST|AUTHOR|AUTO|AUTOS|AVIANCA|AW|AWS|AX|AXA|AZ|AZURE|BA|BABY|BAIDU|BANAMEX|BANANAREPUBLIC|BAND|BANK|BAR|BARCELONA|BARCLAYCARD|BARCLAYS|BAREFOOT|BARGAINS|BASEBALL|BASKETBALL|BAUHAUS|BAYERN|BB|BBC|BBT|BBVA|BCG|BCN|BD|BE|BEATS|BEAUTY|BEER|BENTLEY|BERLIN|BEST|BESTBUY|BET|BF|BG|BH|BHARTI|BI|BIBLE|BID|BIKE|BING|BINGO|BIO|BIZ|BJ|BLACK|BLACKFRIDAY|BLOCKBUSTER|BLOG|BLOOMBERG|BLUE|BM|BMS|BMW|BN|BNPPARIBAS|BO|BOATS|BOEHRINGER|BOFA|BOM|BOND|BOO|BOOK|BOOKING|BOSCH|BOSTIK|BOSTON|BOT|BOUTIQUE|BOX|BR|BRADESCO|BRIDGESTONE|BROADWAY|BROKER|BROTHER|BRUSSELS|BS|BT|BUDAPEST|BUGATTI|BUILD|BUILDERS|BUSINESS|BUY|BUZZ|BV|BW|BY|BZ|BZH|CA|CAB|CAFE|CAL|CALL|CALVINKLEIN|CAM|CAMERA|CAMP|CANCERRESEARCH|CANON|CAPETOWN|CAPITAL|CAPITALONE|CAR|CARAVAN|CARDS|CARE|CAREER|CAREERS|CARS|CASA|CASE|CASH|CASINO|CAT|CATERING|CATHOLIC|CBA|CBN|CBRE|CBS|CC|CD|CENTER|CEO|CERN|CF|CFA|CFD|CG|CH|CHANEL|CHANNEL|CHARITY|CHASE|CHAT|CHEAP|CHINTAI|CHRISTMAS|CHROME|CHURCH|CI|CIPRIANI|CIRCLE|CISCO|CITADEL|CITI|CITIC|CITY|CITYEATS|CK|CL|CLAIMS|CLEANING|CLICK|CLINIC|CLINIQUE|CLOTHING|CLOUD|CLUB|CLUBMED|CM|CN|CO|COACH|CODES|COFFEE|COLLEGE|COLOGNE|COM|COMCAST|COMMBANK|COMMUNITY|COMPANY|COMPARE|COMPUTER|COMSEC|CONDOS|CONSTRUCTION|CONSULTING|CONTACT|CONTRACTORS|COOKING|COOKINGCHANNEL|COOL|COOP|CORSICA|COUNTRY|COUPON|COUPONS|COURSES|CPA|CR|CREDIT|CREDITCARD|CREDITUNION|CRICKET|CROWN|CRS|CRUISE|CRUISES|CSC|CU|CUISINELLA|CV|CW|CX|CY|CYMRU|CYOU|CZ|DABUR|DAD|DANCE|DATA|DATE|DATING|DATSUN|DAY|DCLK|DDS|DE|DEAL|DEALER|DEALS|DEGREE|DELIVERY|DELL|DELOITTE|DELTA|DEMOCRAT|DENTAL|DENTIST|DESI|DESIGN|DEV|DHL|DIAMONDS|DIET|DIGITAL|DIRECT|DIRECTORY|DISCOUNT|DISCOVER|DISH|DIY|DJ|DK|DM|DNP|DO|DOCS|DOCTOR|DOG|DOMAINS|DOT|DOWNLOAD|DRIVE|DTV|DUBAI|DUCK|DUNLOP|DUPONT|DURBAN|DVAG|DVR|DZ|EARTH|EAT|EC|ECO|EDEKA|EDU|EDUCATION|EE|EG|EMAIL|EMERCK|ENERGY|ENGINEER|ENGINEERING|ENTERPRISES|EPSON|EQUIPMENT|ER|ERICSSON|ERNI|ES|ESQ|ESTATE|ET|ETISALAT|EU|EUROVISION|EUS|EVENTS|EXCHANGE|EXPERT|EXPOSED|EXPRESS|EXTRASPACE|FAGE|FAIL|FAIRWINDS|FAITH|FAMILY|FAN|FANS|FARM|FARMERS|FASHION|FAST|FEDEX|FEEDBACK|FERRARI|FERRERO|FI|FIAT|FIDELITY|FIDO|FILM|FINAL|FINANCE|FINANCIAL|FIRE|FIRESTONE|FIRMDALE|FISH|FISHING|FIT|FITNESS|FJ|FK|FLICKR|FLIGHTS|FLIR|FLORIST|FLOWERS|FLY|FM|FO|FOO|FOOD|FOODNETWORK|FOOTBALL|FORD|FOREX|FORSALE|FORUM|FOUNDATION|FOX|FR|FREE|FRESENIUS|FRL|FROGANS|FRONTDOOR|FRONTIER|FTR|FUJITSU|FUJIXEROX|FUN|FUND|FURNITURE|FUTBOL|FYI|GA|GAL|GALLERY|GALLO|GALLUP|GAME|GAMES|GAP|GARDEN|GAY|GB|GBIZ|GD|GDN|GE|GEA|GENT|GENTING|GEORGE|GF|GG|GGEE|GH|GI|GIFT|GIFTS|GIVES|GIVING|GL|GLADE|GLASS|GLE|GLOBAL|GLOBO|GM|GMAIL|GMBH|GMO|GMX|GN|GODADDY|GOLD|GOLDPOINT|GOLF|GOO|GOODYEAR|GOOG|GOOGLE|GOP|GOT|GOV|GP|GQ|GR|GRAINGER|GRAPHICS|GRATIS|GREEN|GRIPE|GROCERY|GROUP|GS|GT|GU|GUARDIAN|GUCCI|GUGE|GUIDE|GUITARS|GURU|GW|GY|HAIR|HAMBURG|HANGOUT|HAUS|HBO|HDFC|HDFCBANK|HEALTH|HEALTHCARE|HELP|HELSINKI|HERE|HERMES|HGTV|HIPHOP|HISAMITSU|HITACHI|HIV|HK|HKT|HM|HN|HOCKEY|HOLDINGS|HOLIDAY|HOMEDEPOT|HOMEGOODS|HOMES|HOMESENSE|HONDA|HORSE|HOSPITAL|HOST|HOSTING|HOT|HOTELES|HOTELS|HOTMAIL|HOUSE|HOW|HR|HSBC|HT|HU|HUGHES|HYATT|HYUNDAI|IBM|ICBC|ICE|ICU|ID|IE|IEEE|IFM|IKANO|IL|IM|IMAMAT|IMDB|IMMO|IMMOBILIEN|IN|INC|INDUSTRIES|INFINITI|INFO|ING|INK|INSTITUTE|INSURANCE|INSURE|INT|INTERNATIONAL|INTUIT|INVESTMENTS|IO|IPIRANGA|IQ|IR|IRISH|IS|ISMAILI|IST|ISTANBUL|IT|ITAU|ITV|IVECO|JAGUAR|JAVA|JCB|JE|JEEP|JETZT|JEWELRY|JIO|JLL|JM|JMP|JNJ|JO|JOBS|JOBURG|JOT|JOY|JP|JPMORGAN|JPRS|JUEGOS|JUNIPER|KAUFEN|KDDI|KE|KERRYHOTELS|KERRYLOGISTICS|KERRYPROPERTIES|KFH|KG|KH|KI|KIA|KIM|KINDER|KINDLE|KITCHEN|KIWI|KM|KN|KOELN|KOMATSU|KOSHER|KP|KPMG|KPN|KR|KRD|KRED|KUOKGROUP|KW|KY|KYOTO|KZ|LA|LACAIXA|LAMBORGHINI|LAMER|LANCASTER|LANCIA|LAND|LANDROVER|LANXESS|LASALLE|LAT|LATINO|LATROBE|LAW|LAWYER|LB|LC|LDS|LEASE|LECLERC|LEFRAK|LEGAL|LEGO|LEXUS|LGBT|LI|LIDL|LIFE|LIFEINSURANCE|LIFESTYLE|LIGHTING|LIKE|LILLY|LIMITED|LIMO|LINCOLN|LINDE|LINK|LIPSY|LIVE|LIVING|LIXIL|LK|LLC|LLP|LOAN|LOANS|LOCKER|LOCUS|LOFT|LOL|LONDON|LOTTE|LOTTO|LOVE|LPL|LPLFINANCIAL|LR|LS|LT|LTD|LTDA|LU|LUNDBECK|LUXE|LUXURY|LV|LY|MA|MACYS|MADRID|MAIF|MAISON|MAKEUP|MAN|MANAGEMENT|MANGO|MAP|MARKET|MARKETING|MARKETS|MARRIOTT|MARSHALLS|MASERATI|MATTEL|MBA|MC|MCKINSEY|MD|ME|MED|MEDIA|MEET|MELBOURNE|MEME|MEMORIAL|MEN|MENU|MERCKMSD|MG|MH|MIAMI|MICROSOFT|MIL|MINI|MINT|MIT|MITSUBISHI|MK|ML|MLB|MLS|MM|MMA|MN|MO|MOBI|MOBILE|MODA|MOE|MOI|MOM|MONASH|MONEY|MONSTER|MORMON|MORTGAGE|MOSCOW|MOTO|MOTORCYCLES|MOV|MOVIE|MP|MQ|MR|MS|MSD|MT|MTN|MTR|MU|MUSEUM|MUTUAL|MV|MW|MX|MY|MZ|NA|NAB|NAGOYA|NAME|NATIONWIDE|NATURA|NAVY|NBA|NC|NE|NEC|NET|NETBANK|NETFLIX|NETWORK|NEUSTAR|NEW|NEWS|NEXT|NEXTDIRECT|NEXUS|NF|NFL|NG|NGO|NHK|NI|NICO|NIKE|NIKON|NINJA|NISSAN|NISSAY|NL|NO|NOKIA|NORTHWESTERNMUTUAL|NORTON|NOW|NOWRUZ|NOWTV|NP|NR|NRA|NRW|NTT|NU|NYC|NZ|OBI|OBSERVER|OFF|OFFICE|OKINAWA|OLAYAN|OLAYANGROUP|OLDNAVY|OLLO|OM|OMEGA|ONE|ONG|ONL|ONLINE|ONYOURSIDE|OOO|OPEN|ORACLE|ORANGE|ORG|ORGANIC|ORIGINS|OSAKA|OTSUKA|OTT|OVH|PA|PAGE|PANASONIC|PARIS|PARS|PARTNERS|PARTS|PARTY|PASSAGENS|PAY|PCCW|PE|PET|PF|PFIZER|PG|PH|PHARMACY|PHD|PHILIPS|PHONE|PHOTO|PHOTOGRAPHY|PHOTOS|PHYSIO|PICS|PICTET|PICTURES|PID|PIN|PING|PINK|PIONEER|PIZZA|PK|PL|PLACE|PLAY|PLAYSTATION|PLUMBING|PLUS|PM|PN|PNC|POHL|POKER|POLITIE|PORN|POST|PR|PRAMERICA|PRAXI|PRESS|PRIME|PRO|PROD|PRODUCTIONS|PROF|PROGRESSIVE|PROMO|PROPERTIES|PROPERTY|PROTECTION|PRU|PRUDENTIAL|PS|PT|PUB|PW|PWC|PY|QA|QPON|QUEBEC|QUEST|QVC|RACING|RADIO|RAID|RE|READ|REALESTATE|REALTOR|REALTY|RECIPES|RED|REDSTONE|REDUMBRELLA|REHAB|REISE|REISEN|REIT|RELIANCE|REN|RENT|RENTALS|REPAIR|REPORT|REPUBLICAN|REST|RESTAURANT|REVIEW|REVIEWS|REXROTH|RICH|RICHARDLI|RICOH|RIL|RIO|RIP|RMIT|RO|ROCHER|ROCKS|RODEO|ROGERS|ROOM|RS|RSVP|RU|RUGBY|RUHR|RUN|RW|RWE|RYUKYU|SA|SAARLAND|SAFE|SAFETY|SAKURA|SALE|SALON|SAMSCLUB|SAMSUNG|SANDVIK|SANDVIKCOROMANT|SANOFI|SAP|SARL|SAS|SAVE|SAXO|SB|SBI|SBS|SC|SCA|SCB|SCHAEFFLER|SCHMIDT|SCHOLARSHIPS|SCHOOL|SCHULE|SCHWARZ|SCIENCE|SCJOHNSON|SCOT|SD|SE|SEARCH|SEAT|SECURE|SECURITY|SEEK|SELECT|SENER|SERVICES|SES|SEVEN|SEW|SEX|SEXY|SFR|SG|SH|SHANGRILA|SHARP|SHAW|SHELL|SHIA|SHIKSHA|SHOES|SHOP|SHOPPING|SHOUJI|SHOW|SHOWTIME|SI|SILK|SINA|SINGLES|SITE|SJ|SK|SKI|SKIN|SKY|SKYPE|SL|SLING|SM|SMART|SMILE|SN|SNCF|SO|SOCCER|SOCIAL|SOFTBANK|SOFTWARE|SOHU|SOLAR|SOLUTIONS|SONG|SONY|SOY|SPA|SPACE|SPORT|SPOT|SPREADBETTING|SR|SRL|SS|ST|STADA|STAPLES|STAR|STATEBANK|STATEFARM|STC|STCGROUP|STOCKHOLM|STORAGE|STORE|STREAM|STUDIO|STUDY|STYLE|SU|SUCKS|SUPPLIES|SUPPLY|SUPPORT|SURF|SURGERY|SUZUKI|SV|SWATCH|SWIFTCOVER|SWISS|SX|SY|SYDNEY|SYSTEMS|SZ|TAB|TAIPEI|TALK|TAOBAO|TARGET|TATAMOTORS|TATAR|TATTOO|TAX|TAXI|TC|TCI|TD|TDK|TEAM|TECH|TECHNOLOGY|TEL|TEMASEK|TENNIS|TEVA|TF|TG|TH|THD|THEATER|THEATRE|TIAA|TICKETS|TIENDA|TIFFANY|TIPS|TIRES|TIROL|TJ|TJMAXX|TJX|TK|TKMAXX|TL|TM|TMALL|TN|TO|TODAY|TOKYO|TOOLS|TOP|TORAY|TOSHIBA|TOTAL|TOURS|TOWN|TOYOTA|TOYS|TR|TRADE|TRADING|TRAINING|TRAVEL|TRAVELCHANNEL|TRAVELERS|TRAVELERSINSURANCE|TRUST|TRV|TT|TUBE|TUI|TUNES|TUSHU|TV|TVS|TW|TZ|UA|UBANK|UBS|UG|UK|UNICOM|UNIVERSITY|UNO|UOL|UPS|US|UY|UZ|VA|VACATIONS|VANA|VANGUARD|VC|VE|VEGAS|VENTURES|VERISIGN|VERSICHERUNG|VET|VG|VI|VIAJES|VIDEO|VIG|VIKING|VILLAS|VIN|VIP|VIRGIN|VISA|VISION|VIVA|VIVO|VLAANDEREN|VN|VODKA|VOLKSWAGEN|VOLVO|VOTE|VOTING|VOTO|VOYAGE|VU|VUELOS|WALES|WALMART|WALTER|WANG|WANGGOU|WATCH|WATCHES|WEATHER|WEATHERCHANNEL|WEBCAM|WEBER|WEBSITE|WED|WEDDING|WEIBO|WEIR|WF|WHOSWHO|WIEN|WIKI|WILLIAMHILL|WIN|WINDOWS|WINE|WINNERS|WME|WOLTERSKLUWER|WOODSIDE|WORK|WORKS|WORLD|WOW|WS|WTC|WTF|XBOX|XEROX|XFINITY|XIHUAN|XIN|XN--11B4C3D|XN--1CK2E1B|XN--1QQW23A|XN--2SCRJ9C|XN--30RR7Y|XN--3BST00M|XN--3DS443G|XN--3E0B707E|XN--3HCRJ9C|XN--3OQ18VL8PN36A|XN--3PXU8K|XN--42C2D9A|XN--45BR5CYL|XN--45BRJ9C|XN--45Q11C|XN--4DBRK0CE|XN--4GBRIM|XN--54B7FTA0CC|XN--55QW42G|XN--55QX5D|XN--5SU34J936BGSG|XN--5TZM5G|XN--6FRZ82G|XN--6QQ986B3XL|XN--80ADXHKS|XN--80AO21A|XN--80AQECDR1A|XN--80ASEHDB|XN--80ASWG|XN--8Y0A063A|XN--90A3AC|XN--90AE|XN--90AIS|XN--9DBQ2A|XN--9ET52U|XN--9KRT00A|XN--B4W605FERD|XN--BCK1B9A5DRE4C|XN--C1AVG|XN--C2BR7G|XN--CCK2B3B|XN--CCKWCXETD|XN--CG4BKI|XN--CLCHC0EA0B2G2A9GCD|XN--CZR694B|XN--CZRS0T|XN--CZRU2D|XN--D1ACJ3B|XN--D1ALF|XN--E1A4C|XN--ECKVDTC9D|XN--EFVY88H|XN--FCT429K|XN--FHBEI|XN--FIQ228C5HS|XN--FIQ64B|XN--FIQS8S|XN--FIQZ9S|XN--FJQ720A|XN--FLW351E|XN--FPCRJ9C3D|XN--FZC2C9E2C|XN--FZYS8D69UVGM|XN--G2XX48C|XN--GCKR3F0F|XN--GECRJ9C|XN--GK3AT1E|XN--H2BREG3EVE|XN--H2BRJ9C|XN--H2BRJ9C8C|XN--HXT814E|XN--I1B6B1A6A2E|XN--IMR513N|XN--IO0A7I|XN--J1AEF|XN--J1AMH|XN--J6W193G|XN--JLQ480N2RG|XN--JLQ61U9W7B|XN--JVR189M|XN--KCRX77D1X4A|XN--KPRW13D|XN--KPRY57D|XN--KPUT3I|XN--L1ACC|XN--LGBBAT1AD8J|XN--MGB9AWBF|XN--MGBA3A3EJT|XN--MGBA3A4F16A|XN--MGBA7C0BBN0A|XN--MGBAAKC7DVF|XN--MGBAAM7A8H|XN--MGBAB2BD|XN--MGBAH1A3HJKRD|XN--MGBAI9AZGQP6J|XN--MGBAYH7GPA|XN--MGBBH1A|XN--MGBBH1A71E|XN--MGBC0A9AZCG|XN--MGBCA7DZDO|XN--MGBCPQ6GPA1A|XN--MGBERP4A5D4AR|XN--MGBGU82A|XN--MGBI4ECEXP|XN--MGBPL2FH|XN--MGBT3DHD|XN--MGBTX2B|XN--MGBX4CD0AB|XN--MIX891F|XN--MK1BU44C|XN--MXTQ1M|XN--NGBC5AZD|XN--NGBE9E0A|XN--NGBRX|XN--NODE|XN--NQV7F|XN--NQV7FS00EMA|XN--NYQY26A|XN--O3CW4H|XN--OGBPF8FL|XN--OTU796D|XN--P1ACF|XN--P1AI|XN--PGBS0DH|XN--PSSY2U|XN--Q7CE6A|XN--Q9JYB4C|XN--QCKA1PMC|XN--QXA6A|XN--QXAM|XN--RHQV96G|XN--ROVU88B|XN--RVC1E0AM3E|XN--S9BRJ9C|XN--SES554G|XN--T60B56A|XN--TCKWE|XN--TIQ49XQYJ|XN--UNUP4Y|XN--VERMGENSBERATER-CTB|XN--VERMGENSBERATUNG-PWB|XN--VHQUV|XN--VUQ861B|XN--W4R85EL8FHU5DNRA|XN--W4RS40L|XN--WGBH1C|XN--WGBL6A|XN--XHQ521B|XN--XKC2AL3HYE2A|XN--XKC2DL3A5EE0H|XN--Y9A3AQ|XN--YFRO4I67O|XN--YGBI2AMMX|XN--ZFR164B|XXX|XYZ|YACHTS|YAHOO|YAMAXUN|YANDEX|YE|YODOBASHI|YOGA|YOKOHAMA|YOU|YOUTUBE|YT|YUN|ZA|ZAPPOS|ZARA|ZERO|ZIP|ZM|ZONE|ZUERICH|ZW)$/i; 8 | module.exports = regexp; 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/Rob--W/cors-anywhere.svg?branch=master)](https://travis-ci.com/Rob--W/cors-anywhere) 2 | [![Coverage Status](https://coveralls.io/repos/github/Rob--W/cors-anywhere/badge.svg?branch=master)](https://coveralls.io/github/Rob--W/cors-anywhere?branch=master) 3 | 4 | **CORS Anywhere** is a NodeJS proxy which adds CORS headers to the proxied request. 5 | 6 | The url to proxy is literally taken from the path, validated and proxied. The protocol 7 | part of the proxied URI is optional, and defaults to "http". If port 443 is specified, 8 | the protocol defaults to "https". 9 | 10 | This package does not put any restrictions on the http methods or headers, except for 11 | cookies. Requesting [user credentials](http://www.w3.org/TR/cors/#user-credentials) is disallowed. 12 | The app can be configured to require a header for proxying a request, for example to avoid 13 | a direct visit from the browser. 14 | 15 | ## Example 16 | 17 | ```javascript 18 | // Listen on a specific host via the HOST environment variable 19 | var host = process.env.HOST || '0.0.0.0'; 20 | // Listen on a specific port via the PORT environment variable 21 | var port = process.env.PORT || 8080; 22 | 23 | var cors_proxy = require('cors-anywhere'); 24 | cors_proxy.createServer({ 25 | originWhitelist: [], // Allow all origins 26 | requireHeader: ['origin', 'x-requested-with'], 27 | removeHeaders: ['cookie', 'cookie2'] 28 | }).listen(port, host, function() { 29 | console.log('Running CORS Anywhere on ' + host + ':' + port); 30 | }); 31 | 32 | ``` 33 | Request examples: 34 | 35 | * `http://localhost:8080/http://google.com/` - Google.com with CORS headers 36 | * `http://localhost:8080/google.com` - Same as previous. 37 | * `http://localhost:8080/google.com:443` - Proxies `https://google.com/` 38 | * `http://localhost:8080/` - Shows usage text, as defined in `libs/help.txt` 39 | * `http://localhost:8080/favicon.ico` - Replies 404 Not found 40 | 41 | Live examples: 42 | 43 | * https://cors-anywhere.herokuapp.com/ 44 | * https://robwu.nl/cors-anywhere.html - This demo shows how to use the API. 45 | 46 | ## Documentation 47 | 48 | ### Client 49 | 50 | To use the API, just prefix the URL with the API URL. Take a look at [demo.html](demo.html) for an example. 51 | A concise summary of the documentation is provided at [lib/help.txt](lib/help.txt). 52 | 53 | **Note: as of February 2021, access to the demo server requires an opt-in**, 54 | see: https://github.com/Rob--W/cors-anywhere/issues/301 55 | 56 | If you want to automatically enable cross-domain requests when needed, use the following snippet: 57 | 58 | ```javascript 59 | (function() { 60 | var cors_api_host = 'cors-anywhere.herokuapp.com'; 61 | var cors_api_url = 'https://' + cors_api_host + '/'; 62 | var slice = [].slice; 63 | var origin = window.location.protocol + '//' + window.location.host; 64 | var open = XMLHttpRequest.prototype.open; 65 | XMLHttpRequest.prototype.open = function() { 66 | var args = slice.call(arguments); 67 | var targetOrigin = /^https?:\/\/([^\/]+)/i.exec(args[1]); 68 | if (targetOrigin && targetOrigin[0].toLowerCase() !== origin && 69 | targetOrigin[1] !== cors_api_host) { 70 | args[1] = cors_api_url + args[1]; 71 | } 72 | return open.apply(this, args); 73 | }; 74 | })(); 75 | ``` 76 | 77 | If you're using jQuery, you can also use the following code **instead of** the previous one: 78 | 79 | ```javascript 80 | jQuery.ajaxPrefilter(function(options) { 81 | if (options.crossDomain && jQuery.support.cors) { 82 | options.url = 'https://cors-anywhere.herokuapp.com/' + options.url; 83 | } 84 | }); 85 | ``` 86 | 87 | ### Server 88 | 89 | The module exports `createServer(options)`, which creates a server that handles 90 | proxy requests. The following options are supported: 91 | 92 | * function `getProxyForUrl` - If set, specifies which intermediate proxy to use for a given URL. 93 | If the return value is void, a direct request is sent. The default implementation is 94 | [`proxy-from-env`](https://github.com/Rob--W/proxy-from-env), which respects the standard proxy 95 | environment variables (e.g. `https_proxy`, `no_proxy`, etc.). 96 | * array of strings `originBlacklist` - If set, requests whose origin is listed are blocked. 97 | Example: `['https://bad.example.com', 'http://bad.example.com']` 98 | * array of strings `originWhitelist` - If set, requests whose origin is not listed are blocked. 99 | If this list is empty, all origins are allowed. 100 | Example: `['https://good.example.com', 'http://good.example.com']` 101 | * function `handleInitialRequest` - If set, it is called with the request, response and a parsed 102 | URL of the requested destination (null if unavailable). If the function returns true, the request 103 | will not be handled further. Then the function is responsible for handling the request. 104 | This feature can be used to passively monitor requests, for example for logging (return false). 105 | * function `checkRateLimit` - If set, it is called with the origin (string) of the request. If this 106 | function returns a non-empty string, the request is rejected and the string is send to the client. 107 | * boolean `redirectSameOrigin` - If true, requests to URLs from the same origin will not be proxied but redirected. 108 | The primary purpose for this option is to save server resources by delegating the request to the client 109 | (since same-origin requests should always succeed, even without proxying). 110 | * array of strings `requireHeader` - If set, the request must include this header or the API will refuse to proxy. 111 | Recommended if you want to prevent users from using the proxy for normal browsing. 112 | Example: `['Origin', 'X-Requested-With']`. 113 | * array of lowercase strings `removeHeaders` - Exclude certain headers from being included in the request. 114 | Example: `["cookie"]` 115 | * dictionary of lowercase strings `setHeaders` - Set headers for the request (overwrites existing ones). 116 | Example: `{"x-powered-by": "CORS Anywhere"}` 117 | * number `corsMaxAge` - If set, an Access-Control-Max-Age request header with this value (in seconds) will be added. 118 | Example: `600` - Allow CORS preflight request to be cached by the browser for 10 minutes. 119 | * string `helpFile` - Set the help file (shown at the homepage). 120 | Example: `"myCustomHelpText.txt"` 121 | 122 | For advanced users, the following options are also provided. 123 | 124 | * `httpProxyOptions` - Under the hood, [http-proxy](https://github.com/nodejitsu/node-http-proxy) 125 | is used to proxy requests. Use this option if you really need to pass options 126 | to http-proxy. The documentation for these options can be found [here](https://github.com/nodejitsu/node-http-proxy#options). 127 | * `httpsOptions` - If set, a `https.Server` will be created. The given options are passed to the 128 | [`https.createServer`](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener) method. 129 | 130 | For even more advanced usage (building upon CORS Anywhere), 131 | see the sample code in [test/test-examples.js](test/test-examples.js). 132 | 133 | ### Demo server 134 | 135 | A public demo of CORS Anywhere is available at https://cors-anywhere.herokuapp.com. This server is 136 | only provided so that you can easily and quickly try out CORS Anywhere. To ensure that the service 137 | stays available to everyone, the number of requests per period is limited, except for requests from 138 | some explicitly whitelisted origins. 139 | 140 | **Note: as of February 2021, access to the demo server requires an opt-in**, 141 | see: https://github.com/Rob--W/cors-anywhere/issues/301 142 | 143 | If you expect lots of traffic, please host your own instance of CORS Anywhere, and make sure that 144 | the CORS Anywhere server only whitelists your site to prevent others from using your instance of 145 | CORS Anywhere as an open proxy. 146 | 147 | For instance, to run a CORS Anywhere server that accepts any request from some example.com sites on 148 | port 8080, use: 149 | ``` 150 | export PORT=8080 151 | export CORSANYWHERE_WHITELIST=https://example.com,http://example.com,http://example.com:8080 152 | node server.js 153 | ``` 154 | 155 | This application can immediately be run on Heroku, see https://devcenter.heroku.com/articles/nodejs 156 | for instructions. Note that their [Acceptable Use Policy](https://www.heroku.com/policy/aup) forbids 157 | the use of Heroku for operating an open proxy, so make sure that you either enforce a whitelist as 158 | shown above, or severly rate-limit the number of requests. 159 | 160 | For example, to blacklist abuse.example.com and rate-limit everything to 50 requests per 3 minutes, 161 | except for my.example.com and my2.example.com (which may be unlimited), use: 162 | 163 | ``` 164 | export PORT=8080 165 | export CORSANYWHERE_BLACKLIST=https://abuse.example.com,http://abuse.example.com 166 | export CORSANYWHERE_RATELIMIT='50 3 my.example.com my2.example.com' 167 | node server.js 168 | ``` 169 | 170 | 171 | ## License 172 | 173 | Copyright (C) 2013 - 2021 Rob Wu 174 | 175 | Permission is hereby granted, free of charge, to any person obtaining a copy of 176 | this software and associated documentation files (the "Software"), to deal in 177 | the Software without restriction, including without limitation the rights to 178 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 179 | of the Software, and to permit persons to whom the Software is furnished to do 180 | so, subject to the following conditions: 181 | 182 | The above copyright notice and this permission notice shall be included in all 183 | copies or substantial portions of the Software. 184 | 185 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 186 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 187 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 188 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 189 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 190 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 191 | SOFTWARE. 192 | -------------------------------------------------------------------------------- /test/test-ratelimit.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | var createRateLimitChecker = require('../lib/rate-limit'); 4 | 5 | var lolex = require('lolex'); 6 | var assert = require('assert'); 7 | 8 | function assertNotLimited(rateLimitReturnValue) { 9 | if (rateLimitReturnValue) { 10 | assert.fail('Expected no limit, but got ' + rateLimitReturnValue); 11 | } 12 | } 13 | 14 | function assertLimited(rateLimitReturnValue, limit, period) { 15 | var msg; 16 | if (period === 1) { 17 | msg = 'The number of requests is limited to ' + limit + ' per minute. '; 18 | } else { 19 | msg = 'The number of requests is limited to ' + limit + ' per ' + period + ' minutes. '; 20 | } 21 | msg += 'Please self-host CORS Anywhere if you need more quota. ' + 22 | 'See https://github.com/Rob--W/cors-anywhere#demo-server'; 23 | assert.equal(rateLimitReturnValue, msg); 24 | } 25 | 26 | describe('Rate limit', function() { 27 | var clock; 28 | beforeEach(function() { 29 | clock = lolex.install(); 30 | }); 31 | afterEach(function() { 32 | clock.uninstall(); 33 | }); 34 | it('is unlimited by default', function() { 35 | var checkRateLimit = createRateLimitChecker(); 36 | assertNotLimited(checkRateLimit('http://example.com')); 37 | assertNotLimited(checkRateLimit('https://example.com')); 38 | assertNotLimited(checkRateLimit('https://example.com:1234')); 39 | 40 | checkRateLimit = createRateLimitChecker(''); 41 | assertNotLimited(checkRateLimit('http://example.com')); 42 | 43 | checkRateLimit = createRateLimitChecker(' '); 44 | assertNotLimited(checkRateLimit('http://example.com')); 45 | }); 46 | 47 | it('zero per minute / 5 minutes', function() { 48 | var checkRateLimit = createRateLimitChecker('0 1'); 49 | assertLimited(checkRateLimit('http://example.com'), 0, 1); 50 | assertLimited(checkRateLimit('https://example.com'), 0, 1); 51 | 52 | checkRateLimit = createRateLimitChecker('0 5'); 53 | assertLimited(checkRateLimit('http://example.com'), 0, 5); 54 | assertLimited(checkRateLimit('https://example.com'), 0, 5); 55 | }); 56 | 57 | it('one per minute', function() { 58 | var checkRateLimit = createRateLimitChecker('1 1'); 59 | assertNotLimited(checkRateLimit('http://example.com')); 60 | assertLimited(checkRateLimit('http://example.com'), 1, 1); 61 | assertNotLimited(checkRateLimit('http://example.com:1234')); 62 | assertLimited(checkRateLimit('http://example.com:1234'), 1, 1); 63 | 64 | clock.tick(59000); 65 | assertLimited(checkRateLimit('http://example.com'), 1, 1); 66 | 67 | clock.tick(1000); 68 | assertNotLimited(checkRateLimit('http://example.com')); 69 | assertLimited(checkRateLimit('http://example.com'), 1, 1); 70 | assertNotLimited(checkRateLimit('http://example.com:1234')); 71 | assertLimited(checkRateLimit('http://example.com:1234'), 1, 1); 72 | }); 73 | 74 | it('different domains, one per minute', function() { 75 | var checkRateLimit = createRateLimitChecker('1 1'); 76 | assertNotLimited(checkRateLimit('http://example.com')); 77 | assertNotLimited(checkRateLimit('http://example.net')); 78 | assertNotLimited(checkRateLimit('http://wexample.net')); 79 | assertNotLimited(checkRateLimit('http://xample.net')); 80 | assertNotLimited(checkRateLimit('http://www.example.net')); 81 | assertLimited(checkRateLimit('http://example.com'), 1, 1); 82 | assertLimited(checkRateLimit('http://example.net'), 1, 1); 83 | assertLimited(checkRateLimit('http://wexample.net'), 1, 1); 84 | assertLimited(checkRateLimit('http://xample.net'), 1, 1); 85 | assertLimited(checkRateLimit('http://www.example.net'), 1, 1); 86 | 87 | clock.tick(60000); // 1 minute 88 | assertNotLimited(checkRateLimit('http://example.com')); 89 | assertNotLimited(checkRateLimit('http://example.net')); 90 | assertNotLimited(checkRateLimit('http://wexample.net')); 91 | assertNotLimited(checkRateLimit('http://xample.net')); 92 | assertNotLimited(checkRateLimit('http://www.example.net')); 93 | }); 94 | 95 | it('unlimited domains, string', function() { 96 | var checkRateLimit = createRateLimitChecker('1 2 example.com'); 97 | assertNotLimited(checkRateLimit('http://example.com')); 98 | assertNotLimited(checkRateLimit('http://example.com')); 99 | 100 | assertNotLimited(checkRateLimit('http://wexample.com')); 101 | assertNotLimited(checkRateLimit('http://xample.com')); 102 | assertNotLimited(checkRateLimit('http://www.example.com')); 103 | assertLimited(checkRateLimit('http://wexample.com'), 1, 2); 104 | assertLimited(checkRateLimit('http://xample.com'), 1, 2); 105 | assertLimited(checkRateLimit('http://www.example.com'), 1, 2); 106 | }); 107 | 108 | it('unlimited domains, RegExp', function() { 109 | var checkRateLimit = createRateLimitChecker('1 2 /example\\.com/'); 110 | assertNotLimited(checkRateLimit('http://example.com')); 111 | assertNotLimited(checkRateLimit('http://example.com')); 112 | 113 | assertNotLimited(checkRateLimit('http://wexample.com')); 114 | assertNotLimited(checkRateLimit('http://xample.com')); 115 | assertNotLimited(checkRateLimit('http://www.example.com')); 116 | assertLimited(checkRateLimit('http://wexample.com'), 1, 2); 117 | assertLimited(checkRateLimit('http://xample.com'), 1, 2); 118 | assertLimited(checkRateLimit('http://www.example.com'), 1, 2); 119 | }); 120 | 121 | it('multiple domains, string', function() { 122 | var checkRateLimit = createRateLimitChecker('1 2 a b cc '); 123 | assertNotLimited(checkRateLimit('http://a')); 124 | assertNotLimited(checkRateLimit('http://a')); 125 | assertNotLimited(checkRateLimit('http://b')); 126 | assertNotLimited(checkRateLimit('http://b')); 127 | assertNotLimited(checkRateLimit('http://cc')); 128 | assertNotLimited(checkRateLimit('http://cc')); 129 | assertNotLimited(checkRateLimit('http://c')); 130 | assertLimited(checkRateLimit('http://c'), 1, 2); 131 | }); 132 | 133 | it('multiple domains, RegExp', function() { 134 | var checkRateLimit = createRateLimitChecker('1 2 /a/ /b/ /cc/ '); 135 | assertNotLimited(checkRateLimit('http://a')); 136 | assertNotLimited(checkRateLimit('http://a')); 137 | assertNotLimited(checkRateLimit('http://b')); 138 | assertNotLimited(checkRateLimit('http://b')); 139 | assertNotLimited(checkRateLimit('http://cc')); 140 | assertNotLimited(checkRateLimit('http://cc')); 141 | assertNotLimited(checkRateLimit('http://ccc')); 142 | assertLimited(checkRateLimit('http://ccc'), 1, 2); 143 | }); 144 | 145 | it('multiple domains, string and RegExp', function() { 146 | var checkRateLimit = createRateLimitChecker('1 2 a /b/'); 147 | assertNotLimited(checkRateLimit('http://a')); 148 | assertNotLimited(checkRateLimit('http://a')); 149 | assertNotLimited(checkRateLimit('http://b')); 150 | assertNotLimited(checkRateLimit('http://b')); 151 | assertNotLimited(checkRateLimit('http://ab')); 152 | assertLimited(checkRateLimit('http://ab'), 1, 2); 153 | }); 154 | 155 | it('multiple domains, RegExp and string', function() { 156 | var checkRateLimit = createRateLimitChecker('1 2 /a/ b'); 157 | assertNotLimited(checkRateLimit('http://a')); 158 | assertNotLimited(checkRateLimit('http://a')); 159 | assertNotLimited(checkRateLimit('http://b')); 160 | assertNotLimited(checkRateLimit('http://b')); 161 | assertNotLimited(checkRateLimit('http://ab')); 162 | assertLimited(checkRateLimit('http://ab'), 1, 2); 163 | }); 164 | 165 | it('wildcard subdomains', function() { 166 | var checkRateLimit = createRateLimitChecker('0 1 /(.*\\.)?example\\.com/'); 167 | assertNotLimited(checkRateLimit('http://example.com')); 168 | assertNotLimited(checkRateLimit('http://www.example.com')); 169 | assertLimited(checkRateLimit('http://xexample.com'), 0, 1); 170 | assertLimited(checkRateLimit('http://example.com.br'), 0, 1); 171 | }); 172 | 173 | it('wildcard ports', function() { 174 | var checkRateLimit = createRateLimitChecker('0 1 /example\\.com(:\\d{1,5})?/'); 175 | assertNotLimited(checkRateLimit('http://example.com')); 176 | assertNotLimited(checkRateLimit('http://example.com:1234')); 177 | }); 178 | 179 | it('empty host', function() { 180 | var checkRateLimit = createRateLimitChecker('0 1'); 181 | assertLimited(checkRateLimit(''), 0, 1); 182 | // Empty host actually means empty origin. But let's also test for 'http://'. 183 | assertLimited(checkRateLimit('http://'), 0, 1); 184 | 185 | checkRateLimit = createRateLimitChecker('0 1 '); 186 | assertLimited(checkRateLimit(''), 0, 1); 187 | assertLimited(checkRateLimit('http://'), 0, 1); 188 | 189 | checkRateLimit = createRateLimitChecker('0 1 //'); 190 | assertNotLimited(checkRateLimit('')); 191 | assertNotLimited(checkRateLimit('http://')); 192 | }); 193 | 194 | it('null origin', function() { 195 | var checkRateLimit = createRateLimitChecker('0 1'); 196 | assertLimited(checkRateLimit('null'), 0, 1); 197 | assertLimited(checkRateLimit('http://null'), 0, 1); 198 | 199 | checkRateLimit = createRateLimitChecker('0 1 null'); 200 | assertNotLimited(checkRateLimit('null')); 201 | assertNotLimited(checkRateLimit('http://null')); 202 | 203 | checkRateLimit = createRateLimitChecker('0 1 /null/'); 204 | assertNotLimited(checkRateLimit('null')); 205 | assertNotLimited(checkRateLimit('http://null')); 206 | }); 207 | 208 | it('case-insensitive', function() { 209 | var checkRateLimit = createRateLimitChecker('0 1 NULL'); 210 | assertNotLimited(checkRateLimit('null')); 211 | assertNotLimited(checkRateLimit('http://null')); 212 | 213 | checkRateLimit = createRateLimitChecker('0 1 /NULL/'); 214 | assertNotLimited(checkRateLimit('null')); 215 | assertNotLimited(checkRateLimit('http://null')); 216 | }); 217 | 218 | it('bad input', function() { 219 | assert.throws(function() { 220 | createRateLimitChecker('0 1 /'); 221 | }, /Invalid CORSANYWHERE_RATELIMIT\. Regex at index 0 must start and end with a slash \("\/"\)\./); 222 | 223 | assert.throws(function() { 224 | createRateLimitChecker('0 1 a /'); 225 | }, /Invalid CORSANYWHERE_RATELIMIT\. Regex at index 1 must start and end with a slash \("\/"\)\./); 226 | 227 | assert.throws(function() { 228 | createRateLimitChecker('0 1 /(/'); 229 | }, /Invalid regular expression/); 230 | }); 231 | }); 232 | -------------------------------------------------------------------------------- /lib/cors-anywhere.js: -------------------------------------------------------------------------------- 1 | // © 2013 - 2016 Rob Wu 2 | // Released under the MIT license 3 | 4 | 'use strict'; 5 | 6 | var httpProxy = require('http-proxy'); 7 | var net = require('net'); 8 | var url = require('url'); 9 | var regexp_tld = require('./regexp-top-level-domain'); 10 | var getProxyForUrl = require('proxy-from-env').getProxyForUrl; 11 | 12 | var help_text = {}; 13 | function showUsage(help_file, headers, response) { 14 | var isHtml = /\.html$/.test(help_file); 15 | headers['content-type'] = isHtml ? 'text/html' : 'text/plain'; 16 | if (help_text[help_file] != null) { 17 | response.writeHead(200, headers); 18 | response.end(help_text[help_file]); 19 | } else { 20 | require('fs').readFile(help_file, 'utf8', function(err, data) { 21 | if (err) { 22 | console.error(err); 23 | response.writeHead(500, headers); 24 | response.end(); 25 | } else { 26 | help_text[help_file] = data; 27 | showUsage(help_file, headers, response); // Recursive call, but since data is a string, the recursion will end 28 | } 29 | }); 30 | } 31 | } 32 | 33 | /** 34 | * Check whether the specified hostname is valid. 35 | * 36 | * @param hostname {string} Host name (excluding port) of requested resource. 37 | * @return {boolean} Whether the requested resource can be accessed. 38 | */ 39 | function isValidHostName(hostname) { 40 | return !!( 41 | regexp_tld.test(hostname) || 42 | net.isIPv4(hostname) || 43 | net.isIPv6(hostname) 44 | ); 45 | } 46 | 47 | /** 48 | * Adds CORS headers to the response headers. 49 | * 50 | * @param headers {object} Response headers 51 | * @param request {ServerRequest} 52 | */ 53 | function withCORS(headers, request) { 54 | headers['access-control-allow-origin'] = request.headers.origin; 55 | var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge; 56 | if (request.method === 'OPTIONS' && corsMaxAge) { 57 | headers['access-control-max-age'] = corsMaxAge; 58 | } 59 | if (request.headers['access-control-request-method']) { 60 | headers['access-control-allow-methods'] = request.headers['access-control-request-method']; 61 | delete request.headers['access-control-request-method']; 62 | } 63 | // if (request.headers['access-control-request-headers']) { 64 | // headers['access-control-allow-headers'] = request.headers['access-control-request-headers']; 65 | // delete request.headers['access-control-request-headers']; 66 | // } 67 | 68 | if (headers['set-cookie']) { 69 | if (Array.isArray(headers['set-cookie'])) { 70 | headers['set-cookie'] = headers['set-cookie'].map(function(item) { 71 | return item + '; SameSite=None; Secure'; 72 | }); 73 | } else { 74 | headers['set-cookie'] = headers['set-cookie'] + '; SameSite=None; Secure'; 75 | } 76 | } 77 | 78 | headers['access-control-allow-credentials'] = true; 79 | 80 | headers['access-control-expose-headers'] = Object.keys(headers).concat( 81 | Object.keys(request.headers)).join(',') + ',' + request.headers['access-control-request-headers']; 82 | headers['access-control-allow-headers'] = headers['access-control-expose-headers']; 83 | 84 | return headers; 85 | } 86 | 87 | /** 88 | * Performs the actual proxy request. 89 | * 90 | * @param req {ServerRequest} Incoming http request 91 | * @param res {ServerResponse} Outgoing (proxied) http request 92 | * @param proxy {HttpProxy} 93 | */ 94 | function proxyRequest(req, res, proxy) { 95 | var location = req.corsAnywhereRequestState.location; 96 | req.url = location.path; 97 | 98 | var proxyOptions = { 99 | changeOrigin: false, 100 | prependPath: false, 101 | target: location, 102 | headers: { 103 | host: location.host, 104 | }, 105 | // HACK: Get hold of the proxyReq object, because we need it later. 106 | // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L144 107 | buffer: { 108 | pipe: function(proxyReq) { 109 | var proxyReqOn = proxyReq.on; 110 | // Intercepts the handler that connects proxyRes to res. 111 | // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L146-L158 112 | proxyReq.on = function(eventName, listener) { 113 | if (eventName !== 'response') { 114 | return proxyReqOn.call(this, eventName, listener); 115 | } 116 | return proxyReqOn.call(this, 'response', function(proxyRes) { 117 | if (onProxyResponse(proxy, proxyReq, proxyRes, req, res)) { 118 | try { 119 | listener(proxyRes); 120 | } catch (err) { 121 | // Wrap in try-catch because an error could occur: 122 | // "RangeError: Invalid status code: 0" 123 | // https://github.com/Rob--W/cors-anywhere/issues/95 124 | // https://github.com/nodejitsu/node-http-proxy/issues/1080 125 | 126 | // Forward error (will ultimately emit the 'error' event on our proxy object): 127 | // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L134 128 | proxyReq.emit('error', err); 129 | } 130 | } 131 | }); 132 | }; 133 | return req.pipe(proxyReq); 134 | }, 135 | }, 136 | }; 137 | 138 | var proxyThroughUrl = req.corsAnywhereRequestState.getProxyForUrl(location.href); 139 | if (proxyThroughUrl) { 140 | proxyOptions.target = proxyThroughUrl; 141 | proxyOptions.toProxy = true; 142 | // If a proxy URL was set, req.url must be an absolute URL. Then the request will not be sent 143 | // directly to the proxied URL, but through another proxy. 144 | req.url = location.href; 145 | } 146 | 147 | // Start proxying the request 148 | try { 149 | proxy.web(req, res, proxyOptions); 150 | } catch (err) { 151 | proxy.emit('error', err, req, res); 152 | } 153 | } 154 | 155 | /** 156 | * This method modifies the response headers of the proxied response. 157 | * If a redirect is detected, the response is not sent to the client, 158 | * and a new request is initiated. 159 | * 160 | * client (req) -> CORS Anywhere -> (proxyReq) -> other server 161 | * client (res) <- CORS Anywhere <- (proxyRes) <- other server 162 | * 163 | * @param proxy {HttpProxy} 164 | * @param proxyReq {ClientRequest} The outgoing request to the other server. 165 | * @param proxyRes {ServerResponse} The response from the other server. 166 | * @param req {IncomingMessage} Incoming HTTP request, augmented with property corsAnywhereRequestState 167 | * @param req.corsAnywhereRequestState {object} 168 | * @param req.corsAnywhereRequestState.location {object} See parseURL 169 | * @param req.corsAnywhereRequestState.getProxyForUrl {function} See proxyRequest 170 | * @param req.corsAnywhereRequestState.proxyBaseUrl {string} Base URL of the CORS API endpoint 171 | * @param req.corsAnywhereRequestState.maxRedirects {number} Maximum number of redirects 172 | * @param req.corsAnywhereRequestState.redirectCount_ {number} Internally used to count redirects 173 | * @param res {ServerResponse} Outgoing response to the client that wanted to proxy the HTTP request. 174 | * 175 | * @returns {boolean} true if http-proxy should continue to pipe proxyRes to res. 176 | */ 177 | function onProxyResponse(proxy, proxyReq, proxyRes, req, res) { 178 | var requestState = req.corsAnywhereRequestState; 179 | 180 | var statusCode = proxyRes.statusCode; 181 | 182 | if (!requestState.redirectCount_) { 183 | res.setHeader('x-request-url', requestState.location.href); 184 | } 185 | // Handle redirects 186 | if (statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308) { 187 | var locationHeader = proxyRes.headers.location; 188 | var parsedLocation; 189 | if (locationHeader) { 190 | locationHeader = url.resolve(requestState.location.href, locationHeader); 191 | parsedLocation = parseURL(locationHeader); 192 | } 193 | if (parsedLocation) { 194 | if (statusCode === 301 || statusCode === 302 || statusCode === 303) { 195 | // Exclude 307 & 308, because they are rare, and require preserving the method + request body 196 | requestState.redirectCount_ = requestState.redirectCount_ + 1 || 1; 197 | if (requestState.redirectCount_ <= requestState.maxRedirects) { 198 | // Handle redirects within the server, because some clients (e.g. Android Stock Browser) 199 | // cancel redirects. 200 | // Set header for debugging purposes. Do not try to parse it! 201 | res.setHeader('X-CORS-Redirect-' + requestState.redirectCount_, statusCode + ' ' + locationHeader); 202 | 203 | req.method = 'GET'; 204 | req.headers['content-length'] = '0'; 205 | delete req.headers['content-type']; 206 | requestState.location = parsedLocation; 207 | 208 | // Remove all listeners (=reset events to initial state) 209 | req.removeAllListeners(); 210 | 211 | // Remove the error listener so that the ECONNRESET "error" that 212 | // may occur after aborting a request does not propagate to res. 213 | // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L134 214 | proxyReq.removeAllListeners('error'); 215 | proxyReq.once('error', function catchAndIgnoreError() {}); 216 | proxyReq.abort(); 217 | 218 | // Initiate a new proxy request. 219 | proxyRequest(req, res, proxy); 220 | return false; 221 | } 222 | } 223 | proxyRes.headers.location = requestState.proxyBaseUrl + '/' + locationHeader; 224 | } 225 | } 226 | 227 | proxyRes.headers['x-final-url'] = requestState.location.href; 228 | withCORS(proxyRes.headers, req); 229 | return true; 230 | } 231 | 232 | 233 | /** 234 | * @param req_url {string} The requested URL (scheme is optional). 235 | * @return {object} URL parsed using url.parse 236 | */ 237 | function parseURL(req_url) { 238 | var match = req_url.match(/^(?:(https?:)?\/\/)?(([^\/?]+?)(?::(\d{0,5})(?=[\/?]|$))?)([\/?][\S\s]*|$)/i); 239 | // ^^^^^^^ ^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^ 240 | // 1:protocol 3:hostname 4:port 5:path + query string 241 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 242 | // 2:host 243 | if (!match) { 244 | return null; 245 | } 246 | if (!match[1]) { 247 | if (/^https?:/i.test(req_url)) { 248 | // The pattern at top could mistakenly parse "http:///" as host="http:" and path=///. 249 | return null; 250 | } 251 | // Scheme is omitted. 252 | if (req_url.lastIndexOf('//', 0) === -1) { 253 | // "//" is omitted. 254 | req_url = '//' + req_url; 255 | } 256 | req_url = (match[4] === '443' ? 'https:' : 'http:') + req_url; 257 | } 258 | var parsed = url.parse(req_url); 259 | if (!parsed.hostname) { 260 | // "http://:1/" and "http:/notenoughslashes" could end up here. 261 | return null; 262 | } 263 | return parsed; 264 | } 265 | 266 | // Request handler factory 267 | function getHandler(options, proxy) { 268 | var corsAnywhere = { 269 | handleInitialRequest: null, // Function that may handle the request instead, by returning a truthy value. 270 | getProxyForUrl: getProxyForUrl, // Function that specifies the proxy to use 271 | maxRedirects: 5, // Maximum number of redirects to be followed. 272 | originBlacklist: [], // Requests from these origins will be blocked. 273 | originWhitelist: [], // If non-empty, requests not from an origin in this list will be blocked. 274 | checkRateLimit: null, // Function that may enforce a rate-limit by returning a non-empty string. 275 | redirectSameOrigin: false, // Redirect the client to the requested URL for same-origin requests. 276 | requireHeader: null, // Require a header to be set? 277 | removeHeaders: [], // Strip these request headers. 278 | setHeaders: {}, // Set these request headers. 279 | corsMaxAge: 0, // If set, an Access-Control-Max-Age header with this value (in seconds) will be added. 280 | helpFile: __dirname + '/help.txt', 281 | }; 282 | 283 | Object.keys(corsAnywhere).forEach(function(option) { 284 | if (Object.prototype.hasOwnProperty.call(options, option)) { 285 | corsAnywhere[option] = options[option]; 286 | } 287 | }); 288 | 289 | // Convert corsAnywhere.requireHeader to an array of lowercase header names, or null. 290 | if (corsAnywhere.requireHeader) { 291 | if (typeof corsAnywhere.requireHeader === 'string') { 292 | corsAnywhere.requireHeader = [corsAnywhere.requireHeader.toLowerCase()]; 293 | } else if (!Array.isArray(corsAnywhere.requireHeader) || corsAnywhere.requireHeader.length === 0) { 294 | corsAnywhere.requireHeader = null; 295 | } else { 296 | corsAnywhere.requireHeader = corsAnywhere.requireHeader.map(function(headerName) { 297 | return headerName.toLowerCase(); 298 | }); 299 | } 300 | } 301 | var hasRequiredHeaders = function(headers) { 302 | return !corsAnywhere.requireHeader || corsAnywhere.requireHeader.some(function(headerName) { 303 | return Object.hasOwnProperty.call(headers, headerName); 304 | }); 305 | }; 306 | 307 | return function(req, res) { 308 | req.corsAnywhereRequestState = { 309 | getProxyForUrl: corsAnywhere.getProxyForUrl, 310 | maxRedirects: corsAnywhere.maxRedirects, 311 | corsMaxAge: corsAnywhere.corsMaxAge, 312 | }; 313 | 314 | var cors_headers = withCORS({}, req); 315 | if (req.method === 'OPTIONS') { 316 | // Pre-flight request. Reply successfully: 317 | res.writeHead(200, cors_headers); 318 | res.end(); 319 | return; 320 | } 321 | 322 | var location = parseURL(req.url.slice(1)); 323 | 324 | if (corsAnywhere.handleInitialRequest && corsAnywhere.handleInitialRequest(req, res, location)) { 325 | return; 326 | } 327 | 328 | if (!location) { 329 | // Special case http:/notenoughslashes, because new users of the library frequently make the 330 | // mistake of putting this application behind a server/router that normalizes the URL. 331 | // See https://github.com/Rob--W/cors-anywhere/issues/238#issuecomment-629638853 332 | if (/^\/https?:\/[^/]/i.test(req.url)) { 333 | res.writeHead(400, 'Missing slash', cors_headers); 334 | res.end('The URL is invalid: two slashes are needed after the http(s):.'); 335 | return; 336 | } 337 | // Invalid API call. Show how to correctly use the API 338 | showUsage(corsAnywhere.helpFile, cors_headers, res); 339 | return; 340 | } 341 | 342 | if (location.host === 'iscorsneeded') { 343 | // Is CORS needed? This path is provided so that API consumers can test whether it's necessary 344 | // to use CORS. The server's reply is always No, because if they can read it, then CORS headers 345 | // are not necessary. 346 | res.writeHead(200, {'Content-Type': 'text/plain'}); 347 | res.end('no'); 348 | return; 349 | } 350 | 351 | if (location.port > 65535) { 352 | // Port is higher than 65535 353 | res.writeHead(400, 'Invalid port', cors_headers); 354 | res.end('Port number too large: ' + location.port); 355 | return; 356 | } 357 | 358 | if (!/^\/https?:/.test(req.url) && !isValidHostName(location.hostname)) { 359 | // Don't even try to proxy invalid hosts (such as /favicon.ico, /robots.txt) 360 | res.writeHead(404, 'Invalid host', cors_headers); 361 | res.end('Invalid host: ' + location.hostname); 362 | return; 363 | } 364 | 365 | if (!hasRequiredHeaders(req.headers)) { 366 | res.writeHead(400, 'Header required', cors_headers); 367 | res.end('Missing required request header. Must specify one of: ' + corsAnywhere.requireHeader); 368 | return; 369 | } 370 | 371 | var origin = req.headers.origin || ''; 372 | if (corsAnywhere.originBlacklist.indexOf(origin) >= 0) { 373 | res.writeHead(403, 'Forbidden', cors_headers); 374 | res.end('The origin "' + origin + '" was blacklisted by the operator of this proxy.'); 375 | return; 376 | } 377 | 378 | if (corsAnywhere.originWhitelist.length && corsAnywhere.originWhitelist.indexOf(origin) === -1) { 379 | res.writeHead(403, 'Forbidden', cors_headers); 380 | res.end('The origin "' + origin + '" was not whitelisted by the operator of this proxy.'); 381 | return; 382 | } 383 | 384 | var rateLimitMessage = corsAnywhere.checkRateLimit && corsAnywhere.checkRateLimit(origin); 385 | if (rateLimitMessage) { 386 | res.writeHead(429, 'Too Many Requests', cors_headers); 387 | res.end('The origin "' + origin + '" has sent too many requests.\n' + rateLimitMessage); 388 | return; 389 | } 390 | 391 | if (corsAnywhere.redirectSameOrigin && origin && location.href[origin.length] === '/' && 392 | location.href.lastIndexOf(origin, 0) === 0) { 393 | // Send a permanent redirect to offload the server. Badly coded clients should not waste our resources. 394 | cors_headers.vary = 'origin'; 395 | cors_headers['cache-control'] = 'private'; 396 | cors_headers.location = location.href; 397 | res.writeHead(301, 'Please use a direct request', cors_headers); 398 | res.end(); 399 | return; 400 | } 401 | 402 | var isRequestedOverHttps = req.connection.encrypted || /^\s*https/.test(req.headers['x-forwarded-proto']); 403 | var proxyBaseUrl = (isRequestedOverHttps ? 'https://' : 'http://') + req.headers.host; 404 | 405 | corsAnywhere.removeHeaders.forEach(function(header) { 406 | delete req.headers[header]; 407 | }); 408 | 409 | Object.keys(corsAnywhere.setHeaders).forEach(function(header) { 410 | req.headers[header] = corsAnywhere.setHeaders[header]; 411 | }); 412 | 413 | req.corsAnywhereRequestState.location = location; 414 | req.corsAnywhereRequestState.proxyBaseUrl = proxyBaseUrl; 415 | 416 | proxyRequest(req, res, proxy); 417 | }; 418 | } 419 | 420 | // Create server with default and given values 421 | // Creator still needs to call .listen() 422 | exports.createServer = function createServer(options) { 423 | options = options || {}; 424 | 425 | // Default options: 426 | var httpProxyOptions = { 427 | xfwd: true, // Append X-Forwarded-* headers 428 | secure: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0', 429 | }; 430 | // Allow user to override defaults and add own options 431 | if (options.httpProxyOptions) { 432 | Object.keys(options.httpProxyOptions).forEach(function(option) { 433 | httpProxyOptions[option] = options.httpProxyOptions[option]; 434 | }); 435 | } 436 | 437 | var proxy = httpProxy.createServer(httpProxyOptions); 438 | var requestHandler = getHandler(options, proxy); 439 | var server; 440 | if (options.httpsOptions) { 441 | server = require('https').createServer(options.httpsOptions, requestHandler); 442 | } else { 443 | server = require('http').createServer(requestHandler); 444 | } 445 | 446 | // When the server fails, just show a 404 instead of Internal server error 447 | proxy.on('error', function(err, req, res) { 448 | if (res.headersSent) { 449 | // This could happen when a protocol error occurs when an error occurs 450 | // after the headers have been received (and forwarded). Do not write 451 | // the headers because it would generate an error. 452 | // Prior to Node 13.x, the stream would have ended. 453 | // As of Node 13.x, we must explicitly close it. 454 | if (res.writableEnded === false) { 455 | res.end(); 456 | } 457 | return; 458 | } 459 | 460 | // When the error occurs after setting headers but before writing the response, 461 | // then any previously set headers must be removed. 462 | var headerNames = res.getHeaderNames ? res.getHeaderNames() : Object.keys(res._headers || {}); 463 | headerNames.forEach(function(name) { 464 | res.removeHeader(name); 465 | }); 466 | 467 | res.writeHead(404, {'Access-Control-Allow-Origin': '*'}); 468 | res.end('Not found because of proxy error: ' + err); 469 | }); 470 | 471 | return server; 472 | }; 473 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | require('./setup'); 3 | 4 | var createServer = require('../').createServer; 5 | var request = require('supertest'); 6 | var path = require('path'); 7 | var http = require('http'); 8 | var https = require('https'); 9 | var fs = require('fs'); 10 | var assert = require('assert'); 11 | 12 | var helpTextPath = path.join(__dirname, '../lib/help.txt'); 13 | var helpText = fs.readFileSync(helpTextPath, {encoding: 'utf8'}); 14 | 15 | request.Test.prototype.expectJSON = function(json, done) { 16 | this.expect(function(res) { 17 | // Assume that the response can be parsed as JSON (otherwise it throws). 18 | var actual = JSON.parse(res.text); 19 | assert.deepEqual(actual, json); 20 | }); 21 | return done ? this.end(done) : this; 22 | }; 23 | 24 | request.Test.prototype.expectNoHeader = function(header, done) { 25 | this.expect(function(res) { 26 | if (header.toLowerCase() in res.headers) { 27 | return new Error('Unexpected header in response: ' + header); 28 | } 29 | }); 30 | return done ? this.end(done) : this; 31 | }; 32 | 33 | var cors_anywhere; 34 | var cors_anywhere_port; 35 | function stopServer(done) { 36 | cors_anywhere.close(function() { 37 | done(); 38 | }); 39 | cors_anywhere = null; 40 | } 41 | 42 | describe('Basic functionality', function() { 43 | before(function() { 44 | cors_anywhere = createServer(); 45 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 46 | }); 47 | after(stopServer); 48 | 49 | it('GET /', function(done) { 50 | request(cors_anywhere) 51 | .get('/') 52 | .type('text/plain') 53 | .expect('Access-Control-Allow-Origin', '*') 54 | .expect(200, helpText, done); 55 | }); 56 | 57 | it('GET /iscorsneeded', function(done) { 58 | request(cors_anywhere) 59 | .get('/iscorsneeded') 60 | .expectNoHeader('access-control-allow-origin', done); 61 | }); 62 | 63 | it('GET /example.com:65536', function(done) { 64 | request(cors_anywhere) 65 | .get('/example.com:65536') 66 | .expect('Access-Control-Allow-Origin', '*') 67 | .expect(400, 'Port number too large: 65536', done); 68 | }); 69 | 70 | it('GET /favicon.ico', function(done) { 71 | request(cors_anywhere) 72 | .get('/favicon.ico') 73 | .expect('Access-Control-Allow-Origin', '*') 74 | .expect(404, 'Invalid host: favicon.ico', done); 75 | }); 76 | 77 | it('GET /robots.txt', function(done) { 78 | request(cors_anywhere) 79 | .get('/robots.txt') 80 | .expect('Access-Control-Allow-Origin', '*') 81 | .expect(404, 'Invalid host: robots.txt', done); 82 | }); 83 | 84 | it('GET /http://robots.txt should be proxied', function(done) { 85 | request(cors_anywhere) 86 | .get('/http://robots.txt') 87 | .expect('Access-Control-Allow-Origin', '*') 88 | .expect(200, 'this is http://robots.txt', done); 89 | }); 90 | 91 | it('GET /example.com', function(done) { 92 | request(cors_anywhere) 93 | .get('/example.com') 94 | .expect('Access-Control-Allow-Origin', '*') 95 | .expect('x-request-url', 'http://example.com/') 96 | .expect(200, 'Response from example.com', done); 97 | }); 98 | 99 | it('GET /example.com:80', function(done) { 100 | request(cors_anywhere) 101 | .get('/example.com:80') 102 | .expect('Access-Control-Allow-Origin', '*') 103 | .expect('x-request-url', 'http://example.com:80/') 104 | .expect(200, 'Response from example.com', done); 105 | }); 106 | 107 | it('GET /example.com:443', function(done) { 108 | request(cors_anywhere) 109 | .get('/example.com:443') 110 | .expect('Access-Control-Allow-Origin', '*') 111 | .expect('x-request-url', 'https://example.com:443/') 112 | .expect(200, 'Response from https://example.com', done); 113 | }); 114 | 115 | it('GET //example.com', function(done) { 116 | // '/example.com' is an invalid URL. 117 | request(cors_anywhere) 118 | .get('//example.com') 119 | .expect('Access-Control-Allow-Origin', '*') 120 | .expect(200, helpText, done); 121 | }); 122 | 123 | it('GET /http://:1234', function(done) { 124 | // 'http://:1234' is an invalid URL. 125 | request(cors_anywhere) 126 | .get('/http://:1234') 127 | .expect('Access-Control-Allow-Origin', '*') 128 | .expect(200, helpText, done); 129 | }); 130 | 131 | it('GET /http:///', function(done) { 132 | // 'http://:1234' is an invalid URL. 133 | request(cors_anywhere) 134 | .get('/http:///') 135 | .expect('Access-Control-Allow-Origin', '*') 136 | .expect(200, helpText, done); 137 | }); 138 | 139 | it('GET /http:/notenoughslashes', function(done) { 140 | // 'http:/notenoughslashes' is an invalid URL. 141 | request(cors_anywhere) 142 | .get('/http:/notenoughslashes') 143 | .expect('Access-Control-Allow-Origin', '*') 144 | .expect(400, 'The URL is invalid: two slashes are needed after the http(s):.', done); 145 | }); 146 | 147 | 148 | it('GET ///example.com', function(done) { 149 | // API base URL (with trailing slash) + '//example.com' 150 | request(cors_anywhere) 151 | .get('///example.com') 152 | .expect('Access-Control-Allow-Origin', '*') 153 | .expect('x-request-url', 'http://example.com/') 154 | .expect(200, 'Response from example.com', done); 155 | }); 156 | 157 | it('GET /http://example.com', function(done) { 158 | request(cors_anywhere) 159 | .get('/http://example.com') 160 | .expect('Access-Control-Allow-Origin', '*') 161 | .expect('x-request-url', 'http://example.com/') 162 | .expect(200, 'Response from example.com', done); 163 | }); 164 | 165 | it('POST plain text', function(done) { 166 | request(cors_anywhere) 167 | .post('/example.com/echopost') 168 | .send('{"this is a request body & should not be mangled":1.00}') 169 | .expect('Access-Control-Allow-Origin', '*') 170 | .expect('{"this is a request body & should not be mangled":1.00}', done); 171 | }); 172 | 173 | it('POST file', function(done) { 174 | request(cors_anywhere) 175 | .post('/example.com/echopost') 176 | .attach('file', path.join(__dirname, 'dummy.txt')) 177 | .expect('Access-Control-Allow-Origin', '*') 178 | .expect(/\r\nContent-Disposition: form-data; name="file"; filename="dummy.txt"\r\nContent-Type: text\/plain\r\n\r\ndummy content\n\r\n/, done); // eslint-disable-line max-len 179 | }); 180 | 181 | it('HEAD with redirect should be followed', function(done) { 182 | // Redirects are automatically followed, because redirects are to be 183 | // followed automatically per specification regardless of the HTTP verb. 184 | request(cors_anywhere) 185 | .head('/example.com/redirect') 186 | .redirects(0) 187 | .expect('Access-Control-Allow-Origin', '*') 188 | .expect('some-header', 'value') 189 | .expect('x-request-url', 'http://example.com/redirect') 190 | .expect('x-cors-redirect-1', '302 http://example.com/redirecttarget') 191 | .expect('x-final-url', 'http://example.com/redirecttarget') 192 | .expect('access-control-expose-headers', /some-header,x-final-url/) 193 | .expectNoHeader('header at redirect') 194 | .expect(200, undefined, done); 195 | }); 196 | 197 | it('GET with redirect should be followed', function(done) { 198 | request(cors_anywhere) 199 | .get('/example.com/redirect') 200 | .redirects(0) 201 | .expect('Access-Control-Allow-Origin', '*') 202 | .expect('some-header', 'value') 203 | .expect('x-request-url', 'http://example.com/redirect') 204 | .expect('x-cors-redirect-1', '302 http://example.com/redirecttarget') 205 | .expect('x-final-url', 'http://example.com/redirecttarget') 206 | .expect('access-control-expose-headers', /some-header,x-final-url/) 207 | .expectNoHeader('header at redirect') 208 | .expect(200, 'redirect target', done); 209 | }); 210 | 211 | it('GET with redirect loop should interrupt', function(done) { 212 | request(cors_anywhere) 213 | .get('/example.com/redirectloop') 214 | .redirects(0) 215 | .expect('Access-Control-Allow-Origin', '*') 216 | .expect('x-request-url', 'http://example.com/redirectloop') 217 | .expect('x-cors-redirect-1', '302 http://example.com/redirectloop') 218 | .expect('x-cors-redirect-2', '302 http://example.com/redirectloop') 219 | .expect('x-cors-redirect-3', '302 http://example.com/redirectloop') 220 | .expect('x-cors-redirect-4', '302 http://example.com/redirectloop') 221 | .expect('x-cors-redirect-5', '302 http://example.com/redirectloop') 222 | .expect('Location', /^http:\/\/127.0.0.1:\d+\/http:\/\/example.com\/redirectloop$/) 223 | .expect(302, 'redirecting ad infinitum...', done); 224 | }); 225 | 226 | it('POST with 302 redirect should be followed', function(done) { 227 | request(cors_anywhere) 228 | .post('/example.com/redirectpost') 229 | .redirects(0) 230 | .expect('Access-Control-Allow-Origin', '*') 231 | .expect('x-request-url', 'http://example.com/redirectpost') 232 | .expect('x-cors-redirect-1', '302 http://example.com/redirectposttarget') 233 | .expect('x-final-url', 'http://example.com/redirectposttarget') 234 | .expect('access-control-expose-headers', /x-final-url/) 235 | .expect(200, 'post target', done); 236 | }); 237 | 238 | it('GET with 302 redirect without Location header should not be followed', function(done) { 239 | // There is nothing to follow, so let the browser decide what to do with it. 240 | request(cors_anywhere) 241 | .get('/example.com/redirectwithoutlocation') 242 | .redirects(0) 243 | .expect('Access-Control-Allow-Origin', '*') 244 | .expect('x-request-url', 'http://example.com/redirectwithoutlocation') 245 | .expect('x-final-url', 'http://example.com/redirectwithoutlocation') 246 | .expect('access-control-expose-headers', /x-final-url/) 247 | .expect(302, 'maybe found', done); 248 | }); 249 | 250 | it('GET with 302 redirect to an invalid Location should not be followed', function(done) { 251 | // There is nothing to follow, so let the browser decide what to do with it. 252 | request(cors_anywhere) 253 | .get('/example.com/redirectinvalidlocation') 254 | .redirects(0) 255 | .expect('Access-Control-Allow-Origin', '*') 256 | .expect('x-request-url', 'http://example.com/redirectinvalidlocation') 257 | .expect('x-final-url', 'http://example.com/redirectinvalidlocation') 258 | .expect('access-control-expose-headers', /x-final-url/) 259 | .expect('Location', 'http:///') 260 | .expect(302, 'redirecting to junk...', done); 261 | }); 262 | 263 | it('POST with 307 redirect should not be handled', function(done) { 264 | // Because of implementation difficulties (having to keep the request body 265 | // in memory), handling HTTP 307/308 redirects is deferred to the requestor. 266 | request(cors_anywhere) 267 | .post('/example.com/redirect307') 268 | .redirects(0) 269 | .expect('Access-Control-Allow-Origin', '*') 270 | .expect('x-request-url', 'http://example.com/redirect307') 271 | .expect('Location', /^http:\/\/127.0.0.1:\d+\/http:\/\/example.com\/redirectposttarget$/) 272 | .expect('x-final-url', 'http://example.com/redirect307') 273 | .expect('access-control-expose-headers', /x-final-url/) 274 | .expect(307, 'redirecting...', done); 275 | }); 276 | 277 | it('OPTIONS /', function(done) { 278 | request(cors_anywhere) 279 | .options('/') 280 | .expect('Access-Control-Allow-Origin', '*') 281 | .expect(200, '', done); 282 | }); 283 | 284 | it('OPTIONS / with Access-Control-Request-Method / -Headers', function(done) { 285 | request(cors_anywhere) 286 | .options('/') 287 | .set('Access-Control-Request-Method', 'DELETE') 288 | .set('Access-Control-Request-Headers', 'X-Tralala') 289 | .expect('Access-Control-Allow-Origin', '*') 290 | .expect('Access-Control-Allow-Methods', 'DELETE') 291 | .expect('Access-Control-Allow-Headers', 'X-Tralala') 292 | .expect(200, '', done); 293 | }); 294 | 295 | it('OPTIONS //bogus', function(done) { 296 | // The preflight request always succeeds, regardless of whether the request 297 | // is valid. 298 | request(cors_anywhere) 299 | .options('//bogus') 300 | .expect('Access-Control-Allow-Origin', '*') 301 | .expect(200, '', done); 302 | }); 303 | 304 | it('X-Forwarded-* headers', function(done) { 305 | request(cors_anywhere) 306 | .get('/example.com/echoheaders') 307 | .set('test-include-xfwd', '') 308 | .expect('Access-Control-Allow-Origin', '*') 309 | .expectJSON({ 310 | host: 'example.com', 311 | 'x-forwarded-port': String(cors_anywhere_port), 312 | 'x-forwarded-proto': 'http', 313 | }, done); 314 | }); 315 | 316 | it('X-Forwarded-* headers (non-standard port)', function(done) { 317 | request(cors_anywhere) 318 | .get('/example.com:1337/echoheaders') 319 | .set('test-include-xfwd', '') 320 | .expect('Access-Control-Allow-Origin', '*') 321 | .expectJSON({ 322 | host: 'example.com:1337', 323 | 'x-forwarded-port': String(cors_anywhere_port), 324 | 'x-forwarded-proto': 'http', 325 | }, done); 326 | }); 327 | 328 | it('X-Forwarded-* headers (https)', function(done) { 329 | request(cors_anywhere) 330 | .get('/https://example.com/echoheaders') 331 | .set('test-include-xfwd', '') 332 | .expect('Access-Control-Allow-Origin', '*') 333 | .expectJSON({ 334 | host: 'example.com', 335 | 'x-forwarded-port': String(cors_anywhere_port), 336 | 'x-forwarded-proto': 'http', 337 | }, done); 338 | }); 339 | 340 | it('Ignore cookies', function(done) { 341 | request(cors_anywhere) 342 | .get('/example.com/setcookie') 343 | .expect('Access-Control-Allow-Origin', '*') 344 | .expect('Set-Cookie3', 'z') 345 | .expectNoHeader('set-cookie') 346 | .expectNoHeader('set-cookie2', done); 347 | }); 348 | }); 349 | 350 | describe('Proxy errors', function() { 351 | before(function() { 352 | cors_anywhere = createServer(); 353 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 354 | }); 355 | after(stopServer); 356 | 357 | var bad_http_server; 358 | var bad_http_server_url; 359 | before(function() { 360 | bad_http_server = http.createServer(function(req, res) { 361 | res.writeHead(418, { 362 | 'Content-Length': 'Not a digit', 363 | }); 364 | res.end('This response has an invalid Content-Length header.'); 365 | }); 366 | bad_http_server_url = 'http://127.0.0.1:' + bad_http_server.listen(0).address().port; 367 | }); 368 | after(function(done) { 369 | bad_http_server.close(function() { 370 | done(); 371 | }); 372 | }); 373 | 374 | var bad_status_http_server; 375 | var bad_status_http_server_url; 376 | before(function() { 377 | bad_status_http_server = require('net').createServer(function(socket) { 378 | socket.setEncoding('utf-8'); 379 | socket.on('data', function(data) { 380 | if (data.indexOf('\r\n') >= 0) { 381 | // Assume end of headers. 382 | socket.write('HTTP/1.0 0\r\n'); 383 | socket.write('Content-Length: 0\r\n'); 384 | socket.end('\r\n'); 385 | } 386 | }); 387 | }); 388 | bad_status_http_server_url = 'http://127.0.0.1:' + bad_status_http_server.listen(0).address().port; 389 | }); 390 | after(function(done) { 391 | bad_status_http_server.close(function() { 392 | done(); 393 | }); 394 | }); 395 | 396 | var bad_tcp_server; 397 | var bad_tcp_server_url; 398 | before(function() { 399 | bad_tcp_server = require('net').createServer(function(socket) { 400 | socket.setEncoding('utf-8'); 401 | socket.on('data', function(data) { 402 | if (data.indexOf('\r\n') >= 0) { 403 | // Assume end of headers. 404 | socket.write('HTTP/1.1 418 OK\r\n'); 405 | socket.write('Transfer-Encoding: chunked\r\n'); 406 | socket.write('\r\n'); 407 | socket.end('JK I lied, this is NOT a chunked response!'); 408 | } 409 | }); 410 | }); 411 | bad_tcp_server_url = 'http://127.0.0.1:' + bad_tcp_server.listen(0).address().port; 412 | }); 413 | after(function(done) { 414 | bad_tcp_server.close(function() { 415 | done(); 416 | }); 417 | }); 418 | 419 | it('Proxy error', function(done) { 420 | request(cors_anywhere) 421 | .get('/example.com/proxyerror') 422 | .expect('Access-Control-Allow-Origin', '*') 423 | .expect(404, 'Not found because of proxy error: Error: throw node', done); 424 | }); 425 | 426 | it('Content-Length mismatch', function(done) { 427 | var errorMessage = 'Error: Parse Error: Invalid character in Content-Length'; 428 | // <13.0.0: https://github.com/nodejs/node/commit/ba565a37349e81c9d2402b0c8ef05ab39dca8968 429 | // <12.7.0: https://github.com/nodejs/node/pull/28817 430 | var nodev = process.versions.node.split('.').map(function(v) { return parseInt(v); }); 431 | if (nodev[0] < 12 || 432 | nodev[0] === 12 && nodev[1] < 7) { 433 | errorMessage = 'Error: Parse Error'; 434 | } 435 | request(cors_anywhere) 436 | .get('/' + bad_http_server_url) 437 | .expect('Access-Control-Allow-Origin', '*') 438 | .expect(404, 'Not found because of proxy error: ' + errorMessage, done); 439 | }); 440 | 441 | it('Invalid HTTP status code', function(done) { 442 | // Strict HTTP status validation was introduced in Node 4.5.5+, 5.11.0+. 443 | // https://github.com/nodejs/node/pull/6291 444 | var nodev = process.versions.node.split('.').map(function(v) { return parseInt(v); }); 445 | if (nodev[0] < 4 || 446 | nodev[0] === 4 && nodev[1] < 5 || 447 | nodev[0] === 4 && nodev[1] === 5 && nodev[2] < 5 || 448 | nodev[0] === 5 && nodev[1] < 11) { 449 | this.skip(); 450 | } 451 | 452 | var errorMessage = 'RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code: 0'; 453 | if (parseInt(process.versions.node, 10) < 9) { 454 | errorMessage = 'RangeError: Invalid status code: 0'; 455 | } 456 | request(cors_anywhere) 457 | .get('/' + bad_status_http_server_url) 458 | .expect('Access-Control-Allow-Origin', '*') 459 | .expect(404, 'Not found because of proxy error: ' + errorMessage, done); 460 | }); 461 | 462 | it('Content-Encoding invalid body', function(done) { 463 | // The HTTP status can't be changed because the headers have already been 464 | // sent. 465 | request(cors_anywhere) 466 | .get('/' + bad_tcp_server_url) 467 | .expect('Access-Control-Allow-Origin', '*') 468 | .expect(418, '', done); 469 | }); 470 | 471 | it('Invalid header values', function(done) { 472 | if (parseInt(process.versions.node, 10) < 6) { 473 | // >=6.0.0: https://github.com/nodejs/node/commit/7bef1b790727430cb82bf8be80cfe058480de100 474 | this.skip(); 475 | } 476 | // >=9.0.0: https://github.com/nodejs/node/commit/11a2ca29babcb35132e7d93244b69c544d52dfe4 477 | var errorMessage = 'TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["headername"]'; 478 | if (parseInt(process.versions.node, 10) < 9) { 479 | // >=6.0.0, <9.0.0: https://github.com/nodejs/node/commit/7bef1b790727430cb82bf8be80cfe058480de100 480 | errorMessage = 'TypeError: The header content contains invalid characters'; 481 | } 482 | stopServer(function() { 483 | cors_anywhere = createServer({ 484 | // Setting an invalid header below in request(...).set(...) would trigger 485 | // a header validation error in superagent. So we use setHeaders to test 486 | // the attempt to proxy a request with invalid request headers. 487 | setHeaders: {headername: 'invalid\x01value'}, 488 | }); 489 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 490 | request(cors_anywhere) 491 | .get('/' + bad_tcp_server_url) // Any URL that isn't intercepted by Nock would do. 492 | .expect('Access-Control-Allow-Origin', '*') 493 | .expect(404, 'Not found because of proxy error: ' + errorMessage, done); 494 | }); 495 | }); 496 | }); 497 | 498 | describe('server on https', function() { 499 | var NODE_TLS_REJECT_UNAUTHORIZED; 500 | before(function() { 501 | cors_anywhere = createServer({ 502 | httpsOptions: { 503 | key: fs.readFileSync(path.join(__dirname, 'key.pem')), 504 | cert: fs.readFileSync(path.join(__dirname, 'cert.pem')), 505 | }, 506 | }); 507 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 508 | // Disable certificate validation in case the certificate expires. 509 | NODE_TLS_REJECT_UNAUTHORIZED = process.env.NODE_TLS_REJECT_UNAUTHORIZED; 510 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 511 | }); 512 | after(function(done) { 513 | if (NODE_TLS_REJECT_UNAUTHORIZED === undefined) { 514 | delete process.env.NODE_TLS_REJECT_UNAUTHORIZED; 515 | } else { 516 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = NODE_TLS_REJECT_UNAUTHORIZED; 517 | } 518 | stopServer(done); 519 | }); 520 | 521 | it('X-Forwarded-* headers (http)', function(done) { 522 | request(cors_anywhere) 523 | .get('/example.com/echoheaders') 524 | .set('test-include-xfwd', '') 525 | .expect('Access-Control-Allow-Origin', '*') 526 | .expectJSON({ 527 | host: 'example.com', 528 | 'x-forwarded-port': String(cors_anywhere_port), 529 | 'x-forwarded-proto': 'https', 530 | }, done); 531 | }); 532 | 533 | it('X-Forwarded-* headers (https)', function(done) { 534 | request(cors_anywhere) 535 | .get('/https://example.com/echoheaders') 536 | .set('test-include-xfwd', '') 537 | .expect('Access-Control-Allow-Origin', '*') 538 | .expectJSON({ 539 | host: 'example.com', 540 | 'x-forwarded-port': String(cors_anywhere_port), 541 | 'x-forwarded-proto': 'https', 542 | }, done); 543 | }); 544 | 545 | it('X-Forwarded-* headers (https, non-standard port)', function(done) { 546 | request(cors_anywhere) 547 | .get('/https://example.com:1337/echoheaders') 548 | .set('test-include-xfwd', '') 549 | .expect('Access-Control-Allow-Origin', '*') 550 | .expectJSON({ 551 | host: 'example.com:1337', 552 | 'x-forwarded-port': String(cors_anywhere_port), 553 | 'x-forwarded-proto': 'https', 554 | }, done); 555 | }); 556 | }); 557 | 558 | describe('NODE_TLS_REJECT_UNAUTHORIZED', function() { 559 | var NODE_TLS_REJECT_UNAUTHORIZED; 560 | var bad_https_server; 561 | var bad_https_server_port; 562 | 563 | var certErrorMessage = 'Error: certificate has expired'; 564 | // <0.11.11: https://github.com/nodejs/node/commit/262a752c2943842df7babdf55a034beca68794cd 565 | if (/^0\.(?!11\.1[1-4]|12\.)/.test(process.versions.node)) { 566 | certErrorMessage = 'Error: CERT_HAS_EXPIRED'; 567 | } 568 | 569 | before(function() { 570 | cors_anywhere = createServer({}); 571 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 572 | }); 573 | after(function(done) { 574 | stopServer(done); 575 | }); 576 | 577 | before(function() { 578 | bad_https_server = https.createServer({ 579 | // rejectUnauthorized: false, 580 | key: fs.readFileSync(path.join(__dirname, 'key.pem')), 581 | cert: fs.readFileSync(path.join(__dirname, 'cert.pem')), 582 | }, function(req, res) { 583 | res.end('Response from server with expired cert'); 584 | }); 585 | bad_https_server_port = bad_https_server.listen(0).address().port; 586 | 587 | NODE_TLS_REJECT_UNAUTHORIZED = process.env.NODE_TLS_REJECT_UNAUTHORIZED; 588 | }); 589 | after(function(done) { 590 | if (NODE_TLS_REJECT_UNAUTHORIZED === undefined) { 591 | delete process.env.NODE_TLS_REJECT_UNAUTHORIZED; 592 | } else { 593 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = NODE_TLS_REJECT_UNAUTHORIZED; 594 | } 595 | bad_https_server.close(function() { 596 | done(); 597 | }); 598 | }); 599 | 600 | it('respects certificate errors by default', function(done) { 601 | // Test is expected to run without NODE_TLS_REJECT_UNAUTHORIZED=0 602 | request(cors_anywhere) 603 | .get('/https://127.0.0.1:' + bad_https_server_port) 604 | .set('test-include-xfwd', '') 605 | .expect('Access-Control-Allow-Origin', '*') 606 | .expect('Not found because of proxy error: ' + certErrorMessage, done); 607 | }); 608 | 609 | it('ignore certificate errors via NODE_TLS_REJECT_UNAUTHORIZED=0', function(done) { 610 | stopServer(function() { 611 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 612 | cors_anywhere = createServer({}); 613 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 614 | request(cors_anywhere) 615 | .get('/https://127.0.0.1:' + bad_https_server_port) 616 | .set('test-include-xfwd', '') 617 | .expect('Access-Control-Allow-Origin', '*') 618 | .expect('Response from server with expired cert', done); 619 | }); 620 | }); 621 | 622 | it('respects certificate errors when httpProxyOptions.secure=true', function(done) { 623 | stopServer(function() { 624 | cors_anywhere = createServer({ 625 | httpProxyOptions: { 626 | secure: true, 627 | }, 628 | }); 629 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 630 | request(cors_anywhere) 631 | .get('/https://127.0.0.1:' + bad_https_server_port) 632 | .set('test-include-xfwd', '') 633 | .expect('Access-Control-Allow-Origin', '*') 634 | .expect('Not found because of proxy error: ' + certErrorMessage, done); 635 | }); 636 | }); 637 | }); 638 | 639 | describe('originBlacklist', function() { 640 | before(function() { 641 | cors_anywhere = createServer({ 642 | originBlacklist: ['http://denied.origin.test'], 643 | }); 644 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 645 | }); 646 | after(stopServer); 647 | 648 | it('GET /example.com with denied origin', function(done) { 649 | request(cors_anywhere) 650 | .get('/example.com/') 651 | .set('Origin', 'http://denied.origin.test') 652 | .expect('Access-Control-Allow-Origin', '*') 653 | .expect(403, done); 654 | }); 655 | 656 | it('GET /example.com without denied origin', function(done) { 657 | request(cors_anywhere) 658 | .get('/example.com/') 659 | .set('Origin', 'https://denied.origin.test') // Note: different scheme! 660 | .expect('Access-Control-Allow-Origin', '*') 661 | .expect(200, done); 662 | }); 663 | 664 | it('GET /example.com without origin', function(done) { 665 | request(cors_anywhere) 666 | .get('/example.com/') 667 | .expect('Access-Control-Allow-Origin', '*') 668 | .expect(200, done); 669 | }); 670 | }); 671 | 672 | describe('originWhitelist', function() { 673 | before(function() { 674 | cors_anywhere = createServer({ 675 | originWhitelist: ['https://permitted.origin.test'], 676 | }); 677 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 678 | }); 679 | after(stopServer); 680 | 681 | it('GET /example.com with permitted origin', function(done) { 682 | request(cors_anywhere) 683 | .get('/example.com/') 684 | .set('Origin', 'https://permitted.origin.test') 685 | .expect('Access-Control-Allow-Origin', '*') 686 | .expect(200, done); 687 | }); 688 | 689 | it('GET /example.com without permitted origin', function(done) { 690 | request(cors_anywhere) 691 | .get('/example.com/') 692 | .set('Origin', 'http://permitted.origin.test') // Note: different scheme! 693 | .expect('Access-Control-Allow-Origin', '*') 694 | .expect(403, done); 695 | }); 696 | 697 | it('GET /example.com without origin', function(done) { 698 | request(cors_anywhere) 699 | .get('/example.com/') 700 | .expect('Access-Control-Allow-Origin', '*') 701 | .expect(403, done); 702 | }); 703 | }); 704 | 705 | describe('handleInitialRequest', function() { 706 | afterEach(stopServer); 707 | 708 | it('GET / with handleInitialRequest', function(done) { 709 | cors_anywhere = createServer({ 710 | handleInitialRequest: function(req, res, location) { 711 | res.writeHead(419); 712 | res.end('res:' + (location && location.href)); 713 | return true; 714 | }, 715 | }); 716 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 717 | request(cors_anywhere) 718 | .get('/') 719 | .expect(419, 'res:null', done); 720 | }); 721 | 722 | it('GET /dummy with handleInitialRequest', function(done) { 723 | cors_anywhere = createServer({ 724 | handleInitialRequest: function(req, res, location) { 725 | res.writeHead(419); 726 | res.end('res:' + (location && location.href)); 727 | return true; 728 | }, 729 | }); 730 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 731 | request(cors_anywhere) 732 | .get('/dummy') 733 | .expect(419, 'res:http://dummy/', done); 734 | }); 735 | 736 | it('GET /example.com with handleInitialRequest', function(done) { 737 | cors_anywhere = createServer({ 738 | handleInitialRequest: function(req, res, location) { 739 | res.setHeader('X-Extra-Header', 'hello ' + location.href); 740 | }, 741 | }); 742 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 743 | request(cors_anywhere) 744 | .get('/example.com') 745 | .set('Origin', 'null') 746 | .expect('Access-Control-Allow-Origin', '*') 747 | .expect('X-Extra-Header', 'hello http://example.com/') 748 | .expect(200, 'Response from example.com', done); 749 | }); 750 | }); 751 | 752 | describe('checkRateLimit', function() { 753 | afterEach(stopServer); 754 | 755 | it('GET /example.com without rate-limit', function(done) { 756 | cors_anywhere = createServer({ 757 | checkRateLimit: function() {}, 758 | }); 759 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 760 | request(cors_anywhere) 761 | .get('/example.com/') 762 | .expect('Access-Control-Allow-Origin', '*') 763 | .expect(200, done); 764 | }); 765 | 766 | it('GET /example.com with rate-limit', function(done) { 767 | cors_anywhere = createServer({ 768 | checkRateLimit: function(origin) { 769 | // Non-empty value. Let's return the origin parameter so that we also verify that the 770 | // the parameter is really the origin. 771 | return '[' + origin + ']'; 772 | }, 773 | }); 774 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 775 | request(cors_anywhere) 776 | .get('/example.com/') 777 | .set('Origin', 'http://example.net:1234') 778 | .expect('Access-Control-Allow-Origin', '*') 779 | .expect(429, done, 780 | 'The origin "http://example.net" has sent too many requests.\n[http://example.com:1234]'); 781 | }); 782 | }); 783 | 784 | describe('redirectSameOrigin', function() { 785 | before(function() { 786 | cors_anywhere = createServer({ 787 | redirectSameOrigin: true, 788 | }); 789 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 790 | }); 791 | after(stopServer); 792 | 793 | it('GET /example.com with Origin: http://example.com', function(done) { 794 | request(cors_anywhere) 795 | .get('/example.com/') 796 | .set('Origin', 'http://example.com') 797 | .expect('Access-Control-Allow-Origin', '*') 798 | .expect('Cache-Control', 'private') 799 | .expect('Vary', 'origin') 800 | .expect('Location', 'http://example.com/') 801 | .expect(301, done); 802 | }); 803 | 804 | it('GET /example.com with Origin: https://example.com', function(done) { 805 | // Not same-origin because of different schemes. 806 | request(cors_anywhere) 807 | .get('/example.com/') 808 | .set('Origin', 'https://example.com') 809 | .expect('Access-Control-Allow-Origin', '*') 810 | .expect(200, 'Response from example.com', done); 811 | }); 812 | 813 | it('GET /example.com with Origin: http://example.com:1234', function(done) { 814 | // Not same-origin because of different ports. 815 | request(cors_anywhere) 816 | .get('/example.com/') 817 | .set('Origin', 'http://example.com:1234') 818 | .expect('Access-Control-Allow-Origin', '*') 819 | .expect(200, 'Response from example.com', done); 820 | }); 821 | 822 | it('GET /example.com:1234 with Origin: http://example.com', function(done) { 823 | // Not same-origin because of different ports. 824 | request(cors_anywhere) 825 | .get('/example.com:1234/') 826 | .set('Origin', 'http://example.com') 827 | .expect('Access-Control-Allow-Origin', '*') 828 | .expect(200, 'Response from example.com:1234', done); 829 | }); 830 | 831 | it('GET /example.com with Origin: http://example.com.test', function(done) { 832 | // Not same-origin because of different host names. 833 | request(cors_anywhere) 834 | .get('/example.com/') 835 | .set('Origin', 'http://example.com.test') 836 | .expect('Access-Control-Allow-Origin', '*') 837 | .expect(200, 'Response from example.com', done); 838 | }); 839 | 840 | it('GET /example.com.com with Origin: http://example.com', function(done) { 841 | // Not same-origin because of different host names. 842 | request(cors_anywhere) 843 | .get('/example.com.com/') 844 | .set('Origin', 'http://example.com') 845 | .expect('Access-Control-Allow-Origin', '*') 846 | .expect(200, 'Response from example.com.com', done); 847 | }); 848 | 849 | it('GET /prefix.example.com with Origin: http://example.com', function(done) { 850 | // Not same-origin because of different host names. 851 | request(cors_anywhere) 852 | .get('/prefix.example.com/') 853 | .set('Origin', 'http://example.com') 854 | .expect('Access-Control-Allow-Origin', '*') 855 | .expect(200, 'Response from prefix.example.com', done); 856 | }); 857 | }); 858 | 859 | describe('requireHeader', function() { 860 | before(function() { 861 | cors_anywhere = createServer({ 862 | requireHeader: ['origin', 'x-requested-with'], 863 | }); 864 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 865 | }); 866 | after(stopServer); 867 | 868 | it('GET /example.com without header', function(done) { 869 | request(cors_anywhere) 870 | .get('/example.com/') 871 | .expect('Access-Control-Allow-Origin', '*') 872 | .expect(400, 'Missing required request header. Must specify one of: origin,x-requested-with', done); 873 | }); 874 | 875 | it('GET /example.com with X-Requested-With header', function(done) { 876 | request(cors_anywhere) 877 | .get('/example.com/') 878 | .set('X-Requested-With', '') 879 | .expect('Access-Control-Allow-Origin', '*') 880 | .expect(200, done); 881 | }); 882 | 883 | it('GET /example.com with Origin header', function(done) { 884 | request(cors_anywhere) 885 | .get('/example.com/') 886 | .set('Origin', 'null') 887 | .expect('Access-Control-Allow-Origin', '*') 888 | .expect(200, done); 889 | }); 890 | 891 | it('GET /example.com without header (requireHeader as string)', function(done) { 892 | stopServer(function() { 893 | cors_anywhere = createServer({ 894 | requireHeader: 'origin', 895 | }); 896 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 897 | request(cors_anywhere) 898 | .get('/example.com/') 899 | .expect('Access-Control-Allow-Origin', '*') 900 | .expect(400, 'Missing required request header. Must specify one of: origin', done); 901 | }); 902 | }); 903 | 904 | it('GET /example.com with header (requireHeader as string)', function(done) { 905 | stopServer(function() { 906 | cors_anywhere = createServer({ 907 | requireHeader: 'origin', 908 | }); 909 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 910 | request(cors_anywhere) 911 | .get('/example.com/') 912 | .set('Origin', 'null') 913 | .expect('Access-Control-Allow-Origin', '*') 914 | .expect(200, 'Response from example.com', done); 915 | }); 916 | }); 917 | 918 | it('GET /example.com without header (requireHeader as string, uppercase)', function(done) { 919 | stopServer(function() { 920 | cors_anywhere = createServer({ 921 | requireHeader: 'ORIGIN', 922 | }); 923 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 924 | request(cors_anywhere) 925 | .get('/example.com/') 926 | .expect('Access-Control-Allow-Origin', '*') 927 | .expect(400, 'Missing required request header. Must specify one of: origin', done); 928 | }); 929 | }); 930 | 931 | it('GET /example.com with header (requireHeader as string, uppercase)', function(done) { 932 | stopServer(function() { 933 | cors_anywhere = createServer({ 934 | requireHeader: 'ORIGIN', 935 | }); 936 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 937 | request(cors_anywhere) 938 | .get('/example.com/') 939 | .set('Origin', 'null') 940 | .expect('Access-Control-Allow-Origin', '*') 941 | .expect(200, 'Response from example.com', done); 942 | }); 943 | }); 944 | 945 | it('GET /example.com (requireHeader is an empty array)', function(done) { 946 | stopServer(function() { 947 | cors_anywhere = createServer({ 948 | requireHeader: [], 949 | }); 950 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 951 | request(cors_anywhere) 952 | .get('/example.com/') 953 | .expect('Access-Control-Allow-Origin', '*') 954 | .expect(200, 'Response from example.com', done); 955 | }); 956 | }); 957 | }); 958 | 959 | describe('removeHeaders', function() { 960 | before(function() { 961 | cors_anywhere = createServer({ 962 | removeHeaders: ['cookie', 'cookie2'], 963 | }); 964 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 965 | }); 966 | after(stopServer); 967 | 968 | it('GET /example.com with request cookie', function(done) { 969 | request(cors_anywhere) 970 | .get('/example.com/echoheaders') 971 | .set('cookie', 'a') 972 | .set('cookie2', 'b') 973 | .expect('Access-Control-Allow-Origin', '*') 974 | .expectJSON({ 975 | host: 'example.com', 976 | }, done); 977 | }); 978 | 979 | it('GET /example.com with unknown header', function(done) { 980 | request(cors_anywhere) 981 | .get('/example.com/echoheaders') 982 | .set('cookie', 'a') 983 | .set('cookie2', 'b') 984 | .set('cookie3', 'c') 985 | .expect('Access-Control-Allow-Origin', '*') 986 | .expectJSON({ 987 | host: 'example.com', 988 | cookie3: 'c', 989 | }, done); 990 | }); 991 | }); 992 | 993 | describe('setHeaders', function() { 994 | before(function() { 995 | cors_anywhere = createServer({ 996 | setHeaders: {'x-powered-by': 'CORS Anywhere'}, 997 | }); 998 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 999 | }); 1000 | after(stopServer); 1001 | 1002 | it('GET /example.com', function(done) { 1003 | request(cors_anywhere) 1004 | .get('/example.com/echoheaders') 1005 | .expect('Access-Control-Allow-Origin', '*') 1006 | .expectJSON({ 1007 | host: 'example.com', 1008 | 'x-powered-by': 'CORS Anywhere', 1009 | }, done); 1010 | }); 1011 | 1012 | it('GET /example.com should replace header', function(done) { 1013 | request(cors_anywhere) 1014 | .get('/example.com/echoheaders') 1015 | .set('x-powered-by', 'should be replaced') 1016 | .expect('Access-Control-Allow-Origin', '*') 1017 | .expectJSON({ 1018 | host: 'example.com', 1019 | 'x-powered-by': 'CORS Anywhere', 1020 | }, done); 1021 | }); 1022 | }); 1023 | 1024 | describe('setHeaders + removeHeaders', function() { 1025 | before(function() { 1026 | // setHeaders takes precedence over removeHeaders 1027 | cors_anywhere = createServer({ 1028 | removeHeaders: ['x-powered-by'], 1029 | setHeaders: {'x-powered-by': 'CORS Anywhere'}, 1030 | }); 1031 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 1032 | }); 1033 | after(stopServer); 1034 | 1035 | it('GET /example.com', function(done) { 1036 | request(cors_anywhere) 1037 | .get('/example.com/echoheaders') 1038 | .expect('Access-Control-Allow-Origin', '*') 1039 | .expectJSON({ 1040 | host: 'example.com', 1041 | 'x-powered-by': 'CORS Anywhere', 1042 | }, done); 1043 | }); 1044 | 1045 | it('GET /example.com should replace header', function(done) { 1046 | request(cors_anywhere) 1047 | .get('/example.com/echoheaders') 1048 | .set('x-powered-by', 'should be replaced') 1049 | .expect('Access-Control-Allow-Origin', '*') 1050 | .expectJSON({ 1051 | host: 'example.com', 1052 | 'x-powered-by': 'CORS Anywhere', 1053 | }, done); 1054 | }); 1055 | }); 1056 | 1057 | describe('Access-Control-Max-Age set', function() { 1058 | before(function() { 1059 | cors_anywhere = createServer({ 1060 | corsMaxAge: 600, 1061 | }); 1062 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 1063 | }); 1064 | after(stopServer); 1065 | 1066 | it('OPTIONS /', function(done) { 1067 | request(cors_anywhere) 1068 | .options('/') 1069 | .expect('Access-Control-Allow-Origin', '*') 1070 | .expect('Access-Control-Max-Age', '600') 1071 | .expect(200, '', done); 1072 | }); 1073 | 1074 | it('OPTIONS /example.com', function(done) { 1075 | request(cors_anywhere) 1076 | .options('/example.com') 1077 | .expect('Access-Control-Allow-Origin', '*') 1078 | .expect('Access-Control-Max-Age', '600') 1079 | .expect(200, '', done); 1080 | }); 1081 | 1082 | it('GET / no Access-Control-Max-Age on GET', function(done) { 1083 | request(cors_anywhere) 1084 | .get('/') 1085 | .type('text/plain') 1086 | .expect('Access-Control-Allow-Origin', '*') 1087 | .expectNoHeader('Access-Control-Max-Age') 1088 | .expect(200, helpText, done); 1089 | }); 1090 | 1091 | it('GET /example.com no Access-Control-Max-Age on GET', function(done) { 1092 | request(cors_anywhere) 1093 | .get('/example.com') 1094 | .expect('Access-Control-Allow-Origin', '*') 1095 | .expectNoHeader('Access-Control-Max-Age') 1096 | .expect(200, 'Response from example.com', done); 1097 | }); 1098 | }); 1099 | 1100 | describe('Access-Control-Max-Age not set', function() { 1101 | before(function() { 1102 | cors_anywhere = createServer(); 1103 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 1104 | }); 1105 | after(stopServer); 1106 | 1107 | it('OPTIONS / corsMaxAge disabled', function(done) { 1108 | request(cors_anywhere) 1109 | .options('/') 1110 | .expect('Access-Control-Allow-Origin', '*') 1111 | .expectNoHeader('Access-Control-Max-Age') 1112 | .expect(200, '', done); 1113 | }); 1114 | 1115 | it('OPTIONS /example.com corsMaxAge disabled', function(done) { 1116 | request(cors_anywhere) 1117 | .options('/example.com') 1118 | .expect('Access-Control-Allow-Origin', '*') 1119 | .expectNoHeader('Access-Control-Max-Age') 1120 | .expect(200, '', done); 1121 | }); 1122 | 1123 | it('GET /', function(done) { 1124 | request(cors_anywhere) 1125 | .get('/') 1126 | .type('text/plain') 1127 | .expect('Access-Control-Allow-Origin', '*') 1128 | .expectNoHeader('Access-Control-Max-Age') 1129 | .expect(200, helpText, done); 1130 | }); 1131 | 1132 | it('GET /example.com', function(done) { 1133 | request(cors_anywhere) 1134 | .get('/example.com') 1135 | .expect('Access-Control-Allow-Origin', '*') 1136 | .expectNoHeader('Access-Control-Max-Age') 1137 | .expect(200, 'Response from example.com', done); 1138 | }); 1139 | }); 1140 | 1141 | describe('httpProxyOptions.xfwd=false', function() { 1142 | before(function() { 1143 | cors_anywhere = createServer({ 1144 | httpProxyOptions: { 1145 | xfwd: false, 1146 | }, 1147 | }); 1148 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 1149 | }); 1150 | after(stopServer); 1151 | 1152 | it('X-Forwarded-* headers should not be set', function(done) { 1153 | request(cors_anywhere) 1154 | .get('/example.com/echoheaders') 1155 | .set('test-include-xfwd', '') 1156 | .expect('Access-Control-Allow-Origin', '*') 1157 | .expectJSON({ 1158 | host: 'example.com', 1159 | }, done); 1160 | }); 1161 | }); 1162 | 1163 | describe('httpProxyOptions.getProxyForUrl', function() { 1164 | var proxy_server; 1165 | var proxy_url; 1166 | before(function() { 1167 | // Using a real server instead of a mock because Nock doesn't can't mock proxies. 1168 | proxy_server = http.createServer(function(req, res) { 1169 | res.end(req.method + ' ' + req.url + ' Host=' + req.headers.host); 1170 | }); 1171 | proxy_url = 'http://127.0.0.1:' + proxy_server.listen(0).address().port; 1172 | 1173 | cors_anywhere = createServer({ 1174 | httpProxyOptions: { 1175 | xfwd: false, 1176 | }, 1177 | }); 1178 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 1179 | }); 1180 | afterEach(function() { 1181 | // Assuming that they were not set before. 1182 | delete process.env.https_proxy; 1183 | delete process.env.http_proxy; 1184 | delete process.env.no_proxy; 1185 | }); 1186 | after(function(done) { 1187 | proxy_server.close(function() { 1188 | done(); 1189 | }); 1190 | }); 1191 | after(stopServer); 1192 | 1193 | it('http_proxy should be respected for matching domains', function(done) { 1194 | process.env.http_proxy = proxy_url; 1195 | 1196 | request(cors_anywhere) 1197 | .get('/http://example.com') 1198 | .expect('Access-Control-Allow-Origin', '*') 1199 | .expect(200, 'GET http://example.com/ Host=example.com', done); 1200 | }); 1201 | 1202 | it('http_proxy should be ignored for http URLs', function(done) { 1203 | process.env.http_proxy = proxy_url; 1204 | request(cors_anywhere) 1205 | .get('/https://example.com') 1206 | .expect('Access-Control-Allow-Origin', '*') 1207 | .expect(200, 'Response from https://example.com', done); 1208 | }); 1209 | 1210 | it('https_proxy should be respected for matching domains', function(done) { 1211 | process.env.https_proxy = proxy_url; 1212 | 1213 | request(cors_anywhere) 1214 | .get('/https://example.com') 1215 | .expect('Access-Control-Allow-Origin', '*') 1216 | .expect(200, 'GET https://example.com/ Host=example.com', done); 1217 | }); 1218 | 1219 | it('https_proxy should be ignored for http URLs', function(done) { 1220 | process.env.https_proxy = proxy_url; 1221 | request(cors_anywhere) 1222 | .get('/http://example.com') 1223 | .expect('Access-Control-Allow-Origin', '*') 1224 | .expect(200, 'Response from example.com', done); 1225 | }); 1226 | 1227 | it('https_proxy + no_proxy should not intercept requests in no_proxy', function(done) { 1228 | process.env.https_proxy = proxy_url; 1229 | process.env.no_proxy = 'example.com:443'; 1230 | request(cors_anywhere) 1231 | .get('/https://example.com') 1232 | .expect('Access-Control-Allow-Origin', '*') 1233 | .expect(200, 'Response from https://example.com', done); 1234 | }); 1235 | }); 1236 | 1237 | describe('helpFile', function() { 1238 | 1239 | afterEach(stopServer); 1240 | 1241 | it('GET / with custom text helpFile', function(done) { 1242 | var customHelpTextPath = path.join(__dirname, './customHelp.txt'); 1243 | var customHelpText = fs.readFileSync(customHelpTextPath, {encoding: 'utf8'}); 1244 | 1245 | cors_anywhere = createServer({ 1246 | helpFile: customHelpTextPath, 1247 | }); 1248 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 1249 | 1250 | request(cors_anywhere) 1251 | .get('/') 1252 | .type('text/plain') 1253 | .expect('Access-Control-Allow-Origin', '*') 1254 | .expect(200, customHelpText, done); 1255 | }); 1256 | 1257 | it('GET / with custom HTML helpFile', function(done) { 1258 | var customHelpTextPath = path.join(__dirname, './customHelp.html'); 1259 | var customHelpText = fs.readFileSync(customHelpTextPath, {encoding: 'utf8'}); 1260 | 1261 | cors_anywhere = createServer({ 1262 | helpFile: customHelpTextPath, 1263 | }); 1264 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 1265 | 1266 | request(cors_anywhere) 1267 | .get('/') 1268 | .type('text/html') 1269 | .expect('Access-Control-Allow-Origin', '*') 1270 | .expect(200, customHelpText, done); 1271 | }); 1272 | 1273 | it('GET / with non-existent help file', function(done) { 1274 | var customHelpTextPath = path.join(__dirname, 'Some non-existing file.'); 1275 | 1276 | cors_anywhere = createServer({ 1277 | helpFile: customHelpTextPath, 1278 | }); 1279 | cors_anywhere_port = cors_anywhere.listen(0).address().port; 1280 | 1281 | request(cors_anywhere) 1282 | .get('/') 1283 | .type('text/plain') 1284 | .expect('Access-Control-Allow-Origin', '*') 1285 | .expect(500, '', done); 1286 | }); 1287 | }); 1288 | --------------------------------------------------------------------------------