From 918e80eaac2331c908886d330f6e0930afccec43 Mon Sep 17 00:00:00 2001 From: zino Date: Wed, 20 Jan 2021 13:14:30 +0100 Subject: [PATCH] added static cors-anywhere --- cors-anywhere/.eslintignore | 1 + cors-anywhere/.eslintrc | 28 + cors-anywhere/.gitignore | 6 + cors-anywhere/.travis.yml | 12 + cors-anywhere/Procfile | 1 + cors-anywhere/README.md | 181 ++ cors-anywhere/demo.html | 107 + cors-anywhere/lib/cors-anywhere.js | 431 ++++ cors-anywhere/lib/help.txt | 30 + cors-anywhere/lib/rate-limit.js | 74 + cors-anywhere/lib/regexp-top-level-domain.js | 8 + cors-anywhere/package-lock.json | 2024 ++++++++++++++++++ cors-anywhere/package.json | 51 + cors-anywhere/server.js | 66 + cors-anywhere/test/cert.pem | 12 + cors-anywhere/test/child.js | 64 + cors-anywhere/test/customHelp.html | 7 + cors-anywhere/test/customHelp.txt | 1 + cors-anywhere/test/dummy.txt | 1 + cors-anywhere/test/key.pem | 15 + cors-anywhere/test/setup.js | 152 ++ cors-anywhere/test/test-examples.js | 133 ++ cors-anywhere/test/test-memory.js | 111 + cors-anywhere/test/test-ratelimit.js | 231 ++ cors-anywhere/test/test.js | 1080 ++++++++++ 25 files changed, 4827 insertions(+) create mode 100644 cors-anywhere/.eslintignore create mode 100644 cors-anywhere/.eslintrc create mode 100644 cors-anywhere/.gitignore create mode 100644 cors-anywhere/.travis.yml create mode 100644 cors-anywhere/Procfile create mode 100644 cors-anywhere/README.md create mode 100644 cors-anywhere/demo.html create mode 100644 cors-anywhere/lib/cors-anywhere.js create mode 100644 cors-anywhere/lib/help.txt create mode 100644 cors-anywhere/lib/rate-limit.js create mode 100644 cors-anywhere/lib/regexp-top-level-domain.js create mode 100644 cors-anywhere/package-lock.json create mode 100644 cors-anywhere/package.json create mode 100644 cors-anywhere/server.js create mode 100644 cors-anywhere/test/cert.pem create mode 100644 cors-anywhere/test/child.js create mode 100644 cors-anywhere/test/customHelp.html create mode 100644 cors-anywhere/test/customHelp.txt create mode 100644 cors-anywhere/test/dummy.txt create mode 100644 cors-anywhere/test/key.pem create mode 100644 cors-anywhere/test/setup.js create mode 100644 cors-anywhere/test/test-examples.js create mode 100644 cors-anywhere/test/test-memory.js create mode 100644 cors-anywhere/test/test-ratelimit.js create mode 100644 cors-anywhere/test/test.js diff --git a/cors-anywhere/.eslintignore b/cors-anywhere/.eslintignore new file mode 100644 index 0000000..404abb2 --- /dev/null +++ b/cors-anywhere/.eslintignore @@ -0,0 +1 @@ +coverage/ diff --git a/cors-anywhere/.eslintrc b/cors-anywhere/.eslintrc new file mode 100644 index 0000000..c264cd3 --- /dev/null +++ b/cors-anywhere/.eslintrc @@ -0,0 +1,28 @@ +{ + "env": { + "node": true + }, + "rules": { + "array-bracket-spacing": [2, "never"], + "block-scoped-var": 2, + "brace-style": [2, "1tbs", {"allowSingleLine": true}], + "comma-dangle": [2, "always-multiline"], + "computed-property-spacing": [2, "never"], + "curly": 2, + "eol-last": 2, + "eqeqeq": [2, "smart"], + "max-len": [1, 125], + "new-cap": 1, + "no-extend-native": 2, + "no-mixed-spaces-and-tabs": 2, + "no-trailing-spaces": 2, + "no-undef": 2, + "no-unused-vars": 1, + "no-use-before-define": [2, "nofunc"], + "object-curly-spacing": [2, "never"], + "quotes": [2, "single", "avoid-escape"], + "semi": [2, "always"], + "keyword-spacing": 2, + "space-unary-ops": 2 + } +} diff --git a/cors-anywhere/.gitignore b/cors-anywhere/.gitignore new file mode 100644 index 0000000..b6be0ec --- /dev/null +++ b/cors-anywhere/.gitignore @@ -0,0 +1,6 @@ +*.swp +*.tmp +*.log + +coverage/ +node_modules/ diff --git a/cors-anywhere/.travis.yml b/cors-anywhere/.travis.yml new file mode 100644 index 0000000..f4d6c76 --- /dev/null +++ b/cors-anywhere/.travis.yml @@ -0,0 +1,12 @@ +language: node_js +node_js: + - 0.10 + - 4 + - 6 + - 7 + - 8 + - 9 +script: + - npm run lint + - npm run test + - npm run test-coverage && cat coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf coverage diff --git a/cors-anywhere/Procfile b/cors-anywhere/Procfile new file mode 100644 index 0000000..489b270 --- /dev/null +++ b/cors-anywhere/Procfile @@ -0,0 +1 @@ +web: node server.js diff --git a/cors-anywhere/README.md b/cors-anywhere/README.md new file mode 100644 index 0000000..b264190 --- /dev/null +++ b/cors-anywhere/README.md @@ -0,0 +1,181 @@ +[![Build Status](https://travis-ci.org/Rob--W/cors-anywhere.svg?branch=master)](https://travis-ci.org/Rob--W/cors-anywhere) +[![Coverage Status](https://coveralls.io/repos/github/Rob--W/cors-anywhere/badge.svg?branch=master)](https://coveralls.io/github/Rob--W/cors-anywhere?branch=master) + +**CORS Anywhere** is a NodeJS proxy which adds CORS headers to the proxied request. + +The url to proxy is literally taken from the path, validated and proxied. The protocol +part of the proxied URI is optional, and defaults to "http". If port 443 is specified, +the protocol defaults to "https". + +This package does not put any restrictions on the http methods or headers, except for +cookies. Requesting [user credentials](http://www.w3.org/TR/cors/#user-credentials) is disallowed. +The app can be configured to require a header for proxying a request, for example to avoid +a direct visit from the browser. + +## Example + +```javascript +// Listen on a specific host via the HOST environment variable +var host = process.env.HOST || '0.0.0.0'; +// Listen on a specific port via the PORT environment variable +var port = process.env.PORT || 8080; + +var cors_proxy = require('cors-anywhere'); +cors_proxy.createServer({ + originWhitelist: [], // Allow all origins + requireHeader: ['origin', 'x-requested-with'], + removeHeaders: ['cookie', 'cookie2'] +}).listen(port, host, function() { + console.log('Running CORS Anywhere on ' + host + ':' + port); +}); + +``` +Request examples: + +* `http://localhost:8080/http://google.com/` - Google.com with CORS headers +* `http://localhost:8080/google.com` - Same as previous. +* `http://localhost:8080/google.com:443` - Proxies `https://google.com/` +* `http://localhost:8080/` - Shows usage text, as defined in `libs/help.txt` +* `http://localhost:8080/favicon.ico` - Replies 404 Not found + +Live examples: + +* https://cors-anywhere.herokuapp.com/ +* https://robwu.nl/cors-anywhere.html - This demo shows how to use the API. + +## Documentation + +### Client + +To use the API, just prefix the URL with the API URL. Take a look at [demo.html](demo.html) for an example. +A concise summary of the documentation is provided at [lib/help.txt](lib/help.txt). + +If you want to automatically enable cross-domain requests when needed, use the following snippet: + +```javascript +(function() { + var cors_api_host = 'cors-anywhere.herokuapp.com'; + var cors_api_url = 'https://' + cors_api_host + '/'; + var slice = [].slice; + var origin = window.location.protocol + '//' + window.location.host; + var open = XMLHttpRequest.prototype.open; + XMLHttpRequest.prototype.open = function() { + var args = slice.call(arguments); + var targetOrigin = /^https?:\/\/([^\/]+)/i.exec(args[1]); + if (targetOrigin && targetOrigin[0].toLowerCase() !== origin && + targetOrigin[1] !== cors_api_host) { + args[1] = cors_api_url + args[1]; + } + return open.apply(this, args); + }; +})(); +``` + +If you're using jQuery, you can also use the following code **instead of** the previous one: + +```javascript +jQuery.ajaxPrefilter(function(options) { + if (options.crossDomain && jQuery.support.cors) { + options.url = 'https://cors-anywhere.herokuapp.com/' + options.url; + } +}); +``` + +### Server + +The module exports `createServer(options)`, which creates a server that handles +proxy requests. The following options are supported: + +* function `getProxyForUrl` - If set, specifies which intermediate proxy to use for a given URL. + If the return value is void, a direct request is sent. The default implementation is + [`proxy-from-env`](https://github.com/Rob--W/proxy-from-env), which respects the standard proxy + environment variables (e.g. `https_proxy`, `no_proxy`, etc.). +* array of strings `originBlacklist` - If set, requests whose origin is listed are blocked. + Example: `['https://bad.example.com', 'http://bad.example.com']` +* array of strings `originWhitelist` - If set, requests whose origin is not listed are blocked. + If this list is empty, all origins are allowed. + Example: `['https://good.example.com', 'http://good.example.com']` +* function `checkRateLimit` - If set, it is called with the origin (string) of the request. If this + function returns a non-empty string, the request is rejected and the string is send to the client. +* boolean `redirectSameOrigin` - If true, requests to URLs from the same origin will not be proxied but redirected. + The primary purpose for this option is to save server resources by delegating the request to the client + (since same-origin requests should always succeed, even without proxying). +* array of strings `requireHeader` - If set, the request must include this header or the API will refuse to proxy. + Recommended if you want to prevent users from using the proxy for normal browsing. + Example: `['Origin', 'X-Requested-With']`. +* array of lowercase strings `removeHeaders` - Exclude certain headers from being included in the request. + Example: `["cookie"]` +* dictionary of lowercase strings `setHeaders` - Set headers for the request (overwrites existing ones). + Example: `{"x-powered-by": "CORS Anywhere"}` +* number `corsMaxAge` - If set, an Access-Control-Max-Age request header with this value (in seconds) will be added. + Example: `600` - Allow CORS preflight request to be cached by the browser for 10 minutes. +* string `helpFile` - Set the help file (shown at the homepage). + Example: `"myCustomHelpText.txt"` + +For advanced users, the following options are also provided. + +* `httpProxyOptions` - Under the hood, [http-proxy](https://github.com/nodejitsu/node-http-proxy) + is used to proxy requests. Use this option if you really need to pass options + to http-proxy. The documentation for these options can be found [here](https://github.com/nodejitsu/node-http-proxy#options). +* `httpsOptions` - If set, a `https.Server` will be created. The given options are passed to the + [`https.createServer`](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener) method. + +For even more advanced usage (building upon CORS Anywhere), +see the sample code in [test/test-examples.js](test/test-examples.js). + +### Demo server + +A public demo of CORS Anywhere is available at https://cors-anywhere.herokuapp.com. This server is +only provided so that you can easily and quickly try out CORS Anywhere. To ensure that the service +stays available to everyone, the number of requests per period is limited, except for requests from +some explicitly whitelisted origins. + +If you expect lots of traffic, please host your own instance of CORS Anywhere, and make sure that +the CORS Anywhere server only whitelists your site to prevent others from using your instance of +CORS Anywhere as an open proxy. + +For instance, to run a CORS Anywhere server that accepts any request from some example.com sites on +port 8080, use: +``` +export PORT=8080 +export CORSANYWHERE_WHITELIST=https://example.com,http://example.com,http://example.com:8080 +node server.js +``` + +This application can immediately be run on Heroku, see https://devcenter.heroku.com/articles/nodejs +for instructions. Note that their [Acceptable Use Policy](https://www.heroku.com/policy/aup) forbids +the use of Heroku for operating an open proxy, so make sure that you either enforce a whitelist as +shown above, or severly rate-limit the number of requests. + +For example, to blacklist abuse.example.com and rate-limit everything to 50 requests per 3 minutes, +except for my.example.com and my2.example.com (which may be unlimited), use: + +``` +export PORT=8080 +export CORSANYWHERE_BLACKLIST=https://abuse.example.com,http://abuse.example.com +export CORSANYWHERE_RATELIMIT='50 3 my.example.com my2.example.com' +node server.js +``` + + +## License + +Copyright (C) 2013 - 2016 Rob Wu + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cors-anywhere/demo.html b/cors-anywhere/demo.html new file mode 100644 index 0000000..89f1996 --- /dev/null +++ b/cors-anywhere/demo.html @@ -0,0 +1,107 @@ + + + + +Demo of CORS Anywhere + + + + +
+ CORS Anywhere demo • GithubLive server. + + + +
+
+ +
+ + + + diff --git a/cors-anywhere/lib/cors-anywhere.js b/cors-anywhere/lib/cors-anywhere.js new file mode 100644 index 0000000..bedfc47 --- /dev/null +++ b/cors-anywhere/lib/cors-anywhere.js @@ -0,0 +1,431 @@ +// © 2013 - 2016 Rob Wu +// Released under the MIT license + +'use strict'; + +var httpProxy = require('http-proxy'); +var net = require('net'); +var url = require('url'); +var regexp_tld = require('./regexp-top-level-domain'); +var getProxyForUrl = require('proxy-from-env').getProxyForUrl; + +var help_text = {}; +function showUsage(help_file, headers, response) { + var isHtml = /\.html$/.test(help_file); + headers['content-type'] = isHtml ? 'text/html' : 'text/plain'; + if (help_text[help_file] != null) { + response.writeHead(200, headers); + response.end(help_text[help_file]); + } else { + require('fs').readFile(help_file, 'utf8', function(err, data) { + if (err) { + console.error(err); + response.writeHead(500, headers); + response.end(); + } else { + help_text[help_file] = data; + showUsage(help_file, headers, response); // Recursive call, but since data is a string, the recursion will end + } + }); + } +} + +/** + * Check whether the specified hostname is valid. + * + * @param hostname {string} Host name (excluding port) of requested resource. + * @return {boolean} Whether the requested resource can be accessed. + */ +function isValidHostName(hostname) { + return !!( + regexp_tld.test(hostname) || + net.isIPv4(hostname) || + net.isIPv6(hostname) + ); +} + +/** + * Adds CORS headers to the response headers. + * + * @param headers {object} Response headers + * @param request {ServerRequest} + */ +function withCORS(headers, request) { + headers['access-control-allow-origin'] = '*'; + var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge; + if (corsMaxAge) { + headers['access-control-max-age'] = corsMaxAge; + } + if (request.headers['access-control-request-method']) { + headers['access-control-allow-methods'] = request.headers['access-control-request-method']; + delete request.headers['access-control-request-method']; + } + if (request.headers['access-control-request-headers']) { + headers['access-control-allow-headers'] = request.headers['access-control-request-headers']; + delete request.headers['access-control-request-headers']; + } + + headers['access-control-expose-headers'] = Object.keys(headers).join(','); + + return headers; +} + +/** + * Performs the actual proxy request. + * + * @param req {ServerRequest} Incoming http request + * @param res {ServerResponse} Outgoing (proxied) http request + * @param proxy {HttpProxy} + */ +function proxyRequest(req, res, proxy) { + var location = req.corsAnywhereRequestState.location; + req.url = location.path; + + var proxyOptions = { + changeOrigin: false, + prependPath: false, + target: location, + headers: { + host: location.host, + }, + // HACK: Get hold of the proxyReq object, because we need it later. + // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L144 + buffer: { + pipe: function(proxyReq) { + var proxyReqOn = proxyReq.on; + // Intercepts the handler that connects proxyRes to res. + // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L146-L158 + proxyReq.on = function(eventName, listener) { + if (eventName !== 'response') { + return proxyReqOn.call(this, eventName, listener); + } + return proxyReqOn.call(this, 'response', function(proxyRes) { + if (onProxyResponse(proxy, proxyReq, proxyRes, req, res)) { + try { + listener(proxyRes); + } catch (err) { + // Wrap in try-catch because an error could occur: + // "RangeError: Invalid status code: 0" + // https://github.com/Rob--W/cors-anywhere/issues/95 + // https://github.com/nodejitsu/node-http-proxy/issues/1080 + + // Forward error (will ultimately emit the 'error' event on our proxy object): + // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L134 + proxyReq.emit('error', err); + } + } + }); + }; + return req.pipe(proxyReq); + }, + }, + }; + + var proxyThroughUrl = req.corsAnywhereRequestState.getProxyForUrl(location.href); + if (proxyThroughUrl) { + proxyOptions.target = proxyThroughUrl; + proxyOptions.toProxy = true; + // If a proxy URL was set, req.url must be an absolute URL. Then the request will not be sent + // directly to the proxied URL, but through another proxy. + req.url = location.href; + } + + // Start proxying the request + try { + proxy.web(req, res, proxyOptions); + } catch (err) { + proxy.emit('error', err, req, res); + } +} + +/** + * This method modifies the response headers of the proxied response. + * If a redirect is detected, the response is not sent to the client, + * and a new request is initiated. + * + * client (req) -> CORS Anywhere -> (proxyReq) -> other server + * client (res) <- CORS Anywhere <- (proxyRes) <- other server + * + * @param proxy {HttpProxy} + * @param proxyReq {ClientRequest} The outgoing request to the other server. + * @param proxyRes {ServerResponse} The response from the other server. + * @param req {IncomingMessage} Incoming HTTP request, augmented with property corsAnywhereRequestState + * @param req.corsAnywhereRequestState {object} + * @param req.corsAnywhereRequestState.location {object} See parseURL + * @param req.corsAnywhereRequestState.getProxyForUrl {function} See proxyRequest + * @param req.corsAnywhereRequestState.proxyBaseUrl {string} Base URL of the CORS API endpoint + * @param req.corsAnywhereRequestState.maxRedirects {number} Maximum number of redirects + * @param req.corsAnywhereRequestState.redirectCount_ {number} Internally used to count redirects + * @param res {ServerResponse} Outgoing response to the client that wanted to proxy the HTTP request. + * + * @returns {boolean} true if http-proxy should continue to pipe proxyRes to res. + */ +function onProxyResponse(proxy, proxyReq, proxyRes, req, res) { + var requestState = req.corsAnywhereRequestState; + + var statusCode = proxyRes.statusCode; + + if (!requestState.redirectCount_) { + res.setHeader('x-request-url', requestState.location.href); + } + // Handle redirects + if (statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308) { + var locationHeader = proxyRes.headers.location; + if (locationHeader) { + locationHeader = url.resolve(requestState.location.href, locationHeader); + + if (statusCode === 301 || statusCode === 302 || statusCode === 303) { + // Exclude 307 & 308, because they are rare, and require preserving the method + request body + requestState.redirectCount_ = requestState.redirectCount_ + 1 || 1; + if (requestState.redirectCount_ <= requestState.maxRedirects) { + // Handle redirects within the server, because some clients (e.g. Android Stock Browser) + // cancel redirects. + // Set header for debugging purposes. Do not try to parse it! + res.setHeader('X-CORS-Redirect-' + requestState.redirectCount_, statusCode + ' ' + locationHeader); + + req.method = 'GET'; + req.headers['content-length'] = '0'; + delete req.headers['content-type']; + requestState.location = parseURL(locationHeader); + + // Remove all listeners (=reset events to initial state) + req.removeAllListeners(); + + // Remove the error listener so that the ECONNRESET "error" that + // may occur after aborting a request does not propagate to res. + // https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L134 + proxyReq.removeAllListeners('error'); + proxyReq.once('error', function catchAndIgnoreError() {}); + proxyReq.abort(); + + // Initiate a new proxy request. + proxyRequest(req, res, proxy); + return false; + } + } + proxyRes.headers.location = requestState.proxyBaseUrl + '/' + locationHeader; + } + } + + // Strip cookies + delete proxyRes.headers['set-cookie']; + delete proxyRes.headers['set-cookie2']; + + proxyRes.headers['x-final-url'] = requestState.location.href; + withCORS(proxyRes.headers, req); + return true; +} + + +/** + * @param req_url {string} The requested URL (scheme is optional). + * @return {object} URL parsed using url.parse + */ +function parseURL(req_url) { + var match = req_url.match(/^(?:(https?:)?\/\/)?(([^\/?]+?)(?::(\d{0,5})(?=[\/?]|$))?)([\/?][\S\s]*|$)/i); + // ^^^^^^^ ^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^ + // 1:protocol 3:hostname 4:port 5:path + query string + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // 2:host + if (!match) { + return null; + } + if (!match[1]) { + // Scheme is omitted. + if (req_url.lastIndexOf('//', 0) === -1) { + // "//" is omitted. + req_url = '//' + req_url; + } + req_url = (match[4] === '443' ? 'https:' : 'http:') + req_url; + } + return url.parse(req_url); +} + +// Request handler factory +function getHandler(options, proxy) { + var corsAnywhere = { + getProxyForUrl: getProxyForUrl, // Function that specifies the proxy to use + maxRedirects: 5, // Maximum number of redirects to be followed. + originBlacklist: [], // Requests from these origins will be blocked. + originWhitelist: [], // If non-empty, requests not from an origin in this list will be blocked. + checkRateLimit: null, // Function that may enforce a rate-limit by returning a non-empty string. + redirectSameOrigin: false, // Redirect the client to the requested URL for same-origin requests. + requireHeader: null, // Require a header to be set? + removeHeaders: [], // Strip these request headers. + setHeaders: {}, // Set these request headers. + corsMaxAge: 0, // If set, an Access-Control-Max-Age header with this value (in seconds) will be added. + helpFile: __dirname + '/help.txt', + }; + + Object.keys(corsAnywhere).forEach(function(option) { + if (Object.prototype.hasOwnProperty.call(options, option)) { + corsAnywhere[option] = options[option]; + } + }); + + // Convert corsAnywhere.requireHeader to an array of lowercase header names, or null. + if (corsAnywhere.requireHeader) { + if (typeof corsAnywhere.requireHeader === 'string') { + corsAnywhere.requireHeader = [corsAnywhere.requireHeader.toLowerCase()]; + } else if (!Array.isArray(corsAnywhere.requireHeader) || corsAnywhere.requireHeader.length === 0) { + corsAnywhere.requireHeader = null; + } else { + corsAnywhere.requireHeader = corsAnywhere.requireHeader.map(function(headerName) { + return headerName.toLowerCase(); + }); + } + } + var hasRequiredHeaders = function(headers) { + return !corsAnywhere.requireHeader || corsAnywhere.requireHeader.some(function(headerName) { + return Object.hasOwnProperty.call(headers, headerName); + }); + }; + + return function(req, res) { + req.corsAnywhereRequestState = { + getProxyForUrl: corsAnywhere.getProxyForUrl, + maxRedirects: corsAnywhere.maxRedirects, + corsMaxAge: corsAnywhere.corsMaxAge, + }; + + var cors_headers = withCORS({}, req); + if (req.method === 'OPTIONS') { + // Pre-flight request. Reply successfully: + res.writeHead(200, cors_headers); + res.end(); + return; + } + + var location = parseURL(req.url.slice(1)); + + if (!location) { + // Invalid API call. Show how to correctly use the API + showUsage(corsAnywhere.helpFile, cors_headers, res); + return; + } + + if (location.host === 'iscorsneeded') { + // Is CORS needed? This path is provided so that API consumers can test whether it's necessary + // to use CORS. The server's reply is always No, because if they can read it, then CORS headers + // are not necessary. + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('no'); + return; + } + + if (location.port > 65535) { + // Port is higher than 65535 + res.writeHead(400, 'Invalid port', cors_headers); + res.end('Port number too large: ' + location.port); + return; + } + + if (!/^\/https?:/.test(req.url) && !isValidHostName(location.hostname)) { + // Don't even try to proxy invalid hosts (such as /favicon.ico, /robots.txt) + res.writeHead(404, 'Invalid host', cors_headers); + res.end('Invalid host: ' + location.hostname); + return; + } + + if (!hasRequiredHeaders(req.headers)) { + res.writeHead(400, 'Header required', cors_headers); + res.end('Missing required request header. Must specify one of: ' + corsAnywhere.requireHeader); + return; + } + + var origin = req.headers.origin || ''; + if (corsAnywhere.originBlacklist.indexOf(origin) >= 0) { + res.writeHead(403, 'Forbidden', cors_headers); + res.end('The origin "' + origin + '" was blacklisted by the operator of this proxy.'); + return; + } + + if (corsAnywhere.originWhitelist.length && corsAnywhere.originWhitelist.indexOf(origin) === -1) { + res.writeHead(403, 'Forbidden', cors_headers); + res.end('The origin "' + origin + '" was not whitelisted by the operator of this proxy.'); + return; + } + + var rateLimitMessage = corsAnywhere.checkRateLimit && corsAnywhere.checkRateLimit(origin); + if (rateLimitMessage) { + res.writeHead(429, 'Too Many Requests', cors_headers); + res.end('The origin "' + origin + '" has sent too many requests.\n' + rateLimitMessage); + return; + } + + if (corsAnywhere.redirectSameOrigin && origin && location.href[origin.length] === '/' && + location.href.lastIndexOf(origin, 0) === 0) { + // Send a permanent redirect to offload the server. Badly coded clients should not waste our resources. + cors_headers.vary = 'origin'; + cors_headers['cache-control'] = 'private'; + cors_headers.location = location.href; + res.writeHead(301, 'Please use a direct request', cors_headers); + res.end(); + return; + } + + var isRequestedOverHttps = req.connection.encrypted || /^\s*https/.test(req.headers['x-forwarded-proto']); + var proxyBaseUrl = (isRequestedOverHttps ? 'https://' : 'http://') + req.headers.host; + + corsAnywhere.removeHeaders.forEach(function(header) { + delete req.headers[header]; + }); + + Object.keys(corsAnywhere.setHeaders).forEach(function(header) { + req.headers[header] = corsAnywhere.setHeaders[header]; + }); + + req.corsAnywhereRequestState.location = location; + req.corsAnywhereRequestState.proxyBaseUrl = proxyBaseUrl; + + proxyRequest(req, res, proxy); + }; +} + +// Create server with default and given values +// Creator still needs to call .listen() +exports.createServer = function createServer(options) { + options = options || {}; + + // Default options: + var httpProxyOptions = { + xfwd: true, // Append X-Forwarded-* headers + }; + // Allow user to override defaults and add own options + if (options.httpProxyOptions) { + Object.keys(options.httpProxyOptions).forEach(function(option) { + httpProxyOptions[option] = options.httpProxyOptions[option]; + }); + } + + var proxy = httpProxy.createServer(httpProxyOptions); + var requestHandler = getHandler(options, proxy); + var server; + if (options.httpsOptions) { + server = require('https').createServer(options.httpsOptions, requestHandler); + } else { + server = require('http').createServer(requestHandler); + } + + // When the server fails, just show a 404 instead of Internal server error + proxy.on('error', function(err, req, res) { + if (res.headersSent) { + // This could happen when a protocol error occurs when an error occurs + // after the headers have been received (and forwarded). Do not write + // the headers because it would generate an error. + return; + } + + // When the error occurs after setting headers but before writing the response, + // then any previously set headers must be removed. + var headerNames = res.getHeaderNames ? res.getHeaderNames() : Object.keys(res._headers || {}); + headerNames.forEach(function(name) { + res.removeHeader(name); + }); + + res.writeHead(404, {'Access-Control-Allow-Origin': '*'}); + res.end('Not found because of proxy error: ' + err); + }); + + return server; +}; diff --git a/cors-anywhere/lib/help.txt b/cors-anywhere/lib/help.txt new file mode 100644 index 0000000..3d0d654 --- /dev/null +++ b/cors-anywhere/lib/help.txt @@ -0,0 +1,30 @@ +This API enables cross-origin requests to anywhere. + +Usage: + +/ Shows help +/iscorsneeded This is the only resource on this host which is served without CORS headers. +/ Create a request to , and includes CORS headers in the response. + +If the protocol is omitted, it defaults to http (https if port 443 is specified). + +Cookies are disabled and stripped from requests. + +Redirects are automatically followed. For debugging purposes, each followed redirect results +in the addition of a X-CORS-Redirect-n header, where n starts at 1. These headers are not +accessible by the XMLHttpRequest API. +After 5 redirects, redirects are not followed any more. The redirect response is sent back +to the browser, which can choose to follow the redirect (handled automatically by the browser). + +The requested URL is available in the X-Request-URL response header. +The final URL, after following all redirects, is available in the X-Final-URL response header. + + +To prevent the use of the proxy for casual browsing, the API requires either the Origin +or the X-Requested-With header to be set. To avoid unnecessary preflight (OPTIONS) requests, +it's recommended to not manually set these headers in your code. + + +Demo : https://robwu.nl/cors-anywhere.html +Source code : https://github.com/Rob--W/cors-anywhere/ +Documentation : https://github.com/Rob--W/cors-anywhere/#documentation diff --git a/cors-anywhere/lib/rate-limit.js b/cors-anywhere/lib/rate-limit.js new file mode 100644 index 0000000..ae4616e --- /dev/null +++ b/cors-anywhere/lib/rate-limit.js @@ -0,0 +1,74 @@ +'use strict'; +module.exports = function createRateLimitChecker(CORSANYWHERE_RATELIMIT) { + // Configure rate limit. The following format is accepted for CORSANYWHERE_RATELIMIT: + // + // where is a space-separated list of strings or regexes (/.../) that + // matches the whole host (ports have to be listed explicitly if applicable). + // cannot be zero. + // + // Examples: + // - Allow any origin to make one request per 5 minutes: + // 1 5 + // + // - Allow example.com to make an unlimited number of requests, and the others 1 per 5 minutes. + // 1 5 example.com + // + // - Allow example.com, or any subdomain to make any number of requests and block the rest: + // 0 1 /(.*\.)?example\.com/ + // + // - Allow example.com and www.example.com, and block the rest: + // 0 1 example.com www.example.com + var rateLimitConfig = /^(\d+) (\d+)(?:\s*$|\s+(.+)$)/.exec(CORSANYWHERE_RATELIMIT); + if (!rateLimitConfig) { + // No rate limit by default. + return function checkRateLimit() {}; + } + var maxRequestsPerPeriod = parseInt(rateLimitConfig[1]); + var periodInMinutes = parseInt(rateLimitConfig[2]); + var unlimitedPattern = rateLimitConfig[3]; // Will become a RegExp or void. + if (unlimitedPattern) { + var unlimitedPatternParts = []; + unlimitedPattern.trim().split(/\s+/).forEach(function(unlimitedHost, i) { + var startsWithSlash = unlimitedHost.charAt(0) === '/'; + var endsWithSlash = unlimitedHost.slice(-1) === '/'; + if (startsWithSlash || endsWithSlash) { + if (unlimitedHost.length === 1 || !startsWithSlash || !endsWithSlash) { + throw new Error('Invalid CORSANYWHERE_RATELIMIT. Regex at index ' + i + + ' must start and end with a slash ("/").'); + } + unlimitedHost = unlimitedHost.slice(1, -1); + // Throws if the pattern is invalid. + new RegExp(unlimitedHost); + } else { + // Just escape RegExp characters even though they cannot appear in a host name. + // The only actual important escape is the dot. + unlimitedHost = unlimitedHost.replace(/[$()*+.?[\\\]^{|}]/g, '\\$&'); + } + unlimitedPatternParts.push(unlimitedHost); + }); + unlimitedPattern = new RegExp('^(?:' + unlimitedPatternParts.join('|') + ')$', 'i'); + } + + var accessedHosts = Object.create(null); + setInterval(function() { + accessedHosts = Object.create(null); + }, periodInMinutes * 60000); + + var rateLimitMessage = 'The number of requests is limited to ' + maxRequestsPerPeriod + + (periodInMinutes === 1 ? ' per minute' : ' per ' + periodInMinutes + ' minutes') + '. ' + + 'Please self-host CORS Anywhere if you need more quota. ' + + 'See https://github.com/Rob--W/cors-anywhere#demo-server'; + + return function checkRateLimit(origin) { + var host = origin.replace(/^[\w\-]+:\/\//i, ''); + if (unlimitedPattern && unlimitedPattern.test(host)) { + return; + } + var count = accessedHosts[host] || 0; + ++count; + if (count > maxRequestsPerPeriod) { + return rateLimitMessage; + } + accessedHosts[host] = count; + }; +}; diff --git a/cors-anywhere/lib/regexp-top-level-domain.js b/cors-anywhere/lib/regexp-top-level-domain.js new file mode 100644 index 0000000..fec4049 --- /dev/null +++ b/cors-anywhere/lib/regexp-top-level-domain.js @@ -0,0 +1,8 @@ +// Based on http://data.iana.org/TLD/tlds-alpha-by-domain.txt +// '/\\.(?:' + document.body.firstChild.textContent.trim().split('\n').slice(1).join('|') + ')$/i'; + +/* eslint max-len:0 */ + +// # Version 2017111000, Last Updated Fri Nov 10 07:07:01 2017 UTC +var regexp = /\.(?:AAA|AARP|ABARTH|ABB|ABBOTT|ABBVIE|ABC|ABLE|ABOGADO|ABUDHABI|AC|ACADEMY|ACCENTURE|ACCOUNTANT|ACCOUNTANTS|ACO|ACTIVE|ACTOR|AD|ADAC|ADS|ADULT|AE|AEG|AERO|AETNA|AF|AFAMILYCOMPANY|AFL|AFRICA|AG|AGAKHAN|AGENCY|AI|AIG|AIGO|AIRBUS|AIRFORCE|AIRTEL|AKDN|AL|ALFAROMEO|ALIBABA|ALIPAY|ALLFINANZ|ALLSTATE|ALLY|ALSACE|ALSTOM|AM|AMERICANEXPRESS|AMERICANFAMILY|AMEX|AMFAM|AMICA|AMSTERDAM|ANALYTICS|ANDROID|ANQUAN|ANZ|AO|AOL|APARTMENTS|APP|APPLE|AQ|AQUARELLE|AR|ARAB|ARAMCO|ARCHI|ARMY|ARPA|ART|ARTE|AS|ASDA|ASIA|ASSOCIATES|AT|ATHLETA|ATTORNEY|AU|AUCTION|AUDI|AUDIBLE|AUDIO|AUSPOST|AUTHOR|AUTO|AUTOS|AVIANCA|AW|AWS|AX|AXA|AZ|AZURE|BA|BABY|BAIDU|BANAMEX|BANANAREPUBLIC|BAND|BANK|BAR|BARCELONA|BARCLAYCARD|BARCLAYS|BAREFOOT|BARGAINS|BASEBALL|BASKETBALL|BAUHAUS|BAYERN|BB|BBC|BBT|BBVA|BCG|BCN|BD|BE|BEATS|BEAUTY|BEER|BENTLEY|BERLIN|BEST|BESTBUY|BET|BF|BG|BH|BHARTI|BI|BIBLE|BID|BIKE|BING|BINGO|BIO|BIZ|BJ|BLACK|BLACKFRIDAY|BLANCO|BLOCKBUSTER|BLOG|BLOOMBERG|BLUE|BM|BMS|BMW|BN|BNL|BNPPARIBAS|BO|BOATS|BOEHRINGER|BOFA|BOM|BOND|BOO|BOOK|BOOKING|BOOTS|BOSCH|BOSTIK|BOSTON|BOT|BOUTIQUE|BOX|BR|BRADESCO|BRIDGESTONE|BROADWAY|BROKER|BROTHER|BRUSSELS|BS|BT|BUDAPEST|BUGATTI|BUILD|BUILDERS|BUSINESS|BUY|BUZZ|BV|BW|BY|BZ|BZH|CA|CAB|CAFE|CAL|CALL|CALVINKLEIN|CAM|CAMERA|CAMP|CANCERRESEARCH|CANON|CAPETOWN|CAPITAL|CAPITALONE|CAR|CARAVAN|CARDS|CARE|CAREER|CAREERS|CARS|CARTIER|CASA|CASE|CASEIH|CASH|CASINO|CAT|CATERING|CATHOLIC|CBA|CBN|CBRE|CBS|CC|CD|CEB|CENTER|CEO|CERN|CF|CFA|CFD|CG|CH|CHANEL|CHANNEL|CHASE|CHAT|CHEAP|CHINTAI|CHRISTMAS|CHROME|CHRYSLER|CHURCH|CI|CIPRIANI|CIRCLE|CISCO|CITADEL|CITI|CITIC|CITY|CITYEATS|CK|CL|CLAIMS|CLEANING|CLICK|CLINIC|CLINIQUE|CLOTHING|CLOUD|CLUB|CLUBMED|CM|CN|CO|COACH|CODES|COFFEE|COLLEGE|COLOGNE|COM|COMCAST|COMMBANK|COMMUNITY|COMPANY|COMPARE|COMPUTER|COMSEC|CONDOS|CONSTRUCTION|CONSULTING|CONTACT|CONTRACTORS|COOKING|COOKINGCHANNEL|COOL|COOP|CORSICA|COUNTRY|COUPON|COUPONS|COURSES|CR|CREDIT|CREDITCARD|CREDITUNION|CRICKET|CROWN|CRS|CRUISE|CRUISES|CSC|CU|CUISINELLA|CV|CW|CX|CY|CYMRU|CYOU|CZ|DABUR|DAD|DANCE|DATA|DATE|DATING|DATSUN|DAY|DCLK|DDS|DE|DEAL|DEALER|DEALS|DEGREE|DELIVERY|DELL|DELOITTE|DELTA|DEMOCRAT|DENTAL|DENTIST|DESI|DESIGN|DEV|DHL|DIAMONDS|DIET|DIGITAL|DIRECT|DIRECTORY|DISCOUNT|DISCOVER|DISH|DIY|DJ|DK|DM|DNP|DO|DOCS|DOCTOR|DODGE|DOG|DOHA|DOMAINS|DOT|DOWNLOAD|DRIVE|DTV|DUBAI|DUCK|DUNLOP|DUNS|DUPONT|DURBAN|DVAG|DVR|DZ|EARTH|EAT|EC|ECO|EDEKA|EDU|EDUCATION|EE|EG|EMAIL|EMERCK|ENERGY|ENGINEER|ENGINEERING|ENTERPRISES|EPOST|EPSON|EQUIPMENT|ER|ERICSSON|ERNI|ES|ESQ|ESTATE|ESURANCE|ET|ETISALAT|EU|EUROVISION|EUS|EVENTS|EVERBANK|EXCHANGE|EXPERT|EXPOSED|EXPRESS|EXTRASPACE|FAGE|FAIL|FAIRWINDS|FAITH|FAMILY|FAN|FANS|FARM|FARMERS|FASHION|FAST|FEDEX|FEEDBACK|FERRARI|FERRERO|FI|FIAT|FIDELITY|FIDO|FILM|FINAL|FINANCE|FINANCIAL|FIRE|FIRESTONE|FIRMDALE|FISH|FISHING|FIT|FITNESS|FJ|FK|FLICKR|FLIGHTS|FLIR|FLORIST|FLOWERS|FLY|FM|FO|FOO|FOOD|FOODNETWORK|FOOTBALL|FORD|FOREX|FORSALE|FORUM|FOUNDATION|FOX|FR|FREE|FRESENIUS|FRL|FROGANS|FRONTDOOR|FRONTIER|FTR|FUJITSU|FUJIXEROX|FUN|FUND|FURNITURE|FUTBOL|FYI|GA|GAL|GALLERY|GALLO|GALLUP|GAME|GAMES|GAP|GARDEN|GB|GBIZ|GD|GDN|GE|GEA|GENT|GENTING|GEORGE|GF|GG|GGEE|GH|GI|GIFT|GIFTS|GIVES|GIVING|GL|GLADE|GLASS|GLE|GLOBAL|GLOBO|GM|GMAIL|GMBH|GMO|GMX|GN|GODADDY|GOLD|GOLDPOINT|GOLF|GOO|GOODHANDS|GOODYEAR|GOOG|GOOGLE|GOP|GOT|GOV|GP|GQ|GR|GRAINGER|GRAPHICS|GRATIS|GREEN|GRIPE|GROCERY|GROUP|GS|GT|GU|GUARDIAN|GUCCI|GUGE|GUIDE|GUITARS|GURU|GW|GY|HAIR|HAMBURG|HANGOUT|HAUS|HBO|HDFC|HDFCBANK|HEALTH|HEALTHCARE|HELP|HELSINKI|HERE|HERMES|HGTV|HIPHOP|HISAMITSU|HITACHI|HIV|HK|HKT|HM|HN|HOCKEY|HOLDINGS|HOLIDAY|HOMEDEPOT|HOMEGOODS|HOMES|HOMESENSE|HONDA|HONEYWELL|HORSE|HOSPITAL|HOST|HOSTING|HOT|HOTELES|HOTELS|HOTMAIL|HOUSE|HOW|HR|HSBC|HT|HU|HUGHES|HYATT|HYUNDAI|IBM|ICBC|ICE|ICU|ID|IE|IEEE|IFM|IKANO|IL|IM|IMAMAT|IMDB|IMMO|IMMOBILIEN|IN|INDUSTRIES|INFINITI|INFO|ING|INK|INSTITUTE|INSURANCE|INSURE|INT|INTEL|INTERNATIONAL|INTUIT|INVESTMENTS|IO|IPIRANGA|IQ|IR|IRISH|IS|ISELECT|ISMAILI|IST|ISTANBUL|IT|ITAU|ITV|IVECO|IWC|JAGUAR|JAVA|JCB|JCP|JE|JEEP|JETZT|JEWELRY|JIO|JLC|JLL|JM|JMP|JNJ|JO|JOBS|JOBURG|JOT|JOY|JP|JPMORGAN|JPRS|JUEGOS|JUNIPER|KAUFEN|KDDI|KE|KERRYHOTELS|KERRYLOGISTICS|KERRYPROPERTIES|KFH|KG|KH|KI|KIA|KIM|KINDER|KINDLE|KITCHEN|KIWI|KM|KN|KOELN|KOMATSU|KOSHER|KP|KPMG|KPN|KR|KRD|KRED|KUOKGROUP|KW|KY|KYOTO|KZ|LA|LACAIXA|LADBROKES|LAMBORGHINI|LAMER|LANCASTER|LANCIA|LANCOME|LAND|LANDROVER|LANXESS|LASALLE|LAT|LATINO|LATROBE|LAW|LAWYER|LB|LC|LDS|LEASE|LECLERC|LEFRAK|LEGAL|LEGO|LEXUS|LGBT|LI|LIAISON|LIDL|LIFE|LIFEINSURANCE|LIFESTYLE|LIGHTING|LIKE|LILLY|LIMITED|LIMO|LINCOLN|LINDE|LINK|LIPSY|LIVE|LIVING|LIXIL|LK|LOAN|LOANS|LOCKER|LOCUS|LOFT|LOL|LONDON|LOTTE|LOTTO|LOVE|LPL|LPLFINANCIAL|LR|LS|LT|LTD|LTDA|LU|LUNDBECK|LUPIN|LUXE|LUXURY|LV|LY|MA|MACYS|MADRID|MAIF|MAISON|MAKEUP|MAN|MANAGEMENT|MANGO|MAP|MARKET|MARKETING|MARKETS|MARRIOTT|MARSHALLS|MASERATI|MATTEL|MBA|MC|MCKINSEY|MD|ME|MED|MEDIA|MEET|MELBOURNE|MEME|MEMORIAL|MEN|MENU|MEO|MERCKMSD|METLIFE|MG|MH|MIAMI|MICROSOFT|MIL|MINI|MINT|MIT|MITSUBISHI|MK|ML|MLB|MLS|MM|MMA|MN|MO|MOBI|MOBILE|MOBILY|MODA|MOE|MOI|MOM|MONASH|MONEY|MONSTER|MOPAR|MORMON|MORTGAGE|MOSCOW|MOTO|MOTORCYCLES|MOV|MOVIE|MOVISTAR|MP|MQ|MR|MS|MSD|MT|MTN|MTR|MU|MUSEUM|MUTUAL|MV|MW|MX|MY|MZ|NA|NAB|NADEX|NAGOYA|NAME|NATIONWIDE|NATURA|NAVY|NBA|NC|NE|NEC|NET|NETBANK|NETFLIX|NETWORK|NEUSTAR|NEW|NEWHOLLAND|NEWS|NEXT|NEXTDIRECT|NEXUS|NF|NFL|NG|NGO|NHK|NI|NICO|NIKE|NIKON|NINJA|NISSAN|NISSAY|NL|NO|NOKIA|NORTHWESTERNMUTUAL|NORTON|NOW|NOWRUZ|NOWTV|NP|NR|NRA|NRW|NTT|NU|NYC|NZ|OBI|OBSERVER|OFF|OFFICE|OKINAWA|OLAYAN|OLAYANGROUP|OLDNAVY|OLLO|OM|OMEGA|ONE|ONG|ONL|ONLINE|ONYOURSIDE|OOO|OPEN|ORACLE|ORANGE|ORG|ORGANIC|ORIGINS|OSAKA|OTSUKA|OTT|OVH|PA|PAGE|PANASONIC|PANERAI|PARIS|PARS|PARTNERS|PARTS|PARTY|PASSAGENS|PAY|PCCW|PE|PET|PF|PFIZER|PG|PH|PHARMACY|PHD|PHILIPS|PHONE|PHOTO|PHOTOGRAPHY|PHOTOS|PHYSIO|PIAGET|PICS|PICTET|PICTURES|PID|PIN|PING|PINK|PIONEER|PIZZA|PK|PL|PLACE|PLAY|PLAYSTATION|PLUMBING|PLUS|PM|PN|PNC|POHL|POKER|POLITIE|PORN|POST|PR|PRAMERICA|PRAXI|PRESS|PRIME|PRO|PROD|PRODUCTIONS|PROF|PROGRESSIVE|PROMO|PROPERTIES|PROPERTY|PROTECTION|PRU|PRUDENTIAL|PS|PT|PUB|PW|PWC|PY|QA|QPON|QUEBEC|QUEST|QVC|RACING|RADIO|RAID|RE|READ|REALESTATE|REALTOR|REALTY|RECIPES|RED|REDSTONE|REDUMBRELLA|REHAB|REISE|REISEN|REIT|RELIANCE|REN|RENT|RENTALS|REPAIR|REPORT|REPUBLICAN|REST|RESTAURANT|REVIEW|REVIEWS|REXROTH|RICH|RICHARDLI|RICOH|RIGHTATHOME|RIL|RIO|RIP|RMIT|RO|ROCHER|ROCKS|RODEO|ROGERS|ROOM|RS|RSVP|RU|RUGBY|RUHR|RUN|RW|RWE|RYUKYU|SA|SAARLAND|SAFE|SAFETY|SAKURA|SALE|SALON|SAMSCLUB|SAMSUNG|SANDVIK|SANDVIKCOROMANT|SANOFI|SAP|SAPO|SARL|SAS|SAVE|SAXO|SB|SBI|SBS|SC|SCA|SCB|SCHAEFFLER|SCHMIDT|SCHOLARSHIPS|SCHOOL|SCHULE|SCHWARZ|SCIENCE|SCJOHNSON|SCOR|SCOT|SD|SE|SEARCH|SEAT|SECURE|SECURITY|SEEK|SELECT|SENER|SERVICES|SES|SEVEN|SEW|SEX|SEXY|SFR|SG|SH|SHANGRILA|SHARP|SHAW|SHELL|SHIA|SHIKSHA|SHOES|SHOP|SHOPPING|SHOUJI|SHOW|SHOWTIME|SHRIRAM|SI|SILK|SINA|SINGLES|SITE|SJ|SK|SKI|SKIN|SKY|SKYPE|SL|SLING|SM|SMART|SMILE|SN|SNCF|SO|SOCCER|SOCIAL|SOFTBANK|SOFTWARE|SOHU|SOLAR|SOLUTIONS|SONG|SONY|SOY|SPACE|SPIEGEL|SPOT|SPREADBETTING|SR|SRL|SRT|ST|STADA|STAPLES|STAR|STARHUB|STATEBANK|STATEFARM|STATOIL|STC|STCGROUP|STOCKHOLM|STORAGE|STORE|STREAM|STUDIO|STUDY|STYLE|SU|SUCKS|SUPPLIES|SUPPLY|SUPPORT|SURF|SURGERY|SUZUKI|SV|SWATCH|SWIFTCOVER|SWISS|SX|SY|SYDNEY|SYMANTEC|SYSTEMS|SZ|TAB|TAIPEI|TALK|TAOBAO|TARGET|TATAMOTORS|TATAR|TATTOO|TAX|TAXI|TC|TCI|TD|TDK|TEAM|TECH|TECHNOLOGY|TEL|TELECITY|TELEFONICA|TEMASEK|TENNIS|TEVA|TF|TG|TH|THD|THEATER|THEATRE|TIAA|TICKETS|TIENDA|TIFFANY|TIPS|TIRES|TIROL|TJ|TJMAXX|TJX|TK|TKMAXX|TL|TM|TMALL|TN|TO|TODAY|TOKYO|TOOLS|TOP|TORAY|TOSHIBA|TOTAL|TOURS|TOWN|TOYOTA|TOYS|TR|TRADE|TRADING|TRAINING|TRAVEL|TRAVELCHANNEL|TRAVELERS|TRAVELERSINSURANCE|TRUST|TRV|TT|TUBE|TUI|TUNES|TUSHU|TV|TVS|TW|TZ|UA|UBANK|UBS|UCONNECT|UG|UK|UNICOM|UNIVERSITY|UNO|UOL|UPS|US|UY|UZ|VA|VACATIONS|VANA|VANGUARD|VC|VE|VEGAS|VENTURES|VERISIGN|VERSICHERUNG|VET|VG|VI|VIAJES|VIDEO|VIG|VIKING|VILLAS|VIN|VIP|VIRGIN|VISA|VISION|VISTA|VISTAPRINT|VIVA|VIVO|VLAANDEREN|VN|VODKA|VOLKSWAGEN|VOLVO|VOTE|VOTING|VOTO|VOYAGE|VU|VUELOS|WALES|WALMART|WALTER|WANG|WANGGOU|WARMAN|WATCH|WATCHES|WEATHER|WEATHERCHANNEL|WEBCAM|WEBER|WEBSITE|WED|WEDDING|WEIBO|WEIR|WF|WHOSWHO|WIEN|WIKI|WILLIAMHILL|WIN|WINDOWS|WINE|WINNERS|WME|WOLTERSKLUWER|WOODSIDE|WORK|WORKS|WORLD|WOW|WS|WTC|WTF|XBOX|XEROX|XFINITY|XIHUAN|XIN|XN--11B4C3D|XN--1CK2E1B|XN--1QQW23A|XN--2SCRJ9C|XN--30RR7Y|XN--3BST00M|XN--3DS443G|XN--3E0B707E|XN--3HCRJ9C|XN--3OQ18VL8PN36A|XN--3PXU8K|XN--42C2D9A|XN--45BR5CYL|XN--45BRJ9C|XN--45Q11C|XN--4GBRIM|XN--54B7FTA0CC|XN--55QW42G|XN--55QX5D|XN--5SU34J936BGSG|XN--5TZM5G|XN--6FRZ82G|XN--6QQ986B3XL|XN--80ADXHKS|XN--80AO21A|XN--80AQECDR1A|XN--80ASEHDB|XN--80ASWG|XN--8Y0A063A|XN--90A3AC|XN--90AE|XN--90AIS|XN--9DBQ2A|XN--9ET52U|XN--9KRT00A|XN--B4W605FERD|XN--BCK1B9A5DRE4C|XN--C1AVG|XN--C2BR7G|XN--CCK2B3B|XN--CG4BKI|XN--CLCHC0EA0B2G2A9GCD|XN--CZR694B|XN--CZRS0T|XN--CZRU2D|XN--D1ACJ3B|XN--D1ALF|XN--E1A4C|XN--ECKVDTC9D|XN--EFVY88H|XN--ESTV75G|XN--FCT429K|XN--FHBEI|XN--FIQ228C5HS|XN--FIQ64B|XN--FIQS8S|XN--FIQZ9S|XN--FJQ720A|XN--FLW351E|XN--FPCRJ9C3D|XN--FZC2C9E2C|XN--FZYS8D69UVGM|XN--G2XX48C|XN--GCKR3F0F|XN--GECRJ9C|XN--GK3AT1E|XN--H2BREG3EVE|XN--H2BRJ9C|XN--H2BRJ9C8C|XN--HXT814E|XN--I1B6B1A6A2E|XN--IMR513N|XN--IO0A7I|XN--J1AEF|XN--J1AMH|XN--J6W193G|XN--JLQ61U9W7B|XN--JVR189M|XN--KCRX77D1X4A|XN--KPRW13D|XN--KPRY57D|XN--KPU716F|XN--KPUT3I|XN--L1ACC|XN--LGBBAT1AD8J|XN--MGB9AWBF|XN--MGBA3A3EJT|XN--MGBA3A4F16A|XN--MGBA7C0BBN0A|XN--MGBAAKC7DVF|XN--MGBAAM7A8H|XN--MGBAB2BD|XN--MGBAI9AZGQP6J|XN--MGBAYH7GPA|XN--MGBB9FBPOB|XN--MGBBH1A|XN--MGBBH1A71E|XN--MGBC0A9AZCG|XN--MGBCA7DZDO|XN--MGBERP4A5D4AR|XN--MGBGU82A|XN--MGBI4ECEXP|XN--MGBPL2FH|XN--MGBT3DHD|XN--MGBTX2B|XN--MGBX4CD0AB|XN--MIX891F|XN--MK1BU44C|XN--MXTQ1M|XN--NGBC5AZD|XN--NGBE9E0A|XN--NGBRX|XN--NODE|XN--NQV7F|XN--NQV7FS00EMA|XN--NYQY26A|XN--O3CW4H|XN--OGBPF8FL|XN--P1ACF|XN--P1AI|XN--PBT977C|XN--PGBS0DH|XN--PSSY2U|XN--Q9JYB4C|XN--QCKA1PMC|XN--QXAM|XN--RHQV96G|XN--ROVU88B|XN--RVC1E0AM3E|XN--S9BRJ9C|XN--SES554G|XN--T60B56A|XN--TCKWE|XN--TIQ49XQYJ|XN--UNUP4Y|XN--VERMGENSBERATER-CTB|XN--VERMGENSBERATUNG-PWB|XN--VHQUV|XN--VUQ861B|XN--W4R85EL8FHU5DNRA|XN--W4RS40L|XN--WGBH1C|XN--WGBL6A|XN--XHQ521B|XN--XKC2AL3HYE2A|XN--XKC2DL3A5EE0H|XN--Y9A3AQ|XN--YFRO4I67O|XN--YGBI2AMMX|XN--ZFR164B|XPERIA|XXX|XYZ|YACHTS|YAHOO|YAMAXUN|YANDEX|YE|YODOBASHI|YOGA|YOKOHAMA|YOU|YOUTUBE|YT|YUN|ZA|ZAPPOS|ZARA|ZERO|ZIP|ZIPPO|ZM|ZONE|ZUERICH|ZW)$/i; +module.exports = regexp; diff --git a/cors-anywhere/package-lock.json b/cors-anywhere/package-lock.json new file mode 100644 index 0000000..a10fb93 --- /dev/null +++ b/cors-anywhere/package-lock.json @@ -0,0 +1,2024 @@ +{ + "name": "cors-anywhere", + "version": "0.4.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "optional": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "1.1.0", + "deep-eql": "0.1.3", + "type-detect": "1.0.0" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" + } + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "coveralls": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.13.3.tgz", + "integrity": "sha512-iiAmn+l1XqRwNLXhW8Rs5qHZRFMYp9ZIPjEOVRpC/c4so6Y/f4/lFi0FfR5B9cCqgyhkJ5cZmbvcVRfP8MHchw==", + "dev": true, + "requires": { + "js-yaml": "3.6.1", + "lcov-parse": "0.0.10", + "log-driver": "1.2.5", + "minimist": "1.2.0", + "request": "2.79.0" + } + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "0.10.50", + "type": "1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" + } + }, + "es5-ext": { + "version": "0.10.50", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz", + "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1.0.1", + "es5-ext": "0.10.50", + "es6-symbol": "3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1.0.1", + "es5-ext": "0.10.50", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1.0.1", + "es5-ext": "0.10.50", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1.0.1", + "es5-ext": "0.10.50" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1.0.1", + "es5-ext": "0.10.50", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "2.7.3", + "estraverse": "1.9.3", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.2.0" + }, + "dependencies": { + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.3", + "esrecurse": "4.2.1", + "estraverse": "4.2.0" + } + }, + "eslint": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-2.13.1.tgz", + "integrity": "sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "concat-stream": "1.6.2", + "debug": "2.6.9", + "doctrine": "1.5.0", + "es6-map": "0.1.5", + "escope": "3.6.0", + "espree": "3.5.4", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "1.3.1", + "glob": "7.1.4", + "globals": "9.18.0", + "ignore": "3.3.10", + "imurmurhash": "0.1.4", + "inquirer": "0.12.0", + "is-my-json-valid": "2.20.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.6.1", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.11", + "mkdirp": "0.5.1", + "optionator": "0.8.2", + "path-is-absolute": "1.0.1", + "path-is-inside": "1.0.2", + "pluralize": "1.2.1", + "progress": "1.1.8", + "require-uncached": "1.0.3", + "shelljs": "0.6.1", + "strip-json-comments": "1.0.4", + "table": "3.8.3", + "text-table": "0.2.0", + "user-home": "2.0.0" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "5.7.3", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1.0.1", + "es5-ext": "0.10.50" + } + }, + "eventemitter3": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz", + "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg=" + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "file-entry-cache": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.3.1.tgz", + "integrity": "sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g=", + "dev": true, + "requires": { + "flat-cache": "1.3.4", + "object-assign": "4.1.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "graceful-fs": "4.1.15", + "rimraf": "2.6.3", + "write": "0.2.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.8", + "mime-types": "2.1.24" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "handlebars": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "dev": true, + "requires": { + "neo-async": "2.6.1", + "optimist": "0.6.1", + "source-map": "0.6.1", + "uglify-js": "3.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "commander": "2.20.0", + "is-my-json-valid": "2.20.0", + "pinkie-promise": "2.0.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "http-proxy": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.11.1.tgz", + "integrity": "sha1-cd9VdX6ALVjqgQ3yJEAZ3aBa6F0=", + "requires": { + "eventemitter3": "1.2.0", + "requires-port": "0.0.1" + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.16.1" + } + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "ansi-regex": "2.1.1", + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-width": "2.2.0", + "figures": "1.7.0", + "lodash": "4.17.11", + "readline2": "1.0.1", + "run-async": "0.1.0", + "rx-lite": "3.1.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "through": "2.3.8" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", + "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", + "dev": true, + "requires": { + "generate-function": "2.3.1", + "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.9", + "async": "1.5.2", + "escodegen": "1.8.1", + "esprima": "2.7.3", + "glob": "5.0.15", + "handlebars": "4.1.2", + "js-yaml": "3.6.1", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "once": "1.4.0", + "resolve": "1.1.7", + "supports-color": "3.2.3", + "which": "1.3.1", + "wordwrap": "1.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "js-yaml": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", + "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", + "dev": true, + "requires": { + "argparse": "1.0.10", + "esprima": "2.7.3" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "lcov-parse": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", + "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._basecreate": "3.0.3", + "lodash._isiterateecall": "3.0.9" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "log-driver": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.5.tgz", + "integrity": "sha1-euTsJXMC/XkNVXyxDJcQDYV7AFY=", + "dev": true + }, + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nock": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/nock/-/nock-8.2.2.tgz", + "integrity": "sha512-f4s5qR4Eg/NgaLuBYTThc/abl5mohCgIvnGdHkoqR5WgRe5amjFQTU2aia085OE8o3OAY7ZerDkRAeXfR720TA==", + "dev": true, + "requires": { + "chai": "3.5.0", + "debug": "2.6.9", + "deep-equal": "1.0.1", + "json-stringify-safe": "5.0.1", + "lodash": "4.9.0", + "mkdirp": "0.5.1", + "propagate": "0.4.0", + "qs": "6.3.2" + }, + "dependencies": { + "lodash": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.9.0.tgz", + "integrity": "sha1-TCDXQvA86F3HAODderm8q4Xm/BQ=", + "dev": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.0.9" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "0.0.10", + "wordwrap": "0.0.3" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "propagate": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-0.4.0.tgz", + "integrity": "sha1-8/zKCm/gZzanulcpZgaWF8EwtIE=", + "dev": true + }, + "proxy-from-env": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-0.0.1.tgz", + "integrity": "sha1-snxJRunm1dutt1mKZDXTAUxM/Uk=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qs": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", + "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "mute-stream": "0.0.5" + } + }, + "request": { + "version": "2.79.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", + "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", + "dev": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.8.0", + "caseless": "0.11.0", + "combined-stream": "1.0.8", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.24", + "oauth-sign": "0.8.2", + "qs": "6.3.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.4.3", + "uuid": "3.3.2" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "requires-port": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-0.0.1.tgz", + "integrity": "sha1-S0QUQR2d98hVmV3YmajHiilRwW0=" + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "7.1.4" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "shelljs": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz", + "integrity": "sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": "1.0.1" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "superagent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-2.3.0.tgz", + "integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=", + "dev": true, + "requires": { + "component-emitter": "1.3.0", + "cookiejar": "2.1.2", + "debug": "2.6.9", + "extend": "3.0.2", + "form-data": "1.0.0-rc4", + "formidable": "1.2.1", + "methods": "1.1.2", + "mime": "1.6.0", + "qs": "6.3.2", + "readable-stream": "2.3.6" + }, + "dependencies": { + "form-data": { + "version": "1.0.0-rc4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz", + "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=", + "dev": true, + "requires": { + "async": "1.5.2", + "combined-stream": "1.0.8", + "mime-types": "2.1.24" + } + } + } + }, + "supertest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-2.0.1.tgz", + "integrity": "sha1-oFgIHXiPFRXUcA11Aogea3WeRM0=", + "dev": true, + "requires": { + "methods": "1.1.2", + "superagent": "2.3.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.11", + "slice-ansi": "0.0.4", + "string-width": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz", + "integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", + "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "dev": true, + "optional": true, + "requires": { + "commander": "2.20.0", + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } +} diff --git a/cors-anywhere/package.json b/cors-anywhere/package.json new file mode 100644 index 0000000..4e034db --- /dev/null +++ b/cors-anywhere/package.json @@ -0,0 +1,51 @@ +{ + "name": "cors-anywhere", + "version": "0.4.1", + "description": "CORS Anywhere is a reverse proxy which adds CORS headers to the proxied request. Request URL is taken from the path", + "license": "MIT", + "author": "Rob Wu ", + "repository": { + "type": "git", + "url": "https://github.com/Rob--W/cors-anywhere.git" + }, + "bugs": { + "url": "https://github.com/Rob--W/cors-anywhere/issues/", + "email": "rob@robwu.nl" + }, + "keywords": [ + "cors", + "cross-domain", + "http-proxy", + "proxy", + "heroku" + ], + "main": "./lib/cors-anywhere.js", + "files": [ + "lib/", + "test/", + "Procfile", + "demo.html", + "server.js" + ], + "dependencies": { + "http-proxy": "1.11.1", + "proxy-from-env": "0.0.1" + }, + "devDependencies": { + "coveralls": "^2.11.6", + "eslint": "^2.2.0", + "istanbul": "^0.4.2", + "lolex": "^1.5.0", + "mocha": "^3.4.2", + "nock": "^8.2.1", + "supertest": "^2.0.1" + }, + "scripts": { + "lint": "eslint .", + "test": "mocha ./test/test*.js --reporter spec", + "test-coverage": "istanbul cover ./node_modules/.bin/_mocha -- test/test.js test/test-ratelimit.js --reporter spec" + }, + "engines": { + "node": ">=0.10.0" + } +} diff --git a/cors-anywhere/server.js b/cors-anywhere/server.js new file mode 100644 index 0000000..bdc2c02 --- /dev/null +++ b/cors-anywhere/server.js @@ -0,0 +1,66 @@ +// Listen on a specific host via the HOST environment variable +var host = process.env.HOST || '0.0.0.0'; +// Listen on a specific port via the PORT environment variable +var port = process.env.PORT || 8088; + +// Grab the blacklist from the command-line so that we can update the blacklist without deploying +// again. CORS Anywhere is open by design, and this blacklist is not used, except for countering +// immediate abuse (e.g. denial of service). If you want to block all origins except for some, +// use originWhitelist instead. +var originBlacklist = parseEnvList(process.env.CORSANYWHERE_BLACKLIST); +var originWhitelist = parseEnvList(process.env.CORSANYWHERE_WHITELIST); +function parseEnvList(env) { + if (!env) { + return []; + } + return env.split(','); +} + +// Set up rate-limiting to avoid abuse of the public CORS Anywhere server. +var checkRateLimit = require('./lib/rate-limit')(process.env.CORSANYWHERE_RATELIMIT); +// console.log(checkRateLimit); + +// var cors_proxy = require('./lib/cors-anywhere'); +// cors_proxy.createServer({ +// originBlacklist: originBlacklist, +// originWhitelist: originWhitelist, +// // requireHeader: ['origin', 'x-requested-with'], +// // checkRateLimit: checkRateLimit, +// removeHeaders: [ +// 'cookie', +// 'cookie2', +// // Strip Heroku-specific headers +// 'x-heroku-queue-wait-time', +// 'x-heroku-queue-depth', +// 'x-heroku-dynos-in-use', +// 'x-request-start', +// ], +// redirectSameOrigin: true, +// httpProxyOptions: { +// // Do not add X-Forwarded-For, etc. headers, because Heroku already adds it. +// xfwd: false, +// }, +// }).listen(port, host, function() { +// console.log('Running CORS Anywhere on ' + host + ':' + port); +// }); + + +var fs = require('fs'); +var host = process.env.HOST || '0.0.0.0'; +var port = process.env.PORT || 8088; + +var cors_proxy = require('./lib/cors-anywhere'); +cors_proxy.createServer({ + httpsOptions: { + key: fs.readFileSync('/etc/letsencrypt/live/zinomedia.de/privkey.pem'), + cert: fs.readFileSync('/etc/letsencrypt/live/zinomedia.de/fullchain.pem') + }, + // originWhitelist: ['https://heise.de'], + originWhitelist: ['https://zinomedia.de' , 'https://purchase.tickets.com', 'http://purchase.tickets.com', 'http://zinomedia.de', 'https://purchase.tickets.zinomedia.de', 'http://purchase.tickets.zinomedia.de', 'http://seatmap-testing.zinomedia.de', 'https://seatmap-testing.zinomedia.de'], + // requireHeader: ['origin', 'x-requested-with'], + requireHeader: [], + removeHeaders: ['cookie', 'cookie2'], + // checkRateLimit: checkRateLimit, +}).listen(port, host, function() { + console.log('Running CORS Anywhere on ' + host + ':' + port); +}); \ No newline at end of file diff --git a/cors-anywhere/test/cert.pem b/cors-anywhere/test/cert.pem new file mode 100644 index 0000000..3684422 --- /dev/null +++ b/cors-anywhere/test/cert.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBsTCCARoCCQDp0DuED0RAJzANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJj +b3JzLWFueXdoZXJlIHRlc3QwHhcNMTUwNTA2MDcyOTM1WhcNMTUwNjA1MDcyOTM1 +WjAdMRswGQYDVQQDDBJjb3JzLWFueXdoZXJlIHRlc3QwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBALzTF5ClJKvkB6h9h7kLORV+mMV3ySDs+oGZn0NgXM+yb9Zh +69r5e95zZJl/V432LFdy0hkEcVteUkC2REWG8D4COGfiwWsXyZdaP1qqLpDpPAMm +v6xFHjW6rVuxzfr4GUjE0Zh9Fg2R2SbtCOcHS/LZoDVOqOvn6+urP6XFY4aFAgMB +AAEwDQYJKoZIhvcNAQELBQADgYEAYXMhS8ouff/c8lSUUs/CLh010cj5RPk/ivS7 +aN2PArzQ6pZvhpgJKf7XAQksBtLYYZMzIpG6W8zhPSbqzly7lELAdE+sxcbbfu8A +FMjNVFQ2Fm1c8ImX8qpE3nhVrPAiwfPjGBqKHTl730gvbh1XH9TC4O4dZcbEomX3 +5MsxQfc= +-----END CERTIFICATE----- diff --git a/cors-anywhere/test/child.js b/cors-anywhere/test/child.js new file mode 100644 index 0000000..c95fc0a --- /dev/null +++ b/cors-anywhere/test/child.js @@ -0,0 +1,64 @@ +// When this module is loaded, CORS Anywhere is started. +// Then, a request is generated to warm up the server (just in case). +// Then the base URL of CORS Anywhere is sent to the parent process. +// ... +// When the parent process is done, it sends an empty message to this child +// process, which in turn records the change in used heap space. +// The difference in heap space is finally sent back to the parent process. +// ... +// The parent process should then kill this child. + +process.on('uncaughtException', function(e) { + console.error('Uncaught exception in child process: ' + e); + console.error(e.stack); + process.exit(-1); +}); + +// Invoke memoryUsage() without using its result to make sure that any internal +// datastructures that supports memoryUsage() is initialized and won't pollute +// the memory usage measurement later on. +process.memoryUsage(); + +var heapUsedStart = 0; +function getMemoryUsage(callback) { + // Note: Requires --expose-gc + // 6 is the minimum amount of gc() calls before calling gc() again does not + // reduce memory any more. + for (var i = 0; i < 6; ++i) { + global.gc(); + } + callback(process.memoryUsage().heapUsed); +} + +var server; +if (process.argv.indexOf('use-http-instead-of-cors-anywhere') >= 0) { + server = require('http').createServer(function(req, res) { res.end(); }); +} else { + server = require('../').createServer(); +} + +server.listen(0, function() { + // Perform 1 request to warm up. + require('http').get({ + hostname: '127.0.0.1', + port: server.address().port, + path: '/http://invalid:99999', + agent: false, + }, function() { + notifyParent(); + }); + + function notifyParent() { + getMemoryUsage(function(usage) { + heapUsedStart = usage; + process.send('http://127.0.0.1:' + server.address().port + '/'); + }); + } +}); + +process.once('message', function() { + getMemoryUsage(function(heapUsedEnd) { + var delta = heapUsedEnd - heapUsedStart; + process.send(delta); + }); +}); diff --git a/cors-anywhere/test/customHelp.html b/cors-anywhere/test/customHelp.html new file mode 100644 index 0000000..2ced07a --- /dev/null +++ b/cors-anywhere/test/customHelp.html @@ -0,0 +1,7 @@ + + + + +

Custom HTML help!!

+ + \ No newline at end of file diff --git a/cors-anywhere/test/customHelp.txt b/cors-anywhere/test/customHelp.txt new file mode 100644 index 0000000..5d61f64 --- /dev/null +++ b/cors-anywhere/test/customHelp.txt @@ -0,0 +1 @@ +Server is OK!! diff --git a/cors-anywhere/test/dummy.txt b/cors-anywhere/test/dummy.txt new file mode 100644 index 0000000..eaf5f75 --- /dev/null +++ b/cors-anywhere/test/dummy.txt @@ -0,0 +1 @@ +dummy content diff --git a/cors-anywhere/test/key.pem b/cors-anywhere/test/key.pem new file mode 100644 index 0000000..5680ec9 --- /dev/null +++ b/cors-anywhere/test/key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC80xeQpSSr5AeofYe5CzkVfpjFd8kg7PqBmZ9DYFzPsm/WYeva ++Xvec2SZf1eN9ixXctIZBHFbXlJAtkRFhvA+Ajhn4sFrF8mXWj9aqi6Q6TwDJr+s +RR41uq1bsc36+BlIxNGYfRYNkdkm7QjnB0vy2aA1Tqjr5+vrqz+lxWOGhQIDAQAB +AoGBAISy8OelN01Zlowxk/VWTsqtSl3UHdP21uHHfWaTTQZlxzTpYiBknkmp3LQH +CxfoPidCuSX9ulBUzAdQUFBwUVp8wyPIRjpNyRiD58dLNxG0G+OACqnLxNWqIf6F +vS3UqrRGIA5u+GSz+0g3DAeVA5JmsAyHQGkJsh3pcuD8/7wNAkEA7MScGfySy9td +dDBekVU5/GaVg4DA4ELtDNfa99ARB89XP0ps/XrOPEL9yxTjWIHH+qxuhpfG6zGN +ouxZlvBT9wJBAMwpig4A4JE8M8pBDwMY4213gud8B1grQTbhz5bv51aTaIEQFcxw +sGfEmAfVToI+kVTrdFggy42YCSMSvwuF4mMCQQDZHkqPwf/TlSwT2i8+UstD28aL +uswkWvsKZf9UdKbJZKd7UIK1x6HLvRsC2frJNOnvw6PvJMuy7dQWbWqScXxtAkBv +/5msdO68vbnriiUiHdUliBpXwsKEq7Xq1ZV7x7+wzszVgG106ZzcUAzWvz2CVbCE +VWZNsi/4TR82DmKff6LhAkBA/xceWaZjxh5dkWkIrMFWd2GFhGlpfwYw7oELwRL8 +RYXzc1Mr2fDdZDgwgjg67JQqIhOQ3E4RGKPgZ+E7Pk3/ +-----END RSA PRIVATE KEY----- diff --git a/cors-anywhere/test/setup.js b/cors-anywhere/test/setup.js new file mode 100644 index 0000000..011b577 --- /dev/null +++ b/cors-anywhere/test/setup.js @@ -0,0 +1,152 @@ +var nock = require('nock'); +if (parseInt(process.versions.node, 10) >= 8) { + // See DEP0066 at https://nodejs.org/api/deprecations.html. + // _headers and _headerNames have been removed from Node v8, which causes + // nock <= 9.0.13 to fail. The snippet below monkey-patches the library, see + // https://github.com/node-nock/nock/pull/929/commits/f6369d0edd2a172024124f + // for the equivalent logic without proxies. + Object.defineProperty(require('http').ClientRequest.prototype, '_headers', { + get: function() { + var request = this; + // eslint-disable-next-line no-undef + return new Proxy(request.getHeaders(), { + set: function(target, property, value) { + request.setHeader(property, value); + return true; + }, + }); + }, + set: function() { + // Ignore. + }, + }); +} + +nock.enableNetConnect('127.0.0.1'); + +function echoheaders(origin) { + nock(origin) + .persist() + .get('/echoheaders') + .reply(function() { + var headers = this.req.headers; + var excluded_headers = [ + 'accept-encoding', + 'user-agent', + 'connection', + // Remove this header since its value is platform-specific. + 'x-forwarded-for', + 'test-include-xfwd', + ]; + if (!('test-include-xfwd' in headers)) { + excluded_headers.push('x-forwarded-port'); + excluded_headers.push('x-forwarded-proto'); + } + var response = {}; + Object.keys(headers).forEach(function(name) { + if (excluded_headers.indexOf(name) === -1) { + response[name] = headers[name]; + } + }); + return response; + }); +} + +nock('http://example.com') + .persist() + .get('/') + .reply(200, 'Response from example.com') + + .post('/echopost') + .reply(200, function(uri, requestBody) { + return requestBody; + }) + + .get('/setcookie') + .reply(200, '', { + 'Set-Cookie': 'x', + 'Set-Cookie2': 'y', + 'Set-Cookie3': 'z', // This is not a special cookie setting header. + }) + + .get('/redirecttarget') + .reply(200, 'redirect target', { + 'Some-header': 'value', + }) + + .head('/redirect') + .reply(302, '', { + Location: '/redirecttarget', + }) + + .get('/redirect') + .reply(302, 'redirecting...', { + 'header at redirect': 'should not be here', + Location: '/redirecttarget', + }) + + .get('/redirectposttarget') + .reply(200, 'post target') + + .post('/redirectposttarget') + .reply(200, 'post target (POST)') + + .post('/redirectpost') + .reply(302, 'redirecting...', { + Location: '/redirectposttarget', + }) + + .post('/redirect307') + .reply(307, 'redirecting...', { + Location: '/redirectposttarget', + }) + + .get('/redirect2redirect') + .reply(302, 'redirecting to redirect...', { + Location: '/redirect', + }) + + .get('/redirectloop') + .reply(302, 'redirecting ad infinitum...', { + Location: '/redirectloop', + }) + + .get('/redirectwithoutlocation') + .reply(302, 'maybe found') + + .get('/proxyerror') + .replyWithError('throw node') +; + +nock('https://example.com') + .persist() + .get('/') + .reply(200, 'Response from https://example.com') +; + +nock('http://example.com.com') + .persist() + .get('/') + .reply(200, 'Response from example.com.com') +; + +nock('http://example.com:1234') + .persist() + .get('/') + .reply(200, 'Response from example.com:1234') +; + +nock('http://prefix.example.com') + .persist() + .get('/') + .reply(200, 'Response from prefix.example.com') +; + +echoheaders('http://example.com'); +echoheaders('http://example.com:1337'); +echoheaders('https://example.com'); +echoheaders('https://example.com:1337'); + +nock('http://robots.txt') + .get('/') + .reply(200, 'this is http://robots.txt'); diff --git a/cors-anywhere/test/test-examples.js b/cors-anywhere/test/test-examples.js new file mode 100644 index 0000000..94759d1 --- /dev/null +++ b/cors-anywhere/test/test-examples.js @@ -0,0 +1,133 @@ +/** + * CORS Anywhere is designed for use as a standalone server. Sometimes you want + * to have extra functionality on top of the default CORS server. If it may be + * useful to others, please open a feature request on the issue tracker at + * https://github.com/Rob--W/cors-anywhere/issues. + * + * If it is only useful to your application, look below for some examples. + * These examples are provided as-is without guarantees. Use at your own risk. + */ + +/* eslint-env mocha */ +require('./setup'); + +var createServer = require('../').createServer; +var assert = require('assert'); +var request = require('supertest'); + +var http = require('http'); + +describe('Examples', function() { + // Note: In the examples below we don't listen on any port after calling + // createServer() because it is not needed to start listening on a port if the + // CORS Anywhere is only used internally. + + // And normally you have to listen on some port, like this: + // + // http_server.listen(port_number); + // + // But in these test, the call to request() automatically handles that part so + // the examples don't have an explicit .listen() call. + + it('Rewrite proxy URL', function(done) { + var cors_anywhere = createServer(); + + var http_server = http.createServer(function(req, res) { + // For testing, check whether req.url is the same as what we input below. + assert.strictEqual(req.url, '/dummy-for-testing'); + + // Basic example: Always proxy example.com. + req.url = '/http://example.com'; + + cors_anywhere.emit('request', req, res); + }); + + request(http_server) + .get('/dummy-for-testing') + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/') + .expect(200, 'Response from example.com', done); + }); + + it('Transform response to uppercase (streaming)', function(done) { + var cors_anywhere = createServer(); + + var http_server = http.createServer(function(req, res) { + var originalWrite = res.write; + + res.write = function(data, encoding, callback) { + if (Buffer.isBuffer(data)) { + data = data.toString(); + } + + assert.strictEqual(typeof data, 'string'); + + // This example shows how to transform the response to upper case. + data = data.toUpperCase(); + + originalWrite.call(this, data, encoding, callback); + }; + + cors_anywhere.emit('request', req, res); + }); + + request(http_server) + .get('/example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/') + .expect(200, 'RESPONSE FROM EXAMPLE.COM', done); + }); + + it('Transform response to uppercase (buffered)', function(done) { + var cors_anywhere = createServer(); + + var http_server = http.createServer(function(req, res) { + var originalWrite = res.write; + var originalEnd = res.end; + + var buffers = []; + + res.write = function(data, encoding, callback) { + assert.ok(Buffer.isBuffer(data) || typeof data === 'string'); + + buffers.push(data); + if (callback) { + process.nextTick(callback, null); + } + }; + res.end = function(data, encoding, callback) { + if (data) { + this.write(data, encoding); + } + + // After calling .end(), .write shouldn't be called any more. So let's + // restore it so that the default error handling for writing to closed + // streams would occur. + this.write = originalWrite; + + // Combine all chunks. Note that we're assuming that all chunks are + // utf8 strings or buffers whose content is utf8-encoded. If this + // assumption is not true, then you have to update the .write method + // above. + data = buffers.join(''); + + // This example shows how to transform the response to upper case. + data = data.toUpperCase(); + + // .end should be called once, so let's restore it so that any default + // error handling occurs if it occurs again. + this.end = originalEnd; + this.end(data, 'utf8', callback); + }; + + cors_anywhere.emit('request', req, res); + }); + + request(http_server) + .get('/example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/') + .expect(200, 'RESPONSE FROM EXAMPLE.COM', done); + }); +}); + diff --git a/cors-anywhere/test/test-memory.js b/cors-anywhere/test/test-memory.js new file mode 100644 index 0000000..15c85f3 --- /dev/null +++ b/cors-anywhere/test/test-memory.js @@ -0,0 +1,111 @@ +/* eslint-env mocha */ +// Run this specific test using: +// npm test -- -f memory +var http = require('http'); +var path = require('path'); +var url = require('url'); +var fork = require('child_process').fork; + +describe('memory usage', function() { + var cors_api_url; + + var server; + var cors_anywhere_child; + before(function(done) { + server = http.createServer(function(req, res) { + res.writeHead(200); + res.end(); + }).listen(0, function() { + done(); + }); + }); + + after(function(done) { + server.close(function() { + done(); + }); + }); + + beforeEach(function(done) { + var cors_module_path = path.join(__dirname, 'child'); + var args = []; + // Uncomment this if you want to compare the performance of CORS Anywhere + // with the standard no-op http module. + // args.push('use-http-instead-of-cors-anywhere'); + cors_anywhere_child = fork(cors_module_path, args, { + execArgv: ['--expose-gc'], + }); + cors_anywhere_child.once('message', function(cors_url) { + cors_api_url = cors_url; + done(); + }); + }); + + afterEach(function() { + cors_anywhere_child.kill(); + }); + + /** + * Perform N CORS Anywhere proxy requests to a simple test server. + * + * @param {number} n - number of repetitions. + * @param {number} requestSize - Approximate size of request in kilobytes. + * @param {number} memMax - Expected maximum memory usage in kilobytes. + * @param {function} done - Upon success, called without arguments. + * Upon failure, called with the error as parameter. + */ + function performNRequests(n, requestSize, memMax, done) { + var remaining = n; + var request = url.parse( + cors_api_url + 'http://127.0.0.1:' + server.address().port); + request.agent = false; // Force Connection: Close + request.headers = { + 'Long-header': new Array(requestSize * 1e3).join('x'), + }; + (function requestAgain() { + if (remaining-- === 0) { + cors_anywhere_child.once('message', function(memory_usage_delta) { + console.log('Memory usage delta: ' + memory_usage_delta + + ' (' + n + ' requests of ' + requestSize + ' kb each)'); + if (memory_usage_delta > memMax * 1e3) { + // Note: Even if this error is reached, always profile (e.g. using + // node-inspector) whether it is a true leak, and not e.g. noise + // caused by the implementation of V8/Node.js. + // Uncomment args.push('use-http-instead-of-cors-anywhere') at the + // fork() call to get a sense of what's normal. + throw new Error('Possible memory leak: ' + memory_usage_delta + + ' bytes was not released, which exceeds the ' + memMax + + ' kb limit by ' + + Math.round(memory_usage_delta / memMax / 10 - 100) + '%.'); + } + done(); + }); + cors_anywhere_child.send(null); + return; + } + http.request(request, function() { + requestAgain(); + }).on('error', function(error) { + done(error); + }).end(); + })(); + } + + it('100 GET requests 50k', function(done) { + // This test is just for comparison with the following tests. + performNRequests(100, 50, 1200, done); + }); + + // 100x 1k and 100x 50k for comparison. + // Both should use about the same amount of memory if there is no leak. + it('1000 GET requests 1k', function(done) { + // Every request should complete within 10ms. + this.timeout(1000 * 10); + performNRequests(1000, 1, 2000, done); + }); + it('1000 GET requests 50k', function(done) { + // Every request should complete within 10ms. + this.timeout(1000 * 10); + performNRequests(1000, 50, 2000, done); + }); +}); diff --git a/cors-anywhere/test/test-ratelimit.js b/cors-anywhere/test/test-ratelimit.js new file mode 100644 index 0000000..84aa8e9 --- /dev/null +++ b/cors-anywhere/test/test-ratelimit.js @@ -0,0 +1,231 @@ +/* eslint-env mocha */ + +var createRateLimitChecker = require('../lib/rate-limit'); + +var lolex = require('lolex'); +var assert = require('assert'); + +function assertNotLimited(rateLimitReturnValue) { + if (rateLimitReturnValue) { + assert.fail('Expected no limit, but got ' + rateLimitReturnValue); + } +} + +function assertLimited(rateLimitReturnValue, limit, period) { + var msg; + if (period === 1) { + msg = 'The number of requests is limited to ' + limit + ' per minute. '; + } else { + msg = 'The number of requests is limited to ' + limit + ' per ' + period + ' minutes. '; + } + msg += 'Please self-host CORS Anywhere if you need more quota. ' + + 'See https://github.com/Rob--W/cors-anywhere#demo-server'; + assert.equal(rateLimitReturnValue, msg); +} + +describe('Rate limit', function() { + var clock; + beforeEach(function() { + clock = lolex.install(); + }); + afterEach(function() { + clock.uninstall(); + }); + it('is unlimited by default', function() { + var checkRateLimit = createRateLimitChecker(); + assertNotLimited(checkRateLimit('http://example.com')); + assertNotLimited(checkRateLimit('https://example.com')); + assertNotLimited(checkRateLimit('https://example.com:1234')); + + checkRateLimit = createRateLimitChecker(''); + assertNotLimited(checkRateLimit('http://example.com')); + + checkRateLimit = createRateLimitChecker(' '); + assertNotLimited(checkRateLimit('http://example.com')); + }); + + it('zero per minute / 5 minutes', function() { + var checkRateLimit = createRateLimitChecker('0 1'); + assertLimited(checkRateLimit('http://example.com'), 0, 1); + assertLimited(checkRateLimit('https://example.com'), 0, 1); + + checkRateLimit = createRateLimitChecker('0 5'); + assertLimited(checkRateLimit('http://example.com'), 0, 5); + assertLimited(checkRateLimit('https://example.com'), 0, 5); + }); + + it('one per minute', function() { + var checkRateLimit = createRateLimitChecker('1 1'); + assertNotLimited(checkRateLimit('http://example.com')); + assertLimited(checkRateLimit('http://example.com'), 1, 1); + assertNotLimited(checkRateLimit('http://example.com:1234')); + assertLimited(checkRateLimit('http://example.com:1234'), 1, 1); + + clock.tick(59000); + assertLimited(checkRateLimit('http://example.com'), 1, 1); + + clock.tick(1000); + assertNotLimited(checkRateLimit('http://example.com')); + assertLimited(checkRateLimit('http://example.com'), 1, 1); + assertNotLimited(checkRateLimit('http://example.com:1234')); + assertLimited(checkRateLimit('http://example.com:1234'), 1, 1); + }); + + it('different domains, one per minute', function() { + var checkRateLimit = createRateLimitChecker('1 1'); + assertNotLimited(checkRateLimit('http://example.com')); + assertNotLimited(checkRateLimit('http://example.net')); + assertNotLimited(checkRateLimit('http://wexample.net')); + assertNotLimited(checkRateLimit('http://xample.net')); + assertNotLimited(checkRateLimit('http://www.example.net')); + assertLimited(checkRateLimit('http://example.com'), 1, 1); + assertLimited(checkRateLimit('http://example.net'), 1, 1); + assertLimited(checkRateLimit('http://wexample.net'), 1, 1); + assertLimited(checkRateLimit('http://xample.net'), 1, 1); + assertLimited(checkRateLimit('http://www.example.net'), 1, 1); + + clock.tick(60000); // 1 minute + assertNotLimited(checkRateLimit('http://example.com')); + assertNotLimited(checkRateLimit('http://example.net')); + assertNotLimited(checkRateLimit('http://wexample.net')); + assertNotLimited(checkRateLimit('http://xample.net')); + assertNotLimited(checkRateLimit('http://www.example.net')); + }); + + it('unlimited domains, string', function() { + var checkRateLimit = createRateLimitChecker('1 2 example.com'); + assertNotLimited(checkRateLimit('http://example.com')); + assertNotLimited(checkRateLimit('http://example.com')); + + assertNotLimited(checkRateLimit('http://wexample.com')); + assertNotLimited(checkRateLimit('http://xample.com')); + assertNotLimited(checkRateLimit('http://www.example.com')); + assertLimited(checkRateLimit('http://wexample.com'), 1, 2); + assertLimited(checkRateLimit('http://xample.com'), 1, 2); + assertLimited(checkRateLimit('http://www.example.com'), 1, 2); + }); + + it('unlimited domains, RegExp', function() { + var checkRateLimit = createRateLimitChecker('1 2 /example\\.com/'); + assertNotLimited(checkRateLimit('http://example.com')); + assertNotLimited(checkRateLimit('http://example.com')); + + assertNotLimited(checkRateLimit('http://wexample.com')); + assertNotLimited(checkRateLimit('http://xample.com')); + assertNotLimited(checkRateLimit('http://www.example.com')); + assertLimited(checkRateLimit('http://wexample.com'), 1, 2); + assertLimited(checkRateLimit('http://xample.com'), 1, 2); + assertLimited(checkRateLimit('http://www.example.com'), 1, 2); + }); + + it('multiple domains, string', function() { + var checkRateLimit = createRateLimitChecker('1 2 a b cc '); + assertNotLimited(checkRateLimit('http://a')); + assertNotLimited(checkRateLimit('http://a')); + assertNotLimited(checkRateLimit('http://b')); + assertNotLimited(checkRateLimit('http://b')); + assertNotLimited(checkRateLimit('http://cc')); + assertNotLimited(checkRateLimit('http://cc')); + assertNotLimited(checkRateLimit('http://c')); + assertLimited(checkRateLimit('http://c'), 1, 2); + }); + + it('multiple domains, RegExp', function() { + var checkRateLimit = createRateLimitChecker('1 2 /a/ /b/ /cc/ '); + assertNotLimited(checkRateLimit('http://a')); + assertNotLimited(checkRateLimit('http://a')); + assertNotLimited(checkRateLimit('http://b')); + assertNotLimited(checkRateLimit('http://b')); + assertNotLimited(checkRateLimit('http://cc')); + assertNotLimited(checkRateLimit('http://cc')); + assertNotLimited(checkRateLimit('http://ccc')); + assertLimited(checkRateLimit('http://ccc'), 1, 2); + }); + + it('multiple domains, string and RegExp', function() { + var checkRateLimit = createRateLimitChecker('1 2 a /b/'); + assertNotLimited(checkRateLimit('http://a')); + assertNotLimited(checkRateLimit('http://a')); + assertNotLimited(checkRateLimit('http://b')); + assertNotLimited(checkRateLimit('http://b')); + assertNotLimited(checkRateLimit('http://ab')); + assertLimited(checkRateLimit('http://ab'), 1, 2); + }); + + it('multiple domains, RegExp and string', function() { + var checkRateLimit = createRateLimitChecker('1 2 /a/ b'); + assertNotLimited(checkRateLimit('http://a')); + assertNotLimited(checkRateLimit('http://a')); + assertNotLimited(checkRateLimit('http://b')); + assertNotLimited(checkRateLimit('http://b')); + assertNotLimited(checkRateLimit('http://ab')); + assertLimited(checkRateLimit('http://ab'), 1, 2); + }); + + it('wildcard subdomains', function() { + var checkRateLimit = createRateLimitChecker('0 1 /(.*\\.)?example\\.com/'); + assertNotLimited(checkRateLimit('http://example.com')); + assertNotLimited(checkRateLimit('http://www.example.com')); + assertLimited(checkRateLimit('http://xexample.com'), 0, 1); + assertLimited(checkRateLimit('http://example.com.br'), 0, 1); + }); + + it('wildcard ports', function() { + var checkRateLimit = createRateLimitChecker('0 1 /example\\.com(:\\d{1,5})?/'); + assertNotLimited(checkRateLimit('http://example.com')); + assertNotLimited(checkRateLimit('http://example.com:1234')); + }); + + it('empty host', function() { + var checkRateLimit = createRateLimitChecker('0 1'); + assertLimited(checkRateLimit(''), 0, 1); + // Empty host actually means empty origin. But let's also test for 'http://'. + assertLimited(checkRateLimit('http://'), 0, 1); + + checkRateLimit = createRateLimitChecker('0 1 '); + assertLimited(checkRateLimit(''), 0, 1); + assertLimited(checkRateLimit('http://'), 0, 1); + + checkRateLimit = createRateLimitChecker('0 1 //'); + assertNotLimited(checkRateLimit('')); + assertNotLimited(checkRateLimit('http://')); + }); + + it('null origin', function() { + var checkRateLimit = createRateLimitChecker('0 1'); + assertLimited(checkRateLimit('null'), 0, 1); + assertLimited(checkRateLimit('http://null'), 0, 1); + + checkRateLimit = createRateLimitChecker('0 1 null'); + assertNotLimited(checkRateLimit('null')); + assertNotLimited(checkRateLimit('http://null')); + + checkRateLimit = createRateLimitChecker('0 1 /null/'); + assertNotLimited(checkRateLimit('null')); + assertNotLimited(checkRateLimit('http://null')); + }); + + it('case-insensitive', function() { + var checkRateLimit = createRateLimitChecker('0 1 NULL'); + assertNotLimited(checkRateLimit('null')); + assertNotLimited(checkRateLimit('http://null')); + + checkRateLimit = createRateLimitChecker('0 1 /NULL/'); + assertNotLimited(checkRateLimit('null')); + assertNotLimited(checkRateLimit('http://null')); + }); + + it('bad input', function() { + assert.throws(function() { + createRateLimitChecker('0 1 /'); + }, /Invalid CORSANYWHERE_RATELIMIT\. Regex at index 0 must start and end with a slash \("\/"\)\./); + + assert.throws(function() { + createRateLimitChecker('0 1 a /'); + }, /Invalid CORSANYWHERE_RATELIMIT\. Regex at index 1 must start and end with a slash \("\/"\)\./); + + assert.throws(function() { + createRateLimitChecker('0 1 /(/'); + }, /Invalid regular expression/); + }); +}); diff --git a/cors-anywhere/test/test.js b/cors-anywhere/test/test.js new file mode 100644 index 0000000..66b2008 --- /dev/null +++ b/cors-anywhere/test/test.js @@ -0,0 +1,1080 @@ +/* eslint-env mocha */ +require('./setup'); + +var createServer = require('../').createServer; +var request = require('supertest'); +var path = require('path'); +var http = require('http'); +var fs = require('fs'); +var assert = require('assert'); + +var helpTextPath = path.join(__dirname, '../lib/help.txt'); +var helpText = fs.readFileSync(helpTextPath, {encoding: 'utf8'}); + +request.Test.prototype.expectJSON = function(json, done) { + this.expect(function(res) { + // Assume that the response can be parsed as JSON (otherwise it throws). + var actual = JSON.parse(res.text); + assert.deepEqual(actual, json); + }); + return done ? this.end(done) : this; +}; + +request.Test.prototype.expectNoHeader = function(header, done) { + this.expect(function(res) { + if (header.toLowerCase() in res.headers) { + return 'Unexpected header in response: ' + header; + } + }); + return done ? this.end(done) : this; +}; + +var cors_anywhere; +var cors_anywhere_port; +function stopServer(done) { + cors_anywhere.close(function() { + done(); + }); + cors_anywhere = null; +} + +describe('Basic functionality', function() { + before(function() { + cors_anywhere = createServer(); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /', function(done) { + request(cors_anywhere) + .get('/') + .type('text/plain') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, helpText, done); + }); + + it('GET /iscorsneeded', function(done) { + request(cors_anywhere) + .get('/iscorsneeded') + .expectNoHeader('access-control-allow-origin', done); + }); + + it('GET /example.com:65536', function(done) { + request(cors_anywhere) + .get('/example.com:65536') + .expect('Access-Control-Allow-Origin', '*') + .expect(400, 'Port number too large: 65536', done); + }); + + it('GET /favicon.ico', function(done) { + request(cors_anywhere) + .get('/favicon.ico') + .expect('Access-Control-Allow-Origin', '*') + .expect(404, 'Invalid host: favicon.ico', done); + }); + + it('GET /robots.txt', function(done) { + request(cors_anywhere) + .get('/robots.txt') + .expect('Access-Control-Allow-Origin', '*') + .expect(404, 'Invalid host: robots.txt', done); + }); + + it('GET /http://robots.txt should be proxied', function(done) { + request(cors_anywhere) + .get('/http://robots.txt') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'this is http://robots.txt', done); + }); + + it('GET /example.com', function(done) { + request(cors_anywhere) + .get('/example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/') + .expect(200, 'Response from example.com', done); + }); + + it('GET /example.com:80', function(done) { + request(cors_anywhere) + .get('/example.com:80') + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com:80/') + .expect(200, 'Response from example.com', done); + }); + + it('GET /example.com:443', function(done) { + request(cors_anywhere) + .get('/example.com:443') + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'https://example.com:443/') + .expect(200, 'Response from https://example.com', done); + }); + + it('GET //example.com', function(done) { + // '/example.com' is an invalid URL. + request(cors_anywhere) + .get('//example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, helpText, done); + }); + + it('GET ///example.com', function(done) { + // API base URL (with trailing slash) + '//example.com' + request(cors_anywhere) + .get('///example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/') + .expect(200, 'Response from example.com', done); + }); + + it('GET /http://example.com', function(done) { + request(cors_anywhere) + .get('/http://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/') + .expect(200, 'Response from example.com', done); + }); + + it('POST plain text', function(done) { + request(cors_anywhere) + .post('/example.com/echopost') + .send('{"this is a request body & should not be mangled":1.00}') + .expect('Access-Control-Allow-Origin', '*') + .expect('{"this is a request body & should not be mangled":1.00}', done); + }); + + it('POST file', function(done) { + request(cors_anywhere) + .post('/example.com/echopost') + .attach('file', path.join(__dirname, 'dummy.txt')) + .expect('Access-Control-Allow-Origin', '*') + .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 + }); + + it('HEAD with redirect should be followed', function(done) { + // Redirects are automatically followed, because redirects are to be + // followed automatically per specification regardless of the HTTP verb. + request(cors_anywhere) + .head('/example.com/redirect') + .redirects(0) + .expect('Access-Control-Allow-Origin', '*') + .expect('some-header', 'value') + .expect('x-request-url', 'http://example.com/redirect') + .expect('x-cors-redirect-1', '302 http://example.com/redirecttarget') + .expect('x-final-url', 'http://example.com/redirecttarget') + .expect('access-control-expose-headers', /some-header,x-final-url/) + .expectNoHeader('header at redirect') + .expect(200, undefined, done); + }); + + it('GET with redirect should be followed', function(done) { + request(cors_anywhere) + .get('/example.com/redirect') + .redirects(0) + .expect('Access-Control-Allow-Origin', '*') + .expect('some-header', 'value') + .expect('x-request-url', 'http://example.com/redirect') + .expect('x-cors-redirect-1', '302 http://example.com/redirecttarget') + .expect('x-final-url', 'http://example.com/redirecttarget') + .expect('access-control-expose-headers', /some-header,x-final-url/) + .expectNoHeader('header at redirect') + .expect(200, 'redirect target', done); + }); + + it('GET with redirect loop should interrupt', function(done) { + request(cors_anywhere) + .get('/example.com/redirectloop') + .redirects(0) + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/redirectloop') + .expect('x-cors-redirect-1', '302 http://example.com/redirectloop') + .expect('x-cors-redirect-2', '302 http://example.com/redirectloop') + .expect('x-cors-redirect-3', '302 http://example.com/redirectloop') + .expect('x-cors-redirect-4', '302 http://example.com/redirectloop') + .expect('x-cors-redirect-5', '302 http://example.com/redirectloop') + .expect('Location', /^http:\/\/127.0.0.1:\d+\/http:\/\/example.com\/redirectloop$/) + .expect(302, 'redirecting ad infinitum...', done); + }); + + it('POST with 302 redirect should be followed', function(done) { + request(cors_anywhere) + .post('/example.com/redirectpost') + .redirects(0) + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/redirectpost') + .expect('x-cors-redirect-1', '302 http://example.com/redirectposttarget') + .expect('x-final-url', 'http://example.com/redirectposttarget') + .expect('access-control-expose-headers', /x-final-url/) + .expect(200, 'post target', done); + }); + + it('GET with 302 redirect without Location header should not be followed', function(done) { + // There is nothing to follow, so let the browser decide what to do with it. + request(cors_anywhere) + .get('/example.com/redirectwithoutlocation') + .redirects(0) + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/redirectwithoutlocation') + .expect('x-final-url', 'http://example.com/redirectwithoutlocation') + .expect('access-control-expose-headers', /x-final-url/) + .expect(302, 'maybe found', done); + }); + + it('POST with 307 redirect should not be handled', function(done) { + // Because of implementation difficulties (having to keep the request body + // in memory), handling HTTP 307/308 redirects is deferred to the requestor. + request(cors_anywhere) + .post('/example.com/redirect307') + .redirects(0) + .expect('Access-Control-Allow-Origin', '*') + .expect('x-request-url', 'http://example.com/redirect307') + .expect('Location', /^http:\/\/127.0.0.1:\d+\/http:\/\/example.com\/redirectposttarget$/) + .expect('x-final-url', 'http://example.com/redirect307') + .expect('access-control-expose-headers', /x-final-url/) + .expect(307, 'redirecting...', done); + }); + + it('OPTIONS /', function(done) { + request(cors_anywhere) + .options('/') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, '', done); + }); + + it('OPTIONS / with Access-Control-Request-Method / -Headers', function(done) { + request(cors_anywhere) + .options('/') + .set('Access-Control-Request-Method', 'DELETE') + .set('Access-Control-Request-Headers', 'X-Tralala') + .expect('Access-Control-Allow-Origin', '*') + .expect('Access-Control-Allow-Methods', 'DELETE') + .expect('Access-Control-Allow-Headers', 'X-Tralala') + .expect(200, '', done); + }); + + it('OPTIONS //bogus', function(done) { + // The preflight request always succeeds, regardless of whether the request + // is valid. + request(cors_anywhere) + .options('//bogus') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, '', done); + }); + + it('X-Forwarded-* headers', function(done) { + request(cors_anywhere) + .get('/example.com/echoheaders') + .set('test-include-xfwd', '') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + 'x-forwarded-port': String(cors_anywhere_port), + 'x-forwarded-proto': 'http', + }, done); + }); + + it('X-Forwarded-* headers (non-standard port)', function(done) { + request(cors_anywhere) + .get('/example.com:1337/echoheaders') + .set('test-include-xfwd', '') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com:1337', + 'x-forwarded-port': String(cors_anywhere_port), + 'x-forwarded-proto': 'http', + }, done); + }); + + it('X-Forwarded-* headers (https)', function(done) { + request(cors_anywhere) + .get('/https://example.com/echoheaders') + .set('test-include-xfwd', '') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + 'x-forwarded-port': String(cors_anywhere_port), + 'x-forwarded-proto': 'http', + }, done); + }); + + it('Ignore cookies', function(done) { + request(cors_anywhere) + .get('/example.com/setcookie') + .expect('Access-Control-Allow-Origin', '*') + .expect('Set-Cookie3', 'z') + .expectNoHeader('set-cookie') + .expectNoHeader('set-cookie2', done); + }); +}); + +describe('Proxy errors', function() { + before(function() { + cors_anywhere = createServer(); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + var bad_http_server; + var bad_http_server_url; + before(function() { + bad_http_server = http.createServer(function(req, res) { + res.writeHead(418, { + 'Content-Length': 'Not a digit', + }); + res.end('This response has an invalid Content-Length header.'); + }); + bad_http_server_url = 'http://127.0.0.1:' + bad_http_server.listen(0).address().port; + }); + after(function(done) { + bad_http_server.close(function() { + done(); + }); + }); + + var bad_status_http_server; + var bad_status_http_server_url; + before(function() { + bad_status_http_server = require('net').createServer(function(socket) { + socket.setEncoding('utf-8'); + socket.on('data', function(data) { + if (data.indexOf('\r\n') >= 0) { + // Assume end of headers. + socket.write('HTTP/1.0 0\r\n'); + socket.write('Content-Length: 0\r\n'); + socket.end('\r\n'); + } + }); + }); + bad_status_http_server_url = 'http://127.0.0.1:' + bad_status_http_server.listen(0).address().port; + }); + after(function(done) { + bad_status_http_server.close(function() { + done(); + }); + }); + + var bad_tcp_server; + var bad_tcp_server_url; + before(function() { + bad_tcp_server = require('net').createServer(function(socket) { + socket.setEncoding('utf-8'); + socket.on('data', function(data) { + if (data.indexOf('\r\n') >= 0) { + // Assume end of headers. + socket.write('HTTP/1.1 418 OK\r\n'); + socket.write('Transfer-Encoding: chunked\r\n'); + socket.write('\r\n'); + socket.end('JK I lied, this is NOT a chunked response!'); + } + }); + }); + bad_tcp_server_url = 'http://127.0.0.1:' + bad_tcp_server.listen(0).address().port; + }); + after(function(done) { + bad_tcp_server.close(function() { + done(); + }); + }); + + it('Proxy error', function(done) { + request(cors_anywhere) + .get('/example.com/proxyerror') + .expect('Access-Control-Allow-Origin', '*') + .expect(404, 'Not found because of proxy error: Error: throw node', done); + }); + + it('Content-Length mismatch', function(done) { + request(cors_anywhere) + .get('/' + bad_http_server_url) + .expect('Access-Control-Allow-Origin', '*') + .expect(404, 'Not found because of proxy error: Error: Parse Error', done); + }); + + it('Invalid HTTP status code', function(done) { + // Strict HTTP status validation was introduced in Node 4.5.5+, 5.11.0+. + // https://github.com/nodejs/node/pull/6291 + var nodev = process.versions.node.split('.').map(function(v) { return parseInt(v); }); + if (nodev[0] < 4 || + nodev[0] === 4 && nodev[1] < 5 || + nodev[0] === 4 && nodev[1] === 5 && nodev[2] < 5 || + nodev[0] === 5 && nodev[1] < 11) { + this.skip(); + } + + var errorMessage = 'RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code: 0'; + if (parseInt(process.versions.node, 10) < 9) { + errorMessage = 'RangeError: Invalid status code: 0'; + } + request(cors_anywhere) + .get('/' + bad_status_http_server_url) + .expect('Access-Control-Allow-Origin', '*') + .expect(404, 'Not found because of proxy error: ' + errorMessage, done); + }); + + it('Content-Encoding invalid body', function(done) { + // The HTTP status can't be changed because the headers have already been + // sent. + request(cors_anywhere) + .get('/' + bad_tcp_server_url) + .expect('Access-Control-Allow-Origin', '*') + .expect(418, '', done); + }); + + it('Invalid header values', function(done) { + if (parseInt(process.versions.node, 10) < 6) { + // >=6.0.0: https://github.com/nodejs/node/commit/7bef1b790727430cb82bf8be80cfe058480de100 + this.skip(); + } + // >=9.0.0: https://github.com/nodejs/node/commit/11a2ca29babcb35132e7d93244b69c544d52dfe4 + var errorMessage = 'TypeError [ERR_INVALID_CHAR]: Invalid character in header content ["headername"]'; + if (parseInt(process.versions.node, 10) < 9) { + // >=6.0.0, <9.0.0: https://github.com/nodejs/node/commit/7bef1b790727430cb82bf8be80cfe058480de100 + errorMessage = 'TypeError: The header content contains invalid characters'; + } + stopServer(function() { + cors_anywhere = createServer({ + // Setting an invalid header below in request(...).set(...) would trigger + // a header validation error in superagent. So we use setHeaders to test + // the attempt to proxy a request with invalid request headers. + setHeaders: {headername: 'invalid\x01value'}, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + request(cors_anywhere) + .get('/' + bad_tcp_server_url) // Any URL that isn't intercepted by Nock would do. + .expect('Access-Control-Allow-Origin', '*') + .expect(404, 'Not found because of proxy error: ' + errorMessage, done); + }); + }); +}); + +describe('server on https', function() { + var NODE_TLS_REJECT_UNAUTHORIZED; + before(function() { + cors_anywhere = createServer({ + httpsOptions: { + key: fs.readFileSync(path.join(__dirname, 'key.pem')), + cert: fs.readFileSync(path.join(__dirname, 'cert.pem')), + }, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + // Disable certificate validation in case the certificate expires. + NODE_TLS_REJECT_UNAUTHORIZED = process.env.NODE_TLS_REJECT_UNAUTHORIZED; + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + }); + after(function(done) { + if (NODE_TLS_REJECT_UNAUTHORIZED === undefined) { + delete process.env.NODE_TLS_REJECT_UNAUTHORIZED; + } else { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = NODE_TLS_REJECT_UNAUTHORIZED; + } + stopServer(done); + }); + + it('X-Forwarded-* headers (http)', function(done) { + request(cors_anywhere) + .get('/example.com/echoheaders') + .set('test-include-xfwd', '') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + 'x-forwarded-port': String(cors_anywhere_port), + 'x-forwarded-proto': 'https', + }, done); + }); + + it('X-Forwarded-* headers (https)', function(done) { + request(cors_anywhere) + .get('/https://example.com/echoheaders') + .set('test-include-xfwd', '') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + 'x-forwarded-port': String(cors_anywhere_port), + 'x-forwarded-proto': 'https', + }, done); + }); + + it('X-Forwarded-* headers (https, non-standard port)', function(done) { + request(cors_anywhere) + .get('/https://example.com:1337/echoheaders') + .set('test-include-xfwd', '') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com:1337', + 'x-forwarded-port': String(cors_anywhere_port), + 'x-forwarded-proto': 'https', + }, done); + }); +}); + +describe('originBlacklist', function() { + before(function() { + cors_anywhere = createServer({ + originBlacklist: ['http://denied.origin.test'], + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /example.com with denied origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'http://denied.origin.test') + .expect('Access-Control-Allow-Origin', '*') + .expect(403, done); + }); + + it('GET /example.com without denied origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'https://denied.origin.test') // Note: different scheme! + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); + + it('GET /example.com without origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); +}); + +describe('originWhitelist', function() { + before(function() { + cors_anywhere = createServer({ + originWhitelist: ['https://permitted.origin.test'], + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /example.com with permitted origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'https://permitted.origin.test') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); + + it('GET /example.com without permitted origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'http://permitted.origin.test') // Note: different scheme! + .expect('Access-Control-Allow-Origin', '*') + .expect(403, done); + }); + + it('GET /example.com without origin', function(done) { + request(cors_anywhere) + .get('/example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(403, done); + }); +}); + +describe('checkRateLimit', function() { + afterEach(stopServer); + + it('GET /example.com without rate-limit', function(done) { + cors_anywhere = createServer({ + checkRateLimit: function() {}, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + request(cors_anywhere) + .get('/example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); + + it('GET /example.com with rate-limit', function(done) { + cors_anywhere = createServer({ + checkRateLimit: function(origin) { + // Non-empty value. Let's return the origin parameter so that we also verify that the + // the parameter is really the origin. + return '[' + origin + ']'; + }, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'http://example.net:1234') + .expect('Access-Control-Allow-Origin', '*') + .expect(429, done, + 'The origin "http://example.net" has sent too many requests.\n[http://example.com:1234]'); + }); +}); + +describe('redirectSameOrigin', function() { + before(function() { + cors_anywhere = createServer({ + redirectSameOrigin: true, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /example.com with Origin: http://example.com', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'http://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect('Cache-Control', 'private') + .expect('Vary', 'origin') + .expect('Location', 'http://example.com/') + .expect(301, done); + }); + + it('GET /example.com with Origin: https://example.com', function(done) { + // Not same-origin because of different schemes. + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'https://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from example.com', done); + }); + + it('GET /example.com with Origin: http://example.com:1234', function(done) { + // Not same-origin because of different ports. + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'http://example.com:1234') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from example.com', done); + }); + + it('GET /example.com:1234 with Origin: http://example.com', function(done) { + // Not same-origin because of different ports. + request(cors_anywhere) + .get('/example.com:1234/') + .set('Origin', 'http://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from example.com:1234', done); + }); + + it('GET /example.com with Origin: http://example.com.test', function(done) { + // Not same-origin because of different host names. + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'http://example.com.test') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from example.com', done); + }); + + it('GET /example.com.com with Origin: http://example.com', function(done) { + // Not same-origin because of different host names. + request(cors_anywhere) + .get('/example.com.com/') + .set('Origin', 'http://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from example.com.com', done); + }); + + it('GET /prefix.example.com with Origin: http://example.com', function(done) { + // Not same-origin because of different host names. + request(cors_anywhere) + .get('/prefix.example.com/') + .set('Origin', 'http://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from prefix.example.com', done); + }); +}); + +describe('requireHeader', function() { + before(function() { + cors_anywhere = createServer({ + requireHeader: ['origin', 'x-requested-with'], + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /example.com without header', function(done) { + request(cors_anywhere) + .get('/example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(400, 'Missing required request header. Must specify one of: origin,x-requested-with', done); + }); + + it('GET /example.com with X-Requested-With header', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('X-Requested-With', '') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); + + it('GET /example.com with Origin header', function(done) { + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'null') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, done); + }); + + it('GET /example.com without header (requireHeader as string)', function(done) { + stopServer(function() { + cors_anywhere = createServer({ + requireHeader: 'origin', + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + request(cors_anywhere) + .get('/example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(400, 'Missing required request header. Must specify one of: origin', done); + }); + }); + + it('GET /example.com with header (requireHeader as string)', function(done) { + stopServer(function() { + cors_anywhere = createServer({ + requireHeader: 'origin', + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'null') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from example.com', done); + }); + }); + + it('GET /example.com without header (requireHeader as string, uppercase)', function(done) { + stopServer(function() { + cors_anywhere = createServer({ + requireHeader: 'ORIGIN', + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + request(cors_anywhere) + .get('/example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(400, 'Missing required request header. Must specify one of: origin', done); + }); + }); + + it('GET /example.com with header (requireHeader as string, uppercase)', function(done) { + stopServer(function() { + cors_anywhere = createServer({ + requireHeader: 'ORIGIN', + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + request(cors_anywhere) + .get('/example.com/') + .set('Origin', 'null') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from example.com', done); + }); + }); + + it('GET /example.com (requireHeader is an empty array)', function(done) { + stopServer(function() { + cors_anywhere = createServer({ + requireHeader: [], + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + request(cors_anywhere) + .get('/example.com/') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from example.com', done); + }); + }); +}); + +describe('removeHeaders', function() { + before(function() { + cors_anywhere = createServer({ + removeHeaders: ['cookie', 'cookie2'], + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /example.com with request cookie', function(done) { + request(cors_anywhere) + .get('/example.com/echoheaders') + .set('cookie', 'a') + .set('cookie2', 'b') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + }, done); + }); + + it('GET /example.com with unknown header', function(done) { + request(cors_anywhere) + .get('/example.com/echoheaders') + .set('cookie', 'a') + .set('cookie2', 'b') + .set('cookie3', 'c') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + cookie3: 'c', + }, done); + }); +}); + +describe('setHeaders', function() { + before(function() { + cors_anywhere = createServer({ + setHeaders: {'x-powered-by': 'CORS Anywhere'}, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /example.com', function(done) { + request(cors_anywhere) + .get('/example.com/echoheaders') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + 'x-powered-by': 'CORS Anywhere', + }, done); + }); + + it('GET /example.com should replace header', function(done) { + request(cors_anywhere) + .get('/example.com/echoheaders') + .set('x-powered-by', 'should be replaced') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + 'x-powered-by': 'CORS Anywhere', + }, done); + }); +}); + +describe('setHeaders + removeHeaders', function() { + before(function() { + // setHeaders takes precedence over removeHeaders + cors_anywhere = createServer({ + removeHeaders: ['x-powered-by'], + setHeaders: {'x-powered-by': 'CORS Anywhere'}, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /example.com', function(done) { + request(cors_anywhere) + .get('/example.com/echoheaders') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + 'x-powered-by': 'CORS Anywhere', + }, done); + }); + + it('GET /example.com should replace header', function(done) { + request(cors_anywhere) + .get('/example.com/echoheaders') + .set('x-powered-by', 'should be replaced') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + 'x-powered-by': 'CORS Anywhere', + }, done); + }); +}); + +describe('Access-Control-Max-Age set', function() { + before(function() { + cors_anywhere = createServer({ + corsMaxAge: 600, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /', function(done) { + request(cors_anywhere) + .get('/') + .type('text/plain') + .expect('Access-Control-Allow-Origin', '*') + .expect('Access-Control-Max-Age', '600') + .expect(200, helpText, done); + }); + + it('GET /example.com', function(done) { + request(cors_anywhere) + .get('/example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect('Access-Control-Max-Age', '600') + .expect(200, 'Response from example.com', done); + }); +}); + +describe('Access-Control-Max-Age not set', function() { + before(function() { + cors_anywhere = createServer(); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('GET /', function(done) { + request(cors_anywhere) + .get('/') + .type('text/plain') + .expect('Access-Control-Allow-Origin', '*') + .expectNoHeader('Access-Control-Max-Age') + .expect(200, helpText, done); + }); + + it('GET /example.com', function(done) { + request(cors_anywhere) + .get('/example.com') + .expect('Access-Control-Allow-Origin', '*') + .expectNoHeader('Access-Control-Max-Age') + .expect(200, 'Response from example.com', done); + }); +}); + +describe('httpProxyOptions.xfwd=false', function() { + before(function() { + cors_anywhere = createServer({ + httpProxyOptions: { + xfwd: false, + }, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + after(stopServer); + + it('X-Forwarded-* headers should not be set', function(done) { + request(cors_anywhere) + .get('/example.com/echoheaders') + .set('test-include-xfwd', '') + .expect('Access-Control-Allow-Origin', '*') + .expectJSON({ + host: 'example.com', + }, done); + }); +}); + +describe('httpProxyOptions.getProxyForUrl', function() { + var proxy_server; + var proxy_url; + before(function() { + // Using a real server instead of a mock because Nock doesn't can't mock proxies. + proxy_server = http.createServer(function(req, res) { + res.end(req.method + ' ' + req.url + ' Host=' + req.headers.host); + }); + proxy_url = 'http://127.0.0.1:' + proxy_server.listen(0).address().port; + + cors_anywhere = createServer({ + httpProxyOptions: { + xfwd: false, + }, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + }); + afterEach(function() { + // Assuming that they were not set before. + delete process.env.https_proxy; + delete process.env.http_proxy; + delete process.env.no_proxy; + }); + after(function(done) { + proxy_server.close(function() { + done(); + }); + }); + after(stopServer); + + it('http_proxy should be respected for matching domains', function(done) { + process.env.http_proxy = proxy_url; + + request(cors_anywhere) + .get('/http://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'GET http://example.com/ Host=example.com', done); + }); + + it('http_proxy should be ignored for http URLs', function(done) { + process.env.http_proxy = proxy_url; + request(cors_anywhere) + .get('/https://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from https://example.com', done); + }); + + it('https_proxy should be respected for matching domains', function(done) { + process.env.https_proxy = proxy_url; + + request(cors_anywhere) + .get('/https://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'GET https://example.com/ Host=example.com', done); + }); + + it('https_proxy should be ignored for http URLs', function(done) { + process.env.https_proxy = proxy_url; + request(cors_anywhere) + .get('/http://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from example.com', done); + }); + + it('https_proxy + no_proxy should not intercept requests in no_proxy', function(done) { + process.env.https_proxy = proxy_url; + process.env.no_proxy = 'example.com:443'; + request(cors_anywhere) + .get('/https://example.com') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, 'Response from https://example.com', done); + }); +}); + +describe('helpFile', function() { + + afterEach(stopServer); + + it('GET / with custom text helpFile', function(done) { + var customHelpTextPath = path.join(__dirname, './customHelp.txt'); + var customHelpText = fs.readFileSync(customHelpTextPath, {encoding: 'utf8'}); + + cors_anywhere = createServer({ + helpFile: customHelpTextPath, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + + request(cors_anywhere) + .get('/') + .type('text/plain') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, customHelpText, done); + }); + + it('GET / with custom HTML helpFile', function(done) { + var customHelpTextPath = path.join(__dirname, './customHelp.html'); + var customHelpText = fs.readFileSync(customHelpTextPath, {encoding: 'utf8'}); + + cors_anywhere = createServer({ + helpFile: customHelpTextPath, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + + request(cors_anywhere) + .get('/') + .type('text/html') + .expect('Access-Control-Allow-Origin', '*') + .expect(200, customHelpText, done); + }); + + it('GET / with non-existent help file', function(done) { + var customHelpTextPath = path.join(__dirname, 'Some non-existing file.'); + + cors_anywhere = createServer({ + helpFile: customHelpTextPath, + }); + cors_anywhere_port = cors_anywhere.listen(0).address().port; + + request(cors_anywhere) + .get('/') + .type('text/plain') + .expect('Access-Control-Allow-Origin', '*') + .expect(500, '', done); + }); +});