<commit_before>/*global window:true */
'use strict';

var util = require('util');
var isString = require('101/is-string');
var ApiClient = require('../api-client');
var Socket = require('../socket');
var modelStore = require('../stores/model-store');
var collectionStore = require('../stores/collection-store');
var Boom = require('boom');
var isBrowser = typeof window !== 'undefined';
var Base = require('./base');
var keypather = require('keypather')();
var parseUrl = require('url').parse;
var urlJoin = require('../url-join');
var masterHostnameErrMessage = 'not a master hostname';
var find = require('101/find');
var hasProps = require('101/has-properties');
var exists = require('101/exists');
var qs = require('querystring');

function isMasterHostnameErr (err) {
  return keypather.get(err, 'output.payload.message') === masterHostnameErrMessage;
}
function isInvalidHostnameErr (err) {
  return keypather.get(err, 'output.statusCode') === 400 &&
      keypather.get(err, 'data.errorCode') === 'INVALID_HOSTNAME';
}

module.exports = User;

function User (attrsOrHost, opts) {
  if (!console.trace) {
    console.trace = function () {
      var args = Array.prototype.slice.call(arguments);
      args.unshift('Trace:');
      console.log.apply(console, args);
      console.log(new Error('trace').stack);
    };
  }
  var attrs, host;
  if (isString(attrsOrHost)) {
    host = attrsOrHost;
    attrs = {};
  }
  else {
    attrs = attrsOrHost;
    host = null;
  }
  this.host = host || 'http://api.runnable.com';
  this.opts = opts || {};
  // if (!this.opts.userContentDomain) {
  //   throw new Error('user opts.userContentDomain is required');
  // }
  this.opts.client = this.opts.client || new ApiClient(this.host, this.opts.requestDefaults);

  Base.call(this, attrs, this.opts);
}

util.inherits(User, Base);

User.prototype.urlPath = 'users';

User.prototype.githubLogin = function (accessToken, cb) {
  if (isBrowser) {
    window.redirect('/auth/github');
  }
  else {
    var self = this;
    if (typeof accessToken === 'function') {
      cb = accessToken;
      cb(Boom.badRequest('you need an accessToken to login with GitHub'));
    } else {
      self.client.post('/auth/github/token',
        { json: { accessToken: accessToken }},
        function (err, res, body) {
          if (err) { return cb(err); }
          else if (res.statusCode !== 200) {
            var bodyMessage = keypather.get(body, 'message') || body;
            if (res.statusCode >= 400) {
              return cb(Boom.create(res.statusCode, bodyMessage));
            }
            if (res.statusCode === 302) {
              var headerLocation = keypather.get(res, 'headers.location');
              if (headerLocation && headerLocation.match(/whitelist.*false/i)) {
                return cb(Boom.unauthorized('User was authenticated, but does not belong to any whitelisted org'));
              }
            }
            var errorMessage = 'Error logging in (HTTP status code: ' + res.statusCode + ' )';
            if (bodyMessage) {
              errorMessage += ' (Message: ' + bodyMessage + ')';
            }
            return cb(Boom.create(500, errorMessage));
          }
          self.id('me');
          self.fetch(cb);
        });
      return this; // return model
    }
  }
};

User.prototype.getConnectSid = function () {
  var jarStore = this.client.opts.jar._jar.store.idx;
  return jarStore[Object.keys(jarStore)[0]]['/']['connect.sid'].value;
};

/**
 * Creates the socket connection for updates from api, mainly for instances
 * DO NOT CALL THIS AT THE BEGINNING OF THE APP.  IF THE USER IS NOT AUTHED,
 * IT WILL CAUSE MASSIVE FAILURES LOGGING IN.  SO DON'T!
 * @returns {Object|*} socket
 */
User.prototype.createSocket = function (options) {
  if (!this.socket) {
    this.socket = new Socket(this.host, options);
    return this.socket;
  }
};

User.prototype.logout = function (cb) {
  this.client.delete('/auth', function (err, res, body) {
    modelStore.reset();
    collectionStore.reset();
    if (err) {
      cb(err);
    }
    else if (res.statusCode !== 200) {
      cb(Boom.create(res.statusCode, body.message || body));
    }
    else {
      cb(err, body, res.statusCode);
    }
  });
};
/**
 * should redirect user to API for login
 * @param  {object} redirect  url to redirect to after auth
 *                              format: <protocol>/<host>:<port>
 */
User.prototype.getGithubAuthUrl = function (redirect, forceLogin) {
  var opts = {
    requiresToken: 'yes',
    redirect: redirect
  };
  if (forceLogin) {
    opts.forceLogin = 'yes';
  }
  var query = qs.stringify(opts);
  return urlJoin(this.host, 'auth/github') + '?' + query;
};
User.prototype.isOwnerOf = function (model) {
  if (model.toJSON) {
    model = model.toJSON();
  }
  if (!model.owner) {
    throw new Error("model doesn't have an owner property");
  }
  if (!this.attrs.accounts) {
    throw new Error("user doesn't have account info");
  }
  return model.owner.github === this.attrs.accounts.github.id;
};

User.prototype.gravitar = function () {
  return this.attrs.gravatar;
};

User.prototype.oauthName = function () {
  return keypather.get(this, 'attrs.accounts.github.username');
};

User.prototype.oauthId = function () {
  return keypather.get(this.attrs, 'accounts.github.id');
};

// utils

/**
 * fetch internal ip for url (used by charon)
 * @param  {String}   hostname      master pod instance url
 * @param  {String}   localIp       local ip address for the container making
 *                                  the request.
 * @param  {Function} cb            callback(err, networkIp)
 */
User.prototype.fetchInternalIpForHostname = function (hostname, localIp, cb) {
  var self = this;
  this._fetchMasterInstanceByHostname(hostname, function (err, masterInstance) {
    if (err) {
      if (isMasterHostnameErr(err)) {
        err.errorType = 'not-master-hostname';
      }
      else if (isInvalidHostnameErr(err)) {
        err.errorType = 'invalid-hostname';
      }
      return cb(err);
    }

    // masterInstance exists
    var refererQuery = {
      'container.inspect.NetworkSettings.IPAddress': localIp,
      'container.dockerHost': masterInstance.attrs.container.dockerHost
    };
    self._fetchInstanceAndDepWithHostname(refererQuery, hostname, function (err, dep) {
      if (err) {
        if (isInvalidHostnameErr(err)) {
          err.errorType = 'invalid-hostname';
        }
        return cb(err);
      }

      // Check if the dependency has a host ip
      if (hasHostIp(dep)) {
        return cb(null, dep.attrs.network.hostIp);
      }

      // Check the master instance for a host ip
      if (hasHostIp(masterInstance)) {
        return cb(null, masterInstance.attrs.network.hostIp);
      }

      // If neither have one then error gracefully
      cb(new Error('Master instance missing `network.hostIp`'));
    });
  });

  function hasHostIp(instance) {
    return exists(keypather.get(instance, 'attrs.network.hostIp'));
  }
};

/**
 * check to see if requested url is a direct url
 * if it is, set mapping to that instance
 * if it is not, error with 404
 * directUrls are formated like this
 * <branch>-<repository>-<environment>-<organization>.runnableapp.com
 * we cannot rely on '-' being a delimiter therefore branch can not be parsed
 * search all permutations subtracting the last 2 `-` since they are guaranteed to exist
 * @param  {String}   directUrl format: <protocol>://<host>:<port>
 * @param  {Function} cb        callback(err)
 */
User.prototype.checkAndSetIfDirectUrl = function (directUrl, cb) {
  var self = this;
  var branchPerm = [];
  var carry = '';
  var hostname = parseUrl(directUrl).hostname;
  var parts = hostname.split('-');
  // remove last 2
  parts.splice(parts.length - 2, parts.length);
  // create all possible permutations of branch
  parts.forEach(function(value) {
    carry += value.toLowerCase();
    branchPerm.push(carry);
  });
  var instances = self.fetchInstances({
    name: {
      $in: branchPerm
    }
  }, function (err) {
    if (err) { return cb(err); }
    if (instances.models.length > 1) {
      return cb(Boom.create(404, 'there are multiple instances for this url', parts));
    }
    if (instances.models.length <= 0) {
      return cb(Boom.create(404, 'there is no instance for this url', parts));
    }
    var instance = instances.models[0];
    self.createRoute({
      srcHostname: hostname,
      destInstanceId: instance.attrs._id
    }, cb);
  });
};
/**
 * gets a backend based on the user mapping model
 * @param  {[type]}   url key to look for in mapping
 * @param  {Function} cb  (err, host)
 *                        host format: <protocol>://<host>:<port>
 *                        will always exist
 */
User.prototype.getBackendFromUserMapping = function (url, cb) {
  var self = this;
  var parsedUrl = parseUrl(url);
  self.fetchRoutes(function (err, mappings) {
    if (err) { return cb(err); }
    var targetMapping = find(mappings, hasProps({srcHostname: parsedUrl.hostname}));
    if (!targetMapping) { return cb(Boom.create(404, 'no mapping for this url')); }
    self.fetchInstances({
      _id: targetMapping.destInstanceId,
      githubUsername: self.attrs.accounts.github.username
    }, function (err, instances) {
      if (err) { return cb(err); }
      if (instances.models.length <= 0) {
        return cb(Boom.create(404, 'mapped instances no longer exist'));
      }
      var instance = instances.models[0];
      validateAndCbBackend(instance, parseUrl.port, cb);
    });
  });
};
/**
 * private function that gets the backend url of an instance and verifies the port is exposed
 * @param  {Object}    instance  instance model
 * @param  {Function}  cb        callback
 */
function validateAndCbBackend (instance, urlPort, cb) {
  var backendUrl = instance.dockerUrlForPort(urlPort);
  var err = validateContainer(instance.attrs.container);
  if (err) {
    return cb(err); }
  if (!backendUrl) {
    err = Boom.create(400, 'port not exposed', urlPort);
    return cb(err);
  }

  cb(null, backendUrl);
}
/**
 * validate container existence
 * @param  {Object} container instance.attrs.container - instance container json
 * @return {Error}  err       returns err only if container does not exist
 */
function validateContainer (container) {
  var err;
  if (!container || container.error) {
    err = Boom.create(504, 'instance is down (no container)', container.error);
  }
  else if (container.inspect.error) {
    err = Boom.create(503, 'instance inspect failed (try again)', container.inspect.error);
  }

  return err;
}
/**
 * get backend url for a master pod instance url
 * @param  {String}   url        master pod instance url
 * @param  {String}   name       master pod instance name
 * @param  {String}   referer    http request referer (possible pod instance url) (can be undefined)
 * @param  {Function} cb  (err, host)
 *                        host format: <protocol>://<host>:<port>
 *                        will always exist
 */
