123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- /**
- * Specification: http://www.w3.org/TR/2012/WD-cors-20120403/
- * W3C Working Draft 3 April 2012
- */
- "use strict";
- /*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();
- }
- }
- }
- });
- }
- };
- };
|