/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * XSEC
 *
 * siginf := Output information about a signature found in an XML file
 *
 * Author(s): Berin Lautenbach
 *
 * $Id: siginf.cpp 1832576 2018-05-30 23:45:26Z scantor $
 *
 */

// XSEC

#include <xsec/utils/XSECPlatformUtils.hpp>
#include <xsec/framework/XSECProvider.hpp>
#include <xsec/canon/XSECC14n20010315.hpp>
#include <xsec/dsig/DSIGSignature.hpp>
#include <xsec/dsig/DSIGReference.hpp>
#include <xsec/framework/XSECException.hpp>
#include <xsec/enc/XSECCryptoException.hpp>
#include <xsec/enc/XSECKeyInfoResolverDefault.hpp>

#include <xsec/dsig/DSIGTransformC14n.hpp>
#include <xsec/dsig/DSIGTransformBase64.hpp>
#include <xsec/dsig/DSIGTransformXSL.hpp>
#include <xsec/dsig/DSIGTransformXPath.hpp>
#include <xsec/dsig/DSIGTransformXPathFilter.hpp>
#include <xsec/dsig/DSIGXPathFilterExpr.hpp>
#include <xsec/dsig/DSIGTransformEnvelope.hpp>

#include <xsec/dsig/DSIGTransformList.hpp>

#include "../../utils/XSECDOMUtils.hpp"

// General

#include <memory.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>

#if defined (_DEBUG) && defined (_MSC_VER)
#include <crtdbg.h>
#endif


#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/XMLString.hpp>

#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/util/XMLException.hpp>
#include <xercesc/util/XMLUri.hpp>
#include <xercesc/util/Janitor.hpp>

XERCES_CPP_NAMESPACE_USE

using std::cerr;
using std::cout;
using std::endl;
using std::ostream;

#ifdef XSEC_HAVE_XALAN

// XALAN

#include <xalanc/XPath/XPathEvaluator.hpp>
#include <xalanc/XalanTransformer/XalanTransformer.hpp>

XALAN_USING_XALAN(XPathEvaluator)
XALAN_USING_XALAN(XalanTransformer)

#else

ostream& operator<< (ostream& target, const XMLCh * s)
{
    char *p = XMLString::transcode(s);
    target << p;
    XSEC_RELEASE_XMLCH(p);
    return target;
}

#endif

class X2C {

public:

	X2C(const XMLCh * in) {
		mp_cStr = XMLString::transcode(in);
	}
	~X2C() {
		XSEC_RELEASE_XMLCH(mp_cStr);
	}

	char * str(void) {
		return mp_cStr;
	}

private :

	char * mp_cStr;

};

ostream & operator<<(ostream& target, X2C &x) {
	target << x.str();
	return target;
}

inline
void levelSet(unsigned int level) {

	for (unsigned int i = 0; i < level; ++i)
		cout << "    ";

}

