<commit_before>var saml20                = require('saml').Saml20;
var zlib                  = require('zlib');
var xmldom                = require('xmldom');
var SignedXml             = require('xml-crypto').SignedXml;
var xpath                 = require('xpath');
var xtend                 = require('xtend');

var templates             = require('./templates');
var encoders              = require('./encoders');
var PassportProfileMapper = require('./claims/PassportProfileMapper');

function getSamlRequest(samlRequest, callback) {
  if (!samlRequest) return callback();

  var input = new Buffer(samlRequest, 'base64');
  if (input[0] === 60) {  // open tag
    // content is just encoded, not zipped
    var xml = new xmldom.DOMParser().parseFromString(input.toString().trim());
    if (!xml) return callback(new Error('Invalid SAML Request'));
    callback(null, xml);
  } else {
    zlib.inflateRaw(input, function(err, buffer) {
      if (err) return callback(err);

      var xml = new xmldom.DOMParser().parseFromString(buffer.toString().trim());
      if (!xml) return callback(new Error('Invalid SAML Request'));
      callback(null, xml);
    });
  }
}

function buildSamlResponse(options) {
  var SAMLResponse = templates.samlresponse({
    id:             '_' + generateUniqueID(),
    instant:        generateInstant(),
    destination:    options.destination || options.audience,
    inResponseTo:   options.inResponseTo,
    issuer:         options.issuer,
    samlStatusCode: options.samlStatusCode,
    samlStatusMessage: options.samlStatusMessage,
    assertion:      options.signedAssertion || ''
  });

  if (options.signResponse) {
    var cannonicalized = SAMLResponse
      .replace(/\r\n/g, '')
      .replace(/\n/g,'')
      .replace(/>(\s*)</g, '><') //unindent
      .trim();

    var sig = new SignedXml(null, {
      signatureAlgorithm: algorithms.signature[options.signatureAlgorithm],
      idAttribute: 'ID'
    });

    sig.addReference(
      "//*[local-name(.)='Response' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:protocol']",
      ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/2001/10/xml-exc-c14n#"],
      algorithms.digest[options.digestAlgorithm]);

    sig.signingKey = options.key;

    var pem = encoders.removeHeaders(options.cert);
    sig.keyInfoProvider = {
      getKeyInfo: function () {
        return "<X509Data><X509Certificate>" + pem + "</X509Certificate></X509Data>";
      }
    };

    sig.computeSignature(cannonicalized);
    SAMLResponse = sig.getSignedXml();
  }

  return SAMLResponse;
}

function getSamlResponse(options, user, callback) {
  options.profileMapper = options.profileMapper || PassportProfileMapper;

  var profileMap = options.profileMapper(user);
  var claims = profileMap.getClaims(options);
  var ni = profileMap.getNameIdentifier(options);

  if (!ni || !ni.nameIdentifier) {
    var error = new Error('No attribute was found to generate the nameIdentifier. We tried with: ' + (options.nameIdentifierProbes || []).join(', '));
    error.context = { user: user };
    return callback(error);
  }

  saml20.create({
    signatureAlgorithm:   options.signatureAlgorithm,
    digestAlgorithm:      options.digestAlgorithm,
    cert:                 options.cert,
    key:                  options.key,
    issuer:               options.issuer,
    lifetimeInSeconds:    options.lifetimeInSeconds || 3600,
    audiences:            options.audience,
    attributes:           claims,
    nameIdentifier:       ni.nameIdentifier,
    nameIdentifierFormat: ni.nameIdentifierFormat || options.nameIdentifierFormat,
    recipient:            options.recipient,
    inResponseTo:         options.inResponseTo,
    authnContextClassRef: options.authnContextClassRef,
    encryptionPublicKey:  options.encryptionPublicKey,
    encryptionCert:       options.encryptionCert,
    sessionIndex:         options.sessionIndex
  }, function (err, signedAssertion) {
    if (err) return callback(err);

    options.signedAssertion = signedAssertion;
    options.samlStatusCode = options.samlStatusCode || 'urn:oasis:names:tc:SAML:2.0:status:Success';
    var SAMLResponse = buildSamlResponse(options);
    callback(null, SAMLResponse);
  });
}

function getLogoutResponse (options, callback) {
  var logoutResponse = templates.logoutresponse({
    id:             '_' + generateUniqueID(),
    instant:        generateInstant(),
    destination:    options.destination || options.audience,
    inResponseTo:   options.inResponseTo,
    issuer:         options.issuer,
    samlStatusCode: options.samlStatusCode || 'urn:oasis:names:tc:SAML:2.0:status:Success'
  });

  if (options.signResponse) {
    // sign response and add embedded signature
    var cannonicalized = logoutResponse
      .replace(/\r\n/g, '')
      .replace(/\n/g,'')
      .replace(/>(\s*)</g, '><') //unindent
      .trim();

    var sig = new SignedXml(null, { signatureAlgorithm: algorithms.signature[options.signatureAlgorithm], idAttribute: 'ID' });
    sig.addReference("//*[local-name(.)='LogoutResponse' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:protocol']",
      ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'],
      algorithms.digest[options.digestAlgorithm]);

    sig.signingKey = options.key;

    var pem = encoders.removeHeaders(options.cert);
    sig.keyInfoProvider = {
      getKeyInfo: function () {
        return '<X509Data><X509Certificate>' + pem + '</X509Certificate></X509Data>';
      }
    };

    sig.computeSignature(cannonicalized);
    logoutResponse = sig.getSignedXml();
  }

  callback(null, logoutResponse);
}

function generateUniqueID() {
  var chars = "abcdef0123456789";
  var uniqueID = "";
  for (var i = 0; i < 20; i++) {
    uniqueID += chars.substr(Math.floor((Math.random()*15)), 1);
  }
  return uniqueID;
}

function generateInstant() {
  var date = new Date();
  return date.getUTCFullYear() + '-' + ('0' + (date.getUTCMonth()+1)).slice(-2) + '-' + ('0' + date.getUTCDate()).slice(-2) + 'T' + ('0' + date.getUTCHours()).slice(-2) + ":" + ('0' + date.getUTCMinutes()).slice(-2) + ":" + ('0' + date.getUTCSeconds()).slice(-2) + "Z";
}

var algorithms = {
  signature: {
    'rsa-sha256': 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
    'rsa-sha1':  'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
  },
  digest: {
    'sha256': 'http://www.w3.org/2001/04/xmlenc#sha256',
    'sha1': 'http://www.w3.org/2000/09/xmldsig#sha1'
  }
};

/**
 * SAML Protocol middleware.
 *
 * This middleware creates a SAML endpoint based on the user logged in identity.
 *
 * options:
 * - profileMapper(profile) a ProfileMapper implementation to convert a user profile to claims  (PassportProfile).
 * - getUserFromRequest(req) a function that given a request returns the user. By default req.user
 * - issuer string
 * - cert the public certificate
 * - key the private certificate to sign all tokens
 * - postUrl function (SAMLRequest, request, callback)
 * - responseHandler(SAMLResponse, request, response, next) a function that handles the response. Defaults to HTML POST to postUrl.
 *
 * @param  {[type]} options [description]
 * @return {[type]}         [description]
 */
module.exports.auth = function(options) {
  var opts = xtend({}, options || {}); // clone options
  opts.getUserFromRequest = opts.getUserFromRequest || function(req){ return req.user; };
  opts.signatureAlgorithm = opts.signatureAlgorithm || 'rsa-sha256';
  opts.digestAlgorithm = opts.digestAlgorithm || 'sha256';

  if (typeof opts.getPostURL !== 'function') {
    throw new Error('getPostURL is required');
  }

  function execute (postUrl, audience, req, res, next) {
    var user = opts.getUserFromRequest(req);
    if (!user) return res.send(401);

    opts.audience = audience;

    getSamlResponse(opts, user, function (err, SAMLResponse) {
      if (err) return next(err);

      var response = new Buffer(SAMLResponse);

      if (opts.responseHandler) {
        opts.responseHandler(response, req, res, next);
      } else {
        res.set('Content-Type', 'text/html');
        res.send(templates.form({
          callback:        postUrl,
          RelayState:      opts.RelayState || (req.query || {}).RelayState || (req.body || {}).RelayState || '',
          SAMLResponse:    response.toString('base64')
        }));
      }
    });
  }

  return function (req, res, next) {
    getSamlRequest((req.query || {}).SAMLRequest || (req.body || {}).SAMLRequest, function(err, samlRequestDom) {
      var audience = opts.audience;
      if (samlRequestDom) {
        var issuer = xpath.select("//*[local-name(.)='Issuer' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:assertion']/text()", samlRequestDom);
        if (issuer && issuer.length > 0) audience = audience || issuer[0].textContent;

        var id = samlRequestDom.documentElement.getAttribute('ID');
        if (id) opts.inResponseTo = opts.inResponseTo || id;
      }

      opts.getPostURL(audience, samlRequestDom, req, function (err, postUrl) {
        if (err) { return res.send(500, err); }
        if (!postUrl) { return res.send(401); }

        execute(postUrl, audience, req, res, next);
      });
    });
  };
};

module.exports.parseRequest = function(req, callback) {
  var samlRequest = (req.query || {}).SAMLRequest || (req.body || {}).SAMLRequest;
  if (!samlRequest)
    return callback();

  getSamlRequest(samlRequest, function(err, samlRequestDom) {
    if (err) {
      return callback(err);
    }

    if (!samlRequestDom.documentElement) {
      return callback(new Error('Invalid SAMLP request.'));
    }

    var data = {};
    var issuer = xpath.select("//*[local-name(.)='Issuer' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:assertion']/text()", samlRequestDom);
    if (issuer && issuer.length > 0) data.issuer = issuer[0].textContent;

    var assertionConsumerUrl = samlRequestDom.documentElement.getAttribute('AssertionConsumerServiceURL');
    if (assertionConsumerUrl) data.assertionConsumerServiceURL = assertionConsumerUrl;

    var destination = samlRequestDom.documentElement.getAttribute('Destination');
    if (destination) data.destination = destination;

    var forceAuthn = samlRequestDom.documentElement.getAttribute('ForceAuthn');
    if (forceAuthn) data.forceAuthn = forceAuthn;

    var id = samlRequestDom.documentElement.getAttribute('ID');
    if (id) data.id = id;

    callback(null, data);
  });
};

module.exports.logout = function (options) {
  function execute (postUrl, logoutRequestData, req, res, next) {
    options.inResponseTo = options.inResponseTo || logoutRequestData.id;

    getLogoutResponse(options, function (err, logoutResponse) {
      if (err) return next(err);

      res.set('Content-Type', 'text/html');
      res.send(templates.form({
        callback:     postUrl,
        RelayState:   options.RelayState || (req.query || {}).RelayState || (req.body || {}).RelayState || '',
        SAMLResponse: new Buffer(logoutResponse).toString('base64')
      }));
    });
  }

  return function (req, res, next) {
    module.exports.parseLogoutRequest(req, function (err, logoutRequestData) {
      if (err) { return res.send(500, err); }
      if (!logoutRequestData) { return res.send(400, 'missing SAMLRequest'); }

      // TODO #1: validate LogoutRequest and if valid, find out the associate SSO session for given
      // logoutRequestData.sessionIndex and that also is matched with logoutRequestData.nameID
      // TODO #2: sends LogoutRequest to each SP (another session participant) with corresponding SessionIndex and NameID
      options.getPostURL(logoutRequestData, req, function (err, postUrl) {
        if (err) { return res.send(500, err); }
        if (!postUrl) { return res.send(401); }
        execute(postUrl, logoutRequestData, req, res, next);
      });
    });
  };
};

module.exports.parseLogoutRequest = function (req, callback) {
  var samlRequest = (req.query || {}).SAMLRequest || (req.body || {}).SAMLRequest;
  if (!samlRequest) return callback();

  getSamlRequest(samlRequest, function(err, logoutRequestDom) {
    if (err) return callback(err);

    var data = {};
    var issuer = xpath.select("//*[local-name(.)='Issuer' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:assertion']/text()", logoutRequestDom);
    if (issuer && issuer.length > 0) data.issuer = issuer[0].textContent;

    var sessionIndex = xpath.select("//*[local-name(.)='SessionIndex']/text()", logoutRequestDom);
    if (sessionIndex && sessionIndex.length > 0) data.sessionIndex = sessionIndex[0].textContent;

    var nameId = xpath.select("//*[local-name(.)='NameID']", logoutRequestDom);
    if (nameId && nameId.length > 0) {
      data.nameId = nameId[0].textContent;
      data.nameIdFormat = nameId[0].getAttribute('Format');
    }

    var destination = logoutRequestDom.documentElement.getAttribute('Destination');
    if (destination) data.destination = destination;

    var id = logoutRequestDom.documentElement.getAttribute('ID');
    if (id) data.id = id;

    callback(null, data);
  });
};

