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