/*
   +----------------------------------------------------------------------+
   | PHP version 4.0                                                      |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997, 1998, 1999, 2000 The PHP Group                   |
   +----------------------------------------------------------------------+
   | This source file is subject to version 2.02 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available at through the world-wide-web at                           |
   | http://www.php.net/license/2_02.txt.                                 |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Authors: David Croft <david@infotrek.co.uk>,                         |
   |          John Donagher <john@webmeta.com>                            |
   +----------------------------------------------------------------------+
*/

/* $Id: pfpro.c,v 1.1.1.2 2001/01/25 04:59:53 wsanchez Exp $ */

#include "php.h"
#include "php_ini.h"
#include "php_pfpro.h"

#include "pfpro.h"

#if HAVE_PFPRO

#include "ext/standard/php_string.h"

#ifdef ZTS
int pfpro_globals_id;
#else
php_pfpro_globals pfpro_globals;
#endif

/* Function table */

function_entry pfpro_functions[] = {
	PHP_FE(pfpro_version, NULL)
	PHP_FE(pfpro_init, NULL)
	PHP_FE(pfpro_cleanup, NULL)
	PHP_FE(pfpro_process_raw, NULL)
	PHP_FE(pfpro_process, NULL)
	{NULL, NULL, NULL}
};

zend_module_entry pfpro_module_entry = {
	"pfpro",
	pfpro_functions,
	PHP_MINIT(pfpro),
	PHP_MSHUTDOWN(pfpro),
	PHP_RINIT(pfpro),					/* request start */
	PHP_RSHUTDOWN(pfpro),				/* request end */
	PHP_MINFO(pfpro),
	STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_PFPRO
ZEND_GET_MODULE(pfpro)
#endif


PHP_INI_BEGIN()
	STD_PHP_INI_ENTRY("pfpro.defaulthost",		"test.signio.com",	PHP_INI_ALL, OnUpdateString,	defaulthost,			php_pfpro_globals,	pfpro_globals)
	STD_PHP_INI_ENTRY("pfpro.defaultport",			"443",			PHP_INI_ALL, OnUpdateInt,		defaultport,			php_pfpro_globals,	pfpro_globals)
	STD_PHP_INI_ENTRY("pfpro.defaulttimeout",		"30",			PHP_INI_ALL, OnUpdateInt,		defaulttimeout,			php_pfpro_globals,	pfpro_globals)
	STD_PHP_INI_ENTRY("pfpro.proxyaddress",			"",				PHP_INI_ALL, OnUpdateString,	proxyaddress,			php_pfpro_globals,	pfpro_globals)
	STD_PHP_INI_ENTRY("pfpro.proxyport",			"",				PHP_INI_ALL, OnUpdateInt,		proxyport,			php_pfpro_globals,	pfpro_globals)
	STD_PHP_INI_ENTRY("pfpro.proxylogon",			"",				PHP_INI_ALL, OnUpdateString,	proxylogon,			php_pfpro_globals,	pfpro_globals)
	STD_PHP_INI_ENTRY("pfpro.proxypassword",		"",				PHP_INI_ALL, OnUpdateString,	proxypassword,			php_pfpro_globals,	pfpro_globals)
PHP_INI_END()


PHP_MINIT_FUNCTION(pfpro)
{
	REGISTER_INI_ENTRIES();
	return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(pfpro)
{
	UNREGISTER_INI_ENTRIES();
	return SUCCESS;
}

PHP_RINIT_FUNCTION(pfpro)
{
	PFPROLS_FETCH();

	PFPROG(initialised) = 0;

    return SUCCESS;
}

PHP_RSHUTDOWN_FUNCTION(pfpro)
{
	PFPROLS_FETCH();

	if (PFPROG(initialised) == 1) {
		PNCleanup();
	}

    return SUCCESS;
}


PHP_MINFO_FUNCTION(pfpro)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "Verisign Payflow Pro support", "enabled");
	php_info_print_table_row(2, "libpfpro version", PNVersion());
	php_info_print_table_end();

	DISPLAY_INI_ENTRIES();
}



/* {{{ proto string pfpro_version()
   Returns the version of the Payflow Pro library */
PHP_FUNCTION(pfpro_version)
{
	if (ZEND_NUM_ARGS() != 0) {
		WRONG_PARAM_COUNT;
	}

	RETURN_STRING(PNVersion(), 1);
}
/* }}} */



/* {{{ proto void pfpro_init()
   Initialises the Payflow Pro library */
