/*
 * Copyright (C) 1994-2021 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of both the OpenPBS software ("OpenPBS")
 * and the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * OpenPBS is free software. You can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * OpenPBS 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 Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Commercial License Information:
 *
 * PBS Pro is commercially licensed software that shares a common core with
 * the OpenPBS software.  For a copy of the commercial license terms and
 * conditions, go to: (http://www.pbspro.com/agreement.html) or contact the
 * Altair Legal Department.
 *
 * Altair's dual-license business model allows companies, individuals, and
 * organizations to create proprietary derivative works of OpenPBS and
 * distribute them - whether embedded or bundled with other software -
 * under a commercial license agreement.
 *
 * Use of Altair's trademarks, including but not limited to "PBS™",
 * "OpenPBS®", "PBS Professional®", and "PBS Pro™" and Altair's logos is
 * subject to Altair's trademark licensing policies.
 */

/**
 * @file	xml_encode_decode.c
 */
#include <pbs_config.h> /* the master config generated by configure */

#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <sys/types.h>

#include "pbs_ifl.h"
#include "log.h"

#ifndef _POSIX_ARG_MAX
#define _POSIX_ARG_MAX 4096 /* largest value standards guarantee */
#endif

#define LESS_THAN "&lt;"
#define GRT_THAN "&gt;"
#define DOUBLE_QUOTE "&quot;"
#define SINGLE_QUOTE "&apos;"
#define AMPERSAND "&amp;"
#define START_JSDL_ARG "<jsdl-hpcpa:Argument>"
#define END_JSDL_ARG "</jsdl-hpcpa:Argument>"
#define PBS_NUM_ESC_CHARS 256

/* removed global variables arg_max and escape_chars
 * into function scope, so that these functions become MT-safe
 */

/*
 * init_escapechars -- initializes escape_chars array
 * indices with corresponding escape string
 */

/**
 * @brief
 *	-Find max number of chars an argument can take.
 *
 * @param[out] escape_chars  - list of escape chars
 * @param[out] arg_max - max chars
 *
 * @return	Void
 *
 */
static void
init_escapechars_maxarg(char **escape_chars, long *arg_max)
{
	long sysconf_argmax;

	if (*arg_max == -1) {
#ifdef _SC_ARG_MAX
#define PBS_MAX_ARG_MAX (1 << 24)
		sysconf_argmax = sysconf(_SC_ARG_MAX);

		/*
		 * Constrain the size of arg_max returned.  The value returned
		 * by sysconf(_SC_ARG_MAX) may be enormous:  one system we've
		 * tested regularly returns 4611686018427387903, which it then
		 * (quite reasonably) refuses to let us malloc.  We arbitrarily
		 * constrain it to no more than PBS_MAX_ARG_MAX.
		 */
		if (sysconf_argmax <= 0)
			*arg_max = _POSIX_ARG_MAX;
		else if (sysconf_argmax > PBS_MAX_ARG_MAX)
			*arg_max = PBS_MAX_ARG_MAX;
		else
			*arg_max = sysconf_argmax;
#undef PBS_MAX_ARG_MAX
#else
		*arg_max = _POSIX_ARG_MAX;
#endif /* _SC_ARG_MAX */
	}

	/* initialize escape_chars to nulls, since its no longer static var */
	memset(escape_chars, 0, PBS_NUM_ESC_CHARS * sizeof(char *));
	/*
	 * Initialize ascii value escape char indices with
	 * corresponding escape string.
	 */
	escape_chars[(int) '<'] = LESS_THAN;
	escape_chars[(int) '>'] = GRT_THAN;
	escape_chars[(int) '"'] = DOUBLE_QUOTE;
	escape_chars[(int) '\''] = SINGLE_QUOTE;
	escape_chars[(int) '&'] = AMPERSAND;
}

/**
 * @brief
 *	takes plain/simple string as an input and return the number of
 *      characters in the resultant encoded string.
 *
 * @param[in]   original_arg - the input string to be encoded
 * @param[in/out]   encoded_arg - the encoding of 'orginal_arg'
 * @param[in]   escape_chars
 *              Following table lists escape chracters along with their
 *              replacement strings used by 'encode_argument' and
 *              'decode_argument' at the time of encoding/decoding.
 * -------------------------------------------------------
 *	Original Character	--	Replacement String
 *		<		--	'&lt;
 *		>		--	'&gt;
 *		&		--	&amp;
 *		'		--	&apos;
 *		"		--	&quot;
 * @return int
 * @retval >=0	Number of characters in the resultant encoded string
 *		connection made.
 */
