1262 lines
34 KiB
C++
1262 lines
34 KiB
C++
// CabHandler.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
// #include <stdio.h>
|
|
|
|
#include "../../../../C/Alloc.h"
|
|
|
|
#include "../../../Common/ComTry.h"
|
|
#include "../../../Common/IntToString.h"
|
|
#include "../../../Common/StringConvert.h"
|
|
#include "../../../Common/UTFConvert.h"
|
|
|
|
#include "../../../Windows/PropVariant.h"
|
|
#include "../../../Windows/TimeUtils.h"
|
|
|
|
#include "../../Common/ProgressUtils.h"
|
|
#include "../../Common/StreamUtils.h"
|
|
|
|
#include "../../Compress/CopyCoder.h"
|
|
#include "../../Compress/DeflateDecoder.h"
|
|
#include "../../Compress/LzxDecoder.h"
|
|
#include "../../Compress/QuantumDecoder.h"
|
|
|
|
#include "../Common/ItemNameUtils.h"
|
|
|
|
#include "CabBlockInStream.h"
|
|
#include "CabHandler.h"
|
|
|
|
using namespace NWindows;
|
|
|
|
namespace NArchive {
|
|
namespace NCab {
|
|
|
|
// #define _CAB_DETAILS
|
|
|
|
#ifdef _CAB_DETAILS
|
|
enum
|
|
{
|
|
kpidBlockReal = kpidUserDefined
|
|
};
|
|
#endif
|
|
|
|
static const Byte kProps[] =
|
|
{
|
|
kpidPath,
|
|
kpidSize,
|
|
kpidMTime,
|
|
kpidAttrib,
|
|
kpidMethod,
|
|
kpidBlock
|
|
#ifdef _CAB_DETAILS
|
|
,
|
|
// kpidBlockReal, // L"BlockReal",
|
|
kpidOffset,
|
|
kpidVolume
|
|
#endif
|
|
};
|
|
|
|
static const Byte kArcProps[] =
|
|
{
|
|
kpidTotalPhySize,
|
|
kpidMethod,
|
|
// kpidSolid,
|
|
kpidNumBlocks,
|
|
kpidNumVolumes,
|
|
kpidVolumeIndex,
|
|
kpidId
|
|
};
|
|
|
|
IMP_IInArchive_Props
|
|
IMP_IInArchive_ArcProps
|
|
|
|
static const char * const kMethods[] =
|
|
{
|
|
"None"
|
|
, "MSZip"
|
|
, "Quantum"
|
|
, "LZX"
|
|
};
|
|
|
|
static const unsigned kMethodNameBufSize = 32; // "Quantum:255"
|
|
|
|
static void SetMethodName(char *s, unsigned method, unsigned param)
|
|
{
|
|
if (method < ARRAY_SIZE(kMethods))
|
|
{
|
|
s = MyStpCpy(s, kMethods[method]);
|
|
if (method != NHeader::NMethod::kLZX &&
|
|
method != NHeader::NMethod::kQuantum)
|
|
return;
|
|
*s++ = ':';
|
|
method = param;
|
|
}
|
|
ConvertUInt32ToString(method, s);
|
|
}
|
|
|
|
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NCOM::CPropVariant prop;
|
|
switch (propID)
|
|
{
|
|
case kpidMethod:
|
|
{
|
|
UInt32 mask = 0;
|
|
UInt32 params[2] = { 0, 0 };
|
|
{
|
|
FOR_VECTOR (v, m_Database.Volumes)
|
|
{
|
|
const CRecordVector<CFolder> &folders = m_Database.Volumes[v].Folders;
|
|
FOR_VECTOR (i, folders)
|
|
{
|
|
const CFolder &folder = folders[i];
|
|
unsigned method = folder.GetMethod();
|
|
mask |= ((UInt32)1 << method);
|
|
if (method == NHeader::NMethod::kLZX ||
|
|
method == NHeader::NMethod::kQuantum)
|
|
{
|
|
unsigned di = (method == NHeader::NMethod::kQuantum) ? 0 : 1;
|
|
if (params[di] < folder.MethodMinor)
|
|
params[di] = folder.MethodMinor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AString s;
|
|
|
|
for (unsigned i = 0; i < kNumMethodsMax; i++)
|
|
{
|
|
if ((mask & (1 << i)) == 0)
|
|
continue;
|
|
s.Add_Space_if_NotEmpty();
|
|
char temp[kMethodNameBufSize];
|
|
SetMethodName(temp, i, params[i == NHeader::NMethod::kQuantum ? 0 : 1]);
|
|
s += temp;
|
|
}
|
|
|
|
prop = s;
|
|
break;
|
|
}
|
|
// case kpidSolid: prop = _database.IsSolid(); break;
|
|
case kpidNumBlocks:
|
|
{
|
|
UInt32 numFolders = 0;
|
|
FOR_VECTOR (v, m_Database.Volumes)
|
|
numFolders += m_Database.Volumes[v].Folders.Size();
|
|
prop = numFolders;
|
|
break;
|
|
}
|
|
|
|
case kpidTotalPhySize:
|
|
{
|
|
if (m_Database.Volumes.Size() > 1)
|
|
{
|
|
UInt64 sum = 0;
|
|
FOR_VECTOR (v, m_Database.Volumes)
|
|
sum += m_Database.Volumes[v].ArcInfo.Size;
|
|
prop = sum;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kpidNumVolumes:
|
|
prop = (UInt32)m_Database.Volumes.Size();
|
|
break;
|
|
|
|
case kpidVolumeIndex:
|
|
{
|
|
if (m_Database.Volumes.Size() == 1)
|
|
{
|
|
const CDatabaseEx &db = m_Database.Volumes[0];
|
|
const CInArcInfo &ai = db.ArcInfo;
|
|
prop = (UInt32)ai.CabinetNumber;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kpidId:
|
|
{
|
|
if (m_Database.Volumes.Size() != 0)
|
|
{
|
|
prop = (UInt32)m_Database.Volumes[0].ArcInfo.SetID;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kpidOffset:
|
|
/*
|
|
if (m_Database.Volumes.Size() == 1)
|
|
prop = m_Database.Volumes[0].StartPosition;
|
|
*/
|
|
prop = _offset;
|
|
break;
|
|
|
|
case kpidPhySize:
|
|
/*
|
|
if (m_Database.Volumes.Size() == 1)
|
|
prop = (UInt64)m_Database.Volumes[0].ArcInfo.Size;
|
|
*/
|
|
prop = (UInt64)_phySize;
|
|
break;
|
|
|
|
case kpidErrorFlags:
|
|
{
|
|
UInt32 v = 0;
|
|
if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
|
|
if (_errorInHeaders) v |= kpv_ErrorFlags_HeadersError;
|
|
if (_unexpectedEnd) v |= kpv_ErrorFlags_UnexpectedEnd;
|
|
prop = v;
|
|
break;
|
|
}
|
|
|
|
case kpidError:
|
|
if (!_errorMessage.IsEmpty())
|
|
prop = _errorMessage;
|
|
break;
|
|
|
|
case kpidName:
|
|
{
|
|
if (m_Database.Volumes.Size() == 1)
|
|
{
|
|
const CDatabaseEx &db = m_Database.Volumes[0];
|
|
const CInArcInfo &ai = db.ArcInfo;
|
|
if (ai.SetID != 0)
|
|
{
|
|
AString s;
|
|
char temp[32];
|
|
ConvertUInt32ToString(ai.SetID, temp);
|
|
s += temp;
|
|
ConvertUInt32ToString(ai.CabinetNumber + 1, temp);
|
|
s += '_';
|
|
s += temp;
|
|
s += ".cab";
|
|
prop = s;
|
|
}
|
|
/*
|
|
// that code is incomplete. It gcan give accurate name of volume
|
|
char s[32];
|
|
ConvertUInt32ToString(ai.CabinetNumber + 2, s);
|
|
unsigned len = MyStringLen(s);
|
|
if (ai.IsThereNext())
|
|
{
|
|
AString fn = ai.NextArc.FileName;
|
|
if (fn.Len() > 4 && StringsAreEqualNoCase_Ascii(fn.RightPtr(4), ".cab"))
|
|
fn.DeleteFrom(fn.Len() - 4);
|
|
if (len < fn.Len())
|
|
{
|
|
if (strcmp(s, fn.RightPtr(len)) == 0)
|
|
{
|
|
AString s2 = fn;
|
|
s2.DeleteFrom(fn.Len() - len);
|
|
ConvertUInt32ToString(ai.CabinetNumber + 1, s);
|
|
s2 += s;
|
|
s2 += ".cab";
|
|
prop = GetUnicodeString(s2);
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
break;
|
|
}
|
|
|
|
// case kpidShortComment:
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NCOM::CPropVariant prop;
|
|
|
|
const CMvItem &mvItem = m_Database.Items[index];
|
|
const CDatabaseEx &db = m_Database.Volumes[mvItem.VolumeIndex];
|
|
unsigned itemIndex = mvItem.ItemIndex;
|
|
const CItem &item = db.Items[itemIndex];
|
|
switch (propID)
|
|
{
|
|
case kpidPath:
|
|
{
|
|
UString unicodeName;
|
|
if (item.IsNameUTF())
|
|
ConvertUTF8ToUnicode(item.Name, unicodeName);
|
|
else
|
|
unicodeName = MultiByteToUnicodeString(item.Name, CP_ACP);
|
|
prop = (const wchar_t *)NItemName::WinNameToOSName(unicodeName);
|
|
break;
|
|
}
|
|
|
|
case kpidIsDir: prop = item.IsDir(); break;
|
|
case kpidSize: prop = item.Size; break;
|
|
case kpidAttrib: prop = item.GetWinAttrib(); break;
|
|
|
|
case kpidMTime:
|
|
{
|
|
FILETIME localFileTime, utcFileTime;
|
|
if (NTime::DosTimeToFileTime(item.Time, localFileTime))
|
|
{
|
|
if (!LocalFileTimeToFileTime(&localFileTime, &utcFileTime))
|
|
utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0;
|
|
}
|
|
else
|
|
utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0;
|
|
prop = utcFileTime;
|
|
break;
|
|
}
|
|
|
|
case kpidMethod:
|
|
{
|
|
UInt32 realFolderIndex = item.GetFolderIndex(db.Folders.Size());
|
|
const CFolder &folder = db.Folders[realFolderIndex];
|
|
char s[kMethodNameBufSize];;
|
|
SetMethodName(s, folder.GetMethod(), folder.MethodMinor);
|
|
prop = s;
|
|
break;
|
|
}
|
|
|
|
case kpidBlock: prop = (Int32)m_Database.GetFolderIndex(&mvItem); break;
|
|
|
|
#ifdef _CAB_DETAILS
|
|
|
|
// case kpidBlockReal: prop = (UInt32)item.FolderIndex; break;
|
|
case kpidOffset: prop = (UInt32)item.Offset; break;
|
|
case kpidVolume: prop = (UInt32)mvItem.VolumeIndex; break;
|
|
|
|
#endif
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Open(IInStream *inStream,
|
|
const UInt64 *maxCheckStartPosition,
|
|
IArchiveOpenCallback *callback)
|
|
{
|
|
COM_TRY_BEGIN
|
|
Close();
|
|
|
|
CInArchive archive;
|
|
CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
|
|
callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);
|
|
|
|
CMyComPtr<IInStream> nextStream = inStream;
|
|
bool prevChecked = false;
|
|
UString startVolName;
|
|
bool startVolName_was_Requested = false;
|
|
UInt64 numItems = 0;
|
|
unsigned numTempVolumes = 0;
|
|
// try
|
|
{
|
|
while (nextStream)
|
|
{
|
|
CDatabaseEx db;
|
|
db.Stream = nextStream;
|
|
|
|
HRESULT res = archive.Open(db, maxCheckStartPosition);
|
|
|
|
_errorInHeaders |= archive.HeaderError;
|
|
_errorInHeaders |= archive.ErrorInNames;
|
|
_unexpectedEnd |= archive.UnexpectedEnd;
|
|
|
|
if (res == S_OK && !m_Database.Volumes.IsEmpty())
|
|
{
|
|
const CArchInfo &lastArc = m_Database.Volumes.Back().ArcInfo;
|
|
unsigned cabNumber = db.ArcInfo.CabinetNumber;
|
|
if (lastArc.SetID != db.ArcInfo.SetID)
|
|
res = S_FALSE;
|
|
else if (prevChecked)
|
|
{
|
|
if (cabNumber != lastArc.CabinetNumber + 1)
|
|
res = S_FALSE;
|
|
}
|
|
else if (cabNumber >= lastArc.CabinetNumber)
|
|
res = S_FALSE;
|
|
else if (numTempVolumes != 0)
|
|
{
|
|
const CArchInfo &prevArc = m_Database.Volumes[numTempVolumes - 1].ArcInfo;
|
|
if (cabNumber != prevArc.CabinetNumber + 1)
|
|
res = S_FALSE;
|
|
}
|
|
}
|
|
|
|
if (archive.IsArc || res == S_OK)
|
|
{
|
|
_isArc = true;
|
|
if (m_Database.Volumes.IsEmpty())
|
|
{
|
|
_offset = db.StartPosition;
|
|
_phySize = db.ArcInfo.Size;
|
|
}
|
|
}
|
|
|
|
if (res == S_OK)
|
|
{
|
|
numItems += db.Items.Size();
|
|
m_Database.Volumes.Insert(prevChecked ? m_Database.Volumes.Size() : numTempVolumes, db);
|
|
if (!prevChecked && m_Database.Volumes.Size() > 1)
|
|
{
|
|
numTempVolumes++;
|
|
if (db.ArcInfo.CabinetNumber + 1 == m_Database.Volumes[numTempVolumes].ArcInfo.CabinetNumber)
|
|
numTempVolumes = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (res != S_FALSE)
|
|
return res;
|
|
if (m_Database.Volumes.IsEmpty())
|
|
return S_FALSE;
|
|
if (prevChecked)
|
|
break;
|
|
prevChecked = true;
|
|
if (numTempVolumes != 0)
|
|
{
|
|
m_Database.Volumes.DeleteFrontal(numTempVolumes);
|
|
numTempVolumes = 0;
|
|
}
|
|
}
|
|
|
|
RINOK(callback->SetCompleted(&numItems, NULL));
|
|
|
|
nextStream = NULL;
|
|
|
|
for (;;)
|
|
{
|
|
const COtherArc *otherArc = NULL;
|
|
|
|
if (!prevChecked)
|
|
{
|
|
if (numTempVolumes == 0)
|
|
{
|
|
const CInArcInfo &ai = m_Database.Volumes[0].ArcInfo;
|
|
if (ai.IsTherePrev())
|
|
otherArc = &ai.PrevArc;
|
|
else
|
|
prevChecked = true;
|
|
}
|
|
else
|
|
{
|
|
const CInArcInfo &ai = m_Database.Volumes[numTempVolumes - 1].ArcInfo;
|
|
if (ai.IsThereNext())
|
|
otherArc = &ai.NextArc;
|
|
else
|
|
{
|
|
prevChecked = true;
|
|
m_Database.Volumes.DeleteFrontal(numTempVolumes);
|
|
numTempVolumes = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!otherArc)
|
|
{
|
|
const CInArcInfo &ai = m_Database.Volumes.Back().ArcInfo;
|
|
if (ai.IsThereNext())
|
|
otherArc = &ai.NextArc;
|
|
}
|
|
|
|
if (!otherArc)
|
|
break;
|
|
if (!openVolumeCallback)
|
|
break;
|
|
// printf("\n%s", otherArc->FileName);
|
|
const UString fullName = MultiByteToUnicodeString(otherArc->FileName, CP_ACP);
|
|
|
|
if (!startVolName_was_Requested)
|
|
{
|
|
// some "bad" cab example can contain the link to itself.
|
|
startVolName_was_Requested = true;
|
|
{
|
|
NCOM::CPropVariant prop;
|
|
RINOK(openVolumeCallback->GetProperty(kpidName, &prop));
|
|
if (prop.vt == VT_BSTR)
|
|
startVolName = prop.bstrVal;
|
|
}
|
|
if (fullName == startVolName)
|
|
break;
|
|
}
|
|
|
|
HRESULT result = openVolumeCallback->GetStream(fullName, &nextStream);
|
|
if (result == S_OK)
|
|
break;
|
|
if (result != S_FALSE)
|
|
return result;
|
|
|
|
if (!_errorMessage.IsEmpty())
|
|
_errorMessage.Add_LF();
|
|
_errorMessage.AddAscii("Can't open volume: ");
|
|
_errorMessage += fullName;
|
|
|
|
if (prevChecked)
|
|
break;
|
|
prevChecked = true;
|
|
if (numTempVolumes != 0)
|
|
{
|
|
m_Database.Volumes.DeleteFrontal(numTempVolumes);
|
|
numTempVolumes = 0;
|
|
}
|
|
}
|
|
|
|
} // read nextStream iteration
|
|
|
|
if (numTempVolumes != 0)
|
|
{
|
|
m_Database.Volumes.DeleteFrontal(numTempVolumes);
|
|
numTempVolumes = 0;
|
|
}
|
|
if (m_Database.Volumes.IsEmpty())
|
|
return S_FALSE;
|
|
else
|
|
{
|
|
m_Database.FillSortAndShrink();
|
|
if (!m_Database.Check())
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
COM_TRY_END
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Close()
|
|
{
|
|
_errorMessage.Empty();
|
|
_isArc = false;
|
|
_errorInHeaders = false;
|
|
_unexpectedEnd = false;
|
|
// _mainVolIndex = -1;
|
|
_phySize = 0;
|
|
_offset = 0;
|
|
|
|
m_Database.Clear();
|
|
return S_OK;
|
|
}
|
|
|
|
class CFolderOutStream:
|
|
public ISequentialOutStream,
|
|
public CMyUnknownImp
|
|
{
|
|
public:
|
|
MY_UNKNOWN_IMP
|
|
|
|
STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
|
|
private:
|
|
const CMvDatabaseEx *m_Database;
|
|
const CRecordVector<bool> *m_ExtractStatuses;
|
|
|
|
Byte *TempBuf;
|
|
UInt32 TempBufSize;
|
|
unsigned NumIdenticalFiles;
|
|
bool TempBufMode;
|
|
UInt32 m_BufStartFolderOffset;
|
|
|
|
unsigned m_StartIndex;
|
|
unsigned m_CurrentIndex;
|
|
CMyComPtr<IArchiveExtractCallback> m_ExtractCallback;
|
|
bool m_TestMode;
|
|
|
|
CMyComPtr<ISequentialOutStream> m_RealOutStream;
|
|
|
|
bool m_IsOk;
|
|
bool m_FileIsOpen;
|
|
UInt32 m_RemainFileSize;
|
|
UInt64 m_FolderSize;
|
|
UInt64 m_PosInFolder;
|
|
|
|
void FreeTempBuf()
|
|
{
|
|
::MyFree(TempBuf);
|
|
TempBuf = NULL;
|
|
}
|
|
|
|
HRESULT OpenFile();
|
|
HRESULT CloseFileWithResOp(Int32 resOp);
|
|
HRESULT CloseFile();
|
|
HRESULT Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK);
|
|
public:
|
|
HRESULT WriteEmptyFiles();
|
|
|
|
CFolderOutStream(): TempBuf(NULL) {}
|
|
~CFolderOutStream() { FreeTempBuf(); }
|
|
void Init(
|
|
const CMvDatabaseEx *database,
|
|
const CRecordVector<bool> *extractStatuses,
|
|
unsigned startIndex,
|
|
UInt64 folderSize,
|
|
IArchiveExtractCallback *extractCallback,
|
|
bool testMode);
|
|
HRESULT FlushCorrupted(unsigned folderIndex);
|
|
HRESULT Unsupported();
|
|
|
|
bool NeedMoreWrite() const { return (m_FolderSize > m_PosInFolder); }
|
|
UInt64 GetRemain() const { return m_FolderSize - m_PosInFolder; }
|
|
UInt64 GetPosInFolder() const { return m_PosInFolder; }
|
|
};
|
|
|
|
|
|
void CFolderOutStream::Init(
|
|
const CMvDatabaseEx *database,
|
|
const CRecordVector<bool> *extractStatuses,
|
|
unsigned startIndex,
|
|
UInt64 folderSize,
|
|
IArchiveExtractCallback *extractCallback,
|
|
bool testMode)
|
|
{
|
|
m_Database = database;
|
|
m_ExtractStatuses = extractStatuses;
|
|
m_StartIndex = startIndex;
|
|
m_FolderSize = folderSize;
|
|
|
|
m_ExtractCallback = extractCallback;
|
|
m_TestMode = testMode;
|
|
|
|
m_CurrentIndex = 0;
|
|
m_PosInFolder = 0;
|
|
m_FileIsOpen = false;
|
|
m_IsOk = true;
|
|
TempBufMode = false;
|
|
NumIdenticalFiles = 0;
|
|
}
|
|
|
|
|
|
HRESULT CFolderOutStream::CloseFileWithResOp(Int32 resOp)
|
|
{
|
|
m_RealOutStream.Release();
|
|
m_FileIsOpen = false;
|
|
NumIdenticalFiles--;
|
|
return m_ExtractCallback->SetOperationResult(resOp);
|
|
}
|
|
|
|
|
|
HRESULT CFolderOutStream::CloseFile()
|
|
{
|
|
return CloseFileWithResOp(m_IsOk ?
|
|
NExtract::NOperationResult::kOK:
|
|
NExtract::NOperationResult::kDataError);
|
|
}
|
|
|
|
|
|
HRESULT CFolderOutStream::OpenFile()
|
|
{
|
|
if (NumIdenticalFiles == 0)
|
|
{
|
|
const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex];
|
|
const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];
|
|
unsigned numExtractItems = 0;
|
|
unsigned curIndex;
|
|
|
|
for (curIndex = m_CurrentIndex; curIndex < m_ExtractStatuses->Size(); curIndex++)
|
|
{
|
|
const CMvItem &mvItem2 = m_Database->Items[m_StartIndex + curIndex];
|
|
const CItem &item2 = m_Database->Volumes[mvItem2.VolumeIndex].Items[mvItem2.ItemIndex];
|
|
if (item.Offset != item2.Offset ||
|
|
item.Size != item2.Size ||
|
|
item.Size == 0)
|
|
break;
|
|
if (!m_TestMode && (*m_ExtractStatuses)[curIndex])
|
|
numExtractItems++;
|
|
}
|
|
|
|
NumIdenticalFiles = (curIndex - m_CurrentIndex);
|
|
if (NumIdenticalFiles == 0)
|
|
NumIdenticalFiles = 1;
|
|
TempBufMode = false;
|
|
|
|
if (numExtractItems > 1)
|
|
{
|
|
if (!TempBuf || item.Size > TempBufSize)
|
|
{
|
|
FreeTempBuf();
|
|
TempBuf = (Byte *)MyAlloc(item.Size);
|
|
TempBufSize = item.Size;
|
|
if (TempBuf == NULL)
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
TempBufMode = true;
|
|
m_BufStartFolderOffset = item.Offset;
|
|
}
|
|
else if (numExtractItems == 1)
|
|
{
|
|
while (NumIdenticalFiles && !(*m_ExtractStatuses)[m_CurrentIndex])
|
|
{
|
|
CMyComPtr<ISequentialOutStream> stream;
|
|
RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &stream, NExtract::NAskMode::kSkip));
|
|
if (stream)
|
|
return E_FAIL;
|
|
RINOK(m_ExtractCallback->PrepareOperation(NExtract::NAskMode::kSkip));
|
|
m_CurrentIndex++;
|
|
m_FileIsOpen = true;
|
|
CloseFile();
|
|
}
|
|
}
|
|
}
|
|
|
|
Int32 askMode = (*m_ExtractStatuses)[m_CurrentIndex] ? (m_TestMode ?
|
|
NExtract::NAskMode::kTest :
|
|
NExtract::NAskMode::kExtract) :
|
|
NExtract::NAskMode::kSkip;
|
|
RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &m_RealOutStream, askMode));
|
|
if (!m_RealOutStream && !m_TestMode)
|
|
askMode = NExtract::NAskMode::kSkip;
|
|
return m_ExtractCallback->PrepareOperation(askMode);
|
|
}
|
|
|
|
|
|
HRESULT CFolderOutStream::WriteEmptyFiles()
|
|
{
|
|
if (m_FileIsOpen)
|
|
return S_OK;
|
|
for (; m_CurrentIndex < m_ExtractStatuses->Size(); m_CurrentIndex++)
|
|
{
|
|
const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex];
|
|
const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];
|
|
UInt64 fileSize = item.Size;
|
|
if (fileSize != 0)
|
|
return S_OK;
|
|
HRESULT result = OpenFile();
|
|
m_RealOutStream.Release();
|
|
RINOK(result);
|
|
RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
HRESULT CFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK)
|
|
{
|
|
COM_TRY_BEGIN
|
|
|
|
UInt32 realProcessed = 0;
|
|
if (processedSize)
|
|
*processedSize = 0;
|
|
|
|
while (size != 0)
|
|
{
|
|
if (m_FileIsOpen)
|
|
{
|
|
UInt32 numBytesToWrite = MyMin(m_RemainFileSize, size);
|
|
HRESULT res = S_OK;
|
|
if (numBytesToWrite != 0)
|
|
{
|
|
if (!isOK)
|
|
m_IsOk = false;
|
|
if (m_RealOutStream)
|
|
{
|
|
UInt32 processedSizeLocal = 0;
|
|
res = m_RealOutStream->Write((const Byte *)data, numBytesToWrite, &processedSizeLocal);
|
|
numBytesToWrite = processedSizeLocal;
|
|
}
|
|
if (TempBufMode && TempBuf)
|
|
memcpy(TempBuf + (m_PosInFolder - m_BufStartFolderOffset), data, numBytesToWrite);
|
|
}
|
|
realProcessed += numBytesToWrite;
|
|
if (processedSize)
|
|
*processedSize = realProcessed;
|
|
data = (const void *)((const Byte *)data + numBytesToWrite);
|
|
size -= numBytesToWrite;
|
|
m_RemainFileSize -= numBytesToWrite;
|
|
m_PosInFolder += numBytesToWrite;
|
|
|
|
if (res != S_OK)
|
|
return res;
|
|
|
|
if (m_RemainFileSize == 0)
|
|
{
|
|
RINOK(CloseFile());
|
|
|
|
while (NumIdenticalFiles)
|
|
{
|
|
HRESULT result = OpenFile();
|
|
m_FileIsOpen = true;
|
|
m_CurrentIndex++;
|
|
if (result == S_OK && m_RealOutStream && TempBuf)
|
|
result = WriteStream(m_RealOutStream, TempBuf, (size_t)(m_PosInFolder - m_BufStartFolderOffset));
|
|
|
|
if (!TempBuf && TempBufMode && m_RealOutStream)
|
|
{
|
|
RINOK(CloseFileWithResOp(NExtract::NOperationResult::kUnsupportedMethod));
|
|
}
|
|
else
|
|
{
|
|
RINOK(CloseFile());
|
|
}
|
|
|
|
RINOK(result);
|
|
}
|
|
|
|
TempBufMode = false;
|
|
}
|
|
|
|
if (realProcessed > 0)
|
|
break; // with this break this function works as Write-Part
|
|
}
|
|
else
|
|
{
|
|
if (m_CurrentIndex >= m_ExtractStatuses->Size())
|
|
{
|
|
// we ignore extra data;
|
|
realProcessed += size;
|
|
if (processedSize)
|
|
*processedSize = realProcessed;
|
|
m_PosInFolder += size;
|
|
return S_OK;
|
|
// return E_FAIL;
|
|
}
|
|
|
|
const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex];
|
|
const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];
|
|
|
|
m_RemainFileSize = item.Size;
|
|
|
|
UInt32 fileOffset = item.Offset;
|
|
|
|
if (fileOffset < m_PosInFolder)
|
|
return E_FAIL;
|
|
|
|
if (fileOffset > m_PosInFolder)
|
|
{
|
|
UInt32 numBytesToWrite = MyMin(fileOffset - (UInt32)m_PosInFolder, size);
|
|
realProcessed += numBytesToWrite;
|
|
if (processedSize)
|
|
*processedSize = realProcessed;
|
|
data = (const void *)((const Byte *)data + numBytesToWrite);
|
|
size -= numBytesToWrite;
|
|
m_PosInFolder += numBytesToWrite;
|
|
}
|
|
|
|
if (fileOffset == m_PosInFolder)
|
|
{
|
|
RINOK(OpenFile());
|
|
m_FileIsOpen = true;
|
|
m_CurrentIndex++;
|
|
m_IsOk = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return WriteEmptyFiles();
|
|
|
|
COM_TRY_END
|
|
}
|
|
|
|
|
|
STDMETHODIMP CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)
|
|
{
|
|
return Write2(data, size, processedSize, true);
|
|
}
|
|
|
|
|
|
HRESULT CFolderOutStream::FlushCorrupted(unsigned folderIndex)
|
|
{
|
|
if (!NeedMoreWrite())
|
|
{
|
|
CMyComPtr<IArchiveExtractCallbackMessage> callbackMessage;
|
|
m_ExtractCallback.QueryInterface(IID_IArchiveExtractCallbackMessage, &callbackMessage);
|
|
if (callbackMessage)
|
|
{
|
|
RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, NExtract::NOperationResult::kDataError));
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
const unsigned kBufSize = (1 << 12);
|
|
Byte buf[kBufSize];
|
|
for (unsigned i = 0; i < kBufSize; i++)
|
|
buf[i] = 0;
|
|
|
|
for (;;)
|
|
{
|
|
if (!NeedMoreWrite())
|
|
return S_OK;
|
|
UInt64 remain = GetRemain();
|
|
UInt32 size = (remain < kBufSize ? (UInt32)remain : (UInt32)kBufSize);
|
|
UInt32 processedSizeLocal = 0;
|
|
RINOK(Write2(buf, size, &processedSizeLocal, false));
|
|
}
|
|
}
|
|
|
|
|
|
HRESULT CFolderOutStream::Unsupported()
|
|
{
|
|
while (m_CurrentIndex < m_ExtractStatuses->Size())
|
|
{
|
|
HRESULT result = OpenFile();
|
|
if (result != S_FALSE && result != S_OK)
|
|
return result;
|
|
m_RealOutStream.Release();
|
|
RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod));
|
|
m_CurrentIndex++;
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
|
|
Int32 testModeSpec, IArchiveExtractCallback *extractCallback)
|
|
{
|
|
COM_TRY_BEGIN
|
|
|
|
bool allFilesMode = (numItems == (UInt32)(Int32)-1);
|
|
if (allFilesMode)
|
|
numItems = m_Database.Items.Size();
|
|
if (numItems == 0)
|
|
return S_OK;
|
|
bool testMode = (testModeSpec != 0);
|
|
UInt64 totalUnPacked = 0;
|
|
|
|
UInt32 i;
|
|
int lastFolder = -2;
|
|
UInt64 lastFolderSize = 0;
|
|
|
|
for (i = 0; i < numItems; i++)
|
|
{
|
|
unsigned index = allFilesMode ? i : indices[i];
|
|
const CMvItem &mvItem = m_Database.Items[index];
|
|
const CItem &item = m_Database.Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];
|
|
if (item.IsDir())
|
|
continue;
|
|
int folderIndex = m_Database.GetFolderIndex(&mvItem);
|
|
if (folderIndex != lastFolder)
|
|
totalUnPacked += lastFolderSize;
|
|
lastFolder = folderIndex;
|
|
lastFolderSize = item.GetEndOffset();
|
|
}
|
|
|
|
totalUnPacked += lastFolderSize;
|
|
|
|
extractCallback->SetTotal(totalUnPacked);
|
|
|
|
totalUnPacked = 0;
|
|
|
|
UInt64 totalPacked = 0;
|
|
|
|
CLocalProgress *lps = new CLocalProgress;
|
|
CMyComPtr<ICompressProgressInfo> progress = lps;
|
|
lps->Init(extractCallback, false);
|
|
|
|
NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder;
|
|
CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
|
|
|
|
NCompress::NDeflate::NDecoder::CCOMCoder *deflateDecoderSpec = NULL;
|
|
CMyComPtr<ICompressCoder> deflateDecoder;
|
|
|
|
NCompress::NLzx::CDecoder *lzxDecoderSpec = NULL;
|
|
CMyComPtr<IUnknown> lzxDecoder;
|
|
|
|
NCompress::NQuantum::CDecoder *quantumDecoderSpec = NULL;
|
|
CMyComPtr<IUnknown> quantumDecoder;
|
|
|
|
CCabBlockInStream *cabBlockInStreamSpec = new CCabBlockInStream();
|
|
CMyComPtr<ISequentialInStream> cabBlockInStream = cabBlockInStreamSpec;
|
|
if (!cabBlockInStreamSpec->Create())
|
|
return E_OUTOFMEMORY;
|
|
|
|
CRecordVector<bool> extractStatuses;
|
|
|
|
for (i = 0;;)
|
|
{
|
|
lps->OutSize = totalUnPacked;
|
|
lps->InSize = totalPacked;
|
|
RINOK(lps->SetCur());
|
|
|
|
if (i >= numItems)
|
|
break;
|
|
|
|
unsigned index = allFilesMode ? i : indices[i];
|
|
|
|
const CMvItem &mvItem = m_Database.Items[index];
|
|
const CDatabaseEx &db = m_Database.Volumes[mvItem.VolumeIndex];
|
|
unsigned itemIndex = mvItem.ItemIndex;
|
|
const CItem &item = db.Items[itemIndex];
|
|
|
|
i++;
|
|
if (item.IsDir())
|
|
{
|
|
Int32 askMode = testMode ?
|
|
NExtract::NAskMode::kTest :
|
|
NExtract::NAskMode::kExtract;
|
|
CMyComPtr<ISequentialOutStream> realOutStream;
|
|
RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
|
|
RINOK(extractCallback->PrepareOperation(askMode));
|
|
realOutStream.Release();
|
|
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
|
|
continue;
|
|
}
|
|
|
|
int folderIndex = m_Database.GetFolderIndex(&mvItem);
|
|
|
|
if (folderIndex < 0)
|
|
{
|
|
// If we need previous archive
|
|
Int32 askMode= testMode ?
|
|
NExtract::NAskMode::kTest :
|
|
NExtract::NAskMode::kExtract;
|
|
CMyComPtr<ISequentialOutStream> realOutStream;
|
|
RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
|
|
RINOK(extractCallback->PrepareOperation(askMode));
|
|
realOutStream.Release();
|
|
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kDataError));
|
|
continue;
|
|
}
|
|
|
|
unsigned startIndex2 = m_Database.FolderStartFileIndex[folderIndex];
|
|
unsigned startIndex = startIndex2;
|
|
extractStatuses.Clear();
|
|
for (; startIndex < index; startIndex++)
|
|
extractStatuses.Add(false);
|
|
extractStatuses.Add(true);
|
|
startIndex++;
|
|
UInt64 curUnpack = item.GetEndOffset();
|
|
|
|
for (; i < numItems; i++)
|
|
{
|
|
unsigned indexNext = allFilesMode ? i : indices[i];
|
|
const CMvItem &mvItem2 = m_Database.Items[indexNext];
|
|
const CItem &item2 = m_Database.Volumes[mvItem2.VolumeIndex].Items[mvItem2.ItemIndex];
|
|
if (item2.IsDir())
|
|
continue;
|
|
int newFolderIndex = m_Database.GetFolderIndex(&mvItem2);
|
|
|
|
if (newFolderIndex != folderIndex)
|
|
break;
|
|
for (; startIndex < indexNext; startIndex++)
|
|
extractStatuses.Add(false);
|
|
extractStatuses.Add(true);
|
|
startIndex++;
|
|
curUnpack = item2.GetEndOffset();
|
|
}
|
|
|
|
CFolderOutStream *cabFolderOutStream = new CFolderOutStream;
|
|
CMyComPtr<ISequentialOutStream> outStream(cabFolderOutStream);
|
|
|
|
unsigned folderIndex2 = item.GetFolderIndex(db.Folders.Size());
|
|
const CFolder &folder = db.Folders[folderIndex2];
|
|
|
|
cabFolderOutStream->Init(&m_Database, &extractStatuses, startIndex2,
|
|
curUnpack, extractCallback, testMode);
|
|
|
|
cabBlockInStreamSpec->MsZip = false;
|
|
HRESULT res = S_OK;
|
|
|
|
switch (folder.GetMethod())
|
|
{
|
|
case NHeader::NMethod::kNone:
|
|
break;
|
|
|
|
case NHeader::NMethod::kMSZip:
|
|
if (!deflateDecoder)
|
|
{
|
|
deflateDecoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder;
|
|
deflateDecoder = deflateDecoderSpec;
|
|
}
|
|
cabBlockInStreamSpec->MsZip = true;
|
|
break;
|
|
|
|
case NHeader::NMethod::kLZX:
|
|
if (!lzxDecoder)
|
|
{
|
|
lzxDecoderSpec = new NCompress::NLzx::CDecoder;
|
|
lzxDecoder = lzxDecoderSpec;
|
|
}
|
|
res = lzxDecoderSpec->SetParams_and_Alloc(folder.MethodMinor);
|
|
break;
|
|
|
|
case NHeader::NMethod::kQuantum:
|
|
if (!quantumDecoder)
|
|
{
|
|
quantumDecoderSpec = new NCompress::NQuantum::CDecoder;
|
|
quantumDecoder = quantumDecoderSpec;
|
|
}
|
|
res = quantumDecoderSpec->SetParams(folder.MethodMinor);
|
|
break;
|
|
|
|
default:
|
|
res = E_INVALIDARG;
|
|
break;
|
|
}
|
|
|
|
if (res == E_INVALIDARG)
|
|
{
|
|
RINOK(cabFolderOutStream->Unsupported());
|
|
totalUnPacked += curUnpack;
|
|
continue;
|
|
}
|
|
RINOK(res);
|
|
|
|
{
|
|
unsigned volIndex = mvItem.VolumeIndex;
|
|
int locFolderIndex = item.GetFolderIndex(db.Folders.Size());
|
|
bool keepHistory = false;
|
|
bool keepInputBuffer = false;
|
|
bool thereWasNotAlignedChunk = false;
|
|
|
|
for (UInt32 bl = 0; cabFolderOutStream->NeedMoreWrite();)
|
|
{
|
|
if (volIndex >= m_Database.Volumes.Size())
|
|
{
|
|
res = S_FALSE;
|
|
break;
|
|
}
|
|
|
|
const CDatabaseEx &db2 = m_Database.Volumes[volIndex];
|
|
const CFolder &folder2 = db2.Folders[locFolderIndex];
|
|
|
|
if (bl == 0)
|
|
{
|
|
cabBlockInStreamSpec->ReservedSize = db2.ArcInfo.GetDataBlockReserveSize();
|
|
RINOK(db2.Stream->Seek(db2.StartPosition + folder2.DataStart, STREAM_SEEK_SET, NULL));
|
|
}
|
|
|
|
if (bl == folder2.NumDataBlocks)
|
|
{
|
|
/*
|
|
CFolder::NumDataBlocks (CFFOLDER::cCFData in CAB specification) is 16-bit.
|
|
But there are some big CAB archives from MS that contain more
|
|
than (0xFFFF) CFDATA blocks in folder.
|
|
Old cab extracting software can show error (or ask next volume)
|
|
but cab extracting library in new Windows ignores this error.
|
|
15.00 : We also try to ignore such error, if archive is not multi-volume.
|
|
*/
|
|
if (m_Database.Volumes.Size() > 1)
|
|
{
|
|
volIndex++;
|
|
locFolderIndex = 0;
|
|
bl = 0;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bl++;
|
|
|
|
if (!keepInputBuffer)
|
|
cabBlockInStreamSpec->InitForNewBlock();
|
|
|
|
UInt32 packSize, unpackSize;
|
|
res = cabBlockInStreamSpec->PreRead(db2.Stream, packSize, unpackSize);
|
|
if (res == S_FALSE)
|
|
break;
|
|
RINOK(res);
|
|
keepInputBuffer = (unpackSize == 0);
|
|
if (keepInputBuffer)
|
|
continue;
|
|
|
|
UInt64 totalUnPacked2 = totalUnPacked + cabFolderOutStream->GetPosInFolder();
|
|
totalPacked += packSize;
|
|
|
|
lps->OutSize = totalUnPacked2;
|
|
lps->InSize = totalPacked;
|
|
RINOK(lps->SetCur());
|
|
|
|
const UInt32 kBlockSizeMax = (1 << 15);
|
|
|
|
/* We don't try to reduce last block.
|
|
Note that LZX converts data with x86 filter.
|
|
and filter needs larger input data than reduced size.
|
|
It's simpler to decompress full chunk here.
|
|
also we need full block for quantum for more integrity checks */
|
|
|
|
if (unpackSize > kBlockSizeMax)
|
|
{
|
|
res = S_FALSE;
|
|
break;
|
|
}
|
|
|
|
if (unpackSize != kBlockSizeMax)
|
|
{
|
|
if (thereWasNotAlignedChunk)
|
|
{
|
|
res = S_FALSE;
|
|
break;
|
|
}
|
|
thereWasNotAlignedChunk = true;
|
|
}
|
|
|
|
UInt64 unpackSize64 = unpackSize;
|
|
UInt32 packSizeChunk = cabBlockInStreamSpec->GetPackSizeAvail();
|
|
|
|
switch (folder2.GetMethod())
|
|
{
|
|
case NHeader::NMethod::kNone:
|
|
res = copyCoder->Code(cabBlockInStream, outStream, NULL, &unpackSize64, NULL);
|
|
break;
|
|
|
|
case NHeader::NMethod::kMSZip:
|
|
deflateDecoderSpec->Set_KeepHistory(keepHistory);
|
|
/* v9.31: now we follow MSZIP specification that requires to finish deflate stream at the end of each block.
|
|
But PyCabArc can create CAB archives that doesn't have finish marker at the end of block.
|
|
Cabarc probably ignores such errors in cab archives.
|
|
Maybe we also should ignore that error?
|
|
Or we should extract full file and show the warning? */
|
|
deflateDecoderSpec->Set_NeedFinishInput(true);
|
|
res = deflateDecoder->Code(cabBlockInStream, outStream, NULL, &unpackSize64, NULL);
|
|
if (res == S_OK)
|
|
{
|
|
if (!deflateDecoderSpec->IsFinished())
|
|
res = S_FALSE;
|
|
if (!deflateDecoderSpec->IsFinalBlock())
|
|
res = S_FALSE;
|
|
}
|
|
break;
|
|
|
|
case NHeader::NMethod::kLZX:
|
|
lzxDecoderSpec->SetKeepHistory(keepHistory);
|
|
lzxDecoderSpec->KeepHistoryForNext = true;
|
|
|
|
res = lzxDecoderSpec->Code(cabBlockInStreamSpec->GetData(), packSizeChunk, unpackSize);
|
|
|
|
if (res == S_OK)
|
|
res = WriteStream(outStream,
|
|
lzxDecoderSpec->GetUnpackData(),
|
|
lzxDecoderSpec->GetUnpackSize());
|
|
break;
|
|
|
|
case NHeader::NMethod::kQuantum:
|
|
res = quantumDecoderSpec->Code(cabBlockInStreamSpec->GetData(),
|
|
packSizeChunk, outStream, unpackSize, keepHistory);
|
|
break;
|
|
}
|
|
|
|
if (res != S_OK)
|
|
{
|
|
if (res != S_FALSE)
|
|
RINOK(res);
|
|
break;
|
|
}
|
|
|
|
keepHistory = true;
|
|
}
|
|
|
|
if (res == S_OK)
|
|
{
|
|
RINOK(cabFolderOutStream->WriteEmptyFiles());
|
|
}
|
|
}
|
|
|
|
if (res != S_OK || cabFolderOutStream->NeedMoreWrite())
|
|
{
|
|
RINOK(cabFolderOutStream->FlushCorrupted(folderIndex2));
|
|
}
|
|
|
|
totalUnPacked += curUnpack;
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
COM_TRY_END
|
|
}
|
|
|
|
|
|
STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
|
|
{
|
|
*numItems = m_Database.Items.Size();
|
|
return S_OK;
|
|
}
|
|
|
|
}}
|