// 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 _unicodeNameBuffer; CByteBuffer _comment; CByteBuffer m_FileHeaderData; NHeader::NBlock::CBlock m_BlockHeader; NCrypto::NRar3::CDecoder *m_RarAESSpec; CMyComPtr m_RarAES; CBuffer 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 openVolumeCallback; CMyComPtr 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 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 Coder; }; class CVolsInStream: public ISequentialInStream, public CMyUnknownImp { UInt64 _rem; ISequentialInStream *_stream; const CObjectVector *_arcs; const CObjectVector *_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 *arcs, const CObjectVector *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 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 importantIndexes; CRecordVector 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 methodItems; NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; CMyComPtr copyCoder = copyCoderSpec; CFilterCoder *filterStreamSpec = new CFilterCoder(false); CMyComPtr filterStream = filterStreamSpec; NCrypto::NRar2::CDecoder *rar20CryptoDecoderSpec = NULL; CMyComPtr rar20CryptoDecoder; NCrypto::NRar3::CDecoder *rar3CryptoDecoderSpec = NULL; CMyComPtr rar3CryptoDecoder; CVolsInStream *volsInStreamSpec = NULL; CMyComPtr volsInStream; CLocalProgress *lps = new CLocalProgress; CMyComPtr 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 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 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 inStream; if (item.IsEncrypted()) { // CMyComPtr cryptoSetPassword; if (item.UnPackVersion >= 29) { if (!rar3CryptoDecoder) { rar3CryptoDecoderSpec = new NCrypto::NRar3::CDecoder; rar3CryptoDecoder = rar3CryptoDecoderSpec; } // rar3CryptoDecoderSpec->SetRar350Mode(item.UnPackVersion < 36); /* CMyComPtr 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 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 decoder = methodItems[m].Coder; CMyComPtr 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) }}