Coverage

89%
772
694
78

connect.js

100%
22
22
0
LineHitsSource
1
2/*!
3 * Connect
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var EventEmitter = require('events').EventEmitter
14 , proto = require('./proto')
15 , utils = require('./utils')
16 , path = require('path')
17 , basename = path.basename
18 , fs = require('fs');
19
20// node patches
21
221require('./patch');
23
24// expose createServer() as the module
25
261exports = module.exports = createServer;
27
28/**
29 * Framework version.
30 */
31
321exports.version = '2.0.0alpha1';
33
34/**
35 * Expose the prototype.
36 */
37
381exports.proto = proto;
39
40/**
41 * Auto-load middleware getters.
42 */
43
441exports.middleware = {};
45
46/**
47 * Expose utilities.
48 */
49
501exports.utils = utils;
51
52/**
53 * Create a new connect server.
54 *
55 * @return {Function}
56 * @api public
57 */
58
591function createServer() {
60199 function app(req, res){ app.handle(req, res); }
6171 utils.merge(app, proto);
6271 utils.merge(app, EventEmitter.prototype);
6371 app.route = '/';
6471 app.stack = [];
6571 return app;
661};
67
68/**
69 * Auto-load bundled middleware with getters.
70 */
71
721fs.readdirSync(__dirname + '/middleware').forEach(function(filename){
7323 if (!/\.js$/.test(filename)) return;
7421 var name = basename(filename, '.js');
7521 function load(){
7699 return require('./middleware/' + name);
77 }
7821 exports.middleware.__defineGetter__(name, load);
7921 exports.__defineGetter__(name, load);
80});

proto.js

98%
62
61
1
LineHitsSource
1
2/*!
3 * Connect - HTTPServer
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var http = require('http')
14 , parse = require('url').parse
15 , utils = require('./utils');
16
17// prototype
18
191var app = module.exports = {};
20
21// environment
22
231var env = process.env.NODE_ENV || 'development';
24
25/**
26 * Utilize the given middleware `handle` to the given `route`,
27 * defaulting to _/_. This "route" is the mount-point for the
28 * middleware, when given a value other than _/_ the middleware
29 * is only effective when that segment is present in the request's
30 * pathname.
31 *
32 * For example if we were to mount a function at _/admin_, it would
33 * be invoked on _/admin_, and _/admin/settings_, however it would
34 * not be invoked for _/_, or _/posts_.
35 *
36 * Examples:
37 *
38 * var app = connect();
39 * app.use(connect.favicon());
40 * app.use(connect.logger());
41 * app.use(connect.static(__dirname + '/public'));
42 *
43 * If we wanted to prefix static files with _/public_, we could
44 * "mount" the `static()` middleware:
45 *
46 * app.use('/public', connect.static(__dirname + '/public'));
47 *
48 * This api is chainable, so the following is valid:
49 *
50 * connect
51 * .use(connect.favicon())
52 * .use(connect.logger())
53 * .use(connect.static(__dirname + '/public'))
54 * .listen(3000);
55 *
56 * @param {String|Function|Server} route, callback or server
57 * @param {Function|Server} callback or server
58 * @return {Server} for chaining
59 * @api public
60 */
61
621app.use = function(route, fn){
63 // default route to '/'
64155 if ('string' != typeof route) {
65150 fn = route;
66150 route = '/';
67 }
68
69 // wrap sub-apps
70155 if ('function' == typeof fn.handle) {
714 var server = fn;
724 fn.route = route;
734 fn = function(req, res, next){
742 server.handle(req, res, next);
75 };
76 }
77
78 // wrap vanilla http.Servers
79155 if (fn instanceof http.Server) {
801 fn = fn.listeners('request')[0];
81 }
82
83 // strip trailing slash
84155 if ('/' == route[route.length - 1]) {
85151 route = route.slice(0, -1);
86 }
87
88 // add the middleware
89155 this.stack.push({ route: route, handle: fn });
90
91155 return this;
92};
93
94/**
95 * Handle server requests, punting them down
96 * the middleware stack.
97 *
98 * @api private
99 */
100
1011app.handle = function(req, res, out) {
102130 var stack = this.stack
103 , fqdn = ~req.url.indexOf('://')
104 , removed = ''
105 , index = 0;
106
107130 function next(err) {
108268 var layer, path, status, c;
109268 req.url = removed + req.url;
110268 req.originalUrl = req.originalUrl || req.url;
111268 removed = '';
112
113 // next callback
114268 layer = stack[index++];
115
116 // all done
117268 if (!layer || res.headerSent) {
118 // delegate to parent
11911 if (out) return out(err);
120
121 // unhandled error
12211 if (err) {
123 // default to 500
12416 if (res.statusCode < 400) res.statusCode = 500;
125
126 // respect err.status
12714 if (err.status) res.statusCode = err.status;
128
129 // production gets a basic error message
1308 var msg = 'production' == env
131 ? http.STATUS_CODES[res.statusCode]
132 : err.stack || err.toString();
133
134 // log to stderr in a non-test env
1358 if ('test' != env) console.error(err.stack || err.toString());
1368 if (res.headerSent) return req.socket.destroy();
1378 res.setHeader('Content-Type', 'text/plain');
1388 res.setHeader('Content-Length', Buffer.byteLength(msg));
1398 if ('HEAD' == req.method) return res.end();
1408 res.end(msg);
141 } else {
1423 res.statusCode = 404;
1433 res.setHeader('Content-Type', 'text/plain');
1443 if ('HEAD' == req.method) return res.end();
1453 res.end('Cannot ' + req.method + ' ' + utils.escape(req.url));
146 }
14711 return;
148 }
149
150257 try {
151257 path = parse(req.url).pathname;
152257 if (undefined == path) path = '/';
153
154 // skip this layer if the route doesn't match.
155257 if (0 != path.indexOf(layer.route)) return next(err);
156
157257 c = path[layer.route.length];
158257 if (c && '/' != c && '.' != c) return next(err);
159
160 // Call the layer handler
161 // Trim off the part of the url that matches the route
162257 removed = layer.route;
163257 req.url = req.url.substr(removed.length);
164
165 // Ensure leading slash
166260 if (!fqdn && '/' != req.url[0]) req.url = '/' + req.url;
167
168257 var arity = layer.handle.length;
169257 if (err) {
1708 if (arity === 4) {
1713 layer.handle(err, req, res, next);
172 } else {
1735 next(err);
174 }
175249 } else if (arity < 4) {
176249 layer.handle(req, res, next);
177 } else {
1780 next();
179 }
180 } catch (e) {
1811 next(e);
182 }
183 }
184130 next();
185};

utils.js

