/*
 * Copyright (c) 2006 Tom N Harris.
 *
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 *
 *     A copy of the GNU General Public License can be found at
 *     http://www.gnu.org/
 *
 * Written by Tom N Harris <telliamed@whoopdedo.org>.
 *
 */

#include "win32.h"
#undef GetFileAttributes
#include "Restore.h"

#include <string>

#include "zutil.h"

using namespace Setup;
using namespace zLib;
using namespace std;



Restore::Restore(String const& fname)
	: m_filename(fname), m_archive(NULL)
{
}

Restore::~Restore()
{
	Close();
}

void 
Restore::Close(void)
{
	if (m_archive)
	{
		m_archive->Close();
		delete m_archive;
	}
}

bool
Restore::CopyToDest(String const& dest)
{
	int err = 0;
	if (! m_archive)
	{
		if (! OpenArchive())
			return false;
	}

	String path = dest;
	replace(path.begin(), path.end(), TEXT('/'), TEXT('\\'));
	if (*path.end() != TEXT('\\'))
		path += TEXT('\\');

	err = m_archive->FirstFile();
	while (err == UNZ_OK)
	{
		UnzipFileToDest(path);
		err = m_archive->NextFile();
	}
	if (err != UNZ_END_OF_LIST_OF_FILE)
	{
		::SetLastError(zerr_to_win(err));
		return false;
	}
	return true;
}

bool
Restore::CopyFileToDest(String const& dest, String const& fname)
{
	int err = 0;
	if (! m_archive)
	{
		if (! OpenArchive())
			return false;
	}

	String path = dest;
	replace(path.begin(), path.end(), TEXT('/'), TEXT('\\'));
	if (*path.end() != TEXT('\\'))
		path += TEXT('\\');

	err = m_archive->LocateFile(fname.c_str(), UnzipArchive::DefaultCaseCompare);
	if (err == UNZ_END_OF_LIST_OF_FILE)
	{
		return false;
	}
	if (err != UNZ_OK)
	{
		::SetLastError(zerr_to_win(err));
		return false;
	}

	err = UnzipFileToDest(path);
	if (err != UNZ_OK)
	{
		::SetLastError(zerr_to_win(err));
		return false;
	}
	return true;
}

bool
Restore::OpenArchive(void)
{
	if (m_filename.empty())
	{
		::SetLastError(ERROR_INVALID_NAME);
		return false;
	}
	m_archive = new UnzipArchive();
	int err = m_archive->Open(m_filename.c_str());
	if (err != ZIP_OK)
	{
		delete m_archive;
		m_archive = NULL;
		::SetLastError(zerr_to_win(err));
		return false;
	}
	return true;
}

int
Restore::UnzipFileToDest(String const& dest)
{
	int err = m_archive->OpenFile();
	if (err != UNZ_OK)
		return err;

	unsigned long zflength = m_archive->GetFileNameLength();
	if (zflength <= 0)
	{
		m_archive->CloseFile();
		return UNZ_INTERNALERROR;
	}
	auto_ptr<char> zfname(new char[zflength+1]);
	err = m_archive->GetFileName(zfname.get(), zflength+1);
	
	String fname(zfname.get(), zflength);
	String path = dest + fname;
	
	FILETIME mtime, atime, ctime;
	SECURITY_ATTRIBUTES file_acl;
	LPSECURITY_ATTRIBUTES pacl = NULL;
	DWORD create_flags = m_archive->GetFileAttributes();

	UnzipFileTimes(&mtime, &atime, &ctime);

	if (Win32::IsNT())
	{
		unsigned long acl_length;
		file_acl.lpSecurityDescriptor = UnzipFileSD(&acl_length);
		if (file_acl.lpSecurityDescriptor != NULL)
		{
			file_acl.nLength = sizeof(SECURITY_ATTRIBUTES);
			file_acl.bInheritHandle = FALSE;
			pacl = &file_acl;
		}
		create_flags |= FILE_FLAG_BACKUP_SEMANTICS;
	}

	HANDLE fh = ::CreateFile(path.c_str(), GENERIC_WRITE, 0, pacl, 
			CREATE_ALWAYS, create_flags, NULL);
	if (fh == INVALID_HANDLE_VALUE)
	{
		m_archive->CloseFile();
		return UNZ_ERRNO;
	}

	char readbuf[1024];
	unsigned long nbytes = 0;
	while (1)
	{
		err = m_archive->Read(readbuf, 1024);
		if (err <= 0)
			break;
		nbytes = err;
		if (! ::WriteFile(fh, readbuf, nbytes, &nbytes, NULL))
		{
			err = UNZ_ERRNO;
			break;
		}
	}

	m_archive->CloseFile();

	SetFileInfo(fh, &mtime, &atime, &ctime);
	::CloseHandle(fh);

	if (err != UNZ_OK)
	{
		::SetLastError(zerr_to_win(err));
		return false;
	}
	return true;
}

