/*
 *   $Id: TestSc14n.c $
 *   Last updated:
 *   $Date: 2019-12-13 21:07 $
 *   $Version: 2.1.0 $
 */
/* Some tests using the Sc14n C/C++ interface.
 * Please report any bugs to <http://cryptosys.net/contact/>
 */
/******************************* LICENSE ***********************************
* Copyright (C) 2017-19 David Ireland, DI Management Services Pty Limited.
* All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>
* The code in this module is licensed under the terms of the MIT license,
* unless otherwise marked.
* For a copy, see <http://opensource.org/licenses/MIT>
****************************************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "diSc14n.h"

/* 
 * Requires Sc14n to be installed on your system, available from <https://www.cryptosys.net/sc14n/>.
 * In particular that `diSc14n.dll` is in your library path.
 * You must link to `diSc14n.lib`. In MSVC++ IDE, use
 * Project > Properties > Input > Additional Dependencies and add the full path to `diSc14n.lib`.
 * E.g.
 * Additional Dependencies = $(OutDir)diSc14n.lib;%(AdditionalDependencies)
 * Using command-line:
 *     CL TestSc14n.c /link ..\Release\diSc14n.lib
 *
 * Test files, e.g. `olamundo.xml`, are in `sc14n-testfiles.zip`.
 */

#ifdef NDEBUG
/* Make sure assertion testing is turned on */
#undef NDEBUG
#endif
#include <assert.h>


/* Declaration for 3rd-party function with code below */
char *replace_str(const char *str, const char *old, const char *news);

/* File utility */
static char *read_file_to_string(const char *fname) {
	static char *buf;
	FILE *fp;
	long flen;
	fp = fopen(fname, "rb");
	if (!fp) return NULL;
	fseek(fp, 0, SEEK_END);
	flen = ftell(fp);
	if (flen < 0) return NULL;
	buf = calloc(1, flen + 1);
	rewind(fp);
	fread(buf, 1, flen, fp);
	fclose(fp);
	// User to free memory allocation
	return buf;
}