86%
129
111
18
LineHitsSource
1
2/*!
3 * Connect - utils
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var http = require('http')
14 , crypto = require('crypto')
15 , Path = require('path')
16 , fs = require('fs');
17
18/**
19 * Extract the mime type from the given request's
20 * _Content-Type_ header.
21 *
22 * @param {IncomingMessage} req
23 * @return {String}
24 * @api private
25 */
26
271exports.mime = function(req) {
2849 var str = req.headers['content-type'] || '';
2949 return str.split(';')[0];
30};
31
32/**
33 * Generate an `Error` from the given status `code`.
34 *
35 * @param {Number} code
36 * @return {Error}
37 * @api private
38 */
39
401exports.error = function(code){
415 var err = new Error(http.STATUS_CODES[code]);
425 err.status = code;
435 return err;
44};
45
46/**
47 * Flatten the given `arr`.
48 *
49 * @param {Array} arr
50 * @return {Array}
51 * @api private
52 */
53
541exports.flatten = function(arr, ret){
550 var ret = ret || []
56 , len = arr.length;
570 for (var i = 0; i < len; ++i) {
580 if (Array.isArray(arr[i])) {
590 exports.flatten(arr[i], ret);
60 } else {
610 ret.push(arr[i]);
62 }
63 }
640 return ret;
65};
66
67/**
68 * Return md5 hash of the given string and optional encoding,
69 * defaulting to hex.
70 *
71 * utils.md5('wahoo');
72 * // => "e493298061761236c96b02ea6aa8a2ad"
73 *
74 * @param {String} str
75 * @param {String} encoding
76 * @return {String}
77 * @api public
78 */
79
801exports.md5 = function(str, encoding){
810 return crypto
82 .createHash('md5')
83 .update(str)
84 .digest(encoding || 'hex');
85};
86
87/**
88 * Merge object b with object a.
89 *
90 * var a = { foo: 'bar' }
91 * , b = { bar: 'baz' };
92 *
93 * utils.merge(a, b);
94 * // => { foo: 'bar', bar: 'baz' }
95 *
96 * @param {Object} a
97 * @param {Object} b
98 * @return {Object}
99 * @api private
100 */
101
1021exports.merge = function(a, b){
103178 if (a && b) {
104178 for (var key in b) {
105885 a[key] = b[key];
106 }
107 }
108178 return a;
109};
110
111/**
112 * Escape the given string of `html`.
113 *
114 * @param {String} html
115 * @return {String}
116 * @api private
117 */
118
1191exports.escape = function(html){
1203 return String(html)
121 .replace(/&(?!\w+;)/g, '&amp;')
122 .replace(/</g, '&lt;')
123 .replace(/>/g, '&gt;')
124 .replace(/"/g, '&quot;');
125};
126
127
128/**
129 * Return a unique identifier with the given `len`.
130 *
131 * utils.uid(10);
132 * // => "FDaS435D2z"
133 *
134 * @param {Number} len
135 * @return {String}
136 * @api private
137 */
138
1391exports.uid = function(len) {
14043 return crypto.randomBytes(Math.ceil(len * 3 / 4))
141 .toString('base64')
142 .slice(0, len);
143};
144
145/**
146 * Sign the given `val` with `secret`.
147 *
148 * @param {String} val
149 * @param {String} secret
150 * @return {String}
151 * @api private
152 */
153
1541exports.sign = function(val, secret){
15547 return val + '.' + crypto
156 .createHmac('sha1', secret)
157 .update(val)
158 .digest('base64')
159 .replace(/=+$/, '');
160};
161
162/**
163 * Unsign and decode the given `val` with `secret`,
164 * returning `false` if the signature is invalid.
165 *
166 * @param {String} val
167 * @param {String} secret
168 * @return {String|Boolean}
169 * @api private
170 */
171
1721exports.unsign = function(val, secret){
17317 var str = val.slice(0,val.lastIndexOf('.'));
17417 return exports.sign(str, secret) == val
175 ? str
176 : false;
177};
178
179/**
180 * Parse signed cookies, returning an object
181 * containing the decoded key/value pairs,
182 * while removing the signed key from `obj`.
183 *
184 * @param {Object} obj
185 * @return {Object}
186 * @api private
187 */
188
1891exports.parseSignedCookies = function(obj, secret){
1908 var ret = {};
1918 Object.keys(obj).forEach(function(key){
19212 var val = obj[key]
193 , signed = exports.unsign(val, secret);
194
19512 if (signed) {
1967 ret[key] = signed;
1977 delete obj[key];
198 }
199 });
2008 return ret;
201};
202
203/**
204 * Parse JSON cookies.
205 *
206 * @param {Object} obj
207 * @return {Object}
208 * @api private
209 */
210
2111exports.parseJSONCookies = function(obj){
21216 Object.keys(obj).forEach(function(key){
21312 var val = obj[key];
21412 if (0 == val.indexOf('j:')) {
2152 try {
2162 obj[key] = JSON.parse(val.slice(2));
217 } catch (err) {
218 // nothing
219 }
220 }
221 });
22216 return obj;
223};
224
225/**
226 * Parse the given cookie string into an object.
227 *
228 * @param {String} str
229 * @return {Object}
230 * @api private
231 */
232
2331exports.parseCookie = function(str){
23414 var obj = {}
235 , pairs = str.split(/[;,] */);
23614 for (var i = 0, len = pairs.length; i < len; ++i) {
23719 var pair = pairs[i]
238 , eqlIndex = pair.indexOf('=')
239 , key = pair.substr(0, eqlIndex).trim()
240 , val = pair.substr(++eqlIndex, pair.length).trim();
241
242 // quoted values
24320 if ('"' == val[0]) val = val.slice(1, -1);
244
245 // only assign once
24619 if (undefined == obj[key]) {
24719 val = val.replace(/\+/g, ' ');
24819 try {
24919 obj[key] = decodeURIComponent(val);
250 } catch (err) {
2510 if (err instanceof URIError) {
2520 obj[key] = val;
253 } else {
2540 throw err;
255 }
256 }
257 }
258 }
25914 return obj;
260};
261
262/**
263 * Serialize the given object into a cookie string.
264 *
265 * utils.serializeCookie('name', 'tj', { httpOnly: true })
266 * // => "name=tj; httpOnly"
267 *
268 * @param {String} name
269 * @param {String} val
270 * @param {Object} obj
271 * @return {String}
272 * @api private
273 */
274
2751exports.serializeCookie = function(name, val, obj){
27633 var pairs = [name + '=' + encodeURIComponent(val)]
277 , obj = obj || {};
278
27933 if (obj.domain) pairs.push('domain=' + obj.domain);
28062 if (obj.path) pairs.push('path=' + obj.path);
28150 if (obj.expires) pairs.push('expires=' + obj.expires.toUTCString());
28257 if (obj.httpOnly) pairs.push('httpOnly');
28337 if (obj.secure) pairs.push('secure');
284
28533 return pairs.join('; ');
286};
287
288/**
289 * Pause `data` and `end` events on the given `obj`.
290 * Middleware performing async tasks _should_ utilize
291 * this utility (or similar), to re-emit data once
292 * the async operation has completed, otherwise these
293 * events may be lost.
294 *
295 * var pause = utils.pause(req);
296 * fs.readFile(path, function(){
297 * next();
298 * pause.resume();
299 * });
300 *
301 * @param {Object} obj
302 * @return {Object}
303 * @api private
304 */
305
3061exports.pause = function(obj){
3077 var onData
308 , onEnd
309 , events = [];
310
311 // buffer data
3127 obj.on('data', onData = function(data, encoding){
3130 events.push(['data', data, encoding]);
314 });
315
316 // buffer end
3177 obj.on('end', onEnd = function(data, encoding){
3186 events.push(['end', data, encoding]);
319 });
320
3217 return {
322 end: function(){
3236 obj.removeListener('data', onData);
3246 obj.removeListener('end', onEnd);
325 },
326 resume: function(){
3276 this.end();
3286 for (var i = 0, len = events.length; i < len; ++i) {
3295 obj.emit.apply(obj, events[i]);
330 }
331 }
332 };
333};
334
335/**
336 * Check `req` and `res` to see if it has been modified.
337 *
338 * @param {IncomingMessage} req
339 * @param {ServerResponse} res
340 * @return {Boolean}
341 * @api private
342 */
343
3441exports.modified = function(req, res, headers) {
3451 var headers = headers || res._headers || {}
346 , modifiedSince = req.headers['if-modified-since']
347 , lastModified = headers['last-modified']
348 , noneMatch = req.headers['if-none-match']
349 , etag = headers['etag'];
350
3511 if (noneMatch) noneMatch = noneMatch.split(/ *, */);
352
353 // check If-None-Match
3541 if (noneMatch && etag && ~noneMatch.indexOf(etag)) {
3550 return false;
356 }
357
358 // check If-Modified-Since
3591 if (modifiedSince && lastModified) {
3601 modifiedSince = new Date(modifiedSince);
3611 lastModified = new Date(lastModified);
362 // Ignore invalid dates
3631 if (!isNaN(modifiedSince.getTime())) {
3642 if (lastModified <= modifiedSince) return false;
365 }
366 }
367
3680 return true;
369};
370
371/**
372 * Strip `Content-*` headers from `res`.
373 *
374 * @param {ServerResponse} res
375 * @api private
376 */
377
3781exports.removeContentHeaders = function(res){
3791 Object.keys(res._headers).forEach(function(field){
3806 if (0 == field.indexOf('content')) {
3811 res.removeHeader(field);
382 }
383 });
384};
385
386/**
387 * Check if `req` is a conditional GET request.
388 *
389 * @param {IncomingMessage} req
390 * @return {Boolean}
391 * @api private
392 */
393
3941exports.conditionalGET = function(req) {
39536 return req.headers['if-modified-since']
396 || req.headers['if-none-match'];
397};
398
399/**
400 * Respond with 401 "Unauthorized".
401 *
402 * @param {ServerResponse} res
403 * @param {String} realm
404 * @api private
405 */
406
4071exports.unauthorized = function(res, realm) {
4086 res.statusCode = 401;
4096 res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
4106 res.end('Unauthorized');
411};
412
413/**
414 * Respond with 304 "Not Modified".
415 *
416 * @param {ServerResponse} res
417 * @param {Object} headers
418 * @api private
419 */
420
4211exports.notModified = function(res) {
4221 exports.removeContentHeaders(res);
4231 res.statusCode = 304;
4241 res.end();
425};
426
427/**
428 * Return an ETag in the form of `"<size>-<mtime>"`
429 * from the given `stat`.
430 *
431 * @param {Object} stat
432 * @return {String}
433 * @api private
434 */
435
4361exports.etag = function(stat) {
4370 return '"' + stat.size + '-' + Number(stat.mtime) + '"';
438};
439
440/**
441 * Parse "Range" header `str` relative to the given file `size`.
442 *
443 * @param {Number} size
444 * @param {String} str
445 * @return {Array}
446 * @api private
447 */
448
4491exports.parseRange = function(size, str){
45015 var valid = true;
45115 var arr = str.substr(6).split(',').map(function(range){
45215 var range = range.split('-')
453 , start = parseInt(range[0], 10)
454 , end = parseInt(range[1], 10);
455
456 // -500
45715 if (isNaN(start)) {
4584 start = size - end;
4594 end = size - 1;
460 // 500-
46111 } else if (isNaN(end)) {
4623 end = size - 1;
463 }
464
465 // Invalid
46616 if (isNaN(start) || isNaN(end) || start > end) valid = false;
467
46815 return { start: start, end: end };
469 });
47015 return valid ? arr : undefined;
471};
472
473/**
474 * Parse the given Cache-Control `str`.
475 *
476 * @param {String} str
477 * @return {Object}
478 * @api private
479 */
480
4811exports.parseCacheControl = function(str){
48226 var directives = str.split(',')
483 , obj = {};
484
48526 for(var i = 0, len = directives.length; i < len; i++) {
48637 var parts = directives[i].split('=')
487 , key = parts.shift().trim()
488 , val = parseInt(parts.shift(), 10);
489
49037 obj[key] = isNaN(val) ? true : val;
491 }
492
49326 return obj;
494};
495
496/**
497 * Convert array-like object to an `Array`.
498 *
499 * node-bench measured "16.5 times faster than Array.prototype.slice.call()"
500 *
501 * @param {Object} obj
502 * @return {Array}
503 * @api private
504 */
505
5061var toArray = exports.toArray = function(obj){
5070 var len = obj.length
508 , arr = new Array(len);
5090 for (var i = 0; i < len; ++i) {
5100 arr[i] = obj[i];
511 }
5120 return arr;
513};

patch.js

90%
21
19
2
LineHitsSource
1
2/*!
3 * Connect
4 * Copyright(c) 2011 TJ Holowaychuk
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
121var http = require('http')
13 , res = http.ServerResponse.prototype
14 , setHeader = res.setHeader
15 , _renderHeaders = res._renderHeaders
16 , writeHead = res.writeHead;
17
18// apply only once
19
201if (!res._hasConnectPatch) {
21
22 /**
23 * Provide a public "header sent" flag
24 * until node does.
25 *
26 * @return {Boolean}
27 * @api public
28 */
29
301 res.__defineGetter__('headerSent', function(){
31273 return this._header;
32 });
33
34 /**
35 * Set header `field` to `val`, special-casing
36 * the `Set-Cookie` field for multiple support.
37 *
38 * @param {String} field
39 * @param {String} val
40 * @api public
41 */
42
431 res.setHeader = function(field, val){
44265 var key = field.toLowerCase()
45 , prev;
46
47 // special-case Set-Cookie
48265 if (this._headers && 'set-cookie' == key) {
493 if (prev = this.getHeader(field)) {
500 val = Array.isArray(prev)
51 ? prev.concat(val)
52 : [prev, val];
53 }
54 // charset
55262 } else if ('content-type' == key && this.charset) {
560 val += '; charset=' + this.charset;
57 }
58
59265 return setHeader.call(this, field, val);
60 };
61
62 /**
63 * Proxy to emit "header" event.
64 */
65
661 res._renderHeaders = function(){
6778 if (!this._emittedHeader) this.emit('header');
6878 this._emittedHeader = true;
6978 return _renderHeaders.call(this);
70 };
71
721 res.writeHead = function(){
73256 if (!this._emittedHeader) this.emit('header');
74128 this._emittedHeader = true;
75128 return writeHead.apply(this, arguments);
76 };
77
781 res._hasConnectPatch = true;
79}

