/**
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';
/**
 * This provider is used when the first arg to any of the SSRF
 * sinks is a string(the url)
 *
 * We will parse the url and only check the protocol, host, path
 */

const logger = require('../../core/logger')('contrast:hooks:ssrf-url');
const { isString } = require('../../util/is-string');
const tracker = require('../../tracker');
const { URL } = require('url');
const TagRange = require('../models/tag-range');
const tagRangeUtil = require('../models/tag-range/util');
const QUERY_TAG = 'source-type:querystring';
const PATH_TAG = 'source-type:path';

module.exports = ({ common: { isVulnerable } }) => {
  const ssrfUrl = {};

  /**
   * Add a tag which marks the given url part with a given tag
   * (e.g. source-type:querystring)
   *
   * @param {Object} params
   * @param {TagRange[]} params.tags list of TagRanges for the given part
   * @param {[number]} params.start start index for the new TagRange
   * @param {string} params.stop stop of tag range from a field part (e.g. query, path, etc)
   * @param {string} params.name name of tag to add
   * @return {TagRange[]} params.list of TagRanges including the new tag
   */
  ssrfUrl.addTag = function ({ tags, start, stop, name }) {
    const tagRange = new TagRange(start, stop, name);

    return tagRangeUtil.add(tags, tagRange);
  };

  /**
   * Handler for string based SSRF sinks
   *
   * @param {Object} params
   * @param {string} params.input url
   * @param {Object} requiredTags for sink
   * @param {Object} disallowedTags for sink
   * @return {Boolean} if input is vulnerable or not
   */
  ssrfUrl.handle = function ({ input, requiredTags, disallowedTags }) {
    if (!isString(input)) {
      return false;
    }

    const contrastProps = tracker.getData(input);

    if (!contrastProps) {
      return false;
    }

    try {
      // we define a default base here so that `input` can be a relative path
      const url = new URL(input, 'abc://xyz');
      const qpIndex = url.search ? input.indexOf(url.search) : -1;
      const pathIndex = url.pathname ? input.indexOf(
        url.pathname,
        // start search after protocol + // so it doesn't match `/` as first in the http://
        // if our generated base is used, protocol will be `abc` and won't be present in the input
        url.protocol === 'abc:' ? 0 : url.protocol.length + 2
      ) : -1;

      if (pathIndex >= 0) {
        contrastProps.tagRanges = ssrfUrl.addTag({
          tags: contrastProps.tagRanges,
          start: pathIndex,
          stop: pathIndex + url.pathname.length - 1,
          name: PATH_TAG
        });
      }

      if (qpIndex >= 0) {
        contrastProps.tagRanges = ssrfUrl.addTag({
          tags: contrastProps.tagRanges,
          start: qpIndex,
          stop: qpIndex + url.search.length - 1,
          name: QUERY_TAG
        });
      }
    } catch (err) {
      logger.warn('Unable to parse url %s, err: %o', err);
    }

    return isVulnerable({ input, requiredTags, disallowedTags });
  };

  return ssrfUrl;
};
