<commit_before>(function() {
  var commentPattern = /(\/\*)[\s\S]*?(\*\/)/g;
  var cssRulePattern = /\s*([^{]+)\{([^}]*)\}\s*/g;
  var queryPattern = /:media\s*\(([^)]*)\)/g;
  var selectorPattern = /(?:^|\})\s*([^{]+)\s*/g;
  var queryRulesPattern = /\(?([^\s:]+):\s*(\d+(?:\.\d+)?)(px|em|rem|vw|vh|vmin|vmax)\)?/g;
  var mediaPattern = /(@media[^{]*)\{((?:\s*([^{]+)\{([^}]*)\}\s*)*)\}/g

  var queryMatchers = {
    "max-available-width": function(element, value, units) {
      var parent = element.parentNode;
      var px = convertToPx(parent, value, units);
      return value && parent && parent.offsetWidth <= px;
    },

    "min-available-width": function(element, value, units) {
      var parent = element.parentNode;
      var px = convertToPx(parent, value, units);
      return value && parent && parent.offsetWidth >= px;
    },
  };

  var queries = [];

  var query = {
    selector: "string",
    className: "string",
    match: function() {}
  };

  var ElementQuery = function() {};

  var queriesForSelector = function(selectorString) {
    var selectors = selectorString.split(",");
    var selectorResults = [];
    var queryResults = [];
    for (var i = 0, len = selectors.length; i < len; i++) {
      var result = queriesForSingleSelector(selectors[i]);
      selectorResults.push(result.selector);
      queryResults = queryResults.concat(result.queries);
    }
    return {
      selector: selectorResults.join(","),
      queries: queryResults
    };
  };

  var queriesForSingleSelector = function(selectorString) {
    var queries = [];
    var newSelector = "";
    var lastIndex = 0;
    var queryMatch;
    while (queryMatch = queryPattern.exec(selectorString)) {
      var querySelector = selectorString.slice(0, queryMatch.index);
      var queryRules = parseQuery(queryMatch[1]);
      var className = classNameForRules(queryRules);
      newSelector += selectorString.slice(lastIndex, queryMatch.index);
      lastIndex = queryMatch.index + queryMatch[0].length;
      queries.push({
        selector: newSelector,
        className: className,
        rules: queryRules
      });
      newSelector += "." + className;
    }
    newSelector += selectorString.slice(lastIndex);
    return {
      selector: newSelector,
      queries: queries
    };
  };

  var parseQuery = function(queryString) {
    var rules = [];
    var ruleMatch;
    while (ruleMatch = queryRulesPattern.exec(queryString)) {
      rules.push({
        property: ruleMatch[1],
        value: parseFloat(ruleMatch[2]),
        units: ruleMatch[3]
      });
    }
    return rules;
  };

  var classNameForRules = function(rules) {
    var name = "query";
    for (var i = 0, len = rules.length; i < len; i++) {
      name += "_" + rules[i].property + "_" + rules[i].value + rules[i].units;
    }
    return name;
  };

  var convertToPx = function(element, value, units) {
    switch (units) {
      case "px": return value;
      case "em": return value * getEmSize(element);
      case "rem": return value * getEmSize();
      // Viewport units!
      // According to http://quirksmode.org/mobile/tableViewport.html
      // documentElement.clientWidth/Height gets us the most reliable info
      case "vw": return value * document.documentElement.clientWidth / 100;
      case "vh": return value * document.documentElement.clientHeight / 100;
      case "vmin":
      case "vmax":
        var vw = document.documentElement.clientWidth / 100;
        var vh = document.documentElement.clientHeight / 100;
        var chooser = Math[units === "vmin" ? "min" : "max"];
        return value * chooser(vw, vh);
      default: return value;
      // for now, not supporting physical units (since they are just a set number of px)
      // or ex/ch (getting accurate measurements is hard)
    }
  };

  var getEmSize = function(element) {
    if (!element) {
      element = document.documentElement;
    }
    if (window.getComputedStyle) {
      return parseFloat(getComputedStyle(element).fontSize) || 16;
    }
    // TODO: support IE?
    return 16;
  };

  var scanStyleSheets = function(sheets, callback) {
    var completed = 0;
    for (var i = 0, len = sheets.length; i < len; i++) {
      scanStyleSheet(sheets[i], function() {
        completed += 1;
        if (completed === len) {
          callback && callback();
        }
      });
    }
  };

  var scanStyleSheet = function(sheet, callback) {
    if (sheet.ownerNode.nodeName === "STYLE") {
      var newStyles = scanStyleString(sheet.ownerNode.innerHTML);
      sheet.ownerNode.innerHTML += newStyles;
      callback && callback();
    }
    else if (sheet.href) {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", sheet.href, true);
      xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            var newStyles = scanStyleString(xhr.responseText);
            var style = document.createElement("style");
            style.innerHTML = newStyles;
            document.body.appendChild(style);
          }
          else if (window.console) {
            console.log("Could not load stylesheet at " + sheet.href);
          }
          callback && callback();
        }
      }
      xhr.send(null);
    }
  };

  var scanStyleString = function(sheet) {
    // new stylesheet content to add (replacing rules with `:media()` in them)
    var newRules = "";

    // remove comments
    sheet = sheet.replace(commentPattern, "");

    // manage vanilla media queries
    var mediaRules = "";
    sheet = sheet.replace(mediaPattern, function(mediaString, query, content) {
      var newMediaRules = scanStyleString(content);
      if (!/^\s*$/.test(newMediaRules)) {
        mediaRules += query + "{\n" + newMediaRules + "\n}\n";
      }
      return "";
    });

    var ruleMatch;
    while (ruleMatch = cssRulePattern.exec(sheet)) {
      var results = queriesForSelector(ruleMatch[1]);
      if (results.queries.length) {
        newRules += results.selector + "{" + ruleMatch[2] + "}\n";
        queries.push.apply(queries, results.queries);
      }
    }
    return newRules + "\n" + mediaRules;
  };

  var elementMatchesRules = function(element, rules) {
    for (var i = rules.length - 1; i > -1; i--) {
      var rule = rules[i];
      var matcher = queryMatchers[rule.property];
      if (matcher && !matcher(element, rule.value, rule.units)) {
        return false;
      }
    }
    return true;
  };

  var evaluateQueries = function() {
    for (var i = 0, len = queries.length; i < len; i++) {
      var elements = document.querySelectorAll(queries[i].selector);
      for (var j = 0; j < elements.length; j++) {
        var element = elements[j];
        if (elementMatchesRules(element, queries[i].rules)) {
          element.classList.add(queries[i].className);
        }
        else {
          element.classList.remove(queries[i].className);
        }
      }
    }
  };

  // re-run all queries on resize
  window.addEventListener("resize", function() {
    evaluateQueries();
  }, false);

  // TODO: re-run all queries... on an interval?
  // override setTimeout, addEventListener, etc to hit every possible JS entry
  // point? Not really an ideal solution to this.
  // Repaint events in Mozilla?

  // automatically look for things on window load
  window.addEventListener("load", function() {
    var evaluated = false;
    scanStyleSheets(document.styleSheets, function() {
      evaluated = true;
      evaluateQueries();
    });

    // if we are still waiting for some asynchronous ones, go ahead and evaluate
    // any found queries now for minimum latency
    if (!evaluated) {
      evaluateQueries();
    }
  }, false);
}());<commit_msg>A little cleanup and reorg (mostly of the parsing code) and comments.<commit_after>(function() {
  // Regexes for parsing
  var COMMENT_PATTERN = /(\/\*)[\s\S]*?(\*\/)/g;
  var CSS_RULE_PATTERN = /\s*([^{]+)\{([^}]*)\}\s*/g;
  var QUERY_PATTERN = /:media\s*\(([^)]*)\)/g;
  var QUERY_RULES_PATTERN = /\(?([^\s:]+):\s*(\d+(?:\.\d+)?)(px|em|rem|vw|vh|vmin|vmax)\)?/g;
  var MEDIA_PATTERN = /(@media[^{]*)\{((?:\s*([^{]+)\{([^}]*)\}\s*)*)\}/g;

  // implementations for testing actual element query properties
  var queryMatchers = {
    "max-available-width": function(element, value, units) {
      var parent = element.parentNode;
      var px = convertToPx(parent, value, units);
      return value && parent && parent.offsetWidth <= px;
    },

    "min-available-width": function(element, value, units) {
      var parent = element.parentNode;
      var px = convertToPx(parent, value, units);
      return value && parent && parent.offsetWidth >= px;
    },
  };

  // list of known queries that need matching
  var queries = [];

  // convert an element query into a css class name we can replace it with
  var classNameForRules = function(rules) {
    var name = "query";
    for (var i = 0, len = rules.length; i < len; i++) {
      name += "_" + rules[i].property + "_" + rules[i].value + rules[i].units;
    }
    return name;
  };

  // determine the px value for a measurement (e.g. "5em") on a given element
  var convertToPx = function(element, value, units) {
    switch (units) {
      case "px": return value;
      case "em": return value * getEmSize(element);
      case "rem": return value * getEmSize();
      // Viewport units!
      // According to http://quirksmode.org/mobile/tableViewport.html
      // documentElement.clientWidth/Height gets us the most reliable info
      case "vw": return value * document.documentElement.clientWidth / 100;
      case "vh": return value * document.documentElement.clientHeight / 100;
      case "vmin":
      case "vmax":
        var vw = document.documentElement.clientWidth / 100;
        var vh = document.documentElement.clientHeight / 100;
        var chooser = Math[units === "vmin" ? "min" : "max"];
        return value * chooser(vw, vh);
      default: return value;
      // for now, not supporting physical units (since they are just a set number of px)
      // or ex/ch (getting accurate measurements is hard)
    }
  };

  // determine the size of an em in a given element
  var getEmSize = function(element) {
    if (!element) {
      element = document.documentElement;
    }
    if (window.getComputedStyle) {
      return parseFloat(getComputedStyle(element).fontSize) || 16;
    }
    // TODO: support IE?
    return 16;
  };

  // test whether an element matches a set of query rules
  var elementMatchesRules = function(element, rules) {
    for (var i = rules.length - 1; i > -1; i--) {
      var rule = rules[i];
      var matcher = queryMatchers[rule.property];
      if (matcher && !matcher(element, rule.value, rule.units)) {
        return false;
      }
    }
    return true;
  };

  // re-evaluate all the queries
  var evaluateQueries = function(context) {
    context = context || document;
    for (var i = 0, len = queries.length; i < len; i++) {
      var elements = context.querySelectorAll(queries[i].selector);
      for (var j = 0; j < elements.length; j++) {
        var element = elements[j];
        if (elementMatchesRules(element, queries[i].rules)) {
          element.classList.add(queries[i].className);
        }
        else {
          element.classList.remove(queries[i].className);
        }
      }
    }
  };

  // el-cheapo parser. It at least screws up nested @media rules
  // and puts things that were in @media rules out-of-order
  var parser = {
    // parse an array of CSSStyleSheet objects for element queries
    parseStyleSheets: function(sheets, callback) {
      var completed = 0;
      for (var i = 0, len = sheets.length; i < len; i++) {
        this.parseStyleSheet(sheets[i], function() {
          completed += 1;
          if (completed === len) {
            callback && callback();
          }
        });
      }
    },

    // parse a single CSSStyleSheet object for element queries
    parseStyleSheet: function(sheet, callback) {
      if (sheet.ownerNode.nodeName === "STYLE") {
        var newStyles = parser.parseStyleText(sheet.ownerNode.innerHTML);
        sheet.ownerNode.innerHTML += newStyles;
        callback && callback();
      }
      else if (sheet.href) {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", sheet.href, true);
        xhr.onreadystatechange = function() {
          if (xhr.readyState === 4) {
            if (xhr.status === 200) {
              var newStyles = parser.parseStyleText(xhr.responseText);
              var style = document.createElement("style");
              style.innerHTML = newStyles;
              document.body.appendChild(style);
            }
            else if (window.console) {
              console.log("Could not load stylesheet at " + sheet.href);
            }
            callback && callback();
          }
        }
        xhr.send(null);
      }
    },

    // parse the raw text of a style sheet for element queries
    parseStyleText: function(sheet) {
      // new stylesheet content to add (replacing rules with `:media()` in them)
      var newRules = "";

      // remove comments
      sheet = sheet.replace(COMMENT_PATTERN, "");

      // manage vanilla media queries
      var mediaRules = "";
      sheet = sheet.replace(MEDIA_PATTERN, function(mediaString, query, content) {
        var newMediaRules = parser.parseStyleText(content);
        if (!/^\s*$/.test(newMediaRules)) {
          mediaRules += query + "{\n" + newMediaRules + "\n}\n";
        }
        return "";
      });

      var ruleMatch;
      while (ruleMatch = CSS_RULE_PATTERN.exec(sheet)) {
        var results = this.queriesForSelector(ruleMatch[1]);
        if (results.queries.length) {
          newRules += results.selector + "{" + ruleMatch[2] + "}\n";
          queries.push.apply(queries, results.queries);
        }
      }
      return newRules + "\n" + mediaRules;
    },

    // find all the queries in a selector
    queriesForSelector: function(selectorString) {
      var selectors = selectorString.split(",");
      var selectorResults = [];
      var queryResults = [];
      for (var i = 0, len = selectors.length; i < len; i++) {
        var result = this.queriesForSingleSelector(selectors[i]);
        selectorResults.push(result.selector);
        queryResults = queryResults.concat(result.queries);
      }
      return {
        selector: selectorResults.join(","),
        queries: queryResults
      };
    },

    // find all the queries in a *single* selector (i.e. no commas)
    queriesForSingleSelector: function(selectorString) {
      var queries = [];
      var newSelector = "";
      var lastIndex = 0;
      var queryMatch;
      while (queryMatch = QUERY_PATTERN.exec(selectorString)) {
        var querySelector = selectorString.slice(0, queryMatch.index);
        var queryRules = this.parseQuery(queryMatch[1]);
        var className = classNameForRules(queryRules);
        newSelector += selectorString.slice(lastIndex, queryMatch.index);
        lastIndex = queryMatch.index + queryMatch[0].length;
        queries.push({
          selector: newSelector,
          className: className,
          rules: queryRules
        });
        newSelector += "." + className;
      }
      newSelector += selectorString.slice(lastIndex);
      return {
        selector: newSelector,
        queries: queries
      };
    },

    // find the actual queried properties in an element query
    parseQuery: function(queryString) {
      var rules = [];
      var ruleMatch;
      while (ruleMatch = QUERY_RULES_PATTERN.exec(queryString)) {
        rules.push({
          property: ruleMatch[1],
          value: parseFloat(ruleMatch[2]),
          units: ruleMatch[3]
        });
      }
      return rules;
    }
  };

  // re-run all queries on resize
  window.addEventListener("resize", function() {
    evaluateQueries();
  }, false);

  // TODO: re-run all queries... on an interval?
  // override setTimeout, addEventListener, etc to hit every possible JS entry
  // point? Not really an ideal solution to this.
  // Repaint events in Mozilla?

  // automatically look for things on window load
  window.addEventListener("load", function() {
    var evaluated = false;
    parser.parseStyleSheets(document.styleSheets, function() {
      evaluated = true;
      evaluateQueries();
    });

    // if we are still waiting for some asynchronous ones, go ahead and evaluate
    // any found queries now for minimum latency
    if (!evaluated) {
      evaluateQueries();
    }
  }, false);
}());<|endoftext|>