static int
encode_argument(char *original_arg, char *encoded_arg,
		char **escape_chars)
{
	int j = 0;
	int i, k;
	int ind;

	for (i = 0; original_arg[i] != '\0'; i++) {

		if (((int) original_arg[i] >= 0) &&
		    ((int) original_arg[i] < PBS_NUM_ESC_CHARS) &&
		    (escape_chars[(int) original_arg[i]] != NULL)) {
			/* found an escape char */
			ind = (int) original_arg[i];
			/* Replace with corresponding escape string */
			for (k = 0; escape_chars[ind][k] != '\0'; k++) {
				encoded_arg[j] = escape_chars[ind][k];
				j++;
			}
		} else {
			/* not an escape char */
			encoded_arg[j] = original_arg[i];
			j++;
		}
	}
	encoded_arg[j] = '\0';
	return j;
}

/**
 * @brief
 *	This function is used to decode arguments from given
 *	xml(tags). It is invoked by other functions like
 *	decode_xml_arg_list( ) during xml parsing/decoding
 *
 * @param[in]   encoded_arg  - the token against which to parse
 * @param[in]   original_arg - populated with the decoded string
 *
 * @return int
 * @retval >= 0	length of original_arg
 * @retval -1	error encountered during parsing
 */
static int
decode_argument(char *encoded_arg, char *original_arg)
{
	int i = 0;
	int j = 0;
	int k = 0;
	char escape_chars[10];
#ifdef WIN32
	int quotes_flag = 0;
#endif

#ifdef WIN32
	/*
	 * This is to handle differences between M$ and non-M$ platforms
	 * related to 'Double quotes'(") character at the time of reading
	 * command-line argument(with white spaces) from user(through 'qsub').
	 * In case of non-M$ platforms, it removes the '"' charater and passes
	 * it to the PBS Server. In case PBS Scheduler decides to move job
	 * to M$ MOM, then the M$ MOM would receive an argument without '"'
	 * character and consider it as two different arguments. So, if any
	 * argument(having white spaces) recieved from the PBS Server doesn't
	 * start with '&quot;' string, then the character '"' is prepended
	 * and appended to the argument at the time of decoding XML argument.
	 */
	if ((strchr(encoded_arg, ' ')) &&
	    (strncmp(encoded_arg, DOUBLE_QUOTE,
		     sizeof(DOUBLE_QUOTE)))) {
		/* argument has white space without '&quot;" string */
		/* prefix this arugment with '"' character	*/
		quotes_flag = 1;
		original_arg[k++] = '"';
	}

#endif /* WIN32 */

	while (encoded_arg[i] != '\0') {
		if (encoded_arg[i] != '&')
			original_arg[k] = encoded_arg[i];
		else {
			j = 0;
			while (encoded_arg[i] != ';') {
				escape_chars[j] = encoded_arg[i];
				j++;
				i++;
			}
			escape_chars[j] = encoded_arg[i];
			escape_chars[j + 1] = '\0';
			if (strcmp(escape_chars, LESS_THAN) == 0)
				original_arg[k] = '<';
			else if (strcmp(escape_chars, GRT_THAN) == 0)
				original_arg[k] = '>';
			else if (strcmp(escape_chars, AMPERSAND) == 0)
				original_arg[k] = '&';
			else if (strcmp(escape_chars, DOUBLE_QUOTE) == 0)
				original_arg[k] = '"';
			else if (strcmp(escape_chars, SINGLE_QUOTE) == 0)
				original_arg[k] = '\'';
		}
		i++;
		k++;
	}

#ifdef WIN32
	if (quotes_flag) {
		/* suffix with '"' character */
		original_arg[k++] = '"';
	}
#endif /* WIN32 */

	original_arg[k] = '\0';
	return k;
}