middleware/basicAuth.js

100%
28
28
0
LineHitsSource
1
2/*!
3 * Connect - basicAuth
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var utils = require('../utils')
14 , unauthorized = utils.unauthorized;
15
16/**
17 * Basic Auth:
18 *
19 * Enfore basic authentication by providing a `callback(user, pass)`,
20 * which must return `true` in order to gain access. Alternatively an async
21 * method is provided as well, invoking `callback(user, pass, callback)`. Populates
22 * `req.user`. The final alternative is simply passing username / password
23 * strings.
24 *
25 * Simple username and password
26 *
27 * connect(connect.basicAuth('username', 'password'));
28 *
29 * Callback verification
30 *
31 * connect()
32 * .use(connect.basicAuth(function(user, pass){
33 * return 'tj' == user & 'wahoo' == pass;
34 * }))
35 *
36 * Async callback verification, accepting `fn(err, user)`.
37 *
38 * connect()
39 * .use(connect.basicAuth(function(user, pass, fn){
40 * User.authenticate({ user: user, pass: pass }, fn);
41 * }))
42 *
43 * @param {Function|String} callback or username
44 * @param {String} realm
45 * @api public
46 */
47
481module.exports = function basicAuth(callback, realm) {
493 var username, password;
50
51 // user / pass strings
523 if ('string' == typeof callback) {
531 username = callback;
541 password = realm;
551 if ('string' != typeof password) throw new Error('password argument required');
561 realm = arguments[2];
571 callback = function(user, pass){
582 return user == username && pass == password;
59 }
60 }
61
623 realm = realm || 'Authorization Required';
63
643 return function(req, res, next) {
659 var authorization = req.headers.authorization;
66
679 if (req.user) return next();
6812 if (!authorization) return unauthorized(res, realm);
69
706 var parts = authorization.split(' ')
71 , scheme = parts[0]
72 , credentials = new Buffer(parts[1], 'base64').toString().split(':')
73 , user = credentials[0]
74 , pass = credentials[1];
75
766 if ('Basic' != scheme) return next(utils.error(400));
77
78 // async
796 if (callback.length >= 3) {
802 var pause = utils.pause(req);
812 callback(user, pass, function(err, user){
823 if (err || !user) return unauthorized(res, realm);
831 req.user = user;
841 next();
851 pause.resume();
86 });
87 // sync
88 } else {
894 if (callback(user, pass)) {
902 req.user = user;
912 next();
92 } else {
932 unauthorized(res, realm);
94 }
95 }
96 }
97};
98

middleware/bodyParser.js

100%
9
9
0
LineHitsSource
1
2/*!
3 * Connect - bodyParser
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var multipart = require('./multipart')
14 , urlencoded = require('./urlencoded')
15 , json = require('./json');
16
17/**
18 * Body parser:
19 *
20 * Parse request bodies, supports _application/json_,
21 * _application/x-www-form-urlencoded_, and _multipart/form-data_.
22 *
23 * This is equivalent to:
24 *
25 * app.use(connect.json());
26 * app.use(connect.urlencoded());
27 * app.use(connect.multipart());
28 *
29 * Examples:
30 *
31 * connect()
32 * .use(connect.bodyParser())
33 * .use(function(req, res) {
34 * res.end('viewing user ' + req.body.user.name);
35 * });
36 *
37 * $ curl -d 'user[name]=tj' http://local/
38 * $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://local/
39 *
40 * View [json](json.html), [urlencoded](urlencoded.html), and [multipart](multipart.html) for more info.
41 *
42 * @param {Object} options
43 * @return {Function}
44 * @api public
45 */
46
471exports = module.exports = function bodyParser(options){
486 var _urlencoded = urlencoded(options)
49 , _multipart = multipart(options)
50 , _json = json(options);
51
526 return function bodyParser(req, res, next) {
5311 _json(req, res, function(err){
5411 if (err) return next(err);
5511 _urlencoded(req, res, function(err){
5611 if (err) return next(err);
5711 _multipart(req, res, next);
58 });
59 });
60 }
61};

middleware/multipart.js

94%
34
32
2
LineHitsSource
1
2/*!
3 * Connect - multipart
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var formidable = require('formidable')
14 , utils = require('../utils')
15 , qs = require('qs');
16
17/**
18 * Multipart:
19 *
20 * Parse multipart/form-data request bodies,
21 * providing the parsed object as `req.body`
22 * and `req.files`.
23 *
24 * Configuration:
25 *
26 * The options passed are merged with [formidable](https://github.com/felixge/node-formidable)'s
27 * `IncomingForm` object, allowing you to configure the upload directory,
28 * size limits, etc. For example if you wish to change the upload dir do the following.
29 *
30 * app.use(connect.multipart({ uploadDir: path }));
31 *
32 * @param {Object} options
33 * @return {Function}
34 * @api public
35 */
36
371exports = module.exports = function(options){
3813 options = options || {};
3913 return function multipart(req, res, next) {
4024 if (req._body) return next();
4120 req.body = req.body || {};
4220 req.files = req.files || {};
43
44 // ignore GET
4521 if ('GET' == req.method || 'HEAD' == req.method) return next();
46
47 // check Content-Type
4822 if ('multipart/form-data' != utils.mime(req)) return next();
49
50 // flag as parsed
5116 req._body = true;
52
53 // parse
5416 var form = new formidable.IncomingForm
55 , data = {}
56 , files = {}
57 , done;
58
5916 Object.keys(options).forEach(function(key){
602 form[key] = options[key];
61 });
62
6316 function ondata(name, val, data){
6432 if (Array.isArray(data[name])) {
650 data[name].push(val);
6632 } else if (data[name]) {
672 data[name] = [data[name], val];
68 } else {
6930 data[name] = val;
70 }
71 }
72
7316 form.on('field', function(name, val){
7418 ondata(name, val, data);
75 });
76
7716 form.on('file', function(name, val){
7814 ondata(name, val, files);
79 });
80
8116 form.on('error', function(err){
822 next(err);
832 done = true;
84 });
85
8616 form.on('end', function(){
8718 if (done) return;
8814 try {
8914 req.body = qs.parse(data);
9014 req.files = qs.parse(files);
9114 next();
92 } catch (err) {
930 next(err);
94 }
95 });
96
9716 form.parse(req);
98 }
99};

