/*
* Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/


#ifdef _MSC_VER
#pragma warning (disable: 4786)
#endif 

#include <iostream>
#include <sstream>    // for strstream, wistringstream
#include <stdio.h>
#include <sys/stat.h>
#include <algorithm>
#include <functional>
#include <set>


#include "dumpsis.h"
#include "siscontroller.h"
#include "exception.h"

#define KStubSisMsg "// *** Please add stub-SIS file entry here ***"
CDumpSis::CDumpSis(const std::wstring& aSISFileName, bool aVerbose, bool aCompatible)
		: 	iController(NULL),
			iSisFileName(aSISFileName),
			iVerbose(aVerbose),
			iCompatible(aCompatible),
			iIsStub(false)
	{
	bool isSiSFile = CSISContents::IsSisFile(aSISFileName);
	if(isSiSFile)
		{
		iSisContents.Load (iSisFileName);
		iController = &iSisContents.Controller();
		}
	else
		{
		iIsStub = true;
		iController = new CSISController;
		SISLogger::Log(L"Dumping Stub SIS Controller\n");
		iController->Load(iSisFileName);
		}
	}

CDumpSis::~CDumpSis()
	{
	if(iIsStub)
		{
		delete iController;
		}
	}

void CDumpSis::CreatePackage(const std::wstring& aPkgFileName)
	{
	std::wistringstream inStream (std::ios::in | std::ios::out);
	std::wostream outStream (inStream.rdbuf ());

	if(iCompatible)
		outStream << wchar_t(0xfeff);

	if(iIsStub)
		{
		iController->AddPackageEntry (outStream, iVerbose, iCompatible);
		}
	else
		{
		iSisContents.AddPackageEntry (outStream, iVerbose, iCompatible);
		}
	
	std::wstring str = inStream.str();
	if(!iCompatible)
		{
		std::string ns(str.begin(), str.end());
		WriteToFile(aPkgFileName, reinterpret_cast<const TUint8*>(ns.c_str()), ns.length());
		}
	else
	WriteToFile(aPkgFileName, reinterpret_cast<const TUint8*>(str.c_str()), str.length()*2);
	}

void CDumpSis::CreateIbyFile(const std::wstring& aIbyFileName)
	{
	std::wistringstream inStream (std::ios::in | std::ios::out);
	std::wostream outStream (inStream.rdbuf ());

	if(iCompatible)
		outStream << wchar_t(0xfeff);

	std::wstring tmp;
	int index = aIbyFileName.find_last_of(L"/");
	if(index!=-1)
		tmp = aIbyFileName.substr(index+1,aIbyFileName.length()-index-1);
	else
		tmp = aIbyFileName;
	
	index = tmp.find_last_of(L".");
	if(index!=-1)
		tmp = tmp.replace(index,1,L"_");

	std::transform(tmp.begin(), tmp.end(), tmp.begin(), towupper);

	outStream << L"#ifndef __" << tmp << L"__" << std::endl;
	outStream << L"#define __" << tmp << L"__" << std::endl << std::endl;
	if(iIsStub)
		{
		iController->AddIbyEntry (outStream, iVerbose, iCompatible);
		}
	else
		{
		iSisContents.AddIbyEntry (outStream, iVerbose, iCompatible);
		}
	
	outStream << std::endl << KStubSisMsg << std::endl;
	outStream << std::endl << L"#endif __" << tmp << L"__" << std::endl;

	std::wstring str = inStream.str();

	if(!iCompatible)
		{
		std::string ns(str.begin(), str.end());
		WriteToFile(aIbyFileName, reinterpret_cast<const TUint8*>(ns.c_str()), ns.length());
		}
	else
		WriteToFile(aIbyFileName, reinterpret_cast<const TUint8*>(str.c_str()), str.length()*2);

		
	}

void CDumpSis::CreatePackage(const CSISController& aSisController, const std::wstring& aPkgFileName)
	{
	std::wistringstream inStream (std::ios::in | std::ios::out);
	std::wostream outStream (inStream.rdbuf ());

	if(iCompatible)
		outStream << wchar_t(0xfeff);

	aSisController.AddPackageEntry (outStream, iVerbose, iCompatible);
	std::wstring str = inStream.str();

	if(!iCompatible)
		{
		std::string ns(str.begin(), str.end());
		WriteToFile(aPkgFileName, reinterpret_cast<const TUint8*>(ns.c_str()), ns.length());
		}
	else
		WriteToFile(aPkgFileName, reinterpret_cast<const TUint8*>(str.c_str()), str.length()*2);

	}
void CDumpSis::CreateIbyFile(const CSISController& aSisController, const std::wstring& aIbyFileName)
	{
	std::wistringstream inStream (std::ios::in | std::ios::out);
	std::wostream outStream (inStream.rdbuf ());

	if(iCompatible)
		outStream << wchar_t(0xfeff);

	std::wstring tmp;
	int index = aIbyFileName.find_last_of(L"/");
	if(index!=-1)
		tmp = aIbyFileName.substr(index+1,aIbyFileName.length()-index-1);
	else
		tmp = aIbyFileName;
	
	index = tmp.find_last_of(L".");
	if(index!=-1)
		tmp = tmp.replace(index,1,L"_");

	std::transform(tmp.begin(), tmp.end(), tmp.begin(), towupper);

	outStream << L"#ifndef __" << tmp << L"__" << std::endl;
	outStream << L"#define __" << tmp << L"__" << std::endl << std::endl;

	aSisController.AddIbyEntry (outStream, iVerbose, iCompatible);

	outStream << std::endl << KStubSisMsg << std::endl;
	outStream << std::endl << L"#endif __" << tmp << L"__" << std::endl;
	std::wstring str = inStream.str();

	if(!iCompatible)
		{
		std::string ns(str.begin(), str.end());
		WriteToFile(aIbyFileName, reinterpret_cast<const TUint8*>(ns.c_str()), ns.length());
		}
	else
		WriteToFile(aIbyFileName, reinterpret_cast<const TUint8*>(str.c_str()), str.length()*2);
	
	}

void CDumpSis::ExtractFiles(const std::wstring& aTargetDir, TExtractionLevel aLevel)
	{
	std::wstring targetDir = aTargetDir;
	CreateTargetDir(targetDir);
	
	switch(aLevel)
		{
		case EOnlyPackage:
			{
			ExtractBasePackageFile(targetDir);
			break;
			}
		case ECertificates:
			{
			ExtractCertificates(*iController, targetDir);
			break;
			}
		case EBaseFiles:
			{
			ExtractFiles(*iController, targetDir);
			break;
			}
		case EAllDataFiles:
			{
			ExtractBasePackageFile(targetDir);	
			ExtractFiles(*iController, targetDir);
			ExtractNestedSisFile(targetDir, *iController, false, 0, iSisContents.SisData().DataUnitCount());
			break;
			}
		case EAllButCerts:
			{
			ExtractBasePackageFile(targetDir);	
			ExtractFiles(*iController, targetDir);
			ExtractNestedSisFile(targetDir, *iController, true, 0, iSisContents.SisData().DataUnitCount());
			break;
			}
		case EEverything:
			{
			ExtractBasePackageFile(targetDir);	
			ExtractFiles(*iController, targetDir);
			ExtractNestedSisFile(targetDir, *iController, true, 0, iSisContents.SisData().DataUnitCount());
			ExtractAllCertificates(targetDir);
			break;
			}
		case EIbyFiles:
			{
			ExtractIBYFile(targetDir);
			ExtractBasePackageFile(targetDir);	
			ExtractFiles(*iController, targetDir);
			ExtractNestedSisFile(targetDir, *iController, true, 0, iSisContents.SisData().DataUnitCount());
			ExtractAllCertificates(targetDir);
			break;
			}
		}
	}

void CDumpSis::ExtractAllCertificates(const std::wstring& aTargetDir)
	{
	ExtractCertificates(*iController, aTargetDir);
	
	TControllerMap controllerList;
	iController->InstallBlock().GetEmbeddedControllers(controllerList);
	
	for (TControllerMapConstIter iter = controllerList.begin(); iter != controllerList.end(); ++iter)
		{
		wchar_t pathName[20];
		swprintf(pathName, 20, L"sis%d", iter->first);

		std::wstring extractionPath = aTargetDir + KSisDirectorySeparator + pathName;
		CreateTargetDir(extractionPath);
		ExtractCertificates(*iter->second, extractionPath);
		}	
	}
void CDumpSis::ExtractCertificates(const CSISController& aSisController, const std::wstring& aTargetDir)
	{
	TUint32 certChainCount = aSisController.SignatureCount();
	for(TUint32 i = 0; i < certChainCount; ++i)
		{
		wchar_t certName[30];
		swprintf(certName, 30, L"certChain%d%d.der", aSisController.DataIndex(), i+1);
		std::wstring certFullPath = aTargetDir + KSisDirectorySeparator + certName;
		const CCertChainData certChain = aSisController.SignatureCertChain(i).CertificateChain();
		const TUint8* certChainData = certChain.CertificateData().Data();
		TUint32 certSize =  certChain.CertificateData().Size();
		WriteToFile(certFullPath, certChainData, certSize);
		}
	}

void CDumpSis::ExtractIBYFile(const std::wstring& aTargetDir)
	{
	std::wstring ibyFileName = iSisFileName;
	ibyFileName = FixPathDelimiters(ibyFileName);
	SisFileNameToIbyFileName(ibyFileName);
	ibyFileName = aTargetDir + KSisDirectorySeparator + ibyFileName;
	CreateIbyFile(ibyFileName);
	}

void CDumpSis::ExtractBasePackageFile(const std::wstring& aTargetDir)
	{
	std::wstring pkgFileName = iSisFileName;
	pkgFileName = FixPathDelimiters(pkgFileName);
	SisFileNameToPkgFileName(pkgFileName);
	pkgFileName = aTargetDir + KSisDirectorySeparator + pkgFileName;
	CreatePackage(pkgFileName);
	}
void CDumpSis::ExtractFiles(const CSISController& aSisController, const std::wstring& aTargetDir, int aBaseIndex)
	{
	if(iIsStub)
		{
		// There is no data associated with stub sis file.
		return;
		}
	TFileDescList fileList;
	aSisController.InstallBlock().GetFileList(fileList);
	// Controller's index is relative to base controller's index.
	TUint32 dataIndex = aBaseIndex + aSisController.DataIndex();
	if(dataIndex >= iSisContents.SisData().DataUnitCount())
		{
		return;
		}
	const CSISDataUnit& dataUnit = iSisContents.SisData().DataUnit(dataIndex);
	
	const CSISLogo& logo = aSisController.Logo();
	if(!logo.WasteOfSpace())
		{
		const CSISFileDescription& logoFile = logo.FileDesc();
		const wchar_t* fileName = logoFile.GetFileName();
		std::wstring filePath = aTargetDir + KSisDirectorySeparator + fileName;
		const CSISFileData& fileData = dataUnit.FileData(logoFile.FileIndex());
		WriteToFile(filePath, fileData.Data(), fileData.UncompressedSize());
		delete[] const_cast<wchar_t*>(fileName);
		}
	
	
	if(dataUnit.FileCount() <= 0)
		{
		return; // No files present to extract.
		}

	for(int i = 0; i < fileList.size(); ++i)
		{
		const CSISFileDescription* fdesc = fileList[i];

		const CSISFileData& fileData = dataUnit.FileData(fdesc->FileIndex());

		if(!iCompatible)
			{
			std::wstring filePath = fdesc->Target().GetString();
			std::wstring fileNameOnly;
			int index = filePath.find_last_of(L"\\");
			if(index!=-1)
				{
				fileNameOnly = filePath.substr(index+1,filePath.length()-index-1);
				filePath = filePath.substr(0, index+1);
				index = filePath.find_first_of(L":");
				if(index!=-1)
					{
					filePath = filePath.substr(index+2,filePath.length()-index-2);
					}
				filePath = aTargetDir + KSisDirectorySeparator+filePath;
				CreateTargetDir(filePath);
				try
					{
					WriteToFile(filePath+fileNameOnly, fileData.Data(), fileData.UncompressedSize());
					}
					catch(...){}
				}
			}
		else
			{
			const wchar_t* fileName = fdesc->GetFileName();
			std::wstring filePath = aTargetDir + KSisDirectorySeparator + fileName;
			WriteToFile(filePath, fileData.Data(), fileData.UncompressedSize());
			delete[] const_cast<wchar_t*>(fileName);
			}
	
		}
	}

void CDumpSis::ExtractNestedSisFile(const std::wstring& aTargetDir, 
									const CSISController& aController,
									bool aExtractSis,
									int aStartIndex, 
									int aEndIndex)
	{
	TControllerMap controllerList;
	aController.InstallBlock().GetEmbeddedControllers(controllerList);
	
	for (TControllerMapConstIter iter = controllerList.begin(); iter != controllerList.end(); ++iter)
		{
		TControllerMapConstIter nextIter = iter;
		int curindex = iter->second->DataIndex();
		int maxDataUnit = 0;
		if (++nextIter != controllerList.end())
			{
			maxDataUnit = nextIter->first;
			}
		else
			{
			maxDataUnit = aEndIndex;
			}
		wchar_t pathName[20];
#ifdef _MSC_VER
		swprintf(pathName, L"sis%d", iter->second->GetControllerID());
#else
		swprintf(pathName, 20, L"sis%d", iter->second->GetControllerID());
#endif //_MSC_VER
		
		if(aExtractSis)
			{
			std::wstring sisPath = aTargetDir + KSisDirectorySeparator + pathName + L".sis";
			CSISController* ctl = const_cast<CSISController*>(iter->second);
			CreateEmbeddedSis(sisPath, *ctl, aStartIndex + iter->first, maxDataUnit);
			}

		std::wstring extractionPath = aTargetDir + KSisDirectorySeparator + pathName;
		CreateTargetDir(extractionPath);
		ExtractFiles(*iter->second, extractionPath, aStartIndex);
		std::wstring ibyExtractionPath = extractionPath;
		extractionPath = extractionPath + KSisDirectorySeparator + pathName + L".pkg";
		CreatePackage(*iter->second, extractionPath);
		ibyExtractionPath = ibyExtractionPath + KSisDirectorySeparator + pathName + L".iby";
		CreateIbyFile(*iter->second, ibyExtractionPath);
		ExtractNestedSisFile(aTargetDir, *(iter->second), aExtractSis, aStartIndex + iter->first, maxDataUnit);
		}
	}

void CDumpSis::CreateTargetDir(std::wstring& aTargetDir)
	{
	aTargetDir = FixPathDelimiters(aTargetDir);
	if(CreateDir(aTargetDir.c_str()) != 0)
		{
		return;
		}

	int err = GetErrorValue();
	
	switch (err)
		{
		case ERROR_ALREADY_EXISTS:
			{
			int attrs;

			if (0xFFFFFFFF != (attrs = FileAttributes(aTargetDir.c_str())))
				{
				if (!(attrs & FILE_ATTRIBUTE_DIRECTORY))
					{
					throw CSISException::EDirIsFile;
					}
				else if (attrs & FILE_ATTRIBUTE_READONLY)
					{
					throw CSISException::EPermissionDenied;
					}
				}
			else
				{
				throw CSISException::EInvalidDestination;
				}
			break;
			}
		case ERROR_PATH_NOT_FOUND:
			CreateDirectoriesRecursively(aTargetDir);
			break;

		case ERROR_ACCESS_DENIED:
			throw CSISException::EInvalidDestination;
			break;

		case ERROR_INVALID_NAME:
			aTargetDir = L".";
			break;

		default:
			throw CSISException::EInvalidDestination;
		}
	}


void CDumpSis::CreateDirectoriesRecursively(std::wstring aTargetDir)
	{
	int pos = aTargetDir.find(L'/');
	
	if(0 == pos)
		{
		// Path starting with "/" can be skipped
		pos = aTargetDir.find(L'/', 1);
		}
	
	while(pos != std::wstring::npos)
		{
		std::wstring path = aTargetDir.substr(0, pos);
		
		if (0 == CreateDir(path.c_str()))
			{
			int err = GetErrorValue();
			switch(err)
				{
				case ERROR_ACCESS_DENIED:
					{
					// If this is caused by a path like "D:" we can continue
					bool validPath = ((aTargetDir.size() >= 2) && (isalpha(aTargetDir[0])) && (aTargetDir[1] ==L':'));
					if (!validPath)
						{
						throw CSISException::EInvalidDestination;
						}
					break;
					}
	
				case ERROR_ALREADY_EXISTS:
					// NOOP
					break;
	
				default:
					throw CSISException::EInvalidDestination;
				}
			}
		pos = aTargetDir.find(L'/', pos+1);
		}

	if (0 == CreateDir(aTargetDir.c_str()))
		{
		if ( GetErrorValue() != ERROR_ALREADY_EXISTS)
			{
			throw CSISException::EInvalidDestination;
			}
		}
	}

void CDumpSis::SisFileNameToIbyFileName(std::wstring& aFileName)
	{
	int pos = aFileName.find_last_of(L'/');
	if(std::wstring::npos != pos)
		{
		aFileName = aFileName.substr(pos+1);
		}

	for(int i = 0; i < aFileName.size(); ++i)
		{
		aFileName[i] = tolower(aFileName[i]);
		}
	
	pos = aFileName.rfind(L".sis");
	if(std::wstring::npos == pos)
		{
		pos = aFileName.rfind(L".ctl");
		}

	aFileName = aFileName.substr(0, pos);
	aFileName.append(L".iby");
	}
void CDumpSis::SisFileNameToPkgFileName(std::wstring& aFileName)
	{
	int pos = aFileName.find_last_of(L'/');
	if(std::wstring::npos != pos)
		{
		aFileName = aFileName.substr(pos+1);
		}

	for(int i = 0; i < aFileName.size(); ++i)
		{
		aFileName[i] = tolower(aFileName[i]);
		}
	
	pos = aFileName.rfind(L".sis");
	if(std::wstring::npos == pos)
		{
		pos = aFileName.rfind(L".ctl");
		}

	aFileName = aFileName.substr(0, pos);
	aFileName.append(L".pkg");
	}

void CDumpSis::CreateEmbeddedSis(const std::wstring& aFileName, CSISController& aController, int aStart, int aEnd)
	{
	const CSISData& parentSisData = iSisContents.SisData();
	TUint32 dataIndex = aController.DataIndex();
	aController.SetDataIndex(0);
	CSISCompressed<CSISController> compressedController(aController);
	aController.SetDataIndex(dataIndex);

	CSISData sisData;
	for (int i = aStart; i < aEnd; ++i)
		{
		sisData.AddDataUnit(parentSisData.DataUnit(i));
		}
	
	CSISContents contents(	const_cast<CSISControllerChecksum&>(iSisContents.ControllerChecksum()),
							const_cast<CSISDataChecksum&>(iSisContents.DataChecksum()),
							compressedController,
							sisData
						 	);
	
	contents.Save(aFileName);
	}

void CDumpSis::GetCapVerifiedFileList(TFileCapTestList& aFileCapList)
	{
	TFileDescList fileList;
	iSisContents.Controller().InstallBlock().GetFileList(fileList);
	const CSISDataUnit& dataUnit = iSisContents.SisData().DataUnit(0);
	int fileCount = fileList.size();
	for(int i = 0; i < fileCount; ++i)
		{
		const CSISFileDescription* fdesc = fileList[i];
		const wchar_t* fileName = L"symbiantemp.xyz";
		const CSISFileData& fileData = dataUnit.FileData(fdesc->FileIndex());
		WriteToFile(fileName, fileData.Data(), fileData.UncompressedSize());
		CSISCapabilities capObj;
		capObj.ExtractCapabilities(fileName);
		TFileCapTest fileCapTest;
		fileCapTest.iFileDesc = fdesc;
		fileCapTest.iActualCap = capObj.Capabilities();
		fileCapTest.iCapVerified = (fileCapTest.iActualCap == fileList[i]->Capabilities())? true : false;
		aFileCapList.push_back(fileCapTest);
		MakeSISDeleteFile(fileName);
		}
	}


// End Of File
