//
// $Id: AbsFileFinder.cpp,v 1.10 2007/01/11 18:14:16 bakerj Exp $
//
//****************************************************************************************//
// Copyright (c) 2002-2007, 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 "AbsFileFinder.h"

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

	fileMatcher = new REGEX();
}

AbsFileFinder::~AbsFileFinder() {

	delete fileMatcher;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  Public Members  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
StringPairVector* AbsFileFinder::SearchFiles(ObjectEntity* path, ObjectEntity* fileName, BehaviorVector* behaviors) {
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//	Return the set of filepaths as a vector of StringPairs that match the 
	//  specified criteria.
	// -----------------------------------------------------------------------

	StringPairVector* filePaths = new StringPairVector();

	// 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()));
	}

	// check operation - only allow  equals, not equals and pattern match
	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()));
	}

	// get all the paths
	StringVector* paths = this->GetPaths(path, behaviors);
	StringVector::iterator pathIt;
	for(pathIt = paths->begin(); pathIt != paths->end(); pathIt++) {
		
		// get all files if the file is not nil
		if(fileName->GetNil()) {

			StringPair* filePath = new StringPair((*pathIt), "");
            filePaths->push_back(filePath);

		} else {

			StringVector* fileNames = this->GetFileNames((*pathIt), fileName);
			StringVector::iterator fileNameIt;
			for(fileNameIt = fileNames->begin(); fileNameIt != fileNames->end(); fileNameIt++) {

				StringPair* filePath = new StringPair((*pathIt), (*fileNameIt));
				filePaths->push_back(filePath);

			}
			delete fileNames;
		}
	}
	delete paths;

	return filePaths;
}


StringVector* AbsFileFinder::GetPaths(ObjectEntity* path, BehaviorVector* behaviors) {
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//	Search the file system for paths that match the provided ObjectEntity 
	//  and behaviors. Return a vector of matching file paths. 
	//
	//  Step 1 - Get all paths that match the object entity
	//  Step 2 - Apply behaviors to all paths and add resulting paths to the set
	// -----------------------------------------------------------------------

	StringVector* paths = new StringVector();

	// does this path use variables?
	if(path->GetVarRef() == NULL) {
		
		// proceed based on operation
		if(path->GetOperation() == OvalEnum::EQUALS_OPERATION) {
			if(this->PathExists(path->GetValue())) {
				paths->push_back(path->GetValue());
			}

		} else if(path->GetOperation() == OvalEnum::NOT_EQUAL_OPERATION) {
			// turn the provided path value into a negative pattern match
			// then get all that match the pattern
			this->FindPaths(path->GetValue(), paths, false);

		} else if(path->GetOperation() == OvalEnum::PATTERN_MATCH_OPERATION) {
			this->FindPaths(path->GetValue(), paths);
		}		

	} else {

		StringVector* allPaths = new StringVector();

		if(path->GetOperation() == OvalEnum::EQUALS_OPERATION) {
			// in the case of equals simply loop through all the 
			// variable values and add them to the set of all paths
			// if they exist on the system
			VariableValueVector::iterator iterator;
			for(iterator = path->GetVarRef()->GetValues()->begin(); iterator != path->GetVarRef()->GetValues()->end(); iterator++) {				
				if(this->PathExists((*iterator)->GetValue())) {
					paths->push_back((*iterator)->GetValue());
				}
			}

		} else {
			// for not equals and pattern match fetch all paths that match
			// any of the variable values. Then analyze each path found on 
			// the system against the variable values 			

			// loop through all variable values and call findFiles
			VariableValueVector* values = path->GetVariableValues();
			VariableValueVector::iterator iterator;
			for(iterator = values->begin(); iterator != values->end(); iterator++) {
				if(path->GetOperation() == OvalEnum::NOT_EQUAL_OPERATION) {
					this->FindPaths((*iterator)->GetValue(), allPaths, false);
				} else {
					this->FindPaths((*iterator)->GetValue(), allPaths, true);
				}
			}
		}

		// only keep paths that match operation and value and var check
		ItemEntity* tmp = new ItemEntity("path","", OvalEnum::STRING_TYPE, true, OvalEnum::EXISTS_STATUS);
		StringVector::iterator it;
		for(it = allPaths->begin(); it != allPaths->end(); it++) {
			tmp->SetValue((*it));
			if(path->Analyze(tmp) == OvalEnum::TRUE_RESULT) {
				paths->push_back((*it));
			}
		}
		delete tmp;
		delete allPaths;
	}

	// Process the behaviors to identify any additional paths.
	// initialize these default values based on the defaults 
	// set in the oval definitions schema
	string recurseDirection = Behavior::GetBehaviorValue(behaviors, "recurse_direction");
	if(recurseDirection.compare("") == 0) {
		recurseDirection = "none";
	}

	string maxDepthStr = Behavior::GetBehaviorValue(behaviors, "max_depth");
	int maxDepth = -1;
	if(maxDepthStr.compare("") != 0) {
		maxDepth = atoi(maxDepthStr.c_str());
		if(maxDepth < -1) 
			maxDepth = -1;
	}

	// only need to address recurseDirection up & down if maxDepth is not 0
	StringVector* behaviorPaths = new StringVector();
	if(recurseDirection.compare("up") == 0 && maxDepth != 0) {
		StringVector::iterator path;
		for(path = paths->begin(); path != paths->end(); path++) {
			this->UpwardPathRecursion(behaviorPaths, (*path), maxDepth);
		}

	} else if(recurseDirection.compare("down") == 0 && maxDepth != 0) {
		StringVector::iterator path;
		for(path = paths->begin(); path != paths->end(); path++) {
			this->DownwardPathRecursion(behaviorPaths, (*path), maxDepth);
		}
	}

	// combine all the paths into a unique string vector
	StringVector* uniquePaths = new StringVector();
	UniqueStringVector* usv = new UniqueStringVector(uniquePaths);
	StringVector::iterator it;
	for(it = paths->begin(); it != paths->end(); it++) {
		usv->Append((*it));
	}
	delete paths;
	for(it = behaviorPaths->begin(); it != behaviorPaths->end(); it++) {
		usv->Append((*it));
	}
	delete behaviorPaths;
	delete usv;

	return uniquePaths;
}