PHP_FUNCTION(pfpro_init)
{
	PFPROLS_FETCH();

	if (ZEND_NUM_ARGS() != 0) {
		WRONG_PARAM_COUNT;
	}

	PNInit();

	PFPROG(initialised) = 1;

	RETURN_TRUE;
}
/* }}} */



/* {{{ proto void pfpro_cleanup()
   Shuts down the Payflow Pro library */
PHP_FUNCTION(pfpro_cleanup)
{
	PFPROLS_FETCH();

	if (ZEND_NUM_ARGS() != 0) {
		WRONG_PARAM_COUNT;
	}

	PNCleanup();

	PFPROG(initialised) = 0;

	RETURN_TRUE;
}
/* }}} */



/* {{{ proto string pfpro_process_raw(string parmlist [, string hostaddress [, int port, [, int timeout [, string proxyAddress [, int proxyPort [, string proxyLogon [, string proxyPassword]]]]]]])
   Raw Payflow Pro transaction processing */
PHP_FUNCTION(pfpro_process_raw)
{
	zval ***args;

	char *parmlist;
	char *address = NULL;
	int port = PFPROG(defaultport);
	int timeout = PFPROG(defaulttimeout);
	char *proxyAddress = PFPROG(proxyaddress);
	int proxyPort = PFPROG(proxyport);
	char *proxyLogon = PFPROG(proxylogon);
	char *proxyPassword = PFPROG(proxypassword);

	int freeaddress = 0;

	/* No, I don't like that Signio tell you to use a
	   fixed length buffer either */

	char response[512] = "";

	PFPROLS_FETCH();

	if (ZEND_NUM_ARGS() < 1 || ZEND_NUM_ARGS() > 8) {
		WRONG_PARAM_COUNT;
	}

	args = (zval ***) emalloc(sizeof(zval **) * ZEND_NUM_ARGS());

	if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args) == FAILURE) {
        php_error(E_WARNING, "Unable to read parameters in pfpro_process_raw()");
		efree(args);
		RETURN_FALSE;
	}

	switch (ZEND_NUM_ARGS()) {
		case 8:
			convert_to_string_ex(args[7]);
			proxyPassword = (*args[7])->value.str.val;
			/* fall through */

		case 7:
			convert_to_string_ex(args[6]);
			proxyLogon = (*args[6])->value.str.val;
			/* fall through */

		case 6:
			convert_to_long_ex(args[5]);
			proxyPort = (*args[5])->value.lval;
			/* fall through */

		case 5:
			convert_to_string_ex(args[4]);
			proxyAddress = (*args[4])->value.str.val;
			/* fall through */

		case 4:
			convert_to_long_ex(args[3]);
			timeout = (*args[3])->value.lval;
			/* fall through */

		case 3:
			convert_to_long_ex(args[2]);
			port = (*args[2])->value.lval;
			/* fall through */

		case 2:
			convert_to_string_ex(args[1]);
			address = (*args[1])->value.str.val;
	}

	convert_to_string_ex(args[0]);
	parmlist = (*args[0])->value.str.val;

	efree(args);

	/* Default to signio's test server */

	if (address == NULL) {
		address = estrdup(PFPROG(defaulthost));
		freeaddress = 1;
	}

#if 0
	printf("Address: >%s<\n", address);
	printf("Port: >%d<\n", port);
	printf("Parmlist: >%s<\n", parmlist);
	printf("Timeout: >%d<\n", timeout);
	printf("Proxy address: >%s<\n", proxyAddress);
	printf("Proxy port: >%d<\n", proxyPort);
	printf("Proxy logon: >%s<\n", proxyLogon);
	printf("Proxy password: >%s<\n", proxyPassword);
#endif

	/* Blank the response buffer */

	memset(response, 0, sizeof(response));

	/* Initialise the library if needed */

	if (PFPROG(initialised) == 0) {
		PNInit();
		PFPROG(initialised) = 1;
	}

	/* Perform the transaction */

	ProcessPNTransaction(address, port, proxyAddress, proxyPort, proxyLogon, proxyPassword, parmlist, strlen(parmlist), timeout, response);

	if (freeaddress) {
		efree(address);
	}

	RETURN_STRING(response, 1);
}
/* }}} */



/* {{{ proto array pfpro_process(array parmlist [, string hostaddress [, int port, [, int timeout [, string proxyAddress [, int proxyPort [, string proxyLogon [, string proxyPassword]]]]]]])
   Payflow Pro transaction processing using arrays */