<commit_before>/**
 * From the repository: esgraph (https://github.com/Swatinem/esgraph)
 * @license LGPLv3 (http://www.gnu.org/licenses/lgpl-3.0-standalone.html)
 * @author Swatinem (arpad.borsos@googlemail.com)
 * @modified ChengFuLin (chengfulin0806@gmail.com)
 */
var walker = require('walkes'),
	factoryFlowNode = require('./flownodefactory'),
	FlowNode = require('./flownode');

module.exports = ControlFlowGraph;
module.exports.dot = require('./dot');
module.exports.FlowNode = FlowNode;
module.exports.factoryFlowNode = factoryFlowNode;

// FIXME: switch/case with default before other cases?
// FIXME: catch creates a new scope, so should somehow be handled differently

// TODO: try/finally: finally follows try, but does not return to normal flow?

// TODO: labeled break/continue
// TODO: WithStatement

// TODO: avoid adding and deleting properties on ast nodes

/**
 * Returns [entry, exit] `FlowNode`s for the passed in AST
 */
function ControlFlowGraph(astNode) {
	'use strict';
	var parentStack = [];
	var entryNode = factoryFlowNode.create(FlowNode.ENTRY_NODE_TYPE, astNode);
	var exitNode = factoryFlowNode.create(FlowNode.EXIT_NODE_TYPE);
	var catchStack = [exitNode];

	createNodes(astNode);
	linkSiblings(astNode);

	walker(astNode, {
		CatchClause: function (node, recurse) {
			node.cfg.connect(getEntry(node.body));
			recurse(node.body);
		},
		DoWhileStatement: function (node, recurse) {
			mayThrow(node.test);
			node.test.cfg
				.connect(getEntry(node.body), FlowNode.TRUE_BRANCH_CONNECTION_TYPE)
				.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			recurse(node.body);
		},
		ExpressionStatement: connectNext,
		FunctionDeclaration: function () {},
		ForStatement: function (node, recurse) {
			if (node.test) {
				mayThrow(node.test);
				node.test.cfg
					.connect(getEntry(node.body), FlowNode.TRUE_BRANCH_CONNECTION_TYPE)
					.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
				if (node.update) {
					node.update.cfg.connect(node.test.cfg);
				}
			} else if (node.update) {
				node.update.cfg.connect(getEntry(node.body));
			}
			if (node.update) {
				mayThrow(node.update);
			}
			if (node.init) {
				mayThrow(node.init);
				node.init.cfg.connect(node.test && node.test.cfg || getEntry(node.body));
			}
			recurse(node.body);
		},
		ForInStatement: function (node, recurse) {
			mayThrow(node);
			node.cfg
				.connect(getEntry(node.body), FlowNode.TRUE_BRANCH_CONNECTION_TYPE)
				.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			recurse(node.body);
		},
		IfStatement: function (node, recurse) {
			recurse(node.consequent);
			mayThrow(node.test);
			node.test.cfg.connect(getEntry(node.consequent), FlowNode.TRUE_BRANCH_CONNECTION_TYPE);
			if (node.alternate) {
				recurse(node.alternate);
				node.test.cfg.connect(getEntry(node.alternate), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			} else {
				node.test.cfg.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			}
		},
		ReturnStatement: function (node) {
			mayThrow(node);
			node.cfg.connect(exitNode);
		},
		SwitchCase: function (node, recurse) {
			if (node.test) {
				// if this is a real case, connect `true` to the body
				// or the body of the next case
				var check = node;
				while (!check.consequent.length && check.cfg.nextSibling) {
					check = check.cfg.nextSibling.astNode;
				}

				node.cfg.connect(check.consequent.length && getEntry(check.consequent[0]) || getSuccessor(node.cfg.parent), FlowNode.TRUE_BRANCH_CONNECTION_TYPE);

				// and connect false to the next `case`
				node.cfg.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			} else {
				// this is the `default` case, connect it to the body, or the
				// successor of the parent
				node.cfg.connect(node.consequent.length && getEntry(node.consequent[0]) || getSuccessor(node.cfg.parent));
			}
			node.consequent.forEach(recurse);
		},
		SwitchStatement: function (node, recurse) {
			node.cfg.connect(node.cases[0].cfg);
			node.cases.forEach(recurse);
		},
		ThrowStatement: function (node) {
			node.cfg.connect(getExceptionTarget(node), FlowNode.EXCEPTION_CONNECTION_TYPE);
		},
		TryStatement: function (node, recurse) {
			var handler = node.handlers[0] && node.handlers[0].cfg || getEntry(node.finalizer);
			catchStack.push(handler);
			recurse(node.block);
			catchStack.pop();

			if (node.handlers.length) {
				recurse(node.handlers[0]);
			}
			if (node.finalizer) {
				//node.finalizer.cfg.connect(getSuccessor(node));
				recurse(node.finalizer);
			}
		},
		VariableDeclaration: connectNext,
		WhileStatement: function (node, recurse) {
			mayThrow(node.test);
			node.test.cfg
				.connect(getEntry(node.body), FlowNode.TRUE_BRANCH_CONNECTION_TYPE)
				.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			recurse(node.body);
		}
	});

	entryNode.connect(getEntry(astNode), FlowNode.NORMAL_CONNECTION_TYPE);
	walker(astNode, {default: function (node, recurse) {
		if (!node.cfg) {
			return;
		}
		// ExpressionStatements should refer to their expression directly
		if (node.type === 'ExpressionStatement') {
			node.cfg.astNode = node.expression;
		}
		delete node.cfg;
		walker.checkProps(node, recurse);
	}});

	var allNodes = [];
	var reverseStack = [entryNode];
	var cfgNode;
	while (reverseStack.length) {
		cfgNode = reverseStack.pop();
		allNodes.push(cfgNode);
		[
			FlowNode.EXCEPTION_CONNECTION_TYPE,
			FlowNode.FALSE_BRANCH_CONNECTION_TYPE,
			FlowNode.TRUE_BRANCH_CONNECTION_TYPE,
			FlowNode.NORMAL_CONNECTION_TYPE
		].forEach(eachType);
	}
	/* Actually, in esgraph, it creates a graph with only four kinds of connection types, exception, false, true and normal */
	function eachType(type) {
		var next = cfgNode[type];
		if (type === FlowNode.ON_EVENT_CONNECTION_TYPE) {
			next.forEach(function (node) {
				if (reverseStack.indexOf(node) === -1 && allNodes.indexOf(node) === -1) {
					reverseStack.push(node);
				}
			});
		} else if (!!next && reverseStack.indexOf(next) === -1 && allNodes.indexOf(next) === -1) {
			reverseStack.push(next);
		}
	}

	function getExceptionTarget() {
		return catchStack[catchStack.length - 1];
	}

	function mayThrow(node) {
		if (expressionThrows(node)) {
			node.cfg.connect(getExceptionTarget(node), FlowNode.EXCEPTION_CONNECTION_TYPE);
		}
	}
	function expressionThrows(astNode) {
		if (typeof astNode !== 'object' || 'FunctionExpression' === astNode.type) {
			return false;
		}
		if (astNode.type && throwTypes.indexOf(astNode.type) !== -1) {
			return true;
		}
		var self = astNode;
		return Object.keys(self).some(function (key) {
			var prop = self[key];
			if (prop instanceof Array) {
				return prop.some(expressionThrows);
			} else if (typeof prop === 'object' && prop) {
				return expressionThrows(prop);
			}
			else {
				return false;
			}
		});
	}

	function getJumpTarget(astNode, types) {
		var parent = astNode.cfg.parent;
		while (types.indexOf(parent.type) === -1 && !!parent.cfg.parent) {
			parent = parent.cfg.parent;
		}
		return (types.indexOf(parent.type) !== -1) ? parent : null;
	}

	function connectNext(node) {
		mayThrow(node);
		node.cfg.connect(getSuccessor(node));
	}

	/**
	 * Returns the entry node of a statement
	 */
	function getEntry(astNode) {
		var target;
		switch (astNode.type) {
			case 'BreakStatement':
				target = getJumpTarget(astNode, breakTargets);
				return target ? getSuccessor(target) : exitNode;
			case 'ContinueStatement':
				target = getJumpTarget(astNode, continueTargets);
				switch (target.type) {
					case 'ForStatement':
						// continue goes to the update, test or body
						return target.update && target.update.cfg || target.test && target.test.cfg || getEntry(target.body);
					case 'ForInStatement':
						return target.cfg;
					case 'DoWhileStatement':
					/* falls through */
					case 'WhileStatement':
						return target.test.cfg;
				}
			// unreached
			/* falls through */
			case 'BlockStatement':
			/* falls through */
			case 'Program':
				return astNode.body.length && getEntry(astNode.body[0]) || getSuccessor(astNode);
			case 'DoWhileStatement':
				return getEntry(astNode.body);
			case 'EmptyStatement':
				return getSuccessor(astNode);
			case 'ForStatement':
				return astNode.init && astNode.init.cfg || astNode.test && astNode.test.cfg || getEntry(astNode.body);
			case 'FunctionDeclaration':
				return getSuccessor(astNode);
			case 'IfStatement':
				return astNode.test.cfg;
			case 'SwitchStatement':
				return getEntry(astNode.cases[0]);
			case 'TryStatement':
				return getEntry(astNode.block);
			case 'WhileStatement':
				return astNode.test.cfg;
			default:
				return astNode.cfg;
		}
	}
	/**
	 * Returns the successor node of a statement
	 */
	function getSuccessor(astNode) {
		// part of a block -> it already has a nextSibling
		if (astNode.cfg.nextSibling) {
			return astNode.cfg.nextSibling;
		}
		var parent = astNode.cfg.parent;
		if (!parent) { // it has no parent -> exitNode
			return exitNode;
		}
		switch (parent.type) {
			case 'DoWhileStatement':
				return parent.test.cfg;
			case 'ForStatement':
				return parent.update && parent.update.cfg || parent.test && parent.test.cfg || getEntry(parent.body);
			case 'ForInStatement':
				return parent.cfg;
			case 'TryStatement':
				return parent.finalizer && astNode !== parent.finalizer && getEntry(parent.finalizer) || getSuccessor(parent);
			case 'SwitchCase':
				// the sucessor of a statement at the end of a case block is
				// the entry of the next cases consequent
				if (!parent.cfg.nextSibling) {
					return getSuccessor(parent);
				}
				var check = parent.cfg.nextSibling.astNode;
				while (!check.consequent.length && check.cfg.nextSibling) {
					check = check.cfg.nextSibling.astNode;
				}
				// or the next statement after the switch, if there are no more cases
				return check.consequent.length && getEntry(check.consequent[0]) || getSuccessor(parent.parent);
			case 'WhileStatement':
				return parent.test.cfg;
			default:
				return getSuccessor(parent);
		}
	}

	/**
	 * Creates a FlowNode for every AST node
	 */
	function createNodes(astNode) {
		walker(astNode, { default: function (node, recurse) {
			var parent = parentStack.length ? parentStack[parentStack.length - 1] : undefined;
			createNode(node, parent);
			// do not recurse for FunctionDeclaration or any sub-expression
			if (node.type === 'FunctionDeclaration' || node.type.indexOf('Expression') !== -1) {
				return;
			}
			parentStack.push(node);
			walker.checkProps(node, recurse);
			parentStack.pop();
		}});
	}
	function createNode(astNode, parent) {
		if (!astNode.cfg) {
			Object.defineProperty(astNode, 'cfg', {
				value: factoryFlowNode.create(FlowNode.NORMAL_NODE_TYPE, astNode, parent), configurable: true
			});
		}
	}

	/**
	 * Links in the next sibling for nodes inside a block
	 */
	function linkSiblings(astNode) {
		function backToFront(list, recurse) {
			// link all the children to the next sibling from back to front,
			// so the nodes already have .nextSibling
			// set when their getEntry is called
			for (var i = list.length - 1; i >= 0; i--) {
				var child = list[i];
				if (i < list.length - 1) {
					child.cfg.nextSibling = getEntry(list[i + 1]);
				}
				recurse(child);
			}
		}
		function BlockOrProgram(node, recurse) {
			backToFront(node.body, recurse);
		}
		walker(astNode, {
			BlockStatement: BlockOrProgram,
			Program: BlockOrProgram,
			FunctionDeclaration: function () {},
			FunctionExpression: function () {},
			SwitchCase: function (node, recurse) {
				backToFront(node.consequent, recurse);
			},
			SwitchStatement: function (node, recurse) {
				backToFront(node.cases, recurse);
			}
		});
	}
	/// Reset cfgIds to skip those nodes not shown in CFG
	/// Reset to the first created node in this CFG
	factoryFlowNode.setCounter(entryNode.cfgId);
	allNodes.forEach(function (node, index) {
		node.cfgId = factoryFlowNode.counter + index;
	});
	factoryFlowNode.setCounter(entryNode.cfgId + allNodes.length);
	//factoryFlowNode.setCounter(allNodes.length);
	//allNodes.forEach(function (node, index) {
	//	node.cfgId = index;
	//});
	return [entryNode, exitNode, allNodes];
}

var continueTargets = [
	'ForStatement',
	'ForInStatement',
	'DoWhileStatement',
	'WhileStatement'];
var breakTargets = continueTargets.concat(['SwitchStatement']);
var throwTypes = [
	'AssignmentExpression', // assigning to undef or non-writable prop
	'BinaryExpression', // instanceof and in on non-objects
	'CallExpression', // obviously
	'MemberExpression', // getters may throw
	'NewExpression', // obviously
	'UnaryExpression' // delete non-deletable prop
];

<commit_msg>Fix the bug causing reading undefined property<commit_after>/*
 * From the repository: esgraph (https://github.com/Swatinem/esgraph)
 * @license LGPLv3 (http://www.gnu.org/licenses/lgpl-3.0-standalone.html)
 * @author Swatinem (arpad.borsos@googlemail.com)
 * @lastmodifiedBy ChengFuLin (chengfulin0806@gmail.com)
 * @lastmodifiedDate 2015-08-07
 */
var walker = require('walkes'),
	factoryFlowNode = require('./flownodefactory'),
	FlowNode = require('./flownode');

module.exports = ControlFlowGraph;
module.exports.dot = require('./dot');
module.exports.FlowNode = FlowNode;
module.exports.factoryFlowNode = factoryFlowNode;

// FIXME: switch/case with default before other cases?
// FIXME: catch creates a new scope, so should somehow be handled differently

// TODO: try/finally: finally follows try, but does not return to normal flow?

// TODO: labeled break/continue
// TODO: WithStatement

// TODO: avoid adding and deleting properties on ast nodes

/*
 * Returns [entry, exit] `FlowNode`s for the passed in AST
 */
function ControlFlowGraph(astNode) {
	'use strict';
	var parentStack = [];

	var exitNode = factoryFlowNode.create(FlowNode.EXIT_NODE_TYPE);
	var catchStack = [exitNode];
	createNodes(astNode);
	linkSiblings(astNode);

	walker(astNode, {
		CatchClause: function (node, recurse) {
			node.cfg.connect(getEntry(node.body));
			recurse(node.body);
		},
		DoWhileStatement: function (node, recurse) {
			mayThrow(node.test);
			node.test.cfg
				.connect(getEntry(node.body), FlowNode.TRUE_BRANCH_CONNECTION_TYPE)
				.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			recurse(node.body);
		},
		ExpressionStatement: connectNext,
		FunctionDeclaration: function () {},
		FunctionExpression: function () {},
		ForStatement: function (node, recurse) {
			if (node.test) {
				mayThrow(node.test);
				node.test.cfg
					.connect(getEntry(node.body), FlowNode.TRUE_BRANCH_CONNECTION_TYPE)
					.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
				if (node.update) {
					node.update.cfg.connect(node.test.cfg);
				}
			} else if (node.update) {
				node.update.cfg.connect(getEntry(node.body));
			}
			if (node.update) {
				mayThrow(node.update);
			}
			if (node.init) {
				mayThrow(node.init);
				node.init.cfg.connect(node.test && node.test.cfg || getEntry(node.body));
			}
			recurse(node.body);
		},
		ForInStatement: function (node, recurse) {
			mayThrow(node);
			node.cfg
				.connect(getEntry(node.body), FlowNode.TRUE_BRANCH_CONNECTION_TYPE)
				.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			recurse(node.body);
		},
		IfStatement: function (node, recurse) {
			recurse(node.consequent);
			mayThrow(node.test);
			node.test.cfg.connect(getEntry(node.consequent), FlowNode.TRUE_BRANCH_CONNECTION_TYPE);
			if (node.alternate) {
				recurse(node.alternate);
				node.test.cfg.connect(getEntry(node.alternate), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			} else {
				node.test.cfg.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			}
		},
		ReturnStatement: function (node) {
			mayThrow(node);
			node.cfg.connect(exitNode);
		},
		SwitchCase: function (node, recurse) {
			if (node.test) {
				// if this is a real case, connect `true` to the body
				// or the body of the next case
				var check = node;
				while (!check.consequent.length && check.cfg.nextSibling) {
					check = check.cfg.nextSibling.astNode;
				}
				node.cfg.connect(check.consequent.length && getEntry(check.consequent[0]) || getSuccessor(node.cfg.parent), FlowNode.TRUE_BRANCH_CONNECTION_TYPE);

				// and connect false to the next `case`
				node.cfg.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			} else {
				// this is the `default` case, connect it to the body, or the
				// successor of the parent
				node.cfg.connect(node.consequent.length && getEntry(node.consequent[0]) || getSuccessor(node.cfg.parent));
			}
			node.consequent.forEach(recurse);
		},
		SwitchStatement: function (node, recurse) {
			node.cfg.connect(node.cases[0].cfg);
			node.cases.forEach(recurse);
		},
		ThrowStatement: function (node) {
			node.cfg.connect(getExceptionTarget(node), FlowNode.EXCEPTION_CONNECTION_TYPE);
		},
		TryStatement: function (node, recurse) {
			var handler = node.handlers[0] && node.handlers[0].cfg || getEntry(node.finalizer);
			catchStack.push(handler);
			recurse(node.block);
			catchStack.pop();

			if (node.handlers.length) {
				recurse(node.handlers[0]);
			}
			if (node.finalizer) {
				//node.finalizer.cfg.connect(getSuccessor(node));
				recurse(node.finalizer);
			}
		},
		VariableDeclaration: connectNext,
		WhileStatement: function (node, recurse) {
			mayThrow(node.test);
			node.test.cfg
				.connect(getEntry(node.body), FlowNode.TRUE_BRANCH_CONNECTION_TYPE)
				.connect(getSuccessor(node), FlowNode.FALSE_BRANCH_CONNECTION_TYPE);
			recurse(node.body);
		}
	});

	var entryNode = factoryFlowNode.create(FlowNode.ENTRY_NODE_TYPE, astNode);
	entryNode.connect(getEntry(astNode), FlowNode.NORMAL_CONNECTION_TYPE);
	walker(astNode, {default: function (node, recurse) {
		if (!node.cfg) {
			return;
		}
		// ExpressionStatements should refer to their expression directly
		if (node.type === 'ExpressionStatement') {
			node.cfg.astNode = node.expression;
		}
		delete node.cfg;
		walker.checkProps(node, recurse);
	}});

	var allNodes = [];
	var reverseStack = [entryNode];
	var cfgNode;
	while (reverseStack.length) {
		cfgNode = reverseStack.pop();
		allNodes.push(cfgNode);
		[
			FlowNode.EXCEPTION_CONNECTION_TYPE,
			FlowNode.FALSE_BRANCH_CONNECTION_TYPE,
			FlowNode.TRUE_BRANCH_CONNECTION_TYPE,
			FlowNode.NORMAL_CONNECTION_TYPE
		].forEach(eachType);
	}
	/* Actually, in esgraph, it creates a graph with only four kinds of connection types, exception, false, true and normal */
	function eachType(type) {
		var next = cfgNode[type];
		if (type === FlowNode.ON_EVENT_CONNECTION_TYPE) {
			next.forEach(function (node) {
				if (reverseStack.indexOf(node) === -1 && allNodes.indexOf(node) === -1) {
					reverseStack.push(node);
				}
			});
		} else if (!!next && reverseStack.indexOf(next) === -1 && allNodes.indexOf(next) === -1) {
			reverseStack.push(next);
		}
	}

	function getExceptionTarget() {
		return catchStack[catchStack.length - 1];
	}

	function mayThrow(node) {
		if (expressionThrows(node)) {
			node.cfg.connect(getExceptionTarget(node), FlowNode.EXCEPTION_CONNECTION_TYPE);
		}
	}
	function expressionThrows(astNode) {
		if (typeof astNode !== 'object' || 'FunctionExpression' === astNode.type) {
			return false;
		}
		if (astNode.type && throwTypes.indexOf(astNode.type) !== -1) {
			return true;
		}
		var self = astNode;
		return Object.keys(self).some(function (key) {
			var prop = self[key];
			if (prop instanceof Array) {
				return prop.some(expressionThrows);
			} else if (typeof prop === 'object' && prop) {
				return expressionThrows(prop);
			}
			else {
				return false;
			}
		});
	}

	function getJumpTarget(astNode, types) {
		var parent = astNode.cfg.parent;
		while (types.indexOf(parent.type) === -1 && !!parent.cfg.parent) {
			parent = parent.cfg.parent;
		}
		return (types.indexOf(parent.type) !== -1) ? parent : null;
	}

	function connectNext(node) {
		mayThrow(node);
		node.cfg.connect(getSuccessor(node));
	}

	/**
	 * Returns the entry node of a statement
	 */
	function getEntry(astNode) {
		var target;
		switch (astNode.type) {
			case 'BreakStatement':
				target = getJumpTarget(astNode, breakTargets);
				return target ? getSuccessor(target) : exitNode;
			case 'ContinueStatement':
				target = getJumpTarget(astNode, continueTargets);
				switch (target.type) {
					case 'ForStatement':
						// continue goes to the update, test or body
						return target.update && target.update.cfg || target.test && target.test.cfg || getEntry(target.body);
					case 'ForInStatement':
						return target.cfg;
					case 'DoWhileStatement':
					/* falls through */
					case 'WhileStatement':
						return target.test.cfg;
				}
			// unreached
			/* falls through */
			case 'BlockStatement':
			/* falls through */
			case 'Program':
				return astNode.body.length && getEntry(astNode.body[0]) || getSuccessor(astNode);
			case 'DoWhileStatement':
				return getEntry(astNode.body);
			case 'EmptyStatement':
				return getSuccessor(astNode);
			case 'ForStatement':
				return astNode.init && astNode.init.cfg || astNode.test && astNode.test.cfg || getEntry(astNode.body);
			case 'FunctionDeclaration':
				return getSuccessor(astNode);
			case 'IfStatement':
				return astNode.test.cfg;
			case 'SwitchStatement':
				return getEntry(astNode.cases[0]);
			case 'TryStatement':
				return getEntry(astNode.block);
			case 'WhileStatement':
				return astNode.test.cfg;
			default:
				return astNode.cfg;
		}
	}
	/**
	 * Returns the successor node of a statement
	 */
	function getSuccessor(astNode) {
		// part of a block -> it already has a nextSibling
		if (astNode.cfg.nextSibling) {
			return astNode.cfg.nextSibling;
		}
		var parent = astNode.cfg.parent;
		if (!parent) { // it has no parent -> exitNode
			return exitNode;
		}
		switch (parent.type) {
			case 'DoWhileStatement':
				return parent.test.cfg;
			case 'ForStatement':
				return parent.update && parent.update.cfg || parent.test && parent.test.cfg || getEntry(parent.body);
			case 'ForInStatement':
				return parent.cfg;
			case 'TryStatement':
				return parent.finalizer && astNode !== parent.finalizer && getEntry(parent.finalizer) || getSuccessor(parent);
			case 'SwitchCase':
				// the sucessor of a statement at the end of a case block is
				// the entry of the next cases consequent
				if (!parent.cfg.nextSibling) {
					return getSuccessor(parent);
				}
				var check = parent.cfg.nextSibling.astNode;
				while (!check.consequent.length && check.cfg.nextSibling) {
					check = check.cfg.nextSibling.astNode;
				}
				// or the next statement after the switch, if there are no more cases
				return check.consequent.length && getEntry(check.consequent[0]) || getSuccessor(parent.parent);
			case 'WhileStatement':
				return parent.test.cfg;
			default:
				return getSuccessor(parent);
		}
	}

	/**
	 * Creates a FlowNode for every AST node
	 */
	function createNodes(astNode) {
		walker(astNode, { default: function (node, recurse) {
			var parent = parentStack.length ? parentStack[parentStack.length - 1] : undefined;
			createNode(node, parent);
			// do not recurse for FunctionDeclaration or any sub-expression
			if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type.indexOf('Expression') !== -1) {
				return;
			}
			parentStack.push(node);
			walker.checkProps(node, recurse);
			parentStack.pop();
		}});
	}
	function createNode(astNode, parent) {
		if (!astNode.cfg) {
			var newNode = factoryFlowNode.create(FlowNode.NORMAL_NODE_TYPE, astNode, parent);
			Object.defineProperty(astNode, 'cfg', {value: newNode, configurable: true});
		}
	}

	/**
	 * Links in the next sibling for nodes inside a block
	 */
	function linkSiblings(astNode) {
		function backToFront(list, recurse) {
			// link all the children to the next sibling from back to front,
			// so the nodes already have .nextSibling
			// set when their getEntry is called
			for (var i = list.length - 1; i >= 0; i--) {
				var child = list[i];
				if (i < list.length - 1) {
					child.cfg.nextSibling = getEntry(list[i + 1]);
				}
				recurse(child);
			}
		}
		function BlockOrProgram(node, recurse) {
			backToFront(node.body, recurse);
		}
		walker(astNode, {
			BlockStatement: BlockOrProgram,
			Program: BlockOrProgram,
			FunctionDeclaration: function () {},
			FunctionExpression: function () {},
			SwitchCase: function (node, recurse) {
				backToFront(node.consequent, recurse);
			},
			SwitchStatement: function (node, recurse) {
				backToFront(node.cases, recurse);
			}
		});
	}
	/// Reset cfgIds to skip those nodes not shown in CFG
	/// Reset to the first created node in this CFG
	factoryFlowNode.setCounter(entryNode.cfgId);
	allNodes.forEach(function (node, index) {
		node.cfgId = factoryFlowNode.counter + index;
	});
	factoryFlowNode.setCounter(entryNode.cfgId + allNodes.length);
	//factoryFlowNode.setCounter(allNodes.length);
	//allNodes.forEach(function (node, index) {
	//	node.cfgId = index;
	//});
	return [entryNode, exitNode, allNodes];
}

var continueTargets = [
	'ForStatement',
	'ForInStatement',
	'DoWhileStatement',
	'WhileStatement'];
var breakTargets = continueTargets.concat(['SwitchStatement']);
var throwTypes = [
	'AssignmentExpression', // assigning to undef or non-writable prop
	'BinaryExpression', // instanceof and in on non-objects
	'CallExpression', // obviously
	'MemberExpression', // getters may throw
	'NewExpression', // obviously
	'UnaryExpression' // delete non-deletable prop
];

<|endoftext|>


<commit_before>// TODO:
// - Enable check for empty namespaces with RegExp instead of Array.indexOf()
// - Update *removeListener* to support namespaced events
// - Update *listener* to support namespaced events

;!function(root) {

  var EventEmitter2 = root.EventEmitter2 = function EventEmitter2(conf) {

    // If *_listeners* is undefined invoke *initializeEvents* to set the
    // default properties.
    if (!this._listeners) this.initializeEvents();
  };

  EventEmitter2.prototype.initializeEvents = function(conf) {
    conf = conf || {};
    var self = this;
    self.setEventDelimiter(conf.delimiter);
    self._eventCaseSensitive = ~(conf.eventCaseSensitive || true);
    self._eventDelimiter = conf.eventDelimiter || '.';
    self._maxListeners = conf.maxListeners === 0 ? 0 : conf.maxListeners || 10;
    self._listeners = {};
    self._allListenerFn = [];
    return self;
  }

  EventEmitter2.prototype.addListener = function(event, fn) {
    var self = this;

    // If *fn* is not a function throw an error. An *fn* that is not a function
    // can not be invoked when an event is emitted and therefor is not allowed
    // to be added.
    if ('function' !== typeof fn) {
      throw new Error('addListener only accepts instances of Function');
    }

    // If *_listeners* is undefined invoke *initializeEvents* to set the
    // default properties.
    if (!self._listeners) self.initializeEvents();

    // Set variables.
    var i = 0,
        delimiter = self._eventDelimiter,
        doubleDelimiter = delimiter + delimiter,
        caseSensative = self._eventCaseSensitive,
        listeners = self._listeners,
        maxListeners = self._maxListeners,
        event = caseSensative ? event.toLowerCase() : event,
        ns, exploreNs = listeners,
        listenToEvent, eventListeners;

    // If an event starts or ends with a delimiter character or two or more
    // delimiter characters are used after each other throw an error.
    // Starting or ending an event with a delimiter character or combining
    // characters creates empty namespaces which don't work and therefor are
    // not allowed.
    if (event[event.length - 1] === delimiter || event[0] === delimiter || ~event.indexOf(doubleDelimiter)) {
      self.eventNameError('addListener');
      return false;
    }

    // If the event is namespaced loop through the namespaces, set seperate
    // events for each namespace and set *listenToEvent* to the last
    // namespace to attach the listener function to this event after the loop.
    if (~event.indexOf(delimiter)) {

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);

      // Loop through the namespaces.
      for (i = 0, l = ns.length; i < l; i++) {

        // If the event is undefined in *exploreNs* it means it doesn't exist
        // in *listeners* so a new event should be created.
        if (!exploreNs[ns[i]]) {
          exploreNs[ns[i]] = {
              _name: ns[i],
              _fn: [],
              _ns: {}
            };
        }

        // If the loop is at the end set *listenToEvent* to the current
        // namespace - which is the last one - to attach the listener to this
        // event. If the loop is not at the end rebase *exploreNs* to loop the
        // current event's namespaces.
        if (i === ns.length - 1) {
          listenToEvent = exploreNs[ns[i]];
        } else {
          exploreNs = exploreNs[ns[i]]._ns;
        }
      }
    }

    // If the event is not namespaced set the single event and set
    // *listenToEvent* to this event to attach the listener to.
    else {

      // If the event is undefined in *listeners* it means it doesn't exist
      // so a new event should be created.
      if (!listeners[event]) {
        listeners[event] = {
          _name: event,
          _fn: [],
          _ns: {}
        };
      }

      // Set *listenToEvent* to the current event to attach the listener to.
      listenToEvent = listeners[event];
    }

    eventListeners = listenToEvent._fn;

    // Signal that a new listener is being added.
    self.emit('newListener', event, fn);

    // If the max amount of listeners has been reached signal and return to
    // cancel the addition of this new listener.
    if (eventListeners.length >= maxListeners) {
      self.emit('maxListeners', event);
      return self;
    }

    // Add the function to the event listener collection.
    eventListeners.push(fn);
    return self;
  };

  EventEmitter2.prototype.onAny = function(fn) {

    // If *_listeners* is undefined invoke *initializeEvents* to set the
    // default properties.
    if (!this._listeners) this.initializeEvents();

    // Add the function to the event listener collection.
    this._allListenerFn.push(fn);
  };

  EventEmitter2.prototype.on = EventEmitter2.prototype.addListener;

  EventEmitter2.prototype.once = function(event, fn) {
    this.many(event, 1, fn);
    return this;
  };

  EventEmitter2.prototype.many = function(event, ttl, fn) {
    var self = this;

    self.addListener(event, function() {
      if(ttl-- == 0) {
        self.removeListener(event, fn);
      } else {
        fn.apply(null, arguments);
      }
    });

    return self;
  };

  EventEmitter2.prototype.emit = function(event) {
    var self = this,
        args = arguments,
        i = 0,
        delimiter = self._eventDelimiter,
        doubleDelimiter = delimiter + delimiter,
        caseSensitive = self._eventCaseSensitive,
        listeners = self._listeners,
        ns, exploreNs = [listeners], collectedListeners = [],
        invoked = false;

    // If no listeners are defined the emit will not be able to invoke a
    // function. Therefore return *invoked* to exit.
    if (!listeners) return invoked;

    // If an event starts or ends with a delimiter character or two or more
    // delimiter characters are used after each other throw an error.
    // Starting or ending an event with a delimiter character or combining
    // characters creates empty namespaces which don't work and therefor are
    // not allowed.
    if (event[event.length - 1] === delimiter || event[0] === delimiter || ~event.indexOf(doubleDelimiter)) {
      self.eventNameError('emit');
      return false;
    }

    // If the event is namespaced loop through the namespaces, find all the
    // listeners for the namespaces and wildcards and add them to the
    // *collectedListeners* array for listener invocation after the loop.
    if (~event.indexOf(delimiter) || event === '*') {

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);
      for (i = 0; i < ns.length; i++) {
        if (ns[i] === '') {
          self.eventNameError('addListener');
          return false;
        }
      }

      // Loop through the namespaces.
      for (i = 0; i < ns.length; i++) {

        // While looping through the namespace array loop through *exploreNs*
        // with *i* as well. This is basicaly the same as looping through the
        // the different levels of *events*. *collectedNs* is used to collect
        // all the namespaces that are going to be explored for the next level
        // of *events*.
        var currentNs = ns[i],
            currentExploreNs = exploreNs[i],
            collectedNs = [];

        // Loop through the current level of *events*.
        for (var key1 in currentExploreNs) {

          // Set the current namespace in *events* that is being explored.
          var exploredNs = currentExploreNs[key1],
              name = caseSensitive ? exploredNs._name.toLowerCase() : exploredNs._name;

          // If there is a match for the namespace or a wildcard collect the
          // next level of namespaces or collect the listeners of this
          // particular namespace.
          if (currentNs === '*' || (name === currentNs || name === '*')) {

            // If the loop is at the end collect the listeners for this
            // particular event and add them to the *collectedListeners* array
            // for listener invocation after the loop. If the loop is not at
            // the end collect the next level of namespaces.
            if (i === ns.length - 1) {
              var listeners = exploredNs._fn;
              if (listeners.length > 0) {
                collectedListeners = collectedListeners.concat(listeners);
              }
            } else {
              for (var key2 in exploredNs._ns) {
                collectedNs.push(exploredNs._ns[key2]);
              }
            }
          }
        }

        exploreNs.push(collectedNs);
      }
    }

    // If the event is not namespaced collect all the functions for this
    // particular event and from the wildcard event and add them to
    // the *collectedListeners* array for invocation.
    else if (event !== '*' && listeners[event] && listeners[event]._fn.length > 0) {
      if (listeners['*'] && listeners['*']._fn.length > 0) {
        collectedListeners = collectedListeners.concat(listeners['*']._fn);
      }
      collectedListeners = collectedListeners.concat(listeners[event]._fn);
    }

    // Loop through the collected functions and invoke them.
    if (collectedListeners.length > 0) {
      for (i = 0, l = collectedListeners.length; i < l; i++) {
        collectedListeners[i].apply(self, args);
        invoked = true;
      }
    }

    // Loop through the *_allListnerFn* functions and invoke them.
    if (self._allListenerFn && self._allListenerFn.length > 0) {
      for (i = 0, l = self._allListenerFn.length; i < l; i++) {
        self._allListenerFn[i].apply(self, args);
        invoked = true;
      }
    }

    return invoked;
  };

  EventEmitter2.prototype.emitAll = function() {};

  EventEmitter2.prototype.removeListener = function(event, fn) {
    if (fn && this._listeners[event] && this._listeners[event]._fn) {
      var fns = this._listeners[event]._fn;
      for(var i = 0, l = fns.length; i < l; i++) {
        if(fn === fns[i]) {
          return fns.splice(i, 1);
        }
      }
    } else {
      this._listeners[event] = {};
    }
    return this;
  };

  EventEmitter2.prototype.un = EventEmitter2.prototype.removeListener;

  EventEmitter2.prototype.unAny = function(fn) {
    if (fn && this._allListenerFn && this._allListenerFn.length > 0) {
      var fns = this._allListenerFn;
      for(var i = 0, l = fns.length; i < l; i++) {
        if(fn === fns[i]) {
          return fns.splice(i, 1);
        }
      }
    } else {
      this._allListenerFn = [];
    }
  };

  EventEmitter2.prototype.removeAllListeners = function() {
    this._listeners = {};
    this._allListenerFn = [];
    return this;
  };

  EventEmitter2.prototype.setEventDelimiter = function(delimiter) {
    if (delimiter === '*' && delimiter.length === 1) {
      throw new Error('setEventDelimiter doesn\'t accept a "*" (wild-card) character');
    } else {
      this._eventDelimiter = delimiter;
    }
    return this;
  };

  EventEmitter2.prototype.setEventCaseSensitive = function(caseSensative) {
    this._eventCaseSensitive = caseSensative;
    return this;
  };

  EventEmitter2.prototype.setMaxListeners = function(n) {
    this._maxListeners = n;
    return this;
  };

  EventEmitter2.prototype.listeners = function(event) {
    if(this._listeners[event]) {
      return this._listeners[event]._fn;
    } else {
      return false;
    }
  };

  EventEmitter2.prototype.eventNameError = function(fn) {
    throw new Error(fn + ' doesn\'t accept empty namespaces or events starting or ending with a "' + this._eventDelimiter + '" (delimiter) character ');
  };

}(typeof exports === 'undefined' ? window : exports);
<commit_msg>[lib] fixed reversed case sensitive bug<commit_after>// TODO:
// - Enable check for empty namespaces with RegExp instead of Array.indexOf()
// - Update *removeListener* to support namespaced events
// - Update *listener* to support namespaced events

