<commit_before>'use strict';

var _trader;

// Make sure these are higher than polling interval
// or there will be a lot of errors
var STALE_TICKER  = 180000;
var STALE_BALANCE = 180000;

Error.prototype.toJSON = function () {
  var self = this;
  var ret = {};
  Object.getOwnPropertyNames(self).forEach(function (key) {
    ret[key] = self[key];
  });
  return ret;
};

var poll = function(req, res) {
  if (req.device.unpair) {
    return res.json({
      unpair: true
    });
  }

  var rateRec = _trader.rate(req.params.currency);
  var satoshiBalanceRec = _trader.balance;

  if (rateRec === null || satoshiBalanceRec === null)
    return res.json({err: 'Server initializing'});
  if (Date.now() - rateRec.timestamp > STALE_TICKER)
    return res.json({err: 'Stale ticker'});
  if (Date.now() - rateRec.timestamp > STALE_BALANCE)
    return res.json({err: 'Stale balance'});

  var rate = rateRec.rate;

  res.json({
    err: null,
    rate: rate * _trader.config.exchanges.settings.commission,
    fiat: api.fiatBalance(0, 0),
    currency: req.params.currency,
    txLimit: parseInt(_trader.config.exchanges.settings.compliance.maximum.limit, 10)
  });
};

// TODO need to add in a UID for this trade
var trade = function(req, res) {
  api.trade.trade(req.body.fiat, req.body.satoshis, req.body.currency, function(err) {
    res.json({err: err});
  });
};

var send = function(req, res) {
  var fingerprint = req.connection.getPeerCertificate().fingerprint;
  api.send.sendBitcoins(fingerprint, req.body, function(err, txHash) {
    res.json({err: err, txHash: txHash});
  });
};

var pair = function(req, res) {
  var token = req.body.token;
  var name = req.body.name;

  _lamassuConfig.pair(
    token,
    req.connection.getPeerCertificate().fingerprint,
    name,
    function(err) {
      if (err) res.json(500, { err: err.message });
      else res.json(200);
    }
  );
};

exports.init = function(app, trader, authMiddleware) {
  _trader = trader;

  app.get('/poll/:currency', authMiddleware, poll);
  app.post('/trade', authMiddleware, trade);
  app.post('/send', authMiddleware, send);
  app.post('/pair', pair);

  return app;
};
<commit_msg>Fix `fiatBalance` call<commit_after>'use strict';

var _trader;

// Make sure these are higher than polling interval
// or there will be a lot of errors
var STALE_TICKER  = 180000;
var STALE_BALANCE = 180000;

Error.prototype.toJSON = function () {
  var self = this;
  var ret = {};
  Object.getOwnPropertyNames(self).forEach(function (key) {
    ret[key] = self[key];
  });
  return ret;
};

var poll = function(req, res) {
  if (req.device.unpair) {
    return res.json({
      unpair: true
    });
  }

  var rateRec = _trader.rate(req.params.currency);
  var satoshiBalanceRec = _trader.balance;

  if (rateRec === null || satoshiBalanceRec === null)
    return res.json({err: 'Server initializing'});
  if (Date.now() - rateRec.timestamp > STALE_TICKER)
    return res.json({err: 'Stale ticker'});
  if (Date.now() - rateRec.timestamp > STALE_BALANCE)
    return res.json({err: 'Stale balance'});

  var rate = rateRec.rate;

  res.json({
    err: null,
    rate: rate * _trader.config.exchanges.settings.commission,
    fiat: _trader.fiatBalance(0, 0),
    currency: req.params.currency,
    txLimit: parseInt(_trader.config.exchanges.settings.compliance.maximum.limit, 10)
  });
};

// TODO need to add in a UID for this trade
var trade = function(req, res) {
  api.trade.trade(req.body.fiat, req.body.satoshis, req.body.currency, function(err) {
    res.json({err: err});
  });
};

var send = function(req, res) {
  var fingerprint = req.connection.getPeerCertificate().fingerprint;
  api.send.sendBitcoins(fingerprint, req.body, function(err, txHash) {
    res.json({err: err, txHash: txHash});
  });
};

var pair = function(req, res) {
  var token = req.body.token;
  var name = req.body.name;

  _lamassuConfig.pair(
    token,
    req.connection.getPeerCertificate().fingerprint,
    name,
    function(err) {
      if (err) res.json(500, { err: err.message });
      else res.json(200);
    }
  );
};

exports.init = function(app, trader, authMiddleware) {
  _trader = trader;

  app.get('/poll/:currency', authMiddleware, poll);
  app.post('/trade', authMiddleware, trade);
  app.post('/send', authMiddleware, send);
  app.post('/pair', pair);

  return app;
};
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var express = require('express');

var flatten = require('./situation').flatten;
var Individu = require('./situation').Individu;
var openfisca = require('./simulation/openfisca');
var SituationModel = require('./models/situation');
var TaskModel = require('./models/task');
var expand = require('./situation').expand;

function ensureAuthenticated(req, res, next) {
    if (!req.isAuthenticated()) return res.send(401);
    next();
};

module.exports = function(app, passport, config) {

    app.post('/api/login', function(req, res, next) {
        passport.authenticate('local', function(err, agent) {
            if (err) return next(err);
            if (!agent) return res.send(400);
            req.logIn(agent, function(err) {
                if (err) return next(err);
                res.send(_.omit(agent.toObject(), 'password'));
            });
        })(req, res, next);
    });

    app.get('/api/profile', ensureAuthenticated, function(req, res) {
        res.send(req.user);
    });

    app.get('/api/situations/:situationId', function(req, res, next) {
        SituationModel.findById(req.params.situationId, function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(404);
            res.send(situation);
        });
    });

    app.post('/api/situations', function(req, res, next) {
        var demandeur = new Individu();
        demandeur.ajouteLogement();
        SituationModel.create(flatten(demandeur), function(err, situation) {
            if (err) return next(err);
            res.send(situation.id);
        });
    });

    app.put('/api/situations/:situationId', function(req, res, next) {
        SituationModel.findByIdAndUpdate(req.params.situationId, _.extend({ _updated: Date.now() }, _.omit(req.body, '_id')), { upsert: true }, function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(400);
            res.send(situation);

            if (situation.contact.firstName) {
                TaskModel.generateTasksForSituation(situation);
            }
        });
    });

    app.get('/api/situations/:situationId/simulation', function(req, res, next) {
        SituationModel.findById(req.params.situationId).lean().exec(function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(404);
            openfisca.simulate(expand(situation), function(err, result) {
                if (err) next(err);
                res.send(result);
            });
        });
    });

    app.get('/api/situations/:situationId/openfisca-response', function(req, res, next) {
        SituationModel.findById(req.params.situationId).lean().exec(function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(404);
            openfisca.calculate(expand(situation), function(err, result) {
                if (err) next(err);
                res.send(result);
            });
        });
    });

    app.get('/api/situations/:situationId/openfisca-request', function(req, res, next) {
        SituationModel.findById(req.params.situationId).lean().exec(function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(404);
            res.send(openfisca.buildRequest(expand(situation)));
        });
    });

    app.get('/api/tasks', function(req, res, next) {
        TaskModel
            .find()
            .populate('situation')
            .limit(20)
            .sort('-situation._updated')
            .exec(function(err, tasks) {
                if (err) return next(err);
                res.send(tasks);
            });
    });

    app.get('/api/*', function(req, res) {
        res.send(404);
    });

    var env = app.get('env');
    var viewsDirectory = config.root;
    if ('development' === env) {
        viewsDirectory += '/app/views';
    } else if ('production' === env) {
        viewsDirectory += '/public/views';
    }

    app.use('/partials', express.static(viewsDirectory + '/partials/front'));
    app.use('/back/partials', express.static(viewsDirectory + '/partials/back'));

    function renderBack(req, res) {
        res.sendfile(viewsDirectory + '/back.html');
    }

    app.route('/back').get(renderBack);
    app.route('/back/*').get(renderBack);

    app.route('/*').get(function(req, res) {
        res.sendfile(viewsDirectory + '/front.html');
    });
};
<commit_msg>Remove unnecessary semicolon<commit_after>'use strict';

var _ = require('lodash');
var express = require('express');

var flatten = require('./situation').flatten;
var Individu = require('./situation').Individu;
var openfisca = require('./simulation/openfisca');
var SituationModel = require('./models/situation');
var TaskModel = require('./models/task');
var expand = require('./situation').expand;

function ensureAuthenticated(req, res, next) {
    if (!req.isAuthenticated()) return res.send(401);
    next();
}

module.exports = function(app, passport, config) {

    app.post('/api/login', function(req, res, next) {
        passport.authenticate('local', function(err, agent) {
            if (err) return next(err);
            if (!agent) return res.send(400);
            req.logIn(agent, function(err) {
                if (err) return next(err);
                res.send(_.omit(agent.toObject(), 'password'));
            });
        })(req, res, next);
    });

    app.get('/api/profile', ensureAuthenticated, function(req, res) {
        res.send(req.user);
    });

    app.get('/api/situations/:situationId', function(req, res, next) {
        SituationModel.findById(req.params.situationId, function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(404);
            res.send(situation);
        });
    });

    app.post('/api/situations', function(req, res, next) {
        var demandeur = new Individu();
        demandeur.ajouteLogement();
        SituationModel.create(flatten(demandeur), function(err, situation) {
            if (err) return next(err);
            res.send(situation.id);
        });
    });

    app.put('/api/situations/:situationId', function(req, res, next) {
        SituationModel.findByIdAndUpdate(req.params.situationId, _.extend({ _updated: Date.now() }, _.omit(req.body, '_id')), { upsert: true }, function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(400);
            res.send(situation);

            if (situation.contact.firstName) {
                TaskModel.generateTasksForSituation(situation);
            }
        });
    });

    app.get('/api/situations/:situationId/simulation', function(req, res, next) {
        SituationModel.findById(req.params.situationId).lean().exec(function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(404);
            openfisca.simulate(expand(situation), function(err, result) {
                if (err) next(err);
                res.send(result);
            });
        });
    });

    app.get('/api/situations/:situationId/openfisca-response', function(req, res, next) {
        SituationModel.findById(req.params.situationId).lean().exec(function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(404);
            openfisca.calculate(expand(situation), function(err, result) {
                if (err) next(err);
                res.send(result);
            });
        });
    });

    app.get('/api/situations/:situationId/openfisca-request', function(req, res, next) {
        SituationModel.findById(req.params.situationId).lean().exec(function(err, situation) {
            if (err) return next(err);
            if (!situation) return res.send(404);
            res.send(openfisca.buildRequest(expand(situation)));
        });
    });

    app.get('/api/tasks', function(req, res, next) {
        TaskModel
            .find()
            .populate('situation')
            .limit(20)
            .sort('-situation._updated')
            .exec(function(err, tasks) {
                if (err) return next(err);
                res.send(tasks);
            });
    });

    app.get('/api/*', function(req, res) {
        res.send(404);
    });

    var env = app.get('env');
    var viewsDirectory = config.root;
    if ('development' === env) {
        viewsDirectory += '/app/views';
    } else if ('production' === env) {
        viewsDirectory += '/public/views';
    }

    app.use('/partials', express.static(viewsDirectory + '/partials/front'));
    app.use('/back/partials', express.static(viewsDirectory + '/partials/back'));

    function renderBack(req, res) {
        res.sendfile(viewsDirectory + '/back.html');
    }

    app.route('/back').get(renderBack);
    app.route('/back/*').get(renderBack);

    app.route('/*').get(function(req, res) {
        res.sendfile(viewsDirectory + '/front.html');
    });
};
<|endoftext|>