module.exports.getSamlResponse = getSamlResponse;

module.exports.sendError = function (options) {
  // https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
  function renderResponse(res, postUrl) {
    var error = options.error || {};
    options.samlStatusCode = error.code || 'urn:oasis:names:tc:SAML:2.0:status:Responder';
    options.samlStatusMessage = error.description;

    var SAMLResponse = buildSamlResponse(options);
    var response = new Buffer(SAMLResponse);

    res.set('Content-Type', 'text/html');
    res.send(templates.form({
      callback:     postUrl,
      RelayState:   options.RelayState,
      SAMLResponse: response.toString('base64')
    }));
  }

  return function (req, res, next) {
    options.getPostURL(req, function (err, postUrl) {
      if (err) return next(err);
      if (!postUrl) return next(new Error('postUrl is required'));
      renderResponse(res, postUrl);
    });
  };
};
<commit_msg>getSamlRequest: add try-catch and validate xml.documentElement<commit_after>var saml20                = require('saml').Saml20;
var zlib                  = require('zlib');
var xmldom                = require('xmldom');
var SignedXml             = require('xml-crypto').SignedXml;
var xpath                 = require('xpath');
var xtend                 = require('xtend');

var templates             = require('./templates');
var encoders              = require('./encoders');
var PassportProfileMapper = require('./claims/PassportProfileMapper');

function getSamlRequest(samlRequest, callback) {
  if (!samlRequest) return callback();

  try {
    var input = new Buffer(samlRequest, 'base64');

    if (input[0] === 60) {  // open tag
      // content is just encoded, not zipped
      var xml = new xmldom.DOMParser().parseFromString(input.toString());
      if (!xml || !xml.documentElement) {
        return callback(new Error('Invalid SAML Request'));
      }

      callback(null, xml);
    } else {
      zlib.inflateRaw(input, function(err, buffer) {
        if (err) return callback(err);

        var xml = new xmldom.DOMParser().parseFromString(buffer.toString());
        if (!xml || !xml.documentElement) {
          return callback(new Error('Invalid SAML Request'));
        }

        callback(null, xml);
      });
    }
  } catch(e) {
    callback(e);
  }
}

function buildSamlResponse(options) {
  var SAMLResponse = templates.samlresponse({
    id:             '_' + generateUniqueID(),
    instant:        generateInstant(),
    destination:    options.destination || options.audience,
    inResponseTo:   options.inResponseTo,
    issuer:         options.issuer,
    samlStatusCode: options.samlStatusCode,
    samlStatusMessage: options.samlStatusMessage,
    assertion:      options.signedAssertion || ''
  });

  if (options.signResponse) {
    var cannonicalized = SAMLResponse
      .replace(/\r\n/g, '')
      .replace(/\n/g,'')
      .replace(/>(\s*)</g, '><') //unindent
      .trim();

    var sig = new SignedXml(null, {
      signatureAlgorithm: algorithms.signature[options.signatureAlgorithm],
      idAttribute: 'ID'
    });

    sig.addReference(
      "//*[local-name(.)='Response' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:protocol']",
      ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/2001/10/xml-exc-c14n#"],
      algorithms.digest[options.digestAlgorithm]);

    sig.signingKey = options.key;

    var pem = encoders.removeHeaders(options.cert);
    sig.keyInfoProvider = {
      getKeyInfo: function () {
        return "<X509Data><X509Certificate>" + pem + "</X509Certificate></X509Data>";
      }
    };

    sig.computeSignature(cannonicalized);
    SAMLResponse = sig.getSignedXml();
  }

  return SAMLResponse;
}

function getSamlResponse(options, user, callback) {
  options.profileMapper = options.profileMapper || PassportProfileMapper;

  var profileMap = options.profileMapper(user);
  var claims = profileMap.getClaims(options);
  var ni = profileMap.getNameIdentifier(options);

  if (!ni || !ni.nameIdentifier) {
    var error = new Error('No attribute was found to generate the nameIdentifier. We tried with: ' + (options.nameIdentifierProbes || []).join(', '));
    error.context = { user: user };
    return callback(error);
  }

  saml20.create({
    signatureAlgorithm:   options.signatureAlgorithm,
    digestAlgorithm:      options.digestAlgorithm,
    cert:                 options.cert,
    key:                  options.key,
    issuer:               options.issuer,
    lifetimeInSeconds:    options.lifetimeInSeconds || 3600,
    audiences:            options.audience,
    attributes:           claims,
    nameIdentifier:       ni.nameIdentifier,
    nameIdentifierFormat: ni.nameIdentifierFormat || options.nameIdentifierFormat,
    recipient:            options.recipient,
    inResponseTo:         options.inResponseTo,
    authnContextClassRef: options.authnContextClassRef,
    encryptionPublicKey:  options.encryptionPublicKey,
    encryptionCert:       options.encryptionCert,
    sessionIndex:         options.sessionIndex
  }, function (err, signedAssertion) {
    if (err) return callback(err);

    options.signedAssertion = signedAssertion;
    options.samlStatusCode = options.samlStatusCode || 'urn:oasis:names:tc:SAML:2.0:status:Success';
    var SAMLResponse = buildSamlResponse(options);
    callback(null, SAMLResponse);
  });
}

function getLogoutResponse (options, callback) {
  var logoutResponse = templates.logoutresponse({
    id:             '_' + generateUniqueID(),
    instant:        generateInstant(),
    destination:    options.destination || options.audience,
    inResponseTo:   options.inResponseTo,
    issuer:         options.issuer,
    samlStatusCode: options.samlStatusCode || 'urn:oasis:names:tc:SAML:2.0:status:Success'
  });

  if (options.signResponse) {
    // sign response and add embedded signature
    var cannonicalized = logoutResponse
      .replace(/\r\n/g, '')
      .replace(/\n/g,'')
      .replace(/>(\s*)</g, '><') //unindent
      .trim();

    var sig = new SignedXml(null, { signatureAlgorithm: algorithms.signature[options.signatureAlgorithm], idAttribute: 'ID' });
    sig.addReference("//*[local-name(.)='LogoutResponse' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:protocol']",
      ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', 'http://www.w3.org/2001/10/xml-exc-c14n#'],
      algorithms.digest[options.digestAlgorithm]);

    sig.signingKey = options.key;

    var pem = encoders.removeHeaders(options.cert);
    sig.keyInfoProvider = {
      getKeyInfo: function () {
        return '<X509Data><X509Certificate>' + pem + '</X509Certificate></X509Data>';
      }
    };

    sig.computeSignature(cannonicalized);
    logoutResponse = sig.getSignedXml();
  }

  callback(null, logoutResponse);
}

function generateUniqueID() {
  var chars = "abcdef0123456789";
  var uniqueID = "";
  for (var i = 0; i < 20; i++) {
    uniqueID += chars.substr(Math.floor((Math.random()*15)), 1);
  }
  return uniqueID;
}

function generateInstant() {
  var date = new Date();
  return date.getUTCFullYear() + '-' + ('0' + (date.getUTCMonth()+1)).slice(-2) + '-' + ('0' + date.getUTCDate()).slice(-2) + 'T' + ('0' + date.getUTCHours()).slice(-2) + ":" + ('0' + date.getUTCMinutes()).slice(-2) + ":" + ('0' + date.getUTCSeconds()).slice(-2) + "Z";
}

var algorithms = {
  signature: {
    'rsa-sha256': 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
    'rsa-sha1':  'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
  },
  digest: {
    'sha256': 'http://www.w3.org/2001/04/xmlenc#sha256',
    'sha1': 'http://www.w3.org/2000/09/xmldsig#sha1'
  }
};

/**
 * SAML Protocol middleware.
 *
 * This middleware creates a SAML endpoint based on the user logged in identity.
 *
 * options:
 * - profileMapper(profile) a ProfileMapper implementation to convert a user profile to claims  (PassportProfile).
 * - getUserFromRequest(req) a function that given a request returns the user. By default req.user
 * - issuer string
 * - cert the public certificate
 * - key the private certificate to sign all tokens
 * - postUrl function (SAMLRequest, request, callback)
 * - responseHandler(SAMLResponse, request, response, next) a function that handles the response. Defaults to HTML POST to postUrl.
 *
 * @param  {[type]} options [description]
 * @return {[type]}         [description]
 */
module.exports.auth = function(options) {
  var opts = xtend({}, options || {}); // clone options
  opts.getUserFromRequest = opts.getUserFromRequest || function(req){ return req.user; };
  opts.signatureAlgorithm = opts.signatureAlgorithm || 'rsa-sha256';
  opts.digestAlgorithm = opts.digestAlgorithm || 'sha256';

  if (typeof opts.getPostURL !== 'function') {
    throw new Error('getPostURL is required');
  }

  function execute (postUrl, audience, req, res, next) {
    var user = opts.getUserFromRequest(req);
    if (!user) return res.send(401);

    opts.audience = audience;

    getSamlResponse(opts, user, function (err, SAMLResponse) {
      if (err) return next(err);

      var response = new Buffer(SAMLResponse);

      if (opts.responseHandler) {
        opts.responseHandler(response, req, res, next);
      } else {
        res.set('Content-Type', 'text/html');
        res.send(templates.form({
          callback:        postUrl,
          RelayState:      opts.RelayState || (req.query || {}).RelayState || (req.body || {}).RelayState || '',
          SAMLResponse:    response.toString('base64')
        }));
      }
    });
  }

  return function (req, res, next) {
    getSamlRequest((req.query || {}).SAMLRequest || (req.body || {}).SAMLRequest, function(err, samlRequestDom) {
      var audience = opts.audience;
      if (samlRequestDom) {
        var issuer = xpath.select("//*[local-name(.)='Issuer' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:assertion']/text()", samlRequestDom);
        if (issuer && issuer.length > 0) audience = audience || issuer[0].textContent;

        var id = samlRequestDom.documentElement.getAttribute('ID');
        if (id) opts.inResponseTo = opts.inResponseTo || id;
      }

      opts.getPostURL(audience, samlRequestDom, req, function (err, postUrl) {
        if (err) { return res.send(500, err); }
        if (!postUrl) { return res.send(401); }

        execute(postUrl, audience, req, res, next);
      });
    });
  };
};

module.exports.parseRequest = function(req, callback) {
  var samlRequest = (req.query || {}).SAMLRequest || (req.body || {}).SAMLRequest;
  if (!samlRequest)
    return callback();

  getSamlRequest(samlRequest, function(err, samlRequestDom) {
    if (err) {
      return callback(err);
    }

    var data = {};
    var issuer = xpath.select("//*[local-name(.)='Issuer' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:assertion']/text()", samlRequestDom);
    if (issuer && issuer.length > 0) data.issuer = issuer[0].textContent;

    var assertionConsumerUrl = samlRequestDom.documentElement.getAttribute('AssertionConsumerServiceURL');
    if (assertionConsumerUrl) data.assertionConsumerServiceURL = assertionConsumerUrl;

    var destination = samlRequestDom.documentElement.getAttribute('Destination');
    if (destination) data.destination = destination;

    var forceAuthn = samlRequestDom.documentElement.getAttribute('ForceAuthn');
    if (forceAuthn) data.forceAuthn = forceAuthn;

    var id = samlRequestDom.documentElement.getAttribute('ID');
    if (id) data.id = id;

    callback(null, data);
  });
};

module.exports.logout = function (options) {
  function execute (postUrl, logoutRequestData, req, res, next) {
    options.inResponseTo = options.inResponseTo || logoutRequestData.id;

    getLogoutResponse(options, function (err, logoutResponse) {
      if (err) return next(err);

      res.set('Content-Type', 'text/html');
      res.send(templates.form({
        callback:     postUrl,
        RelayState:   options.RelayState || (req.query || {}).RelayState || (req.body || {}).RelayState || '',
        SAMLResponse: new Buffer(logoutResponse).toString('base64')
      }));
    });
  }

  return function (req, res, next) {
    module.exports.parseLogoutRequest(req, function (err, logoutRequestData) {
      if (err) { return res.send(500, err); }
      if (!logoutRequestData) { return res.send(400, 'missing SAMLRequest'); }

      // TODO #1: validate LogoutRequest and if valid, find out the associate SSO session for given
      // logoutRequestData.sessionIndex and that also is matched with logoutRequestData.nameID
      // TODO #2: sends LogoutRequest to each SP (another session participant) with corresponding SessionIndex and NameID
      options.getPostURL(logoutRequestData, req, function (err, postUrl) {
        if (err) { return res.send(500, err); }
        if (!postUrl) { return res.send(401); }
        execute(postUrl, logoutRequestData, req, res, next);
      });
    });
  };
};