User.prototype.fetchBackendForUrl = function (url, referer, cb) {
  var self = this;
  var parsedUrl = parseUrl(url);
  var urlPort = parsedUrl.port;
  if (!urlPort) {
    // 500 error bc developer is providing url to this function
    var err = Boom.create(500, 'url port is required (even 80)', {
      url: url,
      referer: referer
    });
    return cb(err);
  }
  this._fetchMasterInstanceByHostname(parsedUrl.hostname, function (err, masterInstance) {
    if (err) { return cb(err); }
    // masterInstance guaranteed to exist, else 404 error
    if (!referer) {
      return validateAndCbBackend(masterInstance, urlPort, cb);
    }

    var refererQuery = {
      hostname: parseUrl(referer).hostname
      // TODO: masterPod: false
    };
    self._fetchInstanceAndDepWithHostname(refererQuery, parsedUrl.hostname, function (err, dep) {
      if (err && !isInvalidHostnameErr(err)) {
        return cb(err);
      }
      return validateAndCbBackend(dep || masterInstance, urlPort, cb);
    });
  });
};

/**
 * fetch master instance by hostname
 * @param  {String}   hostname hostname of the instance's url
 * @param  {Function} cb       callback(err, instanceModel) // Never calls back a null model
 */
User.prototype._fetchMasterInstanceByHostname = function (hostname, cb) {
  var query = {
    hostname: hostname,
    masterPod: true
  };
  var instances = this.fetchInstances(query, function (err) {
    if (err) { return cb(err); }
    var masterInstance = instances.models[0];
    if (!masterInstance) {
      // this error message is used by isMasterHostnameErr method
      err = Boom.create(400, masterHostnameErrMessage, { hostname: hostname });
      return cb(err);
    }
    cb(null, masterInstance);
  });
};

/**
 * find an instance with the query and find its dependency with the shared hostname
 * @param  {String} query          instance url
 * @param  {String} hostname       instance dependency hostname (shared with master pod)
 * @param  {String} cb             callback(err, dep) // can callback null dep
 */
User.prototype._fetchInstanceAndDepWithHostname = function (query, hostname, cb) {
  var instances = this.fetchInstances(query, function (err) {
    if (err) { return cb(err); }
    var instance = instances.models[0];

    if (!instance) {
      return cb();
    }
    var query2 = { hostname: hostname };
    var deps = instance.fetchDependencies(query2, function (err) {
      cb(err, deps.models[0]);
    });
  });
};

require('../extend-with-factories')(User, '');
require('../extend-with-factories')(User, 'user');
<commit_msg>Expose connectSid<commit_after>/*global window:true */
'use strict';

var util = require('util');
var isString = require('101/is-string');
var ApiClient = require('../api-client');
var Socket = require('../socket');
var modelStore = require('../stores/model-store');
var collectionStore = require('../stores/collection-store');
var Boom = require('boom');
var isBrowser = typeof window !== 'undefined';
var Base = require('./base');
var keypather = require('keypather')();
var parseUrl = require('url').parse;
var urlJoin = require('../url-join');
var masterHostnameErrMessage = 'not a master hostname';
var find = require('101/find');
var hasProps = require('101/has-properties');
var exists = require('101/exists');
var qs = require('querystring');

function isMasterHostnameErr (err) {
  return keypather.get(err, 'output.payload.message') === masterHostnameErrMessage;
}
function isInvalidHostnameErr (err) {
  return keypather.get(err, 'output.statusCode') === 400 &&
      keypather.get(err, 'data.errorCode') === 'INVALID_HOSTNAME';
}

module.exports = User;

function User (attrsOrHost, opts) {
  if (!console.trace) {
    console.trace = function () {
      var args = Array.prototype.slice.call(arguments);
      args.unshift('Trace:');
      console.log.apply(console, args);
      console.log(new Error('trace').stack);
    };
  }
  var attrs, host;
  if (isString(attrsOrHost)) {
    host = attrsOrHost;
    attrs = {};
  }
  else {
    attrs = attrsOrHost;
    host = null;
  }
  this.host = host || 'http://api.runnable.com';
  this.opts = opts || {};
  // if (!this.opts.userContentDomain) {
  //   throw new Error('user opts.userContentDomain is required');
  // }
  this.opts.client = this.opts.client || new ApiClient(this.host, this.opts.requestDefaults);

  Base.call(this, attrs, this.opts);
}

util.inherits(User, Base);

User.prototype.urlPath = 'users';

User.prototype.githubLogin = function (accessToken, cb) {
  if (isBrowser) {
    window.redirect('/auth/github');
  }
  else {
    var self = this;
    if (typeof accessToken === 'function') {
      cb = accessToken;
      cb(Boom.badRequest('you need an accessToken to login with GitHub'));
    } else {
      self.client.post('/auth/github/token',
        { json: { accessToken: accessToken }},
        function (err, res, body) {
          if (err) { return cb(err); }
          else if (res.statusCode !== 200) {
            var bodyMessage = keypather.get(body, 'message') || body;
            if (res.statusCode >= 400) {
              return cb(Boom.create(res.statusCode, bodyMessage));
            }
            if (res.statusCode === 302) {
              var headerLocation = keypather.get(res, 'headers.location');
              if (headerLocation && headerLocation.match(/whitelist.*false/i)) {
                return cb(Boom.unauthorized('User was authenticated, but does not belong to any whitelisted org'));
              }
            }
            var errorMessage = 'Error logging in (HTTP status code: ' + res.statusCode + ' )';
            if (bodyMessage) {
              errorMessage += ' (Message: ' + bodyMessage + ')';
            }
            return cb(Boom.create(500, errorMessage));
          }
          self.connectSid = res.headers['set-cookie'][0].split(';')[0].split('=')[1];
          self.id('me');
          self.fetch(cb);
        });
      return this; // return model
    }
  }
};

/**
 * Creates the socket connection for updates from api, mainly for instances
 * DO NOT CALL THIS AT THE BEGINNING OF THE APP.  IF THE USER IS NOT AUTHED,
 * IT WILL CAUSE MASSIVE FAILURES LOGGING IN.  SO DON'T!
 * @returns {Object|*} socket
 */
User.prototype.createSocket = function (options) {
  if (!this.socket) {
    this.socket = new Socket(this.host, options);
    return this.socket;
  }
};

User.prototype.logout = function (cb) {
  this.client.delete('/auth', function (err, res, body) {
    modelStore.reset();
    collectionStore.reset();
    if (err) {
      cb(err);
    }
    else if (res.statusCode !== 200) {
      cb(Boom.create(res.statusCode, body.message || body));
    }
    else {
      cb(err, body, res.statusCode);
    }
  });
};
/**
 * should redirect user to API for login
 * @param  {object} redirect  url to redirect to after auth
 *                              format: <protocol>/<host>:<port>
 */
User.prototype.getGithubAuthUrl = function (redirect, forceLogin) {
  var opts = {
    requiresToken: 'yes',
    redirect: redirect
  };
  if (forceLogin) {
    opts.forceLogin = 'yes';
  }
  var query = qs.stringify(opts);
  return urlJoin(this.host, 'auth/github') + '?' + query;
};
User.prototype.isOwnerOf = function (model) {
  if (model.toJSON) {
    model = model.toJSON();
  }
  if (!model.owner) {
    throw new Error("model doesn't have an owner property");
  }
  if (!this.attrs.accounts) {
    throw new Error("user doesn't have account info");
  }
  return model.owner.github === this.attrs.accounts.github.id;
};

User.prototype.gravitar = function () {
  return this.attrs.gravatar;
};

User.prototype.oauthName = function () {
  return keypather.get(this, 'attrs.accounts.github.username');
};

User.prototype.oauthId = function () {
  return keypather.get(this.attrs, 'accounts.github.id');
};

// utils

/**
 * fetch internal ip for url (used by charon)
 * @param  {String}   hostname      master pod instance url
 * @param  {String}   localIp       local ip address for the container making
 *                                  the request.
 * @param  {Function} cb            callback(err, networkIp)
 */
User.prototype.fetchInternalIpForHostname = function (hostname, localIp, cb) {
  var self = this;
  this._fetchMasterInstanceByHostname(hostname, function (err, masterInstance) {
    if (err) {
      if (isMasterHostnameErr(err)) {
        err.errorType = 'not-master-hostname';
      }
      else if (isInvalidHostnameErr(err)) {
        err.errorType = 'invalid-hostname';
      }
      return cb(err);
    }

    // masterInstance exists
    var refererQuery = {
      'container.inspect.NetworkSettings.IPAddress': localIp,
      'container.dockerHost': masterInstance.attrs.container.dockerHost
    };
    self._fetchInstanceAndDepWithHostname(refererQuery, hostname, function (err, dep) {
      if (err) {
        if (isInvalidHostnameErr(err)) {
          err.errorType = 'invalid-hostname';
        }
        return cb(err);
      }

      // Check if the dependency has a host ip
      if (hasHostIp(dep)) {
        return cb(null, dep.attrs.network.hostIp);
      }

      // Check the master instance for a host ip
      if (hasHostIp(masterInstance)) {
        return cb(null, masterInstance.attrs.network.hostIp);
      }

      // If neither have one then error gracefully
      cb(new Error('Master instance missing `network.hostIp`'));
    });
  });

  function hasHostIp(instance) {
    return exists(keypather.get(instance, 'attrs.network.hostIp'));
  }
};

/**
 * check to see if requested url is a direct url
 * if it is, set mapping to that instance
 * if it is not, error with 404
 * directUrls are formated like this
 * <branch>-<repository>-<environment>-<organization>.runnableapp.com
 * we cannot rely on '-' being a delimiter therefore branch can not be parsed
 * search all permutations subtracting the last 2 `-` since they are guaranteed to exist
 * @param  {String}   directUrl format: <protocol>://<host>:<port>
 * @param  {Function} cb        callback(err)
 */
User.prototype.checkAndSetIfDirectUrl = function (directUrl, cb) {
  var self = this;
  var branchPerm = [];
  var carry = '';
  var hostname = parseUrl(directUrl).hostname;
  var parts = hostname.split('-');
  // remove last 2
  parts.splice(parts.length - 2, parts.length);
  // create all possible permutations of branch
  parts.forEach(function(value) {
    carry += value.toLowerCase();
    branchPerm.push(carry);
  });
  var instances = self.fetchInstances({
    name: {
      $in: branchPerm
    }
  }, function (err) {
    if (err) { return cb(err); }
    if (instances.models.length > 1) {
      return cb(Boom.create(404, 'there are multiple instances for this url', parts));
    }
    if (instances.models.length <= 0) {
      return cb(Boom.create(404, 'there is no instance for this url', parts));
    }
    var instance = instances.models[0];
    self.createRoute({
      srcHostname: hostname,
      destInstanceId: instance.attrs._id
    }, cb);
  });
};
/**
 * gets a backend based on the user mapping model
 * @param  {[type]}   url key to look for in mapping
 * @param  {Function} cb  (err, host)
 *                        host format: <protocol>://<host>:<port>
 *                        will always exist
 */
