/**
Copyright: 2024 Contrast Security, Inc
  Contact: support@contrastsecurity.com
  License: Commercial

  NOTICE: This Software and the patented inventions embodied within may only be
  used as part of Contrast Security’s commercial offerings. Even though it is
  made available through public repositories, use of this Software is subject to
  the applicable End User Licensing Agreement found at
  https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
  between Contrast Security and the End User. The Software may not be reverse
  engineered, modified, repackaged, sold, redistributed or otherwise used in a
  way not consistent with the End User License Agreement.
*/
'use strict';

const { CustomSearcher } = require('../../signature');
const common = require('../../../common');
const Builder = require('../../../attack-patterns');

const { SCORES } = common;
const TERMS = {
  CONCAT_OPERATOR: '+',
  STATEMENT_TERMINATORS: ['\r', '\n', ';'],
  DETECTION_FUNCTIONS: ['alert', 'confirm', 'prompt', 'eval']
};

const identifierSearch = Builder()
  .JS()
  .identifier()
  .blockOrInlineComments()
  .anyOf(['(`.*`', '\\(.*\\))'], true)
  .build();
//    =>
//    (?:[a-zA-Z$_][0-9a-zA-Z$_]*)(?:\s*)(?:\/\*.*\*\/)?(?:\s*)(`.*`|\(.*\))

const concatSearch = Builder({ flags: 'gm' })
  .JS()
  .concatenation()
  .build();

/**
 * Search for redefined function execution. This was meant to compensate
 * the vector in this vulnerability:
 *   const a = ${untrusted};
 * With the vectors:
 *   alert;a(1)
 *      or
 *   alert\na(1)
 */
class RedefinedFunctionSearcher extends CustomSearcher {
  constructor() {
    super({
      description: 'Searches for redefined function execution.',
      id: 'CS-REDEF-1',
      name: 'Redefined function searcher'
    });
  }

  getImpact(userInput) {
    let impact;
    let stringCopy;
    const { value } = userInput;

    // Prepare input string for examination
    0 <= value.indexOf(TERMS.CONCAT_OPERATOR)
      ? (stringCopy = value.replace(concatSearch, ''))
      : (stringCopy = value);

    // Look for function name, then a terminator, then a pattern match
    TERMS.DETECTION_FUNCTIONS.some(function(fn) {
      const fnIndex = stringCopy.indexOf(fn);
      if (fnIndex < 0) {
        return false;
      }

      const fnNextIndex = fnIndex + fn.length;

      // Bail if match is at end of string or next char is not relevant
      if (
        stringCopy.length < fnNextIndex ||
        /[a-zA-Z]/.test(stringCopy[fnNextIndex])
      ) {
        return false;
      }

      // Hereafter we look only at substring after function name
      const stringAfterFn = stringCopy.substring(fnNextIndex);

      // Set score and exit if there's a pattern match after terminator
      return TERMS.STATEMENT_TERMINATORS.some(function(terminator) {
        const terminatorIndex = stringAfterFn.indexOf(terminator);
        if (terminatorIndex < 0) {
          return false;
        }

        // Terminator found - score based on pattern match
        if (0 <= stringAfterFn.search(identifierSearch, terminatorIndex)) {
          impact = SCORES.HIGH;
        } else {
          impact = SCORES.LOW;
        }
        return true;
      });
    });

    return impact || SCORES.NONE;
  }
}

module.exports = new RedefinedFunctionSearcher();