PHP_FUNCTION(pfpro_process)
{
	zval ***args;

	HashTable *target_hash;
	ulong num_key;
	char *string_key;
	zval **entry;
	int pass;

	char *address = NULL;
	int port = PFPROG(defaultport);
	int timeout = PFPROG(defaulttimeout);
	char *proxyAddress = PFPROG(proxyaddress);
	int proxyPort = PFPROG(proxyport);
	char *proxyLogon = PFPROG(proxylogon);
	char *proxyPassword = PFPROG(proxypassword);

	int freeaddress = 0;

	char *parmlist = NULL;
	int parmlength = 0;

	/* No, I don't like that Signio tell you to use a
	   fixed length buffer either */

	char response[512] = "";

	char tmpbuf[128];
	char *rsppos, *valpos;

    char buf[128], sbuf[128];
    char *p1, *p2, *p_end,          /* Pointers for string manipulation */
        *sp1, *sp2, *sp_end,
        *pdelim1="&", *pdelim2="=";

	PFPROLS_FETCH();

	if (ZEND_NUM_ARGS() < 1 || ZEND_NUM_ARGS() > 8) {
		WRONG_PARAM_COUNT;
	}

	args = (zval ***) emalloc(sizeof(zval **) * ZEND_NUM_ARGS());

	if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), args) == FAILURE) {
        php_error(E_ERROR, "Unable to read parameters in pfpro_process()");
 		efree(args);
		RETURN_FALSE;
	}

	if ((*args[0])->type != IS_ARRAY) {
		php_error(E_ERROR, "First parameter to pfpro_process() must be an array");
 		efree(args);
		RETURN_FALSE;
	}

	switch (ZEND_NUM_ARGS()) {
		case 8:
			convert_to_string_ex(args[7]);
			proxyPassword = (*args[7])->value.str.val;
			/* fall through */

		case 7:
			convert_to_string_ex(args[6]);
			proxyLogon = (*args[6])->value.str.val;
			/* fall through */

		case 6:
			convert_to_long_ex(args[5]);
			proxyPort = (*args[5])->value.lval;
			/* fall through */

		case 5:
			convert_to_string_ex(args[4]);
			proxyAddress = (*args[4])->value.str.val;
			/* fall through */

		case 4:
			convert_to_long_ex(args[3]);
			timeout = (*args[3])->value.lval;
			/* fall through */

		case 3:
			convert_to_long_ex(args[2]);
			port = (*args[2])->value.lval;
			/* fall through */

		case 2:
			convert_to_string_ex(args[1]);
			address = (*args[1])->value.str.val;
	}

	/* Concatenate the passed array as specified by signio.
	   Basically it's all key=value&key=value, the only exception
	   being if the value contains = or &, in which case we also
	   encode the length, e.g. key[5]=bl&ah */

	target_hash = HASH_OF(*args[0]);

	for (pass = 0; pass <= 1; pass ++) {

		parmlength = 0;
		/* we go around the array twice. the first time to calculate
		   the string length, the second time to actually store it */

		zend_hash_internal_pointer_reset(target_hash);

		while (zend_hash_get_current_data(target_hash, (void **)&entry) == SUCCESS) {

			if (parmlength > 0) {
				if (pass == 1)
					strcpy(parmlist + parmlength, "&");
				parmlength += 1;
			}

			switch (zend_hash_get_current_key(target_hash, &string_key, &num_key)) {

				case HASH_KEY_IS_STRING:

					if (pass == 1)
						strcpy(parmlist + parmlength, string_key);
					parmlength += strlen(string_key);

					efree(string_key);

					break;

				case HASH_KEY_IS_LONG:

					sprintf(tmpbuf, "%d", num_key);
					if (pass == 1)
						strcpy(parmlist + parmlength, tmpbuf);
					parmlength += strlen(tmpbuf);

					break;

				default:
					php_error(E_ERROR, "pfpro_process() array keys must be strings or integers");
					efree(args);
					RETURN_FALSE;
			}


			switch ((*entry)->type) {
				case IS_STRING:
					if (strchr((*entry)->value.str.val, '&')
						|| strchr((*entry)->value.str.val, '=')) {
						sprintf(tmpbuf, "[%d]=", (*entry)->value.str.len);
						if (pass == 1)
							strcpy(parmlist + parmlength, tmpbuf);
						parmlength += strlen(tmpbuf);
					}
					else {
						if (pass == 1)
							strcpy(parmlist + parmlength, "=");
						parmlength += 1;
					}

					if (pass == 1)
						strcpy(parmlist + parmlength, (*entry)->value.str.val);
					parmlength += (*entry)->value.str.len;

					break;

				case IS_LONG:
					sprintf(tmpbuf, "=%d", (*entry)->value.lval);
					if (pass == 1)
						strcpy(parmlist + parmlength, tmpbuf);
					parmlength += strlen(tmpbuf);

					break;

				case IS_DOUBLE:
					sprintf(tmpbuf, "=%.2f", (*entry)->value.dval);
					if (pass == 1)
						strcpy(parmlist + parmlength, tmpbuf);
					parmlength += strlen(tmpbuf);

					break;

				default:
					php_error(E_ERROR, "pfpro_process() array values must be strings, ints or floats");
					efree(args);
					RETURN_FALSE;
			}
			zend_hash_move_forward(target_hash);
		}

		if (pass == 0) {
			parmlist = emalloc(parmlength + 1);
		}
	}

	efree(args);

	/* Default to signio's test server */

	if (address == NULL) {
		/* is it safe to just do address = "test.signio.com"; here? */
		address = estrdup(PFPROG(defaulthost));
		freeaddress = 1;
	}