void outputTransform(const DSIGTransform * t, unsigned int level) {


    if (dynamic_cast<const DSIGTransformBase64*>(t)) {
		cout << "Base64 Decode" << endl;
    }
    else if (dynamic_cast<const DSIGTransformC14n*>(t)) {
        const XMLCh* cm = dynamic_cast<const DSIGTransformC14n*>(t)->getCanonicalizationMethod();
        if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIC14N_NOC)) {
            cout << "c14n 1.0 canonicalization (without comments)" << endl;
        }
        else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIC14N_COM)) {
            cout << "c14n 1.0 canonicalization (with comments)" << endl;
        }
        else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIC14N11_NOC)) {
            cout << "c14n 1.1 canonicalization (without comments)" << endl;
        }
        else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIC14N11_COM)) {
            cout << "c14n 1.1 canonicalization (with comments)" << endl;
        }
        else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIEXC_C14N_NOC)) {
            cout << "Exclusive c14n 1.0 canonicalization (without comments)" << endl;
            if (dynamic_cast<const DSIGTransformC14n*>(t)->getPrefixList() != NULL) {
                levelSet(level);
                cout << "Inclusive prefixes : " <<
                    X2C(dynamic_cast<const DSIGTransformC14n*>(t)->getPrefixList()).str() << endl;
            }
        }
        else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIEXC_C14N_COM)) {
            cout << "Exclusive c14n 1.0 canonicalization (with comments)" << endl;
            if (dynamic_cast<const DSIGTransformC14n*>(t)->getPrefixList() != NULL) {
                levelSet(level);
                cout << "Inclusive prefixes : " <<
                    X2C(dynamic_cast<const DSIGTransformC14n*>(t)->getPrefixList()).str() << endl;
            }
        }
        else {
            cout << "Unknown c14n method" << endl;
        }
    }
    else if (dynamic_cast<const DSIGTransformEnvelope*>(t)) {
        cout << "enveloped signature" << endl;
    }
    else if (dynamic_cast<const DSIGTransformXPath*>(t)) {
        const DSIGTransformXPath* xp = dynamic_cast<const DSIGTransformXPath*>(t);

        cout << "XPath" << endl;
        // Check for namespaces
        DOMNamedNodeMap* atts = xp->getNamespaces();

        if (atts != 0) {
            XMLSize_t s = atts->getLength();
            for (XMLSize_t i = 0 ; i < s; ++i) {
                levelSet(level);
                cout << "Namespace : " << X2C(atts->item(i)->getNodeName()).str() <<
                    "=\"" << X2C(atts->item(i)->getNodeValue()).str() << "\"\n";
            }
        }
        levelSet(level);
        // Hmm - this is really a bug.  This should return a XMLCh string
        cout << "Expr : " << xp->getExpression() << endl;
    }
    else if (dynamic_cast<const DSIGTransformXPathFilter*>(t)) {
        const DSIGTransformXPathFilter * xpf = dynamic_cast<const DSIGTransformXPathFilter*>(t);

        cout << "XPath-Filter2" << endl;

        unsigned int s = xpf->getExprNum();

        for (unsigned int i = 0; i < s; ++i) {

            levelSet(level);
            cout << "Filter : ";

            const DSIGXPathFilterExpr * e = xpf->expr(i);

            switch (e->getFilterType()) {

            case DSIGXPathFilterExpr::FILTER_UNION :
                cout << "union : \"";
                break;
            case DSIGXPathFilterExpr::FILTER_INTERSECT :
                cout << "intersect : \"";
                break;
            default :
                cout << "subtract : \"";
            }

            // Now the expression
            char * str = XMLString::transcode(e->getFilter());
            cout << str << "\"" << endl;
            XSEC_RELEASE_XMLCH(str);
        }
    }
    else if (dynamic_cast<const DSIGTransformXSL*>(t)) {
        cout << "XSLT" << endl;
        // Really should serialise and output stylesheet.
    }
    else {
		cout << "unknown transform type" << endl;
    }
}
		
void outputReferences(DSIGReferenceList *rl, unsigned int level) {

	int s = (int) rl->getSize();

	for (int i = 0; i < s; ++i) {
	
		levelSet(level);
		cout << "Reference " << i + 1 << " : " << endl;
		levelSet(level + 1);
		cout << "URI : \"" << X2C(rl->item(i)->getURI()).str() << "\"" << endl;
		levelSet(level + 1);
		cout << "Digest Algorithm : ";
        char* alg = XMLString::transcode(rl->item(i)->getAlgorithmURI());
        cout << (alg ? alg : "Unknown") << endl;
        XSEC_RELEASE_XMLCH(alg);

		// Now the transforms
		DSIGTransformList * tl = rl->item(i)->getTransforms();
		if (tl != NULL) {

			int tlSize = (int) tl->getSize();
			for (int j = 0 ; j < tlSize; ++j) {

				levelSet(level+1);
				cout << "Transform " << j + 1 << " : ";
				outputTransform(tl->item(j), level + 2);

			}

		}

		if (rl->item(i)->isManifest() == true) {

			levelSet(level + 1);
			cout << "Manifest References : " << endl;
			outputReferences(rl->item(i)->getManifestReferenceList(), level + 2);
			levelSet(level + 1);
			cout << "End Manifest References" << endl;

		}

	}

}

