1756 lines
46 KiB
C++
1756 lines
46 KiB
C++
// RarHandler.cpp
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "../../../../C/CpuArch.h"
|
|
|
|
#include "../../../Common/ComTry.h"
|
|
#include "../../../Common/IntToString.h"
|
|
#include "../../../Common/UTFConvert.h"
|
|
|
|
#include "../../../Windows/PropVariantUtils.h"
|
|
#include "../../../Windows/TimeUtils.h"
|
|
|
|
#include "../../IPassword.h"
|
|
|
|
#include "../../Common/CreateCoder.h"
|
|
#include "../../Common/FilterCoder.h"
|
|
#include "../../Common/LimitedStreams.h"
|
|
#include "../../Common/MethodId.h"
|
|
#include "../../Common/ProgressUtils.h"
|
|
#include "../../Common/RegisterArc.h"
|
|
#include "../../Common/StreamUtils.h"
|
|
|
|
#include "../../Compress/CopyCoder.h"
|
|
|
|
#include "../../Crypto/Rar20Crypto.h"
|
|
#include "../../Crypto/RarAes.h"
|
|
|
|
#include "../Common/FindSignature.h"
|
|
#include "../Common/ItemNameUtils.h"
|
|
#include "../Common/OutStreamWithCRC.h"
|
|
|
|
#include "../HandlerCont.h"
|
|
|
|
#include "RarVol.h"
|
|
#include "RarHandler.h"
|
|
|
|
using namespace NWindows;
|
|
|
|
#define Get16(p) GetUi16(p)
|
|
#define Get32(p) GetUi32(p)
|
|
|
|
namespace NArchive {
|
|
namespace NRar {
|
|
|
|
#define SIGNATURE { 0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 }
|
|
|
|
static const Byte kMarker[NHeader::kMarkerSize] = SIGNATURE;
|
|
|
|
const unsigned kPasswordLen_MAX = 127;
|
|
|
|
bool CItem::IgnoreItem() const
|
|
{
|
|
switch (HostOS)
|
|
{
|
|
case NHeader::NFile::kHostMSDOS:
|
|
case NHeader::NFile::kHostOS2:
|
|
case NHeader::NFile::kHostWin32:
|
|
return ((Attrib & NHeader::NFile::kLabelFileAttribute) != 0);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CItem::IsDir() const
|
|
{
|
|
if (GetDictSize() == NHeader::NFile::kDictDirectoryValue)
|
|
return true;
|
|
switch (HostOS)
|
|
{
|
|
case NHeader::NFile::kHostMSDOS:
|
|
case NHeader::NFile::kHostOS2:
|
|
case NHeader::NFile::kHostWin32:
|
|
if ((Attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
UInt32 CItem::GetWinAttrib() const
|
|
{
|
|
UInt32 a;
|
|
switch (HostOS)
|
|
{
|
|
case NHeader::NFile::kHostMSDOS:
|
|
case NHeader::NFile::kHostOS2:
|
|
case NHeader::NFile::kHostWin32:
|
|
a = Attrib;
|
|
break;
|
|
default:
|
|
a = 0; // must be converted from unix value;
|
|
}
|
|
if (IsDir())
|
|
a |= NHeader::NFile::kWinFileDirectoryAttributeMask;
|
|
return a;
|
|
}
|
|
|
|
static const char * const kHostOS[] =
|
|
{
|
|
"MS DOS"
|
|
, "OS/2"
|
|
, "Win32"
|
|
, "Unix"
|
|
, "Mac OS"
|
|
, "BeOS"
|
|
};
|
|
|
|
static const char *kUnknownOS = "Unknown";
|
|
|
|
static const CUInt32PCharPair k_Flags[] =
|
|
{
|
|
{ 0, "Volume" },
|
|
{ 1, "Comment" },
|
|
{ 2, "Lock" },
|
|
{ 3, "Solid" },
|
|
{ 4, "NewVolName" }, // pack_comment in old versuons
|
|
{ 5, "Authenticity" },
|
|
{ 6, "Recovery" },
|
|
{ 7, "BlockEncryption" },
|
|
{ 8, "FirstVolume" },
|
|
{ 9, "EncryptVer" }
|
|
};
|
|
|
|
enum EErrorType
|
|
{
|
|
k_ErrorType_OK,
|
|
k_ErrorType_Corrupted,
|
|
k_ErrorType_UnexpectedEnd,
|
|
k_ErrorType_DecryptionError
|
|
};
|
|
|
|
class CInArchive
|
|
{
|
|
IInStream *m_Stream;
|
|
UInt64 m_StreamStartPosition;
|
|
CBuffer<wchar_t> _unicodeNameBuffer;
|
|
CByteBuffer _comment;
|
|
CByteBuffer m_FileHeaderData;
|
|
NHeader::NBlock::CBlock m_BlockHeader;
|
|
NCrypto::NRar3::CDecoder *m_RarAESSpec;
|
|
CMyComPtr<ICompressFilter> m_RarAES;
|
|
CBuffer<Byte> m_DecryptedData;
|
|
Byte *m_DecryptedDataAligned;
|
|
UInt32 m_DecryptedDataSize;
|
|
bool m_CryptoMode;
|
|
UInt32 m_CryptoPos;
|
|
|
|
|
|
HRESULT ReadBytesSpec(void *data, size_t *size);
|
|
bool ReadBytesAndTestSize(void *data, UInt32 size);
|
|
void ReadName(const Byte *p, unsigned nameSize, CItem &item);
|
|
bool ReadHeaderReal(const Byte *p, unsigned size, CItem &item);
|
|
|
|
HRESULT Open2(IInStream *stream, const UInt64 *searchHeaderSizeLimit);
|
|
|
|
void AddToSeekValue(UInt64 addValue)
|
|
{
|
|
m_Position += addValue;
|
|
}
|
|
|
|
void FinishCryptoBlock()
|
|
{
|
|
if (m_CryptoMode)
|
|
while ((m_CryptoPos & 0xF) != 0)
|
|
{
|
|
m_CryptoPos++;
|
|
m_Position++;
|
|
}
|
|
}
|
|
|
|
public:
|
|
UInt64 m_Position;
|
|
CInArcInfo ArcInfo;
|
|
bool HeaderErrorWarning;
|
|
|
|
HRESULT Open(IInStream *inStream, const UInt64 *searchHeaderSizeLimit);
|
|
HRESULT GetNextItem(CItem &item, ICryptoGetTextPassword *getTextPassword,
|
|
bool &filled, EErrorType &error);
|
|
};
|
|
|
|
static bool CheckHeaderCrc(const Byte *header, size_t headerSize)
|
|
{
|
|
return Get16(header) == (UInt16)(CrcCalc(header + 2, headerSize - 2) & 0xFFFF);
|
|
}
|
|
|
|
HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit)
|
|
{
|
|
HeaderErrorWarning = false;
|
|
m_CryptoMode = false;
|
|
RINOK(stream->Seek(0, STREAM_SEEK_CUR, &m_StreamStartPosition));
|
|
RINOK(stream->Seek(0, STREAM_SEEK_END, &ArcInfo.FileSize));
|
|
RINOK(stream->Seek(m_StreamStartPosition, STREAM_SEEK_SET, NULL));
|
|
m_Position = m_StreamStartPosition;
|
|
|
|
UInt64 arcStartPos = m_StreamStartPosition;
|
|
{
|
|
Byte marker[NHeader::kMarkerSize];
|
|
RINOK(ReadStream_FALSE(stream, marker, NHeader::kMarkerSize));
|
|
if (memcmp(marker, kMarker, NHeader::kMarkerSize) == 0)
|
|
m_Position += NHeader::kMarkerSize;
|
|
else
|
|
{
|
|
if (searchHeaderSizeLimit && *searchHeaderSizeLimit == 0)
|
|
return S_FALSE;
|
|
RINOK(stream->Seek(m_StreamStartPosition, STREAM_SEEK_SET, NULL));
|
|
RINOK(FindSignatureInStream(stream, kMarker, NHeader::kMarkerSize,
|
|
searchHeaderSizeLimit, arcStartPos));
|
|
m_Position = arcStartPos + NHeader::kMarkerSize;
|
|
RINOK(stream->Seek(m_Position, STREAM_SEEK_SET, NULL));
|
|
}
|
|
}
|
|
Byte buf[NHeader::NArchive::kArchiveHeaderSize + 1];
|
|
|
|
RINOK(ReadStream_FALSE(stream, buf, NHeader::NArchive::kArchiveHeaderSize));
|
|
AddToSeekValue(NHeader::NArchive::kArchiveHeaderSize);
|
|
|
|
|
|
UInt32 blockSize = Get16(buf + 5);
|
|
|
|
ArcInfo.EncryptVersion = 0;
|
|
ArcInfo.Flags = Get16(buf + 3);
|
|
|
|
UInt32 headerSize = NHeader::NArchive::kArchiveHeaderSize;
|
|
|
|
/*
|
|
if (ArcInfo.IsThereEncryptVer())
|
|
{
|
|
if (blockSize <= headerSize)
|
|
return S_FALSE;
|
|
RINOK(ReadStream_FALSE(stream, buf + NHeader::NArchive::kArchiveHeaderSize, 1));
|
|
AddToSeekValue(1);
|
|
ArcInfo.EncryptVersion = buf[NHeader::NArchive::kArchiveHeaderSize];
|
|
headerSize += 1;
|
|
}
|
|
*/
|
|
|
|
if (blockSize < headerSize
|
|
|| buf[2] != NHeader::NBlockType::kArchiveHeader
|
|
|| !CheckHeaderCrc(buf, headerSize))
|
|
return S_FALSE;
|
|
|
|
size_t commentSize = blockSize - headerSize;
|
|
_comment.Alloc(commentSize);
|
|
RINOK(ReadStream_FALSE(stream, _comment, commentSize));
|
|
AddToSeekValue(commentSize);
|
|
m_Stream = stream;
|
|
ArcInfo.StartPos = arcStartPos;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CInArchive::ReadBytesSpec(void *data, size_t *resSize)
|
|
{
|
|
if (m_CryptoMode)
|
|
{
|
|
size_t size = *resSize;
|
|
*resSize = 0;
|
|
const Byte *bufData = m_DecryptedDataAligned;
|
|
UInt32 bufSize = m_DecryptedDataSize;
|
|
size_t i;
|
|
for (i = 0; i < size && m_CryptoPos < bufSize; i++)
|
|
((Byte *)data)[i] = bufData[m_CryptoPos++];
|
|
*resSize = i;
|
|
return S_OK;
|
|
}
|
|
return ReadStream(m_Stream, data, resSize);
|
|
}
|
|
|
|
bool CInArchive::ReadBytesAndTestSize(void *data, UInt32 size)
|
|
{
|
|
size_t processed = size;
|
|
if (ReadBytesSpec(data, &processed) != S_OK)
|
|
return false;
|
|
return processed == size;
|
|
}
|
|
|
|
static void DecodeUnicodeFileName(const Byte *name, const Byte *encName,
|
|
unsigned encSize, wchar_t *unicodeName, unsigned maxDecSize)
|
|
{
|
|
unsigned encPos = 0;
|
|
unsigned decPos = 0;
|
|
unsigned flagBits = 0;
|
|
Byte flags = 0;
|
|
Byte highByte = encName[encPos++];
|
|
while (encPos < encSize && decPos < maxDecSize)
|
|
{
|
|
if (flagBits == 0)
|
|
{
|
|
flags = encName[encPos++];
|
|
flagBits = 8;
|
|
}
|
|
switch (flags >> 6)
|
|
{
|
|
case 0:
|
|
unicodeName[decPos++] = encName[encPos++];
|
|
break;
|
|
case 1:
|
|
unicodeName[decPos++] = (wchar_t)(encName[encPos++] + (highByte << 8));
|
|
break;
|
|
case 2:
|
|
unicodeName[decPos++] = (wchar_t)(encName[encPos] + (encName[encPos + 1] << 8));
|
|
encPos += 2;
|
|
break;
|
|
case 3:
|
|
{
|
|
unsigned len = encName[encPos++];
|
|
if (len & 0x80)
|
|
{
|
|
Byte correction = encName[encPos++];
|
|
for (len = (len & 0x7f) + 2;
|
|
len > 0 && decPos < maxDecSize; len--, decPos++)
|
|
unicodeName[decPos] = (wchar_t)(((name[decPos] + correction) & 0xff) + (highByte << 8));
|
|
}
|
|
else
|
|
for (len += 2; len > 0 && decPos < maxDecSize; len--, decPos++)
|
|
unicodeName[decPos] = name[decPos];
|
|
}
|
|
break;
|
|
}
|
|
flags <<= 2;
|
|
flagBits -= 2;
|
|
}
|
|
unicodeName[decPos < maxDecSize ? decPos : maxDecSize - 1] = 0;
|
|
}
|
|
|
|
void CInArchive::ReadName(const Byte *p, unsigned nameSize, CItem &item)
|
|
{
|
|
item.UnicodeName.Empty();
|
|
if (nameSize > 0)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < nameSize && p[i] != 0; i++);
|
|
item.Name.SetFrom((const char *)p, i);
|
|
|
|
if (item.HasUnicodeName())
|
|
{
|
|
if (i < nameSize)
|
|
{
|
|
i++;
|
|
unsigned uNameSizeMax = MyMin(nameSize, (unsigned)0x400);
|
|
_unicodeNameBuffer.AllocAtLeast(uNameSizeMax + 1);
|
|
DecodeUnicodeFileName(p, p + i, nameSize - i, _unicodeNameBuffer, uNameSizeMax);
|
|
item.UnicodeName = _unicodeNameBuffer;
|
|
}
|
|
else if (!ConvertUTF8ToUnicode(item.Name, item.UnicodeName))
|
|
item.UnicodeName.Empty();
|
|
}
|
|
}
|
|
else
|
|
item.Name.Empty();
|
|
}
|
|
|
|
static int ReadTime(const Byte *p, unsigned size, Byte mask, CRarTime &rarTime)
|
|
{
|
|
rarTime.LowSecond = (Byte)(((mask & 4) != 0) ? 1 : 0);
|
|
unsigned numDigits = (mask & 3);
|
|
rarTime.SubTime[0] =
|
|
rarTime.SubTime[1] =
|
|
rarTime.SubTime[2] = 0;
|
|
if (numDigits > size)
|
|
return -1;
|
|
for (unsigned i = 0; i < numDigits; i++)
|
|
rarTime.SubTime[3 - numDigits + i] = p[i];
|
|
return numDigits;
|
|
}
|
|
|
|
#define READ_TIME(_mask_, _ttt_) \
|
|
{ int size2 = ReadTime(p, size, _mask_, _ttt_); if (size2 < 0) return false; p += (unsigned)size2, size -= (unsigned)size2; }
|
|
|
|
#define READ_TIME_2(_mask_, _def_, _ttt_) \
|
|
_def_ = ((_mask_ & 8) != 0); if (_def_) \
|
|
{ if (size < 4) return false; \
|
|
_ttt_ .DosTime = Get32(p); p += 4; size -= 4; \
|
|
READ_TIME(_mask_, _ttt_); } \
|
|
|
|
|
|
bool CInArchive::ReadHeaderReal(const Byte *p, unsigned size, CItem &item)
|
|
{
|
|
const Byte *pStart = p;
|
|
|
|
item.Clear();
|
|
item.Flags = m_BlockHeader.Flags;
|
|
|
|
const unsigned kFileHeaderSize = 25;
|
|
|
|
if (size < kFileHeaderSize)
|
|
return false;
|
|
|
|
item.PackSize = Get32(p);
|
|
item.Size = Get32(p + 4);
|
|
item.HostOS = p[8];
|
|
item.FileCRC = Get32(p + 9);
|
|
item.MTime.DosTime = Get32(p + 13);
|
|
item.UnPackVersion = p[17];
|
|
item.Method = p[18];
|
|
unsigned nameSize = Get16(p + 19);
|
|
item.Attrib = Get32(p + 21);
|
|
|
|
item.MTime.LowSecond = 0;
|
|
item.MTime.SubTime[0] =
|
|
item.MTime.SubTime[1] =
|
|
item.MTime.SubTime[2] = 0;
|
|
|
|
p += kFileHeaderSize;
|
|
size -= kFileHeaderSize;
|
|
if ((item.Flags & NHeader::NFile::kSize64Bits) != 0)
|
|
{
|
|
if (size < 8)
|
|
return false;
|
|
item.PackSize |= ((UInt64)Get32(p) << 32);
|
|
item.Size |= ((UInt64)Get32(p + 4) << 32);
|
|
p += 8;
|
|
size -= 8;
|
|
}
|
|
if (nameSize > size)
|
|
return false;
|
|
ReadName(p, nameSize, item);
|
|
p += nameSize;
|
|
size -= nameSize;
|
|
|
|
/*
|
|
// It was commented, since it's difficult to support alt Streams for solid archives.
|
|
if (m_BlockHeader.Type == NHeader::NBlockType::kSubBlock)
|
|
{
|
|
if (item.HasSalt())
|
|
{
|
|
if (size < sizeof(item.Salt))
|
|
return false;
|
|
size -= sizeof(item.Salt);
|
|
p += sizeof(item.Salt);
|
|
}
|
|
if (item.Name == "ACL" && size == 0)
|
|
{
|
|
item.IsAltStream = true;
|
|
item.Name.Empty();
|
|
item.UnicodeName.SetFromAscii(".ACL");
|
|
}
|
|
else if (item.Name == "STM" && size != 0 && (size & 1) == 0)
|
|
{
|
|
item.IsAltStream = true;
|
|
item.Name.Empty();
|
|
for (UInt32 i = 0; i < size; i += 2)
|
|
{
|
|
wchar_t c = Get16(p + i);
|
|
if (c == 0)
|
|
return false;
|
|
item.UnicodeName += c;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (item.HasSalt())
|
|
{
|
|
if (size < sizeof(item.Salt))
|
|
return false;
|
|
for (unsigned i = 0; i < sizeof(item.Salt); i++)
|
|
item.Salt[i] = p[i];
|
|
p += sizeof(item.Salt);
|
|
size -= sizeof(item.Salt);
|
|
}
|
|
|
|
// some rar archives have HasExtTime flag without field.
|
|
if (size >= 2 && item.HasExtTime())
|
|
{
|
|
Byte aMask = (Byte)(p[0] >> 4);
|
|
Byte b = p[1];
|
|
p += 2;
|
|
size -= 2;
|
|
Byte mMask = (Byte)(b >> 4);
|
|
Byte cMask = (Byte)(b & 0xF);
|
|
if ((mMask & 8) != 0)
|
|
{
|
|
READ_TIME(mMask, item.MTime);
|
|
}
|
|
READ_TIME_2(cMask, item.CTimeDefined, item.CTime);
|
|
READ_TIME_2(aMask, item.ATimeDefined, item.ATime);
|
|
}
|
|
|
|
unsigned fileHeaderWithNameSize = 7 + (unsigned)(p - pStart);
|
|
|
|
item.Position = m_Position;
|
|
item.MainPartSize = fileHeaderWithNameSize;
|
|
item.CommentSize = (UInt16)(m_BlockHeader.HeadSize - fileHeaderWithNameSize);
|
|
|
|
if (m_CryptoMode)
|
|
item.AlignSize = (UInt16)((16 - ((m_BlockHeader.HeadSize) & 0xF)) & 0xF);
|
|
else
|
|
item.AlignSize = 0;
|
|
AddToSeekValue(m_BlockHeader.HeadSize);
|
|
|
|
// return (m_BlockHeader.Type != NHeader::NBlockType::kSubBlock || item.IsAltStream);
|
|
return true;
|
|
}
|
|
|
|
HRESULT CInArchive::GetNextItem(CItem &item, ICryptoGetTextPassword *getTextPassword, bool &filled, EErrorType &error)
|
|
{
|
|
filled = false;
|
|
error = k_ErrorType_OK;
|
|
for (;;)
|
|
{
|
|
m_Stream->Seek(m_Position, STREAM_SEEK_SET, NULL);
|
|
ArcInfo.EndPos = m_Position;
|
|
if (!m_CryptoMode && (ArcInfo.Flags &
|
|
NHeader::NArchive::kBlockHeadersAreEncrypted) != 0)
|
|
{
|
|
m_CryptoMode = false;
|
|
if (getTextPassword == 0)
|
|
{
|
|
error = k_ErrorType_DecryptionError;
|
|
return S_OK; // return S_FALSE;
|
|
}
|
|
if (!m_RarAES)
|
|
{
|
|
m_RarAESSpec = new NCrypto::NRar3::CDecoder;
|
|
m_RarAES = m_RarAESSpec;
|
|
}
|
|
// m_RarAESSpec->SetRar350Mode(ArcInfo.IsEncryptOld());
|
|
|
|
// Salt
|
|
const UInt32 kSaltSize = 8;
|
|
Byte salt[kSaltSize];
|
|
if (!ReadBytesAndTestSize(salt, kSaltSize))
|
|
return S_FALSE;
|
|
m_Position += kSaltSize;
|
|
RINOK(m_RarAESSpec->SetDecoderProperties2(salt, kSaltSize))
|
|
// Password
|
|
CMyComBSTR password;
|
|
RINOK(getTextPassword->CryptoGetTextPassword(&password))
|
|
unsigned len = 0;
|
|
if (password)
|
|
len = MyStringLen(password);
|
|
if (len > kPasswordLen_MAX)
|
|
len = kPasswordLen_MAX;
|
|
|
|
CByteArr buffer(len * 2);
|
|
for (unsigned i = 0; i < len; i++)
|
|
{
|
|
wchar_t c = password[i];
|
|
((Byte *)buffer)[i * 2] = (Byte)c;
|
|
((Byte *)buffer)[i * 2 + 1] = (Byte)(c >> 8);
|
|
}
|
|
|
|
m_RarAESSpec->SetPassword((const Byte *)buffer, len * 2);
|
|
|
|
const UInt32 kDecryptedBufferSize = (1 << 12);
|
|
if (m_DecryptedData.Size() == 0)
|
|
{
|
|
const UInt32 kAlign = 16;
|
|
m_DecryptedData.Alloc(kDecryptedBufferSize + kAlign);
|
|
m_DecryptedDataAligned = (Byte *)((ptrdiff_t)((Byte *)m_DecryptedData + kAlign - 1) & ~(ptrdiff_t)(kAlign - 1));
|
|
}
|
|
RINOK(m_RarAES->Init());
|
|
size_t decryptedDataSizeT = kDecryptedBufferSize;
|
|
RINOK(ReadStream(m_Stream, m_DecryptedDataAligned, &decryptedDataSizeT));
|
|
m_DecryptedDataSize = (UInt32)decryptedDataSizeT;
|
|
m_DecryptedDataSize = m_RarAES->Filter(m_DecryptedDataAligned, m_DecryptedDataSize);
|
|
|
|
m_CryptoMode = true;
|
|
m_CryptoPos = 0;
|
|
}
|
|
|
|
m_FileHeaderData.AllocAtLeast(7);
|
|
size_t processed = 7;
|
|
RINOK(ReadBytesSpec((Byte *)m_FileHeaderData, &processed));
|
|
if (processed != 7)
|
|
{
|
|
if (processed != 0)
|
|
error = k_ErrorType_UnexpectedEnd;
|
|
ArcInfo.EndPos = m_Position + processed; // test it
|
|
return S_OK;
|
|
}
|
|
|
|
const Byte *p = m_FileHeaderData;
|
|
m_BlockHeader.CRC = Get16(p + 0);
|
|
m_BlockHeader.Type = p[2];
|
|
m_BlockHeader.Flags = Get16(p + 3);
|
|
m_BlockHeader.HeadSize = Get16(p + 5);
|
|
|
|
if (m_BlockHeader.HeadSize < 7)
|
|
{
|
|
error = k_ErrorType_Corrupted;
|
|
return S_OK;
|
|
// ThrowExceptionWithCode(CInArchiveException::kIncorrectArchive);
|
|
}
|
|
|
|
if (m_BlockHeader.Type < NHeader::NBlockType::kFileHeader ||
|
|
m_BlockHeader.Type > NHeader::NBlockType::kEndOfArchive)
|
|
{
|
|
error = m_CryptoMode ?
|
|
k_ErrorType_DecryptionError :
|
|
k_ErrorType_Corrupted;
|
|
return S_OK;
|
|
}
|
|
|
|
if (m_BlockHeader.Type == NHeader::NBlockType::kEndOfArchive)
|
|
{
|
|
bool footerError = false;
|
|
|
|
unsigned expectHeadLen = 7;
|
|
if (m_BlockHeader.Flags & NHeader::NArchive::kEndOfArc_Flags_DataCRC)
|
|
expectHeadLen += 4;
|
|
if (m_BlockHeader.Flags & NHeader::NArchive::kEndOfArc_Flags_VolNumber)
|
|
expectHeadLen += 2;
|
|
if (m_BlockHeader.Flags & NHeader::NArchive::kEndOfArc_Flags_RevSpace)
|
|
expectHeadLen += 7;
|
|
|
|
// rar 5.0 beta 1 writes incorrect RevSpace and headSize
|
|
|
|
if (m_BlockHeader.HeadSize < expectHeadLen)
|
|
HeaderErrorWarning = true;
|
|
|
|
if (m_BlockHeader.HeadSize > 7)
|
|
{
|
|
/* We suppose that EndOfArchive header is always small.
|
|
It's only 20 bytes for multivolume
|
|
Fix the limit, if larger footers are possible */
|
|
if (m_BlockHeader.HeadSize > (1 << 8))
|
|
footerError = true;
|
|
else
|
|
{
|
|
if (m_FileHeaderData.Size() < m_BlockHeader.HeadSize)
|
|
m_FileHeaderData.ChangeSize_KeepData(m_BlockHeader.HeadSize, 7);
|
|
UInt32 afterSize = m_BlockHeader.HeadSize - 7;
|
|
if (ReadBytesAndTestSize(m_FileHeaderData + 7, afterSize))
|
|
processed += afterSize;
|
|
else
|
|
{
|
|
if (!m_CryptoMode)
|
|
{
|
|
error = k_ErrorType_UnexpectedEnd;
|
|
return S_OK;
|
|
}
|
|
footerError = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (footerError || !CheckHeaderCrc(m_FileHeaderData, m_BlockHeader.HeadSize))
|
|
{
|
|
error = m_CryptoMode ?
|
|
k_ErrorType_DecryptionError :
|
|
k_ErrorType_Corrupted;
|
|
}
|
|
else
|
|
{
|
|
ArcInfo.EndFlags = m_BlockHeader.Flags;
|
|
UInt32 offset = 7;
|
|
|
|
if (m_BlockHeader.Flags & NHeader::NArchive::kEndOfArc_Flags_DataCRC)
|
|
{
|
|
if (processed < offset + 4)
|
|
error = k_ErrorType_Corrupted;
|
|
else
|
|
ArcInfo.DataCRC = Get32(m_FileHeaderData + offset);
|
|
offset += 4;
|
|
}
|
|
|
|
if (m_BlockHeader.Flags & NHeader::NArchive::kEndOfArc_Flags_VolNumber)
|
|
{
|
|
if (processed < offset + 2)
|
|
error = k_ErrorType_Corrupted;
|
|
ArcInfo.VolNumber = (UInt32)Get16(m_FileHeaderData + offset);
|
|
}
|
|
|
|
ArcInfo.EndOfArchive_was_Read = true;
|
|
}
|
|
|
|
m_Position += processed;
|
|
FinishCryptoBlock();
|
|
ArcInfo.EndPos = m_Position;
|
|
return S_OK;
|
|
}
|
|
|
|
if (m_BlockHeader.Type == NHeader::NBlockType::kFileHeader
|
|
/* || m_BlockHeader.Type == NHeader::NBlockType::kSubBlock */)
|
|
{
|
|
if (m_FileHeaderData.Size() < m_BlockHeader.HeadSize)
|
|
m_FileHeaderData.ChangeSize_KeepData(m_BlockHeader.HeadSize, 7);
|
|
// m_CurData = (Byte *)m_FileHeaderData;
|
|
// m_PosLimit = m_BlockHeader.HeadSize;
|
|
if (!ReadBytesAndTestSize(m_FileHeaderData + 7, m_BlockHeader.HeadSize - 7))
|
|
{
|
|
error = k_ErrorType_UnexpectedEnd;
|
|
return S_OK;
|
|
}
|
|
|
|
bool okItem = ReadHeaderReal(m_FileHeaderData + 7, m_BlockHeader.HeadSize - 7, item);
|
|
if (okItem)
|
|
{
|
|
if (!CheckHeaderCrc(m_FileHeaderData, (unsigned)m_BlockHeader.HeadSize - item.CommentSize))
|
|
{
|
|
error = k_ErrorType_Corrupted; // ThrowExceptionWithCode(CInArchiveException::kFileHeaderCRCError);
|
|
return S_OK;
|
|
}
|
|
filled = true;
|
|
}
|
|
|
|
FinishCryptoBlock();
|
|
m_CryptoMode = false;
|
|
// Move Position to compressed Data;
|
|
m_Stream->Seek(m_Position, STREAM_SEEK_SET, NULL);
|
|
AddToSeekValue(item.PackSize); // m_Position points to next header;
|
|
// if (okItem)
|
|
return S_OK;
|
|
/*
|
|
else
|
|
continue;
|
|
*/
|
|
}
|
|
|
|
if (m_CryptoMode && m_BlockHeader.HeadSize > (1 << 10))
|
|
{
|
|
error = k_ErrorType_DecryptionError;
|
|
return S_OK;
|
|
}
|
|
|
|
if ((m_BlockHeader.Flags & NHeader::NBlock::kLongBlock) != 0)
|
|
{
|
|
if (m_FileHeaderData.Size() < 7 + 4)
|
|
m_FileHeaderData.ChangeSize_KeepData(7 + 4, 7);
|
|
if (!ReadBytesAndTestSize(m_FileHeaderData + 7, 4))
|
|
{
|
|
error = k_ErrorType_UnexpectedEnd;
|
|
return S_OK;
|
|
}
|
|
UInt32 dataSize = Get32(m_FileHeaderData + 7);
|
|
AddToSeekValue(dataSize);
|
|
if (m_CryptoMode && dataSize > (1 << 27))
|
|
{
|
|
error = k_ErrorType_DecryptionError;
|
|
return S_OK;
|
|
}
|
|
m_CryptoPos = m_BlockHeader.HeadSize;
|
|
}
|
|
else
|
|
m_CryptoPos = 0;
|
|
|
|
{
|
|
UInt64 newPos = m_Position + m_BlockHeader.HeadSize;
|
|
if (newPos > ArcInfo.FileSize)
|
|
{
|
|
error = k_ErrorType_UnexpectedEnd;
|
|
return S_OK;
|
|
}
|
|
}
|
|
AddToSeekValue(m_BlockHeader.HeadSize);
|
|
FinishCryptoBlock();
|
|
m_CryptoMode = false;
|
|
}
|
|
}
|
|
|
|
|
|
static const Byte kProps[] =
|
|
{
|
|
kpidPath,
|
|
kpidIsDir,
|
|
kpidSize,
|
|
kpidPackSize,
|
|
kpidMTime,
|
|
kpidCTime,
|
|
kpidATime,
|
|
kpidAttrib,
|
|
|
|
kpidEncrypted,
|
|
kpidSolid,
|
|
kpidCommented,
|
|
kpidSplitBefore,
|
|
kpidSplitAfter,
|
|
kpidCRC,
|
|
kpidHostOS,
|
|
kpidMethod,
|
|
kpidUnpackVer
|
|
};
|
|
|
|
static const Byte kArcProps[] =
|
|
{
|
|
kpidTotalPhySize,
|
|
kpidCharacts,
|
|
kpidSolid,
|
|
kpidNumBlocks,
|
|
// kpidEncrypted,
|
|
kpidIsVolume,
|
|
kpidVolumeIndex,
|
|
kpidNumVolumes
|
|
// kpidCommented
|
|
};
|
|
|
|
IMP_IInArchive_Props
|
|
IMP_IInArchive_ArcProps
|
|
|
|
UInt64 CHandler::GetPackSize(unsigned refIndex) const
|
|
{
|
|
const CRefItem &refItem = _refItems[refIndex];
|
|
UInt64 totalPackSize = 0;
|
|
for (unsigned i = 0; i < refItem.NumItems; i++)
|
|
totalPackSize += _items[refItem.ItemIndex + i].PackSize;
|
|
return totalPackSize;
|
|
}
|
|
|
|
bool CHandler::IsSolid(unsigned refIndex) const
|
|
{
|
|
const CItem &item = _items[_refItems[refIndex].ItemIndex];
|
|
if (item.UnPackVersion < 20)
|
|
{
|
|
if (_arcInfo.IsSolid())
|
|
return (refIndex > 0);
|
|
return false;
|
|
}
|
|
return item.IsSolid();
|
|
}
|
|
|
|
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NCOM::CPropVariant prop;
|
|
switch (propID)
|
|
{
|
|
case kpidVolumeIndex: if (_arcInfo.Is_VolNumber_Defined()) prop = (UInt32)_arcInfo.VolNumber; break;
|
|
case kpidSolid: prop = _arcInfo.IsSolid(); break;
|
|
case kpidCharacts:
|
|
{
|
|
AString s = FlagsToString(k_Flags, ARRAY_SIZE(k_Flags), _arcInfo.Flags);
|
|
// FLAGS_TO_PROP(k_Flags, _arcInfo.Flags, prop);
|
|
if (_arcInfo.Is_DataCRC_Defined())
|
|
{
|
|
s.Add_Space_if_NotEmpty();
|
|
s += "VolCRC";
|
|
}
|
|
prop = s;
|
|
break;
|
|
}
|
|
// case kpidEncrypted: prop = _arcInfo.IsEncrypted(); break; // it's for encrypted names.
|
|
case kpidIsVolume: prop = _arcInfo.IsVolume(); break;
|
|
case kpidNumVolumes: prop = (UInt32)_arcs.Size(); break;
|
|
case kpidOffset: if (_arcs.Size() == 1 && _arcInfo.StartPos != 0) prop = _arcInfo.StartPos; break;
|
|
|
|
case kpidTotalPhySize:
|
|
{
|
|
if (_arcs.Size() > 1)
|
|
{
|
|
UInt64 sum = 0;
|
|
FOR_VECTOR (v, _arcs)
|
|
sum += _arcs[v].PhySize;
|
|
prop = sum;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kpidPhySize:
|
|
{
|
|
if (_arcs.Size() != 0)
|
|
prop = _arcInfo.GetPhySize();
|
|
break;
|
|
}
|
|
|
|
// case kpidCommented: prop = _arcInfo.IsCommented(); break;
|
|
|
|
case kpidNumBlocks:
|
|
{
|
|
UInt32 numBlocks = 0;
|
|
FOR_VECTOR (i, _refItems)
|
|
if (!IsSolid(i))
|
|
numBlocks++;
|
|
prop = (UInt32)numBlocks;
|
|
break;
|
|
}
|
|
|
|
|
|
case kpidError:
|
|
{
|
|
// if (!_errorMessage.IsEmpty()) prop = _errorMessage; break;
|
|
|
|
if (/* &_missingVol || */ !_missingVolName.IsEmpty())
|
|
{
|
|
UString s;
|
|
s.SetFromAscii("Missing volume : ");
|
|
s += _missingVolName;
|
|
prop = s;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kpidErrorFlags:
|
|
{
|
|
UInt32 v = _errorFlags;
|
|
if (!_isArc)
|
|
v |= kpv_ErrorFlags_IsNotArc;
|
|
prop = v;
|
|
break;
|
|
}
|
|
|
|
case kpidWarningFlags:
|
|
{
|
|
if (_warningFlags != 0)
|
|
prop = _warningFlags;
|
|
break;
|
|
}
|
|
|
|
case kpidExtension:
|
|
if (_arcs.Size() == 1)
|
|
{
|
|
if (_arcInfo.Is_VolNumber_Defined())
|
|
{
|
|
char sz[16];
|
|
ConvertUInt32ToString((UInt32)_arcInfo.VolNumber + 1, sz);
|
|
unsigned len = MyStringLen(sz);
|
|
AString s = "part";
|
|
for (; len < 2; len++)
|
|
s += '0';
|
|
s += sz;
|
|
s += ".rar";
|
|
prop = s;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
|
|
{
|
|
*numItems = _refItems.Size();
|
|
return S_OK;
|
|
}
|
|
|
|
static bool RarTimeToFileTime(const CRarTime &rarTime, FILETIME &result)
|
|
{
|
|
if (!NTime::DosTimeToFileTime(rarTime.DosTime, result))
|
|
return false;
|
|
UInt64 value = (((UInt64)result.dwHighDateTime) << 32) + result.dwLowDateTime;
|
|
value += (UInt64)rarTime.LowSecond * 10000000;
|
|
value += ((UInt64)rarTime.SubTime[2] << 16) +
|
|
((UInt64)rarTime.SubTime[1] << 8) +
|
|
((UInt64)rarTime.SubTime[0]);
|
|
result.dwLowDateTime = (DWORD)value;
|
|
result.dwHighDateTime = DWORD(value >> 32);
|
|
return true;
|
|
}
|
|
|
|
static void RarTimeToProp(const CRarTime &rarTime, NCOM::CPropVariant &prop)
|
|
{
|
|
FILETIME localFileTime, utcFileTime;
|
|
if (RarTimeToFileTime(rarTime, localFileTime))
|
|
{
|
|
if (!LocalFileTimeToFileTime(&localFileTime, &utcFileTime))
|
|
utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0;
|
|
}
|
|
else
|
|
utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0;
|
|
prop = utcFileTime;
|
|
}
|
|
|
|
STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
|
|
{
|
|
COM_TRY_BEGIN
|
|
NCOM::CPropVariant prop;
|
|
const CRefItem &refItem = _refItems[index];
|
|
const CItem &item = _items[refItem.ItemIndex];
|
|
const CItem &lastItem = _items[refItem.ItemIndex + refItem.NumItems - 1];
|
|
|
|
/*
|
|
const CItem *mainItem = &item;
|
|
if (item.BaseFileIndex >= 0)
|
|
mainItem = &_items[_refItems[item.BaseFileIndex].ItemIndex];
|
|
*/
|
|
switch (propID)
|
|
{
|
|
case kpidPath:
|
|
{
|
|
/*
|
|
UString u;
|
|
if (item.BaseFileIndex >= 0)
|
|
u = mainItem->GetName();
|
|
u += item.GetName();
|
|
*/
|
|
prop = (const wchar_t *)NItemName::WinNameToOSName(item.GetName());
|
|
break;
|
|
}
|
|
case kpidIsDir: prop = item.IsDir(); break;
|
|
case kpidSize: if (lastItem.Is_Size_Defined()) prop = lastItem.Size; break;
|
|
case kpidPackSize: prop = GetPackSize(index); break;
|
|
case kpidMTime: RarTimeToProp(item.MTime, prop); break;
|
|
case kpidCTime: if (item.CTimeDefined) RarTimeToProp(item.CTime, prop); break;
|
|
case kpidATime: if (item.ATimeDefined) RarTimeToProp(item.ATime, prop); break;
|
|
case kpidAttrib: prop = item.GetWinAttrib(); break;
|
|
case kpidEncrypted: prop = item.IsEncrypted(); break;
|
|
case kpidSolid: prop = IsSolid(index); break;
|
|
case kpidCommented: prop = item.IsCommented(); break;
|
|
case kpidSplitBefore: prop = item.IsSplitBefore(); break;
|
|
case kpidSplitAfter: prop = _items[refItem.ItemIndex + refItem.NumItems - 1].IsSplitAfter(); break;
|
|
case kpidCRC:
|
|
{
|
|
prop = ((lastItem.IsSplitAfter()) ? item.FileCRC : lastItem.FileCRC);
|
|
break;
|
|
}
|
|
case kpidUnpackVer: prop = item.UnPackVersion; break;
|
|
case kpidMethod:
|
|
{
|
|
char s[16];
|
|
Byte m = item.Method;
|
|
if (m < (Byte)'0' || m > (Byte)'5')
|
|
ConvertUInt32ToString(m, s);
|
|
else
|
|
{
|
|
s[0] = 'm';
|
|
s[1] = (char)m;
|
|
s[2] = 0;
|
|
if (!item.IsDir())
|
|
{
|
|
s[2] = ':';
|
|
ConvertUInt32ToString(16 + item.GetDictSize(), &s[3]);
|
|
}
|
|
}
|
|
prop = s;
|
|
break;
|
|
}
|
|
case kpidHostOS: prop = (item.HostOS < ARRAY_SIZE(kHostOS)) ? kHostOS[item.HostOS] : kUnknownOS; break;
|
|
}
|
|
prop.Detach(value);
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
|
|
HRESULT CHandler::Open2(IInStream *stream,
|
|
const UInt64 *maxCheckStartPosition,
|
|
IArchiveOpenCallback *openCallback)
|
|
{
|
|
{
|
|
CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
|
|
CMyComPtr<ICryptoGetTextPassword> getTextPassword;
|
|
|
|
CVolumeName seqName;
|
|
|
|
UInt64 totalBytes = 0;
|
|
UInt64 curBytes = 0;
|
|
|
|
if (openCallback)
|
|
{
|
|
openCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);
|
|
openCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getTextPassword);
|
|
}
|
|
|
|
bool nextVol_is_Required = false;
|
|
|
|
CInArchive archive;
|
|
|
|
for (;;)
|
|
{
|
|
CMyComPtr<IInStream> inStream;
|
|
if (!_arcs.IsEmpty())
|
|
{
|
|
if (!openVolumeCallback)
|
|
break;
|
|
|
|
if (_arcs.Size() == 1)
|
|
{
|
|
if (!_arcInfo.IsVolume())
|
|
break;
|
|
UString baseName;
|
|
{
|
|
NCOM::CPropVariant prop;
|
|
RINOK(openVolumeCallback->GetProperty(kpidName, &prop));
|
|
if (prop.vt != VT_BSTR)
|
|
break;
|
|
baseName = prop.bstrVal;
|
|
}
|
|
if (!seqName.InitName(baseName, _arcInfo.HaveNewVolumeName()))
|
|
break;
|
|
/*
|
|
if (_arcInfo.HaveNewVolumeName() && !_arcInfo.IsFirstVolume())
|
|
{
|
|
seqName.MakeBeforeFirstName();
|
|
}
|
|
*/
|
|
}
|
|
|
|
const UString volName = seqName.GetNextName();
|
|
|
|
HRESULT result = openVolumeCallback->GetStream(volName, &inStream);
|
|
|
|
if (result != S_OK && result != S_FALSE)
|
|
return result;
|
|
|
|
if (!inStream || result != S_OK)
|
|
{
|
|
if (nextVol_is_Required)
|
|
_missingVolName = volName;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
inStream = stream;
|
|
|
|
UInt64 endPos = 0;
|
|
RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos));
|
|
RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL));
|
|
if (openCallback)
|
|
{
|
|
totalBytes += endPos;
|
|
RINOK(openCallback->SetTotal(NULL, &totalBytes));
|
|
}
|
|
|
|
RINOK(archive.Open(inStream, maxCheckStartPosition));
|
|
_isArc = true;
|
|
CItem item;
|
|
|
|
for (;;)
|
|
{
|
|
if (archive.m_Position > endPos)
|
|
{
|
|
_errorFlags |= kpv_ErrorFlags_UnexpectedEnd;
|
|
break;
|
|
}
|
|
|
|
EErrorType error;
|
|
// bool decryptionError;
|
|
// AString errorMessageLoc;
|
|
bool filled;
|
|
HRESULT result = archive.GetNextItem(item, getTextPassword, filled, error);
|
|
|
|
if (error != k_ErrorType_OK)
|
|
{
|
|
if (error == k_ErrorType_UnexpectedEnd)
|
|
_errorFlags |= kpv_ErrorFlags_UnexpectedEnd;
|
|
else if (error == k_ErrorType_Corrupted)
|
|
_errorFlags |= kpv_ErrorFlags_HeadersError;
|
|
else if (error == k_ErrorType_DecryptionError)
|
|
_errorFlags |= kpv_ErrorFlags_EncryptedHeadersError;
|
|
|
|
// AddErrorMessage(errorMessageLoc);
|
|
}
|
|
RINOK(result);
|
|
|
|
if (!filled)
|
|
{
|
|
if (error == k_ErrorType_DecryptionError && _items.IsEmpty())
|
|
return S_FALSE;
|
|
|
|
if (archive.ArcInfo.ExtraZeroTail_is_Possible())
|
|
{
|
|
/* if there is recovery record for multivolume archive,
|
|
RAR adds 18 bytes (ZERO bytes) at the end for alignment.
|
|
We must skip these bytes to prevent phySize warning. */
|
|
RINOK(inStream->Seek(archive.ArcInfo.EndPos, STREAM_SEEK_SET, NULL));
|
|
bool areThereNonZeros;
|
|
UInt64 numZeros;
|
|
const UInt64 maxSize = 1 << 12;
|
|
RINOK(ReadZeroTail(inStream, areThereNonZeros, numZeros, maxSize));
|
|
if (!areThereNonZeros && numZeros != 0 && numZeros <= maxSize)
|
|
archive.ArcInfo.EndPos += numZeros;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (item.IgnoreItem())
|
|
continue;
|
|
|
|
bool needAdd = true;
|
|
|
|
if (item.IsSplitBefore())
|
|
{
|
|
if (!_refItems.IsEmpty())
|
|
{
|
|
CRefItem &refItem = _refItems.Back();
|
|
refItem.NumItems++;
|
|
needAdd = false;
|
|
}
|
|
}
|
|
|
|
if (needAdd)
|
|
{
|
|
CRefItem refItem;
|
|
refItem.ItemIndex = _items.Size();
|
|
refItem.NumItems = 1;
|
|
refItem.VolumeIndex = _arcs.Size();
|
|
_refItems.Add(refItem);
|
|
}
|
|
|
|
_items.Add(item);
|
|
|
|
if (openCallback && _items.Size() % 100 == 0)
|
|
{
|
|
UInt64 numFiles = _items.Size();
|
|
UInt64 numBytes = curBytes + item.Position;
|
|
RINOK(openCallback->SetCompleted(&numFiles, &numBytes));
|
|
}
|
|
}
|
|
|
|
if (archive.HeaderErrorWarning)
|
|
_warningFlags |= kpv_ErrorFlags_HeadersError;
|
|
|
|
/*
|
|
if (archive.m_Position < endPos)
|
|
_warningFlags |= kpv_ErrorFlags_DataAfterEnd;
|
|
*/
|
|
if (_arcs.IsEmpty())
|
|
_arcInfo = archive.ArcInfo;
|
|
// _arcInfo.EndPos = archive.EndPos;
|
|
|
|
curBytes += endPos;
|
|
{
|
|
CArc &arc = _arcs.AddNew();
|
|
arc.PhySize = archive.ArcInfo.GetPhySize();
|
|
arc.Stream = inStream;
|
|
}
|
|
|
|
nextVol_is_Required = false;
|
|
|
|
if (!archive.ArcInfo.IsVolume())
|
|
break;
|
|
|
|
if (archive.ArcInfo.EndOfArchive_was_Read)
|
|
{
|
|
if (!archive.ArcInfo.AreMoreVolumes())
|
|
break;
|
|
nextVol_is_Required = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
int baseFileIndex = -1;
|
|
for (unsigned i = 0; i < _refItems.Size(); i++)
|
|
{
|
|
CItem &item = _items[_refItems[i].ItemIndex];
|
|
if (item.IsAltStream)
|
|
item.BaseFileIndex = baseFileIndex;
|
|
else
|
|
baseFileIndex = i;
|
|
}
|
|
*/
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Open(IInStream *stream,
|
|
const UInt64 *maxCheckStartPosition,
|
|
IArchiveOpenCallback *openCallback)
|
|
{
|
|
COM_TRY_BEGIN
|
|
Close();
|
|
// try
|
|
{
|
|
HRESULT res = Open2(stream, maxCheckStartPosition, openCallback);
|
|
/*
|
|
if (res != S_OK)
|
|
Close();
|
|
*/
|
|
|
|
return res;
|
|
}
|
|
// catch(const CInArchiveException &) { Close(); return S_FALSE; }
|
|
// catch(...) { Close(); throw; }
|
|
COM_TRY_END
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Close()
|
|
{
|
|
COM_TRY_BEGIN
|
|
// _errorMessage.Empty();
|
|
_missingVolName.Empty();
|
|
_errorFlags = 0;
|
|
_warningFlags = 0;
|
|
_isArc = false;
|
|
_refItems.Clear();
|
|
_items.Clear();
|
|
_arcs.Clear();
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
struct CMethodItem
|
|
{
|
|
Byte RarUnPackVersion;
|
|
CMyComPtr<ICompressCoder> Coder;
|
|
};
|
|
|
|
|
|
class CVolsInStream:
|
|
public ISequentialInStream,
|
|
public CMyUnknownImp
|
|
{
|
|
UInt64 _rem;
|
|
ISequentialInStream *_stream;
|
|
const CObjectVector<CArc> *_arcs;
|
|
const CObjectVector<CItem> *_items;
|
|
CRefItem _refItem;
|
|
unsigned _curIndex;
|
|
UInt32 _crc;
|
|
bool _calcCrc;
|
|
|
|
public:
|
|
MY_UNKNOWN_IMP
|
|
|
|
STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
|
|
|
|
void Init(const CObjectVector<CArc> *arcs,
|
|
const CObjectVector<CItem> *items,
|
|
const CRefItem &refItem)
|
|
{
|
|
_arcs = arcs;
|
|
_items = items;
|
|
_refItem = refItem;
|
|
_curIndex = 0;
|
|
_stream = NULL;
|
|
CrcIsOK = true;
|
|
}
|
|
|
|
bool CrcIsOK;
|
|
};
|
|
|
|
|
|
STDMETHODIMP CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize)
|
|
{
|
|
if (processedSize)
|
|
*processedSize = 0;
|
|
UInt32 realProcessedSize = 0;
|
|
|
|
while (size != 0)
|
|
{
|
|
if (!_stream)
|
|
{
|
|
if (_curIndex >= _refItem.NumItems)
|
|
break;
|
|
const CItem &item = (*_items)[_refItem.ItemIndex + _curIndex];
|
|
IInStream *s = (*_arcs)[_refItem.VolumeIndex + _curIndex].Stream;
|
|
RINOK(s->Seek(item.GetDataPosition(), STREAM_SEEK_SET, NULL));
|
|
_stream = s;
|
|
_calcCrc = (CrcIsOK && item.IsSplitAfter());
|
|
_crc = CRC_INIT_VAL;
|
|
_rem = item.PackSize;
|
|
}
|
|
{
|
|
UInt32 cur = size;
|
|
if (cur > _rem)
|
|
cur = (UInt32)_rem;
|
|
UInt32 num = cur;
|
|
HRESULT res = _stream->Read(data, cur, &cur);
|
|
if (_calcCrc)
|
|
_crc = CrcUpdate(_crc, data, cur);
|
|
realProcessedSize += cur;
|
|
if (processedSize)
|
|
*processedSize = realProcessedSize;
|
|
data = (Byte *)data + cur;
|
|
size -= cur;
|
|
_rem -= cur;
|
|
if (_rem == 0)
|
|
{
|
|
const CItem &item = (*_items)[_refItem.ItemIndex + _curIndex];
|
|
_curIndex++;
|
|
if (_calcCrc && CRC_GET_DIGEST(_crc) != item.FileCRC)
|
|
CrcIsOK = false;
|
|
_stream = NULL;
|
|
}
|
|
if (res != S_OK)
|
|
return res;
|
|
if (realProcessedSize != 0)
|
|
return S_OK;
|
|
if (cur == 0 && num != 0)
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
|
|
Int32 testMode, IArchiveExtractCallback *extractCallback)
|
|
{
|
|
COM_TRY_BEGIN
|
|
CMyComPtr<ICryptoGetTextPassword> getTextPassword;
|
|
UInt64 censoredTotalUnPacked = 0,
|
|
// censoredTotalPacked = 0,
|
|
importantTotalUnPacked = 0;
|
|
// importantTotalPacked = 0;
|
|
bool allFilesMode = (numItems == (UInt32)(Int32)-1);
|
|
if (allFilesMode)
|
|
numItems = _refItems.Size();
|
|
if (numItems == 0)
|
|
return S_OK;
|
|
unsigned lastIndex = 0;
|
|
CRecordVector<unsigned> importantIndexes;
|
|
CRecordVector<bool> extractStatuses;
|
|
|
|
bool isThereUndefinedSize = false;
|
|
|
|
for (UInt32 t = 0; t < numItems; t++)
|
|
{
|
|
unsigned index = allFilesMode ? t : indices[t];
|
|
|
|
{
|
|
const CRefItem &refItem = _refItems[index];
|
|
const CItem &item = _items[refItem.ItemIndex + refItem.NumItems - 1];
|
|
|
|
if (item.Is_Size_Defined())
|
|
censoredTotalUnPacked += item.Size;
|
|
else
|
|
isThereUndefinedSize = true;
|
|
|
|
// censoredTotalPacked += item.PackSize;
|
|
}
|
|
|
|
unsigned j;
|
|
for (j = lastIndex; j <= index; j++)
|
|
// if (!_items[_refItems[j].ItemIndex].IsSolid())
|
|
if (!IsSolid(j))
|
|
lastIndex = j;
|
|
|
|
for (j = lastIndex; j <= index; j++)
|
|
{
|
|
const CRefItem &refItem = _refItems[j];
|
|
const CItem &item = _items[refItem.ItemIndex + refItem.NumItems - 1];
|
|
|
|
if (item.Is_Size_Defined())
|
|
importantTotalUnPacked += item.Size;
|
|
else
|
|
isThereUndefinedSize = true;
|
|
// importantTotalPacked += item.PackSize;
|
|
importantIndexes.Add(j);
|
|
extractStatuses.Add(j == index);
|
|
}
|
|
|
|
lastIndex = index + 1;
|
|
}
|
|
|
|
if (importantTotalUnPacked != 0 || !isThereUndefinedSize)
|
|
{
|
|
RINOK(extractCallback->SetTotal(importantTotalUnPacked));
|
|
}
|
|
|
|
UInt64 currentImportantTotalUnPacked = 0;
|
|
UInt64 currentImportantTotalPacked = 0;
|
|
UInt64 currentUnPackSize, currentPackSize;
|
|
|
|
CObjectVector<CMethodItem> methodItems;
|
|
|
|
NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder;
|
|
CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
|
|
|
|
CFilterCoder *filterStreamSpec = new CFilterCoder(false);
|
|
CMyComPtr<ISequentialInStream> filterStream = filterStreamSpec;
|
|
|
|
NCrypto::NRar2::CDecoder *rar20CryptoDecoderSpec = NULL;
|
|
CMyComPtr<ICompressFilter> rar20CryptoDecoder;
|
|
NCrypto::NRar3::CDecoder *rar3CryptoDecoderSpec = NULL;
|
|
CMyComPtr<ICompressFilter> rar3CryptoDecoder;
|
|
|
|
CVolsInStream *volsInStreamSpec = NULL;
|
|
CMyComPtr<ISequentialInStream> volsInStream;
|
|
|
|
CLocalProgress *lps = new CLocalProgress;
|
|
CMyComPtr<ICompressProgressInfo> progress = lps;
|
|
lps->Init(extractCallback, false);
|
|
|
|
bool solidStart = true;
|
|
|
|
for (unsigned i = 0;;
|
|
i++,
|
|
currentImportantTotalUnPacked += currentUnPackSize,
|
|
currentImportantTotalPacked += currentPackSize)
|
|
{
|
|
lps->InSize = currentImportantTotalPacked;
|
|
lps->OutSize = currentImportantTotalUnPacked;
|
|
RINOK(lps->SetCur());
|
|
|
|
if (i >= importantIndexes.Size())
|
|
break;
|
|
|
|
CMyComPtr<ISequentialOutStream> realOutStream;
|
|
|
|
Int32 askMode;
|
|
if (extractStatuses[i])
|
|
askMode = testMode ?
|
|
NExtract::NAskMode::kTest :
|
|
NExtract::NAskMode::kExtract;
|
|
else
|
|
askMode = NExtract::NAskMode::kSkip;
|
|
|
|
UInt32 index = importantIndexes[i];
|
|
|
|
const CRefItem &refItem = _refItems[index];
|
|
const CItem &item = _items[refItem.ItemIndex];
|
|
const CItem &lastItem = _items[refItem.ItemIndex + refItem.NumItems - 1];
|
|
|
|
UInt64 outSize = (UInt64)(Int64)-1;
|
|
currentUnPackSize = 0;
|
|
if (lastItem.Is_Size_Defined())
|
|
{
|
|
outSize = lastItem.Size;
|
|
currentUnPackSize = outSize;
|
|
}
|
|
|
|
currentPackSize = GetPackSize(index);
|
|
|
|
if (item.IgnoreItem())
|
|
continue;
|
|
|
|
RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
|
|
|
|
if (!IsSolid(index))
|
|
solidStart = true;
|
|
if (item.IsDir())
|
|
{
|
|
RINOK(extractCallback->PrepareOperation(askMode));
|
|
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
|
|
continue;
|
|
}
|
|
|
|
bool mustBeProcessedAnywhere = false;
|
|
if (i < importantIndexes.Size() - 1)
|
|
{
|
|
// const CRefItem &nextRefItem = _refItems[importantIndexes[i + 1]];
|
|
// const CItem &nextItemInfo = _items[nextRefItem.ItemIndex];
|
|
// mustBeProcessedAnywhere = nextItemInfo.IsSolid();
|
|
mustBeProcessedAnywhere = IsSolid(importantIndexes[i + 1]);
|
|
}
|
|
|
|
if (!mustBeProcessedAnywhere && !testMode && !realOutStream)
|
|
continue;
|
|
|
|
if (!realOutStream && !testMode)
|
|
askMode = NExtract::NAskMode::kSkip;
|
|
|
|
RINOK(extractCallback->PrepareOperation(askMode));
|
|
|
|
COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC;
|
|
CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
|
|
outStreamSpec->SetStream(realOutStream);
|
|
outStreamSpec->Init();
|
|
realOutStream.Release();
|
|
|
|
if (!volsInStream)
|
|
{
|
|
volsInStreamSpec = new CVolsInStream;
|
|
volsInStream = volsInStreamSpec;
|
|
}
|
|
|
|
volsInStreamSpec->Init(&_arcs, &_items, refItem);
|
|
|
|
UInt64 packSize = currentPackSize;
|
|
|
|
// packedPos += item.PackSize;
|
|
// unpackedPos += 0;
|
|
|
|
CMyComPtr<ISequentialInStream> inStream;
|
|
|
|
if (item.IsEncrypted())
|
|
{
|
|
// CMyComPtr<ICryptoSetPassword> cryptoSetPassword;
|
|
|
|
if (item.UnPackVersion >= 29)
|
|
{
|
|
if (!rar3CryptoDecoder)
|
|
{
|
|
rar3CryptoDecoderSpec = new NCrypto::NRar3::CDecoder;
|
|
rar3CryptoDecoder = rar3CryptoDecoderSpec;
|
|
}
|
|
// rar3CryptoDecoderSpec->SetRar350Mode(item.UnPackVersion < 36);
|
|
/*
|
|
CMyComPtr<ICompressSetDecoderProperties2> cryptoProperties;
|
|
RINOK(rar3CryptoDecoder.QueryInterface(IID_ICompressSetDecoderProperties2,
|
|
&cryptoProperties));
|
|
*/
|
|
RINOK(rar3CryptoDecoderSpec->SetDecoderProperties2(item.Salt, item.HasSalt() ? sizeof(item.Salt) : 0));
|
|
filterStreamSpec->Filter = rar3CryptoDecoder;
|
|
}
|
|
else if (item.UnPackVersion >= 20)
|
|
{
|
|
if (!rar20CryptoDecoder)
|
|
{
|
|
rar20CryptoDecoderSpec = new NCrypto::NRar2::CDecoder;
|
|
rar20CryptoDecoder = rar20CryptoDecoderSpec;
|
|
}
|
|
filterStreamSpec->Filter = rar20CryptoDecoder;
|
|
}
|
|
else
|
|
{
|
|
outStream.Release();
|
|
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod));
|
|
continue;
|
|
}
|
|
|
|
// RINOK(filterStreamSpec->Filter.QueryInterface(IID_ICryptoSetPassword, &cryptoSetPassword));
|
|
|
|
if (!getTextPassword)
|
|
extractCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getTextPassword);
|
|
|
|
if (!getTextPassword)
|
|
{
|
|
outStream.Release();
|
|
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod));
|
|
continue;
|
|
}
|
|
|
|
// if (getTextPassword)
|
|
{
|
|
CMyComBSTR password;
|
|
RINOK(getTextPassword->CryptoGetTextPassword(&password));
|
|
|
|
if (item.UnPackVersion >= 29)
|
|
{
|
|
unsigned len = 0;
|
|
if (password)
|
|
len = MyStringLen(password);
|
|
if (len > kPasswordLen_MAX)
|
|
len = kPasswordLen_MAX;
|
|
CByteArr buffer(len * 2);
|
|
for (unsigned k = 0; k < len; k++)
|
|
{
|
|
wchar_t c = password[k];
|
|
((Byte *)buffer)[k * 2] = (Byte)c;
|
|
((Byte *)buffer)[k * 2 + 1] = (Byte)(c >> 8);
|
|
}
|
|
rar3CryptoDecoderSpec->SetPassword((const Byte *)buffer, len * 2);
|
|
}
|
|
else
|
|
{
|
|
AString oemPassword;
|
|
if (password)
|
|
{
|
|
UString unicode = (LPCOLESTR)password;
|
|
if (unicode.Len() > kPasswordLen_MAX)
|
|
unicode.DeleteFrom(kPasswordLen_MAX);
|
|
oemPassword = UnicodeStringToMultiByte(unicode, CP_OEMCP);
|
|
}
|
|
rar20CryptoDecoderSpec->SetPassword((const Byte *)(const char *)oemPassword, oemPassword.Len());
|
|
}
|
|
}
|
|
/*
|
|
else
|
|
{
|
|
RINOK(cryptoSetPassword->CryptoSetPassword(NULL, 0));
|
|
}
|
|
*/
|
|
|
|
filterStreamSpec->SetInStream(volsInStream);
|
|
filterStreamSpec->SetOutStreamSize(NULL);
|
|
inStream = filterStream;
|
|
}
|
|
else
|
|
{
|
|
inStream = volsInStream;
|
|
}
|
|
|
|
CMyComPtr<ICompressCoder> commonCoder;
|
|
|
|
switch (item.Method)
|
|
{
|
|
case '0':
|
|
{
|
|
commonCoder = copyCoder;
|
|
break;
|
|
}
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
{
|
|
unsigned m;
|
|
for (m = 0; m < methodItems.Size(); m++)
|
|
if (methodItems[m].RarUnPackVersion == item.UnPackVersion)
|
|
break;
|
|
if (m == methodItems.Size())
|
|
{
|
|
CMethodItem mi;
|
|
mi.RarUnPackVersion = item.UnPackVersion;
|
|
|
|
mi.Coder.Release();
|
|
if (item.UnPackVersion <= 40)
|
|
{
|
|
UInt32 methodID = 0x40300;
|
|
if (item.UnPackVersion < 20)
|
|
methodID += 1;
|
|
else if (item.UnPackVersion < 29)
|
|
methodID += 2;
|
|
else
|
|
methodID += 3;
|
|
RINOK(CreateCoder(EXTERNAL_CODECS_VARS methodID, false, mi.Coder));
|
|
}
|
|
|
|
if (mi.Coder == 0)
|
|
{
|
|
outStream.Release();
|
|
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod));
|
|
continue;
|
|
}
|
|
|
|
m = methodItems.Add(mi);
|
|
}
|
|
CMyComPtr<ICompressCoder> decoder = methodItems[m].Coder;
|
|
|
|
CMyComPtr<ICompressSetDecoderProperties2> compressSetDecoderProperties;
|
|
RINOK(decoder.QueryInterface(IID_ICompressSetDecoderProperties2,
|
|
&compressSetDecoderProperties));
|
|
|
|
Byte isSolid = (Byte)((IsSolid(index) || item.IsSplitBefore()) ? 1: 0);
|
|
if (solidStart)
|
|
{
|
|
isSolid = 0;
|
|
solidStart = false;
|
|
}
|
|
|
|
|
|
RINOK(compressSetDecoderProperties->SetDecoderProperties2(&isSolid, 1));
|
|
|
|
commonCoder = decoder;
|
|
break;
|
|
}
|
|
default:
|
|
outStream.Release();
|
|
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod));
|
|
continue;
|
|
}
|
|
|
|
HRESULT result = commonCoder->Code(inStream, outStream, &packSize, &outSize, progress);
|
|
|
|
if (item.IsEncrypted())
|
|
filterStreamSpec->ReleaseInStream();
|
|
|
|
if (outSize == (UInt64)(Int64)-1)
|
|
currentUnPackSize = outStreamSpec->GetSize();
|
|
|
|
int opRes = (volsInStreamSpec->CrcIsOK && outStreamSpec->GetCRC() == lastItem.FileCRC) ?
|
|
NExtract::NOperationResult::kOK:
|
|
NExtract::NOperationResult::kCRCError;
|
|
outStream.Release();
|
|
|
|
if (result != S_OK)
|
|
{
|
|
if (result == S_FALSE)
|
|
opRes = NExtract::NOperationResult::kDataError;
|
|
else if (result == E_NOTIMPL)
|
|
opRes = NExtract::NOperationResult::kUnsupportedMethod;
|
|
else
|
|
return result;
|
|
}
|
|
RINOK(extractCallback->SetOperationResult(opRes));
|
|
}
|
|
|
|
return S_OK;
|
|
COM_TRY_END
|
|
}
|
|
|
|
IMPL_ISetCompressCodecsInfo
|
|
|
|
REGISTER_ARC_I(
|
|
"Rar", "rar r00", 0, 3,
|
|
kMarker,
|
|
0,
|
|
NArcInfoFlags::kFindSignature,
|
|
NULL)
|
|
|
|
}}
|