Source: scan/Scan.js

const Util = require('../utility/Util');
const Host = require('../host/Host');

/**
  * Processes and stores the values and meta data of a given scan log structure
  * @param {Object} options - Error, warning and debug function references
  * @param {Object} scanStruct - JS structure to process
  */
class Scan {
  constructor(options, scanStruct) {
    /**
      * Merge default options with given options
      * @type {Object}
      */
    this.options = Util.mergeDefault({
      onError: () => {}, // @type {Function}
      onWarning: () => {}, // @type {Function}
      onDebug: () => {}, // @type {Function}
    }, options);

    /**
      * Simple structure validation, an `nmaprun` node in the passed structure
      * is required to continue
      */
    if (typeof scanStruct.nmaprun === 'undefined') {
      this.options.onError(`${this.constructor.name} Class Error:
        Invalid scan log structure, missing property "nmaprun"`);
    }

    /**
      * Scan type meta default
      * @type {String}
      */
    this.scanner = '';

    /**
      * Scan args meta default
      * @type {String}
      */
    this.args = '';

    /**
      * Scan start time unix meta default
      * @type {Number}
      */
    this.start = 0;

    /**
      * Scan start time plain meta default
      * @type {String}
      */
    this.startstr = '';

    /**
      * Scanner version meta default
      * @type {Number}
      */
    this.version = 0;

    /**
      * Scanner output xml version meta default
      * @type {String}
      */
    this.xmloutputversion = 0;

    /**
      * Scan type meta default
      * @type {String}
      */
    this.type = '';

    /**
      * Scan protocol meta default
      * @type {String}
      */
    this.protocol = '';

    /**
      * Scan service count meta default
      * @type {Number}
      */
    this.numservices = 0;

    /**
      * Scan ports meta default
      * @type {String}
      */
    this.services = '';

    /**
      * Scan verbosity level meta default
      * @type {String}
      */
    this.verbosityLevel = 0;

    /**
      * Scan debugging level meta default
      * @type {String}
      */
    this.debuggingLevel = 0;

    /**
      * Scanned hosts object
      * @type {Array}
      */
    this.hosts = [];

    /**
      * Scan stats stats finished time meta default
      * @type {Number}
      */
    this.time = 0;

    /**
      * Scan stats stats finished time as string meta default
      * @type {String}
      */
    this.timestr = '';

    /**
      * Scan stats elapsed time meta default
      * @type {Number}
      */
    this.elapsed = 0;

    /**
      * Scan stats summary meta default
      * @type {String}
      */
    this.summary = 'Possible interrupted scan';

    /**
      * Scan stats exit info meta default
      * @type {String}
      */
    this.exit = 'failure';

    /**
      * Scan stats host count default
      * @type {Number}
      */
    this.up = 0;

    /**
      * Scan stats host count default
      * @type {Number}
      */
    this.down = 0;

    /**
      * Scan stats host count default
      * @type {Number}
      */
    this.total = 0;

    // Attempt parsing or fail with error
    try {
      this.parseStruct(scanStruct.nmaprun || {});
    } catch (err) {
      this.options.onError(`${this.constructor.name} Class Error:
        Error parsing scan log: ${err}`);
    }
  }

  /**
    * Recursive parsing function, sets the value of this classes properties (if
    * it exists), or calls the proper processing function, or builds required
    * child classes for further processing
    * @param {Object} curStruct - Current node being processed
    */
  parseStruct(curStruct) {
    const props = Object.keys(curStruct);

    for (let i = 0, j = props.length; i < j; i += 1) {
      if (typeof this[props[i]] === 'undefined') {
        this.options.onDebug(`${this.constructor.name} Class:
          Unknown property in structure: ${props[i]}`);
      } else {
        this[props[i]] = curStruct[props[i]];
      }
    }
  }

  /**
    * Provides default `attrib` property to ensure node processing, always returns
    * current meta values
    * @type {Object}
    * @readonly
    */
  get attrib() {
    return this.meta;
  }

