/*
*   $Id: TestSc14n.c $
*   Last updated:
*   $Date: 2017-07-16 07:29 $
*   $Version: 0.9.0 $
*/
/* Some tests using the Sc14n C/C++ interface.
 * Please report any bugs to <http://www.di-mgt.com.au/contact>
 */
/******************************* LICENSE ***********************************
* Copyright (C) 2017 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 <http://di-mgt.com.au/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);


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

  /* 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_EXCLUDEBYTAG);
  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_EXCLUDEBYID);
  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_EXCLUDEBYTAG);
  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_EXCLUDEBYID);
  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_EXCLUDEBYTAG);
  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);

  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;
}
/*****************************************************************************************************/