module.exports.parseLogoutRequest = function (req, callback) {
  var samlRequest = (req.query || {}).SAMLRequest || (req.body || {}).SAMLRequest;
  if (!samlRequest) return callback();

  getSamlRequest(samlRequest, function(err, logoutRequestDom) {
    if (err) return callback(err);

    var data = {};
    var issuer = xpath.select("//*[local-name(.)='Issuer' and namespace-uri(.)='urn:oasis:names:tc:SAML:2.0:assertion']/text()", logoutRequestDom);
    if (issuer && issuer.length > 0) data.issuer = issuer[0].textContent;

    var sessionIndex = xpath.select("//*[local-name(.)='SessionIndex']/text()", logoutRequestDom);
    if (sessionIndex && sessionIndex.length > 0) data.sessionIndex = sessionIndex[0].textContent;

    var nameId = xpath.select("//*[local-name(.)='NameID']", logoutRequestDom);
    if (nameId && nameId.length > 0) {
      data.nameId = nameId[0].textContent;
      data.nameIdFormat = nameId[0].getAttribute('Format');
    }

    var destination = logoutRequestDom.documentElement.getAttribute('Destination');
    if (destination) data.destination = destination;

    var id = logoutRequestDom.documentElement.getAttribute('ID');
    if (id) data.id = id;

    callback(null, data);
  });
};

module.exports.getSamlResponse = getSamlResponse;

module.exports.sendError = function (options) {
  // https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf
  function renderResponse(res, postUrl) {
    var error = options.error || {};
    options.samlStatusCode = error.code || 'urn:oasis:names:tc:SAML:2.0:status:Responder';
    options.samlStatusMessage = error.description;

    var SAMLResponse = buildSamlResponse(options);
    var response = new Buffer(SAMLResponse);

    res.set('Content-Type', 'text/html');
    res.send(templates.form({
      callback:     postUrl,
      RelayState:   options.RelayState,
      SAMLResponse: response.toString('base64')
    }));
  }

  return function (req, res, next) {
    options.getPostURL(req, function (err, postUrl) {
      if (err) return next(err);
      if (!postUrl) return next(new Error('postUrl is required'));
      renderResponse(res, postUrl);
    });
  };
};
<|endoftext|>


<commit_before>"use strict";

var PouchDB = require("./constructor");
var utils = require('./utils');
var Promise = utils.Promise;
var EventEmitter = require('events').EventEmitter;
PouchDB.adapters = {};
PouchDB.preferredAdapters = require('./adapters/preferredAdapters.js');

PouchDB.prefix = '_pouch_';

var eventEmitter = new EventEmitter();

var eventEmitterMethods = [
  'on',
  'addListener',
  'emit',
  'listeners',
  'once',
  'removeAllListeners',
  'removeListener',
  'setMaxListeners'
];

eventEmitterMethods.forEach(function (method) {
  PouchDB[method] = eventEmitter[method].bind(eventEmitter);
});
PouchDB.setMaxListeners(0);
PouchDB.parseAdapter = function (name, opts) {
  var match = name.match(/([a-z\-]*):\/\/(.*)/);
  var adapter, adapterName;
  if (match) {
    // the http adapter expects the fully qualified name
    name = /http(s?)/.test(match[1]) ? match[1] + '://' + match[2] : match[2];
    adapter = match[1];
    if (!PouchDB.adapters[adapter].valid()) {
      throw 'Invalid adapter';
    }
    return {name: name, adapter: match[1]};
  }

  // check for browsers that have been upgraded from websql-only to websql+idb
  var skipIdb = 'idb' in PouchDB.adapters && 'websql' in PouchDB.adapters &&
    utils.hasLocalStorage() &&
    localStorage['_pouch__websqldb_' + PouchDB.prefix + name];

  if (typeof opts !== 'undefined' && opts.db) {
    adapterName = 'leveldb';
  } else {
    for (var i = 0; i < PouchDB.preferredAdapters.length; ++i) {
      adapterName = PouchDB.preferredAdapters[i];
      if (adapterName in PouchDB.adapters) {
        if (skipIdb && adapterName === 'idb') {
          continue; // keep using websql to avoid user data loss
        }
        break;
      }
    }
  }

  adapter = PouchDB.adapters[adapterName];
  if (adapterName && adapter) {
    var use_prefix = 'use_prefix' in adapter ? adapter.use_prefix : true;

    return {
      name: use_prefix ? PouchDB.prefix + name : name,
      adapter: adapterName
    };
  }

  throw 'No valid adapter found';
};

PouchDB.destroy = utils.toPromise(function (name, opts, callback) {

  if (typeof opts === 'function' || typeof opts === 'undefined') {
    callback = opts;
    opts = {};
  }
  if (name && typeof name === 'object') {
    opts = name;
    name = undefined;
  }

  if (!opts.internal) {
    console.log('PouchDB.destroy() is deprecated and will be removed. ' +
                'Please use db.destroy() instead.');
  }

  var backend = PouchDB.parseAdapter(opts.name || name, opts);
  var dbName = backend.name;
  var adapter = PouchDB.adapters[backend.adapter];
  var usePrefix = 'use_prefix' in adapter ? adapter.use_prefix : true;
  var baseName = usePrefix ?
    dbName.replace(new RegExp('^' + PouchDB.prefix), '') : dbName;
  var fullName = (backend.adapter === 'http' || backend.adapter === 'https' ?
      '' : (opts.prefix || '')) + dbName;
  function destroyDb() {
    // call destroy method of the particular adaptor
    adapter.destroy(fullName, opts, function (err, resp) {
      if (err) {
        callback(err);
      } else {
        PouchDB.emit('destroyed', name);
        //so we don't have to sift through all dbnames
        PouchDB.emit(name, 'destroyed');
        callback(null, resp || { 'ok': true });
      }
    });
  }

  var createOpts = utils.extend(true, {}, opts, {adapter : backend.adapter});
  new PouchDB(baseName, createOpts, function (err, db) {
    if (err) {
      return callback(err);
    }
    db.get('_local/_pouch_dependentDbs', function (err, localDoc) {
      if (err) {
        if (err.status !== 404) {
          return callback(err);
        } else { // no dependencies
          return destroyDb();
        }
      }
      var dependentDbs = localDoc.dependentDbs;
      var deletedMap = Object.keys(dependentDbs).map(function (name) {
        var trueName = usePrefix ?
          name.replace(new RegExp('^' + PouchDB.prefix), '') : name;
        return db.constructor.destroy(trueName, db.__opts);
      });
      Promise.all(deletedMap).then(destroyDb, function (error) {
        callback(error);
      });
    });
  });
});

PouchDB.adapter = function (id, obj) {
  if (obj.valid()) {
    PouchDB.adapters[id] = obj;
  }
};

PouchDB.plugin = function (obj) {
  Object.keys(obj).forEach(function (id) {
    PouchDB.prototype[id] = obj[id];
  });
};

PouchDB.defaults = function (defaultOpts) {
  function PouchAlt(name, opts, callback) {
    if (typeof opts === 'function' || typeof opts === 'undefined') {
      callback = opts;
      opts = {};
    }
    if (name && typeof name === 'object') {
      opts = name;
      name = undefined;
    }

    opts = utils.extend(true, {}, defaultOpts, opts);
    PouchDB.call(this, name, opts, callback);
  }

  utils.inherits(PouchAlt, PouchDB);

  PouchAlt.destroy = utils.toPromise(function (name, opts, callback) {
    if (typeof opts === 'function' || typeof opts === 'undefined') {
      callback = opts;
      opts = {};
    }

    if (name && typeof name === 'object') {
      opts = name;
      name = undefined;
    }
    opts = utils.extend(true, {}, defaultOpts, opts);
    return PouchDB.destroy(name, opts, callback);
  });

  eventEmitterMethods.forEach(function (method) {
    PouchAlt[method] = eventEmitter[method].bind(eventEmitter);
  });
  PouchAlt.setMaxListeners(0);

  PouchAlt.preferredAdapters = PouchDB.preferredAdapters.slice();
  Object.keys(PouchDB).forEach(function (key) {
    if (!(key in PouchAlt)) {
      PouchAlt[key] = PouchDB[key];
    }
  });

  return PouchAlt;
};