  /**
    * Continues node walking recursion through the current attrib node
    * @param {Object} meta - Current `attrib` node
    */
  set attrib(meta) {
    this.parseStruct(meta);
  }

  /**
    * Returns all current scan meta values
    * @type {Object}
    * @readonly
    */
  get meta() {
    return {
      scanner: this.scanner, // @type {String}
      args: this.args, // @type {String}
      start: this.start, // @type {Number}
      startstr: this.startstr, // @type {String}
      version: this.version, // @type {Number}
      xmloutputversion: this.xmloutputversion, // @type {Number}
      verbosity: this.verbose, // @type {Number}
      debugging: this.debugging, // @type {Number}
    };
  }

  /**
    * Returns all current scan info meta values
    * @type {Object}
    * @readonly
    */
  get scaninfo() {
    return {
      type: this.type, // @type {String}
      protocol: this.protocol, // @type {String}
      numservices: this.numservices, // @type {Number}
      services: this.services, // @type {String}
    };
  }

  /**
    * Continues node walking recursion through the current scaninfo node
    * @param {Object} info - Current `scaninfo` node
    */
  set scaninfo(info) {
    this.parseStruct(info);
  }

  /**
    * Returns all processed data, including child data, of the this scan
    * @type {Object}
    * @readonly
    */
  get data() {
    return {
      meta: this.meta, // @type {Object}
      scaninfo: this.scaninfo, // @type {Object}
      hosts: this.hosts.map((h) => h.data || {}), // @type {Array}
      runstats: this.runstats, // @type {Object}
    };
  }

  /**
    * Provides default `verbose` property to ensure node processing, will return
    * default or newly set value
    * @type {Number}
    * @readonly
    */
  get verbose() {
    return this.verbosityLevel;
  }

  /**
    * Applies the new value for `verbosityLevel`
    * @param {Number} verbosityLevel - Parsed verbosity level
    */
  set verbose(verbosityLevel) {
    this.verbosityLevel = verbosityLevel.attrib.level;
  }

  /**
    * Provides default `debugging` property to ensure node processing, will return
    * default or newly set value
    * @type {Number}
    * @readonly
    */
  get debugging() {
    return this.debuggingLevel;
  }

  /**
    * Applies the new value for `debugging`
    * @param {Number} debuggingLevel - Parsed debugging level
    */
  set debugging(debuggingLevel) {
    this.debuggingLevel = debuggingLevel.attrib.level;
  }

  /**
    * Provides default `host` property to ensure node processing, will return
    * default or newly set value
    * @type {Array}
    * @readonly
    */
  get host() {
    return this.hosts;
  }

  /**
    * Loops through the provided hosts array node and creates Host children for
    * further processing
    * @param {Array} hostStruct - Host array node
    */
  set host(hostStruct) {
    if (Array.isArray(hostStruct)) {
      for (let i = 0, j = hostStruct.length; i < j; i += 1) {
        this.hosts.push(new Host(this.options, hostStruct[i]));
      }
    } else {
      this.hosts.push(new Host(this.options, hostStruct));
    }
  }

  /**
    * Returns all current scan run stats meta values
    * @type {Object}
    * @readonly
    */
  get runstats() {
    return {
      time: this.time, // @type {Number}
      timestr: this.timestr, // @type {String}
      elapsed: this.elapsed, // @type {Number}
      summary: this.summary, // @type {String}
      exit: this.exit, // @type {String}
      up: this.up, // @type {Number}
      down: this.down, // @type {Number}
      total: this.total, // @type {Number}
    };
  }

  /**
    * Continues node walking recursion through the current runstats node
    * @param {Object} stats - Current `runstats` node
    */
  set runstats(stats) {
    if (stats.finished) {
      this.parseStruct(stats.finished);
    }

    if (stats.hosts) {
      this.parseStruct(stats.hosts);
    }
  }
}

module.exports = Scan;