// Archive/RarIn.cpp #include "StdAfx.h" #include "../../../../C/7zCrc.h" #include "Common/StringConvert.h" #include "Common/UTFConvert.h" #include "../../Common/LimitedStreams.h" #include "../../Common/StreamUtils.h" #include "../Common/FindSignature.h" #include "RarIn.h" namespace NArchive { namespace NRar { void CInArchive::ThrowExceptionWithCode( CInArchiveException::CCauseType cause) { throw CInArchiveException(cause); } HRESULT CInArchive::Open(IInStream *inStream, const UInt64 *searchHeaderSizeLimit) { try { Close(); HRESULT res = Open2(inStream, searchHeaderSizeLimit); if (res == S_OK) return res; Close(); return res; } catch(...) { Close(); throw; } } void CInArchive::Close() { m_Stream.Release(); } static inline bool TestMarkerCandidate(const void *aTestBytes) { for (UInt32 i = 0; i < NHeader::kMarkerSize; i++) if (((const Byte *)aTestBytes)[i] != NHeader::kMarker[i]) return false; return true; } HRESULT CInArchive::FindAndReadMarker(IInStream *stream, const UInt64 *searchHeaderSizeLimit) { RINOK(FindSignatureInStream(stream, NHeader::kMarker, NHeader::kMarkerSize, searchHeaderSizeLimit, m_ArchiveStartPosition)); m_Stream = stream; m_Position = m_ArchiveStartPosition + NHeader::kMarkerSize; return m_Stream->Seek(m_Position, STREAM_SEEK_SET, NULL); } void CInArchive::ThrowUnexpectedEndOfArchiveException() { ThrowExceptionWithCode(CInArchiveException::kUnexpectedEndOfArchive); } bool CInArchive::ReadBytesAndTestSize(void *data, UInt32 size) { if (m_CryptoMode) { const Byte *bufData = m_DecryptedDataAligned; UInt32 bufSize = m_DecryptedDataSize; UInt32 i; for (i = 0; i < size && m_CryptoPos < bufSize; i++) ((Byte *)data)[i] = bufData[m_CryptoPos++]; return (i == size); } return (ReadStream_FALSE(m_Stream, data, size) == S_OK); } void CInArchive::ReadBytesAndTestResult(void *data, UInt32 size) { if(!ReadBytesAndTestSize(data,size)) ThrowUnexpectedEndOfArchiveException(); } HRESULT CInArchive::ReadBytes(void *data, UInt32 size, UInt32 *processedSize) { size_t realProcessedSize = size; HRESULT result = ReadStream(m_Stream, data, &realProcessedSize); if (processedSize != NULL) *processedSize = (UInt32)realProcessedSize; AddToSeekValue(realProcessedSize); return result; } static UInt32 CrcUpdateUInt16(UInt32 crc, UInt16 v) { crc = CRC_UPDATE_BYTE(crc, (Byte)(v & 0xFF)); crc = CRC_UPDATE_BYTE(crc, (Byte)((v >> 8) & 0xFF)); return crc; } static UInt32 CrcUpdateUInt32(UInt32 crc, UInt32 v) { crc = CRC_UPDATE_BYTE(crc, (Byte)(v & 0xFF)); crc = CRC_UPDATE_BYTE(crc, (Byte)((v >> 8) & 0xFF)); crc = CRC_UPDATE_BYTE(crc, (Byte)((v >> 16) & 0xFF)); crc = CRC_UPDATE_BYTE(crc, (Byte)((v >> 24) & 0xFF)); return crc; } HRESULT CInArchive::Open2(IInStream *stream, const UInt64 *searchHeaderSizeLimit) { m_CryptoMode = false; RINOK(stream->Seek(0, STREAM_SEEK_SET, &m_StreamStartPosition)); m_Position = m_StreamStartPosition; RINOK(FindAndReadMarker(stream, searchHeaderSizeLimit)); Byte buf[NHeader::NArchive::kArchiveHeaderSize]; UInt32 processedSize; ReadBytes(buf, sizeof(buf), &processedSize); if (processedSize != sizeof(buf)) return S_FALSE; m_CurData = buf; m_CurPos = 0; m_PosLimit = sizeof(buf); m_ArchiveHeader.CRC = ReadUInt16(); m_ArchiveHeader.Type = ReadByte(); m_ArchiveHeader.Flags = ReadUInt16(); m_ArchiveHeader.Size = ReadUInt16(); m_ArchiveHeader.Reserved1 = ReadUInt16(); m_ArchiveHeader.Reserved2 = ReadUInt32(); m_ArchiveHeader.EncryptVersion = 0; UInt32 crc = CRC_INIT_VAL; crc = CRC_UPDATE_BYTE(crc, m_ArchiveHeader.Type); crc = CrcUpdateUInt16(crc, m_ArchiveHeader.Flags); crc = CrcUpdateUInt16(crc, m_ArchiveHeader.Size); crc = CrcUpdateUInt16(crc, m_ArchiveHeader.Reserved1); crc = CrcUpdateUInt32(crc, m_ArchiveHeader.Reserved2); if (m_ArchiveHeader.IsThereEncryptVer() && m_ArchiveHeader.Size > NHeader::NArchive::kArchiveHeaderSize) { ReadBytes(&m_ArchiveHeader.EncryptVersion, 1, &processedSize); if (processedSize != 1) return S_FALSE; crc = CRC_UPDATE_BYTE(crc, m_ArchiveHeader.EncryptVersion); } if(m_ArchiveHeader.CRC != (CRC_GET_DIGEST(crc) & 0xFFFF)) ThrowExceptionWithCode(CInArchiveException::kArchiveHeaderCRCError); if (m_ArchiveHeader.Type != NHeader::NBlockType::kArchiveHeader) return S_FALSE; m_ArchiveCommentPosition = m_Position; m_SeekOnArchiveComment = true; return S_OK; } void CInArchive::SkipArchiveComment() { if (!m_SeekOnArchiveComment) return; AddToSeekValue(m_ArchiveHeader.Size - m_ArchiveHeader.GetBaseSize()); m_SeekOnArchiveComment = false; } void CInArchive::GetArchiveInfo(CInArchiveInfo &archiveInfo) const { archiveInfo.StartPosition = m_ArchiveStartPosition; archiveInfo.Flags = m_ArchiveHeader.Flags; archiveInfo.CommentPosition = m_ArchiveCommentPosition; archiveInfo.CommentSize = (UInt16)(m_ArchiveHeader.Size - NHeader::NArchive::kArchiveHeaderSize); } static void DecodeUnicodeFileName(const char *name, const Byte *encName, int encSize, wchar_t *unicodeName, int maxDecSize) { int encPos = 0; int decPos = 0; int 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: { int length = encName[encPos++]; if (length & 0x80) { Byte correction = encName[encPos++]; for (length = (length & 0x7f) + 2; length > 0 && decPos < maxDecSize; length--, decPos++) unicodeName[decPos] = (wchar_t)(((name[decPos] + correction) & 0xff) + (highByte << 8)); } else for (length += 2; length > 0 && decPos < maxDecSize; length--, decPos++) unicodeName[decPos] = name[decPos]; } break; } flags <<= 2; flagBits -= 2; } unicodeName[decPos < maxDecSize ? decPos : maxDecSize - 1] = 0; } void CInArchive::ReadName(CItemEx &item, int nameSize) { item.UnicodeName.Empty(); if (nameSize > 0) { m_NameBuffer.EnsureCapacity(nameSize + 1); char *buffer = (char *)m_NameBuffer; for (int i = 0; i < nameSize; i++) buffer[i] = ReadByte(); int mainLen; for (mainLen = 0; mainLen < nameSize; mainLen++) if (buffer[mainLen] == '\0') break; buffer[mainLen] = '\0'; item.Name = buffer; if(item.HasUnicodeName()) { if(mainLen < nameSize) { int unicodeNameSizeMax = MyMin(nameSize, (0x400)); _unicodeNameBuffer.EnsureCapacity(unicodeNameSizeMax + 1); DecodeUnicodeFileName(buffer, (const Byte *)buffer + mainLen + 1, nameSize - (mainLen + 1), _unicodeNameBuffer, unicodeNameSizeMax); item.UnicodeName = _unicodeNameBuffer; } else if (!ConvertUTF8ToUnicode(item.Name, item.UnicodeName)) item.UnicodeName.Empty(); } } else item.Name.Empty(); } Byte CInArchive::ReadByte() { if (m_CurPos >= m_PosLimit) throw CInArchiveException(CInArchiveException::kIncorrectArchive); return m_CurData[m_CurPos++]; } UInt16 CInArchive::ReadUInt16() { UInt16 value = 0; for (int i = 0; i < 2; i++) { Byte b = ReadByte(); value |= (UInt16(b) << (8 * i)); } return value; } UInt32 CInArchive::ReadUInt32() { UInt32 value = 0; for (int i = 0; i < 4; i++) { Byte b = ReadByte(); value |= (UInt32(b) << (8 * i)); } return value; } void CInArchive::ReadTime(Byte mask, CRarTime &rarTime) { rarTime.LowSecond = (Byte)(((mask & 4) != 0) ? 1 : 0); int numDigits = (mask & 3); rarTime.SubTime[0] = rarTime.SubTime[1] = rarTime.SubTime[2] = 0; for (int i = 0; i < numDigits; i++) rarTime.SubTime[3 - numDigits + i] = ReadByte(); } void CInArchive::ReadHeaderReal(CItemEx &item) { item.Flags = m_BlockHeader.Flags; item.PackSize = ReadUInt32(); item.Size = ReadUInt32(); item.HostOS = ReadByte(); item.FileCRC = ReadUInt32(); item.MTime.DosTime = ReadUInt32(); item.UnPackVersion = ReadByte(); item.Method = ReadByte(); int nameSize = ReadUInt16(); item.Attrib = ReadUInt32(); item.MTime.LowSecond = 0; item.MTime.SubTime[0] = item.MTime.SubTime[1] = item.MTime.SubTime[2] = 0; if((item.Flags & NHeader::NFile::kSize64Bits) != 0) { item.PackSize |= ((UInt64)ReadUInt32() << 32); item.Size |= ((UInt64)ReadUInt32() << 32); } ReadName(item, nameSize); if (item.HasSalt()) for (int i = 0; i < sizeof(item.Salt); i++) item.Salt[i] = ReadByte(); // some rar archives have HasExtTime flag without field. if (m_CurPos < m_PosLimit && item.HasExtTime()) { Byte accessMask = (Byte)(ReadByte() >> 4); Byte b = ReadByte(); Byte modifMask = (Byte)(b >> 4); Byte createMask = (Byte)(b & 0xF); if ((modifMask & 8) != 0) ReadTime(modifMask, item.MTime); item.CTimeDefined = ((createMask & 8) != 0); if (item.CTimeDefined) { item.CTime.DosTime = ReadUInt32(); ReadTime(createMask, item.CTime); } item.ATimeDefined = ((accessMask & 8) != 0); if (item.ATimeDefined) { item.ATime.DosTime = ReadUInt32(); ReadTime(accessMask, item.ATime); } } UInt16 fileHeaderWithNameSize = (UInt16)m_CurPos; 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); } void CInArchive::AddToSeekValue(UInt64 addValue) { m_Position += addValue; } HRESULT CInArchive::GetNextItem(CItemEx &item, ICryptoGetTextPassword *getTextPassword, bool &decryptionError) { decryptionError = false; if (m_SeekOnArchiveComment) SkipArchiveComment(); for (;;) { if(!SeekInArchive(m_Position)) return S_FALSE; if (!m_CryptoMode && (m_ArchiveHeader.Flags & NHeader::NArchive::kBlockHeadersAreEncrypted) != 0) { m_CryptoMode = false; if (getTextPassword == 0) return S_FALSE; if(!SeekInArchive(m_Position)) return S_FALSE; if (!m_RarAES) { m_RarAESSpec = new NCrypto::NRar29::CDecoder; m_RarAES = m_RarAESSpec; } m_RarAESSpec->SetRar350Mode(m_ArchiveHeader.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)) UString unicodePassword(password); CByteBuffer buffer; const UInt32 sizeInBytes = unicodePassword.Length() * 2; buffer.SetCapacity(sizeInBytes); for (int i = 0; i < unicodePassword.Length(); i++) { wchar_t c = unicodePassword[i]; ((Byte *)buffer)[i * 2] = (Byte)c; ((Byte *)buffer)[i * 2 + 1] = (Byte)(c >> 8); } RINOK(m_RarAESSpec->CryptoSetPassword((const Byte *)buffer, sizeInBytes)); const UInt32 kDecryptedBufferSize = (1 << 12); if (m_DecryptedData.GetCapacity() == 0) { const UInt32 kAlign = 16; m_DecryptedData.SetCapacity(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.EnsureCapacity(7); if(!ReadBytesAndTestSize((Byte *)m_FileHeaderData, 7)) return S_FALSE; m_CurData = (Byte *)m_FileHeaderData; m_CurPos = 0; m_PosLimit = 7; m_BlockHeader.CRC = ReadUInt16(); m_BlockHeader.Type = ReadByte(); m_BlockHeader.Flags = ReadUInt16(); m_BlockHeader.HeadSize = ReadUInt16(); if (m_BlockHeader.HeadSize < 7) ThrowExceptionWithCode(CInArchiveException::kIncorrectArchive); if (m_BlockHeader.Type == NHeader::NBlockType::kEndOfArchive) return S_FALSE; if (m_BlockHeader.Type == NHeader::NBlockType::kFileHeader) { m_FileHeaderData.EnsureCapacity(m_BlockHeader.HeadSize); m_CurData = (Byte *)m_FileHeaderData; m_PosLimit = m_BlockHeader.HeadSize; ReadBytesAndTestResult(m_CurData + m_CurPos, m_BlockHeader.HeadSize - 7); ReadHeaderReal(item); if ((CrcCalc(m_CurData + 2, m_BlockHeader.HeadSize - item.CommentSize - 2) & 0xFFFF) != m_BlockHeader.CRC) ThrowExceptionWithCode(CInArchiveException::kFileHeaderCRCError); FinishCryptoBlock(); m_CryptoMode = false; SeekInArchive(m_Position); // Move Position to compressed Data; AddToSeekValue(item.PackSize); // m_Position points to next header; return S_OK; } if (m_CryptoMode && m_BlockHeader.HeadSize > (1 << 10)) { decryptionError = true; return S_FALSE; } if ((m_BlockHeader.Flags & NHeader::NBlock::kLongBlock) != 0) { m_FileHeaderData.EnsureCapacity(7 + 4); m_CurData = (Byte *)m_FileHeaderData; ReadBytesAndTestResult(m_CurData + m_CurPos, 4); // test it m_PosLimit = 7 + 4; UInt32 dataSize = ReadUInt32(); AddToSeekValue(dataSize); if (m_CryptoMode && dataSize > (1 << 27)) { decryptionError = true; return S_FALSE; } m_CryptoPos = m_BlockHeader.HeadSize; } else m_CryptoPos = 0; AddToSeekValue(m_BlockHeader.HeadSize); FinishCryptoBlock(); m_CryptoMode = false; } } bool CInArchive::SeekInArchive(UInt64 position) { UInt64 newPosition; m_Stream->Seek(position, STREAM_SEEK_SET, &newPosition); return newPosition == position; } ISequentialInStream* CInArchive::CreateLimitedStream(UInt64 position, UInt64 size) { CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(streamSpec); SeekInArchive(position); streamSpec->SetStream(m_Stream); streamSpec->Init(size); return inStream.Detach(); } }}