;!function(root) {

  var EventEmitter2 = root.EventEmitter2 = function EventEmitter2(conf) {

    // If *_listeners* is undefined invoke *initializeEvents* to set the
    // default properties.
    if (!this._listeners) this.initializeEvents();
  };

  EventEmitter2.prototype.initializeEvents = function(conf) {
    conf = conf || {};
    var self = this;
    self.setEventDelimiter(conf.delimiter);
    self._eventCaseSensitive = ~(conf.eventCaseSensitive || true);
    self._eventDelimiter = conf.eventDelimiter || '.';
    self._maxListeners = conf.maxListeners === 0 ? 0 : conf.maxListeners || 10;
    self._listeners = {};
    self._allListenerFn = [];
    return self;
  }

  EventEmitter2.prototype.addListener = function(event, fn) {
    var self = this;

    // If *fn* is not a function throw an error. An *fn* that is not a function
    // can not be invoked when an event is emitted and therefor is not allowed
    // to be added.
    if ('function' !== typeof fn) {
      throw new Error('addListener only accepts instances of Function');
    }

    // If *_listeners* is undefined invoke *initializeEvents* to set the
    // default properties.
    if (!self._listeners) self.initializeEvents();

    // Set variables.
    var i = 0,
        delimiter = self._eventDelimiter,
        doubleDelimiter = delimiter + delimiter,
        caseSensitive = self._eventCaseSensitive,
        listeners = self._listeners,
        maxListeners = self._maxListeners,
        event = caseSensitive ? event : event.toLowerCase(),
        ns, exploreNs = listeners,
        listenToEvent, eventListeners;

    // If an event starts or ends with a delimiter character or two or more
    // delimiter characters are used after each other throw an error.
    // Starting or ending an event with a delimiter character or combining
    // characters creates empty namespaces which don't work and therefor are
    // not allowed.
    if (event[event.length - 1] === delimiter || event[0] === delimiter || ~event.indexOf(doubleDelimiter)) {
      self.eventNameError('addListener');
      return false;
    }

    // If the event is namespaced loop through the namespaces, set seperate
    // events for each namespace and set *listenToEvent* to the last
    // namespace to attach the listener function to this event after the loop.
    if (~event.indexOf(delimiter)) {

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);

      // Loop through the namespaces.
      for (i = 0, l = ns.length; i < l; i++) {

        // If the event is undefined in *exploreNs* it means it doesn't exist
        // in *listeners* so a new event should be created.
        if (!exploreNs[ns[i]]) {
          exploreNs[ns[i]] = {
              _name: ns[i],
              _fn: [],
              _ns: {}
            };
        }

        // If the loop is at the end set *listenToEvent* to the current
        // namespace - which is the last one - to attach the listener to this
        // event. If the loop is not at the end rebase *exploreNs* to loop the
        // current event's namespaces.
        if (i === ns.length - 1) {
          listenToEvent = exploreNs[ns[i]];
        } else {
          exploreNs = exploreNs[ns[i]]._ns;
        }
      }
    }

    // If the event is not namespaced set the single event and set
    // *listenToEvent* to this event to attach the listener to.
    else {

      // If the event is undefined in *listeners* it means it doesn't exist
      // so a new event should be created.
      if (!listeners[event]) {
        listeners[event] = {
          _name: event,
          _fn: [],
          _ns: {}
        };
      }

      // Set *listenToEvent* to the current event to attach the listener to.
      listenToEvent = listeners[event];
    }

    eventListeners = listenToEvent._fn;

    // Signal that a new listener is being added.
    self.emit('newListener', event, fn);

    // If the max amount of listeners has been reached signal and return to
    // cancel the addition of this new listener.
    if (eventListeners.length >= maxListeners) {
      self.emit('maxListeners', event);
      return self;
    }

    // Add the function to the event listener collection.
    eventListeners.push(fn);
    return self;
  };

  EventEmitter2.prototype.onAny = function(fn) {

    // If *_listeners* is undefined invoke *initializeEvents* to set the
    // default properties.
    if (!this._listeners) this.initializeEvents();

    // Add the function to the event listener collection.
    this._allListenerFn.push(fn);
  };

  EventEmitter2.prototype.on = EventEmitter2.prototype.addListener;

  EventEmitter2.prototype.once = function(event, fn) {
    this.many(event, 1, fn);
    return this;
  };

  EventEmitter2.prototype.many = function(event, ttl, fn) {
    var self = this;

    self.addListener(event, function() {
      if(ttl-- == 0) {
        self.removeListener(event, fn);
      } else {
        fn.apply(null, arguments);
      }
    });

    return self;
  };

  EventEmitter2.prototype.emit = function(event) {
    var self = this,
        args = arguments,
        i = 0,
        delimiter = self._eventDelimiter,
        doubleDelimiter = delimiter + delimiter,
        caseSensitive = self._eventCaseSensitive,
        listeners = self._listeners,
        event = caseSensitive ? event : event.toLowerCase(),
        ns, exploreNs = [listeners], collectedListeners = [],
        invoked = false;

    // If no listeners are defined the emit will not be able to invoke a
    // function. Therefore return *invoked* to exit.
    if (!listeners) return invoked;

    // If an event starts or ends with a delimiter character or two or more
    // delimiter characters are used after each other throw an error.
    // Starting or ending an event with a delimiter character or combining
    // characters creates empty namespaces which don't work and therefor are
    // not allowed.
    if (event[event.length - 1] === delimiter || event[0] === delimiter || ~event.indexOf(doubleDelimiter)) {
      self.eventNameError('emit');
      return false;
    }

    // If the event is namespaced loop through the namespaces, find all the
    // listeners for the namespaces and wildcards and add them to the
    // *collectedListeners* array for listener invocation after the loop.
    if (~event.indexOf(delimiter) || event === '*') {

      // Split the event into a namespace array for looping.
      ns = event.split(delimiter);
      for (i = 0; i < ns.length; i++) {
        if (ns[i] === '') {
          self.eventNameError('addListener');
          return false;
        }
      }

      // Loop through the namespaces.
      for (i = 0; i < ns.length; i++) {

        // While looping through the namespace array loop through *exploreNs*
        // with *i* as well. This is basicaly the same as looping through the
        // the different levels of *events*. *collectedNs* is used to collect
        // all the namespaces that are going to be explored for the next level
        // of *events*.
        var currentNs = ns[i],
            currentExploreNs = exploreNs[i],
            collectedNs = [];

        // Loop through the current level of *events*.
        for (var key1 in currentExploreNs) {

          // Set the current namespace in *events* that is being explored.
          var exploredNs = currentExploreNs[key1],
              name = caseSensitive ? exploredNs._name : exploredNs._name.toLowerCase();

          // If there is a match for the namespace or a wildcard collect the
          // next level of namespaces or collect the listeners of this
          // particular namespace.
          if (currentNs === '*' || (name === currentNs || name === '*')) {

            // If the loop is at the end collect the listeners for this
            // particular event and add them to the *collectedListeners* array
            // for listener invocation after the loop. If the loop is not at
            // the end collect the next level of namespaces.
            if (i === ns.length - 1) {
              var listeners = exploredNs._fn;
              if (listeners.length > 0) {
                collectedListeners = collectedListeners.concat(listeners);
              }
            } else {
              for (var key2 in exploredNs._ns) {
                collectedNs.push(exploredNs._ns[key2]);
              }
            }
          }
        }

        exploreNs.push(collectedNs);
      }
    }

    // If the event is not namespaced collect all the functions for this
    // particular event and from the wildcard event and add them to
    // the *collectedListeners* array for invocation.
    else if (event !== '*' && listeners[event] && listeners[event]._fn.length > 0) {
      if (listeners['*'] && listeners['*']._fn.length > 0) {
        collectedListeners = collectedListeners.concat(listeners['*']._fn);
      }
      collectedListeners = collectedListeners.concat(listeners[event]._fn);
    }

    // Loop through the collected functions and invoke them.
    if (collectedListeners.length > 0) {
      for (i = 0, l = collectedListeners.length; i < l; i++) {
        collectedListeners[i].apply(self, args);
        invoked = true;
      }
    }

    // Loop through the *_allListnerFn* functions and invoke them.
    if (self._allListenerFn && self._allListenerFn.length > 0) {
      for (i = 0, l = self._allListenerFn.length; i < l; i++) {
        self._allListenerFn[i].apply(self, args);
        invoked = true;
      }
    }

    return invoked;
  };

  EventEmitter2.prototype.emitAll = function() {};

  EventEmitter2.prototype.removeListener = function(event, fn) {
    if (fn && this._listeners[event] && this._listeners[event]._fn) {
      var fns = this._listeners[event]._fn;
      for(var i = 0, l = fns.length; i < l; i++) {
        if(fn === fns[i]) {
          return fns.splice(i, 1);
        }
      }
    } else {
      this._listeners[event] = {};
    }
    return this;
  };

  EventEmitter2.prototype.un = EventEmitter2.prototype.removeListener;

  EventEmitter2.prototype.unAny = function(fn) {
    if (fn && this._allListenerFn && this._allListenerFn.length > 0) {
      var fns = this._allListenerFn;
      for(var i = 0, l = fns.length; i < l; i++) {
        if(fn === fns[i]) {
          return fns.splice(i, 1);
        }
      }
    } else {
      this._allListenerFn = [];
    }
  };

  EventEmitter2.prototype.removeAllListeners = function() {
    this._listeners = {};
    this._allListenerFn = [];
    return this;
  };

  EventEmitter2.prototype.setEventDelimiter = function(delimiter) {
    if (delimiter === '*' && delimiter.length === 1) {
      throw new Error('setEventDelimiter doesn\'t accept a "*" (wild-card) character');
    } else {
      this._eventDelimiter = delimiter;
    }
    return this;
  };

  EventEmitter2.prototype.setEventCaseSensitive = function(caseSensative) {
    this._eventCaseSensitive = caseSensative;
    return this;
  };

  EventEmitter2.prototype.setMaxListeners = function(n) {
    this._maxListeners = n;
    return this;
  };

  EventEmitter2.prototype.listeners = function(event) {
    if(this._listeners[event]) {
      return this._listeners[event]._fn;
    } else {
      return false;
    }
  };

  EventEmitter2.prototype.eventNameError = function(fn) {
    throw new Error(fn + ' doesn\'t accept empty namespaces or events starting or ending with a "' + this._eventDelimiter + '" (delimiter) character ');
  };

}(typeof exports === 'undefined' ? window : exports);
<|endoftext|>


