├── .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
├── 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 | script:
15 | - npm run lint
16 | - npm run test
17 | - npm run test-coverage && cat coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf coverage
18 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cors-anywhere",
3 | "version": "0.4.3",
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-heroku-queue-wait-time',
33 | 'x-heroku-queue-depth',
34 | 'x-heroku-dynos-in-use',
35 | 'x-request-start',
36 | ],
37 | redirectSameOrigin: true,
38 | httpProxyOptions: {
39 | // Do not add X-Forwarded-For, etc. headers, because Heroku already adds it.
40 | xfwd: false,
41 | },
42 | }).listen(port, host, function() {
43 | console.log('Running CORS Anywhere on ' + host + ':' + port);
44 | });
45 |
--------------------------------------------------------------------------------
/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 •
Github •
Live 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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/Rob--W/cors-anywhere)
2 | [](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 | If you want to automatically enable cross-domain requests when needed, use the following snippet:
54 |
55 | ```javascript
56 | (function() {
57 | var cors_api_host = 'cors-anywhere.herokuapp.com';
58 | var cors_api_url = 'https://' + cors_api_host + '/';
59 | var slice = [].slice;
60 | var origin = window.location.protocol + '//' + window.location.host;
61 | var open = XMLHttpRequest.prototype.open;
62 | XMLHttpRequest.prototype.open = function() {
63 | var args = slice.call(arguments);
64 | var targetOrigin = /^https?:\/\/([^\/]+)/i.exec(args[1]);
65 | if (targetOrigin && targetOrigin[0].toLowerCase() !== origin &&
66 | targetOrigin[1] !== cors_api_host) {
67 | args[1] = cors_api_url + args[1];
68 | }
69 | return open.apply(this, args);
70 | };
71 | })();
72 | ```
73 |
74 | If you're using jQuery, you can also use the following code **instead of** the previous one:
75 |
76 | ```javascript
77 | jQuery.ajaxPrefilter(function(options) {
78 | if (options.crossDomain && jQuery.support.cors) {
79 | options.url = 'https://cors-anywhere.herokuapp.com/' + options.url;
80 | }
81 | });
82 | ```
83 |
84 | ### Server
85 |
86 | The module exports `createServer(options)`, which creates a server that handles
87 | proxy requests. The following options are supported:
88 |
89 | * function `getProxyForUrl` - If set, specifies which intermediate proxy to use for a given URL.
90 | If the return value is void, a direct request is sent. The default implementation is
91 | [`proxy-from-env`](https://github.com/Rob--W/proxy-from-env), which respects the standard proxy
92 | environment variables (e.g. `https_proxy`, `no_proxy`, etc.).
93 | * array of strings `originBlacklist` - If set, requests whose origin is listed are blocked.
94 | Example: `['https://bad.example.com', 'http://bad.example.com']`
95 | * array of strings `originWhitelist` - If set, requests whose origin is not listed are blocked.
96 | If this list is empty, all origins are allowed.
97 | Example: `['https://good.example.com', 'http://good.example.com']`
98 | * function `checkRateLimit` - If set, it is called with the origin (string) of the request. If this
99 | function returns a non-empty string, the request is rejected and the string is send to the client.
100 | * boolean `redirectSameOrigin` - If true, requests to URLs from the same origin will not be proxied but redirected.
101 | The primary purpose for this option is to save server resources by delegating the request to the client
102 | (since same-origin requests should always succeed, even without proxying).
103 | * array of strings `requireHeader` - If set, the request must include this header or the API will refuse to proxy.
104 | Recommended if you want to prevent users from using the proxy for normal browsing.
105 | Example: `['Origin', 'X-Requested-With']`.
106 | * array of lowercase strings `removeHeaders` - Exclude certain headers from being included in the request.
107 | Example: `["cookie"]`
108 | * dictionary of lowercase strings `setHeaders` - Set headers for the request (overwrites existing ones).
109 | Example: `{"x-powered-by": "CORS Anywhere"}`
110 | * number `corsMaxAge` - If set, an Access-Control-Max-Age request header with this value (in seconds) will be added.
111 | Example: `600` - Allow CORS preflight request to be cached by the browser for 10 minutes.
112 | * string `helpFile` - Set the help file (shown at the homepage).
113 | Example: `"myCustomHelpText.txt"`
114 |
115 | For advanced users, the following options are also provided.
116 |
117 | * `httpProxyOptions` - Under the hood, [http-proxy](https://github.com/nodejitsu/node-http-proxy)
118 | is used to proxy requests. Use this option if you really need to pass options
119 | to http-proxy. The documentation for these options can be found [here](https://github.com/nodejitsu/node-http-proxy#options).
120 | * `httpsOptions` - If set, a `https.Server` will be created. The given options are passed to the
121 | [`https.createServer`](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener) method.
122 |
123 | For even more advanced usage (building upon CORS Anywhere),
124 | see the sample code in [test/test-examples.js](test/test-examples.js).
125 |
126 | ### Demo server
127 |
128 | A public demo of CORS Anywhere is available at https://cors-anywhere.herokuapp.com. This server is
129 | only provided so that you can easily and quickly try out CORS Anywhere. To ensure that the service
130 | stays available to everyone, the number of requests per period is limited, except for requests from
131 | some explicitly whitelisted origins.
132 |
133 | If you expect lots of traffic, please host your own instance of CORS Anywhere, and make sure that
134 | the CORS Anywhere server only whitelists your site to prevent others from using your instance of
135 | CORS Anywhere as an open proxy.
136 |
137 | For instance, to run a CORS Anywhere server that accepts any request from some example.com sites on
138 | port 8080, use:
139 | ```
140 | export PORT=8080
141 | export CORSANYWHERE_WHITELIST=https://example.com,http://example.com,http://example.com:8080
142 | node server.js
143 | ```
144 |
145 | This application can immediately be run on Heroku, see https://devcenter.heroku.com/articles/nodejs
146 | for instructions. Note that their [Acceptable Use Policy](https://www.heroku.com/policy/aup) forbids
147 | the use of Heroku for operating an open proxy, so make sure that you either enforce a whitelist as
148 | shown above, or severly rate-limit the number of requests.
149 |
150 | For example, to blacklist abuse.example.com and rate-limit everything to 50 requests per 3 minutes,
151 | except for my.example.com and my2.example.com (which may be unlimited), use:
152 |
153 | ```
154 | export PORT=8080
155 | export CORSANYWHERE_BLACKLIST=https://abuse.example.com,http://abuse.example.com
156 | export CORSANYWHERE_RATELIMIT='50 3 my.example.com my2.example.com'
157 | node server.js
158 | ```
159 |
160 |
161 | ## License
162 |
163 | Copyright (C) 2013 - 2016 Rob Wu
164 |
165 | Permission is hereby granted, free of charge, to any person obtaining a copy of
166 | this software and associated documentation files (the "Software"), to deal in
167 | the Software without restriction, including without limitation the rights to
168 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
169 | of the Software, and to permit persons to whom the Software is furnished to do
170 | so, subject to the following conditions:
171 |
172 | The above copyright notice and this permission notice shall be included in all
173 | copies or substantial portions of the Software.
174 |
175 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
176 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
177 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
178 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
179 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
180 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
181 | SOFTWARE.
182 |
--------------------------------------------------------------------------------
/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 2017111000, Last Updated Fri Nov 10 07:07:01 2017 UTC
7 | var regexp = /\.(?:AAA|AARP|ABARTH|ABB|ABBOTT|ABBVIE|ABC|ABLE|ABOGADO|ABUDHABI|AC|ACADEMY|ACCENTURE|ACCOUNTANT|ACCOUNTANTS|ACO|ACTIVE|ACTOR|AD|ADAC|ADS|ADULT|AE|AEG|AERO|AETNA|AF|AFAMILYCOMPANY|AFL|AFRICA|AG|AGAKHAN|AGENCY|AI|AIG|AIGO|AIRBUS|AIRFORCE|AIRTEL|AKDN|AL|ALFAROMEO|ALIBABA|ALIPAY|ALLFINANZ|ALLSTATE|ALLY|ALSACE|ALSTOM|AM|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|BLANCO|BLOCKBUSTER|BLOG|BLOOMBERG|BLUE|BM|BMS|BMW|BN|BNL|BNPPARIBAS|BO|BOATS|BOEHRINGER|BOFA|BOM|BOND|BOO|BOOK|BOOKING|BOOTS|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|CARTIER|CASA|CASE|CASEIH|CASH|CASINO|CAT|CATERING|CATHOLIC|CBA|CBN|CBRE|CBS|CC|CD|CEB|CENTER|CEO|CERN|CF|CFA|CFD|CG|CH|CHANEL|CHANNEL|CHASE|CHAT|CHEAP|CHINTAI|CHRISTMAS|CHROME|CHRYSLER|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|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|DODGE|DOG|DOHA|DOMAINS|DOT|DOWNLOAD|DRIVE|DTV|DUBAI|DUCK|DUNLOP|DUNS|DUPONT|DURBAN|DVAG|DVR|DZ|EARTH|EAT|EC|ECO|EDEKA|EDU|EDUCATION|EE|EG|EMAIL|EMERCK|ENERGY|ENGINEER|ENGINEERING|ENTERPRISES|EPOST|EPSON|EQUIPMENT|ER|ERICSSON|ERNI|ES|ESQ|ESTATE|ESURANCE|ET|ETISALAT|EU|EUROVISION|EUS|EVENTS|EVERBANK|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|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|GOODHANDS|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|HONEYWELL|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|INDUSTRIES|INFINITI|INFO|ING|INK|INSTITUTE|INSURANCE|INSURE|INT|INTEL|INTERNATIONAL|INTUIT|INVESTMENTS|IO|IPIRANGA|IQ|IR|IRISH|IS|ISELECT|ISMAILI|IST|ISTANBUL|IT|ITAU|ITV|IVECO|IWC|JAGUAR|JAVA|JCB|JCP|JE|JEEP|JETZT|JEWELRY|JIO|JLC|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|LADBROKES|LAMBORGHINI|LAMER|LANCASTER|LANCIA|LANCOME|LAND|LANDROVER|LANXESS|LASALLE|LAT|LATINO|LATROBE|LAW|LAWYER|LB|LC|LDS|LEASE|LECLERC|LEFRAK|LEGAL|LEGO|LEXUS|LGBT|LI|LIAISON|LIDL|LIFE|LIFEINSURANCE|LIFESTYLE|LIGHTING|LIKE|LILLY|LIMITED|LIMO|LINCOLN|LINDE|LINK|LIPSY|LIVE|LIVING|LIXIL|LK|LOAN|LOANS|LOCKER|LOCUS|LOFT|LOL|LONDON|LOTTE|LOTTO|LOVE|LPL|LPLFINANCIAL|LR|LS|LT|LTD|LTDA|LU|LUNDBECK|LUPIN|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|MEO|MERCKMSD|METLIFE|MG|MH|MIAMI|MICROSOFT|MIL|MINI|MINT|MIT|MITSUBISHI|MK|ML|MLB|MLS|MM|MMA|MN|MO|MOBI|MOBILE|MOBILY|MODA|MOE|MOI|MOM|MONASH|MONEY|MONSTER|MOPAR|MORMON|MORTGAGE|MOSCOW|MOTO|MOTORCYCLES|MOV|MOVIE|MOVISTAR|MP|MQ|MR|MS|MSD|MT|MTN|MTR|MU|MUSEUM|MUTUAL|MV|MW|MX|MY|MZ|NA|NAB|NADEX|NAGOYA|NAME|NATIONWIDE|NATURA|NAVY|NBA|NC|NE|NEC|NET|NETBANK|NETFLIX|NETWORK|NEUSTAR|NEW|NEWHOLLAND|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|PANERAI|PARIS|PARS|PARTNERS|PARTS|PARTY|PASSAGENS|PAY|PCCW|PE|PET|PF|PFIZER|PG|PH|PHARMACY|PHD|PHILIPS|PHONE|PHOTO|PHOTOGRAPHY|PHOTOS|PHYSIO|PIAGET|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|RIGHTATHOME|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|SAPO|SARL|SAS|SAVE|SAXO|SB|SBI|SBS|SC|SCA|SCB|SCHAEFFLER|SCHMIDT|SCHOLARSHIPS|SCHOOL|SCHULE|SCHWARZ|SCIENCE|SCJOHNSON|SCOR|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|SHRIRAM|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|SPACE|SPIEGEL|SPOT|SPREADBETTING|SR|SRL|SRT|ST|STADA|STAPLES|STAR|STARHUB|STATEBANK|STATEFARM|STATOIL|STC|STCGROUP|STOCKHOLM|STORAGE|STORE|STREAM|STUDIO|STUDY|STYLE|SU|SUCKS|SUPPLIES|SUPPLY|SUPPORT|SURF|SURGERY|SUZUKI|SV|SWATCH|SWIFTCOVER|SWISS|SX|SY|SYDNEY|SYMANTEC|SYSTEMS|SZ|TAB|TAIPEI|TALK|TAOBAO|TARGET|TATAMOTORS|TATAR|TATTOO|TAX|TAXI|TC|TCI|TD|TDK|TEAM|TECH|TECHNOLOGY|TEL|TELECITY|TELEFONICA|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|UCONNECT|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|VISTA|VISTAPRINT|VIVA|VIVO|VLAANDEREN|VN|VODKA|VOLKSWAGEN|VOLVO|VOTE|VOTING|VOTO|VOYAGE|VU|VUELOS|WALES|WALMART|WALTER|WANG|WANGGOU|WARMAN|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--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--CG4BKI|XN--CLCHC0EA0B2G2A9GCD|XN--CZR694B|XN--CZRS0T|XN--CZRU2D|XN--D1ACJ3B|XN--D1ALF|XN--E1A4C|XN--ECKVDTC9D|XN--EFVY88H|XN--ESTV75G|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--JLQ61U9W7B|XN--JVR189M|XN--KCRX77D1X4A|XN--KPRW13D|XN--KPRY57D|XN--KPU716F|XN--KPUT3I|XN--L1ACC|XN--LGBBAT1AD8J|XN--MGB9AWBF|XN--MGBA3A3EJT|XN--MGBA3A4F16A|XN--MGBA7C0BBN0A|XN--MGBAAKC7DVF|XN--MGBAAM7A8H|XN--MGBAB2BD|XN--MGBAI9AZGQP6J|XN--MGBAYH7GPA|XN--MGBB9FBPOB|XN--MGBBH1A|XN--MGBBH1A71E|XN--MGBC0A9AZCG|XN--MGBCA7DZDO|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--P1ACF|XN--P1AI|XN--PBT977C|XN--PGBS0DH|XN--PSSY2U|XN--Q9JYB4C|XN--QCKA1PMC|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|XPERIA|XXX|XYZ|YACHTS|YAHOO|YAMAXUN|YANDEX|YE|YODOBASHI|YOGA|YOKOHAMA|YOU|YOUTUBE|YT|YUN|ZA|ZAPPOS|ZARA|ZERO|ZIP|ZIPPO|ZM|ZONE|ZUERICH|ZW)$/i;
8 | module.exports = regexp;
9 |
--------------------------------------------------------------------------------
/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'] = '*';
55 | var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge;
56 | if (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 | headers['access-control-expose-headers'] = Object.keys(headers).join(',');
69 |
70 | return headers;
71 | }
72 |
73 | /**
74 | * Performs the actual proxy request.
75 | *
76 | * @param req {ServerRequest} Incoming http request
77 | * @param res {ServerResponse} Outgoing (proxied) http request
78 | * @param proxy {HttpProxy}
79 | */
80 | function proxyRequest(req, res, proxy) {
81 | var location = req.corsAnywhereRequestState.location;
82 | req.url = location.path;
83 |
84 | var proxyOptions = {
85 | changeOrigin: false,
86 | prependPath: false,
87 | target: location,
88 | headers: {
89 | host: location.host,
90 | },
91 | // HACK: Get hold of the proxyReq object, because we need it later.
92 | // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L144
93 | buffer: {
94 | pipe: function(proxyReq) {
95 | var proxyReqOn = proxyReq.on;
96 | // Intercepts the handler that connects proxyRes to res.
97 | // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L146-L158
98 | proxyReq.on = function(eventName, listener) {
99 | if (eventName !== 'response') {
100 | return proxyReqOn.call(this, eventName, listener);
101 | }
102 | return proxyReqOn.call(this, 'response', function(proxyRes) {
103 | if (onProxyResponse(proxy, proxyReq, proxyRes, req, res)) {
104 | try {
105 | listener(proxyRes);
106 | } catch (err) {
107 | // Wrap in try-catch because an error could occur:
108 | // "RangeError: Invalid status code: 0"
109 | // https://github.com/Rob--W/cors-anywhere/issues/95
110 | // https://github.com/nodejitsu/node-http-proxy/issues/1080
111 |
112 | // Forward error (will ultimately emit the 'error' event on our proxy object):
113 | // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L134
114 | proxyReq.emit('error', err);
115 | }
116 | }
117 | });
118 | };
119 | return req.pipe(proxyReq);
120 | },
121 | },
122 | };
123 |
124 | var proxyThroughUrl = req.corsAnywhereRequestState.getProxyForUrl(location.href);
125 | if (proxyThroughUrl) {
126 | proxyOptions.target = proxyThroughUrl;
127 | proxyOptions.toProxy = true;
128 | // If a proxy URL was set, req.url must be an absolute URL. Then the request will not be sent
129 | // directly to the proxied URL, but through another proxy.
130 | req.url = location.href;
131 | }
132 |
133 | // Start proxying the request
134 | try {
135 | proxy.web(req, res, proxyOptions);
136 | } catch (err) {
137 | proxy.emit('error', err, req, res);
138 | }
139 | }
140 |
141 | /**
142 | * This method modifies the response headers of the proxied response.
143 | * If a redirect is detected, the response is not sent to the client,
144 | * and a new request is initiated.
145 | *
146 | * client (req) -> CORS Anywhere -> (proxyReq) -> other server
147 | * client (res) <- CORS Anywhere <- (proxyRes) <- other server
148 | *
149 | * @param proxy {HttpProxy}
150 | * @param proxyReq {ClientRequest} The outgoing request to the other server.
151 | * @param proxyRes {ServerResponse} The response from the other server.
152 | * @param req {IncomingMessage} Incoming HTTP request, augmented with property corsAnywhereRequestState
153 | * @param req.corsAnywhereRequestState {object}
154 | * @param req.corsAnywhereRequestState.location {object} See parseURL
155 | * @param req.corsAnywhereRequestState.getProxyForUrl {function} See proxyRequest
156 | * @param req.corsAnywhereRequestState.proxyBaseUrl {string} Base URL of the CORS API endpoint
157 | * @param req.corsAnywhereRequestState.maxRedirects {number} Maximum number of redirects
158 | * @param req.corsAnywhereRequestState.redirectCount_ {number} Internally used to count redirects
159 | * @param res {ServerResponse} Outgoing response to the client that wanted to proxy the HTTP request.
160 | *
161 | * @returns {boolean} true if http-proxy should continue to pipe proxyRes to res.
162 | */
163 | function onProxyResponse(proxy, proxyReq, proxyRes, req, res) {
164 | var requestState = req.corsAnywhereRequestState;
165 |
166 | var statusCode = proxyRes.statusCode;
167 |
168 | if (!requestState.redirectCount_) {
169 | res.setHeader('x-request-url', requestState.location.href);
170 | }
171 | // Handle redirects
172 | if (statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308) {
173 | var locationHeader = proxyRes.headers.location;
174 | var parsedLocation;
175 | if (locationHeader) {
176 | locationHeader = url.resolve(requestState.location.href, locationHeader);
177 | parsedLocation = parseURL(locationHeader);
178 | }
179 | if (parsedLocation) {
180 | if (statusCode === 301 || statusCode === 302 || statusCode === 303) {
181 | // Exclude 307 & 308, because they are rare, and require preserving the method + request body
182 | requestState.redirectCount_ = requestState.redirectCount_ + 1 || 1;
183 | if (requestState.redirectCount_ <= requestState.maxRedirects) {
184 | // Handle redirects within the server, because some clients (e.g. Android Stock Browser)
185 | // cancel redirects.
186 | // Set header for debugging purposes. Do not try to parse it!
187 | res.setHeader('X-CORS-Redirect-' + requestState.redirectCount_, statusCode + ' ' + locationHeader);
188 |
189 | req.method = 'GET';
190 | req.headers['content-length'] = '0';
191 | delete req.headers['content-type'];
192 | requestState.location = parsedLocation;
193 |
194 | // Remove all listeners (=reset events to initial state)
195 | req.removeAllListeners();
196 |
197 | // Remove the error listener so that the ECONNRESET "error" that
198 | // may occur after aborting a request does not propagate to res.
199 | // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L134
200 | proxyReq.removeAllListeners('error');
201 | proxyReq.once('error', function catchAndIgnoreError() {});
202 | proxyReq.abort();
203 |
204 | // Initiate a new proxy request.
205 | proxyRequest(req, res, proxy);
206 | return false;
207 | }
208 | }
209 | proxyRes.headers.location = requestState.proxyBaseUrl + '/' + locationHeader;
210 | }
211 | }
212 |
213 | // Strip cookies
214 | delete proxyRes.headers['set-cookie'];
215 | delete proxyRes.headers['set-cookie2'];
216 |
217 | proxyRes.headers['x-final-url'] = requestState.location.href;
218 | withCORS(proxyRes.headers, req);
219 | return true;
220 | }
221 |
222 |
223 | /**
224 | * @param req_url {string} The requested URL (scheme is optional).
225 | * @return {object} URL parsed using url.parse
226 | */
227 | function parseURL(req_url) {
228 | var match = req_url.match(/^(?:(https?:)?\/\/)?(([^\/?]+?)(?::(\d{0,5})(?=[\/?]|$))?)([\/?][\S\s]*|$)/i);
229 | // ^^^^^^^ ^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^
230 | // 1:protocol 3:hostname 4:port 5:path + query string
231 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
232 | // 2:host
233 | if (!match) {
234 | return null;
235 | }
236 | if (!match[1]) {
237 | if (/^https?:/i.test(req_url)) {
238 | // The pattern at top could mistakenly parse "http:///" as host="http:" and path=///.
239 | return null;
240 | }
241 | // Scheme is omitted.
242 | if (req_url.lastIndexOf('//', 0) === -1) {
243 | // "//" is omitted.
244 | req_url = '//' + req_url;
245 | }
246 | req_url = (match[4] === '443' ? 'https:' : 'http:') + req_url;
247 | }
248 | var parsed = url.parse(req_url);
249 | if (!parsed.hostname) {
250 | // "http://:1/" and "http:/notenoughslashes" could end up here.
251 | return null;
252 | }
253 | return parsed;
254 | }
255 |
256 | // Request handler factory
257 | function getHandler(options, proxy) {
258 | var corsAnywhere = {
259 | getProxyForUrl: getProxyForUrl, // Function that specifies the proxy to use
260 | maxRedirects: 5, // Maximum number of redirects to be followed.
261 | originBlacklist: [], // Requests from these origins will be blocked.
262 | originWhitelist: [], // If non-empty, requests not from an origin in this list will be blocked.
263 | checkRateLimit: null, // Function that may enforce a rate-limit by returning a non-empty string.
264 | redirectSameOrigin: false, // Redirect the client to the requested URL for same-origin requests.
265 | requireHeader: null, // Require a header to be set?
266 | removeHeaders: [], // Strip these request headers.
267 | setHeaders: {}, // Set these request headers.
268 | corsMaxAge: 0, // If set, an Access-Control-Max-Age header with this value (in seconds) will be added.
269 | helpFile: __dirname + '/help.txt',
270 | };
271 |
272 | Object.keys(corsAnywhere).forEach(function(option) {
273 | if (Object.prototype.hasOwnProperty.call(options, option)) {
274 | corsAnywhere[option] = options[option];
275 | }
276 | });
277 |
278 | // Convert corsAnywhere.requireHeader to an array of lowercase header names, or null.
279 | if (corsAnywhere.requireHeader) {
280 | if (typeof corsAnywhere.requireHeader === 'string') {
281 | corsAnywhere.requireHeader = [corsAnywhere.requireHeader.toLowerCase()];
282 | } else if (!Array.isArray(corsAnywhere.requireHeader) || corsAnywhere.requireHeader.length === 0) {
283 | corsAnywhere.requireHeader = null;
284 | } else {
285 | corsAnywhere.requireHeader = corsAnywhere.requireHeader.map(function(headerName) {
286 | return headerName.toLowerCase();
287 | });
288 | }
289 | }
290 | var hasRequiredHeaders = function(headers) {
291 | return !corsAnywhere.requireHeader || corsAnywhere.requireHeader.some(function(headerName) {
292 | return Object.hasOwnProperty.call(headers, headerName);
293 | });
294 | };
295 |
296 | return function(req, res) {
297 | req.corsAnywhereRequestState = {
298 | getProxyForUrl: corsAnywhere.getProxyForUrl,
299 | maxRedirects: corsAnywhere.maxRedirects,
300 | corsMaxAge: corsAnywhere.corsMaxAge,
301 | };
302 |
303 | var cors_headers = withCORS({}, req);
304 | if (req.method === 'OPTIONS') {
305 | // Pre-flight request. Reply successfully:
306 | res.writeHead(200, cors_headers);
307 | res.end();
308 | return;
309 | }
310 |
311 | var location = parseURL(req.url.slice(1));
312 |
313 | if (!location) {
314 | // Invalid API call. Show how to correctly use the API
315 | showUsage(corsAnywhere.helpFile, cors_headers, res);
316 | return;
317 | }
318 |
319 | if (location.host === 'iscorsneeded') {
320 | // Is CORS needed? This path is provided so that API consumers can test whether it's necessary
321 | // to use CORS. The server's reply is always No, because if they can read it, then CORS headers
322 | // are not necessary.
323 | res.writeHead(200, {'Content-Type': 'text/plain'});
324 | res.end('no');
325 | return;
326 | }
327 |
328 | if (location.port > 65535) {
329 | // Port is higher than 65535
330 | res.writeHead(400, 'Invalid port', cors_headers);
331 | res.end('Port number too large: ' + location.port);
332 | return;
333 | }
334 |
335 | if (!/^\/https?:/.test(req.url) && !isValidHostName(location.hostname)) {
336 | // Don't even try to proxy invalid hosts (such as /favicon.ico, /robots.txt)
337 | res.writeHead(404, 'Invalid host', cors_headers);
338 | res.end('Invalid host: ' + location.hostname);
339 | return;
340 | }
341 |
342 | if (!hasRequiredHeaders(req.headers)) {
343 | res.writeHead(400, 'Header required', cors_headers);
344 | res.end('Missing required request header. Must specify one of: ' + corsAnywhere.requireHeader);
345 | return;
346 | }
347 |
348 | var origin = req.headers.origin || '';
349 | if (corsAnywhere.originBlacklist.indexOf(origin) >= 0) {
350 | res.writeHead(403, 'Forbidden', cors_headers);
351 | res.end('The origin "' + origin + '" was blacklisted by the operator of this proxy.');
352 | return;
353 | }
354 |
355 | if (corsAnywhere.originWhitelist.length && corsAnywhere.originWhitelist.indexOf(origin) === -1) {
356 | res.writeHead(403, 'Forbidden', cors_headers);
357 | res.end('The origin "' + origin + '" was not whitelisted by the operator of this proxy.');
358 | return;
359 | }
360 |
361 | var rateLimitMessage = corsAnywhere.checkRateLimit && corsAnywhere.checkRateLimit(origin);
362 | if (rateLimitMessage) {
363 | res.writeHead(429, 'Too Many Requests', cors_headers);
364 | res.end('The origin "' + origin + '" has sent too many requests.\n' + rateLimitMessage);
365 | return;
366 | }
367 |
368 | if (corsAnywhere.redirectSameOrigin && origin && location.href[origin.length] === '/' &&
369 | location.href.lastIndexOf(origin, 0) === 0) {
370 | // Send a permanent redirect to offload the server. Badly coded clients should not waste our resources.
371 | cors_headers.vary = 'origin';
372 | cors_headers['cache-control'] = 'private';
373 | cors_headers.location = location.href;
374 | res.writeHead(301, 'Please use a direct request', cors_headers);
375 | res.end();
376 | return;
377 | }
378 |
379 | var isRequestedOverHttps = req.connection.encrypted || /^\s*https/.test(req.headers['x-forwarded-proto']);
380 | var proxyBaseUrl = (isRequestedOverHttps ? 'https://' : 'http://') + req.headers.host;
381 |
382 | corsAnywhere.removeHeaders.forEach(function(header) {
383 | delete req.headers[header];
384 | });
385 |
386 | Object.keys(corsAnywhere.setHeaders).forEach(function(header) {
387 | req.headers[header] = corsAnywhere.setHeaders[header];
388 | });
389 |
390 | req.corsAnywhereRequestState.location = location;
391 | req.corsAnywhereRequestState.proxyBaseUrl = proxyBaseUrl;
392 |
393 | proxyRequest(req, res, proxy);
394 | };
395 | }
396 |
397 | // Create server with default and given values
398 | // Creator still needs to call .listen()
399 | exports.createServer = function createServer(options) {
400 | options = options || {};
401 |
402 | // Default options:
403 | var httpProxyOptions = {
404 | xfwd: true, // Append X-Forwarded-* headers
405 | };
406 | // Allow user to override defaults and add own options
407 | if (options.httpProxyOptions) {
408 | Object.keys(options.httpProxyOptions).forEach(function(option) {
409 | httpProxyOptions[option] = options.httpProxyOptions[option];
410 | });
411 | }
412 |
413 | var proxy = httpProxy.createServer(httpProxyOptions);
414 | var requestHandler = getHandler(options, proxy);
415 | var server;
416 | if (options.httpsOptions) {
417 | server = require('https').createServer(options.httpsOptions, requestHandler);
418 | } else {
419 | server = require('http').createServer(requestHandler);
420 | }
421 |
422 | // When the server fails, just show a 404 instead of Internal server error
423 | proxy.on('error', function(err, req, res) {
424 | if (res.headersSent) {
425 | // This could happen when a protocol error occurs when an error occurs
426 | // after the headers have been received (and forwarded). Do not write
427 | // the headers because it would generate an error.
428 | // Prior to Node 13.x, the stream would have ended.
429 | // As of Node 13.x, we must explicitly close it.
430 | if (res.writableEnded === false) {
431 | res.end();
432 | }
433 | return;
434 | }
435 |
436 | // When the error occurs after setting headers but before writing the response,
437 | // then any previously set headers must be removed.
438 | var headerNames = res.getHeaderNames ? res.getHeaderNames() : Object.keys(res._headers || {});
439 | headerNames.forEach(function(name) {
440 | res.removeHeader(name);
441 | });
442 |
443 | res.writeHead(404, {'Access-Control-Allow-Origin': '*'});
444 | res.end('Not found because of proxy error: ' + err);
445 | });
446 |
447 | return server;
448 | };
449 |
--------------------------------------------------------------------------------
/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 fs = require('fs');
9 | var assert = require('assert');
10 |
11 | var helpTextPath = path.join(__dirname, '../lib/help.txt');
12 | var helpText = fs.readFileSync(helpTextPath, {encoding: 'utf8'});
13 |
14 | request.Test.prototype.expectJSON = function(json, done) {
15 | this.expect(function(res) {
16 | // Assume that the response can be parsed as JSON (otherwise it throws).
17 | var actual = JSON.parse(res.text);
18 | assert.deepEqual(actual, json);
19 | });
20 | return done ? this.end(done) : this;
21 | };
22 |
23 | request.Test.prototype.expectNoHeader = function(header, done) {
24 | this.expect(function(res) {
25 | if (header.toLowerCase() in res.headers) {
26 | return 'Unexpected header in response: ' + header;
27 | }
28 | });
29 | return done ? this.end(done) : this;
30 | };
31 |
32 | var cors_anywhere;
33 | var cors_anywhere_port;
34 | function stopServer(done) {
35 | cors_anywhere.close(function() {
36 | done();
37 | });
38 | cors_anywhere = null;
39 | }
40 |
41 | describe('Basic functionality', function() {
42 | before(function() {
43 | cors_anywhere = createServer();
44 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
45 | });
46 | after(stopServer);
47 |
48 | it('GET /', function(done) {
49 | request(cors_anywhere)
50 | .get('/')
51 | .type('text/plain')
52 | .expect('Access-Control-Allow-Origin', '*')
53 | .expect(200, helpText, done);
54 | });
55 |
56 | it('GET /iscorsneeded', function(done) {
57 | request(cors_anywhere)
58 | .get('/iscorsneeded')
59 | .expectNoHeader('access-control-allow-origin', done);
60 | });
61 |
62 | it('GET /example.com:65536', function(done) {
63 | request(cors_anywhere)
64 | .get('/example.com:65536')
65 | .expect('Access-Control-Allow-Origin', '*')
66 | .expect(400, 'Port number too large: 65536', done);
67 | });
68 |
69 | it('GET /favicon.ico', function(done) {
70 | request(cors_anywhere)
71 | .get('/favicon.ico')
72 | .expect('Access-Control-Allow-Origin', '*')
73 | .expect(404, 'Invalid host: favicon.ico', done);
74 | });
75 |
76 | it('GET /robots.txt', function(done) {
77 | request(cors_anywhere)
78 | .get('/robots.txt')
79 | .expect('Access-Control-Allow-Origin', '*')
80 | .expect(404, 'Invalid host: robots.txt', done);
81 | });
82 |
83 | it('GET /http://robots.txt should be proxied', function(done) {
84 | request(cors_anywhere)
85 | .get('/http://robots.txt')
86 | .expect('Access-Control-Allow-Origin', '*')
87 | .expect(200, 'this is http://robots.txt', done);
88 | });
89 |
90 | it('GET /example.com', function(done) {
91 | request(cors_anywhere)
92 | .get('/example.com')
93 | .expect('Access-Control-Allow-Origin', '*')
94 | .expect('x-request-url', 'http://example.com/')
95 | .expect(200, 'Response from example.com', done);
96 | });
97 |
98 | it('GET /example.com:80', function(done) {
99 | request(cors_anywhere)
100 | .get('/example.com:80')
101 | .expect('Access-Control-Allow-Origin', '*')
102 | .expect('x-request-url', 'http://example.com:80/')
103 | .expect(200, 'Response from example.com', done);
104 | });
105 |
106 | it('GET /example.com:443', function(done) {
107 | request(cors_anywhere)
108 | .get('/example.com:443')
109 | .expect('Access-Control-Allow-Origin', '*')
110 | .expect('x-request-url', 'https://example.com:443/')
111 | .expect(200, 'Response from https://example.com', done);
112 | });
113 |
114 | it('GET //example.com', function(done) {
115 | // '/example.com' is an invalid URL.
116 | request(cors_anywhere)
117 | .get('//example.com')
118 | .expect('Access-Control-Allow-Origin', '*')
119 | .expect(200, helpText, done);
120 | });
121 |
122 | it('GET /http://:1234', function(done) {
123 | // 'http://:1234' is an invalid URL.
124 | request(cors_anywhere)
125 | .get('/http://:1234')
126 | .expect('Access-Control-Allow-Origin', '*')
127 | .expect(200, helpText, done);
128 | });
129 |
130 | it('GET /http:///', function(done) {
131 | // 'http://:1234' is an invalid URL.
132 | request(cors_anywhere)
133 | .get('/http:///')
134 | .expect('Access-Control-Allow-Origin', '*')
135 | .expect(200, helpText, done);
136 | });
137 |
138 | it('GET /http:/notenoughslashes', function(done) {
139 | // 'http:/notenoughslashes' is an invalid URL.
140 | request(cors_anywhere)
141 | .get('/http:/notenoughslashes')
142 | .expect('Access-Control-Allow-Origin', '*')
143 | .expect(200, helpText, done);
144 | });
145 |
146 |
147 | it('GET ///example.com', function(done) {
148 | // API base URL (with trailing slash) + '//example.com'
149 | request(cors_anywhere)
150 | .get('///example.com')
151 | .expect('Access-Control-Allow-Origin', '*')
152 | .expect('x-request-url', 'http://example.com/')
153 | .expect(200, 'Response from example.com', done);
154 | });
155 |
156 | it('GET /http://example.com', function(done) {
157 | request(cors_anywhere)
158 | .get('/http://example.com')
159 | .expect('Access-Control-Allow-Origin', '*')
160 | .expect('x-request-url', 'http://example.com/')
161 | .expect(200, 'Response from example.com', done);
162 | });
163 |
164 | it('POST plain text', function(done) {
165 | request(cors_anywhere)
166 | .post('/example.com/echopost')
167 | .send('{"this is a request body & should not be mangled":1.00}')
168 | .expect('Access-Control-Allow-Origin', '*')
169 | .expect('{"this is a request body & should not be mangled":1.00}', done);
170 | });
171 |
172 | it('POST file', function(done) {
173 | request(cors_anywhere)
174 | .post('/example.com/echopost')
175 | .attach('file', path.join(__dirname, 'dummy.txt'))
176 | .expect('Access-Control-Allow-Origin', '*')
177 | .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
178 | });
179 |
180 | it('HEAD with redirect should be followed', function(done) {
181 | // Redirects are automatically followed, because redirects are to be
182 | // followed automatically per specification regardless of the HTTP verb.
183 | request(cors_anywhere)
184 | .head('/example.com/redirect')
185 | .redirects(0)
186 | .expect('Access-Control-Allow-Origin', '*')
187 | .expect('some-header', 'value')
188 | .expect('x-request-url', 'http://example.com/redirect')
189 | .expect('x-cors-redirect-1', '302 http://example.com/redirecttarget')
190 | .expect('x-final-url', 'http://example.com/redirecttarget')
191 | .expect('access-control-expose-headers', /some-header,x-final-url/)
192 | .expectNoHeader('header at redirect')
193 | .expect(200, undefined, done);
194 | });
195 |
196 | it('GET with redirect should be followed', function(done) {
197 | request(cors_anywhere)
198 | .get('/example.com/redirect')
199 | .redirects(0)
200 | .expect('Access-Control-Allow-Origin', '*')
201 | .expect('some-header', 'value')
202 | .expect('x-request-url', 'http://example.com/redirect')
203 | .expect('x-cors-redirect-1', '302 http://example.com/redirecttarget')
204 | .expect('x-final-url', 'http://example.com/redirecttarget')
205 | .expect('access-control-expose-headers', /some-header,x-final-url/)
206 | .expectNoHeader('header at redirect')
207 | .expect(200, 'redirect target', done);
208 | });
209 |
210 | it('GET with redirect loop should interrupt', function(done) {
211 | request(cors_anywhere)
212 | .get('/example.com/redirectloop')
213 | .redirects(0)
214 | .expect('Access-Control-Allow-Origin', '*')
215 | .expect('x-request-url', 'http://example.com/redirectloop')
216 | .expect('x-cors-redirect-1', '302 http://example.com/redirectloop')
217 | .expect('x-cors-redirect-2', '302 http://example.com/redirectloop')
218 | .expect('x-cors-redirect-3', '302 http://example.com/redirectloop')
219 | .expect('x-cors-redirect-4', '302 http://example.com/redirectloop')
220 | .expect('x-cors-redirect-5', '302 http://example.com/redirectloop')
221 | .expect('Location', /^http:\/\/127.0.0.1:\d+\/http:\/\/example.com\/redirectloop$/)
222 | .expect(302, 'redirecting ad infinitum...', done);
223 | });
224 |
225 | it('POST with 302 redirect should be followed', function(done) {
226 | request(cors_anywhere)
227 | .post('/example.com/redirectpost')
228 | .redirects(0)
229 | .expect('Access-Control-Allow-Origin', '*')
230 | .expect('x-request-url', 'http://example.com/redirectpost')
231 | .expect('x-cors-redirect-1', '302 http://example.com/redirectposttarget')
232 | .expect('x-final-url', 'http://example.com/redirectposttarget')
233 | .expect('access-control-expose-headers', /x-final-url/)
234 | .expect(200, 'post target', done);
235 | });
236 |
237 | it('GET with 302 redirect without Location header should not be followed', function(done) {
238 | // There is nothing to follow, so let the browser decide what to do with it.
239 | request(cors_anywhere)
240 | .get('/example.com/redirectwithoutlocation')
241 | .redirects(0)
242 | .expect('Access-Control-Allow-Origin', '*')
243 | .expect('x-request-url', 'http://example.com/redirectwithoutlocation')
244 | .expect('x-final-url', 'http://example.com/redirectwithoutlocation')
245 | .expect('access-control-expose-headers', /x-final-url/)
246 | .expect(302, 'maybe found', done);
247 | });
248 |
249 | it('GET with 302 redirect to an invalid Location should not be followed', function(done) {
250 | // There is nothing to follow, so let the browser decide what to do with it.
251 | request(cors_anywhere)
252 | .get('/example.com/redirectinvalidlocation')
253 | .redirects(0)
254 | .expect('Access-Control-Allow-Origin', '*')
255 | .expect('x-request-url', 'http://example.com/redirectinvalidlocation')
256 | .expect('x-final-url', 'http://example.com/redirectinvalidlocation')
257 | .expect('access-control-expose-headers', /x-final-url/)
258 | .expect('Location', 'http:///')
259 | .expect(302, 'redirecting to junk...', done);
260 | });
261 |
262 | it('POST with 307 redirect should not be handled', function(done) {
263 | // Because of implementation difficulties (having to keep the request body
264 | // in memory), handling HTTP 307/308 redirects is deferred to the requestor.
265 | request(cors_anywhere)
266 | .post('/example.com/redirect307')
267 | .redirects(0)
268 | .expect('Access-Control-Allow-Origin', '*')
269 | .expect('x-request-url', 'http://example.com/redirect307')
270 | .expect('Location', /^http:\/\/127.0.0.1:\d+\/http:\/\/example.com\/redirectposttarget$/)
271 | .expect('x-final-url', 'http://example.com/redirect307')
272 | .expect('access-control-expose-headers', /x-final-url/)
273 | .expect(307, 'redirecting...', done);
274 | });
275 |
276 | it('OPTIONS /', function(done) {
277 | request(cors_anywhere)
278 | .options('/')
279 | .expect('Access-Control-Allow-Origin', '*')
280 | .expect(200, '', done);
281 | });
282 |
283 | it('OPTIONS / with Access-Control-Request-Method / -Headers', function(done) {
284 | request(cors_anywhere)
285 | .options('/')
286 | .set('Access-Control-Request-Method', 'DELETE')
287 | .set('Access-Control-Request-Headers', 'X-Tralala')
288 | .expect('Access-Control-Allow-Origin', '*')
289 | .expect('Access-Control-Allow-Methods', 'DELETE')
290 | .expect('Access-Control-Allow-Headers', 'X-Tralala')
291 | .expect(200, '', done);
292 | });
293 |
294 | it('OPTIONS //bogus', function(done) {
295 | // The preflight request always succeeds, regardless of whether the request
296 | // is valid.
297 | request(cors_anywhere)
298 | .options('//bogus')
299 | .expect('Access-Control-Allow-Origin', '*')
300 | .expect(200, '', done);
301 | });
302 |
303 | it('X-Forwarded-* headers', function(done) {
304 | request(cors_anywhere)
305 | .get('/example.com/echoheaders')
306 | .set('test-include-xfwd', '')
307 | .expect('Access-Control-Allow-Origin', '*')
308 | .expectJSON({
309 | host: 'example.com',
310 | 'x-forwarded-port': String(cors_anywhere_port),
311 | 'x-forwarded-proto': 'http',
312 | }, done);
313 | });
314 |
315 | it('X-Forwarded-* headers (non-standard port)', function(done) {
316 | request(cors_anywhere)
317 | .get('/example.com:1337/echoheaders')
318 | .set('test-include-xfwd', '')
319 | .expect('Access-Control-Allow-Origin', '*')
320 | .expectJSON({
321 | host: 'example.com:1337',
322 | 'x-forwarded-port': String(cors_anywhere_port),
323 | 'x-forwarded-proto': 'http',
324 | }, done);
325 | });
326 |
327 | it('X-Forwarded-* headers (https)', function(done) {
328 | request(cors_anywhere)
329 | .get('/https://example.com/echoheaders')
330 | .set('test-include-xfwd', '')
331 | .expect('Access-Control-Allow-Origin', '*')
332 | .expectJSON({
333 | host: 'example.com',
334 | 'x-forwarded-port': String(cors_anywhere_port),
335 | 'x-forwarded-proto': 'http',
336 | }, done);
337 | });
338 |
339 | it('Ignore cookies', function(done) {
340 | request(cors_anywhere)
341 | .get('/example.com/setcookie')
342 | .expect('Access-Control-Allow-Origin', '*')
343 | .expect('Set-Cookie3', 'z')
344 | .expectNoHeader('set-cookie')
345 | .expectNoHeader('set-cookie2', done);
346 | });
347 | });
348 |
349 | describe('Proxy errors', function() {
350 | before(function() {
351 | cors_anywhere = createServer();
352 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
353 | });
354 | after(stopServer);
355 |
356 | var bad_http_server;
357 | var bad_http_server_url;
358 | before(function() {
359 | bad_http_server = http.createServer(function(req, res) {
360 | res.writeHead(418, {
361 | 'Content-Length': 'Not a digit',
362 | });
363 | res.end('This response has an invalid Content-Length header.');
364 | });
365 | bad_http_server_url = 'http://127.0.0.1:' + bad_http_server.listen(0).address().port;
366 | });
367 | after(function(done) {
368 | bad_http_server.close(function() {
369 | done();
370 | });
371 | });
372 |
373 | var bad_status_http_server;
374 | var bad_status_http_server_url;
375 | before(function() {
376 | bad_status_http_server = require('net').createServer(function(socket) {
377 | socket.setEncoding('utf-8');
378 | socket.on('data', function(data) {
379 | if (data.indexOf('\r\n') >= 0) {
380 | // Assume end of headers.
381 | socket.write('HTTP/1.0 0\r\n');
382 | socket.write('Content-Length: 0\r\n');
383 | socket.end('\r\n');
384 | }
385 | });
386 | });
387 | bad_status_http_server_url = 'http://127.0.0.1:' + bad_status_http_server.listen(0).address().port;
388 | });
389 | after(function(done) {
390 | bad_status_http_server.close(function() {
391 | done();
392 | });
393 | });
394 |
395 | var bad_tcp_server;
396 | var bad_tcp_server_url;
397 | before(function() {
398 | bad_tcp_server = require('net').createServer(function(socket) {
399 | socket.setEncoding('utf-8');
400 | socket.on('data', function(data) {
401 | if (data.indexOf('\r\n') >= 0) {
402 | // Assume end of headers.
403 | socket.write('HTTP/1.1 418 OK\r\n');
404 | socket.write('Transfer-Encoding: chunked\r\n');
405 | socket.write('\r\n');
406 | socket.end('JK I lied, this is NOT a chunked response!');
407 | }
408 | });
409 | });
410 | bad_tcp_server_url = 'http://127.0.0.1:' + bad_tcp_server.listen(0).address().port;
411 | });
412 | after(function(done) {
413 | bad_tcp_server.close(function() {
414 | done();
415 | });
416 | });
417 |
418 | it('Proxy error', function(done) {
419 | request(cors_anywhere)
420 | .get('/example.com/proxyerror')
421 | .expect('Access-Control-Allow-Origin', '*')
422 | .expect(404, 'Not found because of proxy error: Error: throw node', done);
423 | });
424 |
425 | it('Content-Length mismatch', function(done) {
426 | var errorMessage = 'Error: Parse Error: Invalid character in Content-Length';
427 | // <13.0.0: https://github.com/nodejs/node/commit/ba565a37349e81c9d2402b0c8ef05ab39dca8968
428 | // <12.7.0: https://github.com/nodejs/node/pull/28817
429 | var nodev = process.versions.node.split('.').map(function(v) { return parseInt(v); });
430 | if (nodev[0] < 12 ||
431 | nodev[0] === 12 && nodev[1] < 7) {
432 | errorMessage = 'Error: Parse Error';
433 | }
434 | request(cors_anywhere)
435 | .get('/' + bad_http_server_url)
436 | .expect('Access-Control-Allow-Origin', '*')
437 | .expect(404, 'Not found because of proxy error: ' + errorMessage, done);
438 | });
439 |
440 | it('Invalid HTTP status code', function(done) {
441 | // Strict HTTP status validation was introduced in Node 4.5.5+, 5.11.0+.
442 | // https://github.com/nodejs/node/pull/6291
443 | var nodev = process.versions.node.split('.').map(function(v) { return parseInt(v); });
444 | if (nodev[0] < 4 ||
445 | nodev[0] === 4 && nodev[1] < 5 ||
446 | nodev[0] === 4 && nodev[1] === 5 && nodev[2] < 5 ||
447 | nodev[0] === 5 && nodev[1] < 11) {
448 | this.skip();
449 | }
450 |
451 | var errorMessage = 'RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code: 0';
452 | if (parseInt(process.versions.node, 10) < 9) {
453 | errorMessage = 'RangeError: Invalid status code: 0';
454 | }
455 | request(cors_anywhere)
456 | .get('/' + bad_status_http_server_url)
457 | .expect('Access-Control-Allow-Origin', '*')
458 | .expect(404, 'Not found because of proxy error: ' + errorMessage, done);
459 | });
460 |
461 | it('Content-Encoding invalid body', function(done) {
462 | // The HTTP status can't be changed because the headers have already been
463 | // sent.
464 | request(cors_anywhere)
465 | .get('/' + bad_tcp_server_url)
466 | .expect('Access-Control-Allow-Origin', '*')
467 | .expect(418, '', done);
468 | });
469 |
470 | it('Invalid header values', function(done) {
471 | if (parseInt(process.versions.node, 10) < 6) {
472 | // >=6.0.0: https://github.com/nodejs/node/commit/7bef1b790727430cb82bf8be80cfe058480de100
473 | this.skip();
474 | }
475 | // >=9.0.0: https://github.com/nodejs/node/commit/11a2ca29babcb35132e7d93244b69c544d52dfe4
476 | var errorMessage = 'TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["headername"]';
477 | if (parseInt(process.versions.node, 10) < 9) {
478 | // >=6.0.0, <9.0.0: https://github.com/nodejs/node/commit/7bef1b790727430cb82bf8be80cfe058480de100
479 | errorMessage = 'TypeError: The header content contains invalid characters';
480 | }
481 | stopServer(function() {
482 | cors_anywhere = createServer({
483 | // Setting an invalid header below in request(...).set(...) would trigger
484 | // a header validation error in superagent. So we use setHeaders to test
485 | // the attempt to proxy a request with invalid request headers.
486 | setHeaders: {headername: 'invalid\x01value'},
487 | });
488 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
489 | request(cors_anywhere)
490 | .get('/' + bad_tcp_server_url) // Any URL that isn't intercepted by Nock would do.
491 | .expect('Access-Control-Allow-Origin', '*')
492 | .expect(404, 'Not found because of proxy error: ' + errorMessage, done);
493 | });
494 | });
495 | });
496 |
497 | describe('server on https', function() {
498 | var NODE_TLS_REJECT_UNAUTHORIZED;
499 | before(function() {
500 | cors_anywhere = createServer({
501 | httpsOptions: {
502 | key: fs.readFileSync(path.join(__dirname, 'key.pem')),
503 | cert: fs.readFileSync(path.join(__dirname, 'cert.pem')),
504 | },
505 | });
506 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
507 | // Disable certificate validation in case the certificate expires.
508 | NODE_TLS_REJECT_UNAUTHORIZED = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
509 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
510 | });
511 | after(function(done) {
512 | if (NODE_TLS_REJECT_UNAUTHORIZED === undefined) {
513 | delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
514 | } else {
515 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = NODE_TLS_REJECT_UNAUTHORIZED;
516 | }
517 | stopServer(done);
518 | });
519 |
520 | it('X-Forwarded-* headers (http)', function(done) {
521 | request(cors_anywhere)
522 | .get('/example.com/echoheaders')
523 | .set('test-include-xfwd', '')
524 | .expect('Access-Control-Allow-Origin', '*')
525 | .expectJSON({
526 | host: 'example.com',
527 | 'x-forwarded-port': String(cors_anywhere_port),
528 | 'x-forwarded-proto': 'https',
529 | }, done);
530 | });
531 |
532 | it('X-Forwarded-* headers (https)', function(done) {
533 | request(cors_anywhere)
534 | .get('/https://example.com/echoheaders')
535 | .set('test-include-xfwd', '')
536 | .expect('Access-Control-Allow-Origin', '*')
537 | .expectJSON({
538 | host: 'example.com',
539 | 'x-forwarded-port': String(cors_anywhere_port),
540 | 'x-forwarded-proto': 'https',
541 | }, done);
542 | });
543 |
544 | it('X-Forwarded-* headers (https, non-standard port)', function(done) {
545 | request(cors_anywhere)
546 | .get('/https://example.com:1337/echoheaders')
547 | .set('test-include-xfwd', '')
548 | .expect('Access-Control-Allow-Origin', '*')
549 | .expectJSON({
550 | host: 'example.com:1337',
551 | 'x-forwarded-port': String(cors_anywhere_port),
552 | 'x-forwarded-proto': 'https',
553 | }, done);
554 | });
555 | });
556 |
557 | describe('originBlacklist', function() {
558 | before(function() {
559 | cors_anywhere = createServer({
560 | originBlacklist: ['http://denied.origin.test'],
561 | });
562 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
563 | });
564 | after(stopServer);
565 |
566 | it('GET /example.com with denied origin', function(done) {
567 | request(cors_anywhere)
568 | .get('/example.com/')
569 | .set('Origin', 'http://denied.origin.test')
570 | .expect('Access-Control-Allow-Origin', '*')
571 | .expect(403, done);
572 | });
573 |
574 | it('GET /example.com without denied origin', function(done) {
575 | request(cors_anywhere)
576 | .get('/example.com/')
577 | .set('Origin', 'https://denied.origin.test') // Note: different scheme!
578 | .expect('Access-Control-Allow-Origin', '*')
579 | .expect(200, done);
580 | });
581 |
582 | it('GET /example.com without origin', function(done) {
583 | request(cors_anywhere)
584 | .get('/example.com/')
585 | .expect('Access-Control-Allow-Origin', '*')
586 | .expect(200, done);
587 | });
588 | });
589 |
590 | describe('originWhitelist', function() {
591 | before(function() {
592 | cors_anywhere = createServer({
593 | originWhitelist: ['https://permitted.origin.test'],
594 | });
595 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
596 | });
597 | after(stopServer);
598 |
599 | it('GET /example.com with permitted origin', function(done) {
600 | request(cors_anywhere)
601 | .get('/example.com/')
602 | .set('Origin', 'https://permitted.origin.test')
603 | .expect('Access-Control-Allow-Origin', '*')
604 | .expect(200, done);
605 | });
606 |
607 | it('GET /example.com without permitted origin', function(done) {
608 | request(cors_anywhere)
609 | .get('/example.com/')
610 | .set('Origin', 'http://permitted.origin.test') // Note: different scheme!
611 | .expect('Access-Control-Allow-Origin', '*')
612 | .expect(403, done);
613 | });
614 |
615 | it('GET /example.com without origin', function(done) {
616 | request(cors_anywhere)
617 | .get('/example.com/')
618 | .expect('Access-Control-Allow-Origin', '*')
619 | .expect(403, done);
620 | });
621 | });
622 |
623 | describe('checkRateLimit', function() {
624 | afterEach(stopServer);
625 |
626 | it('GET /example.com without rate-limit', function(done) {
627 | cors_anywhere = createServer({
628 | checkRateLimit: function() {},
629 | });
630 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
631 | request(cors_anywhere)
632 | .get('/example.com/')
633 | .expect('Access-Control-Allow-Origin', '*')
634 | .expect(200, done);
635 | });
636 |
637 | it('GET /example.com with rate-limit', function(done) {
638 | cors_anywhere = createServer({
639 | checkRateLimit: function(origin) {
640 | // Non-empty value. Let's return the origin parameter so that we also verify that the
641 | // the parameter is really the origin.
642 | return '[' + origin + ']';
643 | },
644 | });
645 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
646 | request(cors_anywhere)
647 | .get('/example.com/')
648 | .set('Origin', 'http://example.net:1234')
649 | .expect('Access-Control-Allow-Origin', '*')
650 | .expect(429, done,
651 | 'The origin "http://example.net" has sent too many requests.\n[http://example.com:1234]');
652 | });
653 | });
654 |
655 | describe('redirectSameOrigin', function() {
656 | before(function() {
657 | cors_anywhere = createServer({
658 | redirectSameOrigin: true,
659 | });
660 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
661 | });
662 | after(stopServer);
663 |
664 | it('GET /example.com with Origin: http://example.com', function(done) {
665 | request(cors_anywhere)
666 | .get('/example.com/')
667 | .set('Origin', 'http://example.com')
668 | .expect('Access-Control-Allow-Origin', '*')
669 | .expect('Cache-Control', 'private')
670 | .expect('Vary', 'origin')
671 | .expect('Location', 'http://example.com/')
672 | .expect(301, done);
673 | });
674 |
675 | it('GET /example.com with Origin: https://example.com', function(done) {
676 | // Not same-origin because of different schemes.
677 | request(cors_anywhere)
678 | .get('/example.com/')
679 | .set('Origin', 'https://example.com')
680 | .expect('Access-Control-Allow-Origin', '*')
681 | .expect(200, 'Response from example.com', done);
682 | });
683 |
684 | it('GET /example.com with Origin: http://example.com:1234', function(done) {
685 | // Not same-origin because of different ports.
686 | request(cors_anywhere)
687 | .get('/example.com/')
688 | .set('Origin', 'http://example.com:1234')
689 | .expect('Access-Control-Allow-Origin', '*')
690 | .expect(200, 'Response from example.com', done);
691 | });
692 |
693 | it('GET /example.com:1234 with Origin: http://example.com', function(done) {
694 | // Not same-origin because of different ports.
695 | request(cors_anywhere)
696 | .get('/example.com:1234/')
697 | .set('Origin', 'http://example.com')
698 | .expect('Access-Control-Allow-Origin', '*')
699 | .expect(200, 'Response from example.com:1234', done);
700 | });
701 |
702 | it('GET /example.com with Origin: http://example.com.test', function(done) {
703 | // Not same-origin because of different host names.
704 | request(cors_anywhere)
705 | .get('/example.com/')
706 | .set('Origin', 'http://example.com.test')
707 | .expect('Access-Control-Allow-Origin', '*')
708 | .expect(200, 'Response from example.com', done);
709 | });
710 |
711 | it('GET /example.com.com with Origin: http://example.com', function(done) {
712 | // Not same-origin because of different host names.
713 | request(cors_anywhere)
714 | .get('/example.com.com/')
715 | .set('Origin', 'http://example.com')
716 | .expect('Access-Control-Allow-Origin', '*')
717 | .expect(200, 'Response from example.com.com', done);
718 | });
719 |
720 | it('GET /prefix.example.com with Origin: http://example.com', function(done) {
721 | // Not same-origin because of different host names.
722 | request(cors_anywhere)
723 | .get('/prefix.example.com/')
724 | .set('Origin', 'http://example.com')
725 | .expect('Access-Control-Allow-Origin', '*')
726 | .expect(200, 'Response from prefix.example.com', done);
727 | });
728 | });
729 |
730 | describe('requireHeader', function() {
731 | before(function() {
732 | cors_anywhere = createServer({
733 | requireHeader: ['origin', 'x-requested-with'],
734 | });
735 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
736 | });
737 | after(stopServer);
738 |
739 | it('GET /example.com without header', function(done) {
740 | request(cors_anywhere)
741 | .get('/example.com/')
742 | .expect('Access-Control-Allow-Origin', '*')
743 | .expect(400, 'Missing required request header. Must specify one of: origin,x-requested-with', done);
744 | });
745 |
746 | it('GET /example.com with X-Requested-With header', function(done) {
747 | request(cors_anywhere)
748 | .get('/example.com/')
749 | .set('X-Requested-With', '')
750 | .expect('Access-Control-Allow-Origin', '*')
751 | .expect(200, done);
752 | });
753 |
754 | it('GET /example.com with Origin header', function(done) {
755 | request(cors_anywhere)
756 | .get('/example.com/')
757 | .set('Origin', 'null')
758 | .expect('Access-Control-Allow-Origin', '*')
759 | .expect(200, done);
760 | });
761 |
762 | it('GET /example.com without header (requireHeader as string)', function(done) {
763 | stopServer(function() {
764 | cors_anywhere = createServer({
765 | requireHeader: 'origin',
766 | });
767 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
768 | request(cors_anywhere)
769 | .get('/example.com/')
770 | .expect('Access-Control-Allow-Origin', '*')
771 | .expect(400, 'Missing required request header. Must specify one of: origin', done);
772 | });
773 | });
774 |
775 | it('GET /example.com with header (requireHeader as string)', function(done) {
776 | stopServer(function() {
777 | cors_anywhere = createServer({
778 | requireHeader: 'origin',
779 | });
780 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
781 | request(cors_anywhere)
782 | .get('/example.com/')
783 | .set('Origin', 'null')
784 | .expect('Access-Control-Allow-Origin', '*')
785 | .expect(200, 'Response from example.com', done);
786 | });
787 | });
788 |
789 | it('GET /example.com without header (requireHeader as string, uppercase)', function(done) {
790 | stopServer(function() {
791 | cors_anywhere = createServer({
792 | requireHeader: 'ORIGIN',
793 | });
794 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
795 | request(cors_anywhere)
796 | .get('/example.com/')
797 | .expect('Access-Control-Allow-Origin', '*')
798 | .expect(400, 'Missing required request header. Must specify one of: origin', done);
799 | });
800 | });
801 |
802 | it('GET /example.com with header (requireHeader as string, uppercase)', function(done) {
803 | stopServer(function() {
804 | cors_anywhere = createServer({
805 | requireHeader: 'ORIGIN',
806 | });
807 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
808 | request(cors_anywhere)
809 | .get('/example.com/')
810 | .set('Origin', 'null')
811 | .expect('Access-Control-Allow-Origin', '*')
812 | .expect(200, 'Response from example.com', done);
813 | });
814 | });
815 |
816 | it('GET /example.com (requireHeader is an empty array)', function(done) {
817 | stopServer(function() {
818 | cors_anywhere = createServer({
819 | requireHeader: [],
820 | });
821 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
822 | request(cors_anywhere)
823 | .get('/example.com/')
824 | .expect('Access-Control-Allow-Origin', '*')
825 | .expect(200, 'Response from example.com', done);
826 | });
827 | });
828 | });
829 |
830 | describe('removeHeaders', function() {
831 | before(function() {
832 | cors_anywhere = createServer({
833 | removeHeaders: ['cookie', 'cookie2'],
834 | });
835 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
836 | });
837 | after(stopServer);
838 |
839 | it('GET /example.com with request cookie', function(done) {
840 | request(cors_anywhere)
841 | .get('/example.com/echoheaders')
842 | .set('cookie', 'a')
843 | .set('cookie2', 'b')
844 | .expect('Access-Control-Allow-Origin', '*')
845 | .expectJSON({
846 | host: 'example.com',
847 | }, done);
848 | });
849 |
850 | it('GET /example.com with unknown header', function(done) {
851 | request(cors_anywhere)
852 | .get('/example.com/echoheaders')
853 | .set('cookie', 'a')
854 | .set('cookie2', 'b')
855 | .set('cookie3', 'c')
856 | .expect('Access-Control-Allow-Origin', '*')
857 | .expectJSON({
858 | host: 'example.com',
859 | cookie3: 'c',
860 | }, done);
861 | });
862 | });
863 |
864 | describe('setHeaders', function() {
865 | before(function() {
866 | cors_anywhere = createServer({
867 | setHeaders: {'x-powered-by': 'CORS Anywhere'},
868 | });
869 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
870 | });
871 | after(stopServer);
872 |
873 | it('GET /example.com', function(done) {
874 | request(cors_anywhere)
875 | .get('/example.com/echoheaders')
876 | .expect('Access-Control-Allow-Origin', '*')
877 | .expectJSON({
878 | host: 'example.com',
879 | 'x-powered-by': 'CORS Anywhere',
880 | }, done);
881 | });
882 |
883 | it('GET /example.com should replace header', function(done) {
884 | request(cors_anywhere)
885 | .get('/example.com/echoheaders')
886 | .set('x-powered-by', 'should be replaced')
887 | .expect('Access-Control-Allow-Origin', '*')
888 | .expectJSON({
889 | host: 'example.com',
890 | 'x-powered-by': 'CORS Anywhere',
891 | }, done);
892 | });
893 | });
894 |
895 | describe('setHeaders + removeHeaders', function() {
896 | before(function() {
897 | // setHeaders takes precedence over removeHeaders
898 | cors_anywhere = createServer({
899 | removeHeaders: ['x-powered-by'],
900 | setHeaders: {'x-powered-by': 'CORS Anywhere'},
901 | });
902 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
903 | });
904 | after(stopServer);
905 |
906 | it('GET /example.com', function(done) {
907 | request(cors_anywhere)
908 | .get('/example.com/echoheaders')
909 | .expect('Access-Control-Allow-Origin', '*')
910 | .expectJSON({
911 | host: 'example.com',
912 | 'x-powered-by': 'CORS Anywhere',
913 | }, done);
914 | });
915 |
916 | it('GET /example.com should replace header', function(done) {
917 | request(cors_anywhere)
918 | .get('/example.com/echoheaders')
919 | .set('x-powered-by', 'should be replaced')
920 | .expect('Access-Control-Allow-Origin', '*')
921 | .expectJSON({
922 | host: 'example.com',
923 | 'x-powered-by': 'CORS Anywhere',
924 | }, done);
925 | });
926 | });
927 |
928 | describe('Access-Control-Max-Age set', function() {
929 | before(function() {
930 | cors_anywhere = createServer({
931 | corsMaxAge: 600,
932 | });
933 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
934 | });
935 | after(stopServer);
936 |
937 | it('GET /', function(done) {
938 | request(cors_anywhere)
939 | .get('/')
940 | .type('text/plain')
941 | .expect('Access-Control-Allow-Origin', '*')
942 | .expect('Access-Control-Max-Age', '600')
943 | .expect(200, helpText, done);
944 | });
945 |
946 | it('GET /example.com', function(done) {
947 | request(cors_anywhere)
948 | .get('/example.com')
949 | .expect('Access-Control-Allow-Origin', '*')
950 | .expect('Access-Control-Max-Age', '600')
951 | .expect(200, 'Response from example.com', done);
952 | });
953 | });
954 |
955 | describe('Access-Control-Max-Age not set', function() {
956 | before(function() {
957 | cors_anywhere = createServer();
958 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
959 | });
960 | after(stopServer);
961 |
962 | it('GET /', function(done) {
963 | request(cors_anywhere)
964 | .get('/')
965 | .type('text/plain')
966 | .expect('Access-Control-Allow-Origin', '*')
967 | .expectNoHeader('Access-Control-Max-Age')
968 | .expect(200, helpText, done);
969 | });
970 |
971 | it('GET /example.com', function(done) {
972 | request(cors_anywhere)
973 | .get('/example.com')
974 | .expect('Access-Control-Allow-Origin', '*')
975 | .expectNoHeader('Access-Control-Max-Age')
976 | .expect(200, 'Response from example.com', done);
977 | });
978 | });
979 |
980 | describe('httpProxyOptions.xfwd=false', function() {
981 | before(function() {
982 | cors_anywhere = createServer({
983 | httpProxyOptions: {
984 | xfwd: false,
985 | },
986 | });
987 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
988 | });
989 | after(stopServer);
990 |
991 | it('X-Forwarded-* headers should not be set', function(done) {
992 | request(cors_anywhere)
993 | .get('/example.com/echoheaders')
994 | .set('test-include-xfwd', '')
995 | .expect('Access-Control-Allow-Origin', '*')
996 | .expectJSON({
997 | host: 'example.com',
998 | }, done);
999 | });
1000 | });
1001 |
1002 | describe('httpProxyOptions.getProxyForUrl', function() {
1003 | var proxy_server;
1004 | var proxy_url;
1005 | before(function() {
1006 | // Using a real server instead of a mock because Nock doesn't can't mock proxies.
1007 | proxy_server = http.createServer(function(req, res) {
1008 | res.end(req.method + ' ' + req.url + ' Host=' + req.headers.host);
1009 | });
1010 | proxy_url = 'http://127.0.0.1:' + proxy_server.listen(0).address().port;
1011 |
1012 | cors_anywhere = createServer({
1013 | httpProxyOptions: {
1014 | xfwd: false,
1015 | },
1016 | });
1017 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
1018 | });
1019 | afterEach(function() {
1020 | // Assuming that they were not set before.
1021 | delete process.env.https_proxy;
1022 | delete process.env.http_proxy;
1023 | delete process.env.no_proxy;
1024 | });
1025 | after(function(done) {
1026 | proxy_server.close(function() {
1027 | done();
1028 | });
1029 | });
1030 | after(stopServer);
1031 |
1032 | it('http_proxy should be respected for matching domains', function(done) {
1033 | process.env.http_proxy = proxy_url;
1034 |
1035 | request(cors_anywhere)
1036 | .get('/http://example.com')
1037 | .expect('Access-Control-Allow-Origin', '*')
1038 | .expect(200, 'GET http://example.com/ Host=example.com', done);
1039 | });
1040 |
1041 | it('http_proxy should be ignored for http URLs', function(done) {
1042 | process.env.http_proxy = proxy_url;
1043 | request(cors_anywhere)
1044 | .get('/https://example.com')
1045 | .expect('Access-Control-Allow-Origin', '*')
1046 | .expect(200, 'Response from https://example.com', done);
1047 | });
1048 |
1049 | it('https_proxy should be respected for matching domains', function(done) {
1050 | process.env.https_proxy = proxy_url;
1051 |
1052 | request(cors_anywhere)
1053 | .get('/https://example.com')
1054 | .expect('Access-Control-Allow-Origin', '*')
1055 | .expect(200, 'GET https://example.com/ Host=example.com', done);
1056 | });
1057 |
1058 | it('https_proxy should be ignored for http URLs', function(done) {
1059 | process.env.https_proxy = proxy_url;
1060 | request(cors_anywhere)
1061 | .get('/http://example.com')
1062 | .expect('Access-Control-Allow-Origin', '*')
1063 | .expect(200, 'Response from example.com', done);
1064 | });
1065 |
1066 | it('https_proxy + no_proxy should not intercept requests in no_proxy', function(done) {
1067 | process.env.https_proxy = proxy_url;
1068 | process.env.no_proxy = 'example.com:443';
1069 | request(cors_anywhere)
1070 | .get('/https://example.com')
1071 | .expect('Access-Control-Allow-Origin', '*')
1072 | .expect(200, 'Response from https://example.com', done);
1073 | });
1074 | });
1075 |
1076 | describe('helpFile', function() {
1077 |
1078 | afterEach(stopServer);
1079 |
1080 | it('GET / with custom text helpFile', function(done) {
1081 | var customHelpTextPath = path.join(__dirname, './customHelp.txt');
1082 | var customHelpText = fs.readFileSync(customHelpTextPath, {encoding: 'utf8'});
1083 |
1084 | cors_anywhere = createServer({
1085 | helpFile: customHelpTextPath,
1086 | });
1087 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
1088 |
1089 | request(cors_anywhere)
1090 | .get('/')
1091 | .type('text/plain')
1092 | .expect('Access-Control-Allow-Origin', '*')
1093 | .expect(200, customHelpText, done);
1094 | });
1095 |
1096 | it('GET / with custom HTML helpFile', function(done) {
1097 | var customHelpTextPath = path.join(__dirname, './customHelp.html');
1098 | var customHelpText = fs.readFileSync(customHelpTextPath, {encoding: 'utf8'});
1099 |
1100 | cors_anywhere = createServer({
1101 | helpFile: customHelpTextPath,
1102 | });
1103 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
1104 |
1105 | request(cors_anywhere)
1106 | .get('/')
1107 | .type('text/html')
1108 | .expect('Access-Control-Allow-Origin', '*')
1109 | .expect(200, customHelpText, done);
1110 | });
1111 |
1112 | it('GET / with non-existent help file', function(done) {
1113 | var customHelpTextPath = path.join(__dirname, 'Some non-existing file.');
1114 |
1115 | cors_anywhere = createServer({
1116 | helpFile: customHelpTextPath,
1117 | });
1118 | cors_anywhere_port = cors_anywhere.listen(0).address().port;
1119 |
1120 | request(cors_anywhere)
1121 | .get('/')
1122 | .type('text/plain')
1123 | .expect('Access-Control-Allow-Origin', '*')
1124 | .expect(500, '', done);
1125 | });
1126 | });
1127 |
--------------------------------------------------------------------------------