program TestSc14n;
{
Some tests for the Delphi/FreePascal interface to SC14N
<https://www.cryptosys.net/sc14n/>
$Id: TestSc14n.pas $
$Date: 2023-04-14 11:01 $
$Revision: 1.0.1 $
************************** LICENSE *****************************************
Copyright (C) 2023 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.
For a copy, see <http://opensource.org/licenses/MIT>
****************************************************************************
}
{$APPTYPE CONSOLE}
{$mode Delphi}
{$ASSERTIONS ON}
uses
SysUtils, diSc14n;
const
{ WARNING: this is where we expect to find the test files.
CHANGE THIS TO SUIT }
TEST_DIR = '.\Test';
CRLF = #13 + #10;
{ Declare forward functions for safer wrapper functions returning strings. }
Function c14nFile2Digest(szInputFile : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; forward;
Function c14nFile2String(szInputFile : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; forward;
Function c14nString2String(szInput : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; forward;
Function c14nString2Digest(szInput : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; forward;
Function sc14nErrorLookup(nErrCode : LongInt): AnsiString; forward;
Function sc14nLastError(): AnsiString; forward;
// DO SOME TESTS
Procedure do_tests;
var
n : Integer;
s : AnsiString;
buf : AnsiString;
ch : Char;
nchars : Integer;
fname : AnsiString;
outfile : AnsiString;
begin
WriteLn('Running ' + ExtractFileName(ParamStr(0)) + ' at ' + DateTimeToStr(Now));
// Set current working directory to find test files
ChDir(TEST_DIR);
// INTERROGATE THE CORE DLL
// Either return an integer value or fill a pre-dimensioned output string buffer.
// (You could write wrapper functions for the 'Gen' functions here that output to a string)
WriteLn(CRLF + 'GENERAL:');
WriteLn('Interrogate the core DLL:');
WriteLn('Version='+(IntToStr(SC14N_Gen_Version)));
nchars := SC14N_Gen_CompileTime(NIL, 0);
WriteLn('CompileTime returns nchars=' + IntToStr(nchars));
buf := AnsiString(StringOfChar(#0,nchars));
nchars := SC14N_Gen_CompileTime(PAnsiChar(buf), nchars);
WriteLn('CompileTime=' + buf);
Assert (nchars > 0);
ch := Chr(SC14N_Gen_LicenceType());
WriteLn('LicenceType='+ ch);
nchars := SC14N_Gen_ModuleName(NIL, 0, 0);
WriteLn('ModuleName returns nchars=' + IntToStr(nchars));
buf := AnsiString(StringOfChar(#0, nchars));
SC14N_Gen_ModuleName(PAnsiChar(buf), nchars, 0);
WriteLn('ModuleName=' + Trim(string(buf)));
nchars := SC14N_Gen_Platform(NIL, 0);
WriteLn('Platform returns nchars=' + IntToStr(nchars));
buf := AnsiString(StringOfChar(#0, nchars));
SC14N_Gen_Platform(PAnsiChar(buf), nchars);
WriteLn('Platform=' + Trim(string(buf)));
// Compute C14N of FirstName element using tag
fname := 'test_bruce_utf8.xml';
WriteLn('FILE: ' + fname);
outfile := 'fileout.xml';
n := C14N_File2File(outfile, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG);
WriteLn('C14N_File2File returns '+(IntToStr(n))+' (expecting 0)');
Assert(0 = n);
WriteLn('Created output file: ' + outfile);
// Compute digest values the long way...
// Compute SHA-1 digest of C14N'd data
nchars := C14N_File2Digest(NIL, 0, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG);
WriteLn('C14N_File2Digest returns nchars=' + IntToStr(nchars));
buf := AnsiString(StringOfChar(#0, nchars));
C14N_File2Digest(PAnsiChar(buf), nchars, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG);
WriteLn('File2Digest(SHA-1)=' + Trim(string(buf)));
Assert('Qy90ICPAAbnjWl/UpAn37sXS1wU=' = buf);
// Same using SHA-256
nchars := C14N_File2Digest(NIL, 0, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA256);
WriteLn('C14N_File2Digest returns nchars=' + IntToStr(nchars));
buf := AnsiString(StringOfChar(#0, nchars));
C14N_File2Digest(PAnsiChar(buf), nchars, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA256);
WriteLn('File2Digest(SHA-256)=' + Trim(string(buf)));
Assert('93ljdxd4XQ2k66gOZai6a3bhjVO5t6XNqqBuArFyhc0=' = buf);
// Repeat using wrapper function
s := c14nFile2Digest(fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA256);
WriteLn('c14nFile2Digest(SHA-256) returns [' + s + ']');
Assert('93ljdxd4XQ2k66gOZai6a3bhjVO5t6XNqqBuArFyhc0=' = s);
s := c14nFile2Digest(fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA512);
WriteLn('c14nFile2Digest(SHA-512) returns ' + CRLF + '[' + s + ']');
// Extract C14N output from file into a string
s := c14nFile2String(fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG);
WriteLn('c14nFile2String returns:' + CRLF + s);
WriteLn('--expecting:' + CRLF + '<FirstName xml:id="F01">BruceƱ</FirstName>');
// String --> String
s := c14nString2String('<a><b xyz="last" abc="first" /></a>', 'b', '', SC14N_TRAN_SUBSETBYTAG);
WriteLn('c14nString2String returns [' + s + ']');
// String --> Digest
s := c14nString2Digest('<a><b xyz="last" abc="first" /></a>', 'b', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA256);
WriteLn('c14nString2Digest returns [' + s + ']');
WriteLn('Display some error codes...');
for n := 0 to 5 do
begin
WriteLn('Error(' + IntToStr(n) + ')=' + sc14nErrorLookup(n));
end;
WriteLn('Cause a deliberate error...');
n := C14N_File2File(outfile, 'missing.file', 'FirstName', '', SC14N_TRAN_SUBSETBYTAG);
WriteLn('C14N_File2File(missing.file) returns n=', n);
WriteLn('Error(' + IntToStr(n) + ')=' + sc14nErrorLookup(n));
WriteLn('LastError=' + sc14nLastError());
// An error in a wrapper function will raise an exception
try
s := c14nFile2Digest(fname, 'BadName', '', SC14N_TRAN_SUBSETBYTAG);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
WriteLn('...END OF deliberate errors.');
WriteLn('SC14N Version=', SC14N_Gen_Version());
WriteLn(CRLF+'ALL DONE.');
end;
{ WRAPPER FUNCTIONS
-- functions that return a string directly in a safe manner.
}
{ Our crude Exception class }
type ESc14nException = Class(Exception);
{ Compute digest value of C14N transformation (file-to-digest).
@param(szInputFile Name of input file containing XML document to be processed.)
@param(szNameOrId Tag name or Id to include or omit.)
@param(szParams InclusiveNamespaces PrefixList parameter for exclusive c14n.)
@param(nOptions Option flags.)
@returns(Digest value as base64-encoded string, or an empty string on error.)
@raises(ESc14nException if the core function fails for any reason.)
}
Function c14nFile2Digest(szInputFile : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString;
var
buf : AnsiString;
nchars : Integer;
begin
nchars := C14N_File2Digest(NIL, 0, szInputFile, szNameOrId, szParams, nOptions);
if nchars < 0 then raise ESc14nException.Create('SC14N error: ' + IntToStr(nchars));
buf := AnsiString(StringOfChar(#0, nchars));
C14N_File2Digest(PAnsiChar(buf), nchars, szInputFile, szNameOrId, szParams, nOptions);
Result := buf
end;
{ Perform C14N transformation of XML document (file-to-memory).
@param(szInputFile Name of input file containing XML document to be processed.)
@param(szNameOrId Tag name or Id to include or omit.)
@param(szParams InclusiveNamespaces PrefixList parameter for exclusive c14n.)
@param(nOptions Option flags.)
@returns(UTF-8-encoded string containing transformed data, or an empty string on error.)
@raises(ESc14nException if the core function fails for any reason.)
}
Function c14nFile2String(szInputFile : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString;
var
buf : AnsiString;
nchars : Integer;
begin
nchars := C14N_File2String(NIL, 0, szInputFile, szNameOrId, szParams, nOptions);
if nchars < 0 then raise ESc14nException.Create('SC14N error: ' + IntToStr(nchars));
buf := AnsiString(StringOfChar(#0, nchars));
C14N_File2String(PAnsiChar(buf), nchars, szInputFile, szNameOrId, szParams, nOptions);
Result := buf
end;
{ Perform C14N transformation of XML document (string-to-string).
@param(szInput String containing UTF-8-encoded XML data.)
@param(szNameOrId Tag name or Id to include or omit.)
@param(szParams InclusiveNamespaces PrefixList parameter for exclusive c14n.)
@param(nOptions Option flags.)
@returns(UTF-8-encoded string containing transformed data, or an empty string on error.)
@raises(ESc14nException if the core function fails for any reason.)
}
Function c14nString2String(szInput : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString;
var
buf : AnsiString;
nchars : Integer;
begin
nchars := C14N_String2String(NIL, 0, szInput, Length(szInput), szNameOrId, szParams, nOptions);
if nchars < 0 then raise ESc14nException.Create('SC14N error: ' + IntToStr(nchars));
buf := AnsiString(StringOfChar(#0, nchars));
C14N_String2String(PAnsiChar(buf), nchars, szInput, Length(szInput), szNameOrId, szParams, nOptions);
Result := buf
end;
{ Compute digest value of C14N transformation of XML document (string-to-digest).
@param(szInput String containing UTF-8-encoded XML data.)
@param(szNameOrId Tag name or Id to include or omit.)
@param(szParams InclusiveNamespaces PrefixList parameter for exclusive c14n.)
@param(nOptions Option flags.)
@returns(Digest value as base64-encoded string, or an empty string on error.)
@raises(ESc14nException if the core function fails for any reason.)
}
Function c14nString2Digest(szInput : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString;
var
buf : AnsiString;
nchars : Integer;
begin
nchars := C14N_String2Digest(NIL, 0, szInput, Length(szInput), szNameOrId, szParams, nOptions);
if nchars < 0 then raise ESc14nException.Create('SC14N error: ' + IntToStr(nchars));
buf := AnsiString(StringOfChar(#0, nchars));
C14N_String2Digest(PAnsiChar(buf), nchars, szInput, Length(szInput), szNameOrId, szParams, nOptions);
Result := buf
end;
{ Look up description for error code.
@param(nErrCode Value of error code to lookup (may be positive or negative).)
@returns(Error message, or empty string if no corresponding error code.)
}
Function sc14nErrorLookup(nErrCode : LongInt): AnsiString;
var
buf : AnsiString;
nchars : Integer;
begin
nchars := SC14N_Err_ErrorLookup(NIL, 0, nErrCode);
if nchars < 0 then Exit('');
buf := AnsiString(StringOfChar(#0, nchars));
SC14N_Err_ErrorLookup(PAnsiChar(buf), nchars, nErrCode);
Result := buf
end;
{ Retrieve the last error message (if available).
@returns(String with more information about the last error.)
@note(Not all functions set this.)
}
Function sc14nLastError(): AnsiString;
var
buf : AnsiString;
nchars : Integer;
begin
nchars := SC14N_Err_LastError(NIL, 0);
if nchars < 0 then Exit('');
buf := AnsiString(StringOfChar(#0, nchars));
SC14N_Err_LastError(PAnsiChar(buf), nchars);
Result := buf
end;
{ MAIN TEST PROCEDURE }
begin
try
do_tests;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.