/**
 * @brief
 *	encode_xml_arg_list takes current index, number of arguments
 *      passed to 'qsub' program and arguments as input and returns
 *      an encoded string(XML form) to 'qsub' program. It loops through each
 *      argument and converts that into an equivalent encoded string using
 *      ('encode_argument' function). For eg, if a1, a2 are the arguments,
 *      then it returns following encoded XML string to 'qsub' program.
 *      encoded string   --  <jsdl-hpcpa:Argument>a1</jsdl-hpcpa:Argument>
 *                           <jsdl-hpcpa:Argument>a2</jsdl-hpcpa:Argument>
 *
 * @param[in]   optind - current index
 * @param[in]   argc - number of arguments passed to 'qsub' program
 * @param[in]   argv - arguments as input
 *
 * @return char*
 * @retval An encoded string(XML form) to 'qsub' program in case of SUCCESS
 * @retval NULL in case of failure
 */
extern char *
encode_xml_arg_list(int optind, int argc, char **argv)
{
	char *xml_string = NULL;
	int cur_len = 1;
	int j;
	char *arg = NULL;
	int jsdl_tag_len;
	char *temp;
	int first = 1;
	char *escape_chars[PBS_NUM_ESC_CHARS];
	long arg_max = -1;

	jsdl_tag_len = sizeof(START_JSDL_ARG) + sizeof(END_JSDL_ARG) - 2;
	if (argc > 0 && argv == NULL)
		return NULL;
	/*
	 * Initialize escape chars array and max number of
	 * chars an argument can hold.
	 */

	init_escapechars_maxarg(escape_chars, &arg_max);

	/* Allocate memory to hold encoded argument */
	arg = malloc(arg_max * sizeof(char *));
	if (arg == NULL)
		return NULL;

	for (j = optind; j < argc; j++) {
		if (argv[j] == NULL) {
			if (xml_string)
				free(xml_string);
			break;
		}
		cur_len += strlen(argv[j]) + jsdl_tag_len;
		temp = realloc(xml_string, cur_len);
		if (temp == NULL) {
			if (xml_string)
				free(xml_string);
			free(arg);
			return NULL;
		} else
			xml_string = temp;
		if (first) {
			strcpy(xml_string, START_JSDL_ARG);
			first = 0;
		} else
			strcat(xml_string, START_JSDL_ARG);

		cur_len += encode_argument(argv[j], arg, escape_chars);
		temp = realloc(xml_string, cur_len);
		if (temp == NULL) {
			free(xml_string);
			free(arg);
			return NULL;
		} else
			xml_string = temp;

		strcat(xml_string, arg);
		strcat(xml_string, END_JSDL_ARG);
		arg[0] = '\0';
	}
	free(arg);
	return xml_string;
}

/**
 * @brief
 *	Takes 'executable' and 'argument_list'(encoded form) as an input and
 *      stores decoded arguments in address of argarray passed to this function.
 *      It breaks an encoding string into arguments, decodes each argument into
 *      plain string and assigns it to 'argarray'.
 * @param[in]   executable - the input for decoding
 * @param[in]   arg_list - list of arguments passed on command-line
 * @param[in]   shell - 'executable' stored into shell variable
 * @param[in]   argarray - stores the decoded arguments passed to this function
 *
 * @return int
 * @retval  0	indicates SUCCESS
 * @retval -1	indicates FAILURE
 */