<commit_before>var service = require("./service");

module.exports = function(options){
  return [
    {
      method: "GET",
      path: "/discovery/announce",
      config: {
        auth: options.discoveryRoutesAuth,
        handler: function(request, reply){
          service.announce(function(err){
            if(err){
                return reply(err);
            }

            reply();
          });
        }
      }
    },
    {
      method: "GET",
      path: "/discovery/unannounce",
      config: {
        auth: options.discoveryRoutesAuth,
        handler: function(request, reply){
          service.unannounce(function(err){
            if(err){
              return reply(err);
            }
            reply();
          });
        }
      }
    },
    {
      method: "GET",
      path: "/discovery/lease",
      config: {
        auth: options.discoveryRoutesAuth,
        handler: function(request, reply){
          var lease = service.lease();
          if(!lease){
            return reply().code(404);
          }

          reply(lease);
        }
      }
    },
    {
      method: "GET",
      path: "/discovery/lastUpdate",
      config: {
        auth: options.discoveryRoutesAuth,
        handler: function(request, reply){
          reply({ lastUpdate: service.lastUpdate().toISOString() });
        }
      }
    }
  ];
};
<commit_msg>add route descriptions and tags<commit_after>var service = require("./service");

module.exports = function(options){
  return [
    {
      method: "GET",
      path: "/discovery/announce",
      config: {
        auth: options.discoveryRoutesAuth,
        handler: function(request, reply){
          service.announce(function(err){
            if(err){
                return reply(err);
            }

            reply();
          });
        },
        tags: ['discovery', 'non-cacheable'],
        description: 'announce this instance'
      }
    },
    {
      method: "GET",
      path: "/discovery/unannounce",
      config: {
        auth: options.discoveryRoutesAuth,
        handler: function(request, reply){
          service.unannounce(function(err){
            if(err){
              return reply(err);
            }
            reply();
          });
        },
        tags: ['discovery', 'non-cacheable'],
        description: 'unannounce this instance'
      }
    },
    {
      method: "GET",
      path: "/discovery/lease",
      config: {
        auth: options.discoveryRoutesAuth,
        handler: function(request, reply){
          var lease = service.lease();
          if(!lease){
            return reply().code(404);
          }

          reply(lease);
        },
        tags: ['discovery', 'non-cacheable'],
        description: 'view the lease information obtained from the announce call'
      }
    },
    {
      method: "GET",
      path: "/discovery/lastUpdate",
      config: {
        auth: options.discoveryRoutesAuth,
        handler: function(request, reply){
          reply({ lastUpdate: service.lastUpdate().toISOString() });
        },
        tags: ['discovery', 'non-cacheable'],
        description: 'the last time a discovery update was successfully retrieved'
      }
    }
  ];
};
<|endoftext|>


<commit_before>/*global phantom*/
var system  = require('system');
var webpage = require('webpage');

var page    = webpage.create();
var errd    = false;
var timeout = null;
var signal  = '[E_PHANTOMIC] ';
var url     = 'http://localhost:' + system.env.PHANTOMIC_PORT;
var debug   = system.env.PHANTOMIC_DEBUG;
var lastLog = Date.now();
var done    = false;

if (debug) {
  system.stderr.write('PHANTOMIC_DEBUG');
  url += '?debug';
}

page.onConsoleMessage = function (msg) {
  if (msg.indexOf(signal) === 0) {
    errd = true;
    msg = msg.substring(signal.length);
  }
  if (msg === '[X_PHANTOMIC]') {
    if (!done && Date.now() - lastLog > 100) {
      done = true;
      phantom.exit(errd ? 1 : 0);
    }
  } else {
    lastLog = Date.now();
    console.log(msg);
  }
};

page.onError = function (msg, trace) {
  errd = true;
  console.log(msg);
  if (trace) {
    trace.forEach(function (t) {
      console.log('    at ' + url + '/js/bundle:' + t.line);
    });
  }
};

page.open(url);
<commit_msg>Using strict<commit_after>/*global phantom*/
'use strict';

var system  = require('system');
var webpage = require('webpage');

var page    = webpage.create();
var errd    = false;
var timeout = null;
var signal  = '[E_PHANTOMIC] ';
var url     = 'http://localhost:' + system.env.PHANTOMIC_PORT;
var debug   = system.env.PHANTOMIC_DEBUG;
var lastLog = Date.now();
var done    = false;

if (debug) {
  system.stderr.write('PHANTOMIC_DEBUG');
  url += '?debug';
}

page.onConsoleMessage = function (msg) {
  if (msg.indexOf(signal) === 0) {
    errd = true;
    msg = msg.substring(signal.length);
  }
  if (msg === '[X_PHANTOMIC]') {
    if (!done && Date.now() - lastLog > 100) {
      done = true;
      phantom.exit(errd ? 1 : 0);
    }
  } else {
    lastLog = Date.now();
    console.log(msg);
  }
};

page.onError = function (msg, trace) {
  errd = true;
  console.log(msg);
  if (trace) {
    trace.forEach(function (t) {
      console.log('    at ' + url + '/js/bundle:' + t.line);
    });
  }
};

page.open(url);
<|endoftext|>


<commit_before>'use strict';

/**
 * Testing library: runner for tests.
 * (C) 2013 Alex Fernández.
 */

// requires
var Log = require('log');

// globals
var log = new Log('info');
var next;

// constants
var GREEN = '\u001b[32m';
var RED = '\u001b[1;31m';
var PURPLE = '\u001b[1;35m';
var BLACK = '\u001b[0m';
var SUCCESS_START = GREEN + '✓ ';
var END = BLACK;
var FAILURE_START = RED + '✕ ';
var UNKNOWN_START = PURPLE + '? ';
var INVALID_START = PURPLE + '??? ';
var SEPARATOR = ': ';

/**
 * A test result.
 */
var TestResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.message = null;
	var finished = false;

	/**
	 * Callback for the test result.
	 * Can only be called once.
	 */
	self.callback = function(error, result)
	{
		if (error)
		{
			// only report the first failure
			if (!self.failure)
			{
				fail(error);
			}
		}
		else
		{
			if (result && result.failure)
			{
				return fail(result);
			}
			return succeed(result);
		}
	};

	/**
	 * Report a failure.
	 */
	function fail(message)
	{
		self.failure = true;
		self.success = false;
		self.message = message;
		console.log(FAILURE_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Report a success.
	 */
	function succeed(message)
	{
		if (finished)
		{
			if (self.success)
			{
				message = 'Duplicated call to callback';
			}
			// only one success allowed
			return fail(message);
		}
		self.success = true;
		self.message = message;
		console.log(SUCCESS_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		var message = self.key;
		if (self.message)
		{
			message += SEPARATOR + self.message;
		}
		return getPrintable(self, message, indent);
	};
};

/**
 * A result that contains other results.
 */
var CompositeResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.failures = 0;
	self.results = {};

	/**
	 * Add a sub-result.
	 */
	self.add = function(result)
	{
		self.results[result.key] = result;
		if (result.success && !self.failure)
		{
			self.success = true;
		}
		if (result.failure)
		{
			self.success = false;
			self.failure = true;
		}
	};

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		indent = indent || 0;
		var message = getPrintable(self, self.key + SEPARATOR, indent) + '\n';
		message += getIndented(indent) + '{\n';
		for (var key in self.results)
		{
			var result = self.results[key];
			message += result.toString(indent + 1) + ',\n';
		}
		message += getIndented(indent) + '}';
		return message;
	};

	/**
	 * Return a shortened representation.
	 */
	self.getSummary = function()
	{
		return getPrintable(self, self.key, 0);
	};
};

/**
 * Get the printable representation for a result, like this:
 * START message END.
 * An optional indent is applied as an equivalent number of tabs.
 */
function getPrintable(result, message, indent)
{
	var start;
	if (!result.failure && !result.success)
	{
		start = UNKNOWN_START;
	}
	else if (result.success && result.failure)
	{
		start = INVALID_START;
	}
	else if (result.success)
	{
		start = SUCCESS_START;
	}
	else
	{
		start = FAILURE_START;
	}
	return getIndented(indent) + start + message + END;
}

/**
 * Get an indented string, with an equivalent number of tabs.
 */
function getIndented(indent)
{
	var indented = '';
	if (indent)
	{
		for (var i = 0; i < indent; i++)
		{
			indented += '\t';
		}
	}
	return indented;
}

/**
 * Run a series of functions sequentially. Parameters:
 *	- param: an indexed object with functions, or with nested indexed objects.
 *	- callback: a function(error, result) to pass an indexed object with the results.
 */
exports.run = function(param, callback)
{
	var series = clone(param);
	// uncaught exceptions
	process.on('uncaughtException', uncaughtException);
	var runner = new Runner('main', series);
	runner.runAll(function(error, result)
	{
		if (result && result.failure)
		{
			result.key = 'failure';
		}
		callback(error, result);
		process.removeListener('uncaughtException', uncaughtException);
	});
};

/**
 * Process an uncaught exception.
 */
function uncaughtException(error)
{
	if (!next)
	{
		return log.error('Exception without next text');
	}
	next('Uncaught exception: ' + error.stack);
}

/**
 * A runner for a series of tests.
 */
