// SonarQube Scan Action
// Copyright (C) SonarSource Sàrl
// mailto:contact AT sonarsource DOT com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import * as core from "@actions/core";
import * as exec from "@actions/exec";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { parseArgsStringToArgv } from "string-argv";

const KEYTOOL_MAIN_CLASS = "sun.security.tools.keytool.Main";
const TRUSTSTORE_PASSWORD = "changeit"; // default password of the Java truststore!

export async function runSonarScanner(
  inputArgs,
  projectBaseDir,
  scannerDir,
  runnerEnv = {}
) {
  const { runnerDebug, runnerOs, runnerTemp, sonarRootCert, sonarcloudUrl } =
    runnerEnv;

  const scannerBin =
    runnerOs === "Windows" ? "sonar-scanner.bat" : "sonar-scanner";

  const scannerArgs = [];

  /**
   * Not sanitization is needed when populating scannerArgs.
   * @actions/exec will take care of sanitizing the args it receives.
   */

  if (sonarcloudUrl) {
    scannerArgs.push(`-Dsonar.scanner.sonarcloudUrl=${sonarcloudUrl}`);
  }

  if (runnerDebug === "1") {
    scannerArgs.push("--debug");
  }

  if (projectBaseDir) {
    scannerArgs.push(`-Dsonar.projectBaseDir=${projectBaseDir}`);
  }

  // The SSL folder may exist on an uncleaned self-hosted runner
  const sslFolder = path.join(os.homedir(), ".sonar", "ssl");
  const truststoreFile = path.join(sslFolder, "truststore.p12");

  const keytoolParams = {
    scannerDir,
    truststoreFile,
  };

  if (fs.existsSync(truststoreFile)) {
    let aliasSonarIsPresent = true;

    try {
      await checkSonarAliasInTruststore(keytoolParams);
    } catch (_) {
      aliasSonarIsPresent = false;
      core.info(
        `Existing Scanner truststore ${truststoreFile} does not contain 'sonar' alias`
      );
    }

    if (aliasSonarIsPresent) {
      core.info(
        `Removing 'sonar' alias from already existing Scanner truststore: ${truststoreFile}`
      );
      await deleteSonarAliasFromTruststore(keytoolParams);
    }
  }

  if (sonarRootCert) {
    core.info("Adding SSL certificate to the Scanner truststore");
    const tempCertPath = path.join(runnerTemp, "tmpcert.pem");

    try {
      fs.unlinkSync(tempCertPath);
    } catch (_) {
      // File doesn't exist, ignore
    }

    fs.writeFileSync(tempCertPath, sonarRootCert);
    fs.mkdirSync(sslFolder, { recursive: true });

    await importCertificateToTruststore(keytoolParams, tempCertPath);

    scannerArgs.push(
      `-Dsonar.scanner.truststorePassword=${TRUSTSTORE_PASSWORD}`
    );
  }

  if (inputArgs) {
    /**
     * No sanitization, but it is parsing a string into an array of arguments in a safe way (= no command execution),
     * and with good enough support of quotes to support arguments containing spaces.
     */
    const args = parseArgsStringToArgv(inputArgs);
    scannerArgs.push(...args);
  }

  /**
   * Arguments are sanitized by `exec`
   */
  await exec.exec(scannerBin, scannerArgs);
}

/**
 * Use keytool for now, as SonarQube 10.6 and below doesn't support openssl generated keystores
 * keytool requires a password > 6 characters,  so we won't use the default password 'sonar'
 */
function executeKeytoolCommand({
  scannerDir,
  truststoreFile,
  extraArgs,
  options = {},
}) {
  const baseArgs = [
    KEYTOOL_MAIN_CLASS,
    "-storetype",
    "PKCS12",
    "-keystore",
    truststoreFile,
    "-storepass",
    TRUSTSTORE_PASSWORD,
    "-noprompt",
    "-trustcacerts",
    ...extraArgs,
  ];

  return exec.exec(`${scannerDir}/jre/bin/java`, baseArgs, options);
}

function importCertificateToTruststore(keytoolParams, certPath) {
  return executeKeytoolCommand({
    ...keytoolParams,
    extraArgs: ["-importcert", "-alias", "sonar", "-file", certPath],
  });
}

function checkSonarAliasInTruststore(keytoolParams) {
  return executeKeytoolCommand({
    ...keytoolParams,
    extraArgs: ["-list", "-v", "-alias", "sonar"],
    options: { silent: true },
  });
}

function deleteSonarAliasFromTruststore(keytoolParams) {
  return executeKeytoolCommand({
    ...keytoolParams,
    extraArgs: ["-delete", "-alias", "sonar"],
  });
}
