mirror of
https://github.com/d0zingcat/cors-anywhere.git
synced 2026-05-13 15:09:25 +00:00
Fix for 3xx redirects; Disabled credentials
This commit is contained in:
21
README.md
21
README.md
@@ -5,8 +5,10 @@ part of the proxied URI is optional, and defaults to "http". If port 443 is spec
|
||||
the protocol defaults to "https".
|
||||
|
||||
This package does not put any restrictions on the http methods or headers, except for
|
||||
cookies. Credentials are disabled by default, because leaking cookies between different
|
||||
domains is insecure.
|
||||
cookies. Requesting [user credentials](http://www.w3.org/TR/cors/#user-credentials) is disallowed.
|
||||
|
||||
Redirects are not automatically followed. Instead, the server replies with http status code 333 and
|
||||
includes an absolute URL in the `location` response header.
|
||||
|
||||
The package also includes a Procfile, to run the app on Heroku. More information about
|
||||
Heroku can be found at https://devcenter.heroku.com/articles/nodejs.
|
||||
@@ -21,7 +23,6 @@ var port = process.env.PORT || 8080;
|
||||
var cors_proxy = require("cors-anywhere");
|
||||
cors_proxy.createServer({
|
||||
requireHeader: 'x-requested-with',
|
||||
withCredentials: false,
|
||||
removeHeaders: ['cookie', 'cookie2']
|
||||
}).listen(port, host, function() {
|
||||
console.log('Running CORS Anywhere on ' + host + ':' + port);
|
||||
@@ -30,11 +31,11 @@ cors_proxy.createServer({
|
||||
```
|
||||
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
|
||||
* `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:
|
||||
|
||||
@@ -50,10 +51,6 @@ The module exports two properties: `getHandler` and `createServer`.
|
||||
* `createServer(options)` creates a server with the default handler.
|
||||
|
||||
The following options are recognized by both methods:
|
||||
* boolean `withCredentials` - If true, [user credentials](http://www.w3.org/TR/cors/#user-credentials)
|
||||
such as cookies are accepted in the request. It's recommended to set this flag to `false`, because
|
||||
cookies ought not to be leaked to other domains. If you want to use `withCredentials`, make sure that
|
||||
you implement cookie parsing and transforming so that the `path` flag of the cookie is set correctly.
|
||||
* string `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 browsing. Example: `X-Requested-With`
|
||||
* array of lowercase strings `removeHeaders` - Exclude certain headers from being included in the request.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
var httpProxy = require('http-proxy');
|
||||
var net = require('net');
|
||||
var url = require('url');
|
||||
var regexp_tld = require('./regexp-top-level-domain');
|
||||
|
||||
var help_file = __dirname + '/help.txt';
|
||||
@@ -35,23 +36,86 @@ function hasNoContent(hostname) {
|
||||
);
|
||||
}
|
||||
|
||||
function handleCookies(isAllowed, headers) {
|
||||
// Assumed that all headers' names are lowercase
|
||||
if (!isAllowed) {
|
||||
delete headers['set-cookie'];
|
||||
delete headers['set-cookie2'];
|
||||
// First argument: The response.headers object
|
||||
// Second argument: The request object.
|
||||
function withCORS(headers, request) {
|
||||
var origin = request.headers.origin || 'null';
|
||||
headers['access-control-allow-origin'] = origin === 'null' ? '*' : origin;
|
||||
if (request.headers['access-control-request-method']) {
|
||||
headers['access-control-allow-methods'] = request.headers['access-control-request-method'];
|
||||
delete req.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'];
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
function getProto(req) {
|
||||
return req.isSpdy ? 'https' : (req.connection.pair ? 'https' : 'http');
|
||||
}
|
||||
function _clone(obj) {
|
||||
var clone = {};
|
||||
Object.keys(clone).forEach(function(key) {
|
||||
clone[key] = obj[key];
|
||||
});
|
||||
return clone;
|
||||
}
|
||||
function isForbidden(host) {
|
||||
return false; // TODO
|
||||
}
|
||||
function proxyRequest(req, res, proxy, full_url, proxyOptions) {
|
||||
if (isForbidden(proxyOptions.host)) {
|
||||
res.writeHead(403, 'Refused to visit', withCORS({'Location': full_url}, req));
|
||||
return;
|
||||
}
|
||||
// TODO: Parse cookies, and change Domain and Secure flag to match the API domain,
|
||||
// and change Path to /<website>/....
|
||||
//if (headers['set-cookie']) headers['set-cookie'] = _parseCookie(headers['set-cookie']);
|
||||
//if (headers['set-cookie2']) headers['set-cookie2'] = _parseCookie(headers['set-cookie']);
|
||||
// Hook res.writeHead, .write and .end to queue the response
|
||||
var base_host = getProto(req) + req.headers.host;
|
||||
var res_writeHead = res.writeHead;
|
||||
var res_write = res.write;
|
||||
var res_end = res.end;
|
||||
|
||||
res.writeHead = function(statusCode, reasonPhrase, headers) {
|
||||
if (typeof reasonPhrase === 'object') {
|
||||
headers = reasonPhrase;
|
||||
reasonPhrase = undefined;
|
||||
}
|
||||
if (!headers) headers = withCORS({}, req);
|
||||
else {
|
||||
withCORS(headers, req);
|
||||
|
||||
// Handle redirects
|
||||
if (statusCode === 301 || statusCode === 302 || statusCode === 303 || statusCode === 307 || statusCode === 308) {
|
||||
if (headers['location']) {
|
||||
headers['location'] = url.resolve(full_url, headers['location']);
|
||||
}
|
||||
// Don't use 301 or 302 because browsers may cancel the request (observed in Chrome with a custom request header)
|
||||
statusCode = 333;
|
||||
reasonPhrase = 'Redirect';
|
||||
}
|
||||
|
||||
// Don't slip through cookies
|
||||
delete headers['set-cookie'];
|
||||
delete headers['set-cookie2'];
|
||||
|
||||
// Informational purposes
|
||||
headers['x-request-url'] = full_url;
|
||||
}
|
||||
if (reasonPhrase) {
|
||||
return res_writeHead.call(res, statusCode, reasonPhrase, headers);
|
||||
} else {
|
||||
return res_writeHead.call(res, statusCode, headers);
|
||||
}
|
||||
};
|
||||
|
||||
// Start proxying the request
|
||||
proxy.proxyRequest(req, res, proxyOptions);
|
||||
}
|
||||
|
||||
|
||||
// Called on every request
|
||||
var getHandler = exports.getHandler = function(options) {
|
||||
var corsAnywhere = {
|
||||
withCredentials: false, // Toggle credentials/cookies
|
||||
requireHeader: null, // Require a header to be set?
|
||||
removeHeaders: [] // Strip these request headers
|
||||
};
|
||||
@@ -64,20 +128,7 @@ var getHandler = exports.getHandler = function(options) {
|
||||
}
|
||||
|
||||
return function(req, res, proxy) {
|
||||
var cors_headers = {
|
||||
'access-control-allow-origin': req.headers.origin || '*'
|
||||
};
|
||||
if (corsAnywhere.withCredentials) { // <-- If the corsAnywhere property does not exists, throw an error.
|
||||
// Allow sending of credentials ONLY if it's explicitly allowed on creation of the proxy.
|
||||
cors_headers['access-control-allow-credentials'] = 'true';
|
||||
}
|
||||
if (req.headers['access-control-request-method']) {
|
||||
cors_headers['access-control-allow-methods'] = req.headers['access-control-request-method'];
|
||||
}
|
||||
if (req.headers['access-control-request-headers']) {
|
||||
cors_headers['access-control-allow-headers'] = req.headers['access-control-request-headers'];
|
||||
}
|
||||
|
||||
var cors_headers = withCORS({}, req);
|
||||
if (req.method == 'OPTIONS') {
|
||||
// Pre-flight request. Reply successfully:
|
||||
res.writeHead(200, cors_headers);
|
||||
@@ -85,7 +136,7 @@ var getHandler = exports.getHandler = function(options) {
|
||||
return;
|
||||
} else {
|
||||
// Actual request. First, extract the desired URL from the request:
|
||||
var host, hostname, port, path, match;
|
||||
var full_url, host, hostname, port, path, match;
|
||||
match = req.url.match(/^\/(?:(https?:)?\/\/)?(([^\/?]+?)(?::(\d{0,5})(?=[\/?]|$))?)([\/?][\S\s]*|$)/i);
|
||||
// ^^^^^^^ ^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^
|
||||
// 1:protocol 3:hostname 4:port 5:path + query string
|
||||
@@ -117,11 +168,17 @@ var getHandler = exports.getHandler = function(options) {
|
||||
res.end('Missing ' + corsAnywhere.requireHeader + ' header!');
|
||||
return;
|
||||
} else {
|
||||
full_url = match[0].substr(1);
|
||||
host = match[2];
|
||||
hostname = match[3];
|
||||
// Read port from input: :<port> / 443 if https / 80 by default
|
||||
port = match[4] ? +match[4] : (match[1] && match[1].toLowerCase() === 'https:' ? 443 : 80);
|
||||
path = match[5];
|
||||
|
||||
if (!match[1]) {
|
||||
if (full_url.charAt(0) !== '/') full_url = '//' + full_url;
|
||||
full_url = (port === 443 ? 'https:' : 'http:') + full_url;
|
||||
}
|
||||
}
|
||||
// Change the requested path:
|
||||
req.url = path;
|
||||
@@ -130,36 +187,7 @@ var getHandler = exports.getHandler = function(options) {
|
||||
delete req.headers[header];
|
||||
});
|
||||
|
||||
// Hook res.writeHead method to set the correct header
|
||||
var res_writeHead = res.writeHead;
|
||||
res.writeHead = function(statusCode, reasonPhrase, headers) {
|
||||
if (typeof reasonPhrase === 'object') {
|
||||
headers = reasonPhrase;
|
||||
}
|
||||
if (!headers) headers = cors_headers;
|
||||
else {
|
||||
var header;
|
||||
for (header in cors_headers) {
|
||||
// We define the cors_headers object, so we can be damn sure that hasOwnProperty is not a key of it.
|
||||
// and therefor we can use hOP directly instead of Object.prototype.hOP.call(...)
|
||||
if (cors_headers.hasOwnProperty(header)) {
|
||||
headers[header] = cors_headers[header];
|
||||
}
|
||||
}
|
||||
|
||||
if ((statusCode === 301 || statusCode === 302) && headers.location) {
|
||||
// Handle redirects
|
||||
// The X-Forwarded-Proto header is set by Heroku, and also by the http-proxy library when xforward is true)
|
||||
var proxy_base_url = (req.headers['x-forwarded-proto'] || 'http') + '://' + req.headers['host'];
|
||||
headers.location = proxy_base_url + '/' + headers.location;
|
||||
}
|
||||
handleCookies(proxy.withCredentials, headers);
|
||||
}
|
||||
return res_writeHead.apply(this, arguments); // headers are magically updated when variables are modified
|
||||
};
|
||||
|
||||
// Finally, proxy the request
|
||||
proxy.proxyRequest(req, res, {
|
||||
proxyRequest(req, res, proxy, full_url, {
|
||||
host: hostname,
|
||||
port: port
|
||||
});
|
||||
|
||||
13
lib/help.txt
13
lib/help.txt
@@ -6,7 +6,18 @@ Usage:
|
||||
/iscorsneeded This is the only resource on this host which is served without CORS headers.
|
||||
/<url> Create a request to <url>, and includes CORS headers in the response.
|
||||
|
||||
The protocol can be omitted. It defaults to http:, unless port 443 is specified.
|
||||
If the protocol is omitted, it defaults to http (https if port 443 is specified).
|
||||
|
||||
Cookies are disabled and stripped from requests.
|
||||
|
||||
Redirects are not automatically followed: The API response has status code 333.
|
||||
The client ought to confirm this redirection by creating a new request.
|
||||
|
||||
The requested URL is available in the X-Request-URL response header. Non-existence of this
|
||||
header implies that the requested URL was not recognized.
|
||||
|
||||
This API has one requirement: The X-Requested-With header must be set.
|
||||
|
||||
|
||||
Demo : http://rob.lekensteyn.nl/cors-anywhere.html
|
||||
Source code : https://github.com/Rob--W/cors-anywhere/
|
||||
|
||||
@@ -5,7 +5,6 @@ var port = process.env.PORT || 8080;
|
||||
var cors_proxy = require("./lib/cors-anywhere");
|
||||
cors_proxy.createServer({
|
||||
requireHeader: 'x-requested-with',
|
||||
withCredentials: false,
|
||||
removeHeaders: ['cookie', 'cookie2']
|
||||
}).listen(port, host, function() {
|
||||
console.log('Running CORS Anywhere on ' + host + ':' + port);
|
||||
|
||||
Reference in New Issue
Block a user