//
// ZipArchive.cpp
//
// Library: Zip
// Package: Zip
// Module:	ZipArchive
//
// Copyright (c) 2007, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//


#include "Poco/Zip/ZipArchive.h"
#include "Poco/Zip/SkipCallback.h"
#include "Poco/Zip/ZipException.h"
#include "Poco/Exception.h"
#include <cstring>


namespace Poco {
namespace Zip {


const std::string ZipArchive::EMPTY_COMMENT;


ZipArchive::ZipArchive(std::istream& in):
	_entries(),
	_infos(),
	_disks(),
	_disks64()
{
	poco_assert_dbg (in);
	SkipCallback skip;
	parse(in, skip);
}


ZipArchive::ZipArchive(const FileHeaders& entries, const FileInfos& infos, const DirectoryInfos& dirs, const DirectoryInfos64& dirs64):
	_entries(entries),
	_infos(infos),
	_disks(dirs),
	_disks64(dirs64)
{
}


ZipArchive::ZipArchive(std::istream& in, ParseCallback& pc):
	_entries(),
	_infos(),
	_disks(),
	_disks64()
{
	poco_assert_dbg (in);
	parse(in, pc);
}


ZipArchive::~ZipArchive()
{
}


void ZipArchive::checkConsistency()
{
	for (const auto& [filename, dirHeader]: _infos)
	{
		Poco::UInt32 dirCRC = dirHeader.getCRC();
		Poco::UInt64 dirUncompressedSize = dirHeader.getUncompressedSize();

		const auto dataIt = _entries.find(filename);
		if (dataIt != _entries.end())
		{
			const ZipLocalFileHeader &dataHeader = dataIt->second;

			Poco::UInt32 dataCRC = dataHeader.getCRC();
			Poco::UInt64 dataUncompressedSize = dataHeader.getUncompressedSize();

			if (dataCRC != dirCRC)
				throw ZipException("CRC-32 mismatch: ", filename);

			if (dataUncompressedSize != dirUncompressedSize)
				throw ZipException("Uncompressed size mismatch: ", filename);
		}
	}

}


void ZipArchive::parse(std::istream& in, ParseCallback& pc)
{
	// read 4 bytes
	bool haveSynced = false;
	while (in.good() && !in.eof())
	{
		char header[ZipCommon::HEADER_SIZE]={'\x00', '\x00', '\x00', '\x00'};
		in.read(header, ZipCommon::HEADER_SIZE);
		if (in.eof())
			return;
		if (std::memcmp(header, ZipLocalFileHeader::HEADER, ZipCommon::HEADER_SIZE) == 0)
		{
			ZipLocalFileHeader entry(in, true, pc);
			std::string uniqueName = entry.getFileName();
			int suffix = 1;
			while (_entries.find(uniqueName) != _entries.end())
			{
				uniqueName = entry.getFileName() + ".duplicate-" + std::to_string(suffix++);
			}
			_entries.insert(std::make_pair(uniqueName, entry));
			haveSynced = false;
		}
		else if (std::memcmp(header, ZipFileInfo::HEADER, ZipCommon::HEADER_SIZE) == 0)
		{
			ZipFileInfo info(in, true);
			std::string uniqueName = info.getFileName();
			int suffix = 1;
			while (_infos.find(uniqueName) != _infos.end())
			{
				uniqueName = info.getFileName() + ".duplicate-" + std::to_string(suffix++);
			}
			FileHeaders::iterator it = _entries.find(info.getFileName());
			if (it != _entries.end())
			{
				it->second.setStartPos(info.getOffset());
			}
			_infos.insert(std::make_pair(uniqueName, info));
			haveSynced = false;
		}
		else if (std::memcmp(header, ZipArchiveInfo::HEADER, ZipCommon::HEADER_SIZE) == 0)
		{
			ZipArchiveInfo nfo(in, true);
			poco_assert (_disks.insert(std::make_pair(nfo.getDiskNumber(), nfo)).second);
			haveSynced = false;
		}
		else if (std::memcmp(header, ZipArchiveInfo64::HEADER, ZipCommon::HEADER_SIZE) == 0)
		{
			ZipArchiveInfo64 nfo(in, true);
			poco_assert (_disks64.insert(std::make_pair(nfo.getDiskNumber(), nfo)).second);
			haveSynced = false;
		}
		else
		{
			if (!haveSynced)
			{
				// Some Zip files have extra data behind the ZipLocalFileHeader.
				// Try to re-sync.
				ZipUtil::sync(in);
				haveSynced = true;
			}
			else
			{
				if (_disks.empty() && _disks64.empty())
					throw Poco::IllegalStateException("Illegal header in zip file");
				else
					throw Poco::IllegalStateException("Garbage after directory header");
			}
		}
	}
}


const std::string& ZipArchive::getZipComment() const
{
	// It seems that only the "first" disk is populated (look at Compress::close()), so getting the first ZipArchiveInfo
	DirectoryInfos::const_iterator it = _disks.begin();
	if (it != _disks.end())
	{
		return it->second.getZipComment();
	}
	else
	{
		DirectoryInfos64::const_iterator it64 = _disks64.begin();
		if (it64 != _disks64.end())
			return it->second.getZipComment();
		else
			return EMPTY_COMMENT;
	}
}


} } // namespace Poco::Zip