var Runner = function(key, series)
{
	// self-reference
	var self = this;

	// attributes
	self.result = new CompositeResult(key);
	var finished = false;

	/**
	 * Run all functions in the series, one by one.
	 */
	self.runAll = function(callback)
	{
		runOne(function(error, result)
		{
			if (error)
			{
				console.error('Error in test %s: %s', key, error);
			}
			if (result)
			{
				console.log('Finished test %s: %s', key, result.getSummary());
			}
			if (finished)
			{
				return;
			}
			finished = true;
			callback(error, result);
		});
	};

	/**
	 * Run one function in the given series, go to the next.
	 */
	function runOne(callback)
	{
		if (isEmpty(series))
		{
			return callback(null, self.result);
		}
		for (var key in series)
		{
			var value = series[key];
			if (!value)
			{
				return callback('Empty test for ' + key);
			}
			else if (typeof value == 'object')
			{
				var runner = new Runner(key, value);
				return runner.runAll(function(error)
				{
					if (error)
					{
						log.error('Could not run all functions');
						return;
					}
					self.result.add(runner.result);
					deleteAndRunNext(key, callback);
				});
			}
			else if (typeof value == 'function')
			{
				var testKey = key;
				if (isNumber(key) && value.name)
				{
					testKey = value.name;
				}
				var subResult = new TestResult(testKey);
				// it is a function to run
				next = getNext(key, subResult, callback);
				return value(next);
			}
			else
			{
				log.error('Key %s has an invalid value %s', key, value);
				return deleteAndRunNext(key, callback);
			}
			// only the first element in the series is used;
			// the rest are called by recursion in deleteAndRunNext()
			return;
		}
	}

	/**
	 * Get a function to call after a test.
	 */
	function getNext(key, testResult, callback)
	{
		return function(error, result)
		{
			testResult.callback(error, result);
			self.result.add(testResult);
			deleteAndRunNext(key, callback);
		};
	}

	/**
	 * Delete the current function, run the next.
	 */
	function deleteAndRunNext(key, callback)
	{
		if (!(key in series))
		{
			// already run
			return;
		}
		delete series[key];
		return process.nextTick(function()
		{
			runOne(callback);
		});
	}
};

/**
 * Test to run some functions.
 */
function testRun()
{
	var series = {
		a: function(callback) {
			callback(null, 'a');
		},
		b: {
			e: function(callback) {
				callback('e');
			},
			c: function(callback) {
				callback(null, 'c');
			},
			f: function() {
				throw new Error('exception');
			},
		},
		g: [function(callback) {
			callback(null, 'g0');
		}, function two(callback) {
			callback(null, 'g1');
		}],
	};
	exports.run(series, function(error, result)
	{
		console.assert(result.failure, 'Root should be failure');
		console.assert(result.results.a, 'Should have result for a');
		console.assert(result.results.a.success, 'Should have success for a');
		console.assert(result.results.a.message == 'a', 'Should have an a for a');
		console.assert(result.results.b, 'Should have result for b');
		console.assert(result.results.b.failure, 'Should have failure for b');
		console.assert(result.results.b.results.c, 'Should have result for b.c');
		console.assert(result.results.b.results.c.success, 'Should have success for b.c');
		console.assert(result.results.b.results.c.message == 'c', 'Should have a c for b.c');
		console.assert(result.results.b.results.e, 'Should have result for b.e');
		console.assert(result.results.b.results.e.failure, 'Should have failure for b.e');
		console.assert(result.results.b.results.e.message == 'e', 'Should have a e for b.e');
		console.assert(result.results.g, 'Should have result for g');
		console.assert(result.results.g.success, 'Should have success for g');
		console.assert(result.results.g.results[0], 'Should have result for g[0]');
		console.assert(result.results.g.results[0].success, 'Should have success for g[0]');
		console.assert(result.results.g.results[0].message == 'g0', 'Should have g0 for g[0]');
		console.assert(result.results.g.results.two, 'Should have result for two');
		console.assert(result.results.g.results.two.success, 'Should have success for two');
		console.assert(result.results.g.results.two.message == 'g1', 'Should have g1 for two');
		log.info('Test run successful with 2 failures: %s', result);
	});
}

/**
 * Clone a series of functions. Performs a sanity check.
 */
function clone(series)
{
	if (typeof series != 'object')
	{
		log.error('Invalid series %s', JSON.stringify(series));
		return;
	}
	var copy = {};
	for (var key in series)
	{
		var value = series[key];
		if (typeof value == 'function')
		{
			copy[key] = value;
		}
		else
		{
			copy[key] = clone(value);
		}
	}
	return copy;
}

/**
 * Test the clone function.
 */
function testClone()
{
	var original = {
		a: function() {},
		b: {
			c: function() {},
		},
	};
	var cloned = clone(original);
	console.assert(cloned.a, 'Cloned object should have function property');
	console.assert(typeof cloned.a == 'function', 'Cloned object should have function');
	console.assert(cloned.b, 'Cloned object should have object property');
	console.assert(typeof cloned.b == 'object', 'Cloned object should have object');
	console.assert(cloned.b.c, 'Cloned object should have sub-object property');
	console.assert(typeof cloned.b.c == 'function', 'Cloned object should have sub-object');
}

/**
 * Find out if the object is empty.
 */
function isEmpty(object)
{
	for (var key in object)
	{
		if (object[key])
		{
			return false;
		}
	}
	return true;
}

/**
 * Test the empty function.
 */
function testEmpty()
{
	console.assert(isEmpty({}), 'Empty object should be empty');
	console.assert(!isEmpty({a: 'a'}), 'Not empty object is empty');
	console.assert(isEmpty([]), 'Empty array should be empty');
	console.assert(!isEmpty(['a']), 'Not empty array is empty');
}

/**
 * Find out if the argument is a number.
 * http://stackoverflow.com/a/1830844/978796
 */
function isNumber(n)
{
	return !isNaN(parseFloat(n)) && isFinite(n);
}

/**
 * Run all module tests.
 * Cannot use testing since it is not defined yet.
 */
function test()
{
	testEmpty();
	testClone();
	testRun();
}

// run tests if invoked directly
if (__filename == process.argv[1])
{
	test();
}

<commit_msg>Reformat: test name instead of key.<commit_after>'use strict';

/**
 * Testing library: runner for tests.
 * (C) 2013 Alex Fernández.
 */

// requires
var Log = require('log');

// globals
var log = new Log('info');
var next;

// constants
var GREEN = '\u001b[32m';
var RED = '\u001b[1;31m';
var PURPLE = '\u001b[1;35m';
var BLACK = '\u001b[0m';
var SUCCESS_START = GREEN + '✓ ';
var END = BLACK;
var FAILURE_START = RED + '✕ ';
var UNKNOWN_START = PURPLE + '? ';
var INVALID_START = PURPLE + '??? ';
var SEPARATOR = ': ';

/**
 * A test result.
 */
var TestResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.message = null;
	var finished = false;

	/**
	 * Callback for the test result.
	 * Can only be called once.
	 */
	self.callback = function(error, result)
	{
		if (error)
		{
			// only report the first failure
			if (!self.failure)
			{
				fail(error);
			}
		}
		else
		{
			if (result && result.failure)
			{
				return fail(result);
			}
			return succeed(result);
		}
	};

	/**
	 * Report a failure.
	 */
	function fail(message)
	{
		self.failure = true;
		self.success = false;
		self.message = message;
		console.log(FAILURE_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Report a success.
	 */
	function succeed(message)
	{
		if (finished)
		{
			if (self.success)
			{
				message = 'Duplicated call to callback';
			}
			// only one success allowed
			return fail(message);
		}
		self.success = true;
		self.message = message;
		console.log(SUCCESS_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		var message = self.key;
		if (self.message)
		{
			message += SEPARATOR + self.message;
		}
		return getPrintable(self, message, indent);
	};
};

/**
 * A result that contains other results.
 */
var CompositeResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.failures = 0;
	self.results = {};

	/**
	 * Add a sub-result.
	 */
	self.add = function(result)
	{
		self.results[result.key] = result;
		if (result.success && !self.failure)
		{
			self.success = true;
		}
		if (result.failure)
		{
			self.success = false;
			self.failure = true;
		}
	};

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		indent = indent || 0;
		var message = getPrintable(self, self.key + SEPARATOR, indent) + '\n';
		message += getIndented(indent) + '{\n';
		for (var key in self.results)
		{
			var result = self.results[key];
			message += result.toString(indent + 1) + ',\n';
		}
		message += getIndented(indent) + '}';
		return message;
	};

	/**
	 * Return a shortened representation.
	 */
	self.getSummary = function()
	{
		return getPrintable(self, self.key, 0);
	};
};

/**
 * Get the printable representation for a result, like this:
 * START message END.
 * An optional indent is applied as an equivalent number of tabs.
 */
function getPrintable(result, message, indent)
{
	var start;
	if (!result.failure && !result.success)
	{
		start = UNKNOWN_START;
	}
	else if (result.success && result.failure)
	{
		start = INVALID_START;
	}
	else if (result.success)
	{
		start = SUCCESS_START;
	}
	else
	{
		start = FAILURE_START;
	}
	return getIndented(indent) + start + message + END;
}

/**
 * Get an indented string, with an equivalent number of tabs.
 */
function getIndented(indent)
{
	var indented = '';
	if (indent)
	{
		for (var i = 0; i < indent; i++)
		{
			indented += '\t';
		}
	}
	return indented;
}

/**
 * Run a series of functions sequentially. Parameters:
 *	- param: an indexed object with functions, or with nested indexed objects.
 *	- callback: a function(error, result) to pass an indexed object with the results.
 */
exports.run = function(param, callback)
{
	var series = clone(param);
	// uncaught exceptions
	process.on('uncaughtException', uncaughtException);
	var runner = new Runner('main', series);
	runner.runAll(function(error, result)
	{
		if (result && result.failure)
		{
			result.key = 'failure';
		}
		callback(error, result);
		process.removeListener('uncaughtException', uncaughtException);
	});
};

/**
 * Process an uncaught exception.
 */
function uncaughtException(error)
{
	if (!next)
	{
		return log.error('Exception without next text');
	}
	next('Uncaught exception: ' + error.stack);
}

/**
 * A runner for a series of tests.
 */
var Runner = function(name, series)
{
	// self-reference
	var self = this;

	// attributes
	self.result = new CompositeResult(name);
	var finished = false;

	/**
	 * Run all functions in the series, one by one.
	 */
	self.runAll = function(callback)
	{
		runOne(function(error, result)
		{
			if (error)
			{
				console.error('Error in test %s: %s', name, error);
			}
			if (result)
			{
				console.log('Finished test %s: %s', name, result.getSummary());
			}
			if (finished)
			{
				return;
			}
			finished = true;
			callback(error, result);
		});
	};

	/**
	 * Run one function in the given series, go to the next.
	 */
	function runOne(callback)
	{
		if (isEmpty(series))
		{
			return callback(null, self.result);
		}
		for (var key in series)
		{
			var value = series[key];
			if (!value)
			{
				return callback('Empty test for ' + key);
			}
			else if (typeof value == 'object')
			{
				var runner = new Runner(key, value);
				return runner.runAll(function(error)
				{
					if (error)
					{
						log.error('Could not run all functions');
						return;
					}
					self.result.add(runner.result);
					deleteAndRunNext(key, callback);
				});
			}
			else if (typeof value == 'function')
			{
				var testKey = key;
				if (isNumber(key) && value.name)
				{
					testKey = value.name;
				}
				var subResult = new TestResult(testKey);
				// it is a function to run
				next = getNext(key, subResult, callback);
				return value(next);
			}
			else
			{
				log.error('Key %s has an invalid value %s', key, value);
				return deleteAndRunNext(key, callback);
			}
			// only the first element in the series is used;
			// the rest are called by recursion in deleteAndRunNext()
			return;
		}
	}

	/**
	 * Get a function to call after a test.
	 */
	function getNext(key, testResult, callback)
	{
		return function(error, result)
		{
			testResult.callback(error, result);
			self.result.add(testResult);
			deleteAndRunNext(key, callback);
		};
	}

	/**
	 * Delete the current function, run the next.
	 */
	function deleteAndRunNext(key, callback)
	{
		if (!(key in series))
		{
			// already run
			return;
		}
		delete series[key];
		return process.nextTick(function()
		{
			runOne(callback);
		});
	}
};

