index.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. (function () {
  2. 'use strict';
  3. var assign = require('object-assign');
  4. var vary = require('vary');
  5. var defaults = {
  6. origin: '*',
  7. methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
  8. preflightContinue: false,
  9. optionsSuccessStatus: 204
  10. };
  11. function isString(s) {
  12. return typeof s === 'string' || s instanceof String;
  13. }
  14. function isOriginAllowed(origin, allowedOrigin) {
  15. if (Array.isArray(allowedOrigin)) {
  16. for (var i = 0; i < allowedOrigin.length; ++i) {
  17. if (isOriginAllowed(origin, allowedOrigin[i])) {
  18. return true;
  19. }
  20. }
  21. return false;
  22. } else if (isString(allowedOrigin)) {
  23. return origin === allowedOrigin;
  24. } else if (allowedOrigin instanceof RegExp) {
  25. return allowedOrigin.test(origin);
  26. } else {
  27. return !!allowedOrigin;
  28. }
  29. }
  30. function configureOrigin(options, req) {
  31. var requestOrigin = req.headers.origin,
  32. headers = [],
  33. isAllowed;
  34. if (!options.origin || options.origin === '*') {
  35. // allow any origin
  36. headers.push([{
  37. key: 'Access-Control-Allow-Origin',
  38. value: '*'
  39. }]);
  40. } else if (isString(options.origin)) {
  41. // fixed origin
  42. headers.push([{
  43. key: 'Access-Control-Allow-Origin',
  44. value: options.origin
  45. }]);
  46. headers.push([{
  47. key: 'Vary',
  48. value: 'Origin'
  49. }]);
  50. } else {
  51. isAllowed = isOriginAllowed(requestOrigin, options.origin);
  52. // reflect origin
  53. headers.push([{
  54. key: 'Access-Control-Allow-Origin',
  55. value: isAllowed ? requestOrigin : false
  56. }]);
  57. headers.push([{
  58. key: 'Vary',
  59. value: 'Origin'
  60. }]);
  61. }
  62. return headers;
  63. }
  64. function configureMethods(options) {
  65. var methods = options.methods;
  66. if (methods.join) {
  67. methods = options.methods.join(','); // .methods is an array, so turn it into a string
  68. }
  69. return {
  70. key: 'Access-Control-Allow-Methods',
  71. value: methods
  72. };
  73. }
  74. function configureCredentials(options) {
  75. if (options.credentials === true) {
  76. return {
  77. key: 'Access-Control-Allow-Credentials',
  78. value: 'true'
  79. };
  80. }
  81. return null;
  82. }
  83. function configureAllowedHeaders(options, req) {
  84. var allowedHeaders = options.allowedHeaders || options.headers;
  85. var headers = [];
  86. if (!allowedHeaders) {
  87. allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers
  88. headers.push([{
  89. key: 'Vary',
  90. value: 'Access-Control-Request-Headers'
  91. }]);
  92. } else if (allowedHeaders.join) {
  93. allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string
  94. }
  95. if (allowedHeaders && allowedHeaders.length) {
  96. headers.push([{
  97. key: 'Access-Control-Allow-Headers',
  98. value: allowedHeaders
  99. }]);
  100. }
  101. return headers;
  102. }
  103. function configureExposedHeaders(options) {
  104. var headers = options.exposedHeaders;
  105. if (!headers) {
  106. return null;
  107. } else if (headers.join) {
  108. headers = headers.join(','); // .headers is an array, so turn it into a string
  109. }
  110. if (headers && headers.length) {
  111. return {
  112. key: 'Access-Control-Expose-Headers',
  113. value: headers
  114. };
  115. }
  116. return null;
  117. }
  118. function configureMaxAge(options) {
  119. var maxAge = (typeof options.maxAge === 'number' || options.maxAge) && options.maxAge.toString()
  120. if (maxAge && maxAge.length) {
  121. return {
  122. key: 'Access-Control-Max-Age',
  123. value: maxAge
  124. };
  125. }
  126. return null;
  127. }
  128. function applyHeaders(headers, res) {
  129. for (var i = 0, n = headers.length; i < n; i++) {
  130. var header = headers[i];
  131. if (header) {
  132. if (Array.isArray(header)) {
  133. applyHeaders(header, res);
  134. } else if (header.key === 'Vary' && header.value) {
  135. vary(res, header.value);
  136. } else if (header.value) {
  137. res.setHeader(header.key, header.value);
  138. }
  139. }
  140. }
  141. }
  142. function cors(options, req, res, next) {
  143. var headers = [],
  144. method = req.method && req.method.toUpperCase && req.method.toUpperCase();
  145. if (method === 'OPTIONS') {
  146. // preflight
  147. headers.push(configureOrigin(options, req));
  148. headers.push(configureCredentials(options, req));
  149. headers.push(configureMethods(options, req));
  150. headers.push(configureAllowedHeaders(options, req));
  151. headers.push(configureMaxAge(options, req));
  152. headers.push(configureExposedHeaders(options, req));
  153. applyHeaders(headers, res);
  154. if (options.preflightContinue) {
  155. next();
  156. } else {
  157. // Safari (and potentially other browsers) need content-length 0,
  158. // for 204 or they just hang waiting for a body
  159. res.statusCode = options.optionsSuccessStatus;
  160. res.setHeader('Content-Length', '0');
  161. res.end();
  162. }
  163. } else {
  164. // actual response
  165. headers.push(configureOrigin(options, req));
  166. headers.push(configureCredentials(options, req));
  167. headers.push(configureExposedHeaders(options, req));
  168. applyHeaders(headers, res);
  169. next();
  170. }
  171. }
  172. function middlewareWrapper(o) {
  173. // if options are static (either via defaults or custom options passed in), wrap in a function
  174. var optionsCallback = null;
  175. if (typeof o === 'function') {
  176. optionsCallback = o;
  177. } else {
  178. optionsCallback = function (req, cb) {
  179. cb(null, o);
  180. };
  181. }
  182. return function corsMiddleware(req, res, next) {
  183. optionsCallback(req, function (err, options) {
  184. if (err) {
  185. next(err);
  186. } else {
  187. var corsOptions = assign({}, defaults, options);
  188. var originCallback = null;
  189. if (corsOptions.origin && typeof corsOptions.origin === 'function') {
  190. originCallback = corsOptions.origin;
  191. } else if (corsOptions.origin) {
  192. originCallback = function (origin, cb) {
  193. cb(null, corsOptions.origin);
  194. };
  195. }
  196. if (originCallback) {
  197. originCallback(req.headers.origin, function (err2, origin) {
  198. if (err2 || !origin) {
  199. next(err2);
  200. } else {
  201. corsOptions.origin = origin;
  202. cors(corsOptions, req, res, next);
  203. }
  204. });
  205. } else {
  206. next();
  207. }
  208. }
  209. });
  210. };
  211. }
  212. // can pass either an options hash, an options delegate, or nothing
  213. module.exports = middlewareWrapper;
  214. }());