#if 0
	printf("Address: >%s<\n", address);
	printf("Port: >%d<\n", port);
	printf("Parmlist: >%s<\n", parmlist);
	printf("Timeout: >%d<\n", timeout);
	printf("Proxy address: >%s<\n", proxyAddress);
	printf("Proxy port: >%d<\n", proxyPort);
	printf("Proxy logon: >%s<\n", proxyLogon);
	printf("Proxy password: >%s<\n", proxyPassword);
#endif

	/* Allocate the array for the response now - so we catch any errors
	   from this BEFORE we knock it off to the bank */

	if (array_init(return_value) == FAILURE) {
		php_error(E_ERROR, "pfpro_process() unable to create array");
		RETURN_FALSE;
	}

	/* Blank the response buffer */

	memset(response, 0, sizeof(response));

	/* Initialise the library if needed */

	if (PFPROG(initialised) == 0) {
		PNInit();
		PFPROG(initialised) = 1;
	}

	/* Perform the transaction */

	ProcessPNTransaction(address, port, proxyAddress, proxyPort, proxyLogon, proxyPassword, parmlist, parmlength, timeout, response);

	if (freeaddress) {
		efree(address);
	}


#if 0
	/* Decode the response back into a PHP array */

	rsppos = strtok(response, "&");

	do {
		valpos = strchr(rsppos, '=');
		if (valpos) {
			strncpy(tmpbuf, rsppos, valpos - rsppos);
			tmpbuf[valpos - rsppos] = 0;
			add_assoc_string(return_value, tmpbuf, valpos + 1, 1);
		}

	} while (rsppos = strtok(NULL, "&"));
#else


	/* This final chunk of code is to walk the string returned by Signio
	   and build a string array to return to the user */

	/* John, I suspect this code will fall over if there are less than
	   3 items in the response string -- david */

	/* Clean our str[n]cpy buffers */
	memset(buf, 0, sizeof(buf));
	memset(sbuf, 0, sizeof(sbuf));

	p_end = response + strlen(response);
	p1 = response;
	p2 = (char*)php_memnstr(response, pdelim1, 1, p_end);

	sp1 = (char*)php_memnstr(response, pdelim2, 1, p2);
	strncpy(buf, p1, sp1-p1);

	sp1++;
	strncpy(sbuf, sp1, p2-sp1);

	add_assoc_string(return_value, &buf[0], &sbuf[0], 1);

	do {
		memset(buf, 0, sizeof(buf));
		memset(sbuf, 0, sizeof(sbuf));

		p1 = p2+1;

		if ((sp2 = (char*)php_memnstr(p1, pdelim1, 1, p_end)) != NULL) {
			sp1 = (char*)php_memnstr(p1, pdelim2, 1, sp2);
			strncpy(buf, p1, sp1-p1);

			sp1++;

			strncpy(sbuf, sp1, sp2-sp1);

			add_assoc_string(return_value, &buf[0], &sbuf[0], 1);
		}


	} while ((p2 = (char*)php_memnstr(p1, pdelim1, 1, p_end)) != NULL);

	if (p1 <= p_end) {
		memset(buf, 0, sizeof(buf));
		memset(sbuf, 0, sizeof(sbuf));

		sp1 = (char*)php_memnstr(p1, pdelim2, 1, p_end);
		strncpy(buf, p1, sp1-p1);

		sp1++;
		strncpy(sbuf, sp1, p_end-sp1);

		add_assoc_string(return_value, &buf[0], &sbuf[0], 1);
	}

#endif

}
/* }}} */

#endif	/* HAVE_PFPRO */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 */
