<?php
/**********************************************************************
 ** Script Name: Functions.php                                        **
 ** Version:     1.3 - 21-jan-05                                      **
 ** Author:      Pat Fox                                              **
 ** Function:    Contains simple procedures to encode, encrypt,       **
 **              decode, decrypt and split the information POSTed     **
 **              to and from VSP Form.                                **
 **                                                                   **
 ** Revision History:                                                 **
 ** Version  Author     Date and notes                                **
 **    1.0   Mat Peck   18/01/2002 - First ASP release                **
 **    1.1   Mat Peck   07/03/2002 - Base64 routines patched          **
 **    1.2   Pat Fox    29/07/2002 - PHP version                      **
 **    1.2   Tony Welch 9/07/2003 - Addition of post code fields 2.21 **
 **    1.3   Peter G    21-jan-05 - Add new 2.22 response fields      **
 **    1.4   Georgi B   09/06/2014 - Backported AES encryption        **
 ***********************************************************************/
// ** Set variables to indentify the vendor **
//$VendorName="mozimoltd";
//$EncryptionPassword="qFTt44siFsriectE"; // Live
//$EncryptionPassword="eIF3TfIaZXWKKpEE"; // Simulator
//** Your server's IP address or dns name and web app directory.  Fully qualified **
//** Examples : $MyServer="https://www.newco.com/php-form-kit/", $MyServer="192.168.0.1/php-form-kit", $MyServer="http://localhost/php-form-kit/" **
$MyServer = SITE_URL;
//*********************************************************************************
// The protx site to send information to **
//** Simulator site **

// Sagepay Simulator no longer exists
/*
$vspsite = "https://test.sagepay.com/Simulator/VSPFormGateway.asp"; // Simulator
$VendorName = SAGEPAY_SIM_VENDOR; // Simulator
$EncryptionPassword = SAGEPAY_SIM_ENCRYPTION; // Simulator
*/
//** Test site **
if (SAGEPAY_TEST) {
    $vspsite = "https://test.sagepay.com/gateway/service/vspform-register.vsp";
    $VendorName = SAGEPAY_VENDOR; // Test
    $EncryptionPassword = SAGEPAY_TEST_ENCRYPTION; // STest
}
//** Live site - ONLY uncomment when going live **
if (SAGEPAY_LIVE) {
    $vspsite = "https://live.sagepay.com/gateway/service/vspform-register.vsp";
    $VendorName = SAGEPAY_VENDOR; // Live
    $EncryptionPassword = SAGEPAY_ENCRYPTION; // Live
}

if (!SAGEPAY_LIVE && !SAGEPAY_TEST) {
    throw new Exception("Please specify TEST or LIVE Sagepay in settings.");
}



//*********************************************************************************
// ** Base 64 Encoding function **
// PHP does it natively but just for consistency and ease of maintenance, let's declare our own function
function base64Encode($plain)
{
    // Initialise output variable
    $output = "";
    // Do encoding
    $output = base64_encode($plain);

    // Return the result
    return $output;
}

// ** Base 64 decoding function **
// PHP does it natively but jus`t for consistency and ease of maintenance, let's declare our own function
function base64Decode($scrambled)
{
    // Initialise output variable
    $output = "";
    // Fix plus to space conversion issue
    $scrambled = str_replace(" ", "+", $scrambled);
    // Do encoding
    $output = base64_decode($scrambled);

    // Return the result
    return $output;
}

/*  The SimpleXor encryption algorithm                                                                                **
**  NOTE: This is a placeholder really.  Future releases of VSP Form will use AES or TwoFish.  Proper encryption      **
**       This simple function and the Base64 will deter script kiddies and prevent the "View Source" type tampering    **
**      It won't stop a half decent hacker though, but the most they could do is change the amount field to something **
**      else, so provided the vendor checks the reports and compares amounts, there is no harm done.  It's still      **
**      more secure than the other PSPs who don't both encrypting their forms at all                                  */
function simpleXor($InString, $Key)
{
    // Initialise key array
    $KeyList = array();
    // Initialise out variable
    $output = "";
    // Convert $Key into array of ASCII values
    for ($i = 0; $i < strlen($Key); $i++) {
        $KeyList[$i] = ord(substr($Key, $i, 1));
    }
    // Step through string a character at a time
    for ($i = 0; $i < strlen($InString); $i++) {
        // Get ASCII code from string, get ASCII code from key (loop through with MOD), XOR the two, get the character from the result
        // % is MOD (modulus), ^ is XOR
        $output .= chr(ord(substr($InString, $i, 1)) ^ ($KeyList[$i % strlen($Key)]));
    }

    // Return the result
    return $output;
}

