UNPKG

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