void AbsFileFinder::DownwardPathRecursion(StringVector* paths, string path, int maxDepth) {

	if(maxDepth == 0) {
		return;
	} 
	if(maxDepth < -1) {
		throw FileFinderException("Error invalid max_depth. max depth must be -1 or more. Found: " + maxDepth);
	}

	// get child directories
	StringVector* childDirectories = this->GetChildDirectories(path);
	StringVector::iterator childDirectory;
	for(childDirectory = childDirectories->begin(); childDirectory != childDirectories->end(); childDirectory++) {

		// store child directory
		(*childDirectory)+=Common::fileSeperator;
		paths->push_back((*childDirectory));
		if(maxDepth == -1) {

			// make recursive call 
			this->DownwardPathRecursion(paths, (*childDirectory), maxDepth);

		} else if(maxDepth > 0) {

			// make recursive call
			this->DownwardPathRecursion(paths, (*childDirectory), --maxDepth);
		}
	}
	delete childDirectories;
}

void AbsFileFinder::UpwardPathRecursion(StringVector* paths, string path, int maxDepth) {
	
	if(maxDepth == 0) {
		return;
	} 
	if(maxDepth < -1) {
		throw FileFinderException("Error invalid max_depth. max depth must be -1 or more. Found: " + maxDepth);
	}

	// get parent directory by parsing the string to find the last file seperator

	// check if path is currently / or c:/ and stop 
	string fileSeperatorStr = "";
	fileSeperatorStr+=Common::fileSeperator;
	string regex = "^[A-Za-z]:\\\\$";
	if(path.compare(fileSeperatorStr) == 0 || this->fileMatcher->IsMatch(regex.c_str(), path.c_str())) {
		return;
	}
	
	// remove the trailing char if it is a file seperator
	if (path[path.length()-1] == Common::fileSeperator) {
		path = path.substr(0, (path.length()-1));
	} 

	// find the last file seperator
	basic_string <char>::size_type index = path.find_last_of(Common::fileSeperator);

	// if no path seperator is found stop recursing up.
	if(index == string::npos) {
		return;
	} 
	string parentDirectory = path.substr(0, index+1);

	// store parent directory
	paths->push_back(parentDirectory);
	if(maxDepth == -1) {

		// make recursive call 
		this->UpwardPathRecursion(paths, parentDirectory, maxDepth);

	} else if(maxDepth > 0) {

		// make recursive call
		this->UpwardPathRecursion(paths, parentDirectory, --maxDepth);
	}
}

