//////////////////////////////////////////////////////////////////////////////// // This source file is part of the ZipArchive library source distribution and // is Copyrighted 2000 - 2011 by Artpol Software - Tadeusz Dracz // // 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. // // For the licensing details refer to the License.txt file. // // Web Site: http://www.artpol-software.com //////////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "ZipStorage.h" #include "ZipArchive.h" #include "ZipPlatform.h" char CZipStorage::m_gszExtHeaderSignat[] = {0x50, 0x4b, 0x07, 0x08}; const ZIP_FILE_USIZE CZipStorage::SignatureNotFound = ZIP_FILE_USIZE(-1); #define SIGNATURE_SIZE 4 using namespace ZipArchiveLib; CZipStorage::CZipStorage() { Initialize(); } void CZipStorage::Initialize() { m_pSplitChangeVolumeFunc = m_pSpanChangeVolumeFunc = m_pChangeVolumeFunc = NULL; m_iWriteBufferSize = 65536; m_pFile = NULL; m_iLocateBufferSize = 32768; m_uBytesBeforeZip = 0; m_uCurrentVolume = ZIP_VOLUME_NUMBER_UNSPECIFIED; m_szArchiveName.Empty(); m_pSplitNames = NULL; m_pCachedSizes = NULL; m_bAutoDeleteSplitNames = false; m_state = 0; } CZipStorage::~CZipStorage() { ClearSplitNames(); ClearCachedSizes(); } DWORD CZipStorage::Read(void *pBuf, DWORD iSize, bool bAtOnce) { if (iSize == 0) return 0; DWORD iRead; for(;;) { iRead = m_pFile->Read(pBuf, iSize); if (!iRead) { if (IsSegmented()) ChangeVolume(); else ThrowError(CZipException::badZipFile); } else break; } if (iRead == iSize) return iRead; else if ((bAtOnce && !IsBinarySplit()) || !IsSegmented()) ThrowError(CZipException::badZipFile); while (iRead < iSize) { ChangeVolume(); UINT iNewRead = m_pFile->Read((char*)pBuf + iRead, iSize - iRead); if (!iNewRead && iRead < iSize) ThrowError(CZipException::badZipFile); iRead += iNewRead; } return iRead; } void CZipStorage::Open(LPCTSTR lpszPathName, int iMode, ZIP_SIZE_TYPE uVolumeSize) { m_uCurrentVolume = ZIP_VOLUME_NUMBER_UNSPECIFIED; m_pWriteBuffer.Allocate(m_iWriteBufferSize); m_uBytesInWriteBuffer = 0; m_state.Set(stateOpened | stateAutoClose); m_pFile = &m_internalfile; m_szArchiveName = lpszPathName; m_pChangeVolumeFunc = NULL; CBitFlag mode(iMode); if (mode.IsSetAny(CZipArchive::zipCreate)) // create new archive { m_uCurrentVolume = 0; if (!mode.IsSetAny(CZipArchive::zipModeSegmented)) { OpenFile(lpszPathName, (mode.IsSetAll(CZipArchive::zipCreateAppend) ? CZipFile::modeNoTruncate : CZipFile::modeCreate) | CZipFile::modeReadWrite); } else // create a segmented archive { m_uBytesWritten = 0; if (mode.IsSetAny(CZipArchive::zipModeSpan)) { if (!m_pSpanChangeVolumeFunc) ThrowError(CZipException::noCallback); if (!ZipPlatform::IsDriveRemovable(lpszPathName)) ThrowError(CZipException::nonRemovable); m_state.Set(stateSpan); m_pChangeVolumeFunc = m_pSpanChangeVolumeFunc; } else if (uVolumeSize <= 0) ThrowError(CZipException::noVolumeSize); else { m_uSplitData = uVolumeSize; if (mode.IsSetAll(CZipArchive::zipModeBinSplit)) { m_state.Set(stateBinarySplit); ClearCachedSizes(); // just in case m_pCachedSizes = new CZipArray(); } else m_state.Set(stateSplit); EnsureSplitNames(); m_pChangeVolumeFunc = m_pSplitChangeVolumeFunc; } NextVolume(4); Write(m_gszExtHeaderSignat, 4, true); } } else // open existing { m_state.Set(stateExisting); bool readOnly = mode.IsSetAll(CZipArchive::zipOpenReadOnly); if (readOnly) m_state.Set(stateReadOnly); OpenFile(lpszPathName, CZipFile::modeNoTruncate | (readOnly ? CZipFile::modeRead : CZipFile::modeReadWrite)); // this will be revised in UpdateSegmMode if (mode.IsSetAny(CZipArchive::zipModeSpan)) m_state.Set(stateSpan); else if (mode.IsSetAll(CZipArchive::zipModeBinSplit)) { m_state.Set(stateBinarySplit); EnsureSplitNames(); m_uCurrentVolume = m_pSplitNames->GetVolumeNumber(m_szArchiveName); if (m_uCurrentVolume == 0) ThrowError(CZipException::badZipFile); m_uCurrentVolume -= 1; if (m_uCurrentVolume > 0) { m_uSplitData = m_uCurrentVolume; CacheSizes(); } else { ClearSplitNames(); m_state.Clear(stateBinarySplit); } } else if (mode.IsSetAny(CZipArchive::zipModeSplit)) m_state.Set(stateSplit); } } void CZipStorage::CacheSizes() { ClearCachedSizes(); // just in case m_pCachedSizes = new CZipArray(); m_pCachedSizes->SetSize((ZIP_ARRAY_SIZE_TYPE)(m_uCurrentVolume + 1)); ZIP_VOLUME_TYPE lastVolume = m_uCurrentVolume; for(;;) { m_pCachedSizes->SetAt((ZIP_ARRAY_SIZE_TYPE)m_uCurrentVolume, m_pFile->GetLength()); if (m_uCurrentVolume == 0) break; ChangeVolume((ZIP_VOLUME_TYPE)(m_uCurrentVolume - 1)); } ChangeVolume(lastVolume); } void CZipStorage::Open(CZipAbstractFile& af, int iMode, bool bAutoClose) { m_pWriteBuffer.Allocate(m_iWriteBufferSize); m_uBytesInWriteBuffer = 0; m_state.Set(stateOpened); if (bAutoClose) m_state.Set(stateAutoClose); m_pFile = ⁡ CBitFlag mode(iMode); if (mode.IsSetAny(CZipArchive::zipCreate)) { m_uCurrentVolume = 0; if (mode.IsSetAll(CZipArchive::zipCreateAppend)) af.SeekToEnd(); else af.SetLength(0); } else // open existing { m_state.Set(stateExisting); if (mode.IsSetAll(CZipArchive::zipOpenReadOnly)) m_state.Set(stateReadOnly); af.SeekToBegin(); } } void CZipStorage::ChangeVolume(ZIP_VOLUME_TYPE uNumber) { if (uNumber == m_uCurrentVolume || !IsSegmented()) // the second condition may happen in some bad archives return; m_uCurrentVolume = uNumber; OpenFile(IsSpanned() ? ChangeSpannedRead() : ChangeSplitRead(), CZipFile::modeNoTruncate | CZipFile::modeRead); } void CZipStorage::ThrowError(int err) const { CZipException::Throw(err, m_pFile->GetFilePath()); } bool CZipStorage::OpenFile(LPCTSTR lpszName, UINT uFlags, bool bThrow) { return m_pFile->Open(lpszName, uFlags | CZipFile::shareDenyWrite, bThrow); } CZipString CZipStorage::ChangeSpannedRead() { CZipString szTemp = m_pFile->GetFilePath(); m_pFile->Close(); CallCallback(0, CZipSegmCallback::scVolumeNeededForRead, szTemp); return m_pChangeVolumeFunc->m_szExternalFile; } CZipString CZipStorage::ChangeSplitRead() { bool lastPart = (ZIP_SIZE_TYPE)m_uCurrentVolume == m_uSplitData; CZipString szVolumeName = GetSplitVolumeName(lastPart); if (m_pChangeVolumeFunc) { int iCode = CZipSegmCallback::scVolumeNeededForRead; for(;;) { CallCallback(lastPart ? ZIP_SPLIT_LAST_VOLUME : 0, iCode, szVolumeName); if (ZipPlatform::FileExists(m_pChangeVolumeFunc->m_szExternalFile)) { szVolumeName = m_pChangeVolumeFunc->m_szExternalFile; break; } else iCode = CZipSegmCallback::scFileNotFound; } } m_pFile->Close(); return szVolumeName; } CZipString CZipStorage::RenameLastFileInSplitArchive() { ASSERT(IsSplit()); // give to the last volume the zip extension CZipString szFileName = m_pFile->GetFilePath(); CZipString szNewFileName = GetSplitVolumeName(true); if (m_pChangeVolumeFunc) { int code = CZipSegmCallback::scVolumeNeededForWrite; for(;;) { CallCallback(ZIP_SPLIT_LAST_VOLUME, code, szNewFileName); szNewFileName = m_pChangeVolumeFunc->m_szExternalFile; if (ZipPlatform::FileExists(szNewFileName)) code = CZipSegmCallback::scFileNameDuplicated; else break; } } m_pFile->Flush(); m_pFile->Close(); ZIPSTRINGCOMPARE pCompare = GetCZipStrCompFunc(ZipPlatform::GetSystemCaseSensitivity()); if ((szFileName.*(pCompare))(szNewFileName) == 0) { return szNewFileName; } if (!m_pChangeVolumeFunc && ZipPlatform::FileExists(szNewFileName)) { ZipPlatform::RemoveFile(szNewFileName); } ZipPlatform::RenameFile(szFileName, szNewFileName); return szNewFileName; } CZipString CZipStorage::Close(bool bWrite, bool bGetLastVolumeName) { bool bClose = true; CZipString sz; if (bWrite) { Flush(); if (IsSplit() && !IsExisting()) { sz = RenameLastFileInSplitArchive(); bClose = false;// already closed in RenameLastFileInSplitArchive } } if (bGetLastVolumeName && sz.IsEmpty()) { if (IsSplit() && IsExisting()) sz = m_pSplitNames->GetVolumeName(m_szArchiveName, (ZIP_VOLUME_TYPE)(m_uSplitData + 1), CZipSplitNamesHandler::flLast | CZipSplitNamesHandler::flExisting); else sz = m_pFile->GetFilePath(); } if (bClose) { if (bWrite) FlushFile(); if (m_state.IsSetAny(stateAutoClose)) m_pFile->Close(); } m_pWriteBuffer.Release(); m_uCurrentVolume = ZIP_VOLUME_NUMBER_UNSPECIFIED; m_state = 0; m_pFile = NULL; m_uBytesBeforeZip = 0; ClearSplitNames(); ClearCachedSizes(); return sz; } void CZipStorage::NextVolume(ZIP_SIZE_TYPE uNeeded) { Flush(); ASSERT(IsSegmented()); bool bSpan = IsSpanned(); if (m_uBytesWritten) { m_uBytesWritten = 0; m_uCurrentVolume++; ZIP_VOLUME_TYPE uMaxVolumes = (ZIP_VOLUME_TYPE)(bSpan ? 999 : 0xFFFF); if (m_uCurrentVolume >= uMaxVolumes) // m_uCurrentVolume is a zero-based index ThrowError(CZipException::tooManyVolumes); } CZipString szFileName; if (bSpan) szFileName = m_szArchiveName; else szFileName = GetSplitVolumeName(false); if (!m_pFile->IsClosed()) { m_pFile->Flush(); if (IsBinarySplit()) m_pCachedSizes->Add(m_pFile->GetLength()); m_pFile->Close(); } if (m_pChangeVolumeFunc) { int iCode = CZipSegmCallback::scVolumeNeededForWrite; for(;;) { CallCallback(uNeeded, iCode, szFileName); // allow changing of the filename szFileName = m_pChangeVolumeFunc->m_szExternalFile; if (ZipPlatform::FileExists(szFileName)) iCode = CZipSegmCallback::scFileNameDuplicated; else { if (bSpan) { CZipString label; label.Format(_T("pkback# %.3d"), m_uCurrentVolume + 1); if (!ZipPlatform::SetVolLabel(szFileName, label)) { iCode = CZipSegmCallback::scCannotSetVolLabel; continue; } } if (OpenFile(szFileName, CZipFile::modeCreate | CZipFile::modeReadWrite, false)) break; else iCode = CZipSegmCallback::scFileCreationFailure; } } m_uCurrentVolSize = bSpan ? GetFreeVolumeSpace() : m_uSplitData; } else { if (bSpan) ThrowError(CZipException::internalError); m_uCurrentVolSize = m_uSplitData; OpenFile(szFileName, CZipFile::modeCreate | CZipFile::modeReadWrite); } } void CZipStorage::CallCallback(ZIP_SIZE_TYPE uNeeded, int iCode, CZipString szTemp) { if (!m_pChangeVolumeFunc) ThrowError(CZipException::internalError); m_pChangeVolumeFunc->m_szExternalFile = szTemp; m_pChangeVolumeFunc->m_uVolumeNeeded = (ZIP_VOLUME_TYPE)(m_uCurrentVolume + 1); m_pChangeVolumeFunc->m_iCode = iCode; if (!m_pChangeVolumeFunc->Callback(uNeeded)) CZipException::Throw(CZipException::aborted, szTemp); } ZIP_SIZE_TYPE CZipStorage::GetFreeVolumeSpace() const { ASSERT (IsSpanned()); CZipString szTemp = m_pFile->GetFilePath(); if (szTemp.IsEmpty()) // called once when creating a segmented archive return 0; else { CZipPathComponent zpc(szTemp); ULONGLONG ret = ZipPlatform::GetDeviceFreeSpace(zpc.GetFilePath()); if (ret > (ZIP_SIZE_TYPE)(-1)) return (ZIP_SIZE_TYPE)(-1); else return (ZIP_SIZE_TYPE)ret; } } void CZipStorage::UpdateSegmMode(ZIP_VOLUME_TYPE uLastDisk) { bool binarySplit = IsBinarySplit(); if (!binarySplit) m_uCurrentVolume = uLastDisk; if (uLastDisk > 0 || binarySplit) { // segmentation detected CZipString szFilePath = m_pFile->GetFilePath(); if (!m_state.IsSetAny(stateSegmented)) m_state.Set(ZipPlatform::IsDriveRemovable(szFilePath) ? stateSpan : stateSplit); if (IsSpanned()) { if (!m_pSpanChangeVolumeFunc) ThrowError(CZipException::noCallback); m_pChangeVolumeFunc = m_pSpanChangeVolumeFunc; } else /*if (IsSplit())*/ { EnsureSplitNames(); if (!binarySplit) m_uSplitData = uLastDisk; // the last volume m_pChangeVolumeFunc = m_pSplitChangeVolumeFunc; } m_pWriteBuffer.Release(); // no need for this in this case } else // it will clear all segmented flags m_state.Clear(stateSpan | stateBinarySplit); } ZIP_SIZE_TYPE CZipStorage::AssureFree(ZIP_SIZE_TYPE uNeeded) { ZIP_SIZE_TYPE uFree; while ((uFree = VolumeLeft()) < uNeeded) { if (IsSplit() && !m_uBytesWritten && !m_uBytesInWriteBuffer) // in the splitArchive mode, if the size of the archive is less // than the size of the packet to be written at once, // increase once the size of the volume m_uCurrentVolSize = uNeeded; else NextVolume(uNeeded); } return uFree; } void CZipStorage::Write(const void *pBuf, DWORD iSize, bool bAtOnce) { if (!IsSegmented()) WriteInternalBuffer((char*)pBuf, iSize); else { bool atOnce = bAtOnce && !IsBinarySplit(); // if not at once, one byte is enough of free space DWORD iNeeded = atOnce ? iSize : 1; DWORD uTotal = 0; while (uTotal < iSize) { ZIP_SIZE_TYPE uFree = AssureFree(iNeeded); DWORD uLeftToWrite = iSize - uTotal; DWORD uToWrite = uFree < uLeftToWrite ? (DWORD)uFree : uLeftToWrite; WriteInternalBuffer((char*)pBuf + uTotal, uToWrite); if (atOnce) return; else uTotal += uToWrite; } } } void CZipStorage::WriteInternalBuffer(const char *pBuf, DWORD uSize) { DWORD uWritten = 0; while (uWritten < uSize) { DWORD uFreeInBuffer = GetFreeInBuffer(); if (uFreeInBuffer == 0) { Flush(); uFreeInBuffer = m_pWriteBuffer.GetSize(); } DWORD uLeftToWrite = uSize - uWritten; DWORD uToCopy = uLeftToWrite < uFreeInBuffer ? uLeftToWrite : uFreeInBuffer; memcpy((char*)m_pWriteBuffer + m_uBytesInWriteBuffer, pBuf + uWritten, uToCopy); uWritten += uToCopy; m_uBytesInWriteBuffer += uToCopy; } } ZIP_SIZE_TYPE CZipStorage::VolumeLeft() const { // for spanned archives m_uCurrentVolSize is updated after each flush() ZIP_SIZE_TYPE uBytes = m_uBytesInWriteBuffer + (IsSpanned() ? 0 : m_uBytesWritten); return uBytes > m_uCurrentVolSize ? 0 : m_uCurrentVolSize - uBytes; } void CZipStorage::Flush() { if (m_uBytesInWriteBuffer) { m_pFile->Write(m_pWriteBuffer, m_uBytesInWriteBuffer); if (IsSegmented()) m_uBytesWritten += m_uBytesInWriteBuffer; m_uBytesInWriteBuffer = 0; } if (IsSpanned()) // after writing it is difficult to predict the free space due to // not completely written clusters, write operation may start from a new cluster m_uCurrentVolSize = GetFreeVolumeSpace(); } ZIP_FILE_USIZE CZipStorage::LocateSignature(char* szSignature, ZIP_SIZE_TYPE uMaxDepth) { m_pFile->SeekToEnd(); int leftToFind = SIGNATURE_SIZE - 1; bool found = false; // for fast checking if leftToFind needs resetting if (!IsBinarySplit()) { return LocateSignature(szSignature, uMaxDepth, leftToFind, found, m_pFile->GetLength()); } else { for(;;) { ZIP_FILE_USIZE uFileLength = GetCachedSize(m_uCurrentVolume); ZIP_FILE_USIZE position = LocateSignature(szSignature, uMaxDepth, leftToFind, found, uFileLength); if (position != SignatureNotFound || uMaxDepth <= uFileLength) return position; uMaxDepth -= (ZIP_SIZE_TYPE)uFileLength; if (m_uCurrentVolume == 0) return SignatureNotFound; ChangeVolumeDec(); m_pFile->SeekToEnd(); } } } ZIP_FILE_USIZE CZipStorage::LocateSignature(char* szSignature, ZIP_SIZE_TYPE uMaxDepth, int& leftToFind, bool& found, ZIP_FILE_USIZE uFileLength) { CZipAutoBuffer buffer(m_iLocateBufferSize); ZIP_SIZE_TYPE max = (ZIP_SIZE_TYPE)(uFileLength < uMaxDepth ? uFileLength : uMaxDepth); ZIP_SIZE_TYPE position = (ZIP_SIZE_TYPE)(uFileLength - m_pFile->GetPosition()); int offset = 0; int toRead = m_iLocateBufferSize; while ( position < max ) { position += toRead; if ( position > max ) { int diff = (int) ( position - max ); toRead -= diff; offset = diff; position = max; } Seek(position, seekFromEnd); int actuallyRead = m_pFile->Read((char*)buffer + offset, toRead); if (actuallyRead != toRead) ThrowError(CZipException::badZipFile); int pos = m_iLocateBufferSize - 1; while ( pos >= offset ) { if ( buffer[pos] == szSignature[leftToFind] ) { if ( leftToFind == 0 ) return (ZIP_FILE_USIZE)(uFileLength - ( position - ( pos - offset ) )); if ( !found ) found = true; leftToFind--; pos--; } else if ( found ) { leftToFind = SIGNATURE_SIZE - 1; found = false; // do not decrease position, the current pos may be the first to find } else pos--; } } return SignatureNotFound; } void CZipStorage::SeekInBinary(ZIP_FILE_SIZE lOff, bool bSeekToBegin) { ASSERT(IsBinarySplit()); if (bSeekToBegin) m_pFile->SeekToBegin(); if (lOff == 0) return; if (lOff > 0) { ZIP_SIZE_TYPE uPosition = (ZIP_SIZE_TYPE)m_pFile->GetPosition(); ZIP_FILE_USIZE uLength = GetCachedSize(m_uCurrentVolume); if ((ZIP_FILE_USIZE)(uPosition + lOff) < uLength) { m_pFile->Seek(lOff, CZipAbstractFile::current); return; } ZIP_VOLUME_TYPE uVolume = (ZIP_VOLUME_TYPE)(m_uCurrentVolume + 1); lOff -= uLength - uPosition; for(;;) { uLength = GetCachedSize(uVolume); if ((ZIP_FILE_USIZE)lOff < uLength) { ChangeVolume(uVolume); if (lOff > 0) { m_pFile->Seek(lOff, CZipAbstractFile::current); } return; } else lOff -= uLength; uVolume++; } } else { ZIP_SIZE_TYPE uPosition = (ZIP_SIZE_TYPE)m_pFile->GetPosition(); if (uPosition >= (ZIP_SIZE_TYPE)(-lOff)) { m_pFile->Seek(lOff, CZipAbstractFile::current); return; } lOff += uPosition; ZIP_VOLUME_TYPE uVolume = (ZIP_VOLUME_TYPE)(m_uCurrentVolume - 1); for(;;) { ZIP_FILE_USIZE uLength = GetCachedSize(uVolume); if (uLength >= (ZIP_SIZE_TYPE)(-lOff)) { ChangeVolume(uVolume); if (lOff < 0) { m_pFile->Seek(lOff, CZipAbstractFile::end); } return; } else lOff += uLength; if (uVolume == 0) ThrowError(CZipException::genericError); uVolume--; } } } ULONGLONG CZipStorage::Seek(ULONGLONG lOff, SeekType iSeekType) { if (iSeekType == seekCurrent) { if (IsExistingSegmented()) { ZIP_SIZE_TYPE uPosition = (ZIP_SIZE_TYPE)m_pFile->GetPosition(); ZIP_FILE_USIZE uLength = m_pFile->GetLength(); while (uPosition + lOff >= uLength) { ZIP_SIZE_TYPE uCanSeek = (ZIP_SIZE_TYPE)(uLength - uPosition); lOff -= uCanSeek; ChangeVolume(); uPosition = 0; uLength = m_pFile->GetLength(); } return lOff > 0 ? m_pFile->SafeSeek((ZIP_FILE_USIZE)lOff) : 0; } else return m_pFile->Seek((ZIP_FILE_SIZE)lOff, CZipAbstractFile::current); } else { if (m_uCurrentVolume == 0 && m_uBytesBeforeZip > 0) lOff += m_uBytesBeforeZip; return m_pFile->SafeSeek((ZIP_FILE_USIZE)lOff, iSeekType == seekFromBeginning); } } void CZipStorage::FinalizeSegm() { ASSERT(IsNewSegmented()); // spanned archive in creation CZipString szFileName; if (IsSplit()) { // the file is already closed szFileName = RenameLastFileInSplitArchive(); if (IsBinarySplit() && m_uCurrentVolume != 0) { ZIP_SIZE_TYPE size; ZipPlatform::GetFileSize(szFileName, size); m_pCachedSizes->Add(ZIP_FILE_USIZE(size)); } } else { szFileName = m_pFile->GetFilePath(); m_pFile->Close(); } m_state.Set(stateExisting); if (m_uCurrentVolume == 0) // one-volume segmented archive was converted to a normal archive { if (IsSplit()) { ClearSplitNames(); if (IsBinarySplit()) ClearCachedSizes(); } m_state.Clear(stateSplit | stateBinarySplit | stateSpan); } else { m_uSplitData = m_uCurrentVolume; if (IsSplit()) { m_szArchiveName = szFileName; } } OpenFile(szFileName, CZipFile::modeNoTruncate | (IsSegmented() ? CZipFile::modeReadWrite : CZipFile::modeRead)); }