socketioMEANseed/www/node_modules/corser/lib/corser.js
www/node_modules/corser/lib/corser.jsRaw
1/**
2 * Specification: http://www.w3.org/TR/2012/WD-cors-20120403/
3 * W3C Working Draft 3 April 2012
4 */
5"use strict";
6
7/*jshint node:true */
8
9var simpleMethods, simpleRequestHeaders, simpleResponseHeaders, toLowerCase, checkOriginMatch;
10
11// A method is said to be a simple method if it is a case-sensitive match for one of the following:
12Object.defineProperty(exports, "simpleMethods", {
13 get: function () {
14 return [
15 "GET",
16 "HEAD",
17 "POST"
18 ];
19 }
20});
21simpleMethods = exports.simpleMethods;
22
23// A header is said to be a simple header if the header field name is an ASCII case-insensitive match for one of
24// the following:
25Object.defineProperty(exports, "simpleRequestHeaders", {
26 get: function () {
27 return [
28 "accept",
29 "accept-language",
30 "content-language",
31 "content-type"
32 ];
33 }
34});
35simpleRequestHeaders = exports.simpleRequestHeaders;
36
37// A header is said to be a simple response header if the header field name is an ASCII case-insensitive
38// match for one of the following:
39Object.defineProperty(exports, "simpleResponseHeaders", {
40 get: function () {
41 return [
42 "cache-control",
43 "content-language",
44 "content-type",
45 "expires",
46 "last-modified",
47 "pragma"
48 ];
49 }
50});
51simpleResponseHeaders = exports.simpleResponseHeaders;
52
53toLowerCase = function (array) {
54 return array.map(function (el) {
55 return el.toLowerCase();
56 });
57};
58
59checkOriginMatch = function (originHeader, origins, callback) {
60 if (typeof origins === "function") {
61 origins(originHeader, function (err, allow) {
62 callback(err, allow);
63 });
64 } else if (origins.length > 0) {
65 callback(null, origins.some(function (origin) {
66 return origin === originHeader;
67 }));
68 } else {
69 // Always matching is acceptable since the list of origins can be unbounded.
70 callback(null, true);
71 }
72};
73
74exports.create = function (options) {
75 options = options || {};
76 options.origins = options.origins || [];
77 options.methods = options.methods || simpleMethods;
78 if (options.hasOwnProperty("requestHeaders") === true) {
79 options.requestHeaders = toLowerCase(options.requestHeaders);
80 } else {
81 options.requestHeaders = simpleRequestHeaders;
82 }
83 if (options.hasOwnProperty("responseHeaders") === true) {
84 options.responseHeaders = toLowerCase(options.responseHeaders);
85 } else {
86 options.responseHeaders = simpleResponseHeaders;
87 }
88 options.maxAge = options.maxAge || null;
89 options.supportsCredentials = options.supportsCredentials || false;
90 if (options.hasOwnProperty("endPreflightRequests") === false) {
91 options.endPreflightRequests = true;
92 }
93 return function (req, res, next) {
94 var methodMatches, headersMatch, requestMethod, requestHeaders, exposedHeaders, endPreflight;
95 // If the Origin header is not present terminate this set of steps.
96 if (!req.headers.hasOwnProperty("origin")) {
97 // The request is outside the scope of the CORS specification. If there is no Origin header,
98 // it could be a same-origin request. Let's let the user-agent handle this situation.
99 next();
100 } else {
101 // If the value of the Origin header is not a case-sensitive match for any of the values in
102 // list of origins, do not set any additional headers and terminate this set of steps.
103 checkOriginMatch(req.headers.origin, options.origins, function (err, originMatches) {
104 if (err !== null) {
105 next(err);
106 } else {
107 if (typeof originMatches !== "boolean" || originMatches === false) {
108 next();
109 } else {
110 // Respond to preflight request.
111 if (req.method === "OPTIONS") {
112 endPreflight = function () {
113 if (options.endPreflightRequests === true) {
114 res.writeHead(204);
115 res.end();
116 } else {
117 next();
118 }
119 };
120 // If there is no Access-Control-Request-Method header or if parsing failed, do not set
121 // any additional headers and terminate this set of steps.
122 if (!req.headers.hasOwnProperty("access-control-request-method")) {
123 endPreflight();
124 } else {
125 requestMethod = req.headers["access-control-request-method"];
126 // If there are no Access-Control-Request-Headers headers let header field-names be the
127 // empty list. If parsing failed do not set any additional headers and terminate this set
128 // of steps.
129 // Checking for an empty header is a workaround for a bug Chrome 52:
130 // https://bugs.chromium.org/p/chromium/issues/detail?id=633729
131 if (req.headers.hasOwnProperty("access-control-request-headers") && req.headers["access-control-request-headers"] !== "") {
132 requestHeaders = toLowerCase(req.headers["access-control-request-headers"].split(/,\s*/));
133 } else {
134 requestHeaders = [];
135 }
136 // If method is not a case-sensitive match for any of the values in list of methods do not
137 // set any additional headers and terminate this set of steps.
138 methodMatches = options.methods.indexOf(requestMethod) !== -1;
139 if (methodMatches === false) {
140 endPreflight();
141 } else {
142 // If any of the header field-names is not a ASCII case-insensitive match for any of
143 // the values in list of headers do not set any additional headers and terminate this
144 // set of steps.
145 headersMatch = requestHeaders.every(function (requestHeader) {
146 // Browsers automatically add Origin to Access-Control-Request-Headers. However,
147 // Origin is not one of the simple request headers. Therefore, the header is
148 // accepted even if it is not in the list of request headers because CORS would
149 // not work without it.
150 if (requestHeader === "origin") {
151 return true;
152 } else {
153 if (options.requestHeaders.indexOf(requestHeader) !== -1) {
154 return true;
155 } else {
156 return false;
157 }
158 }
159 });
160 if (headersMatch === false) {
161 endPreflight();
162 } else {
163 if (options.supportsCredentials === true) {
164 // If the resource supports credentials add a single Access-Control-Allow-Origin
165 // header, with the value of the Origin header as value, and add a single
166 // Access-Control-Allow-Credentials header with the literal string "true"
167 // as value.
168 res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
169 res.setHeader("Access-Control-Allow-Credentials", "true");
170 } else {
171 // Otherwise, add a single Access-Control-Allow-Origin header, with either the
172 // value of the Origin header or the string "*" as value.
173 if (options.origins.length > 0 || typeof options.origins === "function") {
174 res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
175 } else {
176 res.setHeader("Access-Control-Allow-Origin", "*");
177 }
178 }
179 // Optionally add a single Access-Control-Max-Age header with as value the amount
180 // of seconds the user agent is allowed to cache the result of the request.
181 if (options.maxAge !== null) {
182 res.setHeader("Access-Control-Max-Age", options.maxAge);
183 }
184 // Add one or more Access-Control-Allow-Methods headers consisting of (a subset
185 // of) the list of methods.
186 res.setHeader("Access-Control-Allow-Methods", options.methods.join(","));
187 // Add one or more Access-Control-Allow-Headers headers consisting of (a subset
188 // of) the list of headers.
189 res.setHeader("Access-Control-Allow-Headers", options.requestHeaders.join(","));
190 // And out.
191 endPreflight();
192 }
193 }
194 }
195 } else {
196 if (options.supportsCredentials === true) {
197 // If the resource supports credentials add a single Access-Control-Allow-Origin header,
198 // with the value of the Origin header as value, and add a single
199 // Access-Control-Allow-Credentials header with the literal string "true" as value.
200 res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
201 res.setHeader("Access-Control-Allow-Credentials", "true");
202 } else {
203 // Otherwise, add a single Access-Control-Allow-Origin header, with either the value of
204 // the Origin header or the literal string "*" as value.
205 // If the list of origins is empty, use "*" as value.
206 if (options.origins.length > 0 || typeof options.origins === "function") {
207 res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
208 } else {
209 res.setHeader("Access-Control-Allow-Origin", "*");
210 }
211 }
212 // If the list of exposed headers is not empty add one or more Access-Control-Expose-Headers
213 // headers, with as values the header field names given in the list of exposed headers.
214 exposedHeaders = options.responseHeaders.filter(function (optionsResponseHeader) {
215 return simpleResponseHeaders.indexOf(optionsResponseHeader) === -1;
216 });
217 if (exposedHeaders.length > 0) {
218 res.setHeader("Access-Control-Expose-Headers", exposedHeaders.join(","));
219 }
220 // And out.
221 next();
222 }
223 }
224 }
225 });
226 }
227 };
228};