middleware/urlencoded.js

94%
17
16
1
LineHitsSource
1
2/*!
3 * Connect - urlencoded
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var utils = require('../utils')
14 , qs = require('qs');
15
16/**
17 * Urlencoded:
18 *
19 * Parse x-ww-form-urlencoded request bodies,
20 * providing the parsed object as `req.body`.
21 *
22 * @param {Object} options
23 * @return {Function}
24 * @api public
25 */
26
271exports = module.exports = function(options){
287 options = options || {};
297 return function urlencoded(req, res, next) {
3014 if (req._body) return next();
3112 req.body = req.body || {};
32
33 // ignore GET
3412 if ('GET' == req.method || 'HEAD' == req.method) return next();
35
36 // check Content-Type
3722 if ('application/x-www-form-urlencoded' != utils.mime(req)) return next();
38
39 // flag as parsed
402 req._body = true;
41
42 // parse
432 var buf = '';
442 req.setEncoding('utf8');
454 req.on('data', function(chunk){ buf += chunk });
462 req.on('end', function(){
472 try {
482 req.body = buf.length
49 ? qs.parse(buf)
50 : {};
512 next();
52 } catch (err){
530 next(err);
54 }
55 });
56 }
57};

middleware/json.js

100%
18
18
0
LineHitsSource
1
2/*!
3 * Connect - json
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var utils = require('../utils');
14
15/**
16 * JSON:
17 *
18 * Parse JSON request bodies, providing the
19 * parsed object as `req.body`.
20 *
21 * @param {Object} options
22 * @return {Function}
23 * @api public
24 */
25
261exports = module.exports = function(options){
278 options = options || {};
288 return function json(req, res, next) {
2916 if (req._body) return next();
3016 req.body = req.body || {};
31
32 // ignore GET
3317 if ('GET' == req.method || 'HEAD' == req.method) return next();
34
35 // check Content-Type
3626 if ('application/json' != utils.mime(req)) return next();
37
38 // flag as parsed
394 req._body = true;
40
41 // parse
424 var buf = '';
434 req.setEncoding('utf8');
448 req.on('data', function(chunk){ buf += chunk });
454 req.on('end', function(){
464 try {
474 req.body = buf.length
48 ? JSON.parse(buf)
49 : {};
502 next();
51 } catch (err){
522 err.status = 400;
532 next(err);
54 }
55 });
56 }
57};

middleware/compress.js

100%
35
35
0
LineHitsSource
1
2/*!
3 * Connect - compress
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var zlib = require('zlib');
14
15/**
16 * Supported content-encoding methods.
17 */
18
191exports.methods = {
20 gzip: zlib.createGzip
21 , deflate: zlib.createDeflate
22};
23
24/**
25 * Default filter function.
26 */
27
281exports.filter = function(req, res){
299 var type = res.getHeader('Content-Type') || '';
309 return type.match(/json|text|javascript/);
31};
32
33/**
34 * Compress:
35 *
36 * Compress response data with gzip/deflate.
37 *
38 * Filter:
39 *
40 * A `filter` callback function may be passed to
41 * replace the default logic of:
42 *
43 * exports.filter = function(req, res){
44 * var type = res.getHeader('Content-Type') || '';
45 * return type.match(/json|text|javascript/);
46 * };
47 *
48 * Options:
49 *
50 * All remaining options are passed to the gzip/deflate
51 * creation functions. Consult node's docs for additional details.
52 *
53 * - `chunkSize` (default: 16*1024)
54 * - `windowBits`
55 * - `level`: 0-9 where 0 is no compression, and 9 is slow but best compression
56 * - `memLevel`: 1-9 low is slower but uses less memory, high is fast but uses more
57 * - `strategy`: compression strategy
58 *
59 * @param {Object} options
60 * @return {Function}
61 * @api public
62 */
63
641module.exports = function compress(options) {
651 var options = options || {}
66 , names = Object.keys(exports.methods)
67 , filter = options.filter || exports.filter;
68
691 return function(req, res, next){
709 var accept = req.headers['accept-encoding']
71 , write = res.write
72 , end = res.end
73 , stream
74 , method;
75
76 // vary
779 res.setHeader('Vary', 'Accept-Encoding');
78
79 // proxy
80
819 res.write = function(chunk, encoding){
8214 if (!this.headerSent) this._implicitHeader();
837 return stream
84 ? stream.write(chunk, encoding)
85 : write.call(res, chunk, encoding);
86 };
87
889 res.end = function(chunk, encoding){
899 if (chunk) this.write(chunk, encoding);
909 return stream
91 ? stream.end()
92 : end.call(res);
93 };
94
959 res.on('header', function(){
96 // default request filter
9710 if (!filter(req, res)) return;
98
99 // SHOULD use identity
1009 if (!accept) return;
101
102 // head
1038 if ('HEAD' == req.method) return;
104
105 // default to gzip
1066 if ('*' == accept.trim()) method = 'gzip';
107
108 // compression method
1096 if (!method) {
1106 for (var i = 0, len = names.length; i < len; ++i) {
1116 if (~accept.indexOf(names[i])) {
1126 method = names[i];
1136 break;
114 }
115 }
116 }
117
118 // compression method
1196 if (!method) return;
120
121 // compression stream
1226 stream = exports.methods[method](options);
123
124 // header fields
1256 res.setHeader('Content-Encoding', method);
1266 res.removeHeader('Content-Length');
127
128 // compression
129
1306 stream.on('data', function(chunk){
13112 write.call(res, chunk);
132 });
133
1346 stream.on('end', function(){
1356 end.call(res);
136 });
137
138 });
139
1409 next();
141 };
142}

middleware/static.js

90%
77
70
7
LineHitsSource
1/*!
2 * Connect - staticProvider
3 * Copyright(c) 2010 Sencha Inc.
4 * Copyright(c) 2011 TJ Holowaychuk
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
121var fs = require('fs')
13 , path = require('path')
14 , join = path.join
15 , basename = path.basename
16 , normalize = path.normalize
17 , utils = require('../utils')
18 , Buffer = require('buffer').Buffer
19 , parse = require('url').parse
20 , mime = require('mime');
21
22/**
23 * Static:
24 *
25 * Static file server with the given `root` path.
26 *
27 * Examples:
28 *
29 * var oneDay = 86400000;
30 *
31 * connect()
32 * .use(connect.static(__dirname + '/public'))
33 *
34 * connect()
35 * .use(connect.static(__dirname + '/public', { maxAge: oneDay }))
36 *
37 * Options:
38 *
39 * - `maxAge` Browser cache maxAge in milliseconds. defaults to 0
40 * - `hidden` Allow transfer of hidden files. defaults to false
41 * - `redirect` Redirect to trailing "/" when the pathname is a dir
42 *
43 * @param {String} root
44 * @param {Object} options
45 * @return {Function}
46 * @api public
47 */
48
491exports = module.exports = function static(root, options){
506 options = options || {};
51
52 // root required
536 if (!root) throw new Error('static() root path required');
546 options.root = root;
55
566 return function static(req, res, next) {
5736 options.path = req.url;
5836 options.getOnly = true;
5936 send(req, res, next, options);
60 };
61};
62
63/**
64 * Expose mime module.
65 *
66 * If you wish to extend the mime table use this
67 * reference to the "mime" module in the npm registry.
68 */
69
701exports.mime = mime;
71
72/**
73 * decodeURIComponent.
74 *
75 * Allows V8 to only deoptimize this fn instead of all
76 * of send().
77 *
78 * @param {String} path
79 * @api private
80 */
81
821function decode(path){
8336 try {
8436 return decodeURIComponent(path);
85 } catch (err) {
861 return err;
87 }
88}
89
90/**
91 * Attempt to tranfer the requested file to `res`.
92 *
93 * @param {ServerRequest}
94 * @param {ServerResponse}
95 * @param {Function} next
96 * @param {Object} options
97 * @api private
98 */
99
1001var send = exports.send = function(req, res, next, options){
10136 options = options || {};
10236 if (!options.path) throw new Error('path required');
103
104 // setup
10536 var maxAge = options.maxAge || 0
106 , ranges = req.headers.range
107 , head = 'HEAD' == req.method
108 , get = 'GET' == req.method
109 , root = options.root ? normalize(options.root) : null
110 , redirect = false === options.redirect ? false : true
111 , getOnly = options.getOnly
112 , fn = options.callback
113 , hidden = options.hidden
114 , done;
115
116 // replace next() with callback when available
11736 if (fn) next = fn;
118
119 // ignore non-GET requests
12036 if (getOnly && !get && !head) return next();
121
122 // parse url
12336 var url = parse(options.path)
124 , path = decode(url.pathname)
125 , type;
126
12737 if ('URIError: URI malformed' == path) return next(utils.error(400));
128
129 // null byte(s)
13035 if (~path.indexOf('\0')) return next(utils.error(400));
131
132 // when root is not given, consider .. malicious
13335 if (!root && ~path.indexOf('..')) return next(utils.error(403));
134
135 // index.html support
13636 if (normalize('/') == path[path.length - 1]) path += 'index.html';
137
138 // join / normalize from optional root dir
13935 path = normalize(join(root, path));
140
141 // malicious path
14237 if (root && 0 != path.indexOf(root)) return next(utils.error(403));
143
144 // "hidden" file
14534 if (!hidden && '.' == basename(path)[0]) return next();
146
14732 fs.stat(path, function(err, stat){
148 // mime type
14932 type = mime.lookup(path);
150
151 // ignore ENOENT
15232 if (err) {
1531 if (fn) return fn(err);
1541 return ('ENOENT' == err.code || 'ENAMETOOLONG' == err.code)
155 ? next()
156 : next(err);
157 // redirect directory in case index.html is present
15831 } else if (stat.isDirectory()) {
1591 if (!redirect) return next();
1601 res.statusCode = 301;
1611 res.setHeader('Location', url.pathname + '/');
1621 res.end('Redirecting to ' + url.pathname + '/');
1631 return;
164 }
165
166 // header fields
16760 if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString());
16858 if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (maxAge / 1000));
16960 if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString());
17030 if (!res.getHeader('Content-Type')) {
17130 var charset = mime.charsets.lookup(type);
17230 res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
173 }
17430 res.setHeader('Accept-Ranges', 'bytes');
175
176 // conditional GET support
17730 if (utils.conditionalGET(req)) {
1781 if (!utils.modified(req, res)) {
1791 req.emit('static');
1801 return utils.notModified(res);
181 }
182 }
183
18429 var opts = {}
185 , len = stat.size;
186
187 // we have a Range request
18829 if (ranges) {
1897 ranges = utils.parseRange(stat.size, ranges);
190 // valid
1917 if (ranges) {
192 // TODO: stream options
193 // TODO: multiple support
1946 opts.start = ranges[0].start;
1956 opts.end = ranges[0].end;
1966 len = Math.min(len, opts.end - opts.start + 1);
1976 res.statusCode = 206;
1986 res.setHeader('Content-Range', 'bytes '
199 + opts.start
200 + '-'
201 + opts.end
202 + '/'
203 + stat.size);
204 // invalid range
205 } else {
2061 return next(utils.error(416));
207 }
208 }
209
21028 res.setHeader('Content-Length', len);
211
212 // transfer
21332 if (head) return res.end();
214
215 // stream
21624 var stream = fs.createReadStream(path, opts);
21724 req.emit('static', stream);
21824 req.on('close', stream.destroy.bind(stream));
21924 stream.pipe(res);
220
221 // callback
22224 if (fn) {
2230 function callback(err) { done || fn(err); done = true }
2240 req.on('close', callback);
2250 req.socket.on('error', callback);
2260 stream.on('error', callback);
2270 stream.on('end', callback);
228 } else {
22924 stream.on('error', function(err){
2301 if (res.headerSent) {
2310 console.error(err.stack);
2320 req.destroy();
233 } else {
2341 next(err);
235 }
236 });
237 }
238 });
239};

