//
// $Id: AbsXmlFileContentProbe.cpp,v 1.8 2006/09/11 13:49:30 bakerj Exp $
//
//****************************************************************************************//
// Copyright (c) 2006, The MITRE Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice, this list
//       of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this 
//       list of conditions and the following disclaimer in the documentation and/or other
//       materials provided with the distribution.
//     * Neither the name of The MITRE Corporation nor the names of its contributors may be
//       used to endorse or promote products derived from this software without specific 
//       prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//****************************************************************************************//

#include "AbsXmlFileContentProbe.h"

AbsXmlFileContentProbe::AbsXmlFileContentProbe() {
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//	Note that this is a protected constructor
	// -----------------------------------------------------------------------

}

AbsXmlFileContentProbe::~AbsXmlFileContentProbe() {
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//	Note that this is a protected constructor
	// -----------------------------------------------------------------------
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
ItemVector* AbsXmlFileContentProbe::CollectItems(Object* object) {
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//	
	//	Use a platform specifc FileFinder to find all files that match the 
	//  spoecified object. Then evaulate xpath expression on each matching file and
	//  create an Item to hold the resulting data.
	//
	//  Note 1: xpath expressions are treated as though they always evaluate to 
	//  a single node.
	//
	//  Note 2: regular expressions are not allowed as part of an xpath
	//
	//  Note 3: this probe should allow for a context node to be provided
	//  for now the context node will default to '/' (document root)
	// -----------------------------------------------------------------------

	ItemVector *collectedItems = new ItemVector();

	// get the hive, key, and name from the provided object
	ObjectEntity* path = object->GetElementByName("path");
	ObjectEntity* fileName = object->GetElementByName("filename");
	ObjectEntity* xpath = object->GetElementByName("xpath");

	
	// check datatypes - only allow string
	if(path->GetDatatype() != OvalEnum::STRING_TYPE) {
		throw ProbeException("Error: invalid data type specified on path. Found: " + OvalEnum::DatatypeToString(path->GetDatatype()));
	}
	if(fileName->GetDatatype() != OvalEnum::STRING_TYPE) {
		throw ProbeException("Error: invalid data type specified on fileName. Found: " + OvalEnum::DatatypeToString(fileName->GetDatatype()));
	}
	if(xpath->GetDatatype() != OvalEnum::STRING_TYPE) {
		throw ProbeException("Error: invalid data type specified on xpath. Found: " + OvalEnum::DatatypeToString(xpath->GetDatatype()));
	}

	// check operation 
	if(path->GetOperation() != OvalEnum::EQUALS_OPERATION 
		&& path->GetOperation() != OvalEnum::PATTERN_MATCH_OPERATION 
		&& path->GetOperation() != OvalEnum::NOT_EQUAL_OPERATION) {
		throw ProbeException("Error: invalid operation specified on path. Found: " + OvalEnum::OperationToString(path->GetOperation()));
	}
	if(fileName->GetOperation() != OvalEnum::EQUALS_OPERATION 
		&& fileName->GetOperation() != OvalEnum::PATTERN_MATCH_OPERATION 
		&& fileName->GetOperation() != OvalEnum::NOT_EQUAL_OPERATION) {
		throw ProbeException("Error: invalid operation specified on fileName. Found: " + OvalEnum::OperationToString(fileName->GetOperation()));
	}
	if(xpath->GetOperation() != OvalEnum::EQUALS_OPERATION) {
		throw ProbeException("Error: invalid operation specified on xpath. Found: " + OvalEnum::OperationToString(xpath->GetOperation()));
	}

	// TODO - determine how to support behaviors.
	if(object->GetBehaviors()->size() != 0) {
		throw ProbeException("Error: (RegistryProbe) Behaviors are not supported."); 
	}

	StringPairVector* filePaths = this->GetFiles(path, fileName, object->GetBehaviors());

	// Loop through all file paths
	StringPairVector::iterator iterator;
	for(iterator = filePaths->begin(); iterator != filePaths->end(); iterator++) {
		Item* item = this->EvaluateXpath((*iterator)->first, (*iterator)->second, xpath->GetValue());
		if(item != NULL) {
			collectedItems->push_back(item);
		}
		item = NULL;
	}
	delete filePaths;

	return collectedItems;
}

Item* AbsXmlFileContentProbe::EvaluateXpath(string path, string fileName, string xpath) {
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//	
	//	Return an Item for the specifeid xpath if is succeeds
	//	otherwise return NULL
	// -----------------------------------------------------------------------

	Item* item = NULL;

	string contextNode = "/";

	string filePath = path;
	if(path[path.length()-1] != Common::fileSeperator)
		filePath.append(1, Common::fileSeperator);

	if(fileName[0] != Common::fileSeperator) {
		filePath.append(fileName);
	} else {
		filePath.append(fileName.substr(1, fileName.length()-2));
	}

	XALAN_USING_XALAN(XSLException)

	//XALAN_USING_XERCES(XMLPlatformUtils)
	XALAN_USING_XALAN(XPathEvaluator)
	XMLPlatformUtils::Initialize();
	XPathEvaluator::initialize();

	XALAN_USING_XERCES(LocalFileInputSource)
	XALAN_USING_XALAN(XalanDocument)
	XALAN_USING_XALAN(XalanDocumentPrefixResolver)
	XALAN_USING_XALAN(XalanDOMString)
	XALAN_USING_XALAN(XalanNode)
	XALAN_USING_XALAN(XalanSourceTreeInit)
	XALAN_USING_XALAN(XalanSourceTreeDOMSupport)
	XALAN_USING_XALAN(XalanSourceTreeParserLiaison)
	XALAN_USING_XALAN(XObjectPtr)
	XALAN_USING_XALAN(NodeRefList)

	// Initialize the XalanSourceTree subsystem...
	XalanSourceTreeInit	theSourceTreeInit;

	// We'll use these to parse the XML file.
	XalanSourceTreeDOMSupport theDOMSupport;
	XalanSourceTreeParserLiaison theLiaison(theDOMSupport);

	// Hook the two together...
	theDOMSupport.setParserLiaison(&theLiaison);
		
	try {

		const XalanDOMString theFileName(filePath.c_str());

		// Create an input source that represents a local file...
		const LocalFileInputSource theInputSource(theFileName.c_str());

		// Parse the document...
		XalanDocument* theDocument = NULL;
		try {
			theDocument = theLiaison.parseXMLStream(theInputSource);

		} catch(...) {
			theDocument = NULL;
			// this should never happen at this point only documents that exist should get here
			//string errMsg = "Error: The specified document does not exist: " + filePath;
		}

		if(theDocument == NULL) {
			throw ProbeException("Error: Unable to parse the current document: " + filePath);
		} else {

			XalanDocumentPrefixResolver	thePrefixResolver(theDocument);

			XPathEvaluator theEvaluator;

			// find the context node...
			XalanNode* const theContextNode =
					theEvaluator.selectSingleNode(	theDOMSupport,
													theDocument,
													XalanDOMString(contextNode.c_str()).c_str(),
													thePrefixResolver);

			if (theContextNode == 0) {
				throw ProbeException("Error the specified context node, \'" + contextNode + "\' was not found.");
			} else {

				// evaluate the expression...
				/*const XObjectPtr theResult = XObjectPtr(
				theEvaluator.evaluate(
						theDOMSupport,
						theContextNode,
						XalanDOMString(xpath.c_str()).c_str(),
						thePrefixResolver));*/

				NodeRefList nodeList;
				theEvaluator.selectNodeList(nodeList,
											theDOMSupport,
											theContextNode,
											XalanDOMString(xpath.c_str()).c_str(),
											thePrefixResolver);


				if(nodeList.getLength() <= 0) {
					// no nodes were found
					item = this->CreateItem();
					item->SetStatus(OvalEnum::EXISTS_STATUS);
					item->AppendElement(new ItemEntity("path", path, OvalEnum::STRING_TYPE, true, OvalEnum::EXISTS_STATUS));
					item->AppendElement(new ItemEntity("filename", fileName, OvalEnum::STRING_TYPE, true, OvalEnum::EXISTS_STATUS));
					item->AppendElement(new ItemEntity("xpath", xpath, OvalEnum::STRING_TYPE, true, OvalEnum::EXISTS_STATUS));
					item->AppendElement(new ItemEntity("value_of", "", OvalEnum::STRING_TYPE, false, OvalEnum::DOES_NOT_EXIST_STATUS));

				} else {

					item = this->CreateItem();
					item->SetStatus(OvalEnum::EXISTS_STATUS);
					item->AppendElement(new ItemEntity("path", path, OvalEnum::STRING_TYPE, true, OvalEnum::EXISTS_STATUS));
					item->AppendElement(new ItemEntity("filename", fileName, OvalEnum::STRING_TYPE, true, OvalEnum::EXISTS_STATUS));
					item->AppendElement(new ItemEntity("xpath", xpath, OvalEnum::STRING_TYPE, true, OvalEnum::EXISTS_STATUS));

					// add each value returned.
					int length = nodeList.getLength();
					for(int i = 0; i < length; i++) {
						XalanNode* node = nodeList.item(i);

						if(node->getNodeType() == XalanNode::TEXT_NODE) {
							XalanDOMString nodeValue = node->getNodeValue();
							// Convert the result value to a string
							ostringstream r;
							r << nodeValue;
							string value = r.str();

							item->AppendElement(new ItemEntity("value_of", value, OvalEnum::STRING_TYPE, false, OvalEnum::EXISTS_STATUS));

						} else {
							throw ProbeException("Error: invalid xpath specifeid. an xpath is only allowed to select text nodes from a document.");
						}
					}
				}
			}
		}

		XPathEvaluator::terminate();
		XMLPlatformUtils::Terminate();

	} catch(const XSLException& theException) {
		// Convert the XSLException message to a string
		ostringstream m;
		m << (theException.getMessage());
		string errMsg = m.str();

		XPathEvaluator::terminate();
		XMLPlatformUtils::Terminate();

		throw ProbeException("Error while evaluating an xpath. " + errMsg);

	} catch(ProbeException ex) {

		XPathEvaluator::terminate();
		XMLPlatformUtils::Terminate();

		throw ex;

	} catch(Exception ex) {

		XPathEvaluator::terminate();
		XMLPlatformUtils::Terminate();

		throw ex;

	} catch(...) {

		XPathEvaluator::terminate();
		XMLPlatformUtils::Terminate();

		throw ProbeException("Error: AbsXmlFileContentProbe() An unknown error occured while collecting data.");
	}

	return item;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Private Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
Item* AbsXmlFileContentProbe::CreateItem() {
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//  Return a new Item created for storing xml file content information
	//
	// -----------------------------------------------------------------------

	Item* item = new Item(0, 
						"http://oval.mitre.org/XMLSchema/oval-system-characteristics-5#independent", 
						"ind-sc", 
						"http://oval.mitre.org/XMLSchema/oval-system-characteristics-5#independent independent-system-characteristics-schema.xsd", 
						OvalEnum::ERROR_STATUS, 
						"xmlfilecontent_item");

	return item;
}
