| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 22 | 1 | require('./patch'); |
| 23 | ||
| 24 | // expose createServer() as the module | |
| 25 | ||
| 26 | 1 | exports = module.exports = createServer; |
| 27 | ||
| 28 | /** | |
| 29 | * Framework version. | |
| 30 | */ | |
| 31 | ||
| 32 | 1 | exports.version = '2.0.0alpha1'; |
| 33 | ||
| 34 | /** | |
| 35 | * Expose the prototype. | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | exports.proto = proto; |
| 39 | ||
| 40 | /** | |
| 41 | * Auto-load middleware getters. | |
| 42 | */ | |
| 43 | ||
| 44 | 1 | exports.middleware = {}; |
| 45 | ||
| 46 | /** | |
| 47 | * Expose utilities. | |
| 48 | */ | |
| 49 | ||
| 50 | 1 | exports.utils = utils; |
| 51 | ||
| 52 | /** | |
| 53 | * Create a new connect server. | |
| 54 | * | |
| 55 | * @return {Function} | |
| 56 | * @api public | |
| 57 | */ | |
| 58 | ||
| 59 | 1 | function createServer() { |
| 60 | 199 | function app(req, res){ app.handle(req, res); } |
| 61 | 71 | utils.merge(app, proto); |
| 62 | 71 | utils.merge(app, EventEmitter.prototype); |
| 63 | 71 | app.route = '/'; |
| 64 | 71 | app.stack = []; |
| 65 | 71 | return app; |
| 66 | 1 | }; |
| 67 | ||
| 68 | /** | |
| 69 | * Auto-load bundled middleware with getters. | |
| 70 | */ | |
| 71 | ||
| 72 | 1 | fs.readdirSync(__dirname + '/middleware').forEach(function(filename){ |
| 73 | 23 | if (!/\.js$/.test(filename)) return; |
| 74 | 21 | var name = basename(filename, '.js'); |
| 75 | 21 | function load(){ |
| 76 | 99 | return require('./middleware/' + name); |
| 77 | } | |
| 78 | 21 | exports.middleware.__defineGetter__(name, load); |
| 79 | 21 | exports.__defineGetter__(name, load); |
| 80 | }); |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var http = require('http') |
| 14 | , parse = require('url').parse | |
| 15 | , utils = require('./utils'); | |
| 16 | ||
| 17 | // prototype | |
| 18 | ||
| 19 | 1 | var app = module.exports = {}; |
| 20 | ||
| 21 | // environment | |
| 22 | ||
| 23 | 1 | var 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 | ||
| 62 | 1 | app.use = function(route, fn){ |
| 63 | // default route to '/' | |
| 64 | 155 | if ('string' != typeof route) { |
| 65 | 150 | fn = route; |
| 66 | 150 | route = '/'; |
| 67 | } | |
| 68 | ||
| 69 | // wrap sub-apps | |
| 70 | 155 | if ('function' == typeof fn.handle) { |
| 71 | 4 | var server = fn; |
| 72 | 4 | fn.route = route; |
| 73 | 4 | fn = function(req, res, next){ |
| 74 | 2 | server.handle(req, res, next); |
| 75 | }; | |
| 76 | } | |
| 77 | ||
| 78 | // wrap vanilla http.Servers | |
| 79 | 155 | if (fn instanceof http.Server) { |
| 80 | 1 | fn = fn.listeners('request')[0]; |
| 81 | } | |
| 82 | ||
| 83 | // strip trailing slash | |
| 84 | 155 | if ('/' == route[route.length - 1]) { |
| 85 | 151 | route = route.slice(0, -1); |
| 86 | } | |
| 87 | ||
| 88 | // add the middleware | |
| 89 | 155 | this.stack.push({ route: route, handle: fn }); |
| 90 | ||
| 91 | 155 | return this; |
| 92 | }; | |
| 93 | ||
| 94 | /** | |
| 95 | * Handle server requests, punting them down | |
| 96 | * the middleware stack. | |
| 97 | * | |
| 98 | * @api private | |
| 99 | */ | |
| 100 | ||
| 101 | 1 | app.handle = function(req, res, out) { |
| 102 | 130 | var stack = this.stack |
| 103 | , fqdn = ~req.url.indexOf('://') | |
| 104 | , removed = '' | |
| 105 | , index = 0; | |
| 106 | ||
| 107 | 130 | function next(err) { |
| 108 | 268 | var layer, path, status, c; |
| 109 | 268 | req.url = removed + req.url; |
| 110 | 268 | req.originalUrl = req.originalUrl || req.url; |
| 111 | 268 | removed = ''; |
| 112 | ||
| 113 | // next callback | |
| 114 | 268 | layer = stack[index++]; |
| 115 | ||
| 116 | // all done | |
| 117 | 268 | if (!layer || res.headerSent) { |
| 118 | // delegate to parent | |
| 119 | 11 | if (out) return out(err); |
| 120 | ||
| 121 | // unhandled error | |
| 122 | 11 | if (err) { |
| 123 | // default to 500 | |
| 124 | 16 | if (res.statusCode < 400) res.statusCode = 500; |
| 125 | ||
| 126 | // respect err.status | |
| 127 | 14 | if (err.status) res.statusCode = err.status; |
| 128 | ||
| 129 | // production gets a basic error message | |
| 130 | 8 | 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 | |
| 135 | 8 | if ('test' != env) console.error(err.stack || err.toString()); |
| 136 | 8 | if (res.headerSent) return req.socket.destroy(); |
| 137 | 8 | res.setHeader('Content-Type', 'text/plain'); |
| 138 | 8 | res.setHeader('Content-Length', Buffer.byteLength(msg)); |
| 139 | 8 | if ('HEAD' == req.method) return res.end(); |
| 140 | 8 | res.end(msg); |
| 141 | } else { | |
| 142 | 3 | res.statusCode = 404; |
| 143 | 3 | res.setHeader('Content-Type', 'text/plain'); |
| 144 | 3 | if ('HEAD' == req.method) return res.end(); |
| 145 | 3 | res.end('Cannot ' + req.method + ' ' + utils.escape(req.url)); |
| 146 | } | |
| 147 | 11 | return; |
| 148 | } | |
| 149 | ||
| 150 | 257 | try { |
| 151 | 257 | path = parse(req.url).pathname; |
| 152 | 257 | if (undefined == path) path = '/'; |
| 153 | ||
| 154 | // skip this layer if the route doesn't match. | |
| 155 | 257 | if (0 != path.indexOf(layer.route)) return next(err); |
| 156 | ||
| 157 | 257 | c = path[layer.route.length]; |
| 158 | 257 | 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 | |
| 162 | 257 | removed = layer.route; |
| 163 | 257 | req.url = req.url.substr(removed.length); |
| 164 | ||
| 165 | // Ensure leading slash | |
| 166 | 260 | if (!fqdn && '/' != req.url[0]) req.url = '/' + req.url; |
| 167 | ||
| 168 | 257 | var arity = layer.handle.length; |
| 169 | 257 | if (err) { |
| 170 | 8 | if (arity === 4) { |
| 171 | 3 | layer.handle(err, req, res, next); |
| 172 | } else { | |
| 173 | 5 | next(err); |
| 174 | } | |
| 175 | 249 | } else if (arity < 4) { |
| 176 | 249 | layer.handle(req, res, next); |
| 177 | } else { | |
| 178 | 0 | next(); |
| 179 | } | |
| 180 | } catch (e) { | |
| 181 | 1 | next(e); |
| 182 | } | |
| 183 | } | |
| 184 | 130 | next(); |
| 185 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 27 | 1 | exports.mime = function(req) { |
| 28 | 49 | var str = req.headers['content-type'] || ''; |
| 29 | 49 | 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 | ||
| 40 | 1 | exports.error = function(code){ |
| 41 | 5 | var err = new Error(http.STATUS_CODES[code]); |
| 42 | 5 | err.status = code; |
| 43 | 5 | return err; |
| 44 | }; | |
| 45 | ||
| 46 | /** | |
| 47 | * Flatten the given `arr`. | |
| 48 | * | |
| 49 | * @param {Array} arr | |
| 50 | * @return {Array} | |
| 51 | * @api private | |
| 52 | */ | |
| 53 | ||
| 54 | 1 | exports.flatten = function(arr, ret){ |
| 55 | 0 | var ret = ret || [] |
| 56 | , len = arr.length; | |
| 57 | 0 | for (var i = 0; i < len; ++i) { |
| 58 | 0 | if (Array.isArray(arr[i])) { |
| 59 | 0 | exports.flatten(arr[i], ret); |
| 60 | } else { | |
| 61 | 0 | ret.push(arr[i]); |
| 62 | } | |
| 63 | } | |
| 64 | 0 | 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 | ||
| 80 | 1 | exports.md5 = function(str, encoding){ |
| 81 | 0 | 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 | ||
| 102 | 1 | exports.merge = function(a, b){ |
| 103 | 178 | if (a && b) { |
| 104 | 178 | for (var key in b) { |
| 105 | 885 | a[key] = b[key]; |
| 106 | } | |
| 107 | } | |
| 108 | 178 | 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 | ||
| 119 | 1 | exports.escape = function(html){ |
| 120 | 3 | return String(html) |
| 121 | .replace(/&(?!\w+;)/g, '&') | |
| 122 | .replace(/</g, '<') | |
| 123 | .replace(/>/g, '>') | |
| 124 | .replace(/"/g, '"'); | |
| 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 | ||
| 139 | 1 | exports.uid = function(len) { |
| 140 | 43 | 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 | ||
| 154 | 1 | exports.sign = function(val, secret){ |
| 155 | 47 | 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 | ||
| 172 | 1 | exports.unsign = function(val, secret){ |
| 173 | 17 | var str = val.slice(0,val.lastIndexOf('.')); |
| 174 | 17 | 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 | ||
| 189 | 1 | exports.parseSignedCookies = function(obj, secret){ |
| 190 | 8 | var ret = {}; |
| 191 | 8 | Object.keys(obj).forEach(function(key){ |
| 192 | 12 | var val = obj[key] |
| 193 | , signed = exports.unsign(val, secret); | |
| 194 | ||
| 195 | 12 | if (signed) { |
| 196 | 7 | ret[key] = signed; |
| 197 | 7 | delete obj[key]; |
| 198 | } | |
| 199 | }); | |
| 200 | 8 | return ret; |
| 201 | }; | |
| 202 | ||
| 203 | /** | |
| 204 | * Parse JSON cookies. | |
| 205 | * | |
| 206 | * @param {Object} obj | |
| 207 | * @return {Object} | |
| 208 | * @api private | |
| 209 | */ | |
| 210 | ||
| 211 | 1 | exports.parseJSONCookies = function(obj){ |
| 212 | 16 | Object.keys(obj).forEach(function(key){ |
| 213 | 12 | var val = obj[key]; |
| 214 | 12 | if (0 == val.indexOf('j:')) { |
| 215 | 2 | try { |
| 216 | 2 | obj[key] = JSON.parse(val.slice(2)); |
| 217 | } catch (err) { | |
| 218 | // nothing | |
| 219 | } | |
| 220 | } | |
| 221 | }); | |
| 222 | 16 | 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 | ||
| 233 | 1 | exports.parseCookie = function(str){ |
| 234 | 14 | var obj = {} |
| 235 | , pairs = str.split(/[;,] */); | |
| 236 | 14 | for (var i = 0, len = pairs.length; i < len; ++i) { |
| 237 | 19 | 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 | |
| 243 | 20 | if ('"' == val[0]) val = val.slice(1, -1); |
| 244 | ||
| 245 | // only assign once | |
| 246 | 19 | if (undefined == obj[key]) { |
| 247 | 19 | val = val.replace(/\+/g, ' '); |
| 248 | 19 | try { |
| 249 | 19 | obj[key] = decodeURIComponent(val); |
| 250 | } catch (err) { | |
| 251 | 0 | if (err instanceof URIError) { |
| 252 | 0 | obj[key] = val; |
| 253 | } else { | |
| 254 | 0 | throw err; |
| 255 | } | |
| 256 | } | |
| 257 | } | |
| 258 | } | |
| 259 | 14 | 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 | ||
| 275 | 1 | exports.serializeCookie = function(name, val, obj){ |
| 276 | 33 | var pairs = [name + '=' + encodeURIComponent(val)] |
| 277 | , obj = obj || {}; | |
| 278 | ||
| 279 | 33 | if (obj.domain) pairs.push('domain=' + obj.domain); |
| 280 | 62 | if (obj.path) pairs.push('path=' + obj.path); |
| 281 | 50 | if (obj.expires) pairs.push('expires=' + obj.expires.toUTCString()); |
| 282 | 57 | if (obj.httpOnly) pairs.push('httpOnly'); |
| 283 | 37 | if (obj.secure) pairs.push('secure'); |
| 284 | ||
| 285 | 33 | 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 | ||
| 306 | 1 | exports.pause = function(obj){ |
| 307 | 7 | var onData |
| 308 | , onEnd | |
| 309 | , events = []; | |
| 310 | ||
| 311 | // buffer data | |
| 312 | 7 | obj.on('data', onData = function(data, encoding){ |
| 313 | 0 | events.push(['data', data, encoding]); |
| 314 | }); | |
| 315 | ||
| 316 | // buffer end | |
| 317 | 7 | obj.on('end', onEnd = function(data, encoding){ |
| 318 | 6 | events.push(['end', data, encoding]); |
| 319 | }); | |
| 320 | ||
| 321 | 7 | return { |
| 322 | end: function(){ | |
| 323 | 6 | obj.removeListener('data', onData); |
| 324 | 6 | obj.removeListener('end', onEnd); |
| 325 | }, | |
| 326 | resume: function(){ | |
| 327 | 6 | this.end(); |
| 328 | 6 | for (var i = 0, len = events.length; i < len; ++i) { |
| 329 | 5 | 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 | ||
| 344 | 1 | exports.modified = function(req, res, headers) { |
| 345 | 1 | 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 | ||
| 351 | 1 | if (noneMatch) noneMatch = noneMatch.split(/ *, */); |
| 352 | ||
| 353 | // check If-None-Match | |
| 354 | 1 | if (noneMatch && etag && ~noneMatch.indexOf(etag)) { |
| 355 | 0 | return false; |
| 356 | } | |
| 357 | ||
| 358 | // check If-Modified-Since | |
| 359 | 1 | if (modifiedSince && lastModified) { |
| 360 | 1 | modifiedSince = new Date(modifiedSince); |
| 361 | 1 | lastModified = new Date(lastModified); |
| 362 | // Ignore invalid dates | |
| 363 | 1 | if (!isNaN(modifiedSince.getTime())) { |
| 364 | 2 | if (lastModified <= modifiedSince) return false; |
| 365 | } | |
| 366 | } | |
| 367 | ||
| 368 | 0 | return true; |
| 369 | }; | |
| 370 | ||
| 371 | /** | |
| 372 | * Strip `Content-*` headers from `res`. | |
| 373 | * | |
| 374 | * @param {ServerResponse} res | |
| 375 | * @api private | |
| 376 | */ | |
| 377 | ||
| 378 | 1 | exports.removeContentHeaders = function(res){ |
| 379 | 1 | Object.keys(res._headers).forEach(function(field){ |
| 380 | 6 | if (0 == field.indexOf('content')) { |
| 381 | 1 | 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 | ||
| 394 | 1 | exports.conditionalGET = function(req) { |
| 395 | 36 | 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 | ||
| 407 | 1 | exports.unauthorized = function(res, realm) { |
| 408 | 6 | res.statusCode = 401; |
| 409 | 6 | res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"'); |
| 410 | 6 | 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 | ||
| 421 | 1 | exports.notModified = function(res) { |
| 422 | 1 | exports.removeContentHeaders(res); |
| 423 | 1 | res.statusCode = 304; |
| 424 | 1 | 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 | ||
| 436 | 1 | exports.etag = function(stat) { |
| 437 | 0 | 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 | ||
| 449 | 1 | exports.parseRange = function(size, str){ |
| 450 | 15 | var valid = true; |
| 451 | 15 | var arr = str.substr(6).split(',').map(function(range){ |
| 452 | 15 | var range = range.split('-') |
| 453 | , start = parseInt(range[0], 10) | |
| 454 | , end = parseInt(range[1], 10); | |
| 455 | ||
| 456 | // -500 | |
| 457 | 15 | if (isNaN(start)) { |
| 458 | 4 | start = size - end; |
| 459 | 4 | end = size - 1; |
| 460 | // 500- | |
| 461 | 11 | } else if (isNaN(end)) { |
| 462 | 3 | end = size - 1; |
| 463 | } | |
| 464 | ||
| 465 | // Invalid | |
| 466 | 16 | if (isNaN(start) || isNaN(end) || start > end) valid = false; |
| 467 | ||
| 468 | 15 | return { start: start, end: end }; |
| 469 | }); | |
| 470 | 15 | 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 | ||
| 481 | 1 | exports.parseCacheControl = function(str){ |
| 482 | 26 | var directives = str.split(',') |
| 483 | , obj = {}; | |
| 484 | ||
| 485 | 26 | for(var i = 0, len = directives.length; i < len; i++) { |
| 486 | 37 | var parts = directives[i].split('=') |
| 487 | , key = parts.shift().trim() | |
| 488 | , val = parseInt(parts.shift(), 10); | |
| 489 | ||
| 490 | 37 | obj[key] = isNaN(val) ? true : val; |
| 491 | } | |
| 492 | ||
| 493 | 26 | 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 | ||
| 506 | 1 | var toArray = exports.toArray = function(obj){ |
| 507 | 0 | var len = obj.length |
| 508 | , arr = new Array(len); | |
| 509 | 0 | for (var i = 0; i < len; ++i) { |
| 510 | 0 | arr[i] = obj[i]; |
| 511 | } | |
| 512 | 0 | return arr; |
| 513 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Connect | |
| 4 | * Copyright(c) 2011 TJ Holowaychuk | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var 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 | ||
| 20 | 1 | if (!res._hasConnectPatch) { |
| 21 | ||
| 22 | /** | |
| 23 | * Provide a public "header sent" flag | |
| 24 | * until node does. | |
| 25 | * | |
| 26 | * @return {Boolean} | |
| 27 | * @api public | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | res.__defineGetter__('headerSent', function(){ |
| 31 | 273 | 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 | ||
| 43 | 1 | res.setHeader = function(field, val){ |
| 44 | 265 | var key = field.toLowerCase() |
| 45 | , prev; | |
| 46 | ||
| 47 | // special-case Set-Cookie | |
| 48 | 265 | if (this._headers && 'set-cookie' == key) { |
| 49 | 3 | if (prev = this.getHeader(field)) { |
| 50 | 0 | val = Array.isArray(prev) |
| 51 | ? prev.concat(val) | |
| 52 | : [prev, val]; | |
| 53 | } | |
| 54 | // charset | |
| 55 | 262 | } else if ('content-type' == key && this.charset) { |
| 56 | 0 | val += '; charset=' + this.charset; |
| 57 | } | |
| 58 | ||
| 59 | 265 | return setHeader.call(this, field, val); |
| 60 | }; | |
| 61 | ||
| 62 | /** | |
| 63 | * Proxy to emit "header" event. | |
| 64 | */ | |
| 65 | ||
| 66 | 1 | res._renderHeaders = function(){ |
| 67 | 78 | if (!this._emittedHeader) this.emit('header'); |
| 68 | 78 | this._emittedHeader = true; |
| 69 | 78 | return _renderHeaders.call(this); |
| 70 | }; | |
| 71 | ||
| 72 | 1 | res.writeHead = function(){ |
| 73 | 256 | if (!this._emittedHeader) this.emit('header'); |
| 74 | 128 | this._emittedHeader = true; |
| 75 | 128 | return writeHead.apply(this, arguments); |
| 76 | }; | |
| 77 | ||
| 78 | 1 | res._hasConnectPatch = true; |
| 79 | } |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 48 | 1 | module.exports = function basicAuth(callback, realm) { |
| 49 | 3 | var username, password; |
| 50 | ||
| 51 | // user / pass strings | |
| 52 | 3 | if ('string' == typeof callback) { |
| 53 | 1 | username = callback; |
| 54 | 1 | password = realm; |
| 55 | 1 | if ('string' != typeof password) throw new Error('password argument required'); |
| 56 | 1 | realm = arguments[2]; |
| 57 | 1 | callback = function(user, pass){ |
| 58 | 2 | return user == username && pass == password; |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | 3 | realm = realm || 'Authorization Required'; |
| 63 | ||
| 64 | 3 | return function(req, res, next) { |
| 65 | 9 | var authorization = req.headers.authorization; |
| 66 | ||
| 67 | 9 | if (req.user) return next(); |
| 68 | 12 | if (!authorization) return unauthorized(res, realm); |
| 69 | ||
| 70 | 6 | 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 | ||
| 76 | 6 | if ('Basic' != scheme) return next(utils.error(400)); |
| 77 | ||
| 78 | // async | |
| 79 | 6 | if (callback.length >= 3) { |
| 80 | 2 | var pause = utils.pause(req); |
| 81 | 2 | callback(user, pass, function(err, user){ |
| 82 | 3 | if (err || !user) return unauthorized(res, realm); |
| 83 | 1 | req.user = user; |
| 84 | 1 | next(); |
| 85 | 1 | pause.resume(); |
| 86 | }); | |
| 87 | // sync | |
| 88 | } else { | |
| 89 | 4 | if (callback(user, pass)) { |
| 90 | 2 | req.user = user; |
| 91 | 2 | next(); |
| 92 | } else { | |
| 93 | 2 | unauthorized(res, realm); |
| 94 | } | |
| 95 | } | |
| 96 | } | |
| 97 | }; | |
| 98 |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 47 | 1 | exports = module.exports = function bodyParser(options){ |
| 48 | 6 | var _urlencoded = urlencoded(options) |
| 49 | , _multipart = multipart(options) | |
| 50 | , _json = json(options); | |
| 51 | ||
| 52 | 6 | return function bodyParser(req, res, next) { |
| 53 | 11 | _json(req, res, function(err){ |
| 54 | 11 | if (err) return next(err); |
| 55 | 11 | _urlencoded(req, res, function(err){ |
| 56 | 11 | if (err) return next(err); |
| 57 | 11 | _multipart(req, res, next); |
| 58 | }); | |
| 59 | }); | |
| 60 | } | |
| 61 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 37 | 1 | exports = module.exports = function(options){ |
| 38 | 13 | options = options || {}; |
| 39 | 13 | return function multipart(req, res, next) { |
| 40 | 24 | if (req._body) return next(); |
| 41 | 20 | req.body = req.body || {}; |
| 42 | 20 | req.files = req.files || {}; |
| 43 | ||
| 44 | // ignore GET | |
| 45 | 21 | if ('GET' == req.method || 'HEAD' == req.method) return next(); |
| 46 | ||
| 47 | // check Content-Type | |
| 48 | 22 | if ('multipart/form-data' != utils.mime(req)) return next(); |
| 49 | ||
| 50 | // flag as parsed | |
| 51 | 16 | req._body = true; |
| 52 | ||
| 53 | // parse | |
| 54 | 16 | var form = new formidable.IncomingForm |
| 55 | , data = {} | |
| 56 | , files = {} | |
| 57 | , done; | |
| 58 | ||
| 59 | 16 | Object.keys(options).forEach(function(key){ |
| 60 | 2 | form[key] = options[key]; |
| 61 | }); | |
| 62 | ||
| 63 | 16 | function ondata(name, val, data){ |
| 64 | 32 | if (Array.isArray(data[name])) { |
| 65 | 0 | data[name].push(val); |
| 66 | 32 | } else if (data[name]) { |
| 67 | 2 | data[name] = [data[name], val]; |
| 68 | } else { | |
| 69 | 30 | data[name] = val; |
| 70 | } | |
| 71 | } | |
| 72 | ||
| 73 | 16 | form.on('field', function(name, val){ |
| 74 | 18 | ondata(name, val, data); |
| 75 | }); | |
| 76 | ||
| 77 | 16 | form.on('file', function(name, val){ |
| 78 | 14 | ondata(name, val, files); |
| 79 | }); | |
| 80 | ||
| 81 | 16 | form.on('error', function(err){ |
| 82 | 2 | next(err); |
| 83 | 2 | done = true; |
| 84 | }); | |
| 85 | ||
| 86 | 16 | form.on('end', function(){ |
| 87 | 18 | if (done) return; |
| 88 | 14 | try { |
| 89 | 14 | req.body = qs.parse(data); |
| 90 | 14 | req.files = qs.parse(files); |
| 91 | 14 | next(); |
| 92 | } catch (err) { | |
| 93 | 0 | next(err); |
| 94 | } | |
| 95 | }); | |
| 96 | ||
| 97 | 16 | form.parse(req); |
| 98 | } | |
| 99 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 27 | 1 | exports = module.exports = function(options){ |
| 28 | 7 | options = options || {}; |
| 29 | 7 | return function urlencoded(req, res, next) { |
| 30 | 14 | if (req._body) return next(); |
| 31 | 12 | req.body = req.body || {}; |
| 32 | ||
| 33 | // ignore GET | |
| 34 | 12 | if ('GET' == req.method || 'HEAD' == req.method) return next(); |
| 35 | ||
| 36 | // check Content-Type | |
| 37 | 22 | if ('application/x-www-form-urlencoded' != utils.mime(req)) return next(); |
| 38 | ||
| 39 | // flag as parsed | |
| 40 | 2 | req._body = true; |
| 41 | ||
| 42 | // parse | |
| 43 | 2 | var buf = ''; |
| 44 | 2 | req.setEncoding('utf8'); |
| 45 | 4 | req.on('data', function(chunk){ buf += chunk }); |
| 46 | 2 | req.on('end', function(){ |
| 47 | 2 | try { |
| 48 | 2 | req.body = buf.length |
| 49 | ? qs.parse(buf) | |
| 50 | : {}; | |
| 51 | 2 | next(); |
| 52 | } catch (err){ | |
| 53 | 0 | next(err); |
| 54 | } | |
| 55 | }); | |
| 56 | } | |
| 57 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 26 | 1 | exports = module.exports = function(options){ |
| 27 | 8 | options = options || {}; |
| 28 | 8 | return function json(req, res, next) { |
| 29 | 16 | if (req._body) return next(); |
| 30 | 16 | req.body = req.body || {}; |
| 31 | ||
| 32 | // ignore GET | |
| 33 | 17 | if ('GET' == req.method || 'HEAD' == req.method) return next(); |
| 34 | ||
| 35 | // check Content-Type | |
| 36 | 26 | if ('application/json' != utils.mime(req)) return next(); |
| 37 | ||
| 38 | // flag as parsed | |
| 39 | 4 | req._body = true; |
| 40 | ||
| 41 | // parse | |
| 42 | 4 | var buf = ''; |
| 43 | 4 | req.setEncoding('utf8'); |
| 44 | 8 | req.on('data', function(chunk){ buf += chunk }); |
| 45 | 4 | req.on('end', function(){ |
| 46 | 4 | try { |
| 47 | 4 | req.body = buf.length |
| 48 | ? JSON.parse(buf) | |
| 49 | : {}; | |
| 50 | 2 | next(); |
| 51 | } catch (err){ | |
| 52 | 2 | err.status = 400; |
| 53 | 2 | next(err); |
| 54 | } | |
| 55 | }); | |
| 56 | } | |
| 57 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var zlib = require('zlib'); |
| 14 | ||
| 15 | /** | |
| 16 | * Supported content-encoding methods. | |
| 17 | */ | |
| 18 | ||
| 19 | 1 | exports.methods = { |
| 20 | gzip: zlib.createGzip | |
| 21 | , deflate: zlib.createDeflate | |
| 22 | }; | |
| 23 | ||
| 24 | /** | |
| 25 | * Default filter function. | |
| 26 | */ | |
| 27 | ||
| 28 | 1 | exports.filter = function(req, res){ |
| 29 | 9 | var type = res.getHeader('Content-Type') || ''; |
| 30 | 9 | 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 | ||
| 64 | 1 | module.exports = function compress(options) { |
| 65 | 1 | var options = options || {} |
| 66 | , names = Object.keys(exports.methods) | |
| 67 | , filter = options.filter || exports.filter; | |
| 68 | ||
| 69 | 1 | return function(req, res, next){ |
| 70 | 9 | var accept = req.headers['accept-encoding'] |
| 71 | , write = res.write | |
| 72 | , end = res.end | |
| 73 | , stream | |
| 74 | , method; | |
| 75 | ||
| 76 | // vary | |
| 77 | 9 | res.setHeader('Vary', 'Accept-Encoding'); |
| 78 | ||
| 79 | // proxy | |
| 80 | ||
| 81 | 9 | res.write = function(chunk, encoding){ |
| 82 | 14 | if (!this.headerSent) this._implicitHeader(); |
| 83 | 7 | return stream |
| 84 | ? stream.write(chunk, encoding) | |
| 85 | : write.call(res, chunk, encoding); | |
| 86 | }; | |
| 87 | ||
| 88 | 9 | res.end = function(chunk, encoding){ |
| 89 | 9 | if (chunk) this.write(chunk, encoding); |
| 90 | 9 | return stream |
| 91 | ? stream.end() | |
| 92 | : end.call(res); | |
| 93 | }; | |
| 94 | ||
| 95 | 9 | res.on('header', function(){ |
| 96 | // default request filter | |
| 97 | 10 | if (!filter(req, res)) return; |
| 98 | ||
| 99 | // SHOULD use identity | |
| 100 | 9 | if (!accept) return; |
| 101 | ||
| 102 | // head | |
| 103 | 8 | if ('HEAD' == req.method) return; |
| 104 | ||
| 105 | // default to gzip | |
| 106 | 6 | if ('*' == accept.trim()) method = 'gzip'; |
| 107 | ||
| 108 | // compression method | |
| 109 | 6 | if (!method) { |
| 110 | 6 | for (var i = 0, len = names.length; i < len; ++i) { |
| 111 | 6 | if (~accept.indexOf(names[i])) { |
| 112 | 6 | method = names[i]; |
| 113 | 6 | break; |
| 114 | } | |
| 115 | } | |
| 116 | } | |
| 117 | ||
| 118 | // compression method | |
| 119 | 6 | if (!method) return; |
| 120 | ||
| 121 | // compression stream | |
| 122 | 6 | stream = exports.methods[method](options); |
| 123 | ||
| 124 | // header fields | |
| 125 | 6 | res.setHeader('Content-Encoding', method); |
| 126 | 6 | res.removeHeader('Content-Length'); |
| 127 | ||
| 128 | // compression | |
| 129 | ||
| 130 | 6 | stream.on('data', function(chunk){ |
| 131 | 12 | write.call(res, chunk); |
| 132 | }); | |
| 133 | ||
| 134 | 6 | stream.on('end', function(){ |
| 135 | 6 | end.call(res); |
| 136 | }); | |
| 137 | ||
| 138 | }); | |
| 139 | ||
| 140 | 9 | next(); |
| 141 | }; | |
| 142 | } |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 12 | 1 | var 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 | ||
| 49 | 1 | exports = module.exports = function static(root, options){ |
| 50 | 6 | options = options || {}; |
| 51 | ||
| 52 | // root required | |
| 53 | 6 | if (!root) throw new Error('static() root path required'); |
| 54 | 6 | options.root = root; |
| 55 | ||
| 56 | 6 | return function static(req, res, next) { |
| 57 | 36 | options.path = req.url; |
| 58 | 36 | options.getOnly = true; |
| 59 | 36 | 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 | ||
| 70 | 1 | exports.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 | ||
| 82 | 1 | function decode(path){ |
| 83 | 36 | try { |
| 84 | 36 | return decodeURIComponent(path); |
| 85 | } catch (err) { | |
| 86 | 1 | 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 | ||
| 100 | 1 | var send = exports.send = function(req, res, next, options){ |
| 101 | 36 | options = options || {}; |
| 102 | 36 | if (!options.path) throw new Error('path required'); |
| 103 | ||
| 104 | // setup | |
| 105 | 36 | 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 | |
| 117 | 36 | if (fn) next = fn; |
| 118 | ||
| 119 | // ignore non-GET requests | |
| 120 | 36 | if (getOnly && !get && !head) return next(); |
| 121 | ||
| 122 | // parse url | |
| 123 | 36 | var url = parse(options.path) |
| 124 | , path = decode(url.pathname) | |
| 125 | , type; | |
| 126 | ||
| 127 | 37 | if ('URIError: URI malformed' == path) return next(utils.error(400)); |
| 128 | ||
| 129 | // null byte(s) | |
| 130 | 35 | if (~path.indexOf('\0')) return next(utils.error(400)); |
| 131 | ||
| 132 | // when root is not given, consider .. malicious | |
| 133 | 35 | if (!root && ~path.indexOf('..')) return next(utils.error(403)); |
| 134 | ||
| 135 | // index.html support | |
| 136 | 36 | if (normalize('/') == path[path.length - 1]) path += 'index.html'; |
| 137 | ||
| 138 | // join / normalize from optional root dir | |
| 139 | 35 | path = normalize(join(root, path)); |
| 140 | ||
| 141 | // malicious path | |
| 142 | 37 | if (root && 0 != path.indexOf(root)) return next(utils.error(403)); |
| 143 | ||
| 144 | // "hidden" file | |
| 145 | 34 | if (!hidden && '.' == basename(path)[0]) return next(); |
| 146 | ||
| 147 | 32 | fs.stat(path, function(err, stat){ |
| 148 | // mime type | |
| 149 | 32 | type = mime.lookup(path); |
| 150 | ||
| 151 | // ignore ENOENT | |
| 152 | 32 | if (err) { |
| 153 | 1 | if (fn) return fn(err); |
| 154 | 1 | return ('ENOENT' == err.code || 'ENAMETOOLONG' == err.code) |
| 155 | ? next() | |
| 156 | : next(err); | |
| 157 | // redirect directory in case index.html is present | |
| 158 | 31 | } else if (stat.isDirectory()) { |
| 159 | 1 | if (!redirect) return next(); |
| 160 | 1 | res.statusCode = 301; |
| 161 | 1 | res.setHeader('Location', url.pathname + '/'); |
| 162 | 1 | res.end('Redirecting to ' + url.pathname + '/'); |
| 163 | 1 | return; |
| 164 | } | |
| 165 | ||
| 166 | // header fields | |
| 167 | 60 | if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString()); |
| 168 | 58 | if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (maxAge / 1000)); |
| 169 | 60 | if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString()); |
| 170 | 30 | if (!res.getHeader('Content-Type')) { |
| 171 | 30 | var charset = mime.charsets.lookup(type); |
| 172 | 30 | res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); |
| 173 | } | |
| 174 | 30 | res.setHeader('Accept-Ranges', 'bytes'); |
| 175 | ||
| 176 | // conditional GET support | |
| 177 | 30 | if (utils.conditionalGET(req)) { |
| 178 | 1 | if (!utils.modified(req, res)) { |
| 179 | 1 | req.emit('static'); |
| 180 | 1 | return utils.notModified(res); |
| 181 | } | |
| 182 | } | |
| 183 | ||
| 184 | 29 | var opts = {} |
| 185 | , len = stat.size; | |
| 186 | ||
| 187 | // we have a Range request | |
| 188 | 29 | if (ranges) { |
| 189 | 7 | ranges = utils.parseRange(stat.size, ranges); |
| 190 | // valid | |
| 191 | 7 | if (ranges) { |
| 192 | // TODO: stream options | |
| 193 | // TODO: multiple support | |
| 194 | 6 | opts.start = ranges[0].start; |
| 195 | 6 | opts.end = ranges[0].end; |
| 196 | 6 | len = Math.min(len, opts.end - opts.start + 1); |
| 197 | 6 | res.statusCode = 206; |
| 198 | 6 | res.setHeader('Content-Range', 'bytes ' |
| 199 | + opts.start | |
| 200 | + '-' | |
| 201 | + opts.end | |
| 202 | + '/' | |
| 203 | + stat.size); | |
| 204 | // invalid range | |
| 205 | } else { | |
| 206 | 1 | return next(utils.error(416)); |
| 207 | } | |
| 208 | } | |
| 209 | ||
| 210 | 28 | res.setHeader('Content-Length', len); |
| 211 | ||
| 212 | // transfer | |
| 213 | 32 | if (head) return res.end(); |
| 214 | ||
| 215 | // stream | |
| 216 | 24 | var stream = fs.createReadStream(path, opts); |
| 217 | 24 | req.emit('static', stream); |
| 218 | 24 | req.on('close', stream.destroy.bind(stream)); |
| 219 | 24 | stream.pipe(res); |
| 220 | ||
| 221 | // callback | |
| 222 | 24 | if (fn) { |
| 223 | 0 | function callback(err) { done || fn(err); done = true } |
| 224 | 0 | req.on('close', callback); |
| 225 | 0 | req.socket.on('error', callback); |
| 226 | 0 | stream.on('error', callback); |
| 227 | 0 | stream.on('end', callback); |
| 228 | } else { | |
| 229 | 24 | stream.on('error', function(err){ |
| 230 | 1 | if (res.headerSent) { |
| 231 | 0 | console.error(err.stack); |
| 232 | 0 | req.destroy(); |
| 233 | } else { | |
| 234 | 1 | next(err); |
| 235 | } | |
| 236 | }); | |
| 237 | } | |
| 238 | }); | |
| 239 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Connect - limit | |
| 4 | * Copyright(c) 2011 TJ Holowaychuk | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var 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 | ||
| 31 | 1 | module.exports = function limit(bytes){ |
| 32 | 2 | if ('string' == typeof bytes) bytes = parse(bytes); |
| 33 | 1 | if ('number' != typeof bytes) throw new Error('limit() bytes required'); |
| 34 | 1 | return function limit(req, res, next){ |
| 35 | 2 | var received = 0 |
| 36 | , len = req.headers['content-length'] | |
| 37 | ? parseInt(req.headers['content-length'], 10) | |
| 38 | : null; | |
| 39 | ||
| 40 | // self-awareness | |
| 41 | 2 | if (req._limit) return next(); |
| 42 | 2 | req._limit = true; |
| 43 | ||
| 44 | // limit by content-length | |
| 45 | 3 | if (len && len > bytes) return next(utils.error(413)); |
| 46 | ||
| 47 | // limit | |
| 48 | 1 | req.on('data', function(chunk){ |
| 49 | 0 | received += chunk.length; |
| 50 | 0 | if (received > bytes) req.destroy(); |
| 51 | }); | |
| 52 | ||
| 53 | 1 | next(); |
| 54 | }; | |
| 55 | }; | |
| 56 | ||
| 57 | /** | |
| 58 | * Parse byte `size` string. | |
| 59 | * | |
| 60 | * @param {String} size | |
| 61 | * @return {Number} | |
| 62 | * @api private | |
| 63 | */ | |
| 64 | ||
| 65 | 1 | function parse(size) { |
| 66 | 1 | var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/) |
| 67 | , n = parseFloat(parts[1]) | |
| 68 | , type = parts[2]; | |
| 69 | ||
| 70 | 1 | var map = { |
| 71 | kb: 1024 | |
| 72 | , mb: 1024 * 1024 | |
| 73 | , gb: 1024 * 1024 * 1024 | |
| 74 | }; | |
| 75 | ||
| 76 | 1 | return map[type] * n; |
| 77 | } |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 34 | 1 | module.exports = function query(){ |
| 35 | 1 | return function query(req, res, next){ |
| 36 | 2 | req.query = ~req.url.indexOf('?') |
| 37 | ? qs.parse(parse(req.url).query) | |
| 38 | : {}; | |
| 39 | 2 | next(); |
| 40 | }; | |
| 41 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 18 | 1 | module.exports = function responseTime(){ |
| 19 | 1 | return function(req, res, next){ |
| 20 | 1 | var start = new Date; |
| 21 | ||
| 22 | 1 | if (res._responseTime) return next(); |
| 23 | 1 | res._responseTime = true; |
| 24 | ||
| 25 | 1 | res.on('header', function(header){ |
| 26 | 1 | var duration = new Date - start; |
| 27 | 1 | res.setHeader('X-Response-time', duration + 'ms'); |
| 28 | }); | |
| 29 | ||
| 30 | 1 | next(); |
| 31 | }; | |
| 32 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 37 | 1 | module.exports = function cookieParser(secret){ |
| 38 | 29 | return function cookieParser(req, res, next) { |
| 39 | 35 | var cookie = req.headers.cookie; |
| 40 | 35 | if (req.cookies) return next(); |
| 41 | ||
| 42 | 35 | req.secret = secret; |
| 43 | 35 | req.cookies = {}; |
| 44 | 35 | req.signedCookies = {}; |
| 45 | ||
| 46 | 35 | if (cookie) { |
| 47 | 8 | try { |
| 48 | 8 | req.cookies = utils.parseCookie(cookie); |
| 49 | 8 | if (secret) { |
| 50 | 8 | req.signedCookies = utils.parseSignedCookies(req.cookies, secret); |
| 51 | 8 | req.signedCookies = utils.parseJSONCookies(req.signedCookies); |
| 52 | } | |
| 53 | 8 | req.cookies = utils.parseJSONCookies(req.cookies); |
| 54 | } catch (err) { | |
| 55 | 0 | return next(err); |
| 56 | } | |
| 57 | } | |
| 58 | 35 | next(); |
| 59 | }; | |
| 60 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 24 | 1 | var env = process.env.NODE_ENV; |
| 25 | ||
| 26 | /** | |
| 27 | * Expose the middleware. | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | exports = module.exports = session; |
| 31 | ||
| 32 | /** | |
| 33 | * Expose constructors. | |
| 34 | */ | |
| 35 | ||
| 36 | 1 | exports.Store = Store; |
| 37 | 1 | exports.Cookie = Cookie; |
| 38 | 1 | exports.Session = Session; |
| 39 | 1 | exports.MemoryStore = MemoryStore; |
| 40 | ||
| 41 | /** | |
| 42 | * Warning message for `MemoryStore` usage in production. | |
| 43 | */ | |
| 44 | ||
| 45 | 1 | var 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 | ||
| 186 | 1 | function session(options){ |
| 187 | 15 | 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 | |
| 195 | 15 | if ('production' == env && store instanceof MemoryStore) { |
| 196 | 0 | console.warn(warning); |
| 197 | } | |
| 198 | ||
| 199 | // generates the new session | |
| 200 | 15 | store.generate = function(req){ |
| 201 | 21 | req.sessionID = utils.uid(24); |
| 202 | 21 | req.session = new Session(req); |
| 203 | 21 | req.session.cookie = new Cookie(req, cookie); |
| 204 | }; | |
| 205 | ||
| 206 | 15 | return function session(req, res, next) { |
| 207 | // self-awareness | |
| 208 | 24 | if (req.session) return next(); |
| 209 | ||
| 210 | // ensure secret is available or bail | |
| 211 | 24 | if (!req.secret) throw new Error('connect.cookieParser("secret") required for security when using sessions'); |
| 212 | ||
| 213 | // parse url | |
| 214 | 24 | var url = parse(req.url) |
| 215 | , path = url.pathname | |
| 216 | , sessionIsNew; | |
| 217 | ||
| 218 | // expose store | |
| 219 | 24 | req.sessionStore = store; |
| 220 | ||
| 221 | // set-cookie | |
| 222 | 24 | res.on('header', function(){ |
| 223 | 25 | if (!req.session) return; |
| 224 | 23 | 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 | |
| 230 | 24 | if (null == cookie.expires && !sessionIsNew) return; |
| 231 | ||
| 232 | // only send secure cookies via https | |
| 233 | 24 | if (cookie.secure && !secured) return debug('not secured'); |
| 234 | ||
| 235 | 20 | debug('set %s to %s', key, req.sessionID); |
| 236 | 20 | res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID)); |
| 237 | }); | |
| 238 | ||
| 239 | // proxy end() to commit the session | |
| 240 | 24 | var end = res.end; |
| 241 | 24 | res.end = function(data, encoding){ |
| 242 | 24 | res.end = end; |
| 243 | 25 | if (!req.session) return res.end(data, encoding); |
| 244 | 23 | debug('saving'); |
| 245 | 23 | req.session.resetMaxAge(); |
| 246 | 23 | req.session.save(function(){ |
| 247 | 23 | debug('saved'); |
| 248 | 23 | res.end(data, encoding); |
| 249 | }); | |
| 250 | }; | |
| 251 | ||
| 252 | // generate the session | |
| 253 | 24 | function generate() { |
| 254 | 19 | sessionIsNew = true; |
| 255 | 19 | store.generate(req); |
| 256 | } | |
| 257 | ||
| 258 | // get the sessionID from the cookie | |
| 259 | 24 | req.sessionID = req.signedCookies[key]; |
| 260 | ||
| 261 | // generate a session if the browser doesn't send a sessionID | |
| 262 | 24 | if (!req.sessionID) { |
| 263 | 19 | debug('no SID sent, generating session'); |
| 264 | 19 | generate(); |
| 265 | 19 | next(); |
| 266 | 19 | return; |
| 267 | } | |
| 268 | ||
| 269 | // generate the session object | |
| 270 | 5 | var pause = utils.pause(req); |
| 271 | 5 | debug('fetching %s', req.sessionID); |
| 272 | 5 | store.get(req.sessionID, function(err, sess){ |
| 273 | // proxy to resume() events | |
| 274 | 5 | var _next = next; |
| 275 | 5 | next = function(err){ |
| 276 | 5 | _next(err); |
| 277 | 5 | pause.resume(); |
| 278 | } | |
| 279 | ||
| 280 | // error handling | |
| 281 | 5 | if (err) { |
| 282 | 0 | debug('error'); |
| 283 | 0 | if ('ENOENT' == err.code) { |
| 284 | 0 | generate(); |
| 285 | 0 | next(); |
| 286 | } else { | |
| 287 | 0 | next(err); |
| 288 | } | |
| 289 | // no session | |
| 290 | 5 | } else if (!sess) { |
| 291 | 0 | debug('no session found'); |
| 292 | 0 | generate(); |
| 293 | 0 | next(); |
| 294 | // populate req.session | |
| 295 | } else { | |
| 296 | 5 | debug('session found'); |
| 297 | 5 | store.createSession(req, sess); |
| 298 | 5 | next(); |
| 299 | } | |
| 300 | }); | |
| 301 | }; | |
| 302 | 1 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 24 | 1 | var Session = module.exports = function Session(req, data) { |
| 25 | 26 | Object.defineProperty(this, 'req', { value: req }); |
| 26 | 26 | Object.defineProperty(this, 'id', { value: req.sessionID }); |
| 27 | 26 | if ('object' == typeof data) { |
| 28 | 5 | utils.merge(this, data); |
| 29 | } else { | |
| 30 | 21 | 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 | ||
| 44 | 1 | Session.prototype.touch = function(){ |
| 45 | 0 | return this |
| 46 | .resetLastAccess() | |
| 47 | .resetMaxAge(); | |
| 48 | }; | |
| 49 | ||
| 50 | /** | |
| 51 | * Update `.lastAccess` timestamp. | |
| 52 | * | |
| 53 | * @return {Session} for chaining | |
| 54 | * @api public | |
| 55 | */ | |
| 56 | ||
| 57 | 1 | Session.prototype.resetLastAccess = function(){ |
| 58 | 5 | this.lastAccess = Date.now(); |
| 59 | 5 | return this; |
| 60 | }; | |
| 61 | ||
| 62 | /** | |
| 63 | * Reset `.maxAge` to `.originalMaxAge`. | |
| 64 | * | |
| 65 | * @return {Session} for chaining | |
| 66 | * @api public | |
| 67 | */ | |
| 68 | ||
| 69 | 1 | Session.prototype.resetMaxAge = function(){ |
| 70 | 23 | this.cookie.maxAge = this.cookie.originalMaxAge; |
| 71 | 23 | 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 | ||
| 82 | 1 | Session.prototype.save = function(fn){ |
| 83 | 23 | this.req.sessionStore.set(this.id, this, fn || function(){}); |
| 84 | 23 | 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 | ||
| 100 | 1 | Session.prototype.reload = function(fn){ |
| 101 | 0 | var req = this.req |
| 102 | , store = this.req.sessionStore; | |
| 103 | 0 | store.get(this.id, function(err, sess){ |
| 104 | 0 | if (err) return fn(err); |
| 105 | 0 | if (!sess) return fn(new Error('failed to load session')); |
| 106 | 0 | store.createSession(req, sess); |
| 107 | 0 | fn(); |
| 108 | }); | |
| 109 | 0 | return this; |
| 110 | }; | |
| 111 | ||
| 112 | /** | |
| 113 | * Destroy `this` session. | |
| 114 | * | |
| 115 | * @param {Function} fn | |
| 116 | * @return {Session} for chaining | |
| 117 | * @api public | |
| 118 | */ | |
| 119 | ||
| 120 | 1 | Session.prototype.destroy = function(fn){ |
| 121 | 1 | delete this.req.session; |
| 122 | 1 | this.req.sessionStore.destroy(this.id, fn); |
| 123 | 1 | 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 | ||
| 134 | 1 | Session.prototype.regenerate = function(fn){ |
| 135 | 2 | this.req.sessionStore.regenerate(this.req, fn); |
| 136 | 2 | return this; |
| 137 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 23 | 1 | var Cookie = module.exports = function Cookie(req, options) { |
| 24 | 37 | this.path = '/'; |
| 25 | 37 | this.maxAge = null; |
| 26 | 37 | this.httpOnly = true; |
| 27 | 61 | if (options) utils.merge(this, options); |
| 28 | 37 | Object.defineProperty(this, 'req', { value: req }); |
| 29 | 37 | this.originalMaxAge = undefined == this.originalMaxAge |
| 30 | ? this.maxAge | |
| 31 | : this.originalMaxAge; | |
| 32 | }; | |
| 33 | ||
| 34 | /*! | |
| 35 | * Prototype. | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | Cookie.prototype = { |
| 39 | ||
| 40 | /** | |
| 41 | * Set expires `date`. | |
| 42 | * | |
| 43 | * @param {Date} date | |
| 44 | * @api public | |
| 45 | */ | |
| 46 | ||
| 47 | set expires(date) { | |
| 48 | 86 | this._expires = date; |
| 49 | 86 | this.originalMaxAge = this.maxAge; |
| 50 | }, | |
| 51 | ||
| 52 | /** | |
| 53 | * Get expires `date`. | |
| 54 | * | |
| 55 | * @return {Date} | |
| 56 | * @api public | |
| 57 | */ | |
| 58 | ||
| 59 | get expires() { | |
| 60 | 241 | 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) { | |
| 71 | 75 | 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() { | |
| 84 | 105 | 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() { | |
| 97 | 43 | 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){ | |
| 115 | 20 | val = utils.sign(val, this.req.secret); |
| 116 | 20 | 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(){ | |
| 127 | 23 | return this.data; |
| 128 | } | |
| 129 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var Store = require('./store') |
| 14 | , utils = require('../../utils') | |
| 15 | , Session = require('./session'); | |
| 16 | ||
| 17 | /** | |
| 18 | * Initialize a new `MemoryStore`. | |
| 19 | * | |
| 20 | * @api public | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | var MemoryStore = module.exports = function MemoryStore() { |
| 24 | 15 | this.sessions = {}; |
| 25 | }; | |
| 26 | ||
| 27 | /** | |
| 28 | * Inherit from `Store.prototype`. | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | MemoryStore.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 | ||
| 41 | 1 | MemoryStore.prototype.get = function(sid, fn){ |
| 42 | 5 | var self = this; |
| 43 | 5 | process.nextTick(function(){ |
| 44 | 5 | var expires |
| 45 | , sess = self.sessions[sid]; | |
| 46 | 5 | if (sess) { |
| 47 | 5 | sess = JSON.parse(sess); |
| 48 | 5 | expires = 'string' == typeof sess.cookie.expires |
| 49 | ? new Date(sess.cookie.expires) | |
| 50 | : sess.cookie.expires; | |
| 51 | 5 | if (!expires || new Date < expires) { |
| 52 | 5 | fn(null, sess); |
| 53 | } else { | |
| 54 | 0 | self.destroy(sid, fn); |
| 55 | } | |
| 56 | } else { | |
| 57 | 0 | 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 | ||
| 71 | 1 | MemoryStore.prototype.set = function(sid, sess, fn){ |
| 72 | 23 | var self = this; |
| 73 | 23 | process.nextTick(function(){ |
| 74 | 23 | self.sessions[sid] = JSON.stringify(sess); |
| 75 | 23 | 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 | ||
| 86 | 1 | MemoryStore.prototype.destroy = function(sid, fn){ |
| 87 | 3 | var self = this; |
| 88 | 3 | process.nextTick(function(){ |
| 89 | 3 | delete self.sessions[sid]; |
| 90 | 3 | 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 | ||
| 101 | 1 | MemoryStore.prototype.all = function(fn){ |
| 102 | 0 | var arr = [] |
| 103 | , keys = Object.keys(this.sessions); | |
| 104 | 0 | for (var i = 0, len = keys.length; i < len; ++i) { |
| 105 | 0 | arr.push(this.sessions[keys[i]]); |
| 106 | } | |
| 107 | 0 | fn(null, arr); |
| 108 | }; | |
| 109 | ||
| 110 | /** | |
| 111 | * Clear all sessions. | |
| 112 | * | |
| 113 | * @param {Function} fn | |
| 114 | * @api public | |
| 115 | */ | |
| 116 | ||
| 117 | 1 | MemoryStore.prototype.clear = function(fn){ |
| 118 | 0 | this.sessions = {}; |
| 119 | 0 | fn && fn(); |
| 120 | }; | |
| 121 | ||
| 122 | /** | |
| 123 | * Fetch number of sessions. | |
| 124 | * | |
| 125 | * @param {Function} fn | |
| 126 | * @api public | |
| 127 | */ | |
| 128 | ||
| 129 | 1 | MemoryStore.prototype.length = function(fn){ |
| 130 | 0 | fn(null, Object.keys(this.sessions).length); |
| 131 | }; |
| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 13 | 1 | var 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 | ||
| 24 | 1 | var Store = module.exports = function Store(options){}; |
| 25 | ||
| 26 | /** | |
| 27 | * Inherit from `EventEmitter.prototype`. | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | Store.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 | ||
| 40 | 1 | Store.prototype.regenerate = function(req, fn){ |
| 41 | 2 | var self = this; |
| 42 | 2 | this.destroy(req.sessionID, function(err){ |
| 43 | 2 | self.generate(req); |
| 44 | 2 | 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 | ||
| 57 | 1 | Store.prototype.load = function(sid, fn){ |
| 58 | 0 | var self = this; |
| 59 | 0 | this.get(sid, function(err, sess){ |
| 60 | 0 | if (err) return fn(err); |
| 61 | 0 | if (!sess) return fn(); |
| 62 | 0 | var req = { sessionID: sid, sessionStore: self }; |
| 63 | 0 | sess = self.createSession(req, sess, false); |
| 64 | 0 | 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 | ||
| 77 | 1 | Store.prototype.createSession = function(req, sess, update){ |
| 78 | 5 | var expires = sess.cookie.expires |
| 79 | , orig = sess.cookie.originalMaxAge | |
| 80 | , update = null == update ? true : false; | |
| 81 | 5 | sess.cookie = new Cookie(req, sess.cookie); |
| 82 | 9 | if ('string' == typeof expires) sess.cookie.expires = new Date(expires); |
| 83 | 5 | sess.cookie.originalMaxAge = orig; |
| 84 | 5 | req.session = new Session(req, sess); |
| 85 | 10 | if (update) req.session.resetLastAccess(); |
| 86 | 5 | return req.session; |
| 87 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Connect - staticCache | |
| 4 | * Copyright(c) 2011 Sencha Inc. | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var 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 | ||
| 53 | 1 | module.exports = function staticCache(options){ |
| 54 | 3 | var options = options || {} |
| 55 | , cache = new Cache(options.maxObjects || 128) | |
| 56 | , maxlen = options.maxLength || 1024 * 256; | |
| 57 | ||
| 58 | 3 | return function staticCache(req, res, next){ |
| 59 | 10 | 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 | ||
| 67 | 10 | function miss() { |
| 68 | 4 | res.setHeader('X-Cache', 'MISS'); |
| 69 | 4 | next(); |
| 70 | } | |
| 71 | ||
| 72 | // cache static | |
| 73 | // TODO: change from staticCache() -> static() | |
| 74 | // and make this work for any request | |
| 75 | 10 | req.on('static', function(stream){ |
| 76 | 2 | var headers = res._headers |
| 77 | , cc = utils.parseCacheControl(headers['cache-control'] || '') | |
| 78 | , contentLength = headers['content-length'] | |
| 79 | , hit; | |
| 80 | ||
| 81 | // ignore larger files | |
| 82 | 2 | if (!contentLength || contentLength > maxlen) return; |
| 83 | ||
| 84 | // dont cache items we shouldn't be | |
| 85 | // TODO: real support for must-revalidate / no-cache | |
| 86 | 2 | if ( cc['no-cache'] |
| 87 | || cc['no-store'] | |
| 88 | || cc['private'] | |
| 89 | 0 | || cc['must-revalidate']) return; |
| 90 | ||
| 91 | // if already in cache then validate | |
| 92 | 2 | if (hit = cache.get(path)){ |
| 93 | 1 | if (headers.etag == hit[0].etag) { |
| 94 | 1 | hit[0].date = new Date; |
| 95 | 1 | return; |
| 96 | } else { | |
| 97 | 0 | cache.remove(path); |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | // validation notifiactions don't contain a steam | |
| 102 | 1 | if (null == stream) return; |
| 103 | ||
| 104 | // add the cache object | |
| 105 | 1 | var arr = cache.add(path); |
| 106 | 1 | arr.push(headers); |
| 107 | ||
| 108 | // store the chunks | |
| 109 | 1 | stream.on('data', function(chunk){ |
| 110 | 1 | arr.push(chunk); |
| 111 | }); | |
| 112 | ||
| 113 | // flag it as complete | |
| 114 | 1 | stream.on('end', function(){ |
| 115 | 1 | arr.complete = true; |
| 116 | }); | |
| 117 | }); | |
| 118 | ||
| 119 | // cache hit, doesnt support range requests | |
| 120 | 10 | if (hit && hit.complete && !ranges) { |
| 121 | 7 | header = utils.merge({}, hit[0]); |
| 122 | 7 | header.Age = age = (new Date - new Date(header.date)) / 1000 | 0; |
| 123 | 7 | header.date = new Date().toUTCString(); |
| 124 | ||
| 125 | // parse cache-controls | |
| 126 | 7 | hitCC = utils.parseCacheControl(header['cache-control'] || ''); |
| 127 | 7 | uaCC = utils.parseCacheControl(req.headers['cache-control'] || ''); |
| 128 | ||
| 129 | // check if we must revalidate(bypass) | |
| 130 | 8 | if (hitCC['no-cache'] || uaCC['no-cache']) return miss(); |
| 131 | ||
| 132 | // check freshness of entity | |
| 133 | 6 | if (isStale(hitCC, age) || isStale(uaCC, age)) return miss(); |
| 134 | ||
| 135 | // conditional GET support | |
| 136 | 6 | if (utils.conditionalGET(req)) { |
| 137 | 0 | if (!utils.modified(req, res, header)) { |
| 138 | 0 | header['content-length'] = 0; |
| 139 | 0 | res.writeHead(304, header); |
| 140 | 0 | return res.end(); |
| 141 | } | |
| 142 | } | |
| 143 | ||
| 144 | // HEAD support | |
| 145 | 6 | if ('HEAD' == req.method) { |
| 146 | 2 | res.writeHead(200, header); |
| 147 | 2 | return res.end(); |
| 148 | } | |
| 149 | ||
| 150 | // respond with cache | |
| 151 | 4 | header['x-cache'] = 'HIT'; |
| 152 | 4 | res.writeHead(200, header); |
| 153 | ||
| 154 | // backpressure | |
| 155 | 4 | function write(i) { |
| 156 | 8 | var buf = hit[i]; |
| 157 | 12 | if (!buf) return res.end(); |
| 158 | 4 | if (false === res.write(buf)) { |
| 159 | 0 | res.once('drain', function(){ |
| 160 | 0 | write(++i); |
| 161 | }); | |
| 162 | } else { | |
| 163 | 4 | write(++i); |
| 164 | } | |
| 165 | } | |
| 166 | ||
| 167 | 4 | return write(1); |
| 168 | } | |
| 169 | ||
| 170 | 3 | 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 | ||
| 183 | 1 | function isStale(cc, age) { |
| 184 | 12 | return cc['max-age'] && cc['max-age'] <= age; |
| 185 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Connect - Cache | |
| 4 | * Copyright(c) 2011 Sencha Inc. | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Expose `Cache`. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | module.exports = Cache; |
| 13 | ||
| 14 | /** | |
| 15 | * LRU cache store. | |
| 16 | * | |
| 17 | * @param {Number} limit | |
| 18 | * @api private | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | function Cache(limit) { |
| 22 | 3 | this.store = {}; |
| 23 | 3 | this.keys = []; |
| 24 | 3 | 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 | ||
| 35 | 1 | Cache.prototype.touch = function(key, i){ |
| 36 | 0 | this.keys.splice(i,1); |
| 37 | 0 | this.keys.push(key); |
| 38 | }; | |
| 39 | ||
| 40 | /** | |
| 41 | * Remove `key`. | |
| 42 | * | |
| 43 | * @param {String} key | |
| 44 | * @api private | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | Cache.prototype.remove = function(key){ |
| 48 | 0 | 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 | ||
| 59 | 1 | Cache.prototype.get = function(key){ |
| 60 | 12 | 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 | ||
| 71 | 1 | Cache.prototype.add = function(key){ |
| 72 | // initialize store | |
| 73 | 1 | var len = this.keys.push(key); |
| 74 | ||
| 75 | // limit reached, invalidate LRU | |
| 76 | 1 | if (len > this.limit) this.remove(this.keys.shift()); |
| 77 | ||
| 78 | 1 | var arr = this.store[key] = []; |
| 79 | 1 | arr.createdAt = new Date; |
| 80 | 1 | return arr; |
| 81 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Connect - cookieSession | |
| 4 | * Copyright(c) 2011 Sencha Inc. | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var utils = require('./../utils') |
| 13 | , Cookie = require('./session/cookie') | |
| 14 | , debug = require('debug')('connect:cookieSession'); | |
| 15 | ||
| 16 | // environment | |
| 17 | ||
| 18 | 1 | var 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 | ||
| 40 | 1 | module.exports = function cookieSession(options){ |
| 41 | // TODO: utilize Session/Cookie to unify API | |
| 42 | // TODO: only set-cookie on changes to the session data | |
| 43 | ||
| 44 | 14 | var options = options || {} |
| 45 | , key = options.key || 'connect.sess' | |
| 46 | , cookie = options.cookie | |
| 47 | , trustProxy = options.proxy; | |
| 48 | ||
| 49 | 14 | return function cookieSession(req, res, next) { |
| 50 | 11 | req.session = req.signedCookies[key] || {}; |
| 51 | 11 | req.session.cookie = new Cookie(req, cookie); |
| 52 | ||
| 53 | 11 | res.on('header', function(){ |
| 54 | // removed | |
| 55 | 11 | if (!req.session) { |
| 56 | 1 | debug('clear session'); |
| 57 | 1 | res.setHeader('Set-Cookie', key + '=; expires=' + new Date(0).toUTCString()); |
| 58 | 1 | return; |
| 59 | } | |
| 60 | ||
| 61 | 10 | var cookie = req.session.cookie; |
| 62 | 10 | delete req.session.cookie; |
| 63 | ||
| 64 | // check security | |
| 65 | 10 | 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 | |
| 70 | 12 | if (cookie.secure && !secured) return debug('not secured'); |
| 71 | ||
| 72 | // set cookie | |
| 73 | 8 | debug('serializing %j', req.session); |
| 74 | 8 | var val = 'j:' + JSON.stringify(req.session); |
| 75 | 8 | val = utils.sign(val, req.secret); |
| 76 | 8 | val = utils.serializeCookie(key, val, cookie); |
| 77 | 8 | debug('cookie %j', cookie); |
| 78 | 8 | res.setHeader('Set-Cookie', val); |
| 79 | }); | |
| 80 | ||
| 81 | 11 | next(); |
| 82 | }; | |
| 83 | }; |