middleware/limit.js

88%
17
15
2
LineHitsSource
1
2/*!
3 * Connect - limit
4 * Copyright(c) 2011 TJ Holowaychuk
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
121var utils = require('../utils');
13
14/**
15 * Limit:
16 *
17 * Limit request bodies to the given size in `bytes`.
18 *
19 * A string representation of the bytesize may also be passed,
20 * for example "5mb", "200kb", "1gb", etc.
21 *
22 * connect()
23 * .use(connect.limit('5.5mb'))
24 * .use(handleImageUpload)
25 *
26 * @param {Number|String} bytes
27 * @return {Function}
28 * @api public
29 */
30
311module.exports = function limit(bytes){
322 if ('string' == typeof bytes) bytes = parse(bytes);
331 if ('number' != typeof bytes) throw new Error('limit() bytes required');
341 return function limit(req, res, next){
352 var received = 0
36 , len = req.headers['content-length']
37 ? parseInt(req.headers['content-length'], 10)
38 : null;
39
40 // self-awareness
412 if (req._limit) return next();
422 req._limit = true;
43
44 // limit by content-length
453 if (len && len > bytes) return next(utils.error(413));
46
47 // limit
481 req.on('data', function(chunk){
490 received += chunk.length;
500 if (received > bytes) req.destroy();
51 });
52
531 next();
54 };
55};
56
57/**
58 * Parse byte `size` string.
59 *
60 * @param {String} size
61 * @return {Number}
62 * @api private
63 */
64
651function parse(size) {
661 var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/)
67 , n = parseFloat(parts[1])
68 , type = parts[2];
69
701 var map = {
71 kb: 1024
72 , mb: 1024 * 1024
73 , gb: 1024 * 1024 * 1024
74 };
75
761 return map[type] * n;
77}

middleware/query.js

100%
5
5
0
LineHitsSource
1
2/*!
3 * Connect - query
4 * Copyright(c) 2011 TJ Holowaychuk
5 * Copyright(c) 2011 Sencha Inc.
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var qs = require('qs')
14 , parse = require('url').parse;
15
16/**
17 * Query:
18 *
19 * Automatically parse the query-string when available,
20 * populating the `req.query` object.
21 *
22 * Examples:
23 *
24 * connect()
25 * .use(connect.query())
26 * .use(function(req, res){
27 * res.end(JSON.stringify(req.query));
28 * });
29 *
30 * @return {Function}
31 * @api public
32 */
33
341module.exports = function query(){
351 return function query(req, res, next){
362 req.query = ~req.url.indexOf('?')
37 ? qs.parse(parse(req.url).query)
38 : {};
392 next();
40 };
41};

middleware/responseTime.js

100%
9
9
0
LineHitsSource
1
2/*!
3 * Connect - responseTime
4 * Copyright(c) 2011 TJ Holowaychuk
5 * MIT Licensed
6 */
7
8/**
9 * Reponse time:
10 *
11 * Adds the `X-Response-Time` header displaying the response
12 * duration in milliseconds.
13 *
14 * @return {Function}
15 * @api public
16 */
17
181module.exports = function responseTime(){
191 return function(req, res, next){
201 var start = new Date;
21
221 if (res._responseTime) return next();
231 res._responseTime = true;
24
251 res.on('header', function(header){
261 var duration = new Date - start;
271 res.setHeader('X-Response-time', duration + 'ms');
28 });
29
301 next();
31 };
32};

middleware/cookieParser.js

94%
17
16
1
LineHitsSource
1
2/*!
3 * Connect - cookieParser
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var utils = require('./../utils');
14
15/**
16 * Cookie parser:
17 *
18 * Parse _Cookie_ header and populate `req.cookies`
19 * with an object keyed by the cookie names. Optionally
20 * you may enabled signed cookie support by passing
21 * a `secret` string, which assigns `req.secret` so
22 * it may be used by other middleware such as `session()`.
23 *
24 * Examples:
25 *
26 * connect()
27 * .use(connect.cookieParser('keyboard cat'))
28 * .use(function(req, res, next){
29 * res.end(JSON.stringify(req.cookies));
30 * })
31 *
32 * @param {String} secret
33 * @return {Function}
34 * @api public
35 */
36
371module.exports = function cookieParser(secret){
3829 return function cookieParser(req, res, next) {
3935 var cookie = req.headers.cookie;
4035 if (req.cookies) return next();
41
4235 req.secret = secret;
4335 req.cookies = {};
4435 req.signedCookies = {};
45
4635 if (cookie) {
478 try {
488 req.cookies = utils.parseCookie(cookie);
498 if (secret) {
508 req.signedCookies = utils.parseSignedCookies(req.cookies, secret);
518 req.signedCookies = utils.parseJSONCookies(req.signedCookies);
52 }
538 req.cookies = utils.parseJSONCookies(req.cookies);
54 } catch (err) {
550 return next(err);
56 }
57 }
5835 next();
59 };
60};

middleware/session.js