/**
 * Test to run some functions.
 */
function testRun()
{
	var series = {
		a: function(callback) {
			callback(null, 'a');
		},
		b: {
			e: function(callback) {
				callback('e');
			},
			c: function(callback) {
				callback(null, 'c');
			},
			f: function() {
				throw new Error('exception');
			},
		},
		g: [function(callback) {
			callback(null, 'g0');
		}, function two(callback) {
			callback(null, 'g1');
		}],
	};
	exports.run(series, function(error, result)
	{
		console.assert(result.failure, 'Root should be failure');
		console.assert(result.results.a, 'Should have result for a');
		console.assert(result.results.a.success, 'Should have success for a');
		console.assert(result.results.a.message == 'a', 'Should have an a for a');
		console.assert(result.results.b, 'Should have result for b');
		console.assert(result.results.b.failure, 'Should have failure for b');
		console.assert(result.results.b.results.c, 'Should have result for b.c');
		console.assert(result.results.b.results.c.success, 'Should have success for b.c');
		console.assert(result.results.b.results.c.message == 'c', 'Should have a c for b.c');
		console.assert(result.results.b.results.e, 'Should have result for b.e');
		console.assert(result.results.b.results.e.failure, 'Should have failure for b.e');
		console.assert(result.results.b.results.e.message == 'e', 'Should have a e for b.e');
		console.assert(result.results.g, 'Should have result for g');
		console.assert(result.results.g.success, 'Should have success for g');
		console.assert(result.results.g.results[0], 'Should have result for g[0]');
		console.assert(result.results.g.results[0].success, 'Should have success for g[0]');
		console.assert(result.results.g.results[0].message == 'g0', 'Should have g0 for g[0]');
		console.assert(result.results.g.results.two, 'Should have result for two');
		console.assert(result.results.g.results.two.success, 'Should have success for two');
		console.assert(result.results.g.results.two.message == 'g1', 'Should have g1 for two');
		log.info('Test run successful with 2 failures: %s', result);
	});
}

/**
 * Clone a series of functions. Performs a sanity check.
 */
function clone(series)
{
	if (typeof series != 'object')
	{
		log.error('Invalid series %s', JSON.stringify(series));
		return;
	}
	var copy = {};
	for (var key in series)
	{
		var value = series[key];
		if (typeof value == 'function')
		{
			copy[key] = value;
		}
		else
		{
			copy[key] = clone(value);
		}
	}
	return copy;
}

/**
 * Test the clone function.
 */
function testClone()
{
	var original = {
		a: function() {},
		b: {
			c: function() {},
		},
	};
	var cloned = clone(original);
	console.assert(cloned.a, 'Cloned object should have function property');
	console.assert(typeof cloned.a == 'function', 'Cloned object should have function');
	console.assert(cloned.b, 'Cloned object should have object property');
	console.assert(typeof cloned.b == 'object', 'Cloned object should have object');
	console.assert(cloned.b.c, 'Cloned object should have sub-object property');
	console.assert(typeof cloned.b.c == 'function', 'Cloned object should have sub-object');
}

/**
 * Find out if the object is empty.
 */
function isEmpty(object)
{
	for (var key in object)
	{
		if (object[key])
		{
			return false;
		}
	}
	return true;
}

/**
 * Test the empty function.
 */
function testEmpty()
{
	console.assert(isEmpty({}), 'Empty object should be empty');
	console.assert(!isEmpty({a: 'a'}), 'Not empty object is empty');
	console.assert(isEmpty([]), 'Empty array should be empty');
	console.assert(!isEmpty(['a']), 'Not empty array is empty');
}

/**
 * Find out if the argument is a number.
 * http://stackoverflow.com/a/1830844/978796
 */
function isNumber(n)
{
	return !isNaN(parseFloat(n)) && isFinite(n);
}

/**
 * Run all module tests.
 * Cannot use testing since it is not defined yet.
 */
function test()
{
	testEmpty();
	testClone();
	testRun();
}

// run tests if invoked directly
if (__filename == process.argv[1])
{
	test();
}

<|endoftext|>


<commit_before>"use strict";

var _ = require('underscore');
var helpers = require('./helpers');
var logger = require('./logger');

function afterRun(keyword, keywordsToRun, keywordsDefined, localVars, then, returnValue) {
    logger('Returned from ' + keyword.name + ', return value:', returnValue);

    if(keyword.returnVar && returnValue !== undefined) {
        localVars.set(keyword.returnVar, returnValue);
    }

    runKeyword(keywordsToRun, keywordsDefined, localVars, then);
}

function getKeyword(name, keywordsDefined) {
    var def = keywordsDefined[name];

    if(!def) {
        console.error("Can not find keyword '" + name + "'");
        throw new Error("Can not find keyword '" + name + "'");
    } else {
        return def;
    }
}

function runKeyword(keywordsToRun, keywordsDefined, localVars, then) {
    var keyword = keywordsToRun.shift();

    if(!keyword) {
        var retVal = localVars.get('return');
        localVars.remove('return');
        return (then || _.identity)(retVal);
    }

    var def = getKeyword(keyword.name, keywordsDefined);

    logger('Calling def of keyword ' + keyword.name + " with args", JSON.stringify(keyword.args));
    var replacedArgs = helpers.replaceArgPlaceholders(keyword.args, localVars);
    logger('Calling def of keyword ' + keyword.name + " with REPLACED args", JSON.stringify(replacedArgs));

    var defArgs = [afterRun.bind(null, keyword, keywordsToRun, keywordsDefined, localVars, then)].concat(replacedArgs);
    defArgs.push(keyword);
    defArgs.push(localVars);

    def.apply(null, defArgs);
}

/**
    Take the following ARRAY as an argument:
    [n]: keywords to run
    [last]["next"]: next()
    [last]["args"]: args
    [last]["keywordInfo"]: keywordInfo
    [last]["vars"]: vars
*/
function run(keywordsToRun, keywordsDefined, passedArguments) {
    var vars;

    vars = helpers.createLocalEnv((passedArguments || {}).args || []);
    keywordsToRun = helpers.createKeywords(keywordsToRun);

    return {
        then: function(callback) {
            runKeyword(keywordsToRun, keywordsDefined, vars, callback);
        }
    };
}

module.exports = Object.freeze({
    run: run
});<commit_msg>Cleaned logging<commit_after>"use strict";

var _ = require('underscore');
var helpers = require('./helpers');
var logger = require('./logger');

function afterRun(keyword, keywordsToRun, keywordsDefined, localVars, then, returnValue) {
    logger('Returned from ' + keyword.name + ', return value:', returnValue);

    if(keyword.returnVar && returnValue !== undefined) {
        localVars.set(keyword.returnVar, returnValue);
    }

    runKeyword(keywordsToRun, keywordsDefined, localVars, then);
}

function getKeyword(name, keywordsDefined) {
    var def = keywordsDefined[name];

    if(!def) {
        console.error("Can not find keyword '" + name + "'");
        throw new Error("Can not find keyword '" + name + "'");
    } else {
        return def;
    }
}

function runKeyword(keywordsToRun, keywordsDefined, localVars, then) {
    var keyword = keywordsToRun.shift();

    if(!keyword) {
        var retVal = localVars.get('return');
        localVars.remove('return');
        return (then || _.identity)(retVal);
    }

    var replacedArgs = helpers.replaceArgPlaceholders(keyword.args, localVars);
    var callback = afterRun.bind(null, keyword, keywordsToRun, keywordsDefined, localVars, then);
    var defArgs = [callback].concat(replacedArgs);
    defArgs.push(keyword);
    defArgs.push(localVars);

    logger("Calling keyword '" + keyword.name + "'. Original args " +
        JSON.stringify(keyword.args) + " replaced with " + JSON.stringify(replacedArgs));

    var def = getKeyword(keyword.name, keywordsDefined);
    def.apply(null, defArgs);
}

/**
    Take the following ARRAY as an argument:
    [n]: keywords to run
    [last]["next"]: next()
    [last]["args"]: args
    [last]["keywordInfo"]: keywordInfo
    [last]["vars"]: vars
*/
function run(keywordsToRun, keywordsDefined, passedArguments) {
    var vars;

    vars = helpers.createLocalEnv((passedArguments || {}).args || []);
    keywordsToRun = helpers.createKeywords(keywordsToRun);

    return {
        then: function(callback) {
            runKeyword(keywordsToRun, keywordsDefined, vars, callback);
        }
    };
}

module.exports = Object.freeze({
    run: run
});<|endoftext|>


<commit_before>/**
 * Sitespeed.io - How speedy is your site? (http://www.sitespeed.io)
 * Copyright (c) 2014, Peter Hedenskog, Tobias Lidskog
 * and other contributors
 * Released under the Apache 2.0 License
 */
var crawler = require('./crawler'),
  Analyzer = require('./analyze/analyzer'),
  HTMLRenderer = require('./htmlRenderer'),
  Collector = require('./collector'),
  JUnitRenderer = require('./junitRenderer'),
  Graphite = require('./graphite'),
  logger = require('./log'),
  path = require('path'),
  dateFormat = require('dateformat'),
  fs = require('fs-extra'),
  config = require('./conf'),
  async = require("async"),
  urlParser = require('url'),
  os = require('os'),
  EOL = os.EOL,
  binPath = require('phantomjs').path,
  childProcess = require('child_process'),
  log = require('winston');

module.exports = Runner;

function Runner() {
  this.analyzer = new Analyzer();
  this.collector = new Collector();
  this.htmlRenderer = new HTMLRenderer();
  this.junitRenderer = new JUnitRenderer(this.collector);
  this.graphite = new Graphite(config.graphiteHost, config.graphitePort, config
    .graphiteNamespace, this.collector);
}

