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