/*
* Copyright (c) 2008-2010 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"
#include "utf8.h"

CDumpSis::CDumpSis(const std::wstring& aSISFileName, bool aVerbose)
		: 	iController(NULL),
			iSisFileName(aSISFileName),
			iVerbose(aVerbose),
			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 ());

	outStream << wchar_t(0xfeff);

	if(iIsStub)
		{
		iController->AddPackageEntry (outStream, iVerbose);
		}
	else
		{
		iSisContents.AddPackageEntry (outStream, iVerbose);
		}
	
	std::wstring str = inStream.str();
#ifndef __TOOLS2_LINUX__
	WriteToFile(aPkgFileName, reinterpret_cast<const TUint8*>(str.c_str()), str.length()*2);
#else
	TUint32 size = str.size();
	const wchar_t * source = str.c_str();

	unsigned short int* buffer = new unsigned short int[size*2];

	// Using a temp variable in place of buffer as ConvertUTF32toUTF16 modifies the source pointer passed.
	unsigned short int* temp = buffer;

	//Converting to UTF-16 because makesis do not support UTF-32.	
	ConvertUTF32toUTF16(&source, source + size, &temp,  temp + (size*2), lenientConversion);

	TUint32 buflen = temp-buffer;
	// Appending NULL to the converted buffer.
	*temp = NULL;

	WriteToFile(aPkgFileName, reinterpret_cast<const TUint8*>(buffer), buflen*2);
	delete[] buffer;
#endif
	}

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

	outStream << wchar_t(0xfeff);
	aSisController.AddPackageEntry (outStream, iVerbose);
	std::wstring str = inStream.str();
#ifndef __TOOLS2_LINUX__
	WriteToFile(aPkgFileName, reinterpret_cast<const TUint8*>(str.c_str()), str.length()*2);
#else
	TUint32 size = str.size();
	const wchar_t * source = str.c_str();
	
	unsigned short int* buffer = new unsigned short int[size*2];
	
	// Using a temp variable in place of buffer as ConvertUTF32toUTF16 modifies the source pointer passed.
	unsigned short int* temp = buffer;
	
	//Converting to UTF-16 because makesis do not support UTF-32.	
	ConvertUTF32toUTF16(&source, source + size, &temp,	temp + (size*2), lenientConversion);
	
	TUint32 buflen = temp-buffer;
	// Appending NULL to the converted buffer.
	*temp = NULL;
	
	WriteToFile(aPkgFileName, reinterpret_cast<const TUint8*>(buffer), buflen*2);
	delete[] buffer;
#endif
	}

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;
			}
		}
	}

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::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 wchar_t* fileName = fdesc->GetFileName();
		std::wstring filePath = aTargetDir + KSisDirectorySeparator + fileName;
		const CSISFileData& fileData = dataUnit.FileData(fdesc->FileIndex());
		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);
		extractionPath = extractionPath + KSisDirectorySeparator + pathName + L".pkg";
		CreatePackage(*iter->second, extractionPath);
		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::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