Runner.prototype.run = function(finshedCb) {
  finshedCb = finshedCb || function() {};
  logVersions();
  var self = this;
  if (config.sites) {
      this.sites = {};
      var urls = fs.readFileSync(config.sites).toString().split(EOL);
      var queue = async.queue(this._setupSite, 1);
      log.log('info', 'Analyze '  + urls.length + ' sites');
    urls.forEach(function (url) {
      if (url!=="") queue.push({
        'url': url,
        'runner': self
      }, function() {
        log.log('info', "Finished with site " + url);
      });
    });
    queue.drain = function() {
      async.parallel({
          copySiteAssets: function(cb) {
            self.htmlRenderer.copyAssets(path.join(config.run.absResultDir,'..'), cb);
          },
          renderSites: function(cb) {
            self.htmlRenderer.renderSites(self.sites, cb);
          }
        },
          function(err, results) {
            if (!err)
              log.log('info', "Wrote sites result to " + config.run.absResultDir);
            finshedCb();
          });
    };

  }
  else {
    // setup the directories needed
    var dataDir = path.join(config.run.absResultDir, config.dataDir);
    fs.mkdirs(dataDir, function(err) {
      if (err) {
        log.log('error', "Couldn't create the data dir:" + dataDir + ' ' + err);
        throw err;
      } else {
        // TODO needs t be stored for sites also
        // store the config file, so we can backtrack errors and/or use it again
        fs.writeFile(path.join(config.run.absResultDir, 'config.json'), JSON.stringify(
          config), function(err) {
          if (err) throw err;
        });

        self._fetchData(finshedCb);
      }
    });
  }
  };

Runner.prototype._setupSite = function(args, cb) {
  var url = args.url;
  config.url= url;
  config.urlObject = urlParser.parse(config.url);
  config.run.absResultDir = path.join(__dirname, '../',config.resultBaseDir, 'sites', dateFormat(config.run.date, "yyyy-mm-dd-HH-MM-ss"), config.urlObject.hostname );
  // setup the directories needed
  var dataDir = path.join(config.run.absResultDir, config.dataDir);
  fs.mkdirs(dataDir, function (err) {
    if (err) {
      log.log('error', "Couldn't create the data dir:" + dataDir + ' ' + err);
      throw err;
    }
    else args.runner._fetchData(cb);
  });

};

Runner.prototype._fetchData = function(cb) {
  var self = this;

  /**
  This is the main flow of the application and this is what we do:
    1. Fetch the URL:s that will be analyzed, either we crawl a site using
       a start url or we read the URL:s from a file.
    2. Finetune the URL:s = do other thing that's needed, store them to disk etc.
    3. Let the analyser take a go at the URL:s, the analyzer
       got a lot to do, lets check the analyzer.js file
    4. The analyze is finished, lets create output
  **/
  async.waterfall([
    function(cb) {
      self._fetchUrls(crawler, cb);
    },
    function(okUrls, errorUrls, cb) {
      self._fineTuneUrls(okUrls, errorUrls, cb);
    },
    function(urls, downloadErrors, cb) {
      self._analyze(urls, downloadErrors, cb);
    },
    function(downloadErrors, analysisErrors, cb) {
      self._createOutput(downloadErrors, analysisErrors, cb);
    }
  ], function(err, result) {
    if (err)
        log.log('error', err);
    cb();
  });
};

Runner.prototype._fineTuneUrls = function (okUrls, errorUrls, callback) {
  var downloadErrors = {};
  Object.keys(errorUrls).forEach(function(url) {
    log.log('error', "Failed to download " + url);
    downloadErrors[url] = errorUrls[url];
  });

  // limit
  if (config.maxPagesToTest) {
    if (okUrls.length > config.maxPagesToTest)
      okUrls.length = config.maxPagesToTest;
  }
  if (okUrls.length === 0) {
    log.log('info', "Didn't get any URLs");
    throw Error('No URL:s to analyze');
  }
  saveUrls(okUrls);
  callback(null, okUrls, downloadErrors);
};

Runner.prototype._fetchUrls = function (crawler, callback) {
  if (config.url) {
    log.log('info', "Will crawl from start point " + config.url +
      " with crawl depth " + config.deep);
    crawler.crawl(config.url, function(okUrls, errorUrls) {
      callback(null, okUrls, errorUrls);
    });
  } else {
    fs.readFile(config.file, function (err, data) {
      if (err) throw err;
      var urls = data.toString().split(EOL);
      urls = urls.filter(function(l) {
        return l.length > 0;
      });

      callback(null, urls, {});
    });
  }
};

Runner.prototype._analyze = function (urls, downloadErrors, callback) {
  var analysisErrors = {};
  var self = this;
  log.log('info', "Will analyze " + urls.length + " pages");
  this.analyzer.analyze(urls, this.collector, downloadErrors, analysisErrors, function(err, url, pageData) {

    if (err) {
      log.log('error', 'Could not analyze ' + url + ' (' + JSON.stringify(err) +
        ')');
      analysisErrors[url] = err;
      return;
    }

    if (config.junit)
      self.junitRenderer.renderForEachPage(url, pageData);
    self.htmlRenderer.renderPage(url, pageData, function() {});
  }, callback);
};

function saveUrls(urls) {
  fs.writeFile(path.join(config.run.absResultDir, 'data', 'urls.txt'), urls.join(
    EOL), function(err) {
    if (err) {
      throw err;
    }
  });
}

function logVersions() {
  /*
  log.log('info', 'OS:' + os.platform() + ' release:' + os.release());
  log.log('info', 'sitespeed.io:' + require("../package.json").version);
  childProcess.execFile(binPath, ['--version'], {
    timeout: 120000
  }, function(err, stdout, stderr) {
    log.log('info', 'phantomjs:' + stdout);
  });

  childProcess.exec('java -version', {
    timeout: 120000
  }, function(err, stdout, stderr) {
    log.log('info', 'java:' + stderr);
  });
  */
}