/* The getToken function.                                                                                         **
** NOTE: A function of convenience that extracts the value from the "name=value&amp;name2=value2..." VSP reply string **
**     Works even if one of the values is a URL containing the & or = signs.                                      */
function getToken($thisString)
{
    // List the possible tokens
    $Tokens = array(
        "Status",
        "StatusDetail",
        "VendorTxCode",
        "VPSTxId",
        "TxAuthNo",
        "Amount",
        "AVSCV2",
        "AddressResult",
        "PostCodeResult",
        "CV2Result",
        "GiftAid",
        "3DSecureStatus",
        "CAVV"
    );
    // Initialise arrays
    $output = array();
    $resultArray = array();
    // Get the next token in the sequence
    for ($i = count($Tokens) - 1; $i >= 0; $i--) {
        // Find the position in the string
        $start = strpos($thisString, $Tokens[$i]);
        // If it's present
        if ($start !== false) {
            // Record position and token name
            $resultArray[$i] = new stdClass();
            $resultArray[$i]->start = $start;
            $resultArray[$i]->token = $Tokens[$i];
        }
    }
    // Sort in order of position
    sort($resultArray);
    // Go through the result array, getting the token values
    for ($i = 0; $i < count($resultArray); $i++) {
        // Get the start point of the value
        $valueStart = $resultArray[$i]->start + strlen($resultArray[$i]->token) + 1;
        // Get the length of the value
        if ($i == (count($resultArray) - 1)) {
            $output[$resultArray[$i]->token] = substr($thisString, $valueStart);
        } else {
            $valueLength = $resultArray[$i + 1]->start - $resultArray[$i]->start - strlen($resultArray[$i]->token) - 2;
            $output[$resultArray[$i]->token] = substr($thisString, $valueStart, $valueLength);
        }
    }

    // Return the ouput array
    return $output;
}

// Randomise based on time
function randomise()
{
    list($usec, $sec) = explode(' ', microtime());

    return (float)$sec + ((float)$usec * 100000);
}

//** Wrapper function do encrypt an encode based on strEncryptionType setting **
function encryptAndEncode($strIn, $strEncryptionPassword, $strEncryptionType = 'AES')
{

    if ($strEncryptionType == "XOR") {
        //** XOR encryption with Base64 encoding **
        return base64Encode(simpleXor($strIn, $strEncryptionPassword));
    } else {
        //** AES encryption, CBC blocking with PKCS5 padding then HEX encoding - DEFAULT **

        //** use initialization vector (IV) set from $strEncryptionPassword
        $strIV = $strEncryptionPassword;

        //** add PKCS5 padding to the text to be encypted
        $strIn = addPKCS5Padding($strIn);

        //** perform encryption with PHP's MCRYPT module
        $strCrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $strEncryptionPassword, $strIn, MCRYPT_MODE_CBC, $strIV);

        //** perform hex encoding and return
        return "@" . bin2hex($strCrypt);
    }
}


//** Wrapper function do decode then decrypt based on header of the encrypted field **
function decodeAndDecrypt($strIn, $strEncryptionPassword)
{

    if (substr($strIn, 0, 1) == "@") {
        //** HEX decoding then AES decryption, CBC blocking with PKCS5 padding - DEFAULT **

        //** use initialization vector (IV) set from $strEncryptionPassword
        $strIV = $strEncryptionPassword;

        //** remove the first char which is @ to flag this is AES encrypted
        $strIn = substr($strIn, 1);

        //** HEX decoding
        $strIn = pack('H*', $strIn);

        //** perform decryption with PHP's MCRYPT module
        return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $strEncryptionPassword, $strIn, MCRYPT_MODE_CBC, $strIV);
    } else {
        //** Base 64 decoding plus XOR decryption **
        return simpleXor(base64Decode($strIn), $strEncryptionPassword);
    }
}


//** PHP's mcrypt does not have built in PKCS5 Padding, so we use this
function addPKCS5Padding($input)
{
    $blocksize = 16;
    $padding = "";

    // Pad input to an even block size boundary
    $padlength = $blocksize - (strlen($input) % $blocksize);
    for ($i = 1; $i <= $padlength; $i++) {
        $padding .= chr($padlength);
    }

    return $input . $padding;
}