User.prototype.getBackendFromUserMapping = function (url, cb) {
  var self = this;
  var parsedUrl = parseUrl(url);
  self.fetchRoutes(function (err, mappings) {
    if (err) { return cb(err); }
    var targetMapping = find(mappings, hasProps({srcHostname: parsedUrl.hostname}));
    if (!targetMapping) { return cb(Boom.create(404, 'no mapping for this url')); }
    self.fetchInstances({
      _id: targetMapping.destInstanceId,
      githubUsername: self.attrs.accounts.github.username
    }, function (err, instances) {
      if (err) { return cb(err); }
      if (instances.models.length <= 0) {
        return cb(Boom.create(404, 'mapped instances no longer exist'));
      }
      var instance = instances.models[0];
      validateAndCbBackend(instance, parseUrl.port, cb);
    });
  });
};
/**
 * private function that gets the backend url of an instance and verifies the port is exposed
 * @param  {Object}    instance  instance model
 * @param  {Function}  cb        callback
 */
function validateAndCbBackend (instance, urlPort, cb) {
  var backendUrl = instance.dockerUrlForPort(urlPort);
  var err = validateContainer(instance.attrs.container);
  if (err) {
    return cb(err); }
  if (!backendUrl) {
    err = Boom.create(400, 'port not exposed', urlPort);
    return cb(err);
  }

  cb(null, backendUrl);
}
/**
 * validate container existence
 * @param  {Object} container instance.attrs.container - instance container json
 * @return {Error}  err       returns err only if container does not exist
 */
function validateContainer (container) {
  var err;
  if (!container || container.error) {
    err = Boom.create(504, 'instance is down (no container)', container.error);
  }
  else if (container.inspect.error) {
    err = Boom.create(503, 'instance inspect failed (try again)', container.inspect.error);
  }

  return err;
}
/**
 * get backend url for a master pod instance url
 * @param  {String}   url        master pod instance url
 * @param  {String}   name       master pod instance name
 * @param  {String}   referer    http request referer (possible pod instance url) (can be undefined)
 * @param  {Function} cb  (err, host)
 *                        host format: <protocol>://<host>:<port>
 *                        will always exist
 */
User.prototype.fetchBackendForUrl = function (url, referer, cb) {
  var self = this;
  var parsedUrl = parseUrl(url);
  var urlPort = parsedUrl.port;
  if (!urlPort) {
    // 500 error bc developer is providing url to this function
    var err = Boom.create(500, 'url port is required (even 80)', {
      url: url,
      referer: referer
    });
    return cb(err);
  }
  this._fetchMasterInstanceByHostname(parsedUrl.hostname, function (err, masterInstance) {
    if (err) { return cb(err); }
    // masterInstance guaranteed to exist, else 404 error
    if (!referer) {
      return validateAndCbBackend(masterInstance, urlPort, cb);
    }

    var refererQuery = {
      hostname: parseUrl(referer).hostname
      // TODO: masterPod: false
    };
    self._fetchInstanceAndDepWithHostname(refererQuery, parsedUrl.hostname, function (err, dep) {
      if (err && !isInvalidHostnameErr(err)) {
        return cb(err);
      }
      return validateAndCbBackend(dep || masterInstance, urlPort, cb);
    });
  });
};

/**
 * fetch master instance by hostname
 * @param  {String}   hostname hostname of the instance's url
 * @param  {Function} cb       callback(err, instanceModel) // Never calls back a null model
 */
User.prototype._fetchMasterInstanceByHostname = function (hostname, cb) {
  var query = {
    hostname: hostname,
    masterPod: true
  };
  var instances = this.fetchInstances(query, function (err) {
    if (err) { return cb(err); }
    var masterInstance = instances.models[0];
    if (!masterInstance) {
      // this error message is used by isMasterHostnameErr method
      err = Boom.create(400, masterHostnameErrMessage, { hostname: hostname });
      return cb(err);
    }
    cb(null, masterInstance);
  });
};

/**
 * find an instance with the query and find its dependency with the shared hostname
 * @param  {String} query          instance url
 * @param  {String} hostname       instance dependency hostname (shared with master pod)
 * @param  {String} cb             callback(err, dep) // can callback null dep
 */
User.prototype._fetchInstanceAndDepWithHostname = function (query, hostname, cb) {
  var instances = this.fetchInstances(query, function (err) {
    if (err) { return cb(err); }
    var instance = instances.models[0];

    if (!instance) {
      return cb();
    }
    var query2 = { hostname: hostname };
    var deps = instance.fetchDependencies(query2, function (err) {
      cb(err, deps.models[0]);
    });
  });
};

require('../extend-with-factories')(User, '');
require('../extend-with-factories')(User, 'user');
<|endoftext|>


<commit_before>var BUFFER_LENGTH = 1024 * 8;

var parser = require('./parser');

exports.render = render;

function render(tokens, context, partials, stream, callback) {
  if (!Array.isArray(context)) {
    context = [context];
  }

  return _render(tokens, context, partials, stream, callback);
}

function _render() {
  process.nextTick(Function.prototype.bind.apply(__render, [this].concat(Array.prototype.slice.call(arguments))));
}

function __render(tokens, context, partials, stream, callback) {
  if (tokens[0] !== 'multi') {
    throw new Error('Mu - WTF did you give me? I expected mustache tokens.');
  }

  var i = 1;

  function next() {
    try {

      if (stream.paused) {
        stream.once('resumed', function () {
          process.nextTick(next);
        });
        return;
      }

      var token = tokens[i++];

      if (!token) {
        return callback ? callback() : true;
      }

      switch (token[0]) {
      case 'static':
        stream.emit('data', token[2]);
        return next();

      case 'mustache':
        switch (token[1]) {
        case 'utag': // Unescaped Tag
          stream.emit('data', s(normalize(context, token[2])));
          return next();

        case 'etag': // Escaped Tag
          stream.emit('data', escape(s(normalize(context, token[2]))));
          return next();

        case 'section':
          var res = normalize(context, token[2], token[3]);
          if (res) {
            return section(context, token[2], res, token[4], partials, stream, next);
          } else {
            return next();
          }

        case 'inverted_section':
          var res = normalize(context, token[2], token[3]);
          if (!res || res.length === 0) {
            return section(context, token[2], true, token[4], partials, stream, next);
          } else {
            return next();
          }

        case 'partial':
          var partial = partials[token[2]];
          // console.log(require('util').inspect(partials));
          if (partial) {
            return render(partial[0].tokens, context, partials, stream, next);
          } else {
            return next();
          }
        }

      }

    } catch (err) {
      stream.emit('error', err);
      next();
    }
  }

  next();
}

function s(val) {
  if (val === null || typeof val === 'undefined') {
    return '';
  } else {
    return val.toString();
  }
}