Runner.prototype._createOutput = function (downloadErrors, analysisErrors, callBack) {
  log.log('info', 'Done analyzing urls');

  // fetch all the data we need, and then generate the output
  var aggregates = this.collector.createAggregates();
  var assets = this.collector.createCollections().assets;
  var pages = this.collector.createCollections().pages;

  var self = this;

  if (this.sites)
    this.sites[config.url] = aggregates;

  /* We got a lot of things to do, lets generate all results
  in parallel and then let us know when we are finished
  */
  async.parallel({
      renderSummary: function(cb) {
        self.htmlRenderer.renderSummary(aggregates, cb);
      },
      renderAssets: function(cb) {
        self.htmlRenderer.renderAssets(assets, cb);
      },
      renderPages: function(cb) {
        self.htmlRenderer.renderPages(pages, cb);
      },
      renderRules: function(cb) {
        // TODO the rules needs to be generated after ...
        self.htmlRenderer.renderRules(cb);
      },
      renderErrors: function(cb) {
        self.htmlRenderer.renderErrors(downloadErrors, analysisErrors, cb);
      },
      copyAssets: function(cb) {
        self.htmlRenderer.copyAssets(config.run.absResultDir, cb);
      },
      renderScreenshots: function(cb) {
        if (config.screenshot) {
          self.htmlRenderer.renderScreenshots(pages, cb);
        } else cb();
      },
      sendToGraphite: function(cb) {
        if (config.graphiteHost)
          self.graphite.sendPageData(aggregates, pages, cb);
        else cb();
      },
      renderJUnit: function(cb) {
        if (config.junit)
          self.junitRenderer.renderAfterFullAnalyse(cb);
        else cb();
      }
    },
    function(err, results) {
      if (!err)
        log.log('info', "Wrote results to " + config.run.absResultDir);
      callBack();
    });
};
<commit_msg>restructure part 1 to do everything async and better structure #445<commit_after>/**
 * Sitespeed.io - How speedy is your site? (http://www.sitespeed.io)
 * Copyright (c) 2014, Peter Hedenskog, Tobias Lidskog
 * and other contributors
 * Released under the Apache 2.0 License
 */
var crawler = require('./crawler'),
  Analyzer = require('./analyze/analyzer'),
  HTMLRenderer = require('./htmlRenderer'),
  Collector = require('./collector'),
  JUnitRenderer = require('./junitRenderer'),
  Graphite = require('./graphite'),
  logger = require('./log'),
  path = require('path'),
  dateFormat = require('dateformat'),
  fs = require('fs-extra'),
  config = require('./conf'),
  async = require("async"),
  urlParser = require('url'),
  os = require('os'),
  EOL = os.EOL,
  binPath = require('phantomjs').path,
  childProcess = require('child_process'),
  log = require('winston');

module.exports = Runner;

function Runner() {
  this.analyzer = new Analyzer();
  this.collector = new Collector();
  this.htmlRenderer = new HTMLRenderer();
  this.junitRenderer = new JUnitRenderer(this.collector);
  this.graphite = new Graphite(config.graphiteHost, config.graphitePort, config
    .graphiteNamespace, this.collector);
}

Runner.prototype.run = function(finshedCb) {
  finshedCb = finshedCb || function() {};
  var self = this;

  async.series([
    function(callback){
        // create the home data dir
        var dataDir = path.join(config.run.absResultDir, config.dataDir);
        fs.mkdirs(dataDir, function(err) {
          if (err) throw err;
          callback(err, null);
        });
    },
    function(callback){
      // write the configuration file
      fs.writeFile(path.join(config.run.absResultDir, 'config.json'), JSON.stringify(
        config), function(err) {
          callback(err, null);
      });

    },
    function(callback){
      logVersions(callback);
    }
    ],

    function(err, results){
      if (err) throw err;
        if (config.sites) {
            self.sites = {};
            var urls = fs.readFileSync(config.sites).toString().split(EOL);
            var queue = async.queue(self._setupSite, 1);
            log.log('info', 'Analyze '  + urls.length + ' sites');
          urls.forEach(function (url) {
            if (url!=="") queue.push({
              'url': url,
              'runner': self
            }, function() {
              log.log('info', "Finished with site " + url);
            });
          });
          queue.drain = function() {
            async.parallel({
                copySiteAssets: function(cb) {
                  self.htmlRenderer.copyAssets(path.join(config.run.absResultDir,'..'), cb);
                },
                renderSites: function(cb) {
                  self.htmlRenderer.renderSites(self.sites, cb);
                }
              },
                function(err, results) {
                  if (!err)
                    log.log('info', "Wrote sites result to " + config.run.absResultDir);
                  finshedCb();
                });
          };

        }
        else
          self._fetchData(finshedCb);
    });
};

Runner.prototype._setupSite = function(args, cb) {
  var url = args.url;
  config.url= url;
  config.urlObject = urlParser.parse(config.url);
  config.run.absResultDir = path.join(__dirname, '../',config.resultBaseDir, 'sites', dateFormat(config.run.date, "yyyy-mm-dd-HH-MM-ss"), config.urlObject.hostname );
  // setup the directories needed
  var dataDir = path.join(config.run.absResultDir, config.dataDir);
  fs.mkdirs(dataDir, function (err) {
    if (err) {
      log.log('error', "Couldn't create the data dir:" + dataDir + ' ' + err);
      throw err;
    }
    else args.runner._fetchData(cb);
  });

};

Runner.prototype._fetchData = function(cb) {
  var self = this;

  /**
  This is the main flow of the application and this is what we do:
    1. Fetch the URL:s that will be analyzed, either we crawl a site using
       a start url or we read the URL:s from a file.
    2. Finetune the URL:s = do other thing that's needed, store them to disk etc.
    3. Let the analyser take a go at the URL:s, the analyzer
       got a lot to do, lets check the analyzer.js file
    4. The analyze is finished, lets create output
  **/
  async.waterfall([
    function(cb) {
      self._fetchUrls(crawler, cb);
    },
    function(okUrls, errorUrls, cb) {
      self._fineTuneUrls(okUrls, errorUrls, cb);
    },
    function(urls, downloadErrors, cb) {
      self._analyze(urls, downloadErrors, cb);
    },
    function(downloadErrors, analysisErrors, cb) {
      self._createOutput(downloadErrors, analysisErrors, cb);
    }
  ], function(err, result) {
    if (err)
        log.log('error', err);
    cb();
  });
};

Runner.prototype._fineTuneUrls = function (okUrls, errorUrls, callback) {
  var downloadErrors = {};
  Object.keys(errorUrls).forEach(function(url) {
    log.log('error', "Failed to download " + url);
    downloadErrors[url] = errorUrls[url];
  });

  // limit
  if (config.maxPagesToTest) {
    if (okUrls.length > config.maxPagesToTest)
      okUrls.length = config.maxPagesToTest;
  }
  if (okUrls.length === 0) {
    log.log('info', "Didn't get any URLs");
    throw Error('No URL:s to analyze');
  }
  saveUrls(okUrls);
  callback(null, okUrls, downloadErrors);
};

Runner.prototype._fetchUrls = function (crawler, callback) {
  if (config.url) {
    log.log('info', "Will crawl from start point " + config.url +
      " with crawl depth " + config.deep);
    crawler.crawl(config.url, function(okUrls, errorUrls) {
      callback(null, okUrls, errorUrls);
    });
  } else {
    fs.readFile(config.file, function (err, data) {
      if (err) throw err;
      var urls = data.toString().split(EOL);
      urls = urls.filter(function(l) {
        return l.length > 0;
      });

      callback(null, urls, {});
    });
  }
};

Runner.prototype._analyze = function (urls, downloadErrors, callback) {
  var analysisErrors = {};
  var self = this;
  log.log('info', "Will analyze " + urls.length + " pages");
  this.analyzer.analyze(urls, this.collector, downloadErrors, analysisErrors, function(err, url, pageData) {

    if (err) {
      log.log('error', 'Could not analyze ' + url + ' (' + JSON.stringify(err) +
        ')');
      analysisErrors[url] = err;
      return;
    }

    if (config.junit)
      self.junitRenderer.renderForEachPage(url, pageData);
    self.htmlRenderer.renderPage(url, pageData, function() {});
  }, callback);
};

function saveUrls(urls) {
  fs.writeFile(path.join(config.run.absResultDir, 'data', 'urls.txt'), urls.join(
    EOL), function(err) {
    if (err) {
      throw err;
    }
  });
}

function logVersions(cb) {

  async.parallel([

      function(callback) {
        childProcess.execFile(binPath, ['--version'], {
          timeout: 120000
        }, function(err, stdout, stderr) {
          callback(null, stdout);
        });
      },
      function(callback) {
        childProcess.exec('java -version', {
          timeout: 120000
        }, function(err, stdout, stderr) {
          callback(null, stderr);
        });
      }
    ],

    function(err, results) {
      log.log('info', 'OS: ' + os.platform() + ' ' + os.release() + ' sitespeed:' + require("../package.json").version +
        ' ' + ' phantomJs:' + results[0].replace(EOL,'') + ' java:' + results[1].split(EOL)[0]);
      cb(null, null);
    });

}

Runner.prototype._createOutput = function (downloadErrors, analysisErrors, callBack) {
  log.log('info', 'Done analyzing urls');

  // fetch all the data we need, and then generate the output
  var aggregates = this.collector.createAggregates();
  var assets = this.collector.createCollections().assets;
  var pages = this.collector.createCollections().pages;

  var self = this;

  if (this.sites)
    this.sites[config.url] = aggregates;

  /* We got a lot of things to do, lets generate all results
  in parallel and then let us know when we are finished
  */
  async.parallel({
      renderSummary: function(cb) {
        self.htmlRenderer.renderSummary(aggregates, cb);
      },
      renderAssets: function(cb) {
        self.htmlRenderer.renderAssets(assets, cb);
      },
      renderPages: function(cb) {
        self.htmlRenderer.renderPages(pages, cb);
      },
      renderRules: function(cb) {
        // TODO the rules needs to be generated after ...
        self.htmlRenderer.renderRules(cb);
      },
      renderErrors: function(cb) {
        self.htmlRenderer.renderErrors(downloadErrors, analysisErrors, cb);
      },
      copyAssets: function(cb) {
        self.htmlRenderer.copyAssets(config.run.absResultDir, cb);
      },
      renderScreenshots: function(cb) {
        if (config.screenshot) {
          self.htmlRenderer.renderScreenshots(pages, cb);
        } else cb();
      },
      sendToGraphite: function(cb) {
        if (config.graphiteHost)
          self.graphite.sendPageData(aggregates, pages, cb);
        else cb();
      },
      renderJUnit: function(cb) {
        if (config.junit)
          self.junitRenderer.renderAfterFullAnalyse(cb);
        else cb();
      }
    },
    function(err, results) {
      if (!err)
        log.log('info', "Wrote results to " + config.run.absResultDir);
      callBack();
    });
};
<|endoftext|>


<commit_before>
var utils = require('./utils'),
    Parser = require('xmldom').DOMParser,
    SignedXml = require('xml-crypto').SignedXml,
    xmlenc = require('xml-encryption'),
    moment = require('moment'),
    xmlNameValidator = require('xml-name-validator'),
    is_uri = require('valid-url').is_uri;

var fs = require('fs');
var path = require('path');
var saml20 = fs.readFileSync(path.join(__dirname, 'saml20.template')).toString();

var NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:assertion';

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'
  }
};

function getAttributeType(value){
  switch(typeof value) {
    case "string":
      return 'xs:string';
    case "boolean":
      return 'xs:boolean';
    case "number":
      // Maybe we should fine-grain this type and check whether it is an integer, float, double xsi:types
      return 'xs:double';
    default:
      return 'xs:anyType';
  }
}

function getNameFormat(name){
  if (is_uri(name)){
    return 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri';
  }

  //  Check that the name is a valid xs:Name -> https://www.w3.org/TR/xmlschema-2/#Name
  //  xmlNameValidate.name takes a string and will return an object of the form { success, error },
  //  where success is a boolean
  //  if it is false, then error is a string containing some hint as to where the match went wrong.
  if (xmlNameValidator.name(name).success){
    return 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic';
  }

  // Default value
  return 'urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified';
}