<commit_before>/**
 * Copyright reelyActive 2014
 * We believe in an open Internet of Things
 */

var util = require('util');
var events = require('events');
var nedb = require('nedb');

var TEMPORAL_EVENTS_INTERVAL_MILLISECONDS = 1000;
var DISAPPEARANCE_MILLISECONDS = 5000;

/**
 * EventsManager Class
 * Collects and manages events in a database
 * @param {Object} options The options as a JSON object.
 * @constructor
 */
function EventsManager(options) {
  var self = this;
  this.disappearanceMilliseconds = options.disappearanceMilliseconds ||
                                   DISAPPEARANCE_MILLISECONDS;
  this.db = new nedb();
  this.db.ensureIndex({ fieldName: "tiraid.timestamp" });
  this.db.ensureIndex({ fieldName: "tiraid.radioDecodings.0.identifier.value" });

  function temporalEventDetection() {
    detectDisappearances(self);
  }

  setInterval(temporalEventDetection, TEMPORAL_EVENTS_INTERVAL_MILLISECONDS);

  events.EventEmitter.call(this);
};
util.inherits(EventsManager, events.EventEmitter);

/**
 * Handle a tiraid, update the database and detect Displacement and
 * Appearance events.
 * @param {Object} tiraid The tiraid object.
 */