86%
67
58
9
LineHitsSource
1
2/*!
3 * Connect - session
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var Session = require('./session/session')
14 , debug = require('debug')('connect:session')
15 , MemoryStore = require('./session/memory')
16 , Cookie = require('./session/cookie')
17 , Store = require('./session/store')
18 , utils = require('./../utils')
19 , parse = require('url').parse
20 , crypto = require('crypto');
21
22// environment
23
241var env = process.env.NODE_ENV;
25
26/**
27 * Expose the middleware.
28 */
29
301exports = module.exports = session;
31
32/**
33 * Expose constructors.
34 */
35
361exports.Store = Store;
371exports.Cookie = Cookie;
381exports.Session = Session;
391exports.MemoryStore = MemoryStore;
40
41/**
42 * Warning message for `MemoryStore` usage in production.
43 */
44
451var warning = 'Warning: connection.session() MemoryStore is not\n'
46 + 'designed for a production environment, as it will leak\n'
47 + 'memory, and obviously only work within a single process.';
48
49/**
50 * Session:
51 *
52 * Setup session store with the given `options`.
53 *
54 * Session data is _not_ saved in the cookie itself, however
55 * cookies are used, so we must use the [cookieParser()](cookieParser.html)
56 * middleware _before_ `session()`.
57 *
58 * Examples:
59 *
60 * connect()
61 * .use(connect.cookieParser('keyboard cat'))
62 * .use(connect.session({ key: 'sid', cookie: { secure: true }}))
63 *
64 * Options:
65 *
66 * - `key` cookie name defaulting to `connect.sid`
67 * - `store` session store instance
68 * - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }`
69 * - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")
70 *
71 * Cookie option:
72 *
73 * By default `cookie.maxAge` is `null`, meaning no "expires" parameter is set
74 * so the cookie becomes a browser-session cookie. When the user closes the
75 * browser the cookie (and session) will be removed.
76 *
77 * ## req.session
78 *
79 * To store or access session data, simply use the request property `req.session`,
80 * which is (generally) serialized as JSON by the store, so nested objects
81 * are typically fine. For example below is a user-specific view counter:
82 *
83 * connect()
84 * .use(connect.favicon())
85 * .use(connect.cookieParser('keyboard cat'))
86 * .use(connect.session({ cookie: { maxAge: 60000 }}))
87 * .use(function(req, res, next){
88 * var sess = req.session;
89 * if (sess.views) {
90 * res.setHeader('Content-Type', 'text/html');
91 * res.write('<p>views: ' + sess.views + '</p>');
92 * res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>');
93 * res.end();
94 * sess.views++;
95 * } else {
96 * sess.views = 1;
97 * res.end('welcome to the session demo. refresh!');
98 * }
99 * }
100 * )).listen(3000);
101 *
102 * ## Session#regenerate()
103 *
104 * To regenerate the session simply invoke the method, once complete
105 * a new SID and `Session` instance will be initialized at `req.session`.
106 *
107 * req.session.regenerate(function(err){
108 * // will have a new session here
109 * });
110 *
111 * ## Session#destroy()
112 *
113 * Destroys the session, removing `req.session`, will be re-generated next request.
114 *
115 * req.session.destroy(function(err){
116 * // cannot access session here
117 * });
118 *
119 * ## Session#reload()
120 *
121 * Reloads the session data.
122 *
123 * req.session.reload(function(err){
124 * // session updated
125 * });
126 *
127 * ## Session#save()
128 *
129 * Save the session.
130 *
131 * req.session.save(function(err){
132 * // session saved
133 * });
134 *
135 * ## Session#touch()
136 *
137 * Updates the `.maxAge`, and `.lastAccess` properties. Typically this is
138 * not necessary to call, as the session middleware does this for you.
139 *
140 * ## Session#cookie
141 *
142 * Each session has a unique cookie object accompany it. This allows
143 * you to alter the session cookie per visitor. For example we can
144 * set `req.session.cookie.expires` to `false` to enable the cookie
145 * to remain for only the duration of the user-agent.
146 *
147 * ## Session#maxAge
148 *
149 * Alternatively `req.session.cookie.maxAge` will return the time
150 * remaining in milliseconds, which we may also re-assign a new value
151 * to adjust the `.expires` property appropriately. The following
152 * are essentially equivalent
153 *
154 * var hour = 3600000;
155 * req.session.cookie.expires = new Date(Date.now() + hour);
156 * req.session.cookie.maxAge = hour;
157 *
158 * For example when `maxAge` is set to `60000` (one minute), and 30 seconds
159 * has elapsed it will return `30000` until the current request has completed,
160 * at which time `req.session.touch()` is called to update `req.session.lastAccess`,
161 * and reset `req.session.maxAge` to its original value.
162 *
163 * req.session.cookie.maxAge;
164 * // => 30000
165 *
166 * Session Store Implementation:
167 *
168 * Every session store _must_ implement the following methods
169 *
170 * - `.get(sid, callback)`
171 * - `.set(sid, session, callback)`
172 * - `.destroy(sid, callback)`
173 *
174 * Recommended methods include, but are not limited to:
175 *
176 * - `.length(callback)`
177 * - `.clear(callback)`
178 *
179 * For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo.
180 *
181 * @param {Object} options
182 * @return {Function}
183 * @api public
184 */
185
1861function session(options){
18715 var options = options || {}
188 , key = options.key || 'connect.sid'
189 , store = options.store || new MemoryStore
190 , cookie = options.cookie
191 , trustProxy = options.proxy;
192
193 // notify user that this store is not
194 // meant for a production environment
19515 if ('production' == env && store instanceof MemoryStore) {
1960 console.warn(warning);
197 }
198
199 // generates the new session
20015 store.generate = function(req){
20121 req.sessionID = utils.uid(24);
20221 req.session = new Session(req);
20321 req.session.cookie = new Cookie(req, cookie);
204 };
205
20615 return function session(req, res, next) {
207 // self-awareness
20824 if (req.session) return next();
209
210 // ensure secret is available or bail
21124 if (!req.secret) throw new Error('connect.cookieParser("secret") required for security when using sessions');
212
213 // parse url
21424 var url = parse(req.url)
215 , path = url.pathname
216 , sessionIsNew;
217
218 // expose store
21924 req.sessionStore = store;
220
221 // set-cookie
22224 res.on('header', function(){
22325 if (!req.session) return;
22423 var cookie = req.session.cookie
225 , proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
226 , tls = req.connection.encrypted || (trustProxy && 'https' == proto)
227 , secured = cookie.secure && tls;
228
229 // browser-session cookies only set-cookie once
23024 if (null == cookie.expires && !sessionIsNew) return;
231
232 // only send secure cookies via https
23324 if (cookie.secure && !secured) return debug('not secured');
234
23520 debug('set %s to %s', key, req.sessionID);
23620 res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID));
237 });
238
239 // proxy end() to commit the session
24024 var end = res.end;
24124 res.end = function(data, encoding){
24224 res.end = end;
24325 if (!req.session) return res.end(data, encoding);
24423 debug('saving');
24523 req.session.resetMaxAge();
24623 req.session.save(function(){
24723 debug('saved');
24823 res.end(data, encoding);
249 });
250 };
251
252 // generate the session
25324 function generate() {
25419 sessionIsNew = true;
25519 store.generate(req);
256 }
257
258 // get the sessionID from the cookie
25924 req.sessionID = req.signedCookies[key];
260
261 // generate a session if the browser doesn't send a sessionID
26224 if (!req.sessionID) {
26319 debug('no SID sent, generating session');
26419 generate();
26519 next();
26619 return;
267 }
268
269 // generate the session object
2705 var pause = utils.pause(req);
2715 debug('fetching %s', req.sessionID);
2725 store.get(req.sessionID, function(err, sess){
273 // proxy to resume() events
2745 var _next = next;
2755 next = function(err){
2765 _next(err);
2775 pause.resume();
278 }
279
280 // error handling
2815 if (err) {
2820 debug('error');
2830 if ('ENOENT' == err.code) {
2840 generate();
2850 next();
286 } else {
2870 next(err);
288 }
289 // no session
2905 } else if (!sess) {
2910 debug('no session found');
2920 generate();
2930 next();
294 // populate req.session
295 } else {
2965 debug('session found');
2975 store.createSession(req, sess);
2985 next();
299 }
300 });
301 };
3021};

middleware/session/session.js

75%
33
25
8
LineHitsSource
1
2/*!
3 * Connect - session - Session
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var utils = require('../../utils')
14 , Cookie = require('./cookie');
15
16/**
17 * Create a new `Session` with the given request and `data`.
18 *
19 * @param {IncomingRequest} req
20 * @param {Object} data
21 * @api private
22 */
23
241var Session = module.exports = function Session(req, data) {
2526 Object.defineProperty(this, 'req', { value: req });
2626 Object.defineProperty(this, 'id', { value: req.sessionID });
2726 if ('object' == typeof data) {
285 utils.merge(this, data);
29 } else {
3021 this.lastAccess = Date.now();
31 }
32};
33
34/**
35 * Update `.lastAccess` timestamp,
36 * and reset `.cookie.maxAge` to prevent
37 * the cookie from expiring when the
38 * session is still active.
39 *
40 * @return {Session} for chaining
41 * @api public
42 */
43
441Session.prototype.touch = function(){
450 return this
46 .resetLastAccess()
47 .resetMaxAge();
48};
49
50/**
51 * Update `.lastAccess` timestamp.
52 *
53 * @return {Session} for chaining
54 * @api public
55 */
56
571Session.prototype.resetLastAccess = function(){
585 this.lastAccess = Date.now();
595 return this;
60};
61
62/**
63 * Reset `.maxAge` to `.originalMaxAge`.
64 *
65 * @return {Session} for chaining
66 * @api public
67 */
68
691Session.prototype.resetMaxAge = function(){
7023 this.cookie.maxAge = this.cookie.originalMaxAge;
7123 return this;
72};
73
74/**
75 * Save the session data with optional callback `fn(err)`.
76 *
77 * @param {Function} fn
78 * @return {Session} for chaining
79 * @api public
80 */
81
821Session.prototype.save = function(fn){
8323 this.req.sessionStore.set(this.id, this, fn || function(){});
8423 return this;
85};
86
87/**
88 * Re-loads the session data _without_ altering
89 * the maxAge or lastAccess properties. Invokes the
90 * callback `fn(err)`, after which time if no exception
91 * has occurred the `req.session` property will be
92 * a new `Session` object, although representing the
93 * same session.
94 *
95 * @param {Function} fn
96 * @return {Session} for chaining
97 * @api public
98 */
99
1001Session.prototype.reload = function(fn){
1010 var req = this.req
102 , store = this.req.sessionStore;
1030 store.get(this.id, function(err, sess){
1040 if (err) return fn(err);
1050 if (!sess) return fn(new Error('failed to load session'));
1060 store.createSession(req, sess);
1070 fn();
108 });
1090 return this;
110};
111
112/**
113 * Destroy `this` session.
114 *
115 * @param {Function} fn
116 * @return {Session} for chaining
117 * @api public
118 */
119
1201Session.prototype.destroy = function(fn){
1211 delete this.req.session;
1221 this.req.sessionStore.destroy(this.id, fn);
1231 return this;
124};
125
126/**
127 * Regenerate this request's session.
128 *
129 * @param {Function} fn
130 * @return {Session} for chaining
131 * @api public
132 */
133
1341Session.prototype.regenerate = function(fn){
1352 this.req.sessionStore.regenerate(this.req, fn);
1362 return this;
137};