/* DO THE BUSINESS... */
int main(void)
{
	long r, n, ver, nchars;
	char *fname, *oname;
	char *cp, *news, *fdata;
	size_t filesize;
	char buf[256];
	char digval[SC14N_MAX_DIGEST_CHARS + 1];
	char digval1[SC14N_MAX_DIGEST_CHARS + 1];
	char *digok;

	/* General information about the core library DLL */
	ver = SC14N_Gen_Version();
	printf("DLL Version=%ld\n", ver);
	printf("LicenceType=%c\n", SC14N_Gen_LicenceType());
	nchars = SC14N_Gen_ModuleName(NULL, 0, 0);
	if (nchars > 0) {
		cp = malloc(nchars + 1);
		nchars = SC14N_Gen_ModuleName(cp, nchars, 0);
		printf("Module=%s\n", cp);
		free(cp);
	}
	nchars = SC14N_Gen_CompileTime(NULL, 0);
	if (nchars > 0) {
		cp = malloc(nchars + 1);
		nchars = SC14N_Gen_CompileTime(cp, nchars);
		printf("Compiled=%s\n", cp);
		free(cp);
	}
	assert(nchars > 0);
	nchars = SC14N_Gen_Platform(buf, sizeof(buf) - 1);
	printf("Platform=%s\n", buf);

	// DO TESTS ON INPUT.XML (these are from the manual)
	printf("FILE: %s\n", "input.xml");

	// Example 1. Excludes the first element with the tag name <Signature>
	r = C14N_File2File("c14nfile1.txt", "input.xml", "Signature", "", SC14N_TRAN_OMITBYTAG);
	assert(0 == r);

	// Example 2. Finds and transforms the first element with the tag name <SignedInfo>
	r = C14N_File2File("c14nfile2.txt", "input.xml", "SignedInfo", "", SC14N_TRAN_SUBSETBYTAG);
	assert(0 == r);

	// Example 3. Finds and transforms the third element with the tag name <Data>
	r = C14N_File2File("c14nfile3.txt", "input.xml", "Data[3]", "", SC14N_TRAN_SUBSETBYTAG);
	assert(0 == r);

	// Example 4. Finds and transforms the element with attribute Id="foo"
	r = C14N_File2File("c14nfile4.txt", "input.xml", "foo", "", SC14N_TRAN_SUBSETBYID);
	assert(0 == r);

	// Example 5. Finds and transforms the element with attribute ID="bar"
	r = C14N_File2File("c14nfile5.txt", "input.xml", "ID=bar", "", SC14N_TRAN_SUBSETBYID);
	assert(0 == r);

	// Example 6. Excludes element with attribute Id="thesig"
	r = C14N_File2File("c14nfile6.txt", "input.xml", "thesig", "", SC14N_TRAN_OMITBYID);
	assert(0 == r);

	// REDO LAST TESTS BUT COMPUTE DIGEST INSTEAD OF CREATING NEW FILE

	// Example 1. Excludes the first element with the tag name <Signature>
	r = C14N_File2Digest(digval, sizeof(digval) - 1, "input.xml", "Signature", "", SC14N_TRAN_OMITBYTAG);
	printf("DIG1=%s\n", digval);
	assert(strlen(digval) > 0);

	// Example 2. Finds and transforms the first element with the tag name <SignedInfo>
	r = C14N_File2Digest(digval, sizeof(digval) - 1, "input.xml", "SignedInfo", "", SC14N_TRAN_SUBSETBYTAG);
	printf("DIG2=%s\n", digval);
	assert(strlen(digval) > 0);

	// Example 3. Finds and transforms the third element with the tag name <Data>
	r = C14N_File2Digest(digval, sizeof(digval) - 1, "input.xml", "Data[3]", "", SC14N_TRAN_SUBSETBYTAG);
	printf("DIG3=%s\n", digval);
	assert(strlen(digval) > 0);

	// Example 4. Finds and transforms the element with attribute Id="foo"
	r = C14N_File2Digest(digval, sizeof(digval) - 1, "input.xml", "foo", "", SC14N_TRAN_SUBSETBYID);
	printf("DIG4=%s\n", digval);
	assert(strlen(digval) > 0);

	// Example 5. Finds and transforms the element with attribute ID="bar"
	r = C14N_File2Digest(digval, sizeof(digval) - 1, "input.xml", "ID=bar", "", SC14N_TRAN_SUBSETBYID);
	printf("DIG5=%s\n", digval);
	assert(strlen(digval) > 0);

	// Example 6. Excludes element with attribute Id="thesig"
	r = C14N_File2Digest(digval, sizeof(digval) - 1, "input.xml", "thesig", "", SC14N_TRAN_OMITBYID);
	printf("DIG6=%s\n", digval);
	assert(strlen(digval) > 0);

	printf("NOTE: Should have DIG1==DIG6 and DIG3==DIG4 above.\n");

	// Transform entire file
	fname = "olamundo.xml";
	oname = "olamundo-out.xml";
	printf("FILE: %s\n", fname);
	n = C14N_File2File(oname, fname, "", "", 0);
	printf("C14N_File2File()->%s returns %ld (expected 0 => OK)\n", oname, n);
	assert(0 == n);

	// Compute digest of entire file
	r = C14N_File2Digest(digval, sizeof(digval) - 1, fname, "", "", 0);
	printf("C14N_File2Digest(%s)=\t%s\n", fname, digval);
	// and do the same to the transformed file we made above
	r = C14N_File2Digest(digval1, sizeof(digval1) - 1, oname, "", "", 0);
	printf("C14N_File2Digest(%s)=\t%s\n", oname, digval1);
	assert(0 == strcmp(digval, digval1));

	// Same data, different encoding plus UTF-8 Byte Order Mark
	fname = "olamundo-utf8bom.xml";
	oname = "olamundo-utf8bom-out.xml";
	printf("FILE: %s\n", fname);
	r = C14N_File2Digest(digval, sizeof(digval) - 1, fname, "", "", 0);
	printf("C14N_File2Digest(%s)=\t%s\n", fname, digval);

	// -- we can transform (X)HTML files, too. 
	// This example is used in a detached signature: see `detached.xml`
	fname = "abc.html";
	oname = "abc-c14n-html.txt";
	printf("FILE: %s\n", fname);
	n = C14N_File2File(oname, fname, "", "", 0);
	printf("C14N_File2File()->%s returns %ld (expected 0 => OK)\n", oname, n);
	assert(0 == n);

	// Display entire file after transforming...
	nchars = C14N_File2String(NULL, 0, fname, "", "", 0);
	assert(nchars > 0);
	cp = malloc(nchars + 1);
	nchars = C14N_File2String(cp, nchars, fname, "", "", 0);
	printf("C14N('%s'):\n", fname);
	printf("%s\n", cp);
	free(cp);

	// Compute SHA-1 digest of entire file
	r = C14N_File2Digest(digval, sizeof(digval) - 1, fname, "", "", 0);
	printf("SHA1(%s)=\t%s\n", fname, digval);
	// and do the same to the transformed file we made above
	r = C14N_File2Digest(digval1, sizeof(digval1) - 1, oname, "", "", 0);
	printf("SHA1(%s)=\t%s\n", oname, digval1);
	assert(0 == strcmp(digval, digval1));

	// Same again but using SHA-256
	r = C14N_File2Digest(digval, sizeof(digval) - 1, fname, "", "", SC14N_DIG_SHA256);
	printf("SHA256(%s)=\t%s\n", fname, digval);


	// Extract and transform the body element
	// Output to a byte array
	fname = "olamundo-base.xml";
	printf("FILE: %s\n", fname);
	nchars = C14N_File2String(NULL, 0, fname, "Body", "", SC14N_TRAN_SUBSETBYTAG);
	assert(nchars > 0);
	cp = malloc(nchars + 1);
	nchars = C14N_File2String(cp, nchars, fname, "Body", "", SC14N_TRAN_SUBSETBYTAG);
	printf("C14N_File2String(BODY) returns %ld bytes.\n", nchars);
	// Note this a byte array containing UTF-8-encoded text, so it may not print OK on a Windows console
	printf("C14N(Body) [may display funny]:\n");
	printf("%s\n", cp);
	free(cp);

	// Compute SHA-1 digest value of the XML document excluding the <Signature> element
	// ... this is the value to be inserted into <SignedInfo>
	r = C14N_File2Digest(digval, sizeof(digval) - 1, fname, "Signature", "", SC14N_TRAN_OMITBYTAG);
	printf("DIGVAL:\n");
	printf("%s\n", digval);

	// Extract the SignedInfo element into memory
	// Note %digval% parameter to be completed in the <DigestValue> element
	nchars = C14N_File2String(NULL, 0, fname, "SignedInfo", "", SC14N_TRAN_SUBSETBYTAG);
	assert(nchars > 0);
	cp = malloc(nchars + 1);
	nchars = C14N_File2String(cp, nchars, fname, "SignedInfo", "", SC14N_TRAN_SUBSETBYTAG);
	printf("SIGNEDINFO (BASE):\n");
	printf("%s\n", cp);

	// Insert the required DigestValue we prepared earlier
	news = replace_str(cp, "%digval%", digval);
	printf("SIGNEDINFO (COMPLETED):\n");
	printf("%s\n", news);

	// Now compute the digest value of this updated string.
	// We could use this to compute the required signature value
	// - see TestSc14nPKI.c
	r = C14N_String2Digest(digval1, sizeof(digval1) - 1, news, strlen(news), "", "", 0);
	printf("SHA1(signedinfo)= %s\n", digval1);

	free(cp);
	free(news);

	/* EXCLUSIVE C14N */
	// Example files from RFC 3741 "Exclusive XML Canonicalization, Version 1.0", s2.2 
	// after errata reference E01 2002-10-03 (xml:space="retain" should be xml:space="preserve")

	// Two example files `example1.xml` and `example 2.xml`: compute c14n of element <n1:elem2>
	// Read in first file to a string then display it
	fname = "example1.xml";
	fdata = read_file_to_string(fname);
	assert(fdata);
	filesize = strlen(fdata);
	printf("\nFILE: %s\n", fname);
	printf("%s\n", fdata);

	// Transform using inclusive c14n
	nchars = C14N_String2String(NULL, 0, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG);
	assert(nchars > 0);
	cp = malloc(nchars + 1);
	nchars = C14N_String2String(cp, nchars, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG);
	printf("1.1 Inclusive c14n of element 'n1:elem2':\n");
	printf("%s\n", cp);
	free(cp);

	// Transform using exclusive c14n
	nchars = C14N_String2String(NULL, 0, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG | SC14N_METHOD_EXCLUSIVE);
	assert(nchars > 0);
	cp = malloc(nchars + 1);
	nchars = C14N_String2String(cp, nchars, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG | SC14N_METHOD_EXCLUSIVE);
	printf("1.2 Exclusive c14n of element 'n1:elem2':\n");
	printf("%s\n", cp);
	free(cp);
	// and compute SHA-1 digest for reference
	r = C14N_String2Digest(digval, sizeof(digval) - 1, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG | SC14N_METHOD_EXCLUSIVE);
	printf("SHA1(ex1.2)=%s\n", digval);

	// Free file data
	free(fdata);

	// Second file
	fname = "example2.xml";
	fdata = read_file_to_string(fname);
	assert(fdata);
	filesize = strlen(fdata);
	printf("\nFILE: %s\n", fname);
	printf("%s\n", fdata);

	// Transform using inclusive c14n
	nchars = C14N_String2String(NULL, 0, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG);
	assert(nchars > 0);
	cp = malloc(nchars + 1);
	nchars = C14N_String2String(cp, nchars, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG);
	printf("2.1 Inclusive c14n of element 'n1:elem2':\n");
	printf("%s\n", cp);
	free(cp);

	// Transform using exclusive c14n
	nchars = C14N_String2String(NULL, 0, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG | SC14N_METHOD_EXCLUSIVE);
	assert(nchars > 0);
	cp = malloc(nchars + 1);
	nchars = C14N_String2String(cp, nchars, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG | SC14N_METHOD_EXCLUSIVE);
	printf("2.2 Exclusive c14n of element 'n1:elem2':\n");
	printf("%s\n", cp);
	free(cp);
	// and compute SHA-1 digest for reference
	r = C14N_String2Digest(digval1, sizeof(digval1) - 1, fdata, filesize, "n1:elem2", "", SC14N_TRAN_SUBSETBYTAG | SC14N_METHOD_EXCLUSIVE);
	printf("SHA1(ex2.2)=%s\n", digval1);

	// Free file data
	free(fdata);

	printf("\nCOMMENT: Excl-c14n 1.2 and 2.2 above should be identical\n");
	printf("SHA1(ex1.2)=%s\n", digval);
	printf("SHA1(ex2.2)=%s\n", digval1);
	assert(0 == strcmp(digval, digval1));

	// [v2.1] DEMONSTRATE FLATTEN OPTION
	fname = "ignorable_ws.xml";
	printf("\nFILE: %s\n", fname);

	// Default c14n...
	digok = "JNluoz+Z+MbLrTX8W//wEEgeFpo=";
	nchars = C14N_File2String(NULL, 0, fname, "", "", 0);
	assert(nchars > 0);
	cp = malloc(nchars + 1);
	nchars = C14N_File2String(cp, nchars, fname, "", "", 0);
	printf("no-flatten:\n");
	printf("%s\n", cp);
	free(cp);
	r = C14N_File2Digest(digval, sizeof(digval) - 1, fname, "", "", 0);
	printf("DIGVAL=%s\n", digval);
	assert(0 == strcmp(digval, digok));

	// Flatten the XML - remove ignorable whitespace	
	digok = "4ZKWJnP7dUperStlOKrq7athzxw=";
	nchars = C14N_File2String(NULL, 0, fname, "", "", SC14N_OPT_FLATTEN);
	assert(nchars > 0);
	cp = malloc(nchars + 1);
	nchars = C14N_File2String(cp, nchars, fname, "", "", SC14N_OPT_FLATTEN);
	printf("FLATTEN:\n");
	printf("%s\n", cp);
	free(cp);
	r = C14N_File2Digest(digval, sizeof(digval) - 1, fname, "", "", SC14N_OPT_FLATTEN);
	printf("DIGVAL=%s\n", digval);
	assert(0 == strcmp(digval, digok));


	printf("\nALL DONE.\n");

}