EventsManager.prototype.handleTiraid = function(tiraid) {
  var self = this;
  var id = tiraid.identifier.value;
  var strongestDecoder = tiraid.radioDecodings[0].identifier.value;

  // Try to update a record with the same ID and strongestDecoder,
  // if successful, no event occurred.
  this.db.update({ _id: id, "tiraid.radioDecodings.0.identifier.value": strongestDecoder },
                 { tiraid: tiraid }, { upsert: true },
                 function(err, numReplaced, newDoc) {

    // ID exists but strongestDecoder is different, this is a Displacement
    if(err && (err.errorType == "uniqueViolated")) {
      self.db.update({ _id: id }, { tiraid: tiraid }, { upsert: true },
                     function(err, numReplaced, newDoc) {
        // TODO: tiraid mashup if multiple origins
        self.emit('displacement', tiraid);
      });
    }

    // A new record was created, this is an Appearance
    else if(newDoc) {
      self.emit('appearance', tiraid);
    }
  });
};

/**
 * Detect disappearances from the database
 * @param {EventsManager} instance The given instance.
 */
function detectDisappearances(instance) {
  var currentTime = new Date();
  var cutoffTime = currentTime.setMilliseconds(currentTime.getMilliseconds() -
                                          instance.disappearanceMilliseconds);
  cutoffTime = new Date(cutoffTime).toISOString();
  instance.db.find({ "tiraid.timestamp": { $lt: cutoffTime } }, function(err, docs) {
    docs.forEach(function(value, index, array) {
      var id = value._id;
      instance.db.remove({ _id: value._id }, {}, function(err, numRemoved) {
        instance.emit('disappearance', value.tiraid);
      });
    });
  });
}