middleware/session/cookie.js

100%
18
18
0
LineHitsSource
1
2/*!
3 * Connect - session - Cookie
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var utils = require('../../utils');
14
15/**
16 * Initialize a new `Cookie` with the given `options`.
17 *
18 * @param {IncomingMessage} req
19 * @param {Object} options
20 * @api private
21 */
22
231var Cookie = module.exports = function Cookie(req, options) {
2437 this.path = '/';
2537 this.maxAge = null;
2637 this.httpOnly = true;
2761 if (options) utils.merge(this, options);
2837 Object.defineProperty(this, 'req', { value: req });
2937 this.originalMaxAge = undefined == this.originalMaxAge
30 ? this.maxAge
31 : this.originalMaxAge;
32};
33
34/*!
35 * Prototype.
36 */
37
381Cookie.prototype = {
39
40 /**
41 * Set expires `date`.
42 *
43 * @param {Date} date
44 * @api public
45 */
46
47 set expires(date) {
4886 this._expires = date;
4986 this.originalMaxAge = this.maxAge;
50 },
51
52 /**
53 * Get expires `date`.
54 *
55 * @return {Date}
56 * @api public
57 */
58
59 get expires() {
60241 return this._expires;
61 },
62
63 /**
64 * Set expires via max-age in `ms`.
65 *
66 * @param {Number} ms
67 * @api public
68 */
69
70 set maxAge(ms) {
7175 this.expires = 'number' == typeof ms
72 ? new Date(Date.now() + ms)
73 : ms;
74 },
75
76 /**
77 * Get expires max-age in `ms`.
78 *
79 * @return {Number}
80 * @api public
81 */
82
83 get maxAge() {
84105 return this.expires instanceof Date
85 ? this.expires.valueOf() - Date.now()
86 : this.expires;
87 },
88
89 /**
90 * Return cookie data object.
91 *
92 * @return {Object}
93 * @api private
94 */
95
96 get data() {
9743 return {
98 originalMaxAge: this.originalMaxAge
99 , expires: this._expires
100 , secure: this.secure
101 , httpOnly: this.httpOnly
102 , domain: this.domain
103 , path: this.path
104 }
105 },
106
107 /**
108 * Return a serialized cookie string.
109 *
110 * @return {String}
111 * @api public
112 */
113
114 serialize: function(name, val){
11520 val = utils.sign(val, this.req.secret);
11620 return utils.serializeCookie(name, val, this.data);
117 },
118
119 /**
120 * Return JSON representation of this cookie.
121 *
122 * @return {Object}
123 * @api private
124 */
125
126 toJSON: function(){
12723 return this.data;
128 }
129};

middleware/session/memory.js

74%
35
26
9
LineHitsSource
1
2/*!
3 * Connect - session - MemoryStore
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var Store = require('./store')
14 , utils = require('../../utils')
15 , Session = require('./session');
16
17/**
18 * Initialize a new `MemoryStore`.
19 *
20 * @api public
21 */
22
231var MemoryStore = module.exports = function MemoryStore() {
2415 this.sessions = {};
25};
26
27/**
28 * Inherit from `Store.prototype`.
29 */
30
311MemoryStore.prototype.__proto__ = Store.prototype;
32
33/**
34 * Attempt to fetch session by the given `sid`.
35 *
36 * @param {String} sid
37 * @param {Function} fn
38 * @api public
39 */
40
411MemoryStore.prototype.get = function(sid, fn){
425 var self = this;
435 process.nextTick(function(){
445 var expires
45 , sess = self.sessions[sid];
465 if (sess) {
475 sess = JSON.parse(sess);
485 expires = 'string' == typeof sess.cookie.expires
49 ? new Date(sess.cookie.expires)
50 : sess.cookie.expires;
515 if (!expires || new Date < expires) {
525 fn(null, sess);
53 } else {
540 self.destroy(sid, fn);
55 }
56 } else {
570 fn();
58 }
59 });
60};
61
62/**
63 * Commit the given `sess` object associated with the given `sid`.
64 *
65 * @param {String} sid
66 * @param {Session} sess
67 * @param {Function} fn
68 * @api public
69 */
70
711MemoryStore.prototype.set = function(sid, sess, fn){
7223 var self = this;
7323 process.nextTick(function(){
7423 self.sessions[sid] = JSON.stringify(sess);
7523 fn && fn();
76 });
77};
78
79/**
80 * Destroy the session associated with the given `sid`.
81 *
82 * @param {String} sid
83 * @api public
84 */
85
861MemoryStore.prototype.destroy = function(sid, fn){
873 var self = this;
883 process.nextTick(function(){
893 delete self.sessions[sid];
903 fn && fn();
91 });
92};
93
94/**
95 * Invoke the given callback `fn` with all active sessions.
96 *
97 * @param {Function} fn
98 * @api public
99 */
100
1011MemoryStore.prototype.all = function(fn){
1020 var arr = []
103 , keys = Object.keys(this.sessions);
1040 for (var i = 0, len = keys.length; i < len; ++i) {
1050 arr.push(this.sessions[keys[i]]);
106 }
1070 fn(null, arr);
108};
109
110/**
111 * Clear all sessions.
112 *
113 * @param {Function} fn
114 * @api public
115 */
116
1171MemoryStore.prototype.clear = function(fn){
1180 this.sessions = {};
1190 fn && fn();
120};
121
122/**
123 * Fetch number of sessions.
124 *
125 * @param {Function} fn
126 * @api public
127 */
128
1291MemoryStore.prototype.length = function(fn){
1300 fn(null, Object.keys(this.sessions).length);
131};

middleware/session/store.js

70%
24
17
7
LineHitsSource
1
2/*!
3 * Connect - session - Store
4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
6 * MIT Licensed
7 */
8
9/**
10 * Module dependencies.
11 */
12
131var EventEmitter = require('events').EventEmitter
14 , Session = require('./session')
15 , Cookie = require('./cookie')
16 , utils = require('../../utils');
17
18/**
19 * Initialize abstract `Store`.
20 *
21 * @api private
22 */
23
241var Store = module.exports = function Store(options){};
25
26/**
27 * Inherit from `EventEmitter.prototype`.
28 */
29
301Store.prototype.__proto__ = EventEmitter.prototype;
31
32/**
33 * Re-generate the given requests's session.
34 *
35 * @param {IncomingRequest} req
36 * @return {Function} fn
37 * @api public
38 */
39
401Store.prototype.regenerate = function(req, fn){
412 var self = this;
422 this.destroy(req.sessionID, function(err){
432 self.generate(req);
442 fn(err);
45 });
46};
47
48/**
49 * Load a `Session` instance via the given `sid`
50 * and invoke the callback `fn(err, sess)`.
51 *
52 * @param {String} sid
53 * @param {Function} fn
54 * @api public
55 */
56
571Store.prototype.load = function(sid, fn){
580 var self = this;
590 this.get(sid, function(err, sess){
600 if (err) return fn(err);
610 if (!sess) return fn();
620 var req = { sessionID: sid, sessionStore: self };
630 sess = self.createSession(req, sess, false);
640 fn(null, sess);
65 });
66};
67
68/**
69 * Create session from JSON `sess` data.
70 *
71 * @param {IncomingRequest} req
72 * @param {Object} sess
73 * @return {Session}
74 * @api private
75 */
76
771Store.prototype.createSession = function(req, sess, update){
785 var expires = sess.cookie.expires
79 , orig = sess.cookie.originalMaxAge
80 , update = null == update ? true : false;
815 sess.cookie = new Cookie(req, sess.cookie);
829 if ('string' == typeof expires) sess.cookie.expires = new Date(expires);
835 sess.cookie.originalMaxAge = orig;
845 req.session = new Session(req, sess);
8510 if (update) req.session.resetLastAccess();
865 return req.session;
87};

