| /** |
| * 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(); |
| } |
| } |
| } |
| }); |
| } |
| }; |
| }; |