extern int
decode_xml_arg_list(char *executable, char *arg_list,
		    char **shell, char ***argarray)
{
	char *argument_list = NULL;
	char *token = NULL;
	char *arg = NULL;
	char seps[] = "<>";
	char **argv = NULL;
	char **argv_temp = NULL;
	int no_of_arguments = 0;
	int arg_len = 0, i;
	char *escape_chars[PBS_NUM_ESC_CHARS];
	long arg_max = -1;
	char *saveptr; /* for use with strtok_r */
	/* Check for executable */
	if (executable == NULL)
		return -1;

	/* store executable into shell variable */
	*shell = executable;

	/*
	 * Initialize escape chars array and max number of
	 * chars an argument can hold.
	 */

	init_escapechars_maxarg(escape_chars, &arg_max);

	no_of_arguments++;
	argv = calloc(no_of_arguments + 1, sizeof(char *));
	if (argv == NULL) {
		return -1;
	}

	argv[0] = malloc(strlen(*shell) + 1);
	if (argv[0] == NULL) {
		free(argv);
		return -1;
	}
	strcpy(argv[0], *shell);

	/* only executable is passed in commend-line */
	if (arg_list == NULL) {
		argv[no_of_arguments] = 0;
		*argarray = argv;
		return 0;
	}

	/* Allocate memory to hold decoded argument */
	arg = malloc(strlen(arg_list) + 1);
	if (arg == NULL) {
		free(argv);
		return -1;
	}
	arg[0] = '\0';

	argument_list = strdup(arg_list);
	if (argument_list == NULL)
		goto error;

	token = strtok_r(argument_list, seps, &saveptr);
	while (token) {
		if (strstr(token, "jsdl-hpcpa:Argument") == NULL) {
			/*
			 * '<>' is used as delimiters, so the strtok might
			 * return '<jsdl-hpcpa:Argument>' as token, so consider
			 * only those contains which doesn't contain
			 * string 'jsdl-hpcpa:Argument'.
			 */
			no_of_arguments++;
			/* found an argument */
			argv_temp = realloc(argv,
					    (no_of_arguments + 1) * sizeof(char *));
			if (argv_temp == NULL)
				goto error;
			else
				argv = argv_temp;

			arg_len = decode_argument(token, arg);
			argv[no_of_arguments - 1] = (char *) malloc(arg_len + 1);
			if (argv[no_of_arguments - 1] == NULL)
				goto error;
			strcpy(argv[no_of_arguments - 1], arg);
			arg[0] = '\0';
		}
		token = strtok_r(NULL, seps, &saveptr);
	}
	argv[no_of_arguments] = 0;
	*argarray = argv;
	free(arg);
	free(argument_list);
	DBPRT(("%s: no of arguments: %d\n", __func__, no_of_arguments))
	return 0;
error:
	if (argv) {
		for (i = 0; i < no_of_arguments; i++) {
			if (argv[i])
				free(argv[i]);
		}
		free(argv);
	}
	if (arg)
		free(arg);
	if (argument_list)
		free(argument_list);
	return -1;
}

/**
 * @brief
 *	takes an encoded XML string as an input, and assigns decoded arguments
 *      to the 'argarray' variable.
 *
 * @param[in]   arg_list - encoded XML input string
 * @param[in]   argarray - decoded argumenta string to send as "extend" data.
 *
 * @return int
 * @retval  0	on SUCCESS
 * @retval -1	on FAILURE
 */

extern int
decode_xml_arg_list_str(char *arg_list,
			char **argarray)
{
	char *argument_list = NULL;
	char *token = NULL;
	char *arg;
	char seps[] = "<>";
	int cur_len = 0;
	char *argv;
	char *argv_temp;
	int first = 1;
	size_t arg_len = 0;
	char *escape_chars[PBS_NUM_ESC_CHARS];
	long arg_max = -1;
	char *saveptr; /* for use with strtok_r */

	/* Arguments are not specified */
	if (arg_list == NULL)
		return 0;

	/*
	 * Initialize escape chars array and max number of
	 * chars an argument can hold.
	 */

	init_escapechars_maxarg(escape_chars, &arg_max);

	/* Allocate memory to hold decoded argument */
	arg_len = strlen(arg_list) + 1;
	arg = malloc(arg_len);
	if (arg == NULL)
		return -1;
	arg[0] = '\0';

	argument_list = strdup(arg_list);
	if (argument_list == NULL) {
		free(arg);
		return -1;
	}

	/* Assign memory to hold argument list */
	argv = malloc(arg_len);
	if (argv == NULL) {
		free(arg);
		free(argument_list);
		return -1;
	}

	token = strtok_r(argument_list, seps, &saveptr);
	while (token) {
		if (strstr(token, "jsdl-hpcpa:Argument") == NULL) {
			/*
			 * '<>' is used as delimiters, so the strtok might
			 * return '<jsdl-hpcpa:Argument>' as token, so consider
			 * only those contains which doesn't contain
			 * string 'jsdl-hpcpa:Argument'.
			 */
			arg_len = decode_argument(token, arg);
			cur_len += arg_len + 1;
			if (first) {
				first = 0;
				strcpy(argv, arg);
			} else {
				strcat(argv, " ");
				strcat(argv, arg);
			}
		}
		token = strtok_r(NULL, seps, &saveptr);
		arg[0] = '\0';
	}
	argv_temp = realloc(argv, cur_len);
	if (argv_temp == NULL) {
		free(arg);
		free(argument_list);
		free(argv);
		return -1;
	} else
		argv = argv_temp;
	*argarray = argv;
	free(arg);
	free(argument_list);
	return 0;
}