exports.create = function(options, callback) {
  if (!options.key)
    throw new Error('Expect a private key in pem format');

  if (!options.cert)
    throw new Error('Expect a public key cert in pem format');

  options.signatureAlgorithm = options.signatureAlgorithm || 'rsa-sha256';
  options.digestAlgorithm = options.digestAlgorithm || 'sha256';

  options.includeAttributeNameFormat = (typeof options.includeAttributeNameFormat !== 'undefined') ? options.includeAttributeNameFormat : true;
  options.typedAttributes = (typeof options.typedAttributes !== 'undefined') ? options.typedAttributes : true;

  // 0.10.1 added prefix, but we want to name it signatureNamespacePrefix - This is just to keep supporting prefix
  options.signatureNamespacePrefix = options.signatureNamespacePrefix || options.prefix;
  options.signatureNamespacePrefix = typeof options.signatureNamespacePrefix === 'string' ? options.signatureNamespacePrefix : '' ;

  var cert = utils.pemToCert(options.cert);

  var sig = new SignedXml(null, { signatureAlgorithm: algorithms.signature[options.signatureAlgorithm], idAttribute: 'ID' });
  sig.addReference("//*[local-name(.)='Assertion']",
                  ["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;

  sig.keyInfoProvider = {
    getKeyInfo: function (key, prefix) {
      prefix = prefix ? prefix + ':' : prefix;
      return "<" + prefix + "X509Data><" + prefix + "X509Certificate>" + cert + "</" + prefix + "X509Certificate></" + prefix + "X509Data>";
    }
  };

  var doc;
  try {
    doc = new Parser().parseFromString(saml20.toString());
  } catch(err){
    return utils.reportError(err, callback);
  }

  doc.documentElement.setAttribute('ID', '_' + (options.uid || utils.uid(32)));
  if (options.issuer) {
    var issuer = doc.documentElement.getElementsByTagName('saml:Issuer');
    issuer[0].textContent = options.issuer;
  }

  var now = moment.utc();
  doc.documentElement.setAttribute('IssueInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
  var conditions = doc.documentElement.getElementsByTagName('saml:Conditions');
  var confirmationData = doc.documentElement.getElementsByTagName('saml:SubjectConfirmationData');

  if (options.lifetimeInSeconds) {
    conditions[0].setAttribute('NotBefore', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
    conditions[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));

    confirmationData[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
  }

  if (options.audiences) {
    var audienceRestriction = doc.createElementNS(NAMESPACE, 'saml:AudienceRestriction');
    var audiences = options.audiences instanceof Array ? options.audiences : [options.audiences];
    audiences.forEach(function (audience) {
      var element = doc.createElementNS(NAMESPACE, 'saml:Audience');
      element.textContent = audience;
      audienceRestriction.appendChild(element);
    });

    conditions[0].appendChild(audienceRestriction);
  }

  if (options.recipient)
    confirmationData[0].setAttribute('Recipient', options.recipient);

  if (options.inResponseTo)
    confirmationData[0].setAttribute('InResponseTo', options.inResponseTo);

  if (options.attributes) {
    var statement = doc.createElementNS(NAMESPACE, 'saml:AttributeStatement');
    statement.setAttribute('xmlns:xs', 'http://www.w3.org/2001/XMLSchema');
    statement.setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
    doc.documentElement.appendChild(statement);
    Object.keys(options.attributes).forEach(function(prop) {
      if(typeof options.attributes[prop] === 'undefined') return;
      // <saml:Attribute AttributeName="name" AttributeNamespace="http://schemas.xmlsoap.org/claims/identity">
      //    <saml:AttributeValue>Foo Bar</saml:AttributeValue>
      // </saml:Attribute>
      var attributeElement = doc.createElementNS(NAMESPACE, 'saml:Attribute');
      attributeElement.setAttribute('Name', prop);

      if (options.includeAttributeNameFormat){
        attributeElement.setAttribute('NameFormat', getNameFormat(prop));
      }

      var values = options.attributes[prop] instanceof Array ? options.attributes[prop] : [options.attributes[prop]];
      values.forEach(function (value) {
        // Check by type, becase we want to include false values
        if (typeof value !== 'undefined') {
          // Ignore undefined values in Array
          var valueElement = doc.createElementNS(NAMESPACE, 'saml:AttributeValue');
          valueElement.setAttribute('xsi:type', options.typedAttributes ? getAttributeType(value) : 'xs:anyType');
          valueElement.textContent = value;
          attributeElement.appendChild(valueElement);
        }
      });

      if (values && values.filter(function(i){ return typeof i !== 'undefined'; }).length > 0) {
        // saml:Attribute must have at least one saml:AttributeValue
        statement.appendChild(attributeElement);
      }
    });
  }

  doc.getElementsByTagName('saml:AuthnStatement')[0]
    .setAttribute('AuthnInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));

  if (options.sessionIndex) {
    doc.getElementsByTagName('saml:AuthnStatement')[0]
      .setAttribute('SessionIndex', options.sessionIndex);
  }

  var nameID = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'NameID')[0];

  if (options.nameIdentifier) {
    nameID.textContent = options.nameIdentifier;
  }

  if (options.nameIdentifierFormat) {
    nameID.setAttribute('Format', options.nameIdentifierFormat);
  }

  if( options.authnContextClassRef ) {
    var authnCtxClassRef = doc.getElementsByTagName('saml:AuthnContextClassRef')[0];
    authnCtxClassRef.textContent = options.authnContextClassRef;
  }

  var token = utils.removeWhitespace(doc.toString());
  var signed;
  try {
    var opts = {
      location: {
        reference: options.xpathToNodeBeforeSignature || "//*[local-name(.)='Issuer']",
        action: 'after'
      },
      prefix: options.signatureNamespacePrefix
    };

    sig.computeSignature(token, opts);
    signed = sig.getSignedXml();
  } catch(err){
    return utils.reportError(err, callback);
  }

  if (!options.encryptionCert) {
    if (callback)
      return callback(null, signed);
    else
      return signed;
  }

  var encryptOptions = {
    rsa_pub: options.encryptionPublicKey,
    pem: options.encryptionCert,
    encryptionAlgorithm: options.encryptionAlgorithm || 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
    keyEncryptionAlgorighm: options.keyEncryptionAlgorighm || 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
  };

  xmlenc.encrypt(signed, encryptOptions, function(err, encrypted) {
    if (err) return callback(err);
    encrypted = '<saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' + encrypted + '</saml:EncryptedAssertion>';
    callback(null, utils.removeWhitespace(encrypted));
  });
};

<commit_msg>fix(saml20): parses saml20.template only once at start up<commit_after>
var utils = require('./utils'),
    Parser = require('xmldom').DOMParser,
    SignedXml = require('xml-crypto').SignedXml,
    xmlenc = require('xml-encryption'),
    moment = require('moment'),
    xmlNameValidator = require('xml-name-validator'),
    is_uri = require('valid-url').is_uri;

var fs = require('fs');
var path = require('path');
var newSaml20Document = (function () {
  var saml20Template = fs.readFileSync(path.join(__dirname, 'saml20.template'));
  var prototypeDoc = new Parser().parseFromString(saml20Template.toString())

  return function () {
    return prototypeDoc.cloneNode(true);
  };
})();

var NAMESPACE = 'urn:oasis:names:tc:SAML:2.0:assertion';

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'
  }
};

function getAttributeType(value){
  switch(typeof value) {
    case "string":
      return 'xs:string';
    case "boolean":
      return 'xs:boolean';
    case "number":
      // Maybe we should fine-grain this type and check whether it is an integer, float, double xsi:types
      return 'xs:double';
    default:
      return 'xs:anyType';
  }
}

function getNameFormat(name){
  if (is_uri(name)){
    return 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri';
  }

  //  Check that the name is a valid xs:Name -> https://www.w3.org/TR/xmlschema-2/#Name
  //  xmlNameValidate.name takes a string and will return an object of the form { success, error },
  //  where success is a boolean
  //  if it is false, then error is a string containing some hint as to where the match went wrong.
  if (xmlNameValidator.name(name).success){
    return 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic';
  }

  // Default value
  return 'urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified';
}

exports.create = function(options, callback) {
  if (!options.key)
    throw new Error('Expect a private key in pem format');

  if (!options.cert)
    throw new Error('Expect a public key cert in pem format');

  options.signatureAlgorithm = options.signatureAlgorithm || 'rsa-sha256';
  options.digestAlgorithm = options.digestAlgorithm || 'sha256';

  options.includeAttributeNameFormat = (typeof options.includeAttributeNameFormat !== 'undefined') ? options.includeAttributeNameFormat : true;
  options.typedAttributes = (typeof options.typedAttributes !== 'undefined') ? options.typedAttributes : true;

  // 0.10.1 added prefix, but we want to name it signatureNamespacePrefix - This is just to keep supporting prefix
  options.signatureNamespacePrefix = options.signatureNamespacePrefix || options.prefix;
  options.signatureNamespacePrefix = typeof options.signatureNamespacePrefix === 'string' ? options.signatureNamespacePrefix : '' ;

  var cert = utils.pemToCert(options.cert);

  var sig = new SignedXml(null, { signatureAlgorithm: algorithms.signature[options.signatureAlgorithm], idAttribute: 'ID' });
  sig.addReference("//*[local-name(.)='Assertion']",
                  ["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;

  sig.keyInfoProvider = {
    getKeyInfo: function (key, prefix) {
      prefix = prefix ? prefix + ':' : prefix;
      return "<" + prefix + "X509Data><" + prefix + "X509Certificate>" + cert + "</" + prefix + "X509Certificate></" + prefix + "X509Data>";
    }
  };

  var doc = newSaml20Document();

  doc.documentElement.setAttribute('ID', '_' + (options.uid || utils.uid(32)));
  if (options.issuer) {
    var issuer = doc.documentElement.getElementsByTagName('saml:Issuer');
    issuer[0].textContent = options.issuer;
  }

  var now = moment.utc();
  doc.documentElement.setAttribute('IssueInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
  var conditions = doc.documentElement.getElementsByTagName('saml:Conditions');
  var confirmationData = doc.documentElement.getElementsByTagName('saml:SubjectConfirmationData');

  if (options.lifetimeInSeconds) {
    conditions[0].setAttribute('NotBefore', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
    conditions[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));

    confirmationData[0].setAttribute('NotOnOrAfter', now.clone().add(options.lifetimeInSeconds, 'seconds').format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));
  }

  if (options.audiences) {
    var audienceRestriction = doc.createElementNS(NAMESPACE, 'saml:AudienceRestriction');
    var audiences = options.audiences instanceof Array ? options.audiences : [options.audiences];
    audiences.forEach(function (audience) {
      var element = doc.createElementNS(NAMESPACE, 'saml:Audience');
      element.textContent = audience;
      audienceRestriction.appendChild(element);
    });

    conditions[0].appendChild(audienceRestriction);
  }

  if (options.recipient)
    confirmationData[0].setAttribute('Recipient', options.recipient);

  if (options.inResponseTo)
    confirmationData[0].setAttribute('InResponseTo', options.inResponseTo);

  if (options.attributes) {
    var statement = doc.createElementNS(NAMESPACE, 'saml:AttributeStatement');
    statement.setAttribute('xmlns:xs', 'http://www.w3.org/2001/XMLSchema');
    statement.setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
    doc.documentElement.appendChild(statement);
    Object.keys(options.attributes).forEach(function(prop) {
      if(typeof options.attributes[prop] === 'undefined') return;
      // <saml:Attribute AttributeName="name" AttributeNamespace="http://schemas.xmlsoap.org/claims/identity">
      //    <saml:AttributeValue>Foo Bar</saml:AttributeValue>
      // </saml:Attribute>
      var attributeElement = doc.createElementNS(NAMESPACE, 'saml:Attribute');
      attributeElement.setAttribute('Name', prop);

      if (options.includeAttributeNameFormat){
        attributeElement.setAttribute('NameFormat', getNameFormat(prop));
      }

      var values = options.attributes[prop] instanceof Array ? options.attributes[prop] : [options.attributes[prop]];
      values.forEach(function (value) {
        // Check by type, becase we want to include false values
        if (typeof value !== 'undefined') {
          // Ignore undefined values in Array
          var valueElement = doc.createElementNS(NAMESPACE, 'saml:AttributeValue');
          valueElement.setAttribute('xsi:type', options.typedAttributes ? getAttributeType(value) : 'xs:anyType');
          valueElement.textContent = value;
          attributeElement.appendChild(valueElement);
        }
      });

      if (values && values.filter(function(i){ return typeof i !== 'undefined'; }).length > 0) {
        // saml:Attribute must have at least one saml:AttributeValue
        statement.appendChild(attributeElement);
      }
    });
  }

  doc.getElementsByTagName('saml:AuthnStatement')[0]
    .setAttribute('AuthnInstant', now.format('YYYY-MM-DDTHH:mm:ss.SSS[Z]'));

  if (options.sessionIndex) {
    doc.getElementsByTagName('saml:AuthnStatement')[0]
      .setAttribute('SessionIndex', options.sessionIndex);
  }

  var nameID = doc.documentElement.getElementsByTagNameNS(NAMESPACE, 'NameID')[0];

  if (options.nameIdentifier) {
    nameID.textContent = options.nameIdentifier;
  }

  if (options.nameIdentifierFormat) {
    nameID.setAttribute('Format', options.nameIdentifierFormat);
  }

  if( options.authnContextClassRef ) {
    var authnCtxClassRef = doc.getElementsByTagName('saml:AuthnContextClassRef')[0];
    authnCtxClassRef.textContent = options.authnContextClassRef;
  }

  var token = utils.removeWhitespace(doc.toString());
  var signed;
  try {
    var opts = {
      location: {
        reference: options.xpathToNodeBeforeSignature || "//*[local-name(.)='Issuer']",
        action: 'after'
      },
      prefix: options.signatureNamespacePrefix
    };

    sig.computeSignature(token, opts);
    signed = sig.getSignedXml();
  } catch(err){
    return utils.reportError(err, callback);
  }

  if (!options.encryptionCert) {
    if (callback)
      return callback(null, signed);
    else
      return signed;
  }

  var encryptOptions = {
    rsa_pub: options.encryptionPublicKey,
    pem: options.encryptionCert,
    encryptionAlgorithm: options.encryptionAlgorithm || 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
    keyEncryptionAlgorighm: options.keyEncryptionAlgorighm || 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'
  };

  xmlenc.encrypt(signed, encryptOptions, function(err, encrypted) {
    if (err) return callback(err);
    encrypted = '<saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' + encrypted + '</saml:EncryptedAssertion>';
    callback(null, utils.removeWhitespace(encrypted));
  });
};

<|endoftext|>


<commit_before>((typeof define === "function" && define.amd && function (m) {
    define("lodash", m);
}) || (typeof module === "object" &&
       typeof require === "function" && function (m) {
        module.exports = m(require("lodash"));
    }) || function (m) { this.samsam = m(_); }
)(function (_) {
    var o = Object.prototype;
    var div = typeof document !== "undefined" && document.createElement("div");

    function isNaN(value) {
        // Unlike global isNaN, this avoids type coercion
        // typeof check avoids IE host object issues, hat tip to
        // lodash
        var val = value; // JsLint thinks value !== value is "weird"
        return typeof value === "number" && value !== val;
    }

    function getClass(value) {
        // Returns the internal [[Class]] by calling Object.prototype.toString
        // with the provided value as this. Return value is a string, naming the
        // internal class, e.g. "Array"
        return o.toString.call(value).split(/[ \]]/)[1];
    }

    /**
     * @name samsam.isElement
     * @param Object object
     *
     * Returns ``true`` if ``object`` is a DOM element node. Unlike
     * Underscore.js/lodash, this function will return ``false`` if ``object``
     * is an *element-like* object, i.e. a regular object with a ``nodeType``
     * property that holds the value ``1``.
     */
    function isElement(object) {
        if (!object || object.nodeType !== 1 || !div) { return false; }
        try {
            object.appendChild(div);
            object.removeChild(div);
        } catch (e) {
            return false;
        }
        return true;
    }

    /**
     * @name samsam.isNegZero
     * @param Object value
     *
     * Returns ``true`` if ``value`` is ``-0``.
     */
    function isNegZero(value) {
        return value === 0 && 1 / value === -Infinity;
    }

    /**
     * @name samsam.equal
     * @param Object obj1
     * @param Object obj2
     *
     * Returns ``true`` if two objects are strictly equal. Compared to
     * ``===`` there are two exceptions:
     *
     *   - NaN is considered equal to NaN
     *   - -0 and +0 are not considered equal
     */
    function identical(obj1, obj2) {
        if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) {
            return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2);
        }
    }

    /**
     * @name samsam.deepEqual
     * @param Object obj1
     * @param Object obj2
     *
     * Deep equal comparison. Two values are "deep equal" if:
     *
     *   - They are equal, according to samsam.identical
     *   - They are both date objects representing the same time
     *   - They are both arrays containing elements that are all deepEqual
     *   - They are objects with the same set of properties, and each property
     *     in ``obj1`` is deepEqual to the corresponding property in ``obj2``
     */
    function deepEqual(obj1, obj2) {
        var type1 = typeof obj1;
        var type2 = typeof obj2;

        // == null also matches undefined
        if (obj1 === obj2 ||
                isNaN(obj1) || isNaN(obj2) ||
                obj1 == null || obj2 == null ||
                type1 !== "object" || type2 !== "object") {
            return identical(obj1, obj2);
        }

        // Elements are only equal if identical(expected, actual)
        if (isElement(obj1) || isElement(obj2)) { return false; }

        var isDate1 = _.isDate(obj1), isDate2 = _.isDate(obj2);
        if (isDate1 || isDate2) {
            if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) {
                return false;
            }
        }

        if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
            if (obj1.toString() !== obj2.toString()) { return false; }
        }

        var class1 = getClass(obj1);
        var class2 = getClass(obj2);
        var keys1 = _.keys(obj1);
        var keys2 = _.keys(obj2);

        if (_.isArguments(obj1) || _.isArguments(obj2)) {
            if (obj1.length !== obj2.length) { return false; }
        } else {
            if (type1 !== type2 || class1 !== class2 ||
                    keys1.length !== keys2.length) {
                return false;
            }
        }

        var key, i, l;

        for (i = 0, l = keys1.length; i < l; i++) {
            key = keys1[i];
            if (!o.hasOwnProperty.call(obj2, key) ||
                    !deepEqual(obj1[key], obj2[key])) {
                return false;
            }
        }

        return true;
    }

    var match;

    function arrayContains(array, subset) {
        var i, l, j, k;
        for (i = 0, l = array.length; i < l; ++i) {
            if (match(array[i], subset[0])) {
                for (j = 0, k = subset.length; j < k; ++j) {
                    if (!match(array[i + j], subset[j])) { return false; }
                }
                return true;
            }
        }
        return false;
    }

    /**
     * @name samsam.match
     * @param Object object
     * @param Object matcher
     *
     * Compare arbitrary value ``object`` with matcher.
     */
    match = function match(object, matcher) {
        if (matcher && typeof matcher.test === "function") {
            return matcher.test(object);
        }

        if (typeof matcher === "function") {
            return matcher(object) === true;
        }

        if (typeof matcher === "string") {
            matcher = matcher.toLowerCase();
            var notNull = typeof object === "string" || !!object;
            return notNull &&
                (String(object)).toLowerCase().indexOf(matcher) >= 0;
        }

        if (typeof matcher === "number") {
            return matcher === object;
        }

        if (typeof matcher === "boolean") {
            return matcher === object;
        }

        if (getClass(object) === "Array" && getClass(matcher) === "Array") {
            return arrayContains(object, matcher);
        }

        if (matcher && typeof matcher === "object") {
            var prop;
            for (prop in matcher) {
                if (!match(object[prop], matcher[prop])) {
                    return false;
                }
            }
            return true;
        }

        throw new Error("Matcher was not a string, a number, a " +
                        "function, a boolean or an object");
    };

    return {
        isElement: isElement,
        isNegZero: isNegZero,
        identical: identical,
        deepEqual: deepEqual,
        match: match
    };
});
<commit_msg>Fix define call<commit_after>((typeof define === "function" && define.amd && function (m) {
    define(["lodash"], m);
}) || (typeof module === "object" &&
       typeof require === "function" && function (m) {
        module.exports = m(require("lodash"));
    }) || function (m) { this.samsam = m(_); }
)(function (_) {
    var o = Object.prototype;
    var div = typeof document !== "undefined" && document.createElement("div");

    function isNaN(value) {
        // Unlike global isNaN, this avoids type coercion
        // typeof check avoids IE host object issues, hat tip to
        // lodash
        var val = value; // JsLint thinks value !== value is "weird"
        return typeof value === "number" && value !== val;
    }

    function getClass(value) {
        // Returns the internal [[Class]] by calling Object.prototype.toString
        // with the provided value as this. Return value is a string, naming the
        // internal class, e.g. "Array"
        return o.toString.call(value).split(/[ \]]/)[1];
    }

    /**
     * @name samsam.isElement
     * @param Object object
     *
     * Returns ``true`` if ``object`` is a DOM element node. Unlike
     * Underscore.js/lodash, this function will return ``false`` if ``object``
     * is an *element-like* object, i.e. a regular object with a ``nodeType``
     * property that holds the value ``1``.
     */
    function isElement(object) {
        if (!object || object.nodeType !== 1 || !div) { return false; }
        try {
            object.appendChild(div);
            object.removeChild(div);
        } catch (e) {
            return false;
        }
        return true;
    }

    /**
     * @name samsam.isNegZero
     * @param Object value
     *
     * Returns ``true`` if ``value`` is ``-0``.
     */
    function isNegZero(value) {
        return value === 0 && 1 / value === -Infinity;
    }

    /**
     * @name samsam.equal
     * @param Object obj1
     * @param Object obj2
     *
     * Returns ``true`` if two objects are strictly equal. Compared to
     * ``===`` there are two exceptions:
     *
     *   - NaN is considered equal to NaN
     *   - -0 and +0 are not considered equal
     */
    function identical(obj1, obj2) {
        if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) {
            return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2);
        }
    }

    /**
     * @name samsam.deepEqual
     * @param Object obj1
     * @param Object obj2
     *
     * Deep equal comparison. Two values are "deep equal" if:
     *
     *   - They are equal, according to samsam.identical
     *   - They are both date objects representing the same time
     *   - They are both arrays containing elements that are all deepEqual
     *   - They are objects with the same set of properties, and each property
     *     in ``obj1`` is deepEqual to the corresponding property in ``obj2``
     */
    function deepEqual(obj1, obj2) {
        var type1 = typeof obj1;
        var type2 = typeof obj2;

        // == null also matches undefined
        if (obj1 === obj2 ||
                isNaN(obj1) || isNaN(obj2) ||
                obj1 == null || obj2 == null ||
                type1 !== "object" || type2 !== "object") {
            return identical(obj1, obj2);
        }

        // Elements are only equal if identical(expected, actual)
        if (isElement(obj1) || isElement(obj2)) { return false; }

        var isDate1 = _.isDate(obj1), isDate2 = _.isDate(obj2);
        if (isDate1 || isDate2) {
            if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) {
                return false;
            }
        }

        if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
            if (obj1.toString() !== obj2.toString()) { return false; }
        }

        var class1 = getClass(obj1);
        var class2 = getClass(obj2);
        var keys1 = _.keys(obj1);
        var keys2 = _.keys(obj2);

        if (_.isArguments(obj1) || _.isArguments(obj2)) {
            if (obj1.length !== obj2.length) { return false; }
        } else {
            if (type1 !== type2 || class1 !== class2 ||
                    keys1.length !== keys2.length) {
                return false;
            }
        }

        var key, i, l;

        for (i = 0, l = keys1.length; i < l; i++) {
            key = keys1[i];
            if (!o.hasOwnProperty.call(obj2, key) ||
                    !deepEqual(obj1[key], obj2[key])) {
                return false;
            }
        }

        return true;
    }

    var match;

    function arrayContains(array, subset) {
        var i, l, j, k;
        for (i = 0, l = array.length; i < l; ++i) {
            if (match(array[i], subset[0])) {
                for (j = 0, k = subset.length; j < k; ++j) {
                    if (!match(array[i + j], subset[j])) { return false; }
                }
                return true;
            }
        }
        return false;
    }

    /**
     * @name samsam.match
     * @param Object object
     * @param Object matcher
     *
     * Compare arbitrary value ``object`` with matcher.
     */
    match = function match(object, matcher) {
        if (matcher && typeof matcher.test === "function") {
            return matcher.test(object);
        }

        if (typeof matcher === "function") {
            return matcher(object) === true;
        }

        if (typeof matcher === "string") {
            matcher = matcher.toLowerCase();
            var notNull = typeof object === "string" || !!object;
            return notNull &&
                (String(object)).toLowerCase().indexOf(matcher) >= 0;
        }

        if (typeof matcher === "number") {
            return matcher === object;
        }

        if (typeof matcher === "boolean") {
            return matcher === object;
        }

        if (getClass(object) === "Array" && getClass(matcher) === "Array") {
            return arrayContains(object, matcher);
        }

        if (matcher && typeof matcher === "object") {
            var prop;
            for (prop in matcher) {
                if (!match(object[prop], matcher[prop])) {
                    return false;
                }
            }
            return true;
        }

        throw new Error("Matcher was not a string, a number, a " +
                        "function, a boolean or an object");
    };

    return {
        isElement: isElement,
        isNegZero: isNegZero,
        identical: identical,
        deepEqual: deepEqual,
        match: match
    };
});
<|endoftext|>