void 
Restore::SetFileInfo(HANDLE fh, const LPFILETIME mt, const LPFILETIME at, const LPFILETIME ct)
{
	/*
	if (! ::SetFileAttributes(fname.c_str(), attr))
		return;
	*/
	::SetFileTime(fh, ct, at, mt);
}

void 
Restore::UnzipFileTimes(LPFILETIME mt, LPFILETIME at, LPFILETIME ct)
{
	ut_struct utblock;
	nt_time ntblock;
	memset(&ntblock, 0, sizeof(ntblock));
	memset(&utblock, 0, sizeof(utblock));

	int err = m_archive->GetExtraField(0x000A, reinterpret_cast<char*>(&ntblock), sizeof(ntblock), NULL, 0);
	if (err == UNZ_OK && ntblock.attr_tag == 1 && ntblock.attr_size == 24)
	{
		*mt = ntblock.mtime;
		*at = ntblock.atime;
		*ct = ntblock.ctime;
	}
	else
	{
		err = m_archive->GetExtraField(0x5455, reinterpret_cast<char*>(&utblock), sizeof(utblock), NULL, 0);
		if (err == UNZ_OK)
		{
			if (utblock.flags & 1)
				ft_from_unix(mt, utblock.mtime);
			else
				ft_from_dos(mt, m_archive->GetFileTime());
			if (utblock.flags & 2)
				ft_from_unix(at, utblock.atime);
			else
				*at = *mt;
			if (utblock.flags & 4)
				ft_from_unix(ct, utblock.ctime);
			else
				*ct = *mt;
		}
	}
}

void 
Restore::SetFileSD(String const& fname, char* psd)
{
	DWORD flags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
	::SetFileSecurity(fname.c_str(), flags, reinterpret_cast<PSECURITY_DESCRIPTOR>(psd));
}

char* 
Restore::UnzipFileSD(unsigned long *plength)
{
	if (! Win32::IsNT())
		return NULL;

	int err;

	unsigned int zlength = 0;
	err = m_archive->GetExtraFieldLength(0x4453, &zlength, NULL);
	if (err != UNZ_OK)
	{
		return NULL;
	}
	char* zdata = new char[zlength + sizeof(sd_struct)];
	sd_struct *sdblock = reinterpret_cast<sd_struct*>(zdata);
	
	err = m_archive->GetExtraField(0x4453, zdata, zlength + sizeof(sd_struct), NULL, 0);
	if (err != UNZ_OK)
	{
		delete[] zdata;
		return NULL;
	}

	if (sdblock->method != Z_DEFLATED)
	{
		delete[] zdata;
		return NULL;
	}

	unsigned long length = sdblock->sd_size;
	char* psd = new char[length];
	::uncompress(reinterpret_cast<Bytef*>(psd), &length, 
			    reinterpret_cast<Bytef*>(sdblock->sd), zlength);
	if (sdblock->crc != ::crc32(0, reinterpret_cast<Bytef*>(psd), length))
	{
		delete[] psd;
		psd = NULL;
		length = 0;
	}
	
	delete[] zdata;
	*plength = length;
	return psd;
}


