Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / express-session / index.js
1 /*!
2  * express-session
3  * Copyright(c) 2010 Sencha Inc.
4  * Copyright(c) 2011 TJ Holowaychuk
5  * Copyright(c) 2014-2015 Douglas Christopher Wilson
6  * MIT Licensed
7  */
8
9 /**
10  * Module dependencies.
11  * @private
12  */
13
14 var cookie = require('cookie');
15 var crc = require('crc').crc32;
16 var debug = require('debug')('express-session');
17 var deprecate = require('depd')('express-session');
18 var parseUrl = require('parseurl');
19 var uid = require('uid-safe').sync
20   , onHeaders = require('on-headers')
21   , signature = require('cookie-signature')
22
23 var Session = require('./session/session')
24   , MemoryStore = require('./session/memory')
25   , Cookie = require('./session/cookie')
26   , Store = require('./session/store')
27
28 // environment
29
30 var env = process.env.NODE_ENV;
31
32 /**
33  * Expose the middleware.
34  */
35
36 exports = module.exports = session;
37
38 /**
39  * Expose constructors.
40  */
41
42 exports.Store = Store;
43 exports.Cookie = Cookie;
44 exports.Session = Session;
45 exports.MemoryStore = MemoryStore;
46
47 /**
48  * Warning message for `MemoryStore` usage in production.
49  * @private
50  */
51
52 var warning = 'Warning: connect.session() MemoryStore is not\n'
53   + 'designed for a production environment, as it will leak\n'
54   + 'memory, and will not scale past a single process.';
55
56 /**
57  * Node.js 0.8+ async implementation.
58  * @private
59  */
60
61 /* istanbul ignore next */
62 var defer = typeof setImmediate === 'function'
63   ? setImmediate
64   : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
65
66 /**
67  * Setup session store with the given `options`.
68  *
69  * @param {Object} [options]
70  * @param {Object} [options.cookie] Options for cookie
71  * @param {Function} [options.genid]
72  * @param {String} [options.name=connect.sid] Session ID cookie name
73  * @param {Boolean} [options.proxy]
74  * @param {Boolean} [options.resave] Resave unmodified sessions back to the store
75  * @param {Boolean} [options.rolling] Enable/disable rolling session expiration
76  * @param {Boolean} [options.saveUninitialized] Save uninitialized sessions to the store
77  * @param {String|Array} [options.secret] Secret for signing session ID
78  * @param {Object} [options.store=MemoryStore] Session store
79  * @param {String} [options.unset]
80  * @return {Function} middleware
81  * @public
82  */
83
84 function session(options){
85   var options = options || {}
86   //  name - previously "options.key"
87     , name = options.name || options.key || 'connect.sid'
88     , store = options.store || new MemoryStore
89     , cookie = options.cookie || {}
90     , trustProxy = options.proxy
91     , storeReady = true
92     , rollingSessions = options.rolling || false;
93   var resaveSession = options.resave;
94   var saveUninitializedSession = options.saveUninitialized;
95   var secret = options.secret;
96
97   var generateId = options.genid || generateSessionId;
98
99   if (typeof generateId !== 'function') {
100     throw new TypeError('genid option must be a function');
101   }
102
103   if (resaveSession === undefined) {
104     deprecate('undefined resave option; provide resave option');
105     resaveSession = true;
106   }
107
108   if (saveUninitializedSession === undefined) {
109     deprecate('undefined saveUninitialized option; provide saveUninitialized option');
110     saveUninitializedSession = true;
111   }
112
113   if (options.unset && options.unset !== 'destroy' && options.unset !== 'keep') {
114     throw new TypeError('unset option must be "destroy" or "keep"');
115   }
116
117   // TODO: switch to "destroy" on next major
118   var unsetDestroy = options.unset === 'destroy';
119
120   if (Array.isArray(secret) && secret.length === 0) {
121     throw new TypeError('secret option array must contain one or more strings');
122   }
123
124   if (secret && !Array.isArray(secret)) {
125     secret = [secret];
126   }
127
128   if (!secret) {
129     deprecate('req.secret; provide secret option');
130   }
131
132   // notify user that this store is not
133   // meant for a production environment
134   if ('production' == env && store instanceof MemoryStore) {
135     console.warn(warning);
136   }
137
138   // generates the new session
139   store.generate = function(req){
140     req.sessionID = generateId(req);
141     req.session = new Session(req);
142     req.session.cookie = new Cookie(cookie);
143   };
144
145   var storeImplementsTouch = typeof store.touch === 'function';
146   store.on('disconnect', function(){ storeReady = false; });
147   store.on('connect', function(){ storeReady = true; });
148
149   return function session(req, res, next) {
150     // self-awareness
151     if (req.session) return next();
152
153     // Handle connection as if there is no session if
154     // the store has temporarily disconnected etc
155     if (!storeReady) return debug('store is disconnected'), next();
156
157     // pathname mismatch
158     var originalPath = parseUrl.original(req).pathname;
159     if (0 != originalPath.indexOf(cookie.path || '/')) return next();
160
161     // ensure a secret is available or bail
162     if (!secret && !req.secret) {
163       next(new Error('secret option required for sessions'));
164       return;
165     }
166
167     // backwards compatibility for signed cookies
168     // req.secret is passed from the cookie parser middleware
169     var secrets = secret || [req.secret];
170
171     var originalHash;
172     var originalId;
173     var savedHash;
174
175     // expose store
176     req.sessionStore = store;
177
178     // get the session ID from the cookie
179     var cookieId = req.sessionID = getcookie(req, name, secrets);
180
181     // set-cookie
182     onHeaders(res, function(){
183       if (!req.session) {
184         debug('no session');
185         return;
186       }
187
188       var cookie = req.session.cookie;
189
190       // only send secure cookies via https
191       if (cookie.secure && !issecure(req, trustProxy)) {
192         debug('not secured');
193         return;
194       }
195
196       if (!shouldSetCookie(req)) {
197         return;
198       }
199
200       setcookie(res, name, req.sessionID, secrets[0], cookie.data);
201     });
202
203     // proxy end() to commit the session
204     var _end = res.end;
205     var _write = res.write;
206     var ended = false;
207     res.end = function end(chunk, encoding) {
208       if (ended) {
209         return false;
210       }
211
212       ended = true;
213
214       var ret;
215       var sync = true;
216
217       function writeend() {
218         if (sync) {
219           ret = _end.call(res, chunk, encoding);
220           sync = false;
221           return;
222         }
223
224         _end.call(res);
225       }
226
227       function writetop() {
228         if (!sync) {
229           return ret;
230         }
231
232         if (chunk == null) {
233           ret = true;
234           return ret;
235         }
236
237         var contentLength = Number(res.getHeader('Content-Length'));
238
239         if (!isNaN(contentLength) && contentLength > 0) {
240           // measure chunk
241           chunk = !Buffer.isBuffer(chunk)
242             ? new Buffer(chunk, encoding)
243             : chunk;
244           encoding = undefined;
245
246           if (chunk.length !== 0) {
247             debug('split response');
248             ret = _write.call(res, chunk.slice(0, chunk.length - 1));
249             chunk = chunk.slice(chunk.length - 1, chunk.length);
250             return ret;
251           }
252         }
253
254         ret = _write.call(res, chunk, encoding);
255         sync = false;
256
257         return ret;
258       }
259
260       if (shouldDestroy(req)) {
261         // destroy session
262         debug('destroying');
263         store.destroy(req.sessionID, function ondestroy(err) {
264           if (err) {
265             defer(next, err);
266           }
267
268           debug('destroyed');
269           writeend();
270         });
271
272         return writetop();
273       }
274
275       // no session to save
276       if (!req.session) {
277         debug('no session');
278         return _end.call(res, chunk, encoding);
279       }
280
281       // touch session
282       req.session.touch();
283
284       if (shouldSave(req)) {
285         req.session.save(function onsave(err) {
286           if (err) {
287             defer(next, err);
288           }
289
290           writeend();
291         });
292
293         return writetop();
294       } else if (storeImplementsTouch && shouldTouch(req)) {
295         // store implements touch method
296         debug('touching');
297         store.touch(req.sessionID, req.session, function ontouch(err) {
298           if (err) {
299             defer(next, err);
300           }
301
302           debug('touched');
303           writeend();
304         });
305
306         return writetop();
307       }
308
309       return _end.call(res, chunk, encoding);
310     };
311
312     // generate the session
313     function generate() {
314       store.generate(req);
315       originalId = req.sessionID;
316       originalHash = hash(req.session);
317       wrapmethods(req.session);
318     }
319
320     // wrap session methods
321     function wrapmethods(sess) {
322       var _save = sess.save;
323
324       function save() {
325         debug('saving %s', this.id);
326         savedHash = hash(this);
327         _save.apply(this, arguments);
328       }
329
330       Object.defineProperty(sess, 'save', {
331         configurable: true,
332         enumerable: false,
333         value: save,
334         writable: true
335       });
336     }
337
338     // check if session has been modified
339     function isModified(sess) {
340       return originalId !== sess.id || originalHash !== hash(sess);
341     }
342
343     // check if session has been saved
344     function isSaved(sess) {
345       return originalId === sess.id && savedHash === hash(sess);
346     }
347
348     // determine if session should be destroyed
349     function shouldDestroy(req) {
350       return req.sessionID && unsetDestroy && req.session == null;
351     }
352
353     // determine if session should be saved to store
354     function shouldSave(req) {
355       // cannot set cookie without a session ID
356       if (typeof req.sessionID !== 'string') {
357         debug('session ignored because of bogus req.sessionID %o', req.sessionID);
358         return false;
359       }
360
361       return !saveUninitializedSession && cookieId !== req.sessionID
362         ? isModified(req.session)
363         : !isSaved(req.session)
364     }
365
366     // determine if session should be touched
367     function shouldTouch(req) {
368       // cannot set cookie without a session ID
369       if (typeof req.sessionID !== 'string') {
370         debug('session ignored because of bogus req.sessionID %o', req.sessionID);
371         return false;
372       }
373
374       return cookieId === req.sessionID && !shouldSave(req);
375     }
376
377     // determine if cookie should be set on response
378     function shouldSetCookie(req) {
379       // cannot set cookie without a session ID
380       if (typeof req.sessionID !== 'string') {
381         return false;
382       }
383
384       // in case of rolling session, always reset the cookie
385       if (rollingSessions) {
386         return true;
387       }
388
389       return cookieId != req.sessionID
390         ? saveUninitializedSession || isModified(req.session)
391         : req.session.cookie.expires != null && isModified(req.session);
392     }
393
394     // generate a session if the browser doesn't send a sessionID
395     if (!req.sessionID) {
396       debug('no SID sent, generating session');
397       generate();
398       next();
399       return;
400     }
401
402     // generate the session object
403     debug('fetching %s', req.sessionID);
404     store.get(req.sessionID, function(err, sess){
405       // error handling
406       if (err) {
407         debug('error %j', err);
408
409         if (err.code !== 'ENOENT') {
410           next(err);
411           return;
412         }
413
414         generate();
415       // no session
416       } else if (!sess) {
417         debug('no session found');
418         generate();
419       // populate req.session
420       } else {
421         debug('session found');
422         store.createSession(req, sess);
423         originalId = req.sessionID;
424         originalHash = hash(sess);
425
426         if (!resaveSession) {
427           savedHash = originalHash
428         }
429
430         wrapmethods(req.session);
431       }
432
433       next();
434     });
435   };
436 };
437
438 /**
439  * Generate a session ID for a new session.
440  *
441  * @return {String}
442  * @private
443  */
444
445 function generateSessionId(sess) {
446   return uid(24);
447 }
448
449 /**
450  * Get the session ID cookie from request.
451  *
452  * @return {string}
453  * @private
454  */
455
456 function getcookie(req, name, secrets) {
457   var header = req.headers.cookie;
458   var raw;
459   var val;
460
461   // read from cookie header
462   if (header) {
463     var cookies = cookie.parse(header);
464
465     raw = cookies[name];
466
467     if (raw) {
468       if (raw.substr(0, 2) === 's:') {
469         val = unsigncookie(raw.slice(2), secrets);
470
471         if (val === false) {
472           debug('cookie signature invalid');
473           val = undefined;
474         }
475       } else {
476         debug('cookie unsigned')
477       }
478     }
479   }
480
481   // back-compat read from cookieParser() signedCookies data
482   if (!val && req.signedCookies) {
483     val = req.signedCookies[name];
484
485     if (val) {
486       deprecate('cookie should be available in req.headers.cookie');
487     }
488   }
489
490   // back-compat read from cookieParser() cookies data
491   if (!val && req.cookies) {
492     raw = req.cookies[name];
493
494     if (raw) {
495       if (raw.substr(0, 2) === 's:') {
496         val = unsigncookie(raw.slice(2), secrets);
497
498         if (val) {
499           deprecate('cookie should be available in req.headers.cookie');
500         }
501
502         if (val === false) {
503           debug('cookie signature invalid');
504           val = undefined;
505         }
506       } else {
507         debug('cookie unsigned')
508       }
509     }
510   }
511
512   return val;
513 }
514
515 /**
516  * Hash the given `sess` object omitting changes to `.cookie`.
517  *
518  * @param {Object} sess
519  * @return {String}
520  * @private
521  */
522
523 function hash(sess) {
524   return crc(JSON.stringify(sess, function (key, val) {
525     if (key !== 'cookie') {
526       return val;
527     }
528   }));
529 }
530
531 /**
532  * Determine if request is secure.
533  *
534  * @param {Object} req
535  * @param {Boolean} [trustProxy]
536  * @return {Boolean}
537  * @private
538  */
539
540 function issecure(req, trustProxy) {
541   // socket is https server
542   if (req.connection && req.connection.encrypted) {
543     return true;
544   }
545
546   // do not trust proxy
547   if (trustProxy === false) {
548     return false;
549   }
550
551   // no explicit trust; try req.secure from express
552   if (trustProxy !== true) {
553     var secure = req.secure;
554     return typeof secure === 'boolean'
555       ? secure
556       : false;
557   }
558
559   // read the proto from x-forwarded-proto header
560   var header = req.headers['x-forwarded-proto'] || '';
561   var index = header.indexOf(',');
562   var proto = index !== -1
563     ? header.substr(0, index).toLowerCase().trim()
564     : header.toLowerCase().trim()
565
566   return proto === 'https';
567 }
568
569 /**
570  * Set cookie on response.
571  *
572  * @private
573  */
574
575 function setcookie(res, name, val, secret, options) {
576   var signed = 's:' + signature.sign(val, secret);
577   var data = cookie.serialize(name, signed, options);
578
579   debug('set-cookie %s', data);
580
581   var prev = res.getHeader('set-cookie') || [];
582   var header = Array.isArray(prev) ? prev.concat(data)
583     : Array.isArray(data) ? [prev].concat(data)
584     : [prev, data];
585
586   res.setHeader('set-cookie', header)
587 }
588
589 /**
590  * Verify and decode the given `val` with `secrets`.
591  *
592  * @param {String} val
593  * @param {Array} secrets
594  * @returns {String|Boolean}
595  * @private
596  */
597 function unsigncookie(val, secrets) {
598   for (var i = 0; i < secrets.length; i++) {
599     var result = signature.unsign(val, secrets[i]);
600
601     if (result !== false) {
602       return result;
603     }
604   }
605
606   return false;
607 }