StringVector* AbsFileFinder::GetFileNames(string path, ObjectEntity* fileName) {
	// -----------------------------------------------------------------------
	//
	//  ABSTRACT
	//
	//	Search the file system for files under the specified path that match the 
	//	provided ObjectEntity.
	//  Return a vector of matching file paths.  
	// -----------------------------------------------------------------------

	StringVector* fileNames = new StringVector();

	// does this fileName use variables?
	if(fileName->GetVarRef() == NULL) {
		
		// proceed based on operation
		if(fileName->GetOperation() == OvalEnum::EQUALS_OPERATION) {
			if(this->FileNameExists(path, fileName->GetValue())) {
				fileNames->push_back(fileName->GetValue());
			}

		} else if(fileName->GetOperation() == OvalEnum::NOT_EQUAL_OPERATION) {
			// turn the provided fileName value into a negative pattern match
			// then get all that match the pattern
			this->GetFilesForPattern(path, fileName->GetValue(), fileNames, false);

		} else if(fileName->GetOperation() == OvalEnum::PATTERN_MATCH_OPERATION) {
			this->GetFilesForPattern(path, fileName->GetValue(), fileNames);
		}		

	} else {

		StringVector* allFileNames = new StringVector();

		if(fileName->GetOperation() == OvalEnum::EQUALS_OPERATION) {
			// in the case of equals simply loop through all the 
			// variable values and add them to the set of all file names
			// if they exist on the system
			VariableValueVector::iterator iterator;
			for(iterator = fileName->GetVarRef()->GetValues()->begin(); iterator != fileName->GetVarRef()->GetValues()->end(); iterator++) {
				
				if(this->FileNameExists(path, (*iterator)->GetValue())) {
					fileNames->push_back((*iterator)->GetValue());
				}
			}

		} else {
	
			// for not equals and pattern match fetch all files that
			// match any of the variable values.		

			// loop through all variable values and call GetFilesForPattern
			VariableValueVector* values = fileName->GetVariableValues();
			VariableValueVector::iterator iterator;
			for(iterator = values->begin(); iterator != values->end(); iterator++) {
				if(fileName->GetOperation() == OvalEnum::NOT_EQUAL_OPERATION) {
					this->GetFilesForPattern(path, (*iterator)->GetValue(), allFileNames, false);
				} else {
					this->GetFilesForPattern(path, (*iterator)->GetValue(), allFileNames, true);
				}
			}
		}

		// only keep files that match operation and value and var check
		ItemEntity* tmp = new ItemEntity("filename","", OvalEnum::STRING_TYPE, true, OvalEnum::EXISTS_STATUS);
		StringVector::iterator it;
		for(it = allFileNames->begin(); it != allFileNames->end(); it++) {
			tmp->SetValue((*it));
			if(fileName->Analyze(tmp) == OvalEnum::TRUE_RESULT) {
				fileNames->push_back((*it));
			}
		}
		delete tmp;
		delete allFileNames;
	}

	return fileNames;
}

bool AbsFileFinder::IsMatch(string pattern, string value, bool isRegex) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return true if the specified value matches the provided pattern 
	//	based on the type of match specified. Match types are either 
	//	regex or not equal
	//
	// -----------------------------------------------------------------------

	bool match = false;

	if(isRegex) {
		if(this->fileMatcher->IsMatch(pattern.c_str(), value.c_str())) {
			match = true;
		}
	} else {	
		if(value.compare(pattern) != 0) {
			match = true;
		}
	}

	return match;
}

//****************************************************************************************//
//								FileFinderException Class								  //	
//****************************************************************************************//
FileFinderException::FileFinderException(string errMsgIn, int severity, Exception* ex) : Exception(errMsgIn, severity, ex) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Set the error message and then set the severity to ERROR_FATAL. This is 
	//	done with the explicit call to the Exception class constructor that 
	//	takes a single string param.
	//
	// -----------------------------------------------------------------------

}

FileFinderException::~FileFinderException() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Do nothing for now
	//
	// -----------------------------------------------------------------------

}