void outputSignatureInfo(DSIGSignature *sig, bool skipReferences) {

	// First get some information about the main signature
	cout << "Signature (Signed Info) settings : " << endl;
	cout << "    Canonicalization Method : ";
	
    const XMLCh* cm = sig->getCanonicalizationMethod();
    if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIC14N_NOC)) {
        cout << "c14n 1.0 canonicalization (without comments)" << endl;
    }
    else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIC14N_COM)) {
        cout << "c14n 1.0 canonicalization (with comments)" << endl;
    }
    else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIC14N11_NOC)) {
        cout << "c14n 1.1 canonicalization (without comments)" << endl;
    }
    else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIC14N11_COM)) {
        cout << "c14n 1.1 canonicalization (with comments)" << endl;
    }
    else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIEXC_C14N_NOC)) {
        cout << "Exclusive c14n 1.0 canonicalization (without comments)" << endl;
    }
    else if (XMLString::equals(cm, DSIGConstants::s_unicodeStrURIEXC_C14N_COM)) {
        cout << "Exclusive c14n 1.0 canonicalization (with comments)" << endl;
    }
    else {
        cout << "Unknown c14n method" << endl;
    }

	cout << endl;

    cout << "    Signature Algorithm : ";
    char* alg = XMLString::transcode(sig->getAlgorithmURI());
    cout << (alg ? alg : "Unknown") << endl;
    XSEC_RELEASE_XMLCH(alg);

	// Read in the references and output

	if (skipReferences == false) {

		DSIGReferenceList * rl = sig->getReferenceList();
	
		if (rl != NULL) {

			cout << endl << "Reference List : " << endl;
			outputReferences(rl, 1);
	
		}
	}
}

void printUsage() {

	cerr << "\nUsage: siginf [options] <input file name>\n\n";
	cerr << "     Where options are :\n\n";
	cerr << "     --skiprefs/-s\n";
	cerr << "         Skip information on references - output main sig info only\n\n";

}