module.exports = EventsManager;
<commit_msg>Extended disappearance delay from 5 to 10 seconds<commit_after>/**
 * Copyright reelyActive 2014
 * We believe in an open Internet of Things
 */

var util = require('util');
var events = require('events');
var nedb = require('nedb');

var TEMPORAL_EVENTS_INTERVAL_MILLISECONDS = 1000;
var DISAPPEARANCE_MILLISECONDS = 10000;

/**
 * EventsManager Class
 * Collects and manages events in a database
 * @param {Object} options The options as a JSON object.
 * @constructor
 */
function EventsManager(options) {
  var self = this;
  this.disappearanceMilliseconds = options.disappearanceMilliseconds ||
                                   DISAPPEARANCE_MILLISECONDS;
  this.db = new nedb();
  this.db.ensureIndex({ fieldName: "tiraid.timestamp" });
  this.db.ensureIndex({ fieldName: "tiraid.radioDecodings.0.identifier.value" });

  function temporalEventDetection() {
    detectDisappearances(self);
  }

  setInterval(temporalEventDetection, TEMPORAL_EVENTS_INTERVAL_MILLISECONDS);

  events.EventEmitter.call(this);
};
util.inherits(EventsManager, events.EventEmitter);

/**
 * Handle a tiraid, update the database and detect Displacement and
 * Appearance events.
 * @param {Object} tiraid The tiraid object.
 */