/* THIRD-PARTY CODE ********************************************************************************/

/* Ref: http://creativeandcritical.net/str-replace-c/
* Description:	Replaces in the string str all the occurrences of the source string old
* with the destination string new. The parameters old and new may be of any length,
* and their lengths are allowed to differ.
* None of the three parameters may be NULL.
*
* Returns:	The post-replacement string, or NULL if memory for the new string could not be allocated.
* Does not modify the original string. The memory for the returned post-replacement
* string may be deallocated with the standard library function free() when it is no longer required.
*
* Licence: Public domain. You may use this code in any way you see fit,
* optionally crediting its author (me, Laird Shaw, with assistance from comp.lang.c).
* http://creativeandcritical.net/contact/
*/

char *replace_str(const char *str, const char *old, const char *news)
{
	char *ret, *r;
	const char *p, *q;
	size_t oldlen = strlen(old);
	size_t count, retlen, newlen = strlen(news);

	if (oldlen != newlen) {
		for (count = 0, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen)
			count++;
		/* this is undefined if p - str > PTRDIFF_MAX */
		retlen = p - str + strlen(p) + count * (newlen - oldlen);
	}
	else
		retlen = strlen(str);

	if ((ret = malloc(retlen + 1)) == NULL)
		return NULL;

	for (r = ret, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen) {
		/* this is undefined if q - p > PTRDIFF_MAX */
		ptrdiff_t l = q - p;
		memcpy(r, p, l);
		r += l;
		memcpy(r, news, newlen);
		r += newlen;
	}
	strcpy(r, p);

	return ret;
}
/*****************************************************************************************************/