239 lines
6.5 KiB
JavaScript
239 lines
6.5 KiB
JavaScript
|
(function () {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var assign = require('object-assign');
|
||
|
var vary = require('vary');
|
||
|
|
||
|
var defaults = {
|
||
|
origin: '*',
|
||
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||
|
preflightContinue: false,
|
||
|
optionsSuccessStatus: 204
|
||
|
};
|
||
|
|
||
|
function isString(s) {
|
||
|
return typeof s === 'string' || s instanceof String;
|
||
|
}
|
||
|
|
||
|
function isOriginAllowed(origin, allowedOrigin) {
|
||
|
if (Array.isArray(allowedOrigin)) {
|
||
|
for (var i = 0; i < allowedOrigin.length; ++i) {
|
||
|
if (isOriginAllowed(origin, allowedOrigin[i])) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
} else if (isString(allowedOrigin)) {
|
||
|
return origin === allowedOrigin;
|
||
|
} else if (allowedOrigin instanceof RegExp) {
|
||
|
return allowedOrigin.test(origin);
|
||
|
} else {
|
||
|
return !!allowedOrigin;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function configureOrigin(options, req) {
|
||
|
var requestOrigin = req.headers.origin,
|
||
|
headers = [],
|
||
|
isAllowed;
|
||
|
|
||
|
if (!options.origin || options.origin === '*') {
|
||
|
// allow any origin
|
||
|
headers.push([{
|
||
|
key: 'Access-Control-Allow-Origin',
|
||
|
value: '*'
|
||
|
}]);
|
||
|
} else if (isString(options.origin)) {
|
||
|
// fixed origin
|
||
|
headers.push([{
|
||
|
key: 'Access-Control-Allow-Origin',
|
||
|
value: options.origin
|
||
|
}]);
|
||
|
headers.push([{
|
||
|
key: 'Vary',
|
||
|
value: 'Origin'
|
||
|
}]);
|
||
|
} else {
|
||
|
isAllowed = isOriginAllowed(requestOrigin, options.origin);
|
||
|
// reflect origin
|
||
|
headers.push([{
|
||
|
key: 'Access-Control-Allow-Origin',
|
||
|
value: isAllowed ? requestOrigin : false
|
||
|
}]);
|
||
|
headers.push([{
|
||
|
key: 'Vary',
|
||
|
value: 'Origin'
|
||
|
}]);
|
||
|
}
|
||
|
|
||
|
return headers;
|
||
|
}
|
||
|
|
||
|
function configureMethods(options) {
|
||
|
var methods = options.methods;
|
||
|
if (methods.join) {
|
||
|
methods = options.methods.join(','); // .methods is an array, so turn it into a string
|
||
|
}
|
||
|
return {
|
||
|
key: 'Access-Control-Allow-Methods',
|
||
|
value: methods
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function configureCredentials(options) {
|
||
|
if (options.credentials === true) {
|
||
|
return {
|
||
|
key: 'Access-Control-Allow-Credentials',
|
||
|
value: 'true'
|
||
|
};
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function configureAllowedHeaders(options, req) {
|
||
|
var allowedHeaders = options.allowedHeaders || options.headers;
|
||
|
var headers = [];
|
||
|
|
||
|
if (!allowedHeaders) {
|
||
|
allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers
|
||
|
headers.push([{
|
||
|
key: 'Vary',
|
||
|
value: 'Access-Control-Request-Headers'
|
||
|
}]);
|
||
|
} else if (allowedHeaders.join) {
|
||
|
allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string
|
||
|
}
|
||
|
if (allowedHeaders && allowedHeaders.length) {
|
||
|
headers.push([{
|
||
|
key: 'Access-Control-Allow-Headers',
|
||
|
value: allowedHeaders
|
||
|
}]);
|
||
|
}
|
||
|
|
||
|
return headers;
|
||
|
}
|
||
|
|
||
|
function configureExposedHeaders(options) {
|
||
|
var headers = options.exposedHeaders;
|
||
|
if (!headers) {
|
||
|
return null;
|
||
|
} else if (headers.join) {
|
||
|
headers = headers.join(','); // .headers is an array, so turn it into a string
|
||
|
}
|
||
|
if (headers && headers.length) {
|
||
|
return {
|
||
|
key: 'Access-Control-Expose-Headers',
|
||
|
value: headers
|
||
|
};
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function configureMaxAge(options) {
|
||
|
var maxAge = (typeof options.maxAge === 'number' || options.maxAge) && options.maxAge.toString()
|
||
|
if (maxAge && maxAge.length) {
|
||
|
return {
|
||
|
key: 'Access-Control-Max-Age',
|
||
|
value: maxAge
|
||
|
};
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function applyHeaders(headers, res) {
|
||
|
for (var i = 0, n = headers.length; i < n; i++) {
|
||
|
var header = headers[i];
|
||
|
if (header) {
|
||
|
if (Array.isArray(header)) {
|
||
|
applyHeaders(header, res);
|
||
|
} else if (header.key === 'Vary' && header.value) {
|
||
|
vary(res, header.value);
|
||
|
} else if (header.value) {
|
||
|
res.setHeader(header.key, header.value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function cors(options, req, res, next) {
|
||
|
var headers = [],
|
||
|
method = req.method && req.method.toUpperCase && req.method.toUpperCase();
|
||
|
|
||
|
if (method === 'OPTIONS') {
|
||
|
// preflight
|
||
|
headers.push(configureOrigin(options, req));
|
||
|
headers.push(configureCredentials(options, req));
|
||
|
headers.push(configureMethods(options, req));
|
||
|
headers.push(configureAllowedHeaders(options, req));
|
||
|
headers.push(configureMaxAge(options, req));
|
||
|
headers.push(configureExposedHeaders(options, req));
|
||
|
applyHeaders(headers, res);
|
||
|
|
||
|
if (options.preflightContinue) {
|
||
|
next();
|
||
|
} else {
|
||
|
// Safari (and potentially other browsers) need content-length 0,
|
||
|
// for 204 or they just hang waiting for a body
|
||
|
res.statusCode = options.optionsSuccessStatus;
|
||
|
res.setHeader('Content-Length', '0');
|
||
|
res.end();
|
||
|
}
|
||
|
} else {
|
||
|
// actual response
|
||
|
headers.push(configureOrigin(options, req));
|
||
|
headers.push(configureCredentials(options, req));
|
||
|
headers.push(configureExposedHeaders(options, req));
|
||
|
applyHeaders(headers, res);
|
||
|
next();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function middlewareWrapper(o) {
|
||
|
// if options are static (either via defaults or custom options passed in), wrap in a function
|
||
|
var optionsCallback = null;
|
||
|
if (typeof o === 'function') {
|
||
|
optionsCallback = o;
|
||
|
} else {
|
||
|
optionsCallback = function (req, cb) {
|
||
|
cb(null, o);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return function corsMiddleware(req, res, next) {
|
||
|
optionsCallback(req, function (err, options) {
|
||
|
if (err) {
|
||
|
next(err);
|
||
|
} else {
|
||
|
var corsOptions = assign({}, defaults, options);
|
||
|
var originCallback = null;
|
||
|
if (corsOptions.origin && typeof corsOptions.origin === 'function') {
|
||
|
originCallback = corsOptions.origin;
|
||
|
} else if (corsOptions.origin) {
|
||
|
originCallback = function (origin, cb) {
|
||
|
cb(null, corsOptions.origin);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (originCallback) {
|
||
|
originCallback(req.headers.origin, function (err2, origin) {
|
||
|
if (err2 || !origin) {
|
||
|
next(err2);
|
||
|
} else {
|
||
|
corsOptions.origin = origin;
|
||
|
cors(corsOptions, req, res, next);
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
next();
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// can pass either an options hash, an options delegate, or nothing
|
||
|
module.exports = middlewareWrapper;
|
||
|
|
||
|
}());
|