EventsManager.prototype.handleTiraid = function(tiraid) {
  var self = this;
  var id = tiraid.identifier.value;
  var strongestDecoder = tiraid.radioDecodings[0].identifier.value;

  // Try to update a record with the same ID and strongestDecoder,
  // if successful, no event occurred.
  this.db.update({ _id: id, "tiraid.radioDecodings.0.identifier.value": strongestDecoder },
                 { tiraid: tiraid }, { upsert: true },
                 function(err, numReplaced, newDoc) {

    // ID exists but strongestDecoder is different, this is a Displacement
    if(err && (err.errorType == "uniqueViolated")) {
      self.db.update({ _id: id }, { tiraid: tiraid }, { upsert: true },
                     function(err, numReplaced, newDoc) {
        // TODO: tiraid mashup if multiple origins
        self.emit('displacement', tiraid);
      });
    }

    // A new record was created, this is an Appearance
    else if(newDoc) {
      self.emit('appearance', tiraid);
    }
  });
};

/**
 * Detect disappearances from the database
 * @param {EventsManager} instance The given instance.
 */
function detectDisappearances(instance) {
  var currentTime = new Date();
  var cutoffTime = currentTime.setMilliseconds(currentTime.getMilliseconds() -
                                          instance.disappearanceMilliseconds);
  cutoffTime = new Date(cutoffTime).toISOString();
  instance.db.find({ "tiraid.timestamp": { $lt: cutoffTime } }, function(err, docs) {
    docs.forEach(function(value, index, array) {
      var id = value._id;
      instance.db.remove({ _id: value._id }, {}, function(err, numRemoved) {
        instance.emit('disappearance', value.tiraid);
      });
    });
  });
}

module.exports = EventsManager;
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var merge = require('utils-merge')
  , TokenError = require('../errors/tokenerror');

module.exports = function(options, issue) {
  if (typeof options == 'function') {
    issue = options;
    options = undefined;
  }
  options = options || {};

  if (!issue) { throw new TypeError('oauth2orize-mfa.push exchange requires an issue callback'); }

  var userProperty = options.userProperty || 'user';

  // For maximum flexibility, multiple scope spearators can optionally be
  // allowed.  This allows the server to accept clients that separate scope
  // with either space or comma (' ', ',').  This violates the specification,
  // but achieves compatibility with existing client libraries that are already
  // deployed.
  var separators = options.scopeSeparator || ' ';
  if (!Array.isArray(separators)) {
    separators = [ separators ];
  }

  return function push(req, res, next) {
    // The 'user' property of `req` holds the authenticated user.  In the case
    // of the token endpoint, the property will contain the OAuth 2.0 client.
    var client = req[userProperty]
      , pushCode = req.body.push_code
      , scope = req.body.scope;

    if (!pushCode) { return next(new TokenError('Missing required parameter: push_code', 'invalid_request')); }

    if (scope) {
      for (var i = 0, len = separators.length; i < len; i++) {
        var separated = scope.split(separators[i]);
        // only separate on the first matching separator.  this allows for a sort
        // of separator "priority" (ie, favor spaces then fallback to commas)
        if (separated.length > 1) {
          scope = separated;
          break;
        }
      }
      if (!Array.isArray(scope)) { scope = [ scope ]; }
    }

    function issued(err, accessToken, refreshToken, params) {
      if (err) { return next(err); }
      if (!accessToken) { return next(new TokenError('Invalid resource owner credentials', 'invalid_grant')); }
      if (refreshToken && typeof refreshToken == 'object') {
        params = refreshToken;
        refreshToken = null;
      }

      var tok = {};
      tok.access_token = accessToken;
      if (refreshToken) { tok.refresh_token = refreshToken; }
      if (params) { merge(tok, params); }
      tok.token_type = tok.token_type || 'Bearer';

      var json = JSON.stringify(tok);
      res.setHeader('Content-Type', 'application/json');
      res.setHeader('Cache-Control', 'no-store');
      res.setHeader('Pragma', 'no-cache');
      res.end(json);
    }

    try {
      var arity = issue.length;
      if (arity == 6) {
        issue(client, pushCode, scope, req.body, req.authInfo, issued);
      } else if (arity == 5) {
        issue(client, pushCode, scope, req.body, issued);
      } else if (arity == 4) {
        issue(client, pushCode, scope, issued);
      } else { // arity == 3
        issue(client, pushCode, issued);
      }
    } catch (ex) {
      return next(ex);
    }
  };
};
<commit_msg>Authenticate access token from first factor exchange.<commit_after>/**
 * Module dependencies.
 */
var merge = require('utils-merge')
  , TokenError = require('../errors/tokenerror');

module.exports = function(options, authenticate, issue) {
  if (typeof options == 'function') {
    issue = authenticate;
    authenticate = options;
    options = undefined;
  }
  options = options || {};

  if (!authenticate) { throw new TypeError('oauth2orize-mfa.push exchange requires an authenticate callback'); }
  if (!issue) { throw new TypeError('oauth2orize-mfa.push exchange requires an issue callback'); }

  var userProperty = options.userProperty || 'user';

  // For maximum flexibility, multiple scope spearators can optionally be
  // allowed.  This allows the server to accept clients that separate scope
  // with either space or comma (' ', ',').  This violates the specification,
  // but achieves compatibility with existing client libraries that are already
  // deployed.
  var separators = options.scopeSeparator || ' ';
  if (!Array.isArray(separators)) {
    separators = [ separators ];
  }

  return function push(req, res, next) {
    // The 'user' property of `req` holds the authenticated user.  In the case
    // of the token endpoint, the property will contain the OAuth 2.0 client.
    var client = req[userProperty]
      , token = req.body.access_token
      , oobCode = req.body.oob_code
      , scope = req.body.scope;

      // TODO: Access token is required
    if (!oobCode) { return next(new TokenError('Missing required parameter: oob_code', 'invalid_request')); }

    if (scope) {
      for (var i = 0, len = separators.length; i < len; i++) {
        var separated = scope.split(separators[i]);
        // only separate on the first matching separator.  this allows for a sort
        // of separator "priority" (ie, favor spaces then fallback to commas)
        if (separated.length > 1) {
          scope = separated;
          break;
        }
      }
      if (!Array.isArray(scope)) { scope = [ scope ]; }
    }

    function authenticated(err, user, info) {
      if (err) { return next(err); }

      function issued(err, accessToken, refreshToken, params) {
        if (err) { return next(err); }
        if (!accessToken) { return next(new TokenError('Invalid resource owner credentials', 'invalid_grant')); }
        if (refreshToken && typeof refreshToken == 'object') {
          params = refreshToken;
          refreshToken = null;
        }

        var tok = {};
        tok.access_token = accessToken;
        if (refreshToken) { tok.refresh_token = refreshToken; }
        if (params) { merge(tok, params); }
        tok.token_type = tok.token_type || 'Bearer';

        var json = JSON.stringify(tok);
        res.setHeader('Content-Type', 'application/json');
        res.setHeader('Cache-Control', 'no-store');
        res.setHeader('Pragma', 'no-cache');
        res.end(json);
      }

      try {
        var arity = issue.length;
        if (arity == 8) {
          issue(client, user, oobCode, scope, req.body, info, req.authInfo, issued);
        } else if (arity == 7) {
          issue(client, user, oobCode, scope, req.body, info, issued);
        } else if (arity == 6) {
          issue(client, user, oobCode, scope, req.body, issued);
        } else if (arity == 5) {
          issue(client, user, oobCode, scope, issued);
        } else { // arity == 4
          issue(client, user, oobCode, issued);
        }
      } catch (ex) {
        return next(ex);
      }
    }

    if (options.passReqToCallback) {
      authenticate(req, token, authenticated);
    } else {
      authenticate(token, authenticated);
    }
  };
};
<|endoftext|>


<commit_before>/**
 * To retrieve TFA -- Today's featured article -- for a given date.
 */

'use strict';

const crypto = require('crypto');
const domino = require('domino');
const preq = require('preq');
const BBPromise = require('bluebird');

const api = require('../api-util');
const dateUtil = require('../dateUtil');
const mUtil = require('../mobile-util');
const mwapi = require('../mwapi');
const sUtil = require('../util');

const HTTPError = sUtil.HTTPError;

const supportedLangs = [ "bg", "cs", "de", "el", "en", "fa", "fr", "he", "hu", "ja", "la", "no",
    "ur", "vi" ];

const isSupported = (domain) => {
    return supportedLangs.indexOf(mUtil.getLanguageFromDomain(domain)) > -1
        && domain.endsWith('.wikipedia.org');
};

/**
 * Builds the request to get the Special:FeedItem provided by the FeaturedFeed extension.
 * @param {!String} domain the requested domain, e.g. 'de.wikipedia.org'
 * @param {!Date} date for which day the featured article is requested
 * @return {!Promise} a promise resolving as an JSON object containing the response
 */
const requestFeaturedFeedSpecialItem = (domain, date) => {
    let lang = mUtil.getLanguageFromDomain(domain);
    if (lang === 'no') {
        lang = 'nb';
    }
    const formattedDateString = `${dateUtil.formatYYYYMMDD(date)}000000`;
    const path = 'wiki/Special:FeedItem/featured';
    return preq.get({
        // useskin=apioutput is a way to get just the content without the header and footer
        uri: `https://${domain}/${path}/${formattedDateString}/${lang}?useskin=apioutput`,
        headers: {
            'accept': 'text/html; charset=UTF-8'
        }
    });
};

// -- functions dealing with responses:

const findPageTitle = (html) => {
    const document = domino.createDocument(html);
    const scope = document.querySelector('div#mw-content-text');
    if (!scope) {
        return;
    }

    const found = scope.querySelector('b > a, a > b');
    if (found) {
        if (found.tagName === 'A') { // b > a
            return found.getAttribute('title');
        } else { // a > b
            // the anchor is the parent node
            return found.parentNode.getAttribute('title');
        }
    }
};

const createSha1 = (input) => {
    const shasum = crypto.createHash('sha1');
    shasum.update(input);
    return shasum.digest('hex');
};

// -- main

const promise = (app, req) => {
    let pageTitle;
    const domain = req.params.domain;
    const aggregated = !!req.query.aggregated;

    if (!dateUtil.validate(dateUtil.hyphenDelimitedDateString(req))) {
        if (aggregated) {
            return BBPromise.resolve({});
        }
        dateUtil.throwDateError();
    }

    if (!isSupported(domain)) {
        if (aggregated) {
            return BBPromise.resolve({});
        } else {
            throw new HTTPError({
                status: 501,
                type: 'unsupported_language',
                title: 'Language not supported',
                detail: 'The language you have requested is not yet supported.'
            });
        }
    }

    const requestedDate = dateUtil.getRequestedDate(req);
    if (domain === 'de.wikipedia.org' && !(dateUtil.isWithinLast3Days(requestedDate))) {
        return BBPromise.resolve({});
    }

    return requestFeaturedFeedSpecialItem(domain, requestedDate)
    .then((response) => {
        api.checkResponseStatus(req, response);
        pageTitle = findPageTitle(response.body);
        if (!pageTitle) {
            return BBPromise.resolve({});
        }

        return BBPromise.props({
            dbTitle: mwapi.getDbTitle(app, req, pageTitle)
        });
    }).then((res) => {
        if (!res.dbTitle) {
            return BBPromise.resolve({});
        }

        return {
            payload: {
                $merge: [ mUtil.getRbPageSummaryUrl(app.restbase_tpl, domain, res.dbTitle) ]
            },
            meta: { etag: createSha1(res.dbTitle) }
        };
    }).catch((err) => {
        if (aggregated) {
            return BBPromise.resolve({});
        }
        throw err;
    });
};

module.exports = {
    promise,
    testing: {
        supportedLangs,
        findPageTitle,
        isSupported
    }
};
<commit_msg>TFA: add scowiki<commit_after>/**
 * To retrieve TFA -- Today's featured article -- for a given date.
 */

'use strict';

const crypto = require('crypto');
const domino = require('domino');
const preq = require('preq');
const BBPromise = require('bluebird');

const api = require('../api-util');
const dateUtil = require('../dateUtil');
const mUtil = require('../mobile-util');
const mwapi = require('../mwapi');
const sUtil = require('../util');

const HTTPError = sUtil.HTTPError;

const supportedLangs = [ "bg", "cs", "de", "el", "en", "fa", "fr", "he", "hu", "ja", "la", "no",
    "sco", "ur", "vi" ];

const isSupported = (domain) => {
    return supportedLangs.indexOf(mUtil.getLanguageFromDomain(domain)) > -1
        && domain.endsWith('.wikipedia.org');
};

/**
 * Builds the request to get the Special:FeedItem provided by the FeaturedFeed extension.
 * @param {!String} domain the requested domain, e.g. 'de.wikipedia.org'
 * @param {!Date} date for which day the featured article is requested
 * @return {!Promise} a promise resolving as an JSON object containing the response
 */
const requestFeaturedFeedSpecialItem = (domain, date) => {
    let lang = mUtil.getLanguageFromDomain(domain);
    if (lang === 'no') {
        lang = 'nb';
    }
    const formattedDateString = `${dateUtil.formatYYYYMMDD(date)}000000`;
    const path = 'wiki/Special:FeedItem/featured';
    return preq.get({
        // useskin=apioutput is a way to get just the content without the header and footer
        uri: `https://${domain}/${path}/${formattedDateString}/${lang}?useskin=apioutput`,
        headers: {
            'accept': 'text/html; charset=UTF-8'
        }
    });
};

// -- functions dealing with responses:

const findPageTitle = (html) => {
    const document = domino.createDocument(html);
    const scope = document.querySelector('div#mw-content-text');
    if (!scope) {
        return;
    }

    const found = scope.querySelector('b > a, a > b');
    if (found) {
        if (found.tagName === 'A') { // b > a
            return found.getAttribute('title');
        } else { // a > b
            // the anchor is the parent node
            return found.parentNode.getAttribute('title');
        }
    }
};

const createSha1 = (input) => {
    const shasum = crypto.createHash('sha1');
    shasum.update(input);
    return shasum.digest('hex');
};

// -- main

const promise = (app, req) => {
    let pageTitle;
    const domain = req.params.domain;
    const aggregated = !!req.query.aggregated;

    if (!dateUtil.validate(dateUtil.hyphenDelimitedDateString(req))) {
        if (aggregated) {
            return BBPromise.resolve({});
        }
        dateUtil.throwDateError();
    }

    if (!isSupported(domain)) {
        if (aggregated) {
            return BBPromise.resolve({});
        } else {
            throw new HTTPError({
                status: 501,
                type: 'unsupported_language',
                title: 'Language not supported',
                detail: 'The language you have requested is not yet supported.'
            });
        }
    }

    const requestedDate = dateUtil.getRequestedDate(req);
    if (domain === 'de.wikipedia.org' && !(dateUtil.isWithinLast3Days(requestedDate))) {
        return BBPromise.resolve({});
    }

    return requestFeaturedFeedSpecialItem(domain, requestedDate)
    .then((response) => {
        api.checkResponseStatus(req, response);
        pageTitle = findPageTitle(response.body);
        if (!pageTitle) {
            return BBPromise.resolve({});
        }

        return BBPromise.props({
            dbTitle: mwapi.getDbTitle(app, req, pageTitle)
        });
    }).then((res) => {
        if (!res.dbTitle) {
            return BBPromise.resolve({});
        }

        return {
            payload: {
                $merge: [ mUtil.getRbPageSummaryUrl(app.restbase_tpl, domain, res.dbTitle) ]
            },
            meta: { etag: createSha1(res.dbTitle) }
        };
    }).catch((err) => {
        if (aggregated) {
            return BBPromise.resolve({});
        }
        throw err;
    });
};

module.exports = {
    promise,
    testing: {
        supportedLangs,
        findPageTitle,
        isSupported
    }
};
<|endoftext|>


<commit_before><commit_msg>Update format-helper.js<commit_after><|endoftext|>


<commit_before>'use strict'

const env = require('@codeshare/env')
env.assert([
  'NODE_ENV',
  'APP_NAME',
  'APP_VERSION'
])

const bunyan = require('bunyan')
const client = require('@google/cloud-errors').start({
  reportUncaughtExceptions: false,
  serviceContext: {
    service: process.env.APP_NAME,
    version: process.env.APP_VERSION
  }
})
const errToJSON = require('error-to-json')
const kp = require('keypather')()
const ReqContainer = require('@google/cloud-errors/lib/classes/request-information-container.js')
const through2 = require('through2')

const levelToName = {
  60: 'fatal',
  50: 'error',
  40: 'warn',
  30: 'info',
  20: 'debug',
  10: 'trace'
}

module.exports = {
  type: 'raw',
  level: bunyan.ERROR,
  stream: through2.obj(function (logObj, enc, cb) {
    if (logObj.err && !kp.get(logObj, 'err.data.dontReport')) {
      const ctx = logObj.ctx || {}
      const userId = kp.get(ctx, 'me.id')
      let reqContainer
      if (ctx.req) {
        reqContainer = new RequestContainer()
        reqContainer.setMethod(ctx.req.method)
        reqContainer.setUrl(ctx.req.url)
        reqContainer.setUserAgent(ctx.req.headers['user-agent'])
        reqContainer.setReferrer(ctx.req.headers['referrer'] || ctx.req.headers['referer'])
        if (ctx.req.socket && ctx.req.socket.remoteAddress) {
          reqContainer.setRemoteAddress(ctx.req.socket.remoteAddress)
        }
      } else if (ctx.spark && ctx.rootSpanData) {
        reqContainer = new RequestContainer()
        reqContainer.setMethod('GET')
        reqContainer.setUrl(kp.get(ctx.rootSpanData, 'spanData.name'))
        reqContainer.setRemoteAddress(ctx.spark.headers['x-forwarded-for'] || ctx.spark.remote)
      }
      if (reqContainer) {
        reqContainer.setStatusCode(logObj.err.statusCode || 500)
      }
      logObj.err.stack += '---\nlog.error: ' + logObj.msg
      console.log('REPORT', logObj.err, reqContainer, null, userId, !!cb)
      // client.report(logObj.err, reqContainer, logObj.msg, userId, cb)
      cb()
    } else {
      cb()
    }
  })
}
<commit_msg>fix gcloud errors<commit_after>'use strict'

const env = require('@codeshare/env')
env.assert([
  'NODE_ENV',
  'APP_NAME',
  'APP_VERSION'
])

const bunyan = require('bunyan')
const client = require('@google/cloud-errors').start({
  reportUncaughtExceptions: false,
  serviceContext: {
    service: process.env.APP_NAME,
    version: process.env.APP_VERSION
  }
})
const errToJSON = require('error-to-json')
const kp = require('keypather')()
const ReqContainer = require('@google/cloud-errors/lib/classes/request-information-container.js')
const through2 = require('through2')

const levelToName = {
  60: 'fatal',
  50: 'error',
  40: 'warn',
  30: 'info',
  20: 'debug',
  10: 'trace'
}

module.exports = {
  type: 'raw',
  level: bunyan.ERROR,
  stream: through2.obj(function (logObj, enc, cb) {
    if (logObj.err && !kp.get(logObj, 'err.data.dontReport')) {
      const ctx = logObj.ctx || {}
      const userId = kp.get(ctx, 'me.id')
      let reqContainer
      if (ctx.req) {
        reqContainer = new RequestContainer()
        reqContainer.setMethod(ctx.req.method)
        reqContainer.setUrl(ctx.req.url)
        reqContainer.setUserAgent(ctx.req.headers['user-agent'])
        reqContainer.setReferrer(ctx.req.headers['referrer'] || ctx.req.headers['referer'])
        if (ctx.req.socket && ctx.req.socket.remoteAddress) {
          reqContainer.setRemoteAddress(ctx.req.socket.remoteAddress)
        }
      } else if (ctx.spark && ctx.rootSpanData) {
        reqContainer = new RequestContainer()
        reqContainer.setMethod('GET')
        reqContainer.setUrl(kp.get(ctx.rootSpanData, 'spanData.name'))
        reqContainer.setRemoteAddress(ctx.spark.headers['x-forwarded-for'] || ctx.spark.remote)
      }
      if (reqContainer) {
        reqContainer.setStatusCode(logObj.err.statusCode || 500)
      }
      logObj.err.stack += '---\nlog.error: ' + logObj.msg
      client.report(logObj.err, reqContainer, null, userId, cb)
    } else {
      cb()
    }
  })
}
<|endoftext|>