p7zip/CPP/7zip/Archive/Rar/Rar5Handler.cpp
2017-10-11 12:35:36 +02:00

2747 lines
63 KiB
C++

// Rar5Handler.cpp
#include "StdAfx.h"
#include "../../../../C/7zCrc.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/FilterCoder.h"
#include "../../Common/LimitedStreams.h"
#include "../../Common/ProgressUtils.h"
#include "../../Common/RegisterArc.h"
#include "../../Common/StreamObjects.h"
#include "../../Common/StreamUtils.h"
#include "../../Common/RegisterCodec.h"
#include "../../Compress/CopyCoder.h"
#include "../../Crypto/Rar5Aes.h"
#include "../Common/FindSignature.h"
#include "../Common/ItemNameUtils.h"
#include "../HandlerCont.h"
#include "RarVol.h"
#include "Rar5Handler.h"
using namespace NWindows;
#define Get32(p) GetUi32(p)
namespace NArchive {
namespace NRar5 {
static const unsigned kMarkerSize = 8;
#define SIGNATURE { 0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0 }
static const Byte kMarker[kMarkerSize] = SIGNATURE;
static const size_t kCommentSize_Max = (size_t)1 << 16;
static const char * const kHostOS[] =
{
"Windows"
, "Unix"
};
static const CUInt32PCharPair k_ArcFlags[] =
{
{ 0, "Volume" },
{ 1, "VolumeField" },
{ 2, "Solid" },
{ 3, "Recovery" },
{ 4, "Lock" }
};
template <unsigned alignMask>
struct CAlignedBuffer
{
Byte *_buf;
Byte *_bufBase;
size_t _size;
CAlignedBuffer(): _buf(NULL), _bufBase(NULL), _size(0) {}
~CAlignedBuffer() { ::MyFree(_bufBase); }
public:
operator Byte *() { return _buf; }
operator const Byte *() const { return _buf; }
void AllocAtLeast(size_t size)
{
if (_buf && _size >= size)
return;
::MyFree(_bufBase);
_buf = NULL;
_size = 0;
_bufBase = (Byte *)::MyAlloc(size + alignMask);
if (_bufBase)
{
_size = size;
// _buf = (Byte *)(((uintptr_t)_bufBase + alignMask) & ~(uintptr_t)alignMask);
_buf = (Byte *)(((ptrdiff_t)_bufBase + alignMask) & ~(ptrdiff_t)alignMask);
}
}
};
static unsigned ReadVarInt(const Byte *p, size_t maxSize, UInt64 *val)
{
*val = 0;
for (unsigned i = 0; i < maxSize;)
{
Byte b = p[i];
if (i < 10)
*val |= (UInt64)(b & 0x7F) << (7 * i++);
if ((b & 0x80) == 0)
return i;
}
return 0;
}
int CItem::FindExtra(unsigned type, unsigned &recordDataSize) const
{
recordDataSize = 0;
size_t offset = 0;
for (;;)
{
size_t rem = Extra.Size() - offset;
if (rem == 0)
return -1;
{
UInt64 size;
unsigned num = ReadVarInt(Extra + offset, rem, &size);
if (num == 0)
return -1;
offset += num;
rem -= num;
if (size > rem)
return -1;
rem = (size_t)size;
}
{
UInt64 type2;
unsigned num = ReadVarInt(Extra + offset, rem, &type2);
if (num == 0)
return -1;
offset += num;
rem -= num;
// There was BUG in RAR 5.21- : it stored (size-1) instead of (size)
// for Subdata record in Service header.
// That record always was last in bad archives, so we can fix that case.
if (type2 == NExtraRecordType::kSubdata
&& RecordType == NHeaderType::kService
&& rem + 1 == Extra.Size() - offset)
rem++;
if (type2 == type)
{
recordDataSize = (unsigned)rem;
return (int)offset;
}
offset += rem;
}
}
}
bool CCryptoInfo::Parse(const Byte *p, size_t size)
{
unsigned num = ReadVarInt(p, size, &Algo);
if (num == 0) return false; p += num; size -= num;
num = ReadVarInt(p, size, &Flags);
if (num == 0) return false; p += num; size -= num;
if (size != 1 + 16 + 16 + (unsigned)(IsThereCheck() ? 12 : 0))
return false;
Cnt = p[0];
return true;
}
bool CItem::FindExtra_Version(UInt64 &version) const
{
unsigned size;
int offset = FindExtra(NExtraRecordType::kVersion, size);
if (offset < 0)
return false;
const Byte *p = Extra + (unsigned)offset;
UInt64 flags;
unsigned num = ReadVarInt(p, size, &flags);
if (num == 0) return false; p += num; size -= num;
num = ReadVarInt(p, size, &version);
if (num == 0) return false; p += num; size -= num;
return size == 0;
}
bool CItem::FindExtra_Link(CLinkInfo &link) const
{
unsigned size;
int offset = FindExtra(NExtraRecordType::kLink, size);
if (offset < 0)
return false;
const Byte *p = Extra + (unsigned)offset;
unsigned num = ReadVarInt(p, size, &link.Type);
if (num == 0) return false; p += num; size -= num;
num = ReadVarInt(p, size, &link.Flags);
if (num == 0) return false; p += num; size -= num;
UInt64 len;
num = ReadVarInt(p, size, &len);
if (num == 0) return false; p += num; size -= num;
if (size != len)
return false;
link.NameLen = (unsigned)len;
link.NameOffset = (unsigned)(p - Extra);
return true;
}
bool CItem::Is_CopyLink() const
{
CLinkInfo link;
return FindExtra_Link(link) && link.Type == NLinkType::kFileCopy;
}
void CItem::Link_to_Prop(unsigned linkType, NWindows::NCOM::CPropVariant &prop) const
{
CLinkInfo link;
if (!FindExtra_Link(link))
return;
if (link.Type != linkType)
{
if (linkType != NLinkType::kUnixSymLink)
return;
switch ((unsigned)link.Type)
{
case NLinkType::kUnixSymLink:
case NLinkType::kWinSymLink:
case NLinkType::kWinJunction:
break;
default: return;
}
}
AString s;
s.SetFrom_CalcLen((const char *)(Extra + link.NameOffset), link.NameLen);
UString unicode;
if (ConvertUTF8ToUnicode(s, unicode))
prop = NItemName::GetOSName(unicode);
}
bool CItem::GetAltStreamName(AString &name) const
{
name.Empty();
unsigned size;
int offset = FindExtra(NExtraRecordType::kSubdata, size);
if (offset < 0)
return false;
name.SetFrom_CalcLen((const char *)(Extra + (unsigned)offset), size);
return true;
}
class CHash
{
bool _calcCRC;
UInt32 _crc;
int _blakeOffset;
CBlake2sp _blake;
public:
void Init_NoCalc()
{
_calcCRC = false;
_crc = CRC_INIT_VAL;
_blakeOffset = -1;
}
void Init(const CItem &item);
void Update(const void *data, size_t size);
UInt32 GetCRC() const { return CRC_GET_DIGEST(_crc); }
bool Check(const CItem &item, NCrypto::NRar5::CDecoder *cryptoDecoderSpec);
};
void CHash::Init(const CItem &item)
{
_crc = CRC_INIT_VAL;
_calcCRC = item.Has_CRC();
_blakeOffset = item.FindExtra_Blake();
if (_blakeOffset >= 0)
Blake2sp_Init(&_blake);
}
void CHash::Update(const void *data, size_t size)
{
if (_calcCRC)
_crc = CrcUpdate(_crc, data, size);
if (_blakeOffset >= 0)
Blake2sp_Update(&_blake, (const Byte *)data, size);
}
bool CHash::Check(const CItem &item, NCrypto::NRar5::CDecoder *cryptoDecoderSpec)
{
if (_calcCRC)
{
UInt32 crc = GetCRC();
if (cryptoDecoderSpec)
crc = cryptoDecoderSpec->Hmac_Convert_Crc32(crc);
if (crc != item.CRC)
return false;
}
if (_blakeOffset >= 0)
{
Byte digest[BLAKE2S_DIGEST_SIZE];
Blake2sp_Final(&_blake, digest);
if (cryptoDecoderSpec)
cryptoDecoderSpec->Hmac_Convert_32Bytes(digest);
if (memcmp(digest, &item.Extra[(unsigned)_blakeOffset], BLAKE2S_DIGEST_SIZE) != 0)
return false;
}
return true;
}
class COutStreamWithHash:
public ISequentialOutStream,
public CMyUnknownImp
{
ISequentialOutStream *_stream;
UInt64 _pos;
UInt64 _size;
bool _size_Defined;
Byte *_destBuf;
public:
CHash _hash;
COutStreamWithHash(): _destBuf(NULL) {}
MY_UNKNOWN_IMP
STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
void SetStream(ISequentialOutStream *stream) { _stream = stream; }
void Init(const CItem &item, Byte *destBuf)
{
_size_Defined = false;
_size = 0;
_destBuf = NULL;
if (!item.Is_UnknownSize())
{
_size_Defined = true;
_size = item.Size;
_destBuf = destBuf;
}
_pos = 0;
_hash.Init(item);
}
UInt64 GetPos() const { return _pos; }
};
STDMETHODIMP COutStreamWithHash::Write(const void *data, UInt32 size, UInt32 *processedSize)
{
HRESULT result = S_OK;
if (_size_Defined)
{
UInt64 rem = _size - _pos;
if (size > rem)
size = (UInt32)rem;
}
if (_stream)
result = _stream->Write(data, size, &size);
if (_destBuf)
memcpy(_destBuf + (size_t)_pos, data, size);
_hash.Update(data, size);
_pos += size;
if (processedSize)
*processedSize = size;
return result;
}
class CInArchive
{
CAlignedBuffer<AES_BLOCK_SIZE - 1> _buf;
size_t _bufSize;
size_t _bufPos;
ISequentialInStream *_stream;
NCrypto::NRar5::CDecoder *m_CryptoDecoderSpec;
CMyComPtr<ICompressFilter> m_CryptoDecoder;
HRESULT ReadStream_Check(void *data, size_t size);
public:
bool m_CryptoMode;
bool WrongPassword;
bool IsArc;
bool UnexpectedEnd;
UInt64 StreamStartPosition;
UInt64 Position;
bool ReadVar(UInt64 &val);
struct CHeader
{
UInt64 Type;
UInt64 Flags;
size_t ExtraSize;
UInt64 DataSize;
};
HRESULT ReadBlockHeader(CHeader &h);
bool ReadFileHeader(const CHeader &header, CItem &item);
void AddToSeekValue(UInt64 addValue)
{
Position += addValue;
}
HRESULT Open(IInStream *inStream, const UInt64 *searchHeaderSizeLimit, ICryptoGetTextPassword *getTextPassword,
CInArcInfo &info);
};
static HRESULT MySetPassword(ICryptoGetTextPassword *getTextPassword, NCrypto::NRar5::CDecoder *cryptoDecoderSpec)
{
CMyComBSTR password;
RINOK(getTextPassword->CryptoGetTextPassword(&password));
AString utf8;
const unsigned kPasswordLen_MAX = 127;
UString unicode = (LPCOLESTR)password;
if (unicode.Len() > kPasswordLen_MAX)
unicode.DeleteFrom(kPasswordLen_MAX);
ConvertUnicodeToUTF8(unicode, utf8);
cryptoDecoderSpec->SetPassword((const Byte *)(const char *)utf8, utf8.Len());
return S_OK;
}
bool CInArchive::ReadVar(UInt64 &val)
{
unsigned offset = ReadVarInt(_buf + _bufPos, _bufSize - _bufPos, &val);
_bufPos += offset;
return (offset != 0);
}
HRESULT CInArchive::ReadStream_Check(void *data, size_t size)
{
size_t size2 = size;
RINOK(ReadStream(_stream, data, &size2));
if (size2 == size)
return S_OK;
UnexpectedEnd = true;
return S_FALSE;
}
HRESULT CInArchive::ReadBlockHeader(CHeader &h)
{
h.Type = 0;
h.Flags = 0;
h.ExtraSize = 0;
h.DataSize = 0;
const unsigned kStartSize = 4 + 3;
const unsigned kBufSize = AES_BLOCK_SIZE + AES_BLOCK_SIZE; // must be >= kStartSize;
Byte buf[kBufSize];
unsigned filled;
if (m_CryptoMode)
{
RINOK(ReadStream_Check(buf, kBufSize));
memcpy(m_CryptoDecoderSpec->_iv, buf, AES_BLOCK_SIZE);
RINOK(m_CryptoDecoderSpec->Init());
_buf.AllocAtLeast(1 << 12);
if (!(Byte *)_buf)
return E_OUTOFMEMORY;
memcpy(_buf, buf + AES_BLOCK_SIZE, AES_BLOCK_SIZE);
if (m_CryptoDecoderSpec->Filter(_buf, AES_BLOCK_SIZE) != AES_BLOCK_SIZE)
return E_FAIL;
memcpy(buf, _buf, AES_BLOCK_SIZE);
filled = AES_BLOCK_SIZE;
}
else
{
RINOK(ReadStream_Check(buf, kStartSize));
filled = kStartSize;
}
UInt64 val;
unsigned offset = ReadVarInt(buf + 4, 3, &val);
if (offset == 0)
return S_FALSE;
{
size_t size = (size_t)val;
_bufPos = (4 + offset);
_bufSize = _bufPos + size;
if (size < 2)
return S_FALSE;
}
size_t allocSize = _bufSize;
if (m_CryptoMode)
allocSize = (allocSize + AES_BLOCK_SIZE - 1) & ~(size_t)(AES_BLOCK_SIZE - 1);
_buf.AllocAtLeast(allocSize);
if (!(Byte *)_buf)
return E_OUTOFMEMORY;
memcpy(_buf, buf, filled);
size_t rem = allocSize - filled;
AddToSeekValue(allocSize + (m_CryptoMode ? AES_BLOCK_SIZE : 0));
RINOK(ReadStream_Check(_buf + filled, rem));
if (m_CryptoMode)
{
if (m_CryptoDecoderSpec->Filter(_buf + filled, (UInt32)rem) != rem)
return E_FAIL;
}
if (CrcCalc(_buf + 4, _bufSize - 4) != Get32(buf))
return S_FALSE;
if (!ReadVar(h.Type)) return S_FALSE;
if (!ReadVar(h.Flags)) return S_FALSE;
if (h.Flags & NHeaderFlags::kExtra)
{
UInt64 extraSize;
if (!ReadVar(extraSize))
return S_FALSE;
if (extraSize > _bufSize)
return S_FALSE;
h.ExtraSize = (size_t)extraSize;
}
if (h.Flags & NHeaderFlags::kData)
{
if (!ReadVar(h.DataSize))
return S_FALSE;
}
return S_OK;
}
/*
int CInArcInfo::FindExtra(unsigned type, unsigned &recordDataSize) const
{
recordDataSize = 0;
size_t offset = 0;
for (;;)
{
size_t rem = Extra.Size() - offset;
if (rem == 0)
return -1;
{
UInt64 size;
unsigned num = ReadVarInt(Extra + offset, rem, &size);
if (num == 0)
return -1;
offset += num;
rem -= num;
if (size > rem)
return -1;
rem = (size_t)size;
}
{
UInt64 type2;
unsigned num = ReadVarInt(Extra + offset, rem, &type2);
if (num == 0)
return -1;
offset += num;
rem -= num;
if (type2 == type)
{
recordDataSize = (unsigned)rem;
return (int)offset;
}
offset += rem;
}
}
}
bool CInArcInfo::FindExtra_Locator(CLocator &locator) const
{
locator.Flags = 0;
locator.QuickOpen = 0;
locator.Recovery = 0;
unsigned size;
int offset = FindExtra(kArcExtraRecordType_Locator, size);
if (offset < 0)
return false;
const Byte *p = Extra + (unsigned)offset;
unsigned num;
num = ReadVarInt(p, size, &locator.Flags);
if (num == 0) return false; p += num; size -= num;
if (locator.Is_QuickOpen())
{
num = ReadVarInt(p, size, &locator.QuickOpen);
if (num == 0) return false; p += num; size -= num;
}
if (locator.Is_Recovery())
{
num = ReadVarInt(p, size, &locator.Recovery);
if (num == 0) return false; p += num; size -= num;
}
return true;
}
*/
HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit, ICryptoGetTextPassword *getTextPassword,
CInArcInfo &info)
{
m_CryptoMode = false;
WrongPassword = false;
IsArc = false;
UnexpectedEnd = false;
Position = StreamStartPosition;
UInt64 arcStartPos = StreamStartPosition;
{
Byte marker[kMarkerSize];
RINOK(ReadStream_FALSE(stream, marker, kMarkerSize));
if (memcmp(marker, kMarker, kMarkerSize) == 0)
Position += kMarkerSize;
else
{
if (searchHeaderSizeLimit && *searchHeaderSizeLimit == 0)
return S_FALSE;
RINOK(stream->Seek(StreamStartPosition, STREAM_SEEK_SET, NULL));
RINOK(FindSignatureInStream(stream, kMarker, kMarkerSize,
searchHeaderSizeLimit, arcStartPos));
arcStartPos += StreamStartPosition;
Position = arcStartPos + kMarkerSize;
RINOK(stream->Seek(Position, STREAM_SEEK_SET, NULL));
}
}
info.StartPos = arcStartPos;
_stream = stream;
CHeader h;
RINOK(ReadBlockHeader(h));
info.IsEncrypted = false;
if (h.Type == NHeaderType::kArcEncrypt)
{
info.IsEncrypted = true;
IsArc = true;
if (!getTextPassword)
return E_NOTIMPL;
m_CryptoMode = true;
if (!m_CryptoDecoder)
{
m_CryptoDecoderSpec = new NCrypto::NRar5::CDecoder;
m_CryptoDecoder = m_CryptoDecoderSpec;
}
RINOK(m_CryptoDecoderSpec->SetDecoderProps(
_buf + _bufPos, (unsigned)(_bufSize - _bufPos), false, false));
RINOK(MySetPassword(getTextPassword, m_CryptoDecoderSpec));
if (!m_CryptoDecoderSpec->CalcKey_and_CheckPassword())
{
WrongPassword = True;
return S_FALSE;
}
RINOK(ReadBlockHeader(h));
}
if (h.Type != NHeaderType::kArc)
return S_FALSE;
IsArc = true;
info.VolNumber = 0;
if (!ReadVar(info.Flags))
return S_FALSE;
if (info.Flags & NArcFlags::kVolNumber)
if (!ReadVar(info.VolNumber))
return S_FALSE;
if (h.ExtraSize != 0)
{
if (_bufSize - _bufPos < h.ExtraSize)
return S_FALSE;
/*
info.Extra.Alloc(h.ExtraSize);
memcpy(info.Extra, _buf + _bufPos, h.ExtraSize);
*/
_bufPos += h.ExtraSize;
/*
CInArcInfo::CLocator locator;
if (info.FindExtra_Locator(locator))
locator.Flags = locator.Flags;
*/
}
if (_bufPos != _bufSize)
return S_FALSE;
return S_OK;
}
bool CInArchive::ReadFileHeader(const CHeader &header, CItem &item)
{
item.UnixMTime = 0;
item.CRC = 0;
item.Flags = 0;
item.CommonFlags = (UInt32)header.Flags;
item.PackSize = header.DataSize;
UInt64 flags64;
if (!ReadVar(flags64)) return false;
item.Flags = (UInt32)flags64;
if (!ReadVar(item.Size)) return false;
{
UInt64 attrib;
if (!ReadVar(attrib)) return false;
item.Attrib = (UInt32)attrib;
}
if (item.Has_UnixMTime())
{
if (_bufSize - _bufPos < 4)
return false;
item.UnixMTime = Get32(_buf + _bufPos);
_bufPos += 4;
}
if (item.Has_CRC())
{
if (_bufSize - _bufPos < 4)
return false;
item.CRC = Get32(_buf + _bufPos);
_bufPos += 4;
}
{
UInt64 method;
if (!ReadVar(method)) return false;
item.Method = (UInt32)method;
}
if (!ReadVar(item.HostOS)) return false;
{
UInt64 len;
if (!ReadVar(len)) return false;
if (len > _bufSize - _bufPos)
return false;
item.Name.SetFrom_CalcLen((const char *)(_buf + _bufPos), (unsigned)len);
_bufPos += (unsigned)len;
}
item.Extra.Free();
size_t extraSize = header.ExtraSize;
if (extraSize != 0)
{
if (_bufSize - _bufPos < extraSize)
return false;
item.Extra.Alloc(extraSize);
memcpy(item.Extra, _buf + _bufPos, extraSize);
_bufPos += extraSize;
}
return (_bufPos == _bufSize);
}
struct CLinkFile
{
unsigned Index;
unsigned NumLinks;
CByteBuffer Data;
HRESULT Res;
bool crcOK;
CLinkFile(): Index(0), NumLinks(0), Res(S_OK), crcOK(true) {}
};
struct CUnpacker
{
NCompress::CCopyCoder *copyCoderSpec;
CMyComPtr<ICompressCoder> copyCoder;
CMyComPtr<ICompressCoder> LzCoders[2];
bool NeedClearSolid[2];
CFilterCoder *filterStreamSpec;
CMyComPtr<ISequentialInStream> filterStream;
NCrypto::NRar5::CDecoder *cryptoDecoderSpec;
CMyComPtr<ICompressFilter> cryptoDecoder;
CMyComPtr<ICryptoGetTextPassword> getTextPassword;
COutStreamWithHash *outStreamSpec;
CMyComPtr<ISequentialOutStream> outStream;
CByteBuffer _tempBuf;
CLinkFile *linkFile;
CUnpacker(): linkFile(NULL) { NeedClearSolid[0] = NeedClearSolid[1] = true; }
HRESULT Create(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, bool isSolid, bool &wrongPassword);
HRESULT Code(const CItem &item, const CItem &lastItem, UInt64 packSize,
ISequentialInStream *inStream, ISequentialOutStream *outStream, ICompressProgressInfo *progress,
bool &isCrcOK);
HRESULT DecodeToBuf(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, UInt64 packSize, ISequentialInStream *inStream, CByteBuffer &buffer);
};
static const unsigned kLzMethodMax = 5;
HRESULT CUnpacker::Create(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, bool isSolid, bool &wrongPassword)
{
wrongPassword = false;
if (item.GetAlgoVersion() != 0)
return E_NOTIMPL;
if (!outStream)
{
outStreamSpec = new COutStreamWithHash;
outStream = outStreamSpec;
}
unsigned method = item.GetMethod();
if (method == 0)
{
if (!copyCoder)
{
copyCoderSpec = new NCompress::CCopyCoder;
copyCoder = copyCoderSpec;
}
}
else
{
if (method > kLzMethodMax)
return E_NOTIMPL;
/*
if (item.IsSplitBefore())
return S_FALSE;
*/
int lzIndex = item.IsService() ? 1 : 0;
CMyComPtr<ICompressCoder> &lzCoder = LzCoders[lzIndex];
if (!lzCoder)
{
const UInt32 methodID = 0x40305;
RINOK(CreateCoder(EXTERNAL_CODECS_LOC_VARS methodID, false, lzCoder));
if (!lzCoder)
return E_NOTIMPL;
}
CMyComPtr<ICompressSetDecoderProperties2> csdp;
RINOK(lzCoder.QueryInterface(IID_ICompressSetDecoderProperties2, &csdp));
Byte props[2] = { (Byte)(item.GetDictSize()), (Byte)(isSolid ? 1 : 0) };
RINOK(csdp->SetDecoderProperties2(props, 2));
}
unsigned cryptoSize = 0;
int cryptoOffset = item.FindExtra(NExtraRecordType::kCrypto, cryptoSize);
if (cryptoOffset >= 0)
{
if (!filterStream)
{
filterStreamSpec = new CFilterCoder(false);
filterStream = filterStreamSpec;
}
if (!cryptoDecoder)
{
cryptoDecoderSpec = new NCrypto::NRar5::CDecoder;
cryptoDecoder = cryptoDecoderSpec;
}
RINOK(cryptoDecoderSpec->SetDecoderProps(item.Extra + (unsigned)cryptoOffset, cryptoSize, true, item.IsService()));
if (!getTextPassword)
{
wrongPassword = True;
return E_NOTIMPL;
}
RINOK(MySetPassword(getTextPassword, cryptoDecoderSpec));
if (!cryptoDecoderSpec->CalcKey_and_CheckPassword())
wrongPassword = True;
}
return S_OK;
}
HRESULT CUnpacker::Code(const CItem &item, const CItem &lastItem, UInt64 packSize,
ISequentialInStream *volsInStream, ISequentialOutStream *realOutStream, ICompressProgressInfo *progress,
bool &isCrcOK)
{
isCrcOK = true;
unsigned method = item.GetMethod();
if (method > kLzMethodMax)
return E_NOTIMPL;
if (linkFile && !lastItem.Is_UnknownSize())
{
size_t dataSize = (size_t)lastItem.Size;
if (dataSize != lastItem.Size)
return E_NOTIMPL;
linkFile->Data.Alloc(dataSize);
}
bool isCryptoMode = false;
ISequentialInStream *inStream;
if (item.IsEncrypted())
{
filterStreamSpec->Filter = cryptoDecoder;
filterStreamSpec->SetInStream(volsInStream);
filterStreamSpec->SetOutStreamSize(NULL);
inStream = filterStream;
isCryptoMode = true;
}
else
inStream = volsInStream;
ICompressCoder *commonCoder = (method == 0) ? copyCoder : LzCoders[item.IsService() ? 1 : 0];
outStreamSpec->SetStream(realOutStream);
outStreamSpec->Init(lastItem, (linkFile ? (Byte *)linkFile->Data : NULL));
NeedClearSolid[item.IsService() ? 1 : 0] = false;
HRESULT res = S_OK;
if (packSize != 0 || lastItem.Is_UnknownSize() || lastItem.Size != 0)
{
res = commonCoder->Code(inStream, outStream, &packSize,
lastItem.Is_UnknownSize() ? NULL : &lastItem.Size, progress);
}
else
{
res = res;
}
if (isCryptoMode)
filterStreamSpec->ReleaseInStream();
UInt64 processedSize = outStreamSpec->GetPos();
if (res == S_OK && !lastItem.Is_UnknownSize() && processedSize != lastItem.Size)
res = S_FALSE;
// if (res == S_OK)
{
unsigned cryptoSize = 0;
int cryptoOffset = lastItem.FindExtra(NExtraRecordType::kCrypto, cryptoSize);
NCrypto::NRar5::CDecoder *crypto = NULL;
if (cryptoOffset >= 0)
{
CCryptoInfo cryptoInfo;
if (cryptoInfo.Parse(lastItem.Extra + (unsigned)cryptoOffset, cryptoSize))
if (cryptoInfo.UseMAC())
crypto = cryptoDecoderSpec;
}
isCrcOK = outStreamSpec->_hash.Check(lastItem, crypto);
}
if (linkFile)
{
linkFile->Res = res;
linkFile->crcOK = isCrcOK;
if (!lastItem.Is_UnknownSize() && processedSize != lastItem.Size)
linkFile->Data.ChangeSize_KeepData((size_t)processedSize, (size_t)processedSize);
}
return res;
}
HRESULT CUnpacker::DecodeToBuf(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, UInt64 packSize, ISequentialInStream *inStream, CByteBuffer &buffer)
{
CBufPtrSeqOutStream *outSpec = new CBufPtrSeqOutStream;
CMyComPtr<ISequentialOutStream> out = outSpec;
_tempBuf.AllocAtLeast((size_t)item.Size);
outSpec->Init(_tempBuf, (size_t)item.Size);
bool wrongPassword;
if (item.IsSolid())
return E_NOTIMPL;
HRESULT res = Create(EXTERNAL_CODECS_LOC_VARS item, item.IsSolid(), wrongPassword);
if (res == S_OK)
{
if (wrongPassword)
return S_FALSE;
CLimitedSequentialInStream *limitedStreamSpec = new CLimitedSequentialInStream;
CMyComPtr<ISequentialInStream> limitedStream(limitedStreamSpec);
limitedStreamSpec->SetStream(inStream);
limitedStreamSpec->Init(packSize);
bool crcOK = true;
res = Code(item, item, packSize, limitedStream, out, NULL, crcOK);
if (res == S_OK)
{
if (!crcOK || outSpec->GetPos() != item.Size)
res = S_FALSE;
else
buffer.CopyFrom(_tempBuf, (size_t)item.Size);
}
}
return res;
}
struct CTempBuf
{
CByteBuffer _buf;
size_t _offset;
bool _isOK;
void Clear()
{
_offset = 0;
_isOK = true;
}
CTempBuf() { Clear(); }
HRESULT Decode(DECL_EXTERNAL_CODECS_LOC_VARS
const CItem &item,
ISequentialInStream *inStream, CUnpacker &unpacker, CByteBuffer &destBuf);
};
HRESULT CTempBuf::Decode(DECL_EXTERNAL_CODECS_LOC_VARS
const CItem &item,
ISequentialInStream *inStream,
CUnpacker &unpacker,
CByteBuffer &destBuf)
{
const size_t kPackSize_Max = (1 << 24);
if (item.Size > (1 << 24)
|| item.Size == 0
|| item.PackSize >= kPackSize_Max)
{
Clear();
return S_OK;
}
if (item.IsSplit() /* && _isOK */)
{
size_t packSize = (size_t)item.PackSize;
if (packSize > kPackSize_Max - _offset)
return S_OK;
size_t newSize = _offset + packSize;
if (newSize > _buf.Size())
_buf.ChangeSize_KeepData(newSize, _offset);
Byte *data = (Byte *)_buf + _offset;
RINOK(ReadStream_FALSE(inStream, data, packSize));
_offset += packSize;
if (item.IsSplitAfter())
{
CHash hash;
hash.Init(item);
hash.Update(data, packSize);
_isOK = hash.Check(item, NULL); // RAR5 doesn't use HMAC for packed part
}
}
if (_isOK)
{
if (!item.IsSplitAfter())
{
if (_offset == 0)
{
RINOK(unpacker.DecodeToBuf(EXTERNAL_CODECS_LOC_VARS
item, item.PackSize, inStream, destBuf));
}
else
{
CBufInStream *bufInStreamSpec = new CBufInStream;
CMyComPtr<ISequentialInStream> bufInStream = bufInStreamSpec;
bufInStreamSpec->Init(_buf, _offset);
RINOK(unpacker.DecodeToBuf(EXTERNAL_CODECS_LOC_VARS
item, _offset, bufInStream, destBuf));
}
}
}
return S_OK;
}
static const Byte kProps[] =
{
kpidPath,
kpidIsDir,
kpidSize,
kpidPackSize,
kpidMTime,
kpidCTime,
kpidATime,
kpidAttrib,
kpidIsAltStream,
kpidEncrypted,
kpidSolid,
kpidSplitBefore,
kpidSplitAfter,
kpidCRC,
kpidHostOS,
kpidMethod,
kpidSymLink,
kpidHardLink,
kpidCopyLink,
};
static const Byte kArcProps[] =
{
kpidTotalPhySize,
kpidCharacts,
kpidSolid,
kpidNumBlocks,
kpidEncrypted,
kpidIsVolume,
kpidVolumeIndex,
kpidNumVolumes,
kpidComment
};
IMP_IInArchive_Props
IMP_IInArchive_ArcProps
UInt64 CHandler::GetPackSize(unsigned refIndex) const
{
UInt64 size = 0;
unsigned index = _refs[refIndex].Item;
for (;;)
{
const CItem &item = _items[index];
size += item.PackSize;
if (item.NextItem < 0)
return size;
index = item.NextItem;
}
}
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
COM_TRY_BEGIN
NCOM::CPropVariant prop;
const CInArcInfo *arcInfo = NULL;
if (!_arcs.IsEmpty())
arcInfo = &_arcs[0].Info;
switch (propID)
{
case kpidVolumeIndex: if (arcInfo && arcInfo->IsVolume()) prop = arcInfo->GetVolIndex(); break;
case kpidSolid: if (arcInfo) prop = arcInfo->IsSolid(); break;
case kpidCharacts:
{
if (!_arcs.IsEmpty())
{
FLAGS_TO_PROP(k_ArcFlags, (UInt32)arcInfo->Flags, prop);
}
break;
}
case kpidEncrypted: if (arcInfo) prop = arcInfo->IsEncrypted; break; // it's for encrypted names.
case kpidIsVolume: if (arcInfo) prop = arcInfo->IsVolume(); break;
case kpidNumVolumes: prop = (UInt32)_arcs.Size(); break;
case kpidOffset: if (arcInfo && arcInfo->StartPos != 0) prop = arcInfo->StartPos; break;
case kpidTotalPhySize:
{
if (_arcs.Size() > 1)
{
UInt64 sum = 0;
FOR_VECTOR (v, _arcs)
sum += _arcs[v].Info.GetPhySize();
prop = sum;
}
break;
}
case kpidPhySize:
{
if (arcInfo)
prop = arcInfo->GetPhySize();
break;
}
case kpidComment:
{
// if (!_arcs.IsEmpty())
{
// const CArc &arc = _arcs[0];
const CByteBuffer &cmt = _comment;
if (cmt.Size() != 0 && cmt.Size() < (1 << 16))
{
AString s;
s.SetFrom_CalcLen((const char *)(const Byte *)cmt, (unsigned)cmt.Size());
UString unicode;
if (ConvertUTF8ToUnicode(s, unicode))
prop = unicode;
}
}
break;
}
case kpidNumBlocks:
{
UInt32 numBlocks = 0;
FOR_VECTOR (i, _refs)
if (!_items[_refs[i].Item].IsSolid())
numBlocks++;
prop = (UInt32)numBlocks;
break;
}
case kpidError:
{
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->IsVolume())
{
char sz[32];
ConvertUInt64ToString(arcInfo->GetVolIndex() + 1, sz);
unsigned len = MyStringLen(sz);
AString s = "part";
for (; len < 2; len++)
s += '0';
s += sz;
s += ".rar";
prop = s;
}
}
break;
case kpidIsAltStream: prop = true; break;
}
prop.Detach(value);
return S_OK;
COM_TRY_END
}
STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
{
*numItems = _refs.Size();
return S_OK;
}
static const Byte kRawProps[] =
{
kpidChecksum,
kpidNtSecure
};
STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps)
{
*numProps = ARRAY_SIZE(kRawProps);
return S_OK;
}
STDMETHODIMP CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)
{
*propID = kRawProps[index];
*name = 0;
return S_OK;
}
STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)
{
*parentType = NParentType::kDir;
*parent = (UInt32)(Int32)-1;
if (index >= _refs.Size())
return S_OK;
const CRefItem &ref = _refs[index];
const CItem &item = _items[ref.Item];
if (item.Is_STM() && ref.Parent >= 0)
{
*parent = (UInt32)ref.Parent;
*parentType = NParentType::kAltStream;
}
return S_OK;
}
STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)
{
*data = NULL;
*dataSize = 0;
*propType = 0;
if (index >= _refs.Size())
return E_INVALIDARG;
const CItem &item = _items[_refs[index].Item];
if (propID == kpidNtSecure)
{
if (item.ACL >= 0)
{
const CByteBuffer &buf = _acls[item.ACL];
*dataSize = (UInt32)buf.Size();
*propType = NPropDataType::kRaw;
*data = (const Byte *)buf;
}
return S_OK;
}
if (propID == kpidChecksum)
{
int hashRecOffset = item.FindExtra_Blake();
if (hashRecOffset >= 0)
{
*dataSize = BLAKE2S_DIGEST_SIZE;
*propType = NPropDataType::kRaw;
*data = &item.Extra[hashRecOffset];
}
return S_OK;
}
return S_OK;
}
static void TimeRecordToProp(const CItem &item, unsigned stampIndex, NCOM::CPropVariant &prop)
{
unsigned size;
int offset = item.FindExtra(NExtraRecordType::kTime, size);
if (offset < 0)
return;
const Byte *p = item.Extra + (unsigned)offset;
UInt64 flags;
{
unsigned num = ReadVarInt(p, size, &flags);
if (num == 0)
return;
p += num;
size -= num;
}
if ((flags & (NTimeRecord::NFlags::kMTime << stampIndex)) == 0)
return;
unsigned numStamps = 0;
unsigned i;
for (i = 0; i < 3; i++)
if ((flags & (NTimeRecord::NFlags::kMTime << i)) != 0)
numStamps++;
unsigned stampSizeLog = ((flags & NTimeRecord::NFlags::kUnixTime) != 0) ? 2 : 3;
if ((numStamps << stampSizeLog) != size)
return;
numStamps = 0;
for (i = 0; i < stampIndex; i++)
if ((flags & (NTimeRecord::NFlags::kMTime << i)) != 0)
numStamps++;
p += (numStamps << stampSizeLog);
FILETIME ft;
if ((flags & NTimeRecord::NFlags::kUnixTime) != 0)
NWindows::NTime::UnixTimeToFileTime(Get32(p), ft);
else
{
ft.dwLowDateTime = Get32(p);
ft.dwHighDateTime = Get32(p + 4);
}
prop = ft;
}
STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
{
COM_TRY_BEGIN
NCOM::CPropVariant prop;
const CRefItem &ref = _refs[index];
const CItem &item = _items[ref.Item];
const CItem &lastItem = _items[ref.Last];
switch (propID)
{
case kpidPath:
{
UString unicodeName;
if (item.Is_STM())
{
AString s;
if (ref.Parent >= 0)
{
CItem &mainItem = _items[_refs[ref.Parent].Item];
s = mainItem.Name;
}
AString name;
item.GetAltStreamName(name);
if (name[0] != ':')
s += ':';
s += name;
if (!ConvertUTF8ToUnicode(s, unicodeName))
break;
}
else
{
if (!ConvertUTF8ToUnicode(item.Name, unicodeName))
break;
if (item.Version_Defined)
{
wchar_t temp[32];
// temp[0] = ';';
// ConvertUInt64ToString(item.Version, temp + 1);
// unicodeName += temp;
ConvertUInt64ToString(item.Version, temp);
UString s2 = L"[VER]" WSTRING_PATH_SEPARATOR;
s2 += temp;
s2.Add_PathSepar();
unicodeName.Insert(0, s2);
}
}
NItemName::ConvertToOSName2(unicodeName);
prop = unicodeName;
break;
}
case kpidIsDir: prop = item.IsDir(); break;
case kpidSize: if (!lastItem.Is_UnknownSize()) prop = lastItem.Size; break;
case kpidPackSize: prop = GetPackSize(index); break;
case kpidMTime:
{
TimeRecordToProp(item, NTimeRecord::k_Index_MTime, prop);
if (prop.vt == VT_EMPTY && item.Has_UnixMTime())
{
FILETIME ft;
NWindows::NTime::UnixTimeToFileTime(item.UnixMTime, ft);
prop = ft;
}
if (prop.vt == VT_EMPTY && ref.Parent >= 0)
{
const CItem &baseItem = _items[_refs[ref.Parent].Item];
TimeRecordToProp(baseItem, NTimeRecord::k_Index_MTime, prop);
if (prop.vt == VT_EMPTY && baseItem.Has_UnixMTime())
{
FILETIME ft;
NWindows::NTime::UnixTimeToFileTime(baseItem.UnixMTime, ft);
prop = ft;
}
}
break;
}
case kpidCTime: TimeRecordToProp(item, NTimeRecord::k_Index_CTime, prop); break;
case kpidATime: TimeRecordToProp(item, NTimeRecord::k_Index_ATime, prop); break;
case kpidName:
{
if (item.Is_STM())
{
AString name;
item.GetAltStreamName(name);
if (name[0] == ':')
{
name.DeleteFrontal(1);
UString unicodeName;
if (ConvertUTF8ToUnicode(name, unicodeName))
prop = unicodeName;
}
}
break;
}
case kpidIsAltStream: prop = item.Is_STM(); break;
case kpidSymLink: item.Link_to_Prop(NLinkType::kUnixSymLink, prop); break;
case kpidHardLink: item.Link_to_Prop(NLinkType::kHardLink, prop); break;
case kpidCopyLink: item.Link_to_Prop(NLinkType::kFileCopy, prop); break;
case kpidAttrib: prop = item.GetWinAttrib(); break;
case kpidEncrypted: prop = item.IsEncrypted(); break;
case kpidSolid: prop = item.IsSolid(); break;
case kpidSplitBefore: prop = item.IsSplitBefore(); break;
case kpidSplitAfter: prop = lastItem.IsSplitAfter(); break;
case kpidCRC:
{
const CItem *item2 = (lastItem.IsSplitAfter() ? &item : &lastItem);
if (item2->Has_CRC())
prop = item2->CRC;
break;
}
case kpidMethod:
{
char temp[64];
unsigned algo = item.GetAlgoVersion();
char *s = temp;
if (algo != 0)
{
ConvertUInt32ToString(algo, s);
s += MyStringLen(s);
*s++ = ':';
}
unsigned m = item.GetMethod();
{
s[0] = 'm';
s[1] = (char)(m + '0');
s[2] = 0;
if (!item.IsDir())
{
s[2] = ':';
ConvertUInt32ToString(item.GetDictSize() + 17, s + 3);
}
}
unsigned cryptoSize = 0;
int cryptoOffset = item.FindExtra(NExtraRecordType::kCrypto, cryptoSize);
if (cryptoOffset >= 0)
{
s = temp + strlen(temp);
*s++ = ' ';
strcpy(s, "AES:");
CCryptoInfo cryptoInfo;
if (cryptoInfo.Parse(item.Extra + (unsigned)cryptoOffset, cryptoSize))
{
s += strlen(s);
ConvertUInt32ToString(cryptoInfo.Cnt, s);
s += strlen(s);
*s++ = ':';
ConvertUInt64ToString(cryptoInfo.Flags, s);
}
}
prop = temp;
break;
}
case kpidHostOS:
if (item.HostOS < ARRAY_SIZE(kHostOS))
prop = kHostOS[(size_t)item.HostOS];
else
prop = (UInt64)item.HostOS;
break;
}
prop.Detach(value);
return S_OK;
COM_TRY_END
}
// ---------- Copy Links ----------
static int CompareItemsPaths(const CHandler &handler, unsigned p1, unsigned p2, const AString *name1)
{
const CItem &item1 = handler._items[handler._refs[p1].Item];
const CItem &item2 = handler._items[handler._refs[p2].Item];
if (item1.Version_Defined)
{
if (!item2.Version_Defined)
return -1;
int res = MyCompare(item1.Version, item2.Version);
if (res != 0)
return res;
}
else if (item2.Version_Defined)
return 1;
if (!name1)
name1 = &item1.Name;
return strcmp(*name1, item2.Name);
}
static int CompareItemsPaths2(const CHandler &handler, unsigned p1, unsigned p2, const AString *name1)
{
int res = CompareItemsPaths(handler, p1, p2, name1);
if (res != 0)
return res;
return MyCompare(p1, p2);
}
static int CompareItemsPaths_Sort(const unsigned *p1, const unsigned *p2, void *param)
{
return CompareItemsPaths2(*(const CHandler *)param, *p1, *p2, NULL);
}
static int FindLink(const CHandler &handler, const CUIntVector &sorted,
const AString &s, unsigned index)
{
unsigned left = 0, right = sorted.Size();
for (;;)
{
if (left == right)
{
if (left > 0)
{
unsigned refIndex = sorted[left - 1];
if (CompareItemsPaths(handler, index, refIndex, &s) == 0)
return refIndex;
}
if (right < sorted.Size())
{
unsigned refIndex = sorted[right];
if (CompareItemsPaths(handler, index, refIndex, &s) == 0)
return refIndex;
}
return -1;
}
unsigned mid = (left + right) / 2;
unsigned refIndex = sorted[mid];
int compare = CompareItemsPaths2(handler, index, refIndex, &s);
if (compare == 0)
return refIndex;
if (compare < 0)
right = mid;
else
left = mid + 1;
}
}
void CHandler::FillLinks()
{
unsigned i;
for (i = 0; i < _refs.Size(); i++)
{
const CItem &item = _items[_refs[i].Item];
if (!item.IsDir() && !item.IsService() && item.NeedUse_as_CopyLink())
break;
}
if (i == _refs.Size())
return;
CUIntVector sorted;
for (i = 0; i < _refs.Size(); i++)
{
const CItem &item = _items[_refs[i].Item];
if (!item.IsDir() && !item.IsService())
sorted.Add(i);
}
if (sorted.IsEmpty())
return;
sorted.Sort(CompareItemsPaths_Sort, (void *)this);
AString link;
for (i = 0; i < _refs.Size(); i++)
{
CRefItem &ref = _refs[i];
const CItem &item = _items[ref.Item];
if (item.IsDir() || item.IsService() || item.PackSize != 0)
continue;
CItem::CLinkInfo linkInfo;
if (!item.FindExtra_Link(linkInfo) || linkInfo.Type != NLinkType::kFileCopy)
continue;
link.SetFrom_CalcLen((const char *)(item.Extra + linkInfo.NameOffset), linkInfo.NameLen);
int linkIndex = FindLink(*this, sorted, link, i);
if (linkIndex < 0)
continue;
if ((unsigned)linkIndex >= i)
continue; // we don't support forward links that can lead to loops
const CRefItem &linkRef = _refs[linkIndex];
const CItem &linkItem = _items[linkRef.Item];
if (linkItem.Size == item.Size)
{
if (linkRef.Link >= 0)
ref.Link = linkRef.Link;
else if (!linkItem.NeedUse_as_CopyLink())
ref.Link = linkIndex;
}
}
}
HRESULT CHandler::Open2(IInStream *stream,
const UInt64 *maxCheckStartPosition,
IArchiveOpenCallback *openCallback)
{
CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
CMyComPtr<ICryptoGetTextPassword> getTextPassword;
NRar::CVolumeName seqName;
UInt64 totalBytes = 0;
UInt64 curBytes = 0;
if (openCallback)
{
openCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);
openCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getTextPassword);
}
CTempBuf tempBuf;
CUnpacker unpacker;
unpacker.getTextPassword = getTextPassword;
int prevSplitFile = -1;
int prevMainFile = -1;
bool nextVol_is_Required = false;
CInArchive arch;
for (;;)
{
CMyComPtr<IInStream> inStream;
if (_arcs.IsEmpty())
inStream = stream;
else
{
if (!openVolumeCallback)
break;
if (_arcs.Size() == 1)
{
UString baseName;
{
NCOM::CPropVariant prop;
RINOK(openVolumeCallback->GetProperty(kpidName, &prop));
if (prop.vt != VT_BSTR)
break;
baseName = prop.bstrVal;
}
if (!seqName.InitName(baseName))
break;
}
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;
}
}
UInt64 endPos = 0;
RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &arch.StreamStartPosition));
RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos));
RINOK(inStream->Seek(arch.StreamStartPosition, STREAM_SEEK_SET, NULL));
if (openCallback)
{
totalBytes += endPos;
RINOK(openCallback->SetTotal(NULL, &totalBytes));
}
CInArcInfo arcInfoOpen;
{
HRESULT res = arch.Open(inStream, maxCheckStartPosition, getTextPassword, arcInfoOpen);
if (arch.IsArc && arch.UnexpectedEnd)
_errorFlags |= kpv_ErrorFlags_UnexpectedEnd;
if (_arcs.IsEmpty())
{
_isArc = arch.IsArc;
}
if (res != S_OK)
{
if (res != S_FALSE)
return res;
if (_arcs.IsEmpty())
return res;
break;
}
}
CArc &arc = _arcs.AddNew();
CInArcInfo &arcInfo = arc.Info;
arcInfo = arcInfoOpen;
arc.Stream = inStream;
CItem item;
for (;;)
{
item.Clear();
arcInfo.EndPos = arch.Position;
if (arch.Position > endPos)
{
_errorFlags |= kpv_ErrorFlags_UnexpectedEnd;
break;
}
RINOK(inStream->Seek(arch.Position, STREAM_SEEK_SET, NULL));
{
CInArchive::CHeader h;
HRESULT res = arch.ReadBlockHeader(h);
if (res != S_OK)
{
if (res != S_FALSE)
return res;
if (arch.UnexpectedEnd)
{
_errorFlags |= kpv_ErrorFlags_UnexpectedEnd;
if (arcInfo.EndPos < arch.Position)
arcInfo.EndPos = arch.Position;
if (arcInfo.EndPos < endPos)
arcInfo.EndPos = endPos;
}
else
_errorFlags |= kpv_ErrorFlags_HeadersError;
break;
}
if (h.Type == NHeaderType::kEndOfArc)
{
arcInfo.EndPos = arch.Position;
arcInfo.EndOfArchive_was_Read = true;
if (!arch.ReadVar(arcInfo.EndFlags))
_errorFlags |= kpv_ErrorFlags_HeadersError;
if (arcInfo.IsVolume())
{
// for multivolume archives RAR can add ZERO bytes at the end for alignment.
// We must skip these bytes to prevent phySize warning.
RINOK(inStream->Seek(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)
arcInfo.EndPos += numZeros;
}
break;
}
if (h.Type != NHeaderType::kFile &&
h.Type != NHeaderType::kService)
{
_errorFlags |= kpv_ErrorFlags_UnsupportedFeature;
break;
}
item.RecordType = (Byte)h.Type;
if (!arch.ReadFileHeader(h, item))
{
_errorFlags |= kpv_ErrorFlags_HeadersError;
break;
}
// item.MainPartSize = (UInt32)(Position - item.Position);
item.DataPos = arch.Position;
}
bool isOk_packSize = true;
{
arcInfo.EndPos = arch.Position;
if (arch.Position + item.PackSize < arch.Position)
{
isOk_packSize = false;
_errorFlags |= kpv_ErrorFlags_HeadersError;
if (arcInfo.EndPos < endPos)
arcInfo.EndPos = endPos;
}
else
{
arch.AddToSeekValue(item.PackSize); // Position points to next header;
arcInfo.EndPos = arch.Position;
}
}
bool needAdd = true;
{
if (_comment.Size() == 0
&& item.Is_CMT()
&& item.PackSize < kCommentSize_Max
&& item.PackSize == item.Size
&& item.PackSize != 0
&& item.GetMethod() == 0
&& !item.IsSplit())
{
RINOK(unpacker.DecodeToBuf(EXTERNAL_CODECS_VARS item, item.PackSize, inStream, _comment));
needAdd = false;
}
}
if (needAdd)
{
CRefItem ref;
ref.Item = _items.Size();
ref.Last = ref.Item;
ref.Parent = -1;
ref.Link = -1;
if (item.IsService())
{
if (item.Is_STM())
{
if (prevMainFile >= 0)
ref.Parent = prevMainFile;
}
else
{
needAdd = false;
if (item.Is_ACL() && (!item.IsEncrypted() || arch.m_CryptoMode))
{
if (prevMainFile >= 0 && item.Size < (1 << 24) && item.Size != 0)
{
CItem &mainItem = _items[_refs[prevMainFile].Item];
if (mainItem.ACL < 0)
{
CByteBuffer acl;
HRESULT res = tempBuf.Decode(EXTERNAL_CODECS_VARS item, inStream, unpacker, acl);
if (!item.IsSplitAfter())
tempBuf.Clear();
if (res != S_OK)
{
tempBuf.Clear();
if (res != S_FALSE && res != E_NOTIMPL)
return res;
}
// RINOK();
if (res == S_OK && acl.Size() != 0)
{
if (_acls.IsEmpty() || acl != _acls.Back())
_acls.Add(acl);
mainItem.ACL = _acls.Size() - 1;
}
}
}
}
}
}
if (needAdd)
{
if (item.IsSplitBefore())
{
if (prevSplitFile >= 0)
{
CRefItem &ref2 = _refs[prevSplitFile];
CItem &prevItem = _items[ref2.Last];
if (item.IsNextForItem(prevItem))
{
ref2.Last = _items.Size();
prevItem.NextItem = ref2.Last;
needAdd = false;
}
}
}
}
if (needAdd)
{
if (item.IsSplitAfter())
prevSplitFile = _refs.Size();
if (!item.IsService())
prevMainFile = _refs.Size();
_refs.Add(ref);
}
}
{
UInt64 version;
if (item.FindExtra_Version(version))
{
item.Version_Defined = true;
item.Version = version;
}
}
item.VolIndex = _arcs.Size() - 1;
_items.Add(item);
if (openCallback && (_items.Size() & 0xFF) == 0)
{
UInt64 numFiles = _items.Size();
UInt64 numBytes = curBytes + item.DataPos;
RINOK(openCallback->SetCompleted(&numFiles, &numBytes));
}
if (!isOk_packSize)
break;
}
curBytes += endPos;
nextVol_is_Required = false;
if (!arcInfo.IsVolume())
break;
if (arcInfo.EndOfArchive_was_Read)
{
if (!arcInfo.AreMoreVolumes())
break;
nextVol_is_Required = true;
}
}
FillLinks();
return S_OK;
}
STDMETHODIMP CHandler::Open(IInStream *stream,
const UInt64 *maxCheckStartPosition,
IArchiveOpenCallback *openCallback)
{
COM_TRY_BEGIN
Close();
return Open2(stream, maxCheckStartPosition, openCallback);
COM_TRY_END
}
STDMETHODIMP CHandler::Close()
{
COM_TRY_BEGIN
_missingVolName.Empty();
_errorFlags = 0;
// _warningFlags = 0;
_isArc = false;
_refs.Clear();
_items.Clear();
_arcs.Clear();
_acls.Clear();
_comment.Free();
return S_OK;
COM_TRY_END
}
class CVolsInStream:
public ISequentialInStream,
public CMyUnknownImp
{
UInt64 _rem;
ISequentialInStream *_stream;
const CObjectVector<CArc> *_arcs;
const CObjectVector<CItem> *_items;
int _itemIndex;
public:
bool CrcIsOK;
private:
CHash _hash;
public:
MY_UNKNOWN_IMP
void Init(const CObjectVector<CArc> *arcs,
const CObjectVector<CItem> *items,
unsigned itemIndex)
{
_arcs = arcs;
_items = items;
_itemIndex = itemIndex;
_stream = NULL;
CrcIsOK = true;
}
STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
};
STDMETHODIMP CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize)
{
if (processedSize)
*processedSize = 0;
UInt32 realProcessedSize = 0;
while (size != 0)
{
if (!_stream)
{
if (_itemIndex < 0)
break;
const CItem &item = (*_items)[_itemIndex];
IInStream *s = (*_arcs)[item.VolIndex].Stream;
RINOK(s->Seek(item.GetDataPosition(), STREAM_SEEK_SET, NULL));
_stream = s;
if (CrcIsOK && item.IsSplitAfter())
_hash.Init(item);
else
_hash.Init_NoCalc();
_rem = item.PackSize;
}
{
UInt32 cur = size;
if (cur > _rem)
cur = (UInt32)_rem;
UInt32 num = cur;
HRESULT res = _stream->Read(data, cur, &cur);
_hash.Update(data, cur);
realProcessedSize += cur;
if (processedSize)
*processedSize = realProcessedSize;
data = (Byte *)data + cur;
size -= cur;
_rem -= cur;
if (_rem == 0)
{
const CItem &item = (*_items)[_itemIndex];
_itemIndex = item.NextItem;
if (!_hash.Check(item, NULL)) // RAR doesn't use MAC here
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;
}
static int FindLinkBuf(CObjectVector<CLinkFile> &linkFiles, unsigned index)
{
unsigned left = 0, right = linkFiles.Size();
for (;;)
{
if (left == right)
return -1;
unsigned mid = (left + right) / 2;
unsigned linkIndex = linkFiles[mid].Index;
if (index == linkIndex)
return mid;
if (index < linkIndex)
right = mid;
else
left = mid + 1;
}
}
static inline int DecoderRes_to_OpRes(HRESULT res, bool crcOK)
{
if (res == E_NOTIMPL)
return NExtract::NOperationResult::kUnsupportedMethod;
// if (res == S_FALSE)
if (res != S_OK)
return NExtract::NOperationResult::kDataError;
return crcOK ?
NExtract::NOperationResult::kOK :
NExtract::NOperationResult::kCRCError;
}
static HRESULT CopyData_with_Progress(const Byte *data, size_t size,
ISequentialOutStream *outStream, ICompressProgressInfo *progress)
{
size_t pos = 0;
while (pos < size)
{
const UInt32 kStepSize = ((UInt32)1 << 24);
UInt32 cur32;
{
size_t cur = size - pos;
if (cur > kStepSize)
cur = kStepSize;
cur32 = (UInt32)cur;
}
RINOK(outStream->Write(data + pos, cur32, &cur32));
if (cur32 == 0)
return E_FAIL;
pos += cur32;
if (progress)
{
UInt64 pos64 = pos;
RINOK(progress->SetRatioInfo(&pos64, &pos64));
}
}
return S_OK;
}
STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
Int32 testMode, IArchiveExtractCallback *extractCallback)
{
COM_TRY_BEGIN
bool allFilesMode = (numItems == (UInt32)(Int32)-1);
if (allFilesMode)
numItems = _refs.Size();
if (numItems == 0)
return S_OK;
CByteArr extractStatuses(_refs.Size());
memset(extractStatuses, 0, _refs.Size());
// we don't want to use temp buffer for big link files.
const size_t k_CopyLinkFile_MaxSize = (size_t)1 << (28 + sizeof(size_t) / 2);
const Byte kStatus_Extract = 1 << 0;
const Byte kStatus_Skip = 1 << 1;
const Byte kStatus_Link = 1 << 2;
CObjectVector<CLinkFile> linkFiles;
{
UInt64 total = 0;
bool isThereUndefinedSize = false;
bool thereAreLinks = false;
{
unsigned solidLimit = 0;
for (UInt32 t = 0; t < numItems; t++)
{
unsigned index = allFilesMode ? t : indices[t];
const CRefItem &ref = _refs[index];
const CItem &item = _items[ref.Item];
const CItem &lastItem = _items[ref.Last];
extractStatuses[index] |= kStatus_Extract;
if (!lastItem.Is_UnknownSize())
total += lastItem.Size;
else
isThereUndefinedSize = true;
if (ref.Link >= 0)
{
if (!testMode)
{
if ((unsigned)ref.Link < index)
{
const CRefItem &linkRef = _refs[(unsigned)ref.Link];
const CItem &linkItem = _items[linkRef.Item];
if (linkItem.IsSolid() && linkItem.Size <= k_CopyLinkFile_MaxSize)
{
if (extractStatuses[(unsigned)ref.Link] == 0)
{
const CItem &lastLinkItem = _items[linkRef.Last];
if (!lastLinkItem.Is_UnknownSize())
total += lastLinkItem.Size;
else
isThereUndefinedSize = true;
}
extractStatuses[(unsigned)ref.Link] |= kStatus_Link;
thereAreLinks = true;
}
}
}
continue;
}
if (item.IsService())
continue;
if (item.IsSolid())
{
unsigned j = index;
while (j > solidLimit)
{
j--;
const CRefItem &ref2 = _refs[j];
const CItem &item2 = _items[ref2.Item];
if (!item2.IsService())
{
if (extractStatuses[j] == 0)
{
const CItem &lastItem2 = _items[ref2.Last];
if (!lastItem2.Is_UnknownSize())
total += lastItem2.Size;
else
isThereUndefinedSize = true;
}
extractStatuses[j] |= kStatus_Skip;
if (!item2.IsSolid())
break;
}
}
}
solidLimit = index + 1;
}
}
if (thereAreLinks)
{
unsigned solidLimit = 0;
FOR_VECTOR(i, _refs)
{
if ((extractStatuses[i] & kStatus_Link) == 0)
continue;
const CItem &item = _items[_refs[i].Item];
/*
if (item.IsService())
continue;
*/
CLinkFile &linkFile = linkFiles.AddNew();
linkFile.Index = i;
if (item.IsSolid())
{
unsigned j = i;
while (j > solidLimit)
{
j--;
const CRefItem &ref2 = _refs[j];
const CItem &item2 = _items[ref2.Item];
if (!item2.IsService())
{
if (extractStatuses[j] != 0)
break;
extractStatuses[j] = kStatus_Skip;
{
const CItem &lastItem2 = _items[ref2.Last];
if (!lastItem2.Is_UnknownSize())
total += lastItem2.Size;
else
isThereUndefinedSize = true;
}
if (!item2.IsSolid())
break;
}
}
}
solidLimit = i + 1;
}
for (UInt32 t = 0; t < numItems; t++)
{
unsigned index = allFilesMode ? t : indices[t];
const CRefItem &ref = _refs[index];
int linkIndex = ref.Link;
if (linkIndex < 0 || (unsigned)linkIndex >= index)
continue;
const CItem &linkItem = _items[_refs[(unsigned)linkIndex].Item];
if (!linkItem.IsSolid() || linkItem.Size > k_CopyLinkFile_MaxSize)
continue;
int bufIndex = FindLinkBuf(linkFiles, linkIndex);
if (bufIndex < 0)
return E_FAIL;
linkFiles[bufIndex].NumLinks++;
}
}
if (total != 0 || !isThereUndefinedSize)
{
RINOK(extractCallback->SetTotal(total));
}
}
UInt64 totalUnpacked = 0;
UInt64 totalPacked = 0;
UInt64 curUnpackSize = 0;
UInt64 curPackSize = 0;
CUnpacker unpacker;
CVolsInStream *volsInStreamSpec = new CVolsInStream;
CMyComPtr<ISequentialInStream> volsInStream = volsInStreamSpec;
CLocalProgress *lps = new CLocalProgress;
CMyComPtr<ICompressProgressInfo> progress = lps;
lps->Init(extractCallback, false);
// bool needClearSolid = true;
FOR_VECTOR(i, _refs)
{
if (extractStatuses[i] == 0)
continue;
totalUnpacked += curUnpackSize;
totalPacked += curPackSize;
lps->InSize = totalPacked;
lps->OutSize = totalUnpacked;
RINOK(lps->SetCur());
CMyComPtr<ISequentialOutStream> realOutStream;
Int32 askMode =
((extractStatuses[i] & kStatus_Extract) != 0) ? (testMode ?
NExtract::NAskMode::kTest :
NExtract::NAskMode::kExtract) :
NExtract::NAskMode::kSkip;
unpacker.linkFile = NULL;
if (((extractStatuses[i] & kStatus_Link) != 0))
{
int bufIndex = FindLinkBuf(linkFiles, i);
if (bufIndex < 0)
return E_FAIL;
unpacker.linkFile = &linkFiles[bufIndex];
}
UInt32 index = i;
const CRefItem *ref = &_refs[index];
const CItem *item = &_items[ref->Item];
const CItem &lastItem = _items[ref->Last];
curUnpackSize = 0;
if (!lastItem.Is_UnknownSize())
curUnpackSize = lastItem.Size;
curPackSize = GetPackSize(index);
RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
bool isSolid;
{
bool &needClearSolid = unpacker.NeedClearSolid[item->IsService() ? 1 : 0];
isSolid = (item->IsSolid() && !needClearSolid);
if (item->IsService())
isSolid = false;
needClearSolid = !item->IsSolid();
}
if (item->IsDir())
{
RINOK(extractCallback->PrepareOperation(askMode));
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
continue;
}
int index2 = ref->Link;
int bufIndex = -1;
if (index2 >= 0)
{
const CRefItem &ref2 = _refs[index2];
const CItem &item2 = _items[ref2.Item];
const CItem &lastItem2 = _items[ref2.Last];
if (!item2.IsSolid())
{
item = &item2;
ref = &ref2;
if (!lastItem2.Is_UnknownSize())
curUnpackSize = lastItem2.Size;
else
curUnpackSize = 0;
curPackSize = GetPackSize(index2);
}
else if ((unsigned)index2 < index)
bufIndex = FindLinkBuf(linkFiles, index2);
}
if (!realOutStream)
{
if (testMode)
{
if (item->Is_CopyLink() && item->PackSize == 0)
{
RINOK(extractCallback->PrepareOperation(askMode));
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
continue;
}
}
else
{
if (item->IsService())
continue;
bool needDecode = false;
for (unsigned n = i + 1; n < _refs.Size(); n++)
{
const CItem &nextItem = _items[_refs[n].Item];
if (nextItem.IsService())
continue;
if (!nextItem.IsSolid())
break;
if (extractStatuses[i] != 0)
{
needDecode = true;
break;
}
}
if (!needDecode)
continue;
askMode = NExtract::NAskMode::kSkip;
}
}
RINOK(extractCallback->PrepareOperation(askMode));
if (bufIndex >= 0)
{
CLinkFile &linkFile = linkFiles[bufIndex];
if (linkFile.NumLinks == 0)
return E_FAIL;
if (realOutStream)
{
RINOK(CopyData_with_Progress(linkFile.Data, linkFile.Data.Size(), realOutStream, progress));
}
if (--linkFile.NumLinks == 0)
linkFile.Data.Free();
RINOK(extractCallback->SetOperationResult(DecoderRes_to_OpRes(linkFile.Res, linkFile.crcOK)));
continue;
}
if (item->Is_CopyLink() && item->PackSize == 0)
{
RINOK(extractCallback->SetOperationResult(
realOutStream ?
NExtract::NOperationResult::kUnsupportedMethod:
NExtract::NOperationResult::kOK));
continue;
}
volsInStreamSpec->Init(&_arcs, &_items, ref->Item);
UInt64 packSize = curPackSize;
if (item->IsEncrypted())
if (!unpacker.getTextPassword)
extractCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&unpacker.getTextPassword);
bool wrongPassword;
HRESULT result = unpacker.Create(EXTERNAL_CODECS_VARS *item, isSolid, wrongPassword);
if (wrongPassword)
{
realOutStream.Release();
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kWrongPassword));
continue;
}
bool crcOK = true;
if (result == S_OK)
result = unpacker.Code(*item, _items[ref->Last], packSize, volsInStream, realOutStream, progress, crcOK);
realOutStream.Release();
if (!volsInStreamSpec->CrcIsOK)
crcOK = false;
int opRes = crcOK ?
NExtract::NOperationResult::kOK:
NExtract::NOperationResult::kCRCError;
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));
}
{
FOR_VECTOR(i, linkFiles)
if (linkFiles[i].NumLinks != 0)
return E_FAIL;
}
return S_OK;
COM_TRY_END
}
IMPL_ISetCompressCodecsInfo
REGISTER_ARC_I(
"Rar5", "rar r00", 0, 0xCC,
kMarker,
0,
NArcInfoFlags::kFindSignature,
NULL)
}}
class CBlake2spHasher:
public IHasher,
public CMyUnknownImp
{
CBlake2sp _blake;
Byte mtDummy[1 << 7];
public:
CBlake2spHasher() { Init(); }
MY_UNKNOWN_IMP
INTERFACE_IHasher(;)
};
STDMETHODIMP_(void) CBlake2spHasher::Init() throw()
{
Blake2sp_Init(&_blake);
}
STDMETHODIMP_(void) CBlake2spHasher::Update(const void *data, UInt32 size) throw()
{
Blake2sp_Update(&_blake, (const Byte *)data, size);
}
STDMETHODIMP_(void) CBlake2spHasher::Final(Byte *digest) throw()
{
Blake2sp_Final(&_blake, digest);
}
REGISTER_HASHER(CBlake2spHasher, 0x202, "BLAKE2sp", BLAKE2S_DIGEST_SIZE)