HLLib/HLLib/ZIPFile.cpp

609 lines
18 KiB
C++

/*
* HLLib
* Copyright (C) 2006-2012 Ryan Gregg
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later
* version.
*/
#include "HLLib.h"
#include "ZIPFile.h"
#include "Streams.h"
#include "Checksum.h"
#if USE_ZLIB
# ifdef _WIN32
# define ZLIB_WINAPI
# endif
# include <zlib.h>
struct OutDesc
{
Bytef *dest;
uLongf destLen;
};
static unsigned in(void FAR *in_desc, unsigned char FAR * FAR *input)
{
return 0;
}
static int out(void FAR *out_desc, unsigned char FAR *ouput, unsigned len)
{
if(len <= ((OutDesc*)(out_desc))->destLen)
{
memcpy(((OutDesc*)(out_desc))->dest, ouput, len);
((OutDesc*)(out_desc))->dest += len;
((OutDesc*)(out_desc))->destLen -= len;
return 0;
}
return 1;
}
static int uncompressBack(Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
{
z_stream stream;
int err;
stream.next_in = (Bytef*)source;
stream.avail_in = (uInt)sourceLen;
stream.next_out = dest;
stream.avail_out = (uInt)*destLen;
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
unsigned char window[32768];
err = inflateBackInit(&stream, 15, window);
if (err != Z_OK) return err;
OutDesc out_desc = { dest, *destLen };
err = inflateBack(&stream, in, 0, out, &out_desc);
if (err != Z_STREAM_END) {
inflateBackEnd(&stream);
if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
return Z_DATA_ERROR;
return err;
}
*destLen = *destLen - out_desc.destLen;
err = inflateBackEnd(&stream);
return err;
}
#endif
using namespace HLLib;
#define HL_ZIP_LOCAL_FILE_HEADER_SIGNATURE 0x04034b50
#define HL_ZIP_FILE_HEADER_SIGNATURE 0x02014b50
#define HL_ZIP_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE 0x06054b50
#define HL_ZIP_CHECKSUM_LENGTH 0x00008000
const char *CZIPFile::lpAttributeNames[] = { "Disk", "Comment" };
const char *CZIPFile::lpItemAttributeNames[] = { "Create Version", "Extract Version", "Flags", "Compression Method", "CRC", "Disk", "Comment" };
CZIPFile::CZIPFile() : CPackage(), pFileHeaderView(0), pEndOfCentralDirectoryRecordView(0), pEndOfCentralDirectoryRecord(0)
{
}
CZIPFile::~CZIPFile()
{
this->Close();
}
HLPackageType CZIPFile::GetType() const
{
return HL_PACKAGE_ZIP;
}
const hlChar *CZIPFile::GetExtension() const
{
return "zip";
}
const hlChar *CZIPFile::GetDescription() const
{
return "Zip File";
}
hlBool CZIPFile::MapDataStructures()
{
if(sizeof(ZIPEndOfCentralDirectoryRecord) > this->pMapping->GetMappingSize())
{
LastError.SetErrorMessage("Invalid file: the file map is too small for it's header.");
return hlFalse;
}
hlUInt uiTest;
hlULongLong uiOffset = 0, uiLength = this->pMapping->GetMappingSize();
while(uiOffset < uiLength - sizeof(uiTest))
{
Mapping::CView *pTestView = 0;
if(!this->pMapping->Map(pTestView, uiOffset, sizeof(hlUInt)))
{
return hlFalse;
}
uiTest = *(hlUInt *)pTestView->GetView();
this->pMapping->Unmap(pTestView);
switch(uiTest)
{
case HL_ZIP_END_OF_CENTRAL_DIRECTORY_RECORD_SIGNATURE:
{
if(!this->pMapping->Map(pTestView, uiOffset, sizeof(ZIPEndOfCentralDirectoryRecord)))
{
return hlFalse;
}
const ZIPEndOfCentralDirectoryRecord EndOfCentralDirRecord = *static_cast<const ZIPEndOfCentralDirectoryRecord *>(pTestView->GetView());
this->pMapping->Unmap(pTestView);
if(!this->pMapping->Map(this->pEndOfCentralDirectoryRecordView, uiOffset, sizeof(ZIPEndOfCentralDirectoryRecord) + EndOfCentralDirRecord.uiCommentLength))
{
return hlFalse;
}
this->pEndOfCentralDirectoryRecord = static_cast<const ZIPEndOfCentralDirectoryRecord *>(this->pEndOfCentralDirectoryRecordView->GetView());
if(!this->pMapping->Map(this->pFileHeaderView, this->pEndOfCentralDirectoryRecord->uiStartOfCentralDirOffset, this->pEndOfCentralDirectoryRecord->uiCentralDirectorySize))
{
return hlFalse;
}
return hlTrue;
}
case HL_ZIP_FILE_HEADER_SIGNATURE:
{
if(!this->pMapping->Map(pTestView, uiOffset, sizeof(ZIPFileHeader)))
{
return hlFalse;
}
const ZIPFileHeader FileHeader = *static_cast<const ZIPFileHeader *>(pTestView->GetView());
this->pMapping->Unmap(pTestView);
uiOffset += static_cast<hlULongLong>(sizeof(ZIPFileHeader) + FileHeader.uiFileNameLength + FileHeader.uiExtraFieldLength + FileHeader.uiFileCommentLength);
break;
}
case HL_ZIP_LOCAL_FILE_HEADER_SIGNATURE:
{
if(!this->pMapping->Map(pTestView, uiOffset, sizeof(ZIPLocalFileHeader)))
{
return hlFalse;
}
const ZIPLocalFileHeader LocalFileHeader = *static_cast<const ZIPLocalFileHeader *>(pTestView->GetView());
this->pMapping->Unmap(pTestView);
uiOffset += static_cast<hlULongLong>(sizeof(ZIPLocalFileHeader) + LocalFileHeader.uiFileNameLength + LocalFileHeader.uiExtraFieldLength + LocalFileHeader.uiCompressedSize);
break;
}
default:
{
LastError.SetErrorMessageFormated("Invalid file: unknown section signature %#.8x.", uiTest);
return hlFalse;
}
}
}
LastError.SetErrorMessage("Invalid file: unexpected end of file while scanning for end of central directory record.");
return hlFalse;
}
hlVoid CZIPFile::UnmapDataStructures()
{
this->pMapping->Unmap(this->pFileHeaderView);
this->pEndOfCentralDirectoryRecord = 0;
this->pMapping->Unmap(this->pEndOfCentralDirectoryRecordView);
}
CDirectoryFolder *CZIPFile::CreateRoot()
{
CDirectoryFolder *pRoot = new CDirectoryFolder(this);
hlUInt uiTest, uiOffset = 0;
while(uiOffset < this->pEndOfCentralDirectoryRecord->uiCentralDirectorySize - sizeof(uiTest))
{
uiTest = *(hlUInt *)((hlByte *)this->pFileHeaderView->GetView() + uiOffset);
switch(uiTest)
{
case HL_ZIP_FILE_HEADER_SIGNATURE:
{
ZIPFileHeader *pFileHeader = static_cast<ZIPFileHeader *>((hlVoid *)((hlByte *)this->pFileHeaderView->GetView() + uiOffset));
hlChar *lpFileName = new hlChar[pFileHeader->uiFileNameLength + 1];
memcpy(lpFileName, (hlByte *)pFileHeader + sizeof(ZIPFileHeader), pFileHeader->uiFileNameLength);
lpFileName[pFileHeader->uiFileNameLength] = '\0';
// Check if we have just a file, or if the file has directories we need to create.
if(strchr(lpFileName, '/') == 0 && strchr(lpFileName, '\\') == 0)
{
pRoot->AddFile(lpFileName, HL_ID_INVALID, pFileHeader);
}
else
{
// Tokenize the file path and create the directories.
CDirectoryFolder *pInsertFolder = pRoot;
hlChar lpTemp[256] = "";
hlChar *lpToken = strtok(lpFileName, "/\\");
while(lpToken != 0)
{
strcpy(lpTemp, lpToken);
lpToken = strtok(0, "/\\");
if(lpToken != 0)
{
// Check if the directory exists.
CDirectoryItem *pItem = pInsertFolder->GetItem(lpTemp);
if(pItem == 0 || pItem->GetType() == HL_ITEM_FILE)
{
// It doesn't, create it.
pInsertFolder = pInsertFolder->AddFolder(lpTemp);
}
else
{
// It does, use it.
pInsertFolder = static_cast<CDirectoryFolder *>(pItem);
}
}
}
// The file name is the last token, add it.
pInsertFolder->AddFile(lpTemp, HL_ID_INVALID, pFileHeader);
}
delete []lpFileName;
uiOffset += sizeof(ZIPFileHeader) + pFileHeader->uiFileNameLength + pFileHeader->uiExtraFieldLength + pFileHeader->uiFileCommentLength;
break;
}
default:
{
uiOffset = this->pEndOfCentralDirectoryRecord->uiCentralDirectorySize;
break;
}
}
}
return pRoot;
}
hlUInt CZIPFile::GetAttributeCountInternal() const
{
return HL_ZIP_PACKAGE_COUNT;
}
const hlChar *CZIPFile::GetAttributeNameInternal(HLPackageAttribute eAttribute) const
{
if(eAttribute < HL_ZIP_PACKAGE_COUNT)
{
return this->lpAttributeNames[eAttribute];
}
return 0;
}
hlBool CZIPFile::GetAttributeInternal(HLPackageAttribute eAttribute, HLAttribute &Attribute) const
{
switch(eAttribute)
{
case HL_ZIP_PACKAGE_DISK:
{
hlAttributeSetUnsignedInteger(&Attribute, this->lpAttributeNames[eAttribute], this->pEndOfCentralDirectoryRecord->uiNumberOfThisDisk, hlFalse);
return hlTrue;
}
case HL_ZIP_PACKAGE_COMMENT:
{
hlChar *lpComment = new hlChar[this->pEndOfCentralDirectoryRecord->uiCommentLength + 1];
memcpy(lpComment, (hlByte *)this->pEndOfCentralDirectoryRecord + sizeof(ZIPEndOfCentralDirectoryRecord), this->pEndOfCentralDirectoryRecord->uiCommentLength);
lpComment[this->pEndOfCentralDirectoryRecord->uiCommentLength] = '\0';
hlAttributeSetString(&Attribute, this->lpAttributeNames[eAttribute], lpComment);
delete []lpComment;
return hlTrue;
}
default:
{
return hlFalse;
}
}
}
hlUInt CZIPFile::GetItemAttributeCountInternal() const
{
return HL_ZIP_ITEM_COUNT;
}
const hlChar *CZIPFile::GetItemAttributeNameInternal(HLPackageAttribute eAttribute) const
{
if(eAttribute < HL_ZIP_ITEM_COUNT)
{
return this->lpItemAttributeNames[eAttribute];
}
return 0;
}
hlBool CZIPFile::GetItemAttributeInternal(const CDirectoryItem *pItem, HLPackageAttribute eAttribute, HLAttribute &Attribute) const
{
switch(pItem->GetType())
{
case HL_ITEM_FILE:
{
const CDirectoryFile *pFile = static_cast<const CDirectoryFile *>(pItem);
const ZIPFileHeader *pDirectoryItem = static_cast<const ZIPFileHeader *>(pFile->GetData());
switch(eAttribute)
{
case HL_ZIP_ITEM_CREATE_VERSION:
{
hlAttributeSetUnsignedInteger(&Attribute, this->lpItemAttributeNames[eAttribute], pDirectoryItem->uiVersionMadeBy, hlFalse);
return hlTrue;
}
case HL_ZIP_ITEM_EXTRACT_VERSION:
{
hlAttributeSetUnsignedInteger(&Attribute, this->lpItemAttributeNames[eAttribute], pDirectoryItem->uiVersionNeededToExtract, hlFalse);
return hlTrue;
}
case HL_ZIP_ITEM_FLAGS:
{
hlAttributeSetUnsignedInteger(&Attribute, this->lpItemAttributeNames[eAttribute], pDirectoryItem->uiFlags, hlTrue);
return hlTrue;
}
case HL_ZIP_ITEM_COMPRESSION_METHOD:
{
hlAttributeSetUnsignedInteger(&Attribute, this->lpItemAttributeNames[eAttribute], pDirectoryItem->uiCompressionMethod, hlTrue);
return hlTrue;
}
case HL_ZIP_ITEM_CRC:
{
hlAttributeSetUnsignedInteger(&Attribute, this->lpItemAttributeNames[eAttribute], pDirectoryItem->uiCRC32, hlTrue);
return hlTrue;
}
case HL_ZIP_ITEM_DISK:
{
hlAttributeSetUnsignedInteger(&Attribute, this->lpItemAttributeNames[eAttribute], pDirectoryItem->uiDiskNumberStart, hlFalse);
return hlTrue;
}
case HL_ZIP_ITEM_COMMENT:
{
hlChar *lpComment = new hlChar[pDirectoryItem->uiFileCommentLength + 1];
memcpy(lpComment, (hlByte *)pDirectoryItem + sizeof(ZIPFileHeader) + pDirectoryItem->uiFileNameLength + pDirectoryItem->uiExtraFieldLength, pDirectoryItem->uiFileCommentLength);
lpComment[pDirectoryItem->uiFileCommentLength] = '\0';
hlAttributeSetString(&Attribute, this->lpItemAttributeNames[eAttribute], lpComment);
delete []lpComment;
return hlTrue;
}
default:
break;
}
break;
}
default:
break;
}
return hlFalse;
}
hlBool CZIPFile::GetFileExtractableInternal(const CDirectoryFile *pFile, hlBool &bExtractable) const
{
const ZIPFileHeader *pDirectoryItem = static_cast<const ZIPFileHeader *>(pFile->GetData());
switch(pDirectoryItem->uiCompressionMethod)
{
case 0: // None.
#if USE_ZLIB
case 8: // Deflate.
#endif
bExtractable = (pDirectoryItem->uiFlags & 0x01u) == 0 && pDirectoryItem->uiDiskNumberStart == this->pEndOfCentralDirectoryRecord->uiNumberOfThisDisk;
break;
default:
bExtractable = hlFalse;
break;
}
return hlTrue;
}
hlBool CZIPFile::GetFileValidationInternal(const CDirectoryFile *pFile, HLValidation &eValidation) const
{
const ZIPFileHeader *pDirectoryItem = static_cast<const ZIPFileHeader *>(pFile->GetData());
hlBool bExtractable = false;
if(!GetFileExtractableInternal(pFile, bExtractable) || !bExtractable)
{
eValidation = HL_VALIDATES_ASSUMED_OK;
return hlTrue;
}
hlULong uiChecksum = 0;
Streams::IStream *pStream = 0;
if(const_cast<CZIPFile *>(this)->CreateStreamInternal(pFile, pStream))
{
if(pStream->Open(HL_MODE_READ))
{
hlULongLong uiTotalBytes = 0, uiFileBytes = pStream->GetStreamSize();
hlUInt uiBufferSize;
hlByte lpBuffer[HL_ZIP_CHECKSUM_LENGTH];
hlBool bCancel = hlFalse;
hlValidateFileProgress(const_cast<CDirectoryFile *>(pFile), uiTotalBytes, uiFileBytes, &bCancel);
while((uiBufferSize = pStream->Read(lpBuffer, sizeof(lpBuffer))) != 0)
{
if(bCancel)
{
eValidation = HL_VALIDATES_CANCELED;
break;
}
uiChecksum = CRC32(lpBuffer, uiBufferSize, uiChecksum);
uiTotalBytes += static_cast<hlULongLong>(uiBufferSize);
hlValidateFileProgress(const_cast<CDirectoryFile *>(pFile), uiTotalBytes, uiFileBytes, &bCancel);
}
pStream->Close();
}
const_cast<CZIPFile *>(this)->ReleaseStreamInternal(*pStream);
delete pStream;
}
if(eValidation == HL_VALIDATES_ASSUMED_OK)
{
eValidation = (hlULong)pDirectoryItem->uiCRC32 == uiChecksum ? HL_VALIDATES_OK : HL_VALIDATES_CORRUPT;
}
return hlTrue;
}
hlBool CZIPFile::GetFileSizeInternal(const CDirectoryFile *pFile, hlUInt &uiSize) const
{
const ZIPFileHeader *pDirectoryItem = static_cast<const ZIPFileHeader *>(pFile->GetData());
uiSize = pDirectoryItem->uiUncompressedSize;
return hlTrue;
}
hlBool CZIPFile::GetFileSizeOnDiskInternal(const CDirectoryFile *pFile, hlUInt &uiSize) const
{
const ZIPFileHeader *pDirectoryItem = static_cast<const ZIPFileHeader *>(pFile->GetData());
uiSize = pDirectoryItem->uiCompressedSize;
return hlTrue;
}
hlBool CZIPFile::CreateStreamInternal(const CDirectoryFile *pFile, Streams::IStream *&pStream) const
{
const ZIPFileHeader *pDirectoryItem = static_cast<const ZIPFileHeader *>(pFile->GetData());
switch(pDirectoryItem->uiCompressionMethod)
{
case 0: // None.
#if USE_ZLIB
case 8: // Deflate.
#endif
break;
default:
LastError.SetErrorMessageFormated("Compression format %#.2x not supported.", pDirectoryItem->uiCompressionMethod);
return hlFalse;
}
if((pDirectoryItem->uiFlags & 0x01u) != 0)
{
LastError.SetErrorMessageFormated("File is encrypted.");
return hlFalse;
}
if(pDirectoryItem->uiDiskNumberStart != this->pEndOfCentralDirectoryRecord->uiNumberOfThisDisk)
{
LastError.SetErrorMessageFormated("File resides on disk %u.", pDirectoryItem->uiDiskNumberStart);
return hlFalse;
}
Mapping::CView *pDirectoryEnrtyView = 0;
if(!this->pMapping->Map(pDirectoryEnrtyView, pDirectoryItem->uiRelativeOffsetOfLocalHeader, sizeof(ZIPLocalFileHeader)))
{
return hlFalse;
}
ZIPLocalFileHeader DirectoryEntry = *static_cast<const ZIPLocalFileHeader *>(pDirectoryEnrtyView->GetView());
this->pMapping->Unmap(pDirectoryEnrtyView);
if((DirectoryEntry.uiFlags & 0x08u) != 0)
{
DirectoryEntry.uiCRC32 = pDirectoryItem->uiCRC32;
DirectoryEntry.uiUncompressedSize = pDirectoryItem->uiUncompressedSize;
DirectoryEntry.uiCompressedSize = pDirectoryItem->uiCompressedSize;
}
if(DirectoryEntry.uiSignature != HL_ZIP_LOCAL_FILE_HEADER_SIGNATURE)
{
LastError.SetErrorMessageFormated("Invalid file data offset.");
return hlFalse;
}
switch(pDirectoryItem->uiCompressionMethod)
{
case 0: // None.
{
pStream = new Streams::CMappingStream(*this->pMapping, pDirectoryItem->uiRelativeOffsetOfLocalHeader + sizeof(ZIPLocalFileHeader) + DirectoryEntry.uiFileNameLength + DirectoryEntry.uiExtraFieldLength, DirectoryEntry.uiUncompressedSize);
return hlTrue;
}
#if USE_ZLIB
case 8: // Deflate.
{
Mapping::CView *pFileDataView = 0;
if(this->pMapping->Map(pFileDataView, pDirectoryItem->uiRelativeOffsetOfLocalHeader + sizeof(ZIPLocalFileHeader) + DirectoryEntry.uiFileNameLength + DirectoryEntry.uiExtraFieldLength, DirectoryEntry.uiCompressedSize))
{
hlBool bResult = hlFalse;
hlByte *lpInflateBuffer = new hlByte[DirectoryEntry.uiUncompressedSize];
uLongf iInflateSize = DirectoryEntry.uiUncompressedSize;
switch(uncompressBack(lpInflateBuffer, &iInflateSize, static_cast<const hlByte *>(pFileDataView->GetView()), (uLong)DirectoryEntry.uiCompressedSize))
{
case Z_OK:
pStream = new Streams::CMemoryStream(lpInflateBuffer, iInflateSize);
bResult = hlTrue;
break;
case Z_MEM_ERROR:
delete []lpInflateBuffer;
LastError.SetErrorMessage("Deflate Error: Z_MEM_ERROR.");
break;
case Z_BUF_ERROR:
delete []lpInflateBuffer;
LastError.SetErrorMessage("Deflate Error: Z_BUF_ERROR.");
break;
case Z_DATA_ERROR:
delete []lpInflateBuffer;
LastError.SetErrorMessage("Deflate Error: Z_DATA_ERROR.");
break;
default:
delete []lpInflateBuffer;
LastError.SetErrorMessage("Deflate Error: Unknown.");
break;
}
this->pMapping->Unmap(pFileDataView);
return bResult;
}
return hlFalse;
}
#endif
default:
return hlFalse;
}
}
hlVoid CZIPFile::ReleaseStreamInternal(Streams::IStream &Stream) const
{
if(Stream.GetType() == HL_STREAM_MEMORY)
{
delete []static_cast<const hlByte *>(static_cast<Streams::CMemoryStream &>(Stream).GetBuffer());
}
}