function escape(string) {
  return string.replace(/[&<>"]/g, escapeReplace);
}

function normalize(context, name, body) {
  var val = walkToFind(context, name);

  if (typeof(val) === 'function') {
    val = val.call(smashContext(context), body);
  }

  return val;
}

function walkToFind(context, name) {
  var i = context.length;

  while (i--) {
    var result = contextLevelContains(context[i], name);

    if (result !== undefined) {
      return result;
    }
  }

  return undefined;
}

function contextLevelContains(context, fullPath) {
  var pathParts = fullPath.split('.');
  var obj = context;

  for (var i = 0; i < pathParts.length; i++) {
    var part = pathParts[i];

    if (typeof obj == 'object' && part in obj) {
      obj = obj[part];
    } else {
      obj = undefined;
      break;
    }
  }

  return obj;
}

// TODO: if Proxy, make more efficient
// TODO: cache?
function smashContext(context) {
  var obj = {};

  for (var i = 0; i < context.length; i++) {
    var level = context[i];

    for (var k in level) {
      obj[k] = level[k];
    }
  }

  return obj;
}

function section(context, name, val, tokens, partials, stream, callback) {
  if (val instanceof Array) {
    var i = 0;

    (function next() {
      var item = val[i++];

      if (item) {
        context.push(item);
        _render(tokens, context, partials, stream, function () {
          context.pop();
          next();
        });
      } else {
        callback();
      }

    }());

    return;
  }

  if (typeof val === 'object') {
    context.push(val);
    _render(tokens, context, partials, stream, function () {
      context.pop();
      callback();
    });
    return;
  }

  if (val) {
    return _render(tokens, context, partials, stream, callback);
  }

  return callback();
}

//
//
//
function findInContext(context, key) {
  var i = context.length;
  while (i--) {
    if (context[i][key]) {
      return context[i][key];
    }
  }

  return undefined;
}

//
//
//
function escapeReplace(char) {
  switch (char) {
    case '<': return '<';
    case '>': return '>';
    case '&': return '&';
    case '"': return '"';
    default: return char;
  }
}
<commit_msg>Add check to prevent the stack from overflowing. Fix #43.<commit_after>var BUFFER_LENGTH = 1024 * 8;
var MAX_STACK_SIZE = 100;

var parser = require('./parser');

exports.render = render;

function render(tokens, context, partials, stream, callback) {
  if (!Array.isArray(context)) {
    context = [context];
  }

  return _render(tokens, context, partials, stream, callback);
}

function _render(tokens, context, partials, stream, callback) {
  if (tokens[0] !== 'multi') {
    throw new Error('Mu - WTF did you give me? I expected mustache tokens.');
  }

  var i = 1
    , stackSize = 0;

  function next() {
    try {

      if (stream.paused) {
        stream.once('resumed', function () {
          process.nextTick(next);
        });
        return;
      }

      if (++stackSize % MAX_STACK_SIZE == 0) {
        process.nextTick(next);
        return;
      }

      var token = tokens[i++];

      if (!token) {
        return callback ? callback() : true;
      }

      switch (token[0]) {
      case 'static':
        stream.emit('data', token[2]);
        return next();

      case 'mustache':
        switch (token[1]) {
        case 'utag': // Unescaped Tag
          stream.emit('data', s(normalize(context, token[2])));
          return next();

        case 'etag': // Escaped Tag
          stream.emit('data', escape(s(normalize(context, token[2]))));
          return next();

        case 'section':
          var res = normalize(context, token[2], token[3]);
          if (res) {
            return section(context, token[2], res, token[4], partials, stream, next);
          } else {
            return next();
          }

        case 'inverted_section':
          var res = normalize(context, token[2], token[3]);
          if (!res || res.length === 0) {
            return section(context, token[2], true, token[4], partials, stream, next);
          } else {
            return next();
          }

        case 'partial':
          var partial = partials[token[2]];
          // console.log(require('util').inspect(partials));
          if (partial) {
            return render(partial[0].tokens, context, partials, stream, next);
          } else {
            return next();
          }
        }

      }

    } catch (err) {
      stream.emit('error', err);
      next();
    }
  }

  next();
}

function s(val) {
  if (val === null || typeof val === 'undefined') {
    return '';
  } else {
    return val.toString();
  }
}

function escape(string) {
  return string.replace(/[&<>"]/g, escapeReplace);
}

function normalize(context, name, body) {
  var val = walkToFind(context, name);

  if (typeof(val) === 'function') {
    val = val.call(smashContext(context), body);
  }

  return val;
}

function walkToFind(context, name) {
  var i = context.length;

  while (i--) {
    var result = contextLevelContains(context[i], name);

    if (result !== undefined) {
      return result;
    }
  }

  return undefined;
}

function contextLevelContains(context, fullPath) {
  var pathParts = fullPath.split('.');
  var obj = context;

  for (var i = 0; i < pathParts.length; i++) {
    var part = pathParts[i];

    if (typeof obj == 'object' && part in obj) {
      obj = obj[part];
    } else {
      obj = undefined;
      break;
    }
  }

  return obj;
}

// TODO: if Proxy, make more efficient
// TODO: cache?
function smashContext(context) {
  var obj = {};

  for (var i = 0; i < context.length; i++) {
    var level = context[i];

    for (var k in level) {
      obj[k] = level[k];
    }
  }

  return obj;
}

function section(context, name, val, tokens, partials, stream, callback) {
  if (val instanceof Array) {
    var i = 0;

    (function next() {
      var item = val[i++];

      if (item) {
        context.push(item);
        _render(tokens, context, partials, stream, function () {
          context.pop();

          if (i % MAX_STACK_SIZE == 0) {
            return process.nextTick(next);
          } else {
            next();
          }
        });
      } else {
        callback();
      }

    }());

    return;
  }

  if (typeof val === 'object') {
    context.push(val);
    _render(tokens, context, partials, stream, function () {
      context.pop();
      callback();
    });
    return;
  }

  if (val) {
    return _render(tokens, context, partials, stream, callback);
  }

  return callback();
}

//
//
//
function findInContext(context, key) {
  var i = context.length;
  while (i--) {
    if (context[i][key]) {
      return context[i][key];
    }
  }

  return undefined;
}

//
//
//
function escapeReplace(char) {
  switch (char) {
    case '<': return '<';
    case '>': return '>';
    case '&': return '&';
    case '"': return '"';
    default: return char;
  }
}
<|endoftext|>


<commit_before>var _               = require('underscore');
var assert = require('assert');
var path = require('path');

//expand source and target then merge,
//if source and target are both have route recurse
//XXX if one or the the have route replace target with source
//otherwise merge source keys into target, if source key has falsy value, remove the key
//if handle is being added assume its the current app unless app is specified?
function update(app, target, source) {
    for (var prop in source) {
        if (!source.hasOwnProperty(prop)) continue;
        var def = expandDefinition(app, prop, source[prop]);
        if (prop in target) {
            if (!def) {
              delete target[prop];
            } else if (def.route) {
              debugger; //XXX
            } else if (def.pathonly) {
              target[prop].path = def.path;
            } else {
              _.extend(target[prop], def);
            }
        } else if (!def.pathonly) {
          if (typeof def.path === 'undefined')
            def.path = prop; //use name as path
          target[prop] = def;
        }
    }
    return target;
}

function applyRoute(route, base, pre) {
  base = base || '/';
  if (base.slice(-1) != '/')
    base += '/';
  var path = base + route.path;
  if (pre)
    route = pre(route);
  if (route.route) { //nested routes
    applyRoutes(route.route, path, pre);
  } else {
    var app = route.app;
    assert(app);
    for (var key in route) {
      if (key == "path" || key == 'app' || !app[key])
        continue;
      //call app.METHOD(path, ...route[METHOD]):
      app[key].apply(app, [path].concat(route[key]));
    }
  }
}

function applyRoutes(routes, base, pre) {
  for (var route in routes) {
    if (!routes.hasOwnProperty(route)) continue;
    applyRoute(routes[route], base, pre);
  }
}

module.exports = NamedRoutes = function() {}

NamedRoutes.prototype = {
  //XXXs use PATH_REGEXP in https://github.com/component/path-to-regexp/blob/master/index.js for a complete solution
  PARAMREGEX: /(?:[^\\]|^)\:(\w+)/,

  applyRoutes: function(pre) {
    if (pre && Array.isArray(pre)) {
      if (pre.length) {
        //chain functions
        pre = pre.reduce(function(ff, f) { return function(route) { return ff(f(route));}});
      } else {
        pre = null;
      }
    }
    return applyRoutes(this, null, pre);
  },

  updateRoutes: function(app, source) {
    return update(app, this, source);
  },

  getUrlMap: function() {
    var PARAMREGEX = this.PARAMREGEX,
      PARAMREGEXG = new RegExp(PARAMREGEX.source, 'g');
    return _.object(_.map(this,
      function(value, key){
        var urlpath = path.join('/', value.path);
        var resolved;
        if (value.path.match(PARAMREGEX)) {
          //generate a function that builds the url given an object
          resolved = function(obj) {
            return urlpath.replace(PARAMREGEXG, function(param) {
              return obj[param];
            });
          }
        } else {
          resolved = urlpath;
        }
        return [key, resolved];
      }));
  },

  getUrlMapSource: function() {
    var PARAMREGEX = this.PARAMREGEX;
    return JSON.stringify(_.object(_.map(this,
      function(value, key){
        var urlpath = path.join('/', value.path);
        var resolved;
        if (value.path.match(PARAMREGEX)) {
          resolved = "@@@"+ urlpath + "@@@";
        } else {
          resolved = urlpath;
        }
        return [key, resolved];
      }))).replace(/"@@@(.+?)@@@"/g, 'function(obj) {return "$1".replace(/'
         + PARAMREGEX.source +'/g, function(param) { return obj[param];});}'
      );
  }

};

/*
If path is omitted the name of the route is used as the path
If method is ommitted, GET is used
*/
function expandDefinition(app, name, route) {
  if (!route)
    return null;
  if (typeof route === 'function') {
    return {
      app: app,
      get: [route]
    }
  } else if (Array.isArray(route)) {
    var path = undefined, funcs;
    if (typeof route[0] !== 'string') {
      funcs = route;
    } else {
      path = route[0];
      funcs = route.slice(1);
    }
    return {
      app: app,
      path: path,
      get: funcs
    }
  } else if (route.route) { //nested routes
    debugger; //XXX
  } else {
    var def = {
      app: app,
      path: route.path
    };
    var found = false;
    for (var key in route) {
      if (key == "path" || key == 'app' || !app[key])
        continue;
      def[key] = (!route[key] || Array.isArray(route[key])) ? route[key] : [route[key]];
      found = true;
    }
    if (!found) {
      def.pathonly = true;
      if (typeof route.path === 'undefined')
        def.path = name
    }
    return def;
  }
}
<commit_msg>more intuitive merging of updated named routes<commit_after>var _               = require('underscore');
var assert = require('assert');
var path = require('path');

//expand source and target then merge,
//if source and target are both have route recurse
//XXX if one or the the have route replace target with source
//otherwise merge source keys into target, if source key has falsy value, remove the key
//if handle is being added assume its the current app unless app is specified?
function update(app, target, source) {
    for (var prop in source) {
        if (!source.hasOwnProperty(prop)) continue;
        var def = expandDefinition(app, prop, source[prop]);
        if (prop in target) {
            if (!def) {
              delete target[prop];
            } else if (def.route) {
              debugger; //XXX
            } else if (def.pathonly) {
              target[prop].path = def.path;
            } else {
              //use defaults() instead of extends() so undefined are skipped
              target[prop] = _.defaults({}, def, target[prop]);
            }
        } else if (!def.pathonly) {
          if (typeof def.path === 'undefined')
            def.path = prop; //use name as path
          target[prop] = def;
        }
    }
    return target;
}

function applyRoute(route, base, pre) {
  base = base || '/';
  if (base.slice(-1) != '/')
    base += '/';
  var path = base + route.path;
  if (pre)
    route = pre(route);
  if (route.route) { //nested routes
    applyRoutes(route.route, path, pre);
  } else {
    var app = route.app;
    assert(app);
    for (var key in route) {
      if (key == "path" || key == 'app' || !app[key])
        continue;
      //call app.METHOD(path, ...route[METHOD]):
      app[key].apply(app, [path].concat(route[key]));
    }
  }
}

function applyRoutes(routes, base, pre) {
  for (var route in routes) {
    if (!routes.hasOwnProperty(route)) continue;
    applyRoute(routes[route], base, pre);
  }
}

module.exports = NamedRoutes = function() {}

NamedRoutes.prototype = {
  //XXXs use PATH_REGEXP in https://github.com/component/path-to-regexp/blob/master/index.js for a complete solution
  PARAMREGEX: /(?:[^\\]|^)\:(\w+)/,

  applyRoutes: function(pre) {
    if (pre && Array.isArray(pre)) {
      if (pre.length) {
        //chain functions
        pre = pre.reduce(function(ff, f) { return function(route) { return ff(f(route));}});
      } else {
        pre = null;
      }
    }
    return applyRoutes(this, null, pre);
  },

  updateRoutes: function(app, source) {
    return update(app, this, source);
  },

  getUrlMap: function() {
    var PARAMREGEX = this.PARAMREGEX,
      PARAMREGEXG = new RegExp(PARAMREGEX.source, 'g');
    return _.object(_.map(this,
      function(value, key){
        var urlpath = path.join('/', value.path);
        var resolved;
        if (value.path.match(PARAMREGEX)) {
          //generate a function that builds the url given an object
          resolved = function(obj) {
            return urlpath.replace(PARAMREGEXG, function(param) {
              return obj[param];
            });
          }
        } else {
          resolved = urlpath;
        }
        return [key, resolved];
      }));
  },

  getUrlMapSource: function() {
    var PARAMREGEX = this.PARAMREGEX;
    return JSON.stringify(_.object(_.map(this,
      function(value, key){
        var urlpath = path.join('/', value.path);
        var resolved;
        if (value.path.match(PARAMREGEX)) {
          resolved = "@@@"+ urlpath + "@@@";
        } else {
          resolved = urlpath;
        }
        return [key, resolved];
      }))).replace(/"@@@(.+?)@@@"/g, 'function(obj) {return "$1".replace(/'
         + PARAMREGEX.source +'/g, function(param) { return obj[param];});}'
      );
  }

};

/*
If path is omitted the name of the route is used as the path
If method is ommitted, GET is used
*/
function expandDefinition(app, name, route) {
  if (!route)
    return null;
  if (typeof route === 'function') {
    return {
      app: app,
      get: [route]
    }
  } else if (Array.isArray(route)) {
    var path = undefined, funcs;
    if (typeof route[0] !== 'string') {
      funcs = route;
    } else {
      path = route[0];
      funcs = route.slice(1);
    }
    return {
      app: app,
      path: path,
      get: funcs
    }
  } else if (route.route) { //nested routes
    debugger; //XXX
  } else {
    var def = {
      app: app,
      path: route.path
    };
    var found = false;
    for (var key in route) {
      if (key == "path" || key == 'app' || !app[key])
        continue;
      def[key] = (!route[key] || Array.isArray(route[key])) ? route[key] : [route[key]];
      found = true;
    }
    if (!found) {
      def.pathonly = true;
      if (typeof route.path === 'undefined')
        def.path = name
    }
    return def;
  }
}
<|endoftext|>


<commit_before>var minify = (function(undefined) {

	var exec = require('child_process').exec,
			_fs = require('fs');

	var minify = function(options) {
		this.type = options.type;
		this.tempFile = new Date().getTime().toString();

		if(options.type == 'gcc' && typeof options.fileIn === 'string') {
			this.fileIn = [options.fileIn];
		}
		else if(typeof options.fileIn === 'string') {
			this.fileIn = options.fileIn;
		}

		if(typeof options.fileIn === 'object' && options.fileIn instanceof Array && options.type != 'gcc') {
			var out = options.fileIn.map(function(path) {
				return _fs.readFileSync(path, 'utf8');
			});

			_fs.writeFileSync(this.tempFile, out.join('\n'), 'utf8');

			this.fileIn = this.tempFile;
		}
		else if(typeof options.fileIn === 'object' && options.fileIn instanceof Array && options.type == 'gcc') {
			this.fileIn = options.fileIn;
		}

		this.fileOut = options.fileOut;
		this.options = options.options || [];
		this.buffer = options.buffer || 1000 * 1024;
		if (typeof options.callback !== 'undefined') {
			this.callback = options.callback;
		}

		this.compress();
	};

	minify.prototype = minify.fn = {
		type: null,
		fileIn: null,
		fileOut: null,
		callback: null,
		buffer: null, // with larger files you will need a bigger buffer for closure compiler
		compress: function() {
			var self = this, command;

			switch (this.type) {
				case 'yui':
					command = 'java -jar -Xss2048k "' + __dirname + '/yuicompressor-2.4.7.jar" "' + this.fileIn + '" -o "' + this.fileOut + '" --type css ' + this.options.join(' ');
					break;
				case 'gcc':
					var fileInCommand = this.fileIn.map(function(file) {
						return '--js="' + file + '"';
					});
					command = 'java -jar "' + __dirname + '/google_closure_compiler-r1810.jar" ' + fileInCommand.join(' ') + ' --js_output_file="' + this.fileOut + '" ' + this.options.join(' ');
					break;
				case 'uglifyjs':
					command = '"' + __dirname  + '/../node_modules/uglify-js/bin/uglifyjs" --output "' + this.fileOut + '" --no-copyright "' + this.fileIn + '" ' + this.options.join(' ');
					break;
			}

			exec(command, { maxBuffer: this.buffer }, function (err, stdout, stderr) {
				//console.log(err);
				//console.log(stdout);
				//console.log(stderr);

				if(self.fileIn === self.tempFile) {
					// remove the temp concat file here
					_fs.unlinkSync(self.tempFile);
				}

				if(self.callback){
					if (err) {
						self.callback(err);
					} else {
						self.callback(null);
					}
				}
			});
		}
	};

	return minify;

})();

exports.version = '0.3.9';
exports.minify = minify;<commit_msg>Allow using YUI Compressor to compress JavaScript files<commit_after>var minify = (function(undefined) {

	var exec = require('child_process').exec,
			_fs = require('fs');

	var minify = function(options) {
		this.type = options.type;
		this.tempFile = new Date().getTime().toString();

		if(options.type == 'gcc' && typeof options.fileIn === 'string') {
			this.fileIn = [options.fileIn];
		}
		else if(typeof options.fileIn === 'string') {
			this.fileIn = options.fileIn;
		}

		if(typeof options.fileIn === 'object' && options.fileIn instanceof Array && options.type != 'gcc') {
			var out = options.fileIn.map(function(path) {
				return _fs.readFileSync(path, 'utf8');
			});

			_fs.writeFileSync(this.tempFile, out.join('\n'), 'utf8');

			this.fileIn = this.tempFile;
		}
		else if(typeof options.fileIn === 'object' && options.fileIn instanceof Array && options.type == 'gcc') {
			this.fileIn = options.fileIn;
		}

		this.fileOut = options.fileOut;
		this.options = options.options || [];
		this.buffer = options.buffer || 1000 * 1024;
		if (typeof options.callback !== 'undefined') {
			this.callback = options.callback;
		}

		this.compress();
	};

	minify.prototype = minify.fn = {
		type: null,
		fileIn: null,
		fileOut: null,
		callback: null,
		buffer: null, // with larger files you will need a bigger buffer for closure compiler
		compress: function() {
			var self = this, command;

			switch (this.type) {
				case 'yui':
				case 'yui-css':
					command = 'java -jar -Xss2048k "' + __dirname + '/yuicompressor-2.4.7.jar" "' + this.fileIn + '" -o "' + this.fileOut + '" --type css ' + this.options.join(' ');
					break;
				case 'yui-js':
					command = 'java -jar -Xss2048k "' + __dirname + '/yuicompressor-2.4.7.jar" "' + this.fileIn + '" -o "' + this.fileOut + '" --type js ' + this.options.join(' ');
					break;
				case 'gcc':
					var fileInCommand = this.fileIn.map(function(file) {
						return '--js="' + file + '"';
					});
					command = 'java -jar "' + __dirname + '/google_closure_compiler-r1810.jar" ' + fileInCommand.join(' ') + ' --js_output_file="' + this.fileOut + '" ' + this.options.join(' ');
					break;
				case 'uglifyjs':
					command = '"' + __dirname  + '/../node_modules/uglify-js/bin/uglifyjs" --output "' + this.fileOut + '" --no-copyright "' + this.fileIn + '" ' + this.options.join(' ');
					break;
			}

			exec(command, { maxBuffer: this.buffer }, function (err, stdout, stderr) {
				//console.log(err);
				//console.log(stdout);
				//console.log(stderr);

				if(self.fileIn === self.tempFile) {
					// remove the temp concat file here
					_fs.unlinkSync(self.tempFile);
				}

				if(self.callback){
					if (err) {
						self.callback(err);
					} else {
						self.callback(null);
					}
				}
			});
		}
	};

	return minify;

})();

exports.version = '0.3.9';
exports.minify = minify;<|endoftext|>


<commit_before>var fs     = require('fs')
  , events = require('events')
  , buffer = require('buffer')
  , http   = require('http')
  , url    = require('url')
  , path   = require('path')
  , mime   = require('mime')
  , util   = require('./node-static/util');

// Current version
var version = [0, 7, 3];

Server = function (root, options) {
    if (root && (typeof(root) === 'object')) { options = root; root = null }

    // resolve() doesn't normalize (to lowercase) drive letters on Windows
    this.root    = path.normalize(path.resolve(root || '.'));
    this.options = options || {};
    this.cache   = 3600;

    this.defaultHeaders  = {};
    this.options.headers = this.options.headers || {};

    if ('cache' in this.options) {
        if (typeof(this.options.cache) === 'number') {
            this.cache = this.options.cache;
        } else if (! this.options.cache) {
            this.cache = false;
        }
    }

    if ('serverInfo' in this.options) {
        this.serverInfo = this.options.serverInfo.toString();
    } else {
        this.serverInfo = 'node-static/' + version.join('.');
    }

    this.defaultHeaders['server'] = this.serverInfo;

    if (this.cache !== false) {
        this.defaultHeaders['cache-control'] = 'max-age=' + this.cache;
    }

    for (var k in this.defaultHeaders) {
        this.options.headers[k] = this.options.headers[k] ||
                                  this.defaultHeaders[k];
    }
};

Server.prototype.serveDir = function (pathname, req, res, finish) {
    var htmlIndex = path.join(pathname, 'index.html'),
        that = this;

    fs.stat(htmlIndex, function (e, stat) {
        if (!e) {
            var status = 200;
            var headers = {};
            var originalPathname = decodeURI(url.parse(req.url).pathname);
            if (originalPathname.length && originalPathname.charAt(originalPathname.length - 1) !== '/') {
                return finish(301, { 'Location': originalPathname + '/' });
            } else {
                that.respond(null, status, headers, [htmlIndex], stat, req, res, finish);
            }
        } else {
            // Stream a directory of files as a single file.
            fs.readFile(path.join(pathname, 'index.json'), function (e, contents) {
                if (e) { return finish(404, {}) }
                var index = JSON.parse(contents);
                streamFiles(index.files);
            });
        }
    });
    function streamFiles(files) {
        util.mstat(pathname, files, function (e, stat) {
            if (e) { return finish(404, {}) }
            that.respond(pathname, 200, {}, files, stat, req, res, finish);
        });
    }
};

Server.prototype.serveFile = function (pathname, status, headers, req, res) {
    var that = this;
    var promise = new(events.EventEmitter);

    pathname = this.resolve(pathname);

    fs.stat(pathname, function (e, stat) {
        if (e) {
            return promise.emit('error', e);
        }
        that.respond(null, status, headers, [pathname], stat, req, res, function (status, headers) {
            that.finish(status, headers, req, res, promise);
        });
    });
    return promise;
};

Server.prototype.finish = function (status, headers, req, res, promise, callback) {
    var result = {
        status:  status,
        headers: headers,
        message: http.STATUS_CODES[status]
    };

    headers['server'] = this.serverInfo;

    if (!status || status >= 400) {
        if (callback) {
            callback(result);
        } else {
            if (promise.listeners('error').length > 0) {
                promise.emit('error', result);
            }
            else {
              res.writeHead(status, headers);
              res.end();
            }
        }
    } else {
        // Don't end the request here, if we're streaming;
        // it's taken care of in `prototype.stream`.
        if (status !== 200 || req.method !== 'GET') {
            res.writeHead(status, headers);
            res.end();
        }
        callback && callback(null, result);
        promise.emit('success', result);
    }
};

Server.prototype.servePath = function (pathname, status, headers, req, res, finish) {
    var that = this,
        promise = new(events.EventEmitter);

    pathname = this.resolve(pathname);

    // Make sure we're not trying to access a
    // file outside of the root.
    if (pathname.indexOf(that.root) === 0) {
        fs.stat(pathname, function (e, stat) {
            if (e) {
                finish(404, {});
            } else if (stat.isFile()) {      // Stream a single file.
                that.respond(null, status, headers, [pathname], stat, req, res, finish);
            } else if (stat.isDirectory()) { // Stream a directory of files.
                that.serveDir(pathname, req, res, finish);
            } else {
                finish(400, {});
            }
        });
    } else {
        // Forbidden
        finish(403, {});
    }
    return promise;
};

Server.prototype.resolve = function (pathname) {
    return path.resolve(path.join(this.root, pathname));
};

Server.prototype.serve = function (req, res, callback) {
    var that    = this,
        promise = new(events.EventEmitter),
        pathname;

    var finish = function (status, headers) {
        that.finish(status, headers, req, res, promise, callback);
    };

    try {
        pathname = decodeURI(url.parse(req.url).pathname);
    }
    catch(e) {
        return process.nextTick(function() {
            return finish(400, {});
        });
    }

    process.nextTick(function () {
        that.servePath(pathname, 200, {}, req, res, finish).on('success', function (result) {
            promise.emit('success', result);
        }).on('error', function (err) {
            promise.emit('error');
        });
    });
    if (! callback) { return promise }
};

/* Check if we should consider sending a gzip version of the file based on the
 * file content type and client's Accept-Encoding header value.
 */
Server.prototype.gzipOk = function(req, contentType) {
    var enable = this.options.gzip;
    if(enable &&
        (typeof enable === 'boolean' ||
            (contentType && (enable instanceof RegExp) && enable.test(contentType)))) {
        var acceptEncoding = req.headers['accept-encoding'];
        return acceptEncoding && acceptEncoding.indexOf("gzip") >= 0;
    }
    return false;
}

/* Send a gzipped version of the file if the options and the client indicate gzip is enabled and
 * we find a .gz file mathing the static resource requested.
 */
Server.prototype.respondGzip = function(pathname, status, contentType, _headers, files, stat, req, res, finish) {
    var that = this;
    if(files.length == 1 && this.gzipOk(req, contentType)) {
        var gzFile = files[0] + ".gz";
        fs.stat(gzFile, function(e, gzStat) {
            if(!e && gzStat.isFile()) {
                //console.log('Serving', gzFile, 'to gzip-capable client instead of', files[0], 'new size is', gzStat.size, 'uncompressed size', stat.size);
                var vary = _headers['Vary'];
                _headers['Vary'] = (vary && vary != 'Accept-Encoding'?vary+', ':'')+'Accept-Encoding';
                _headers['Content-Encoding'] = 'gzip';
                stat.size = gzStat.size;
                files = [gzFile];
            } else {
                //console.log('gzip file not found or error finding it', gzFile, String(e), stat.isFile());
            }
            that.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
        });
    } else {
        // Client doesn't want gzip or we're sending multiple files
        that.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
    }
}

Server.prototype.parseByteRange = function(req, stat) {

    var byteRange   = {
            from:0,
            to:0,
            valid: false
        },
        rangeHeader = req.headers['range'],
        flavor = 'bytes=';

    if (rangeHeader) {
        if (rangeHeader.indexOf(flavor) == 0 && rangeHeader.indexOf(',') == -1) {
            /* Parse */
            rangeHeader = rangeHeader.substr(flavor.length).split('-');
            byteRange.from = parseInt(rangeHeader[0]);
            byteRange.to = parseInt(rangeHeader[1]);

            /* Replace empty fields of differential requests by absolute values */
            if (isNaN(byteRange.from) && !isNaN(byteRange.to)) {
                byteRange.from = stat.size - byteRange.to;
                byteRange.to = stat.size - 1;
            } else if (!isNaN(byteRange.from) && isNaN(byteRange.to)) {
                byteRange.to = stat.size - 1;
            }

            /* General byte range validation */
            if (!isNaN(byteRange.from) && !!byteRange.to && 0 <= byteRange.from < byteRange.to) {
                byteRange.valid = true;
            } else {
                console.warn("Request contains invalid range header: ", rangeHeader);
            }
        } else {
            console.warn("Request contains unsupported range header: ", rangeHeader);
        }
    }
    return byteRange;
}

Server.prototype.respondNoGzip = function (pathname, status, contentType, _headers, files, stat, req, res, finish) {
    var mtime           = Date.parse(stat.mtime),
        key             = pathname || files[0],
        headers         = {},
        clientETag      = req.headers['if-none-match'],
        clientMTime     = Date.parse(req.headers['if-modified-since']),
        startByte       = 0,
        length          = stat.size,
        byteRange       = this.parseByteRange(req, stat);

    /* Handle byte ranges */
    if (files.length == 1 && byteRange.valid) {
        if (byteRange.to < length) {
            // Note: HTTP Range param is inclusive
            startByte = byteRange.from;
            length = byteRange.to - byteRange.from + 1;
            status = 206;
        } else {
            byteRange.valid = false;
            console.warn("Range request exceeds file boundaries, goes until byte no", byteRange.to, "against file size of", length, "bytes");
        }
    }

    /* In any case, check for unhandled byte range headers */
    if (!byteRange.valid && req.headers['range']) {
        console.error(new Error("Range request present but invalid, might serve whole file instead"));
    }

    // Copy default headers
    for (var k in this.options.headers) {  headers[k] = this.options.headers[k] }
    // Copy custom headers
    for (var k in _headers) { headers[k] = _headers[k] }

    headers['Etag']          = JSON.stringify([stat.ino, stat.size, mtime].join('-'));
    headers['Date']          = new(Date)().toUTCString();
    headers['Last-Modified'] = new(Date)(stat.mtime).toUTCString();
    headers['Content-Type']   = contentType;
    headers['Content-Length'] = length;

    for (var k in _headers) { headers[k] = _headers[k] }

    // Conditional GET
    // If the "If-Modified-Since" or "If-None-Match" headers
    // match the conditions, send a 304 Not Modified.
    if ((clientMTime  || clientETag) &&
        (!clientETag  || clientETag === headers['Etag']) &&
        (!clientMTime || clientMTime >= mtime)) {
        // 304 response should not contain entity headers
        ['Content-Encoding',
         'Content-Language',
         'Content-Length',
         'Content-Location',
         'Content-MD5',
         'Content-Range',
         'Content-Type',
         'Expires',
         'Last-Modified'].forEach(function(entityHeader) {
            delete headers[entityHeader];
        });
        finish(304, headers);
    } else {

        res.writeHead(status, headers);

        this.stream(key, files, new(buffer.Buffer)(length), startByte, res, function (e, buffer) {
            if (e) { return finish(500, {}) }
            finish(status, headers);
        });
    }
};

Server.prototype.respond = function (pathname, status, _headers, files, stat, req, res, finish) {
    var contentType = _headers['Content-Type'] ||
                      mime.lookup(files[0]) ||
                      'application/octet-stream';

    if(this.options.gzip) {
        this.respondGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
    } else {
        this.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
    }
}

Server.prototype.stream = function (pathname, files, buffer, startByte, res, callback) {

    (function streamFile(files, offset) {
        var file = files.shift();

        if (file) {
            file = path.resolve(file) === path.normalize(file)  ? file : path.join(pathname || '.', file);

            // Stream the file to the client
            fs.createReadStream(file, {
                flags: 'r',
                mode: 0666,
                start: startByte,
                end: startByte + buffer.length - 1
            }).on('data', function (chunk) {
                // Bounds check the incoming chunk and offset, as copying
                // a buffer from an invalid offset will throw an error and crash
                if (chunk.length && offset < buffer.length && offset >= 0) {
                    chunk.copy(buffer, offset);
                    offset += chunk.length;
                }
            }).on('close', function () {
                streamFile(files, offset);
            }).on('error', function (err) {
                callback(err);
                console.error(err);
            }).pipe(res, { end: false });
        } else {
            res.end();
            callback(null, buffer, offset);
        }
    })(files.slice(0), 0);
};

// Exports
exports.Server       = Server;
exports.version      = version;
exports.mime         = mime;

<commit_msg>Code style<commit_after>var fs     = require('fs')
  , events = require('events')
  , buffer = require('buffer')
  , http   = require('http')
  , url    = require('url')
  , path   = require('path')
  , mime   = require('mime')
  , util   = require('./node-static/util');

// Current version
var version = [0, 7, 3];

Server = function (root, options) {
    if (root && (typeof(root) === 'object')) { options = root; root = null }

    // resolve() doesn't normalize (to lowercase) drive letters on Windows
    this.root    = path.normalize(path.resolve(root || '.'));
    this.options = options || {};
    this.cache   = 3600;

    this.defaultHeaders  = {};
    this.options.headers = this.options.headers || {};

    if ('cache' in this.options) {
        if (typeof(this.options.cache) === 'number') {
            this.cache = this.options.cache;
        } else if (! this.options.cache) {
            this.cache = false;
        }
    }

    if ('serverInfo' in this.options) {
        this.serverInfo = this.options.serverInfo.toString();
    } else {
        this.serverInfo = 'node-static/' + version.join('.');
    }

    this.defaultHeaders['server'] = this.serverInfo;

    if (this.cache !== false) {
        this.defaultHeaders['cache-control'] = 'max-age=' + this.cache;
    }

    for (var k in this.defaultHeaders) {
        this.options.headers[k] = this.options.headers[k] ||
                                  this.defaultHeaders[k];
    }
};

Server.prototype.serveDir = function (pathname, req, res, finish) {
    var htmlIndex = path.join(pathname, 'index.html'),
        that = this;

    fs.stat(htmlIndex, function (e, stat) {
        if (!e) {
            var status = 200;
            var headers = {};
            var originalPathname = decodeURI(url.parse(req.url).pathname);
            if (originalPathname.length && originalPathname.charAt(originalPathname.length - 1) !== '/') {
                return finish(301, { 'Location': originalPathname + '/' });
            } else {
                that.respond(null, status, headers, [htmlIndex], stat, req, res, finish);
            }
        } else {
            // Stream a directory of files as a single file.
            fs.readFile(path.join(pathname, 'index.json'), function (e, contents) {
                if (e) { return finish(404, {}) }
                var index = JSON.parse(contents);
                streamFiles(index.files);
            });
        }
    });
    function streamFiles(files) {
        util.mstat(pathname, files, function (e, stat) {
            if (e) { return finish(404, {}) }
            that.respond(pathname, 200, {}, files, stat, req, res, finish);
        });
    }
};

Server.prototype.serveFile = function (pathname, status, headers, req, res) {
    var that = this;
    var promise = new(events.EventEmitter);

    pathname = this.resolve(pathname);

    fs.stat(pathname, function (e, stat) {
        if (e) {
            return promise.emit('error', e);
        }
        that.respond(null, status, headers, [pathname], stat, req, res, function (status, headers) {
            that.finish(status, headers, req, res, promise);
        });
    });
    return promise;
};

Server.prototype.finish = function (status, headers, req, res, promise, callback) {
    var result = {
        status:  status,
        headers: headers,
        message: http.STATUS_CODES[status]
    };

    headers['server'] = this.serverInfo;

    if (!status || status >= 400) {
        if (callback) {
            callback(result);
        } else {
            if (promise.listeners('error').length > 0) {
                promise.emit('error', result);
            }
            else {
              res.writeHead(status, headers);
              res.end();
            }
        }
    } else {
        // Don't end the request here, if we're streaming;
        // it's taken care of in `prototype.stream`.
        if (status !== 200 || req.method !== 'GET') {
            res.writeHead(status, headers);
            res.end();
        }
        callback && callback(null, result);
        promise.emit('success', result);
    }
};

Server.prototype.servePath = function (pathname, status, headers, req, res, finish) {
    var that = this,
        promise = new(events.EventEmitter);

    pathname = this.resolve(pathname);

    // Make sure we're not trying to access a
    // file outside of the root.
    if (pathname.indexOf(that.root) === 0) {
        fs.stat(pathname, function (e, stat) {
            if (e) {
                finish(404, {});
            } else if (stat.isFile()) {      // Stream a single file.
                that.respond(null, status, headers, [pathname], stat, req, res, finish);
            } else if (stat.isDirectory()) { // Stream a directory of files.
                that.serveDir(pathname, req, res, finish);
            } else {
                finish(400, {});
            }
        });
    } else {
        // Forbidden
        finish(403, {});
    }
    return promise;
};

Server.prototype.resolve = function (pathname) {
    return path.resolve(path.join(this.root, pathname));
};

Server.prototype.serve = function (req, res, callback) {
    var that    = this,
        promise = new(events.EventEmitter),
        pathname;

    var finish = function (status, headers) {
        that.finish(status, headers, req, res, promise, callback);
    };

    try {
        pathname = decodeURI(url.parse(req.url).pathname);
    }
    catch(e) {
        return process.nextTick(function() {
            return finish(400, {});
        });
    }

    process.nextTick(function () {
        that.servePath(pathname, 200, {}, req, res, finish).on('success', function (result) {
            promise.emit('success', result);
        }).on('error', function (err) {
            promise.emit('error');
        });
    });
    if (! callback) { return promise }
};

/* Check if we should consider sending a gzip version of the file based on the
 * file content type and client's Accept-Encoding header value.
 */
Server.prototype.gzipOk = function(req, contentType) {
    var enable = this.options.gzip;
    if(enable &&
        (typeof enable === 'boolean' ||
            (contentType && (enable instanceof RegExp) && enable.test(contentType)))) {
        var acceptEncoding = req.headers['accept-encoding'];
        return acceptEncoding && acceptEncoding.indexOf("gzip") >= 0;
    }
    return false;
}

/* Send a gzipped version of the file if the options and the client indicate gzip is enabled and
 * we find a .gz file mathing the static resource requested.
 */
Server.prototype.respondGzip = function(pathname, status, contentType, _headers, files, stat, req, res, finish) {
    var that = this;
    if(files.length == 1 && this.gzipOk(req, contentType)) {
        var gzFile = files[0] + ".gz";
        fs.stat(gzFile, function(e, gzStat) {
            if(!e && gzStat.isFile()) {
                //console.log('Serving', gzFile, 'to gzip-capable client instead of', files[0], 'new size is', gzStat.size, 'uncompressed size', stat.size);
                var vary = _headers['Vary'];
                _headers['Vary'] = (vary && vary != 'Accept-Encoding'?vary+', ':'')+'Accept-Encoding';
                _headers['Content-Encoding'] = 'gzip';
                stat.size = gzStat.size;
                files = [gzFile];
            } else {
                //console.log('gzip file not found or error finding it', gzFile, String(e), stat.isFile());
            }
            that.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
        });
    } else {
        // Client doesn't want gzip or we're sending multiple files
        that.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
    }
}

Server.prototype.parseByteRange = function(req, stat) {
    var byteRange = {
      from: 0,
      to: 0,
      valid: false
    }

    var rangeHeader = req.headers['range'];
    var flavor = 'bytes=';

    if (rangeHeader) {
        if (rangeHeader.indexOf(flavor) == 0 && rangeHeader.indexOf(',') == -1) {
            /* Parse */
            rangeHeader = rangeHeader.substr(flavor.length).split('-');
            byteRange.from = parseInt(rangeHeader[0]);
            byteRange.to = parseInt(rangeHeader[1]);

            /* Replace empty fields of differential requests by absolute values */
            if (isNaN(byteRange.from) && !isNaN(byteRange.to)) {
                byteRange.from = stat.size - byteRange.to;
                byteRange.to = stat.size - 1;
            } else if (!isNaN(byteRange.from) && isNaN(byteRange.to)) {
                byteRange.to = stat.size - 1;
            }

            /* General byte range validation */
            if (!isNaN(byteRange.from) && !!byteRange.to && 0 <= byteRange.from < byteRange.to) {
                byteRange.valid = true;
            } else {
                console.warn("Request contains invalid range header: ", rangeHeader);
            }
        } else {
            console.warn("Request contains unsupported range header: ", rangeHeader);
        }
    }
    return byteRange;
}

Server.prototype.respondNoGzip = function (pathname, status, contentType, _headers, files, stat, req, res, finish) {
    var mtime           = Date.parse(stat.mtime),
        key             = pathname || files[0],
        headers         = {},
        clientETag      = req.headers['if-none-match'],
        clientMTime     = Date.parse(req.headers['if-modified-since']),
        startByte       = 0,
        length          = stat.size,
        byteRange       = this.parseByteRange(req, stat);

    /* Handle byte ranges */
    if (files.length == 1 && byteRange.valid) {
        if (byteRange.to < length) {
            // Note: HTTP Range param is inclusive
            startByte = byteRange.from;
            length = byteRange.to - byteRange.from + 1;
            status = 206;
        } else {
            byteRange.valid = false;
            console.warn("Range request exceeds file boundaries, goes until byte no", byteRange.to, "against file size of", length, "bytes");
        }
    }

    /* In any case, check for unhandled byte range headers */
    if (!byteRange.valid && req.headers['range']) {
        console.error(new Error("Range request present but invalid, might serve whole file instead"));
    }

    // Copy default headers
    for (var k in this.options.headers) {  headers[k] = this.options.headers[k] }
    // Copy custom headers
    for (var k in _headers) { headers[k] = _headers[k] }

    headers['Etag']          = JSON.stringify([stat.ino, stat.size, mtime].join('-'));
    headers['Date']          = new(Date)().toUTCString();
    headers['Last-Modified'] = new(Date)(stat.mtime).toUTCString();
    headers['Content-Type']   = contentType;
    headers['Content-Length'] = length;

    for (var k in _headers) { headers[k] = _headers[k] }

    // Conditional GET
    // If the "If-Modified-Since" or "If-None-Match" headers
    // match the conditions, send a 304 Not Modified.
    if ((clientMTime  || clientETag) &&
        (!clientETag  || clientETag === headers['Etag']) &&
        (!clientMTime || clientMTime >= mtime)) {
        // 304 response should not contain entity headers
        ['Content-Encoding',
         'Content-Language',
         'Content-Length',
         'Content-Location',
         'Content-MD5',
         'Content-Range',
         'Content-Type',
         'Expires',
         'Last-Modified'].forEach(function(entityHeader) {
            delete headers[entityHeader];
        });
        finish(304, headers);
    } else {

        res.writeHead(status, headers);

        this.stream(key, files, new(buffer.Buffer)(length), startByte, res, function (e, buffer) {
            if (e) { return finish(500, {}) }
            finish(status, headers);
        });
    }
};

Server.prototype.respond = function (pathname, status, _headers, files, stat, req, res, finish) {
    var contentType = _headers['Content-Type'] ||
                      mime.lookup(files[0]) ||
                      'application/octet-stream';

    if(this.options.gzip) {
        this.respondGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
    } else {
        this.respondNoGzip(pathname, status, contentType, _headers, files, stat, req, res, finish);
    }
}

Server.prototype.stream = function (pathname, files, buffer, startByte, res, callback) {

    (function streamFile(files, offset) {
        var file = files.shift();

        if (file) {
            file = path.resolve(file) === path.normalize(file)  ? file : path.join(pathname || '.', file);

            // Stream the file to the client
            fs.createReadStream(file, {
                flags: 'r',
                mode: 0666,
                start: startByte,
                end: startByte + buffer.length - 1
            }).on('data', function (chunk) {
                // Bounds check the incoming chunk and offset, as copying
                // a buffer from an invalid offset will throw an error and crash
                if (chunk.length && offset < buffer.length && offset >= 0) {
                    chunk.copy(buffer, offset);
                    offset += chunk.length;
                }
            }).on('close', function () {
                streamFile(files, offset);
            }).on('error', function (err) {
                callback(err);
                console.error(err);
            }).pipe(res, { end: false });
        } else {
            res.end();
            callback(null, buffer, offset);
        }
    })(files.slice(0), 0);
};

// Exports
exports.Server       = Server;
exports.version      = version;
exports.mime         = mime;

<|endoftext|>


<commit_before>// Functions to apply limits to nodes

'use strict';

var nodeError = require('../limits/error');
var estimates = require('../limits/estimates');

function limitNumberOfRows(node, context, limit, callback) {
    if (!Number.isFinite(limit.value)) {
        context.logError('Limit for number of rows is not defined');
        return callback(null);
    }
    estimates.estimatedNumberOfRows(node, context, function(err, outputRows) {
        if (err) {
            // if estimation is not available don't abort the analysis
            context.logError(err);
            err = null;
        } else {
            if (outputRows > limit.value) {
                err = nodeError(node, [limit.message]);
            }
        }
        return callback(err);
    });
}

function limitInputRows(node, input, context, limit, callback) {
    if (!Number.isFinite(limit.value)) {
        context.logError('Limit for input rows is not defined');
        return callback(null);
    }
    estimates.estimatedNumberOfRows(node[input], context, function(err, numRows) {
        if (err) {
            context.logError(err);
            err = null;
        } else {
            if (numRows > limit.value) {
                err = nodeError(node, [limit.message]);
            }
        }
        return callback(err);
    });
}

function limitInputRowsAndAvgGroupedRows(node, input, categoriesData, context, lims, callback) {
    var limit = lims.rowsLimit;
    var groupedLimit = lims.groupedRowsLimit;
    var limitPresent = Number.isFinite(limit.value);
    var groupedLimitPresent = Number.isFinite(groupedLimit.value);
    if (!limitPresent && !groupedLimitPresent) {
        context.logError('limitInputRowsAndAvgGroupedRows called without any passed limit');
        return callback(null);
    } else if (groupedLimitPresent && !_verifyCategoryData(categoriesData)) {
        context.logError('Categories data must contain source and column name keys');
        return callback(null);
    }

    function check(inputRows, numCategories) {
        var numberOfRowsPerCategory = inputRows/(numCategories || 1);
        var messages = [];
        if (limitPresent && inputRows > limit.value) {
            messages.push(limit.message);
        }
        if (groupedLimitPresent && numberOfRowsPerCategory > groupedLimit.value) {
            messages.push(groupedLimit.message);
        }
        return callback(nodeError(node, messages));
    }

    _limitInputRowsWithCategories(node, input, categoriesData, context, check, callback);
}

function limitInputRowsMultipliedByCategories(node, input, categoryTarget, context, limit, callback) {
    var limitPresent = Number.isFinite(limit.value);
    if (!limitPresent) {
        context.logError('Limit for input rows multiplied by categories is not defined');
        return callback(null);
    } else if (!_verifyCategoryData(categoryTarget)) {
        context.logError('Categories data must contain source and column name keys');
        return callback(null);
    }

    function check(inputRows, numCategories) {
        var maxNumberOfPossibleRows = inputRows*numCategories || 1;
        var messages = [];
        if (limitPresent && maxNumberOfPossibleRows > limit.value) {
            messages.push(limit.message);
        }
        return callback(nodeError(node, messages));
    }

    _limitInputRowsWithCategories(node, input, categoryTarget, context, check, callback);

}

function _limitInputRowsWithCategories(node, input, categoryTarget, context, checkFunction, callback) {
    estimates.estimatedNumberOfRows(node[input], context, function(err, inputRows) {
        if (err) {
            context.logError(err);
            // something went wrong... lack of stats? anyway, we'll let the analysis pass
            return callback(null);
        } else {
            var numCategories = 1;
            if (categoryTarget.column) {
                estimates.numberOfDistinctValues(
                    node[categoryTarget.source], context, categoryTarget.column,
                    function(err, num) {
                        if (err) {
                            context.logError(err);
                        } else {
                            numCategories = num;
                        }
                        checkFunction(inputRows, numCategories);
                    }
                );
             }
             else {
               checkFunction(inputRows, numCategories);
             }
        }
    });
}

function _verifyCategoryData(categoryTarget) {
    // Object could be empty but if not we check for required keys
    if (!categoryTarget) {
        return true;
    } else {
        return categoryTarget.hasOwnProperty('source') && categoryTarget.hasOwnProperty('column');
    }
}

module.exports = {
    limitNumberOfRows: limitNumberOfRows,
    limitInputRows: limitInputRows,
    limitInputRowsAndAvgGroupedRows: limitInputRowsAndAvgGroupedRows,
    limitInputRowsMultipliedByCategories: limitInputRowsMultipliedByCategories
};
<commit_msg>fix message logs<commit_after>// Functions to apply limits to nodes

'use strict';

var nodeError = require('../limits/error');
var estimates = require('../limits/estimates');

function limitNumberOfRows(node, context, limit, callback) {
    if (!Number.isFinite(limit.value)) {
        context.logError({message: 'Limit for number of rows is not defined'});
        return callback(null);
    }
    estimates.estimatedNumberOfRows(node, context, function(err, outputRows) {
        if (err) {
            // if estimation is not available don't abort the analysis
            context.logError(err);
            err = null;
        } else {
            if (outputRows > limit.value) {
                err = nodeError(node, [limit.message]);
            }
        }
        return callback(err);
    });
}

function limitInputRows(node, input, context, limit, callback) {
    if (!Number.isFinite(limit.value)) {
        context.logError({message: 'Limit for input rows is not defined'});
        return callback(null);
    }
    estimates.estimatedNumberOfRows(node[input], context, function(err, numRows) {
        if (err) {
            context.logError(err);
            err = null;
        } else {
            if (numRows > limit.value) {
                err = nodeError(node, [limit.message]);
            }
        }
        return callback(err);
    });
}

function limitInputRowsAndAvgGroupedRows(node, input, categoriesData, context, lims, callback) {
    var limit = lims.rowsLimit;
    var groupedLimit = lims.groupedRowsLimit;
    var limitPresent = Number.isFinite(limit.value);
    var groupedLimitPresent = Number.isFinite(groupedLimit.value);
    if (!limitPresent && !groupedLimitPresent) {
        context.logError({
            message: 'limitInputRowsAndAvgGroupedRows called without any passed limit'
        });
        return callback(null);
    } else if (groupedLimitPresent && !_verifyCategoryData(categoriesData)) {
        context.logError({message: 'Categories data must contain source and column name keys'});
        return callback(null);
    }

    function check(inputRows, numCategories) {
        var numberOfRowsPerCategory = inputRows/(numCategories || 1);
        var messages = [];
        if (limitPresent && inputRows > limit.value) {
            messages.push(limit.message);
        }
        if (groupedLimitPresent && numberOfRowsPerCategory > groupedLimit.value) {
            messages.push(groupedLimit.message);
        }
        return callback(nodeError(node, messages));
    }

    _limitInputRowsWithCategories(node, input, categoriesData, context, check, callback);
}

function limitInputRowsMultipliedByCategories(node, input, categoryTarget, context, limit, callback) {
    var limitPresent = Number.isFinite(limit.value);
    if (!limitPresent) {
        context.logError({message: 'Limit for input rows multiplied by categories is not defined'});
        return callback(null);
    } else if (!_verifyCategoryData(categoryTarget)) {
        context.logError({message: 'Categories data must contain source and column name keys'});
        return callback(null);
    }

    function check(inputRows, numCategories) {
        var maxNumberOfPossibleRows = inputRows*numCategories || 1;
        var messages = [];
        if (limitPresent && maxNumberOfPossibleRows > limit.value) {
            messages.push(limit.message);
        }
        return callback(nodeError(node, messages));
    }

    _limitInputRowsWithCategories(node, input, categoryTarget, context, check, callback);

}

function _limitInputRowsWithCategories(node, input, categoryTarget, context, checkFunction, callback) {
    estimates.estimatedNumberOfRows(node[input], context, function(err, inputRows) {
        if (err) {
            context.logError(err);
            // something went wrong... lack of stats? anyway, we'll let the analysis pass
            return callback(null);
        } else {
            var numCategories = 1;
            if (categoryTarget.column) {
                estimates.numberOfDistinctValues(
                    node[categoryTarget.source], context, categoryTarget.column,
                    function(err, num) {
                        if (err) {
                            context.logError(err);
                        } else {
                            numCategories = num;
                        }
                        checkFunction(inputRows, numCategories);
                    }
                );
             }
             else {
               checkFunction(inputRows, numCategories);
             }
        }
    });
}

function _verifyCategoryData(categoryTarget) {
    // Object could be empty but if not we check for required keys
    if (!categoryTarget) {
        return true;
    } else {
        return categoryTarget.hasOwnProperty('source') && categoryTarget.hasOwnProperty('column');
    }
}

module.exports = {
    limitNumberOfRows: limitNumberOfRows,
    limitInputRows: limitInputRows,
    limitInputRowsAndAvgGroupedRows: limitInputRowsAndAvgGroupedRows,
    limitInputRowsMultipliedByCategories: limitInputRowsMultipliedByCategories
};
<|endoftext|>


<commit_before>var child_process = require('child_process');
var TCP = process.binding('tcp_wrap').TCP;
var _ = require('../util.js');

var MAX_WORKERS = 10;
var WORKER_PATH = __dirname + '/worker.js';
var GRACE_EXIT_TIME = 2000;//2s

var server;

var timer;
function destroy(){
    if(timer) return;
    server.close();
    workers.forEach(function(worker){
        worker.kill();
    });
    timer = setTimeout(function(){
        process.exit(0);
    }, GRACE_EXIT_TIME);
}

var handles = [];
function send(handle){
    if(workers.length){
        var worker = workers.shift();
        worker.send({'handle' : true}, handle);
        //handle.close();
    } else {
        handles.push(handle);
    }
}

var workers = [];

function create(opt){
    if(timer) return;
    var worker = child_process.fork(WORKER_PATH, opt);
    worker.on('exit', function() {
        create(opt);
    });
    workers.push(worker);
    if(handles.length){
        send(handles.shift());
    }
}

function createWorker(script) {
    for(var i = 0; i < MAX_WORKERS; i++){
        create(script);
    }
}

function createServer() {
    var argv = _.parseArgs(process.argv);
    var host = argv['host'] || '0.0.0.0';
    var port = argv['port'] || '8080';
    var BACK_LOG = 128;
    //create worker
    createWorker(process.argv.slice(2));
    server = new TCP;
    server.bind(host, port);
    server.onconnection = send;
    server.listen(BACK_LOG);
    process.stderr.write('[oo] master is running...\n');
    process.on('SIGINT' , destroy);
    process.on('SIGTERM' , destroy);
}

try {
    //main
    createServer();
} catch (e) {
    process.stderr.write('[oo] Exception\n' + e.toString());
}
<commit_msg>bugfix windows下打开了node窗口的问题<commit_after>var child_process = require('child_process');
var TCP = process.binding('tcp_wrap').TCP;
var _ = require('../util.js');

var MAX_WORKERS = 10;
var WORKER_PATH = __dirname + '/worker.js';
var GRACE_EXIT_TIME = 2000;//2s

var server;

var timer;
function destroy(){
    if(timer) return;
    server.close();
    workers.forEach(function(worker){
        worker.kill();
    });
    timer = setTimeout(function(){
        process.exit(0);
    }, GRACE_EXIT_TIME);
}

var handles = [];
function send(handle){
    if(workers.length){
        var worker = workers.shift();
        worker.send({'handle' : true}, handle);
        //handle.close();
    } else {
        handles.push(handle);
    }
}

var workers = [];

function create(opt){
    if(timer) return;
    var worker = child_process.fork(WORKER_PATH, opt, {cwd : __dirname, detached: true});
    worker.on('exit', function() {
        create(opt);
    });
    workers.push(worker);
    if(handles.length){
        send(handles.shift());
    }
}

function createWorker(script) {
    for(var i = 0; i < MAX_WORKERS; i++){
        create(script);
    }
}

function createServer() {
    var argv = _.parseArgs(process.argv);
    var host = argv['host'] || '0.0.0.0';
    var port = argv['port'] || '8080';
    var BACK_LOG = 128;
    //create worker
    createWorker(process.argv.slice(2));
    server = new TCP;
    server.bind(host, port);
    server.onconnection = send;
    server.listen(BACK_LOG);
    process.stderr.write('[oo] master is running...\n');
    process.on('SIGINT' , destroy);
    process.on('SIGTERM' , destroy);
}

try {
    //main
    createServer();
} catch (e) {
    process.stderr.write('[oo] Exception\n' + e.toString());
}
<|endoftext|>