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