middleware/staticCache.js

85%
54
46
8
LineHitsSource
1
2/*!
3 * Connect - staticCache
4 * Copyright(c) 2011 Sencha Inc.
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
121var http = require('http')
13 , utils = require('../utils')
14 , Cache = require('../cache')
15 , url = require('url')
16 , fs = require('fs');
17
18/**
19 * Static cache:
20 *
21 * Enables a memory cache layer on top of
22 * the `static()` middleware, serving popular
23 * static files.
24 *
25 * By default a maximum of 128 objects are
26 * held in cache, with a max of 256k each,
27 * totalling ~32mb.
28 *
29 * A Least-Recently-Used (LRU) cache algo
30 * is implemented through the `Cache` object,
31 * simply rotating cache objects as they are
32 * hit. This means that increasingly popular
33 * objects maintain their positions while
34 * others get shoved out of the stack and
35 * garbage collected.
36 *
37 * Benchmarks:
38 *
39 * static(): 2700 rps
40 * node-static: 5300 rps
41 * static() + staticCache(): 7500 rps
42 *
43 * Options:
44 *
45 * - `maxObjects` max cache objects [128]
46 * - `maxLength` max cache object length 256kb
47 *
48 * @param {Type} name
49 * @return {Type}
50 * @api public
51 */
52
531module.exports = function staticCache(options){
543 var options = options || {}
55 , cache = new Cache(options.maxObjects || 128)
56 , maxlen = options.maxLength || 1024 * 256;
57
583 return function staticCache(req, res, next){
5910 var path = url.parse(req.url).pathname
60 , ranges = req.headers.range
61 , hit = cache.get(path)
62 , hitCC
63 , uaCC
64 , header
65 , age;
66
6710 function miss() {
684 res.setHeader('X-Cache', 'MISS');
694 next();
70 }
71
72 // cache static
73 // TODO: change from staticCache() -> static()
74 // and make this work for any request
7510 req.on('static', function(stream){
762 var headers = res._headers
77 , cc = utils.parseCacheControl(headers['cache-control'] || '')
78 , contentLength = headers['content-length']
79 , hit;
80
81 // ignore larger files
822 if (!contentLength || contentLength > maxlen) return;
83
84 // dont cache items we shouldn't be
85 // TODO: real support for must-revalidate / no-cache
862 if ( cc['no-cache']
87 || cc['no-store']
88 || cc['private']
890 || cc['must-revalidate']) return;
90
91 // if already in cache then validate
922 if (hit = cache.get(path)){
931 if (headers.etag == hit[0].etag) {
941 hit[0].date = new Date;
951 return;
96 } else {
970 cache.remove(path);
98 }
99 }
100
101 // validation notifiactions don't contain a steam
1021 if (null == stream) return;
103
104 // add the cache object
1051 var arr = cache.add(path);
1061 arr.push(headers);
107
108 // store the chunks
1091 stream.on('data', function(chunk){
1101 arr.push(chunk);
111 });
112
113 // flag it as complete
1141 stream.on('end', function(){
1151 arr.complete = true;
116 });
117 });
118
119 // cache hit, doesnt support range requests
12010 if (hit && hit.complete && !ranges) {
1217 header = utils.merge({}, hit[0]);
1227 header.Age = age = (new Date - new Date(header.date)) / 1000 | 0;
1237 header.date = new Date().toUTCString();
124
125 // parse cache-controls
1267 hitCC = utils.parseCacheControl(header['cache-control'] || '');
1277 uaCC = utils.parseCacheControl(req.headers['cache-control'] || '');
128
129 // check if we must revalidate(bypass)
1308 if (hitCC['no-cache'] || uaCC['no-cache']) return miss();
131
132 // check freshness of entity
1336 if (isStale(hitCC, age) || isStale(uaCC, age)) return miss();
134
135 // conditional GET support
1366 if (utils.conditionalGET(req)) {
1370 if (!utils.modified(req, res, header)) {
1380 header['content-length'] = 0;
1390 res.writeHead(304, header);
1400 return res.end();
141 }
142 }
143
144 // HEAD support
1456 if ('HEAD' == req.method) {
1462 res.writeHead(200, header);
1472 return res.end();
148 }
149
150 // respond with cache
1514 header['x-cache'] = 'HIT';
1524 res.writeHead(200, header);
153
154 // backpressure
1554 function write(i) {
1568 var buf = hit[i];
15712 if (!buf) return res.end();
1584 if (false === res.write(buf)) {
1590 res.once('drain', function(){
1600 write(++i);
161 });
162 } else {
1634 write(++i);
164 }
165 }
166
1674 return write(1);
168 }
169
1703 miss();
171 }
172};
173
174/**
175 * Check if cache item is stale
176 *
177 * @param {Object} cc
178 * @param {Number} age
179 * @return {Boolean}
180 * @api private
181 */
182
1831function isStale(cc, age) {
18412 return cc['max-age'] && cc['max-age'] <= age;
185}

cache.js

83%
18
15
3
LineHitsSource
1
2/*!
3 * Connect - Cache
4 * Copyright(c) 2011 Sencha Inc.
5 * MIT Licensed
6 */
7
8/**
9 * Expose `Cache`.
10 */
11
121module.exports = Cache;
13
14/**
15 * LRU cache store.
16 *
17 * @param {Number} limit
18 * @api private
19 */
20
211function Cache(limit) {
223 this.store = {};
233 this.keys = [];
243 this.limit = limit;
25}
26
27/**
28 * Touch `key`, promoting the object.
29 *
30 * @param {String} key
31 * @param {Number} i
32 * @api private
33 */
34
351Cache.prototype.touch = function(key, i){
360 this.keys.splice(i,1);
370 this.keys.push(key);
38};
39
40/**
41 * Remove `key`.
42 *
43 * @param {String} key
44 * @api private
45 */
46
471Cache.prototype.remove = function(key){
480 delete this.store[key];
49};
50
51/**
52 * Get the object stored for `key`.
53 *
54 * @param {String} key
55 * @return {Array}
56 * @api private
57 */
58
591Cache.prototype.get = function(key){
6012 return this.store[key];
61};
62
63/**
64 * Add a cache `key`.
65 *
66 * @param {String} key
67 * @return {Array}
68 * @api private
69 */
70
711Cache.prototype.add = function(key){
72 // initialize store
731 var len = this.keys.push(key);
74
75 // limit reached, invalidate LRU
761 if (len > this.limit) this.remove(this.keys.shift());
77
781 var arr = this.store[key] = [];
791 arr.createdAt = new Date;
801 return arr;
81};

middleware/cookieSession.js

100%
23
23
0
LineHitsSource
1
2/*!
3 * Connect - cookieSession
4 * Copyright(c) 2011 Sencha Inc.
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
121var utils = require('./../utils')
13 , Cookie = require('./session/cookie')
14 , debug = require('debug')('connect:cookieSession');
15
16// environment
17
181var env = process.env.NODE_ENV;
19
20/**
21 * Cookie Session:
22 *
23 * Cookie session middleware.
24 *
25 * var app = connect();
26 * app.use(connect.cookieParser('tobo!'));
27 * app.use(connect.cookieSession({ cookie: { maxAge: 60 * 60 * 1000 }}));
28 *
29 * Options:
30 *
31 * - `key` cookie name defaulting to `connect.sess`
32 * - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }`
33 * - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")
34 *
35 * @param {Object} options
36 * @return {Function}
37 * @api public
38 */
39
401module.exports = function cookieSession(options){
41 // TODO: utilize Session/Cookie to unify API
42 // TODO: only set-cookie on changes to the session data
43
4414 var options = options || {}
45 , key = options.key || 'connect.sess'
46 , cookie = options.cookie
47 , trustProxy = options.proxy;
48
4914 return function cookieSession(req, res, next) {
5011 req.session = req.signedCookies[key] || {};
5111 req.session.cookie = new Cookie(req, cookie);
52
5311 res.on('header', function(){
54 // removed
5511 if (!req.session) {
561 debug('clear session');
571 res.setHeader('Set-Cookie', key + '=; expires=' + new Date(0).toUTCString());
581 return;
59 }
60
6110 var cookie = req.session.cookie;
6210 delete req.session.cookie;
63
64 // check security
6510 var proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
66 , tls = req.connection.encrypted || (trustProxy && 'https' == proto)
67 , secured = cookie.secure && tls;
68
69 // only send secure cookies via https
7012 if (cookie.secure && !secured) return debug('not secured');
71
72 // set cookie
738 debug('serializing %j', req.session);
748 var val = 'j:' + JSON.stringify(req.session);
758 val = utils.sign(val, req.secret);
768 val = utils.serializeCookie(key, val, cookie);
778 debug('cookie %j', cookie);
788 res.setHeader('Set-Cookie', val);
79 });
80
8111 next();
82 };
83};