int evaluate(int argc, char ** argv) {
	
	char					* filename = NULL;
	bool					skipRefs = false;

	if (argc < 2) {

		printUsage();
		return 2;
	}

	// Run through parameters
	int paramCount = 1;

	while (paramCount < argc - 1) {

		if (_stricmp(argv[paramCount], "--skiprefs") == 0 || _stricmp(argv[paramCount], "-s") == 0) {
			skipRefs = true;
			paramCount++;
		}
		else {
			printUsage();
			return 2;
		}
	}

	if (paramCount >= argc) {
		printUsage();
		return 2;
	}

	filename = argv[paramCount];

	// Create and set up the parser

	XercesDOMParser * parser = new XercesDOMParser;
	Janitor<XercesDOMParser> j_parser(parser);

	parser->setDoNamespaces(true);
	parser->setCreateEntityReferenceNodes(true);

	// Now parse out file

	bool errorsOccured = false;
	XMLSize_t errorCount = 0;
    try
    {
    	parser->parse(filename);
        errorCount = parser->getErrorCount();
    }

    catch (const XMLException& e)
    {
		char * msg = XMLString::transcode(e.getMessage());
        cerr << "An error occurred during parsing\n   Message: "
             << msg << endl;
		XSEC_RELEASE_XMLCH(msg);
        errorsOccured = true;
    }


    catch (const DOMException& e)
    {
       cerr << "A DOM error occurred during parsing\n   DOMException code: "
             << e.code << endl;
        errorsOccured = true;
    }

	if (errorCount > 0 || errorsOccured) {

		cout << "Errors during parse" << endl;
		return (2);

	}

	/*

		Now that we have the parsed file, get the DOM document and start looking at it

	*/
	
	DOMNode *doc = parser->getDocument();
	DOMDocument *theDOM = parser->getDocument();

	// Find the signature node
	
	DOMNode *sigNode = findDSIGNode(doc, "Signature");

	// Create the signature checker

	if (sigNode == 0) {

		cerr << "Could not find <Signature> node in " << argv[argc-1] << endl;
		return 1;
	}

	XSECProvider prov;
	DSIGSignature * sig = prov.newSignatureFromDOM(theDOM, sigNode);

	try {

		sig->load();

		// If we didn't get an exception, things went well

		cout << "Filename : " << filename << endl;

		outputSignatureInfo(sig, skipRefs);
//		if (skipRefs == false)
//			result = sig->verifySignatureOnly();
//		else
//			result = sig->verify();
	}

	catch (const XSECException &e) {
		char * msg = XMLString::transcode(e.getMsg());
		cerr << "An error occurred during signature loading\n   Message: "
		<< msg << endl;
		XSEC_RELEASE_XMLCH(msg);
		errorsOccured = true;
		return 2;
	}
	catch (...) {

		cerr << "Unknown Exception type occurred.  Cleaning up and exiting\n" << endl;
		return 2;

	}

	// Clean up

	prov.releaseSignature(sig);
	// Janitor will clean up the parser
	return 0;
}


int main(int argc, char **argv) {

	int retResult;

	/* We output a version number to overcome a "feature" in Microsoft's memory
	   leak detection */

	cout << "DSIG Info (Using Apache XML-Security-C Library v" << XSEC_VERSION_MAJOR <<
		"." << XSEC_VERSION_MEDIUM << "." << XSEC_VERSION_MINOR << ")\n";

#if defined (_DEBUG) && defined (_MSC_VER)

	// Do some memory debugging under Visual C++

	_CrtMemState s1, s2, s3;

	// At this point we are about to start really using XSEC, so
	// Take a "before" checkpoing

	_CrtMemCheckpoint( &s1 );

#endif

	// Initialise the XML system

	try {

		XMLPlatformUtils::Initialize();
#ifdef XSEC_HAVE_XALAN
		XPathEvaluator::initialize();
		XalanTransformer::initialize();
#endif
		XSECPlatformUtils::Initialise();

	}
	catch (const XMLException &e) {

		cerr << "Error during initialisation of Xerces" << endl;
		cerr << "Error Message = : "
		     << e.getMessage() << endl;

	}

	retResult = evaluate(argc, argv);

	XSECPlatformUtils::Terminate();
#ifdef XSEC_HAVE_XALAN
	XalanTransformer::terminate();
	XPathEvaluator::terminate();
#endif
	XMLPlatformUtils::Terminate();

#if defined (_DEBUG) && defined (_MSC_VER)

	_CrtMemCheckpoint( &s2 );

	if ( _CrtMemDifference( &s3, &s1, &s2 ) && s3.lCounts[1] > 1) {

		std::cerr << "Total count = " << (unsigned int) s3.lTotalCount << endl;

		// Send all reports to STDOUT
		_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
		_CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT );
		_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE );
		_CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDOUT );
		_CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE );
		_CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDOUT );

		// Dumpy memory stats

 		_CrtMemDumpAllObjectsSince( &s3 );
	    _CrtMemDumpStatistics( &s3 );
	}

	// Now turn off memory leak checking and end as there are some 
	// Globals that are allocated that get seen as leaks (Xalan?)

	int dbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
	dbgFlag &= ~(_CRTDBG_LEAK_CHECK_DF);
	_CrtSetDbgFlag( dbgFlag );

#endif

	return retResult;
}