module.exports = PouchDB;
<commit_msg>(#3757) - notify when force-downgrading websql<commit_after>"use strict";

var PouchDB = require("./constructor");
var utils = require('./utils');
var Promise = utils.Promise;
var EventEmitter = require('events').EventEmitter;
PouchDB.adapters = {};
PouchDB.preferredAdapters = require('./adapters/preferredAdapters.js');

PouchDB.prefix = '_pouch_';

var eventEmitter = new EventEmitter();

var eventEmitterMethods = [
  'on',
  'addListener',
  'emit',
  'listeners',
  'once',
  'removeAllListeners',
  'removeListener',
  'setMaxListeners'
];

eventEmitterMethods.forEach(function (method) {
  PouchDB[method] = eventEmitter[method].bind(eventEmitter);
});
PouchDB.setMaxListeners(0);
PouchDB.parseAdapter = function (name, opts) {
  var match = name.match(/([a-z\-]*):\/\/(.*)/);
  var adapter, adapterName;
  if (match) {
    // the http adapter expects the fully qualified name
    name = /http(s?)/.test(match[1]) ? match[1] + '://' + match[2] : match[2];
    adapter = match[1];
    if (!PouchDB.adapters[adapter].valid()) {
      throw 'Invalid adapter';
    }
    return {name: name, adapter: match[1]};
  }

  // check for browsers that have been upgraded from websql-only to websql+idb
  var skipIdb = 'idb' in PouchDB.adapters && 'websql' in PouchDB.adapters &&
    utils.hasLocalStorage() &&
    localStorage['_pouch__websqldb_' + PouchDB.prefix + name];

  if (typeof opts !== 'undefined' && opts.db) {
    adapterName = 'leveldb';
  } else {
    for (var i = 0; i < PouchDB.preferredAdapters.length; ++i) {
      adapterName = PouchDB.preferredAdapters[i];
      if (adapterName in PouchDB.adapters) {
        if (skipIdb && adapterName === 'idb') {
          // log it, because this can be confusing during development
          console.log('PouchDB is downgrading "' + name + '" to WebSQL to' +
            ' avoid data loss, because it was already opened with WebSQL.');
          continue; // keep using websql to avoid user data loss
        }
        break;
      }
    }
  }

  adapter = PouchDB.adapters[adapterName];
  if (adapterName && adapter) {
    var use_prefix = 'use_prefix' in adapter ? adapter.use_prefix : true;

    return {
      name: use_prefix ? PouchDB.prefix + name : name,
      adapter: adapterName
    };
  }

  throw 'No valid adapter found';
};

PouchDB.destroy = utils.toPromise(function (name, opts, callback) {

  if (typeof opts === 'function' || typeof opts === 'undefined') {
    callback = opts;
    opts = {};
  }
  if (name && typeof name === 'object') {
    opts = name;
    name = undefined;
  }

  if (!opts.internal) {
    console.log('PouchDB.destroy() is deprecated and will be removed. ' +
                'Please use db.destroy() instead.');
  }

  var backend = PouchDB.parseAdapter(opts.name || name, opts);
  var dbName = backend.name;
  var adapter = PouchDB.adapters[backend.adapter];
  var usePrefix = 'use_prefix' in adapter ? adapter.use_prefix : true;
  var baseName = usePrefix ?
    dbName.replace(new RegExp('^' + PouchDB.prefix), '') : dbName;
  var fullName = (backend.adapter === 'http' || backend.adapter === 'https' ?
      '' : (opts.prefix || '')) + dbName;
  function destroyDb() {
    // call destroy method of the particular adaptor
    adapter.destroy(fullName, opts, function (err, resp) {
      if (err) {
        callback(err);
      } else {
        PouchDB.emit('destroyed', name);
        //so we don't have to sift through all dbnames
        PouchDB.emit(name, 'destroyed');
        callback(null, resp || { 'ok': true });
      }
    });
  }

  var createOpts = utils.extend(true, {}, opts, {adapter : backend.adapter});
  new PouchDB(baseName, createOpts, function (err, db) {
    if (err) {
      return callback(err);
    }
    db.get('_local/_pouch_dependentDbs', function (err, localDoc) {
      if (err) {
        if (err.status !== 404) {
          return callback(err);
        } else { // no dependencies
          return destroyDb();
        }
      }
      var dependentDbs = localDoc.dependentDbs;
      var deletedMap = Object.keys(dependentDbs).map(function (name) {
        var trueName = usePrefix ?
          name.replace(new RegExp('^' + PouchDB.prefix), '') : name;
        return db.constructor.destroy(trueName, db.__opts);
      });
      Promise.all(deletedMap).then(destroyDb, function (error) {
        callback(error);
      });
    });
  });
});

PouchDB.adapter = function (id, obj) {
  if (obj.valid()) {
    PouchDB.adapters[id] = obj;
  }
};

PouchDB.plugin = function (obj) {
  Object.keys(obj).forEach(function (id) {
    PouchDB.prototype[id] = obj[id];
  });
};

PouchDB.defaults = function (defaultOpts) {
  function PouchAlt(name, opts, callback) {
    if (typeof opts === 'function' || typeof opts === 'undefined') {
      callback = opts;
      opts = {};
    }
    if (name && typeof name === 'object') {
      opts = name;
      name = undefined;
    }

    opts = utils.extend(true, {}, defaultOpts, opts);
    PouchDB.call(this, name, opts, callback);
  }

  utils.inherits(PouchAlt, PouchDB);

  PouchAlt.destroy = utils.toPromise(function (name, opts, callback) {
    if (typeof opts === 'function' || typeof opts === 'undefined') {
      callback = opts;
      opts = {};
    }

    if (name && typeof name === 'object') {
      opts = name;
      name = undefined;
    }
    opts = utils.extend(true, {}, defaultOpts, opts);
    return PouchDB.destroy(name, opts, callback);
  });

  eventEmitterMethods.forEach(function (method) {
    PouchAlt[method] = eventEmitter[method].bind(eventEmitter);
  });
  PouchAlt.setMaxListeners(0);

  PouchAlt.preferredAdapters = PouchDB.preferredAdapters.slice();
  Object.keys(PouchDB).forEach(function (key) {
    if (!(key in PouchAlt)) {
      PouchAlt[key] = PouchDB[key];
    }
  });

  return PouchAlt;
};

module.exports = PouchDB;
<|endoftext|>


<commit_before>function Sites( api ) {
	this.api = api;
}

Sites.prototype.query = function( args ) {
	return new Promise( function( resolve, reject ) {
		this.api
			.get( '/sites' )
			.query( args )
			.end( function( err, res ) {
				if ( err ) {
					return reject( err );
				}

				return resolve( res.body );
			});
	});
}

Sites.prototype.getMeta = function( site_id, meta_key ) {
	return new Promise( ( resolve, reject ) => {
		this.api
		.get( '/sites/' + site_id + '/meta/' + meta_key )
		.end( ( err, res ) => {
			if ( err ) {
				return reject( err );
			}

			return resolve( res.body );
			});
	});
}

module.exports = Sites;
<commit_msg>Ensure `this` is defined<commit_after>function Sites( api ) {
	this.api = api;
}

Sites.prototype.query = function( args ) {
	return new Promise( function( resolve, reject ) {
		this.api
			.get( '/sites' )
			.query( args )
			.end( function( err, res ) {
				if ( err ) {
					return reject( err );
				}

				return resolve( res.body );
			});
	});
}

Sites.prototype.getMeta = function( site_id, meta_key ) {
	var self = this;

	return new Promise( ( resolve, reject ) => {
		self.api
		.get( '/sites/' + site_id + '/meta/' + meta_key )
		.end( ( err, res ) => {
			if ( err ) {
				return reject( err );
			}

			return resolve( res.body );
			});
	});
}

module.exports = Sites;
<|endoftext|>


<commit_before>var fs = require("fs");
var ejs = require("ejs");
var uuid = require("node-uuid");
var when = require("when");
var bResources = require("buster-resources");
var bResourcesResourceSet = bResources.resourceSet;

var templates = {
    capturedBrowser: fs.readFileSync(__dirname + "/templates/captured_browser.html", "utf8")
};

var frameworkScriptPaths = [
    require.resolve("./../vendor/json/json2"),
    require.resolve("buster-core"),
    require.resolve("buster-core").replace("buster-core.js", "buster-event-emitter.js"),
    require.resolve("faye/faye-browser-min"),
];
var slaveScriptPaths = [
    require.resolve("./browser/cross-browser-util"),
    require.resolve("./browser/session-loader"),
    require.resolve("./browser/session-loader-init")
];
var buster = require("buster-core");

exports.create = function (server, bayeuxServer, resourcesMiddleware, currentSession) {
    var bayeuxClient = server.bayeux;
    var ended = false;
    var bayeuxDisconnectListener;
    var isReady;
    var bayeuxClientId;
    var headerHeight;
    var headerResourceSet;
    var headerPath;

    var slave = buster.extend(buster.eventEmitter.create(), {
        header: function (height, resourceSet) {
            resourcesMiddleware.unmount(headerPath);

            // Unset
            if (height == undefined) return;

            // Set
            headerHeight = height;
            headerResourceSet = resourceSet;
            resourcesMiddleware.mount(headerPath, resourceSet);
        },

        startSession: function (session) {
            this.sessionInProgress = true;
            session.on("end", endSession);
            currentSession = session;
            broadcastSession();
        },

        // Note: when changing this, make sure browser/control-frame.js still
        // works. It's not currently integration tested.
        getEnv: function() {
            var env = {
                slaveId: this.id,
                bayeuxPath: server.messagingContextPath,
                capturePath: server.capturePath
            };

            return buster.extend(env, this.env);
        },

        serialize: function () {
            return {
                id: this.id,
                url: this.url
            };
        }
    });

    slave.id = uuid();
    var baseUrl = "/slaves/" + slave.id;
    slave.url = baseUrl + "/browser"
    slave.env = {};

    headerPath = baseUrl + "/header";
    bayeuxDisconnectListener = function (_bayeuxClientId) {
        if (bayeuxClientId == _bayeuxClientId) endSlave();
    };

    function ready(_bayeuxClientId) {
        isReady = true;
        bayeuxClientId = _bayeuxClientId;
        broadcastSession();
        slave.emit("ready");
    }

    function broadcastSession() {
        if (currentSession !== undefined && isReady) {
            bayeuxClient.publish("/" + slave.id + "/session/start", currentSession.toJSON());

            var session = currentSession;
            var readyEvent = "/" + slave.id + "/session/" + currentSession.id + "/ready";
            var sessionReadyHandler = function () {
                slave.emit("sessionLoaded", session);
                bayeuxClient.unsubscribe(readyEvent, sessionReadyHandler);
            };
            bayeuxClient.subscribe(readyEvent, sessionReadyHandler);
        }
    }

    function endSession() {
        slave.sessionInProgress = false;
        bayeuxClient.publish("/" + slave.id + "/session/end", {});
        currentSession = undefined;
    }

    function endSlave() {
        if (ended) return;
        ended = true;

        bayeuxServer.unbind("disconnect", bayeuxDisconnectListener);
        slave.emit("end");
    }

    function loadFileInResourceSet(path) {
        resourceSet.addFileResource(path);
        resourceSet.appendLoad(path);
    }

    var resourceSet = bResourcesResourceSet.create();
    resourceSet.addResource({
        path: "/",
        content: function () {
            var locals = {};
            locals.slaveRoot = this.url;
            locals.scripts = resourceSet.loadPath.paths();
            locals.hasHeaderFrame = (headerResourceSet !== undefined);
            if (locals.hasHeaderFrame) {
                locals.headerFrameHeight = headerHeight;
                locals.headerFramePath = headerPath;
            }

            return ejs.render(templates.capturedBrowser, {locals: locals});
        }
    });

    frameworkScriptPaths.forEach(loadFileInResourceSet);
    resourceSet.addResource("/env.js", {
        content: function (promise, req) {
            promise.resolve("buster.env = " + JSON.stringify(slave.getEnv()) + ";");
        }
    });
    resourceSet.appendLoad("/env.js");
    slaveScriptPaths.forEach(loadFileInResourceSet);

    bayeuxClient.subscribe("/" + slave.id + "/ready", ready);
    bayeuxServer.bind("disconnect", bayeuxDisconnectListener);

    if (currentSession && !(currentSession.joinable == false)) {
        slave.startSession(currentSession);
    }

    resourcesMiddleware.mount(slave.url, resourceSet);

    return slave;
};<commit_msg>Making id and slave getters, this property is not settable.<commit_after>var fs = require("fs");
var ejs = require("ejs");
var uuid = require("node-uuid");
var when = require("when");
var bResources = require("buster-resources");
var bResourcesResourceSet = bResources.resourceSet;

var templates = {
    capturedBrowser: fs.readFileSync(__dirname + "/templates/captured_browser.html", "utf8")
};

var frameworkScriptPaths = [
    require.resolve("./../vendor/json/json2"),
    require.resolve("buster-core"),
    require.resolve("buster-core").replace("buster-core.js", "buster-event-emitter.js"),
    require.resolve("faye/faye-browser-min"),
];
var slaveScriptPaths = [
    require.resolve("./browser/cross-browser-util"),
    require.resolve("./browser/session-loader"),
    require.resolve("./browser/session-loader-init")
];
var buster = require("buster-core");

exports.create = function (server, bayeuxServer, resourcesMiddleware, currentSession) {
    var bayeuxClient = server.bayeux;
    var ended = false;
    var bayeuxDisconnectListener;
    var isReady;
    var bayeuxClientId;
    var headerHeight;
    var headerResourceSet;

    var id = uuid();
    var baseUrl = "/slaves/" + id;
    var url = baseUrl + "/browser"
    var headerPath = baseUrl + "/header";

    var slave = buster.extend(buster.eventEmitter.create(), {
        header: function (height, resourceSet) {
            resourcesMiddleware.unmount(headerPath);

            // Unset
            if (height == undefined) return;

            // Set
            headerHeight = height;
            headerResourceSet = resourceSet;
            resourcesMiddleware.mount(headerPath, resourceSet);
        },

        startSession: function (session) {
            this.sessionInProgress = true;
            session.on("end", endSession);
            currentSession = session;
            broadcastSession();
        },

        // Note: when changing this, make sure browser/control-frame.js still
        // works. It's not currently integration tested.
        getEnv: function() {
            return {
                slaveId: id,
                bayeuxPath: server.messagingContextPath,
                capturePath: server.capturePath
            };
        },

        serialize: function () {
            return {
                id: id,
                url: url
            };
        }
    });

    Object.defineProperty(slave, "id", {
        get: function () { return id; }
    });

    Object.defineProperty(slave, "url", {
        get: function () { return url; }
    });

    bayeuxDisconnectListener = function (_bayeuxClientId) {
        if (bayeuxClientId == _bayeuxClientId) endSlave();
    };

    function ready(_bayeuxClientId) {
        isReady = true;
        bayeuxClientId = _bayeuxClientId;
        broadcastSession();
        slave.emit("ready");
    }

    function broadcastSession() {
        if (currentSession !== undefined && isReady) {
            bayeuxClient.publish("/" + id + "/session/start", currentSession.toJSON());

            var session = currentSession;
            var readyEvent = "/" + id + "/session/" + currentSession.id + "/ready";
            var sessionReadyHandler = function () {
                slave.emit("sessionLoaded", session);
                bayeuxClient.unsubscribe(readyEvent, sessionReadyHandler);
            };
            bayeuxClient.subscribe(readyEvent, sessionReadyHandler);
        }
    }

    function endSession() {
        slave.sessionInProgress = false;
        bayeuxClient.publish("/" + id + "/session/end", {});
        currentSession = undefined;
    }

    function endSlave() {
        if (ended) return;
        ended = true;

        bayeuxServer.unbind("disconnect", bayeuxDisconnectListener);
        slave.emit("end");
    }

    function loadFileInResourceSet(path) {
        resourceSet.addFileResource(path);
        resourceSet.appendLoad(path);
    }

    var resourceSet = bResourcesResourceSet.create();
    resourceSet.addResource({
        path: "/",
        content: function () {
            var locals = {};
            locals.slaveRoot = url;
            locals.scripts = resourceSet.loadPath.paths();
            locals.hasHeaderFrame = (headerResourceSet !== undefined);
            if (locals.hasHeaderFrame) {
                locals.headerFrameHeight = headerHeight;
                locals.headerFramePath = headerPath;
            }

            return ejs.render(templates.capturedBrowser, {locals: locals});
        }
    });

    frameworkScriptPaths.forEach(loadFileInResourceSet);
    resourceSet.addResource("/env.js", {
        content: function (promise, req) {
            promise.resolve("buster.env = " + JSON.stringify(slave.getEnv()) + ";");
        }
    });
    resourceSet.appendLoad("/env.js");
    slaveScriptPaths.forEach(loadFileInResourceSet);

    bayeuxClient.subscribe("/" + id + "/ready", ready);
    bayeuxServer.bind("disconnect", bayeuxDisconnectListener);

    if (currentSession && !(currentSession.joinable == false)) {
        slave.startSession(currentSession);
    }

    resourcesMiddleware.mount(url, resourceSet);

    return slave;
};<|endoftext|>


<commit_before>var constants = require('./constants');

/**
 *
 *
 * @param distance Float meters
 * @param time1 Date
 * @param time2 Date
 *
 * @return float speed in meter per second
 */
var calculateSpeed = function(distance, time1, time2) {
	if( typeof distance == 'undefined' || distance < 0) {
		return undefined;
	}
	if(!time1 || !time2 || time2 <= time1) {
		return undefined;
	}
	return (1000 * distance) / (time2.getTime() - time1.getTime());
};
var convertTypes = {
	MPSec2KmPH : 'MPSec2KmPH',
	KmPH2MPSec : 'KmPH2MPSec',
	MPSec2MPH : 'MPSec2MPH',
	MPH2MPSec : 'MPH2MPSec'
};

/* Those may make the tests fail if they are run in diff precision :-D */
var convertFactors = {
	MPSec2KmPH : 3.6,
	KmPH2MPSec : 1 / 3.6,
	MPSec2MPH : 3600 / constants.MILE_IN_METERS,
	MPH2MPSec : constants.MILE_IN_METERS / 3600
};

var convertSpeed = function(speed, fromto) {
	var scaleFactor;

	if(convertFactors.hasOwnProperty(fromto)) {
		scaleFactor = convertFactors[fromto];
	} else {
		throw new Error('Not supported conversion: ' + fromto);
	}

	return speed * scaleFactor;
};
var calculatePaceTypes = {
	PER_KM : 'PER_KM',
	PER_MILE : 'PER_MILE'
};

var PaceResult = function(hours, minutes, seconds, type) {
	this.hours = hours;
	this.minutes = minutes;
	this.seconds = seconds;
	this.type = type;
};

PaceResult.prototype.padDigitsString = function(digits) {
	if(digits < 10) {
		return '0' + digits;
	}
	return String(digits);
};

PaceResult.prototype.toString = function() {
	var str = '';
	if(this.hours) {
		str += this.padDigitsString(this.hours) + ':';
	}
	str += this.padDigitsString(this.minutes) + ':';
	str += this.padDigitsString(this.seconds);
	switch(this.type) {
		case calculatePaceTypes.PER_KM:
			str += ' / km';
			break;
		case calculatePaceTypes.PER_MILE:
			str += ' / mile';
			break;
	}
	return str;
};
var toHoursMinutesSeconds = function(secs) {
	var dd = {};
	dd.hours = Math.floor(secs / 3600);
	dd.minutes = Math.floor((secs % 3600) / 60);
	dd.seconds = Math.round(secs % 60);
	return dd;
};
/**
 *
 * @param speed m/s
 * @param type /km or /mile
 */
var calculatePace = function(speed, type) {
	var result = new PaceResult(), secs, dd;
	result.type = type;

	if(speed > 0) {
		switch(type) {
			case calculatePaceTypes.PER_KM:
				secs = 1000 / speed;
				break;
			case calculatePaceTypes.PER_MILE:
				secs = constants.MILE_IN_METERS / speed;

				break;
			default:
				throw new Error('Not supported type: ' + type);
		}
		dd = toHoursMinutesSeconds(secs);
		result.hours = dd.hours;
		result.minutes = dd.minutes;
		result.seconds = dd.seconds;
	}
	return result;
};

module.exports = {
	calculateSpeed : calculateSpeed,
	calculatePace : calculatePace,
	calculatePaceTypes : calculatePaceTypes,
	convertSpeed : convertSpeed,
	convertTypes : convertTypes
};
<commit_msg>Use RelativeTime instead of toHoursMinutesSeconds<commit_after>var constants = require('./constants');
var time = require('./time');

/**
 *
 *
 * @param distance Float meters
 * @param time1 Date
 * @param time2 Date
 *
 * @return float speed in meter per second
 */
var calculateSpeed = function(distance, time1, time2) {
	if( typeof distance == 'undefined' || distance < 0) {
		return undefined;
	}
	if(!time1 || !time2 || time2 <= time1) {
		return undefined;
	}
	return (1000 * distance) / (time2.getTime() - time1.getTime());
};
var convertTypes = {
	MPSec2KmPH : 'MPSec2KmPH',
	KmPH2MPSec : 'KmPH2MPSec',
	MPSec2MPH : 'MPSec2MPH',
	MPH2MPSec : 'MPH2MPSec'
};

/* Those may make the tests fail if they are run in diff precision :-D */
var convertFactors = {
	MPSec2KmPH : 3.6,
	KmPH2MPSec : 1 / 3.6,
	MPSec2MPH : 3600 / constants.MILE_IN_METERS,
	MPH2MPSec : constants.MILE_IN_METERS / 3600
};

var convertSpeed = function(speed, fromto) {
	var scaleFactor;

	if(convertFactors.hasOwnProperty(fromto)) {
		scaleFactor = convertFactors[fromto];
	} else {
		throw new Error('Not supported conversion: ' + fromto);
	}

	return speed * scaleFactor;
};
var calculatePaceTypes = {
	PER_KM : 'PER_KM',
	PER_MILE : 'PER_MILE'
};

var PaceResult = function(hours, minutes, seconds, type) {
	this.hours = hours;
	this.minutes = minutes;
	this.seconds = seconds;
	this.type = type;
};

PaceResult.prototype.padDigitsString = function(digits) {
	if(digits < 10) {
		return '0' + digits;
	}
	return String(digits);
};

PaceResult.prototype.toString = function() {
	var str = '';
	if(this.hours) {
		str += this.padDigitsString(this.hours) + ':';
	}
	str += this.padDigitsString(this.minutes) + ':';
	str += this.padDigitsString(this.seconds);
	switch(this.type) {
		case calculatePaceTypes.PER_KM:
			str += ' / km';
			break;
		case calculatePaceTypes.PER_MILE:
			str += ' / mile';
			break;
	}
	return str;
};

/**
 *
 * @param speed m/s
 * @param type /km or /mile
 */
var calculatePace = function(speed, type) {
	var result = new PaceResult(), secs, dd;
	result.type = type;

	if(speed > 0) {
		switch(type) {
			case calculatePaceTypes.PER_KM:
				secs = 1000 / speed;
				break;
			case calculatePaceTypes.PER_MILE:
				secs = constants.MILE_IN_METERS / speed;

				break;
			default:
				throw new Error('Not supported type: ' + type);
		}
		dd = new time.RelativeTime(secs);
		result.hours = dd.hours;
		result.minutes = dd.minutes;
		result.seconds = dd.seconds;
	}
	return result;
};

module.exports = {
	calculateSpeed : calculateSpeed,
	calculatePace : calculatePace,
	calculatePaceTypes : calculatePaceTypes,
	convertSpeed : convertSpeed,
	convertTypes : convertTypes
};
<|endoftext|>


<commit_before>/**
 * AMNA: Amazing Mongoose Node.js API
 *
 * @author Nate Ferrero
 * @url https://github.com/NateFerrero/amna
 *
 * AMNA - Start
 */
var multipart = require('connect-multiparty');

module.exports = function (amna, log) {
    return function (config, done) {

        /**
         * Set settings
         */
        if (!config.settings || typeof config.settings !== 'object') {
            config.settings = {};
        }
        Object.keys(config.settings).forEach(function (key) {
            amna.$express.set(key, config.settings[key]);
        });

        /**
         * Set log path
         */
        amna.$LOG_PATH = process.cwd();
        if (typeof config.log === 'string' && config.log.length) {
            if (config.log[0] === '/') {
                amna.$LOG_PATH = config.log;
            }
            else {
                amna.$LOG_PATH += '/' + config.log;
            }
        }
        else {
            amna.$LOG_PATH += '/logs/amna.log';
        }

        /**
         * Connect to mongo
         */
        amna.mongo.connect(config.mongo, function () {
            log('database connected');

            /**
             * Authentication
             */
            if (typeof amna.authentication === 'function') {
                amna.authentication = amna.authentication();
            }

            /**
             * Connect to auth service and continue when ready
             */
            amna.$auth(function () {

                /**
                 * File upload capacity
                 */
                amna.$express.use(multipart({uploadDir: process.cwd() + '/uploads'}));

                /**
                 * Register all normal modules here
                 */
                amna.$registerModules();

                /**
                 * Error handling
                 */
                amna.$express.use(function (err, req, res, next) {
                    if (typeof err !== 'object') {
                        var _err = new Error();
                        _err.reason = err;
                        err = _err;
                    }
                    err.status = err.status || 500;
                    log.request(req, 'HTTP', err.status, err);
                    var reason = 'Unknown Error';
                    if (err.status == '500') {
                        reason = 'Server Error';
                    }
                    else if (err.reason) {
                        reason = err.reason;
                    }
                    else if (err.message) {
                        reason = err.message;
                    }
                    if (!res.headersSent) {
                        res.json(err.status, amna.responses.error(reason));
                    }
                });

                /**
                 * No route matched
                 */
                amna.$express.use(function (req, res, next) {
                    log.request(req, 'HTTP', 404, 'Not Found');
                    res.json(404, amna.responses.error('Not Found'));
                });

                /**
                 * Start the express app
                 */
                if (!config.console) {
                    amna.$express.listen(config.port || 8080, done);
                }
            });
        });
    };
};
<commit_msg>Fix deprecated .json(status, data) calls<commit_after>/**
 * AMNA: Amazing Mongoose Node.js API
 *
 * @author Nate Ferrero
 * @url https://github.com/NateFerrero/amna
 *
 * AMNA - Start
 */
var multipart = require('connect-multiparty');

module.exports = function (amna, log) {
    return function (config, done) {

        /**
         * Set settings
         */
        if (!config.settings || typeof config.settings !== 'object') {
            config.settings = {};
        }
        Object.keys(config.settings).forEach(function (key) {
            amna.$express.set(key, config.settings[key]);
        });

        /**
         * Set log path
         */
        amna.$LOG_PATH = process.cwd();
        if (typeof config.log === 'string' && config.log.length) {
            if (config.log[0] === '/') {
                amna.$LOG_PATH = config.log;
            }
            else {
                amna.$LOG_PATH += '/' + config.log;
            }
        }
        else {
            amna.$LOG_PATH += '/logs/amna.log';
        }

        /**
         * Connect to mongo
         */
        amna.mongo.connect(config.mongo, function () {
            log('database connected');

            /**
             * Authentication
             */
            if (typeof amna.authentication === 'function') {
                amna.authentication = amna.authentication();
            }

            /**
             * Connect to auth service and continue when ready
             */
            amna.$auth(function () {

                /**
                 * File upload capacity
                 */
                amna.$express.use(multipart({uploadDir: process.cwd() + '/uploads'}));

                /**
                 * Register all normal modules here
                 */
                amna.$registerModules();

                /**
                 * Error handling
                 */
                amna.$express.use(function (err, req, res, next) {
                    if (typeof err !== 'object') {
                        var _err = new Error();
                        _err.reason = err;
                        err = _err;
                    }
                    err.status = err.status || 500;
                    log.request(req, 'HTTP', err.status, err);
                    var reason = 'Unknown Error';
                    if (err.status == '500') {
                        reason = 'Server Error';
                    }
                    else if (err.reason) {
                        reason = err.reason;
                    }
                    else if (err.message) {
                        reason = err.message;
                    }
                    if (!res.headersSent) {
                        res.status(err.status).json(amna.responses.error(reason));
                    }
                });

                /**
                 * No route matched
                 */
                amna.$express.use(function (req, res, next) {
                    log.request(req, 'HTTP', 404, 'Not Found');
                    res.status(404).json(amna.responses.error('Not Found'));
                });

                /**
                 * Start the express app
                 */
                if (!config.console) {
                    amna.$express.listen(config.port || 8080, done);
                }
            });
        });
    };
};
<|endoftext|>


<commit_before>var os = require('os')
var fs = require('fs')
var p = require('path')
var exec = require('child_process').exec
var spawn = require('child_process').spawn
var helpers = require('./helpers')
var format = require('util').format

var stats = {
  history: {},
  cpu: null, //used to store cpu informations
  proc: function(pid, options, done) {
    var self = this

    if(this.cpu !== null) {
      fs.readFile('/proc/uptime', 'utf8', function(err, uptime) {
        if(err)
          return done(err, null)

        if(uptime === undefined) {
          console.error("[pidusage] We couldn't find uptime from /proc/uptime")
          self.cpu.uptime = os.uptime()
        } else {
          self.cpu.uptime = uptime.split(' ')[0]
        }

        return self.proc_calc(pid, options, done)
      })
    } else {
      helpers.cpu(function(err, cpu) {
        if(err)
          return done(err, null)

        self.cpu = cpu
        return self.proc_calc(pid, options, done)
      })
    }
  },
  proc_calc: function(pid, options, done) {
    var history = this.history[pid] ? this.history[pid] : {}
    var cpu = this.cpu
    var self = this

    //Arguments to path.join must be strings
    fs.readFile(p.join('/proc', ''+pid, 'stat'), 'utf8', function(err, infos) {

      if(err)
        return done(err, null)

      //https://github.com/arunoda/node-usage/commit/a6ca74ecb8dd452c3c00ed2bde93294d7bb75aa8
      //preventing process space in name by removing values before last ) (pid (name) ...)
      var index = infos.lastIndexOf(')')
      infos = infos.substr(index + 2).split(' ')

      //according to http://man7.org/linux/man-pages/man5/proc.5.html (index 0 based - 2)
      //In kernels before Linux 2.6, start was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks
      var stat = {
          utime: parseFloat(infos[11]),
          stime: parseFloat(infos[12]),
          cutime: parseFloat(infos[13]),
          cstime: parseFloat(infos[14]),
          start: parseFloat(infos[19]) / cpu.clock_tick,
          rss: parseFloat(infos[21])
      }

      //http://stackoverflow.com/questions/16726779/total-cpu-usage-of-an-application-from-proc-pid-stat/16736599#16736599

      var childrens = options.childrens ? stat.cutime + stat.cstime : 0

      var total = stat.stime - (history.stime || 0) + stat.utime - (history.utime || 0) + childrens

      total = total / cpu.clock_tick

      //time elapsed between calls
      var seconds = history.uptime !== undefined ? cpu.uptime - history.uptime : stat.start - cpu.uptime
      seconds = Math.abs(seconds)
      seconds = seconds === 0 ? 0.1 : seconds //we sure can't divide through 0

      self.history[pid] = stat
      self.history[pid].seconds = seconds
      self.history[pid].uptime = cpu.uptime

      return done(null, {
        cpu: (total / seconds) * 100,
        memory: stat.rss * cpu.pagesize
      })
    })
  },
  /**
   * Get pid informations through ps command
   * @param  {int}   pid
   * @return  {Function} done (err, stat)
   * on os x skip headers with pcpu=,rss=
   * on linux it could be --no-header
   * on solaris 11 can't figure out a way to do this properly so...
   */
  ps: function(pid, options, done) {

    var cmd = 'ps -o pcpu,rss -p '

    if(os.platform() == 'aix')
      cmd = 'ps -o pcpu,rssize -p ' //this one could work on other platforms

    exec(cmd + pid, function(err, stdout, stderr) {
      if(err)
        return done(err, null)

      stdout = stdout.split(os.EOL)[1]
      stdout = stdout.replace(/^\s+/, '').replace(/\s\s+/g, ' ').split(' ')

      return done(null, {
        cpu: parseFloat(stdout[0].replace(',', '.')),
        memory: parseFloat(stdout[1]) * 1024
      })
    })
  },
  /**
   * This is really in a beta stage
   */
  win: function(pid, options, done) {
    // The code is tested under:
    // Windows 7 Ultimate/Enterprise SP1 64bit
    // Windows 8.1 Enterprise SP1 64bit
    // Windows 10 Professonal 64bit
    // Windows Server 2008 R2 64bit
    // Windows Server 2012 R2 64bit

    // Ref:
    // https://msdn.microsoft.com/en-us/library/aa394277(v=vs.85).aspx

    var args = 'path Win32_PerfRawData_PerfProc_Process WHERE IDProcess=' + pid + ' get PercentProcessorTime, TimeStamp_Sys100NS, WorkingSet'
    var wmic = spawn('wmic', args.split(' '), {detached: true})
    var stdout = ''
    var stderr = ''
    var self = this

    wmic.stdout.on('data', function(d) {
     stdout += d.toString()
    })

    wmic.stderr.on('data', function(d) {
      stderr += d.toString()
    })

    wmic.on('error', function(err) {
      console.error('[pidusage] Command "wmic ' + args + '" failed with error %s', err)
    })

    wmic.on('close', function(code) {
      stdout = stdout.trim()
      stderr = stderr.trim()

      if(!stdout || code !== 0) {
        var error = format('%s Wmic errored, please open an issue on https://github.com/soyuka/pidusage with this message.%s', new Date().toString(), os.EOL)
        error += format('Command was "wmic %s"%s', args, os.EOL)
        stderr = error + (stderr ? format('Wmic reported the following error: %s.', stderr) : 'Wmic reported no errors.')
        stderr = format('%s%s%s Wmic exited with code %d.%s', os.EOL, stderr, os.EOL, code, os.EOL)
        stdout ? stderr = format('%s%s Stdout was %s', stderr, os.EOL, stdout) : null

        return done(new Error(stderr , null))
      }

      // The new line in Windows is '\r\r\n'
      var lines = stdout.split(/\r?\r\n/)
      // We might need the titles someday.
      //var titles = lines[0].trim().split( / +/ )
      var values = lines[1].trim().split(/ +/)
      var cpuPercent = parseFloat(values[0])
      var cpuTime = parseFloat(values[1])
      var cpu = 0
      var memUsage   = parseFloat(values[2])

      //https://msdn.microsoft.com/en-us/library/aa394597(v=vs.85).aspx
      //https://msdn.microsoft.com/en-us/library/ms974615.aspx

      if (self.history[pid] !== undefined) {
        var history = self.history[pid]
        if (cpuTime - history.cpuTime === 0) {
          cpu = 0
        } else {
          cpu = 100 - (100 * (1 - (cpuPercent - history.cpuPercent)/(cpuTime - history.cpuTime)))
        }
      } else {
        cpu = 100 - (100 * (1 - cpuPercent/cpuTime))
      }

      self.history[pid] = {
        cpuTime: cpuTime,
        cpuPercent: cpuPercent
      }

      return done(null, {cpu : cpu, memory : memUsage})
    })
  }
}

module.exports = stats;
<commit_msg>add release to windows log<commit_after>var os = require('os')
var fs = require('fs')
var p = require('path')
var exec = require('child_process').exec
var spawn = require('child_process').spawn
var helpers = require('./helpers')
var format = require('util').format

var stats = {
  history: {},
  cpu: null, //used to store cpu informations
  proc: function(pid, options, done) {
    var self = this

    if(this.cpu !== null) {
      fs.readFile('/proc/uptime', 'utf8', function(err, uptime) {
        if(err)
          return done(err, null)

        if(uptime === undefined) {
          console.error("[pidusage] We couldn't find uptime from /proc/uptime")
          self.cpu.uptime = os.uptime()
        } else {
          self.cpu.uptime = uptime.split(' ')[0]
        }

        return self.proc_calc(pid, options, done)
      })
    } else {
      helpers.cpu(function(err, cpu) {
        if(err)
          return done(err, null)

        self.cpu = cpu
        return self.proc_calc(pid, options, done)
      })
    }
  },
  proc_calc: function(pid, options, done) {
    var history = this.history[pid] ? this.history[pid] : {}
    var cpu = this.cpu
    var self = this

    //Arguments to path.join must be strings
    fs.readFile(p.join('/proc', ''+pid, 'stat'), 'utf8', function(err, infos) {

      if(err)
        return done(err, null)

      //https://github.com/arunoda/node-usage/commit/a6ca74ecb8dd452c3c00ed2bde93294d7bb75aa8
      //preventing process space in name by removing values before last ) (pid (name) ...)
      var index = infos.lastIndexOf(')')
      infos = infos.substr(index + 2).split(' ')

      //according to http://man7.org/linux/man-pages/man5/proc.5.html (index 0 based - 2)
      //In kernels before Linux 2.6, start was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks
      var stat = {
          utime: parseFloat(infos[11]),
          stime: parseFloat(infos[12]),
          cutime: parseFloat(infos[13]),
          cstime: parseFloat(infos[14]),
          start: parseFloat(infos[19]) / cpu.clock_tick,
          rss: parseFloat(infos[21])
      }

      //http://stackoverflow.com/questions/16726779/total-cpu-usage-of-an-application-from-proc-pid-stat/16736599#16736599

      var childrens = options.childrens ? stat.cutime + stat.cstime : 0

      var total = stat.stime - (history.stime || 0) + stat.utime - (history.utime || 0) + childrens

      total = total / cpu.clock_tick

      //time elapsed between calls
      var seconds = history.uptime !== undefined ? cpu.uptime - history.uptime : stat.start - cpu.uptime
      seconds = Math.abs(seconds)
      seconds = seconds === 0 ? 0.1 : seconds //we sure can't divide through 0

      self.history[pid] = stat
      self.history[pid].seconds = seconds
      self.history[pid].uptime = cpu.uptime

      return done(null, {
        cpu: (total / seconds) * 100,
        memory: stat.rss * cpu.pagesize
      })
    })
  },
  /**
   * Get pid informations through ps command
   * @param  {int}   pid
   * @return  {Function} done (err, stat)
   * on os x skip headers with pcpu=,rss=
   * on linux it could be --no-header
   * on solaris 11 can't figure out a way to do this properly so...
   */
  ps: function(pid, options, done) {

    var cmd = 'ps -o pcpu,rss -p '

    if(os.platform() == 'aix')
      cmd = 'ps -o pcpu,rssize -p ' //this one could work on other platforms

    exec(cmd + pid, function(err, stdout, stderr) {
      if(err)
        return done(err, null)

      stdout = stdout.split(os.EOL)[1]
      stdout = stdout.replace(/^\s+/, '').replace(/\s\s+/g, ' ').split(' ')

      return done(null, {
        cpu: parseFloat(stdout[0].replace(',', '.')),
        memory: parseFloat(stdout[1]) * 1024
      })
    })
  },
  /**
   * This is really in a beta stage
   */
  win: function(pid, options, done) {
    // The code is tested under:
    // Windows 7 Ultimate/Enterprise SP1 64bit
    // Windows 8.1 Enterprise SP1 64bit
    // Windows 10 Professonal 64bit
    // Windows Server 2008 R2 64bit
    // Windows Server 2012 R2 64bit

    // Ref:
    // https://msdn.microsoft.com/en-us/library/aa394277(v=vs.85).aspx

    var args = 'path Win32_PerfRawData_PerfProc_Process WHERE IDProcess=' + pid + ' get PercentProcessorTime, TimeStamp_Sys100NS, WorkingSet'
    var wmic = spawn('wmic', args.split(' '), {detached: true})
    var stdout = ''
    var stderr = ''
    var self = this

    wmic.stdout.on('data', function(d) {
     stdout += d.toString()
    })

    wmic.stderr.on('data', function(d) {
      stderr += d.toString()
    })

    wmic.on('error', function(err) {
      console.error('[pidusage] Command "wmic ' + args + '" failed with error %s', err)
    })

    wmic.on('close', function(code) {
      stdout = stdout.trim()
      stderr = stderr.trim()

      if(!stdout || code !== 0) {
        var error = format('%s Wmic errored, please open an issue on https://github.com/soyuka/pidusage with this message.%s', new Date().toString(), os.EOL)
        error += format('Command was "wmic %s", release is %s%s', args, os.release(), os.EOL)
        stderr = error + (stderr ? format('Wmic reported the following error: %s.', stderr) : 'Wmic reported no errors.')
        stderr = format('%s%s%s Wmic exited with code %d.%s', os.EOL, stderr, os.EOL, code, os.EOL)
        stdout ? stderr = format('%s%s Stdout was %s', stderr, os.EOL, stdout) : null

        return done(new Error(stderr , null))
      }

      // The new line in Windows is '\r\r\n'
      var lines = stdout.split(/\r?\r\n/)
      // We might need the titles someday.
      //var titles = lines[0].trim().split( / +/ )
      var values = lines[1].trim().split(/ +/)
      var cpuPercent = parseFloat(values[0])
      var cpuTime = parseFloat(values[1])
      var cpu = 0
      var memUsage   = parseFloat(values[2])

      //https://msdn.microsoft.com/en-us/library/aa394597(v=vs.85).aspx
      //https://msdn.microsoft.com/en-us/library/ms974615.aspx

      if (self.history[pid] !== undefined) {
        var history = self.history[pid]
        if (cpuTime - history.cpuTime === 0) {
          cpu = 0
        } else {
          cpu = 100 - (100 * (1 - (cpuPercent - history.cpuPercent)/(cpuTime - history.cpuTime)))
        }
      } else {
        cpu = 100 - (100 * (1 - cpuPercent/cpuTime))
      }

      self.history[pid] = {
        cpuTime: cpuTime,
        cpuPercent: cpuPercent
      }

      return done(null, {cpu : cpu, memory : memUsage})
    })
  }
}

module.exports = stats;
<|endoftext|>


<commit_before>var assert = require('assert')
var EventEmitter = require('events').EventEmitter
var each = require('stream-each')
var networkSpeed = require('hyperdrive-network-speed')

module.exports = function (archive) {
  assert.ok(archive, 'lib/stats archive required')
  var stats = new EventEmitter()
  var count = {
    files: 0,
    byteLength: 0,
    length: 0,
    version: 0
  }

  update()
  if (!archive.writable) {
    count.downloaded = 0
    downloadStats()
  }

  // TODO: put in hyperdrive-stats
  stats.get = function () {
    return count
  }
  stats.network = networkSpeed(archive, {timeout: 2000})

  Object.defineProperties(stats, {
    peers: {
      enumerable: true,
      get: function () {
        if (!archive.content || !archive.content.peers) return {} // TODO: how to handle this?
        var peers = archive.content.peers
        var total = peers.length
        var complete = peers.filter(function (peer) {
          return peer.remoteLength === archive.content.length
        }).length

        return {
          total: total,
          complete: complete
        }
      }
    }
  })

  return stats

  function downloadStats () {
    if (!archive.content) return archive.once('content', downloadStats)

    var feed = archive.content
    count.downloaded = 0
    for (var i = 0; i < feed.length; i++) {
      if (feed.has(i)) count.downloaded++
    }
    stats.emit('update')

    archive.content.on('download', countDown)
    archive.once('syncing', function () {
      archive.content.removeListener('download', countDown)
      downloadStats() // recount after update
    })
    function countDown (index, data) {
      count.downloaded++
      stats.emit('update')
    }
  }

  function update () {
    if (stableVersion()) return wait()

    // get current size of archive
    var current = archive.tree.checkout(archive.version)
    var initial = archive.tree.checkout(count.version)
    var stream = initial.diff(current, {dels: true, puts: true})
    each(stream, ondata, function () {
      count.version = archive.version
      stats.emit('update', count)
      wait()
    })

    function ondata (data, next) {
      if (data.type === 'del') {
        count.byteLength -= data.value.size
        count.length -= data.value.blocks
        count.files--
      } else {
        count.byteLength += data.value.size
        count.length += data.value.blocks
        count.files++
      }
      next()
    }

    function stableVersion () {
      if (archive.version < 0) return false
      return count.version === archive.version
    }

    function wait () {
      archive.metadata.update(update)
    }
  }
}
<commit_msg>fix most progress bar issues with this one weird trick (#175)<commit_after>var assert = require('assert')
var EventEmitter = require('events').EventEmitter
var each = require('stream-each')
var networkSpeed = require('hyperdrive-network-speed')

module.exports = function (archive) {
  assert.ok(archive, 'lib/stats archive required')
  var stats = new EventEmitter()
  var count = {
    files: 0,
    byteLength: 0,
    length: 0,
    version: 0
  }

  update()
  if (!archive.writable) {
    count.downloaded = 0
    downloadStats()
  }

  // TODO: put in hyperdrive-stats
  stats.get = function () {
    return count
  }
  stats.network = networkSpeed(archive, {timeout: 2000})

  Object.defineProperties(stats, {
    peers: {
      enumerable: true,
      get: function () {
        if (!archive.content || !archive.content.peers) return {} // TODO: how to handle this?
        var peers = archive.content.peers
        var total = peers.length
        var complete = peers.filter(function (peer) {
          return peer.remoteLength === archive.content.length
        }).length

        return {
          total: total,
          complete: complete
        }
      }
    }
  })

  return stats

  function downloadStats () {
    if (!archive.content) return archive.once('content', downloadStats)

    var feed = archive.content
    count.downloaded = 0
    for (var i = 0; i < feed.length; i++) {
      if (feed.has(i)) count.downloaded++
    }
    stats.emit('update')

    archive.content.on('download', countDown)
    archive.once('syncing', function () {
      archive.content.removeListener('download', countDown)
      downloadStats() // recount after update
    })
    function countDown (index, data) {
      count.downloaded++
      stats.emit('update')
    }
  }

  function update () {
    if (stableVersion()) return wait()

    // get current size of archive
    var current = archive.tree.checkout(archive.version)
    var initial = archive.tree.checkout(count.version)
    var stream = initial.diff(current, {dels: true, puts: true})
    each(stream, ondata, function () {
      count.version = current.version
      stats.emit('update', count)
      if (!stableVersion()) return update()
      wait()
    })

    function ondata (data, next) {
      if (data.type === 'del') {
        count.byteLength -= data.value.size
        count.length -= data.value.blocks
        count.files--
      } else {
        count.byteLength += data.value.size
        count.length += data.value.blocks
        count.files++
      }
      next()
    }

    function stableVersion () {
      if (archive.version < 0) return false
      return count.version === archive.version
    }

    function wait () {
      archive.metadata.update(update)
    }
  }
}
<|endoftext|>


<commit_before>var os = require('os')
  , fs = require('fs')
  , p = require('path')
  , exec = require('child_process').exec
  , helpers = require('./helpers')

var stats = {
	history: {},
	cpu: null, //used to store cpu informations
	proc: function(pid, options, done) {
        var self = this

		if(this.cpu !== null) {
			fs.readFile('/proc/uptime', 'utf8', function(err, uptime) {
				if(err) {
					done(err, null)
				}

				self.cpu.uptime = uptime.split(' ')[0]
				self.proc_calc(pid, options, done)
			})
		} else {
			helpers.cpu(function(err, cpu) {
				self.cpu = cpu
				self.proc_calc(pid, options, done)
			})
		}
	},
	proc_calc: function(pid, options, done) {
		var history = this.history[pid] ? this.history[pid] : {}, cpu = this.cpu
		  , self = this

		//							Arguments to path.join must be strings
		fs.readFile(p.join('/proc', ''+pid, 'stat'), 'utf8', function(err, infos) {

			if(err)
				return done(err, null)

			//https://github.com/arunoda/node-usage/commit/a6ca74ecb8dd452c3c00ed2bde93294d7bb75aa8
			//preventing process space in name by removing values before last ) (pid (name) ...)
			var index = infos.lastIndexOf(')')
			infos = infos.substr(index + 2).split(' ')

			//according to http://man7.org/linux/man-pages/man5/proc.5.html (index 0 based - 2)
			var stat = {
			    utime: parseFloat(infos[11]),
			    stime: parseFloat(infos[12]),
			    cutime: parseFloat(infos[13]),
			    cstime: parseFloat(infos[14]),
                //In kernels before Linux 2.6, this value was expressed in jiffies.  Since Linux 2.6, the value is expressed in clock ticks
			    start: parseFloat(infos[19]) / cpu.clock_tick,
			    rss: parseFloat(infos[21])
			}

			//http://stackoverflow.com/questions/16726779/total-cpu-usage-of-an-application-from-proc-pid-stat/16736599#16736599

			var childrens = options.childrens ? stat.cutime + stat.cstime : 0, total

			if(history.utime) {
				total = (stat.stime - history.stime) + (stat.utime - history.utime) + childrens
			} else {
				total = stat.stime + stat.utime + childrens
			}

			total = total / cpu.clock_tick

		    //time elapsed between calls
		    var seconds = history.uptime !== undefined ? cpu.uptime - history.uptime : stat.start - cpu.uptime
				seconds = Math.abs(seconds)
				seconds = seconds === 0 ? 0.1 : seconds //we sure can't divide through 0

			self.history[pid] = stat
			self.history[pid].seconds = seconds
			self.history[pid].uptime = cpu.uptime

			return done(null, {
				cpu: (total / seconds) * 100,
				memory: stat.rss * cpu.pagesize
			})
		})
	},
	/**
	 * Get pid informations through ps command
	 * @param  {int}   pid
	 * @return  {Function} done (err, stat)
	 * on os x skip headers with pcpu=,rss=
	 * on linux it could be --no-header
	 * on solaris 11 can't figure out a way to do this properly so...
	 */
	ps: function(pid, options, done) {

		exec('ps -o pcpu,rss -p '+pid, function(error, stdout, stderr) {
			if(error) {
				return done(error)
			}

			stdout = stdout.split(os.EOL)[1]
			stdout = stdout.replace(/^\s+/, '').replace(/\s\s+/g, ' ').split(' ')

			return done(null, {
				cpu: parseFloat(stdout[0].replace(',', '.')),
				memory: parseFloat(stdout[1]) * 1024
			})
		})
	},
	/**
	 * This is really in a beta stage
	 */
	win: function(pid, options, done) {

		// var history = this.history[pid] ? this.history[pid] : {}
		//   , uptime = os.uptime()
		//   , self = this

		//http://social.msdn.microsoft.com/Forums/en-US/469ec6b7-4727-4773-9dc7-6e3de40e87b8/cpu-usage-in-for-each-active-process-how-is-this-best-determined-and-implemented-in-an?forum=csharplanguage
		exec('wmic PROCESS '+pid+' get workingsetsize,usermodetime,kernelmodetime', function(error, stdout, stderr) {
			if(error) {
				console.log(error)
				return done(error)
			}

			stdout = stdout.split(os.EOL)[1]
			stdout = stdout.replace(/\s\s+/g, ' ').split(' ')

			var stats = {
				kernelmodetime: parseFloat(stdout[0]),
				usermodetime: parseFloat(stdout[1]),
				workingsetsize: parseFloat(stdout[2])
			}

			//according to http://technet.microsoft.com/en-us/library/ee176718.aspx
			var total = (stats.usermodetime + stats.kernelmodetime) / 10000000 //seconds

		    return done(null, {
				cpu: total,
				memory: stats.workingsetsize
			})
		})
	}
}

module.exports = stats;<commit_msg>This is really anoying<commit_after>var os = require('os')
  , fs = require('fs')
  , p = require('path')
  , exec = require('child_process').exec
  , helpers = require('./helpers')

var stats = {
	history: {},
	cpu: null, //used to store cpu informations
	proc: function(pid, options, done) {
        var self = this

		if(this.cpu !== null) {
			fs.readFile('/proc/uptime', 'utf8', function(err, uptime) {
				if(err) {
					done(err, null)
				}

				self.cpu.uptime = uptime.split(' ')[0]
				self.proc_calc(pid, options, done)
			})
		} else {
			helpers.cpu(function(err, cpu) {
				self.cpu = cpu
				self.proc_calc(pid, options, done)
			})
		}
	},
	proc_calc: function(pid, options, done) {
		var history = this.history[pid] ? this.history[pid] : {}, cpu = this.cpu
		  , self = this

		//							Arguments to path.join must be strings
		fs.readFile(p.join('/proc', ''+pid, 'stat'), 'utf8', function(err, infos) {

			if(err)
				return done(err, null)

			//https://github.com/arunoda/node-usage/commit/a6ca74ecb8dd452c3c00ed2bde93294d7bb75aa8
			//preventing process space in name by removing values before last ) (pid (name) ...)
			var index = infos.lastIndexOf(')')
			infos = infos.substr(index + 2).split(' ')

			//according to http://man7.org/linux/man-pages/man5/proc.5.html (index 0 based - 2)
            //In kernels before Linux 2.6, start was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks
			var stat = {
			    utime: parseFloat(infos[11]),
			    stime: parseFloat(infos[12]),
			    cutime: parseFloat(infos[13]),
			    cstime: parseFloat(infos[14]),
			    start: parseFloat(infos[19]) / cpu.clock_tick,
			    rss: parseFloat(infos[21])
			}

			//http://stackoverflow.com/questions/16726779/total-cpu-usage-of-an-application-from-proc-pid-stat/16736599#16736599

			var childrens = options.childrens ? stat.cutime + stat.cstime : 0, total

			if(history.utime) {
				total = (stat.stime - history.stime) + (stat.utime - history.utime) + childrens
			} else {
				total = stat.stime + stat.utime + childrens
			}

			total = total / cpu.clock_tick

		    //time elapsed between calls
		    var seconds = history.uptime !== undefined ? cpu.uptime - history.uptime : stat.start - cpu.uptime
				seconds = Math.abs(seconds)
				seconds = seconds === 0 ? 0.1 : seconds //we sure can't divide through 0

			self.history[pid] = stat
			self.history[pid].seconds = seconds
			self.history[pid].uptime = cpu.uptime

			return done(null, {
				cpu: (total / seconds) * 100,
				memory: stat.rss * cpu.pagesize
			})
		})
	},
	/**
	 * Get pid informations through ps command
	 * @param  {int}   pid
	 * @return  {Function} done (err, stat)
	 * on os x skip headers with pcpu=,rss=
	 * on linux it could be --no-header
	 * on solaris 11 can't figure out a way to do this properly so...
	 */
	ps: function(pid, options, done) {

		exec('ps -o pcpu,rss -p '+pid, function(error, stdout, stderr) {
			if(error) {
				return done(error)
			}

			stdout = stdout.split(os.EOL)[1]
			stdout = stdout.replace(/^\s+/, '').replace(/\s\s+/g, ' ').split(' ')

			return done(null, {
				cpu: parseFloat(stdout[0].replace(',', '.')),
				memory: parseFloat(stdout[1]) * 1024
			})
		})
	},
	/**
	 * This is really in a beta stage
	 */
	win: function(pid, options, done) {

		// var history = this.history[pid] ? this.history[pid] : {}
		//   , uptime = os.uptime()
		//   , self = this

		//http://social.msdn.microsoft.com/Forums/en-US/469ec6b7-4727-4773-9dc7-6e3de40e87b8/cpu-usage-in-for-each-active-process-how-is-this-best-determined-and-implemented-in-an?forum=csharplanguage
		exec('wmic PROCESS '+pid+' get workingsetsize,usermodetime,kernelmodetime', function(error, stdout, stderr) {
			if(error) {
				console.log(error)
				return done(error)
			}

			stdout = stdout.split(os.EOL)[1]
			stdout = stdout.replace(/\s\s+/g, ' ').split(' ')

			var stats = {
				kernelmodetime: parseFloat(stdout[0]),
				usermodetime: parseFloat(stdout[1]),
				workingsetsize: parseFloat(stdout[2])
			}

			//according to http://technet.microsoft.com/en-us/library/ee176718.aspx
			var total = (stats.usermodetime + stats.kernelmodetime) / 10000000 //seconds

		    return done(null, {
				cpu: total,
				memory: stats.workingsetsize
			})
		})
	}
}

module.exports = stats;<|endoftext|>


<commit_before>const storage = require('node-persist');

module.exports = () => {
  storage.initSync();

  return storage;
};
<commit_msg>fixed persistent storage<commit_after>const path = require('path');
const storage = require('node-persist');
storage.initSync({
  dir: path.resolve(__dirname, '..', 'persist')
});

module.exports = () => {
  return storage;
};
<|endoftext|>


<commit_before>var orm = require('orm');
var q = require('queue-async');

// TODO when a clear API emerges, hide orm behind it
// TODO use a front in-memory cache for most used items - but
// keep the amount of used memory configurable

module.exports = function(raja, uri, cb) {
	var store = new Store(raja);
	orm.connect(uri, function(err, db) {
		if (err) return cb(err);
		store.init(db, cb);
	});
};

function Store(raja) {
	this.raja = raja;
}
Store.prototype.init = function(db, cb) {
	var Resource = this.Resource = db.define('Resource', {
		url : { type: 'text', index: true },
		mtime : { type: 'date', time: true, index: true },
		valid: { type: 'boolean', defaultValue: true },
		data: { type: 'binary' },
		code: { type: 'integer', defaultValue: 200 },
		mime: { type: 'text' }
	}, {
		table: "raja_cached_resources",
		cache: false,
		hooks: {
			beforeSave: function(next) {
				this.mtime = new Date();
				next();
			}
		},
	});
	Resource.hasMany("children", Resource, {}, {
		reverse : "parents",
		key: true
	});
	var store = this;
	Resource.drop(function(err) {
		if (err) console.error(err);
		Resource.sync(function(err) {
			cb(err, store);
		});
	});
};

Store.prototype.get = function(url, cb) {
	this.Resource.one({url: url}, cb);
};

Store.prototype.has = function(url, cb) {
	this.Resource.count({url: url}, function(err, count) {
		cb(err, !!count);
	});
};

Store.prototype.del = function(url, cb) {
	this.Resource.find({url: url}).remove(cb);
};

Store.prototype.invalidate = function(url, cb) {
	// what happens if it does not find any resource ?
	this.Resource.find({url: url}).each(function(res) {
		res.valid = false;
	}).save(cb);
};

Store.prototype.invalidateParents = function(msg, cb) {
	var raja = this.raja;
	var url = msg.room || msg.url;
	this.Resource.find({url: url}, function(err, rows) {
		var queue = q(1);
		if (rows.length > 1) return cb(new Error("invalidateParents expects only one resource at " + url));
		else if (rows.length == 0) return cb();
		var res = rows[0];
		res.getParents().each(function(par) {
			par.valid = false;
			var cmsg = raja.shallow(msg);
			cmsg.room = par.url;
			raja.send(cmsg);
		}).save(cb);
	});
};

Store.prototype.set = function(url, obj, cb) {
	if (!cb && typeof obj == "function") {
		cb = obj;
		obj = null;
	}
	if (!obj) obj = {};
	this.Resource.one({url: url}, function(err, res) {
		if (err) return cb(err);
		if (!res) res = new (this.Resource)();
		if (obj.url !== undefined) res.url = obj.url;
		else if (!res.url) res.url = url;
		if (obj.data !== undefined) res.data = obj.data;
		res.valid = obj.valid !== undefined ? obj.valid : true;
		if (obj.code !== undefined) res.code = obj.code;
		if (obj.mime !== undefined) res.mime = obj.mime;
		res.save(function(err) {
			if (err) return cb(err, res);
			if (obj.resources !== undefined) saveResources.call(this, res, obj.resources, cb);
			else cb(null, res);
		}.bind(this));
	}.bind(this));
};

function saveResources(inst, resources, cb) {
	var self = this;
	if (Array.isArray(resources)) {
		var hash = {};
		for (var i=0; i < resources.length; i++) {
			hash[resources[i]] = true;
		}
		resources = hash;
	}
	inst.getChildren(function(err, children) {
		if (err) return cb(err, inst);
//		var toRemove = [];
		var toNotSave = {};
		for (var i=0; i < children.length; i++) {
			var child = children[i];
			if (resources[child.url]) {
				// no need to add it in any way, but should we do something about status ?
				// if child.status is not between 200 and 300 set it to 200
				// ...
				// then mark it as dealt with
				toNotSave[child.url] = true;
			} else {
//				toRemove.push(child);
			}
		}
		var saveQueue = q(1);
		for (var url in resources) {
			// add this url to resources
			if (!toNotSave[url]) saveQueue.defer(self.set.bind(self), url);
		}
		saveQueue.awaitAll(function(err, toAdd) {
			if (err || toAdd.length == 0) return cb(err, inst);
			inst.addChildren(toAdd, function(err) {
				cb(err, inst);
			});
		});
	});
}

<commit_msg>msg.url is *last* source, msg.room is dest<commit_after>var orm = require('orm');
var q = require('queue-async');

// TODO when a clear API emerges, hide orm behind it
// TODO use a front in-memory cache for most used items - but
// keep the amount of used memory configurable

module.exports = function(raja, uri, cb) {
	var store = new Store(raja);
	orm.connect(uri, function(err, db) {
		if (err) return cb(err);
		store.init(db, cb);
	});
};

function Store(raja) {
	this.raja = raja;
}
Store.prototype.init = function(db, cb) {
	var Resource = this.Resource = db.define('Resource', {
		url : { type: 'text', index: true },
		mtime : { type: 'date', time: true, index: true },
		valid: { type: 'boolean', defaultValue: true },
		data: { type: 'binary' },
		code: { type: 'integer', defaultValue: 200 },
		mime: { type: 'text' }
	}, {
		table: "raja_cached_resources",
		cache: false,
		hooks: {
			beforeSave: function(next) {
				this.mtime = new Date();
				next();
			}
		},
	});
	Resource.hasMany("children", Resource, {}, {
		reverse : "parents",
		key: true
	});
	var store = this;
	Resource.drop(function(err) {
		if (err) console.error(err);
		Resource.sync(function(err) {
			cb(err, store);
		});
	});
};

Store.prototype.get = function(url, cb) {
	this.Resource.one({url: url}, cb);
};

Store.prototype.has = function(url, cb) {
	this.Resource.count({url: url}, function(err, count) {
		cb(err, !!count);
	});
};

Store.prototype.del = function(url, cb) {
	this.Resource.find({url: url}).remove(cb);
};

Store.prototype.invalidate = function(url, cb) {
	// what happens if it does not find any resource ?
	this.Resource.find({url: url}).each(function(res) {
		res.valid = false;
	}).save(cb);
};

Store.prototype.invalidateParents = function(msg, cb) {
	var raja = this.raja;
	var url = msg.room || msg.url;
	this.Resource.find({url: url}, function(err, rows) {
		var queue = q(1);
		if (rows.length > 1) return cb(new Error("invalidateParents expects only one resource at " + url));
		else if (rows.length == 0) return cb();
		var res = rows[0];
		res.getParents().each(function(par) {
			par.valid = false;
			var cmsg = raja.shallow(msg);
			if (cmsg.room) cmsg.url = cmsg.room;
			cmsg.room = par.url;
			raja.send(cmsg);
		}).save(cb);
	});
};

Store.prototype.set = function(url, obj, cb) {
	if (!cb && typeof obj == "function") {
		cb = obj;
		obj = null;
	}
	if (!obj) obj = {};
	this.Resource.one({url: url}, function(err, res) {
		if (err) return cb(err);
		if (!res) res = new (this.Resource)();
		if (obj.url !== undefined) res.url = obj.url;
		else if (!res.url) res.url = url;
		if (obj.data !== undefined) res.data = obj.data;
		res.valid = obj.valid !== undefined ? obj.valid : true;
		if (obj.code !== undefined) res.code = obj.code;
		if (obj.mime !== undefined) res.mime = obj.mime;
		res.save(function(err) {
			if (err) return cb(err, res);
			if (obj.resources !== undefined) saveResources.call(this, res, obj.resources, cb);
			else cb(null, res);
		}.bind(this));
	}.bind(this));
};

function saveResources(inst, resources, cb) {
	var self = this;
	if (Array.isArray(resources)) {
		var hash = {};
		for (var i=0; i < resources.length; i++) {
			hash[resources[i]] = true;
		}
		resources = hash;
	}
	inst.getChildren(function(err, children) {
		if (err) return cb(err, inst);
//		var toRemove = [];
		var toNotSave = {};
		for (var i=0; i < children.length; i++) {
			var child = children[i];
			if (resources[child.url]) {
				// no need to add it in any way, but should we do something about status ?
				// if child.status is not between 200 and 300 set it to 200
				// ...
				// then mark it as dealt with
				toNotSave[child.url] = true;
			} else {
//				toRemove.push(child);
			}
		}
		var saveQueue = q(1);
		for (var url in resources) {
			// add this url to resources
			if (!toNotSave[url]) saveQueue.defer(self.set.bind(self), url);
		}
		saveQueue.awaitAll(function(err, toAdd) {
			if (err || toAdd.length == 0) return cb(err, inst);
			inst.addChildren(toAdd, function(err) {
				cb(err, inst);
			});
		});
	});
}

<|endoftext|>