451 lines · 8.9 KB · JavaScript
RawDownload
 /*! * Connect - utils * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * MIT Licensed */ /** * Module dependencies. */ var crypto = require('crypto')  , Path = require('path')  , fs = require('fs'); /** * Flatten the given `arr`. * * @param {Array} arr * @return {Array} * @api private */ exports.flatten = function(arr, ret){  var ret = ret || []    , len = arr.length;  for (var i = 0; i < len; ++i) {    if (Array.isArray(arr[i])) {      exports.flatten(arr[i], ret);    } else {      ret.push(arr[i]);    }  }  return ret;}; /** * Return md5 hash of the given string and optional encoding, * defaulting to hex. * *     utils.md5('wahoo'); *     // => "e493298061761236c96b02ea6aa8a2ad" * * @param {String} str * @param {String} encoding * @return {String} * @api public */ exports.md5 = function(str, encoding){  return crypto    .createHash('md5')    .update(str)    .digest(encoding || 'hex');}; /** * Merge object b with object a. * *     var a = { foo: 'bar' } *       , b = { bar: 'baz' }; *      *     utils.merge(a, b); *     // => { foo: 'bar', bar: 'baz' } * * @param {Object} a * @param {Object} b * @return {Object} * @api public */ exports.merge = function(a, b){  if (a && b) {    for (var key in b) {      a[key] = b[key];    }  }  return a;}; /** * Escape the given string of `html`. * * @param {String} html * @return {String} * @api public */ exports.escape = function(html){  return String(html)    .replace(/&(?!\w+;)/g, '&amp;')    .replace(/</g, '&lt;')    .replace(/>/g, '&gt;')    .replace(/"/g, '&quot;');};  /** * Return a unique identifier with the given `len`. * *     utils.uid(10); *     // => "FDaS435D2z" * * @param {Number} len * @return {String} * @api public */ exports.uid = function(len) {  var buf = []    , chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'    , charlen = chars.length;   for (var i = 0; i < len; ++i) {    buf.push(chars[getRandomInt(0, charlen - 1)]);  }   return buf.join('');}; /** * Parse the given cookie string into an object. * * @param {String} str * @return {Object} * @api public */ exports.parseCookie = function(str){  var obj = {}    , pairs = str.split(/[;,] */);  for (var i = 0, len = pairs.length; i < len; ++i) {    var pair = pairs[i]      , eqlIndex = pair.indexOf('=')      , key = pair.substr(0, eqlIndex).trim().toLowerCase()      , val = pair.substr(++eqlIndex, pair.length).trim();     // quoted values    if ('"' == val[0]) val = val.slice(1, -1);     // only assign once    if (undefined == obj[key]) {      val = val.replace(/\+/g, ' ');      try {        obj[key] = decodeURIComponent(val);      } catch (err) {        if (err instanceof URIError) {          obj[key] = val;        } else {          throw err;        }      }    }  }  return obj;}; /** * Serialize the given object into a cookie string. * *      utils.serializeCookie('name', 'tj', { httpOnly: true }) *      // => "name=tj; httpOnly" * * @param {String} name * @param {String} val * @param {Object} obj * @return {String} * @api public */ exports.serializeCookie = function(name, val, obj){  var pairs = [name + '=' + encodeURIComponent(val)]    , obj = obj || {};   if (obj.domain) pairs.push('domain=' + obj.domain);  if (obj.path) pairs.push('path=' + obj.path);  if (obj.expires) pairs.push('expires=' + obj.expires.toUTCString());  if (obj.httpOnly) pairs.push('httpOnly');  if (obj.secure) pairs.push('secure');   return pairs.join('; ');}; /** * Pause `data` and `end` events on the given `obj`. * Middleware performing async tasks _should_ utilize * this utility (or similar), to re-emit data once * the async operation has completed, otherwise these * events may be lost. * *      var pause = utils.pause(req); *      fs.readFile(path, function(){ *         next(); *         pause.resume(); *      }); * * @param {Object} obj * @return {Object} * @api public */ exports.pause = function(obj){  var onData    , onEnd    , events = [];   // buffer data  obj.on('data', onData = function(data, encoding){    events.push(['data', data, encoding]);  });   // buffer end  obj.on('end', onEnd = function(data, encoding){    events.push(['end', data, encoding]);  });   return {    end: function(){      obj.removeListener('data', onData);      obj.removeListener('end', onEnd);    },    resume: function(){      this.end();      for (var i = 0, len = events.length; i < len; ++i) {        obj.emit.apply(obj, events[i]);      }    }  };}; /** * Check `req` and `res` to see if it has been modified. * * @param {IncomingMessage} req * @param {ServerResponse} res * @return {Boolean} * @api public */ exports.modified = function(req, res, headers) {  var headers = headers || res._headers || {}    , modifiedSince = req.headers['if-modified-since']    , lastModified = headers['last-modified']    , noneMatch = req.headers['if-none-match']    , etag = headers['etag'];   if (noneMatch) noneMatch = noneMatch.split(/ *, */);   // check If-None-Match  if (noneMatch && etag && ~noneMatch.indexOf(etag)) {    return false;  }   // check If-Modified-Since  if (modifiedSince && lastModified) {    modifiedSince = new Date(modifiedSince);    lastModified = new Date(lastModified);    // Ignore invalid dates    if (!isNaN(modifiedSince.getTime())) {      if (lastModified <= modifiedSince) return false;    }  }    return true;}; /** * Strip `Content-*` headers from `res`. * * @param {ServerResponse} res * @api public */ exports.removeContentHeaders = function(res){  Object.keys(res._headers).forEach(function(field){    if (0 == field.indexOf('content')) {      res.removeHeader(field);    }  });}; /** * Check if `req` is a conditional GET request. * * @param {IncomingMessage} req * @return {Boolean} * @api public */ exports.conditionalGET = function(req) {  return req.headers['if-modified-since']    || req.headers['if-none-match'];}; /** * Respond with 403 "Forbidden". * * @param {ServerResponse} res * @api public */ exports.forbidden = function(res) {  var body = 'Forbidden';  res.setHeader('Content-Type', 'text/plain');  res.setHeader('Content-Length', body.length);  res.statusCode = 403;  res.end(body);}; /** * Respond with 401 "Unauthorized". * * @param {ServerResponse} res * @param {String} realm * @api public */ exports.unauthorized = function(res, realm) {  res.statusCode = 401;  res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');  res.end('Unauthorized');}; /** * Respond with 400 "Bad Request". * * @param {ServerResponse} res * @api public */ exports.badRequest = function(res) {  res.statusCode = 400;  res.end('Bad Request');}; /** * Respond with 304 "Not Modified". * * @param {ServerResponse} res * @param {Object} headers * @api public */ exports.notModified = function(res) {  exports.removeContentHeaders(res);  res.statusCode = 304;  res.end();}; /** * Return an ETag in the form of `"<size>-<mtime>"` * from the given `stat`. * * @param {Object} stat * @return {String} * @api public */ exports.etag = function(stat) {  return '"' + stat.size + '-' + Number(stat.mtime) + '"';}; /** * Parse "Range" header `str` relative to the given file `size`. * * @param {Number} size * @param {String} str * @return {Array} * @api public */ exports.parseRange = function(size, str){  var valid = true;  var arr = str.substr(6).split(',').map(function(range){    var range = range.split('-')      , start = parseInt(range[0], 10)      , end = parseInt(range[1], 10);     // -500    if (isNaN(start)) {      start = size - end;      end = size - 1;    // 500-    } else if (isNaN(end)) {      end = size - 1;    }     // Invalid    if (isNaN(start) || isNaN(end) || start > end) valid = false;     return { start: start, end: end };  });  return valid ? arr : undefined;}; /** * Parse the given Cache-Control `str`. * * @param {String} str * @return {Object} * @api public */ exports.parseCacheControl = function(str){  var directives = str.split(',')    , obj = {};   for(var i = 0, len = directives.length; i < len; i++) {    var parts = directives[i].split('=')      , key = parts.shift().trim()      , val = parseInt(parts.shift(), 10);     obj[key] = isNaN(val) ? true : val;  }   return obj;};  /** * Convert array-like object to an `Array`. * * node-bench measured "16.5 times faster than Array.prototype.slice.call()" * * @param {Object} obj * @return {Array} * @api public */ var toArray = exports.toArray = function(obj){  var len = obj.length    , arr = new Array(len);  for (var i = 0; i < len; ++i) {    arr[i] = obj[i];  }  return arr;}; /** * Retrun a random int, used by `utils.uid()` * * @param {Number} min * @param {Number} max * @return {Number} * @api private */ function getRandomInt(min, max) {  return Math.floor(Math.random() * (max - min + 1)) + min;}