Spaces:
Sleeping
Sleeping
/** | |
* Specification: http://www.w3.org/TR/2012/WD-cors-20120403/ | |
* W3C Working Draft 3 April 2012 | |
*/ | |
; | |
/*jshint node:true */ | |
var simpleMethods, simpleRequestHeaders, simpleResponseHeaders, toLowerCase, checkOriginMatch; | |
// A method is said to be a simple method if it is a case-sensitive match for one of the following: | |
Object.defineProperty(exports, "simpleMethods", { | |
get: function () { | |
return [ | |
"GET", | |
"HEAD", | |
"POST" | |
]; | |
} | |
}); | |
simpleMethods = exports.simpleMethods; | |
// A header is said to be a simple header if the header field name is an ASCII case-insensitive match for one of | |
// the following: | |
Object.defineProperty(exports, "simpleRequestHeaders", { | |
get: function () { | |
return [ | |
"accept", | |
"accept-language", | |
"content-language", | |
"content-type" | |
]; | |
} | |
}); | |
simpleRequestHeaders = exports.simpleRequestHeaders; | |
// A header is said to be a simple response header if the header field name is an ASCII case-insensitive | |
// match for one of the following: | |
Object.defineProperty(exports, "simpleResponseHeaders", { | |
get: function () { | |
return [ | |
"cache-control", | |
"content-language", | |
"content-type", | |
"expires", | |
"last-modified", | |
"pragma" | |
]; | |
} | |
}); | |
simpleResponseHeaders = exports.simpleResponseHeaders; | |
toLowerCase = function (array) { | |
return array.map(function (el) { | |
return el.toLowerCase(); | |
}); | |
}; | |
checkOriginMatch = function (originHeader, origins, callback) { | |
if (typeof origins === "function") { | |
origins(originHeader, function (err, allow) { | |
callback(err, allow); | |
}); | |
} else if (origins.length > 0) { | |
callback(null, origins.some(function (origin) { | |
return origin === originHeader; | |
})); | |
} else { | |
// Always matching is acceptable since the list of origins can be unbounded. | |
callback(null, true); | |
} | |
}; | |
exports.create = function (options) { | |
options = options || {}; | |
options.origins = options.origins || []; | |
options.methods = options.methods || simpleMethods; | |
if (options.hasOwnProperty("requestHeaders") === true) { | |
options.requestHeaders = toLowerCase(options.requestHeaders); | |
} else { | |
options.requestHeaders = simpleRequestHeaders; | |
} | |
if (options.hasOwnProperty("responseHeaders") === true) { | |
options.responseHeaders = toLowerCase(options.responseHeaders); | |
} else { | |
options.responseHeaders = simpleResponseHeaders; | |
} | |
options.maxAge = options.maxAge || null; | |
options.supportsCredentials = options.supportsCredentials || false; | |
if (options.hasOwnProperty("endPreflightRequests") === false) { | |
options.endPreflightRequests = true; | |
} | |
return function (req, res, next) { | |
var methodMatches, headersMatch, requestMethod, requestHeaders, exposedHeaders, endPreflight; | |
// If the Origin header is not present terminate this set of steps. | |
if (!req.headers.hasOwnProperty("origin")) { | |
// The request is outside the scope of the CORS specification. If there is no Origin header, | |
// it could be a same-origin request. Let's let the user-agent handle this situation. | |
next(); | |
} else { | |
// If the value of the Origin header is not a case-sensitive match for any of the values in | |
// list of origins, do not set any additional headers and terminate this set of steps. | |
checkOriginMatch(req.headers.origin, options.origins, function (err, originMatches) { | |
if (err !== null) { | |
next(err); | |
} else { | |
if (typeof originMatches !== "boolean" || originMatches === false) { | |
next(); | |
} else { | |
// Respond to preflight request. | |
if (req.method === "OPTIONS") { | |
endPreflight = function () { | |
if (options.endPreflightRequests === true) { | |
res.writeHead(204); | |
res.end(); | |
} else { | |
next(); | |
} | |
}; | |
// If there is no Access-Control-Request-Method header or if parsing failed, do not set | |
// any additional headers and terminate this set of steps. | |
if (!req.headers.hasOwnProperty("access-control-request-method")) { | |
endPreflight(); | |
} else { | |
requestMethod = req.headers["access-control-request-method"]; | |
// If there are no Access-Control-Request-Headers headers let header field-names be the | |
// empty list. If parsing failed do not set any additional headers and terminate this set | |
// of steps. | |
// Checking for an empty header is a workaround for a bug Chrome 52: | |
// https://bugs.chromium.org/p/chromium/issues/detail?id=633729 | |
if (req.headers.hasOwnProperty("access-control-request-headers") && req.headers["access-control-request-headers"] !== "") { | |
requestHeaders = toLowerCase(req.headers["access-control-request-headers"].split(/,\s*/)); | |
} else { | |
requestHeaders = []; | |
} | |
// If method is not a case-sensitive match for any of the values in list of methods do not | |
// set any additional headers and terminate this set of steps. | |
methodMatches = options.methods.indexOf(requestMethod) !== -1; | |
if (methodMatches === false) { | |
endPreflight(); | |
} else { | |
// If any of the header field-names is not a ASCII case-insensitive match for any of | |
// the values in list of headers do not set any additional headers and terminate this | |
// set of steps. | |
headersMatch = requestHeaders.every(function (requestHeader) { | |
// Browsers automatically add Origin to Access-Control-Request-Headers. However, | |
// Origin is not one of the simple request headers. Therefore, the header is | |
// accepted even if it is not in the list of request headers because CORS would | |
// not work without it. | |
if (requestHeader === "origin") { | |
return true; | |
} else { | |
if (options.requestHeaders.indexOf(requestHeader) !== -1) { | |
return true; | |
} else { | |
return false; | |
} | |
} | |
}); | |
if (headersMatch === false) { | |
endPreflight(); | |
} else { | |
if (options.supportsCredentials === true) { | |
// If the resource supports credentials add a single Access-Control-Allow-Origin | |
// header, with the value of the Origin header as value, and add a single | |
// Access-Control-Allow-Credentials header with the literal string "true" | |
// as value. | |
res.setHeader("Access-Control-Allow-Origin", req.headers.origin); | |
res.setHeader("Access-Control-Allow-Credentials", "true"); | |
} else { | |
// Otherwise, add a single Access-Control-Allow-Origin header, with either the | |
// value of the Origin header or the string "*" as value. | |
if (options.origins.length > 0 || typeof options.origins === "function") { | |
res.setHeader("Access-Control-Allow-Origin", req.headers.origin); | |
} else { | |
res.setHeader("Access-Control-Allow-Origin", "*"); | |
} | |
} | |
// Optionally add a single Access-Control-Max-Age header with as value the amount | |
// of seconds the user agent is allowed to cache the result of the request. | |
if (options.maxAge !== null) { | |
res.setHeader("Access-Control-Max-Age", options.maxAge); | |
} | |
// Add one or more Access-Control-Allow-Methods headers consisting of (a subset | |
// of) the list of methods. | |
res.setHeader("Access-Control-Allow-Methods", options.methods.join(",")); | |
// Add one or more Access-Control-Allow-Headers headers consisting of (a subset | |
// of) the list of headers. | |
res.setHeader("Access-Control-Allow-Headers", options.requestHeaders.join(",")); | |
// And out. | |
endPreflight(); | |
} | |
} | |
} | |
} else { | |
if (options.supportsCredentials === true) { | |
// If the resource supports credentials add a single Access-Control-Allow-Origin header, | |
// with the value of the Origin header as value, and add a single | |
// Access-Control-Allow-Credentials header with the literal string "true" as value. | |
res.setHeader("Access-Control-Allow-Origin", req.headers.origin); | |
res.setHeader("Access-Control-Allow-Credentials", "true"); | |
} else { | |
// Otherwise, add a single Access-Control-Allow-Origin header, with either the value of | |
// the Origin header or the literal string "*" as value. | |
// If the list of origins is empty, use "*" as value. | |
if (options.origins.length > 0 || typeof options.origins === "function") { | |
res.setHeader("Access-Control-Allow-Origin", req.headers.origin); | |
} else { | |
res.setHeader("Access-Control-Allow-Origin", "*"); | |
} | |
} | |
// If the list of exposed headers is not empty add one or more Access-Control-Expose-Headers | |
// headers, with as values the header field names given in the list of exposed headers. | |
exposedHeaders = options.responseHeaders.filter(function (optionsResponseHeader) { | |
return simpleResponseHeaders.indexOf(optionsResponseHeader) === -1; | |
}); | |
if (exposedHeaders.length > 0) { | |
res.setHeader("Access-Control-Expose-Headers", exposedHeaders.join(",")); | |
} | |
// And out. | |
next(); | |
} | |
} | |
} | |
}); | |
} | |
}; | |
}; | |