// ZipHandler.cpp #include "StdAfx.h" #include "Common/ComTry.h" #include "Common/IntToString.h" #include "Windows/PropVariant.h" #include "Windows/Time.h" #include "../../IPassword.h" #include "../../Common/FilterCoder.h" #include "../../Common/ProgressUtils.h" #include "../../Common/StreamObjects.h" #include "../../Common/StreamUtils.h" #include "../../Compress/CopyCoder.h" #include "../../Compress/LzmaDecoder.h" #include "../../Compress/ImplodeDecoder.h" #include "../../Compress/PpmdZip.h" #include "../../Compress/ShrinkDecoder.h" #include "../../Crypto/WzAes.h" #include "../../Crypto/ZipCrypto.h" #include "../../Crypto/ZipStrong.h" #include "../Common/ItemNameUtils.h" #include "../Common/OutStreamWithCRC.h" #include "ZipHandler.h" using namespace NWindows; namespace NArchive { namespace NZip { static const CMethodId kMethodId_ZipBase = 0x040100; static const CMethodId kMethodId_BZip2 = 0x040202; static const char *kHostOS[] = { "FAT", "AMIGA", "VMS", "Unix", "VM/CMS", "Atari", "HPFS", "Macintosh", "Z-System", "CP/M", "TOPS-20", "NTFS", "SMS/QDOS", "Acorn", "VFAT", "MVS", "BeOS", "Tandem", "OS/400", "OS/X" }; static const char *kUnknownOS = "Unknown"; static const char *kMethods[] = { "Store", "Shrink", "Reduced1", "Reduced2", "Reduced3", "Reduced4", "Implode", "Tokenizing", "Deflate", "Deflate64", "PKImploding" }; static const char *kBZip2Method = "BZip2"; static const char *kLZMAMethod = "LZMA"; static const char *kJpegMethod = "Jpeg"; static const char *kWavPackMethod = "WavPack"; static const char *kPPMdMethod = "PPMd"; static const char *kAESMethod = "AES"; static const char *kZipCryptoMethod = "ZipCrypto"; static const char *kStrongCryptoMethod = "StrongCrypto"; static struct CStrongCryptoPair { UInt16 Id; const char *Name; } g_StrongCryptoPairs[] = { { NStrongCryptoFlags::kDES, "DES" }, { NStrongCryptoFlags::kRC2old, "RC2a" }, { NStrongCryptoFlags::k3DES168, "3DES-168" }, { NStrongCryptoFlags::k3DES112, "3DES-112" }, { NStrongCryptoFlags::kAES128, "pkAES-128" }, { NStrongCryptoFlags::kAES192, "pkAES-192" }, { NStrongCryptoFlags::kAES256, "pkAES-256" }, { NStrongCryptoFlags::kRC2, "RC2" }, { NStrongCryptoFlags::kBlowfish, "Blowfish" }, { NStrongCryptoFlags::kTwofish, "Twofish" }, { NStrongCryptoFlags::kRC4, "RC4" } }; static STATPROPSTG kProps[] = { { NULL, kpidPath, VT_BSTR}, { NULL, kpidIsDir, VT_BOOL}, { NULL, kpidSize, VT_UI8}, { NULL, kpidPackSize, VT_UI8}, { NULL, kpidMTime, VT_FILETIME}, { NULL, kpidCTime, VT_FILETIME}, { NULL, kpidATime, VT_FILETIME}, { NULL, kpidAttrib, VT_UI4}, { NULL, kpidEncrypted, VT_BOOL}, { NULL, kpidComment, VT_BSTR}, { NULL, kpidCRC, VT_UI4}, { NULL, kpidMethod, VT_BSTR}, { NULL, kpidHostOS, VT_BSTR}, { NULL, kpidUnpackVer, VT_UI4} }; static STATPROPSTG kArcProps[] = { { NULL, kpidBit64, VT_BOOL}, { NULL, kpidComment, VT_BSTR}, { NULL, kpidPhySize, VT_UI8}, { NULL, kpidOffset, VT_UI8} }; CHandler::CHandler() { InitMethodProperties(); } static AString BytesToString(const CByteBuffer &data) { AString s; int size = (int)data.GetCapacity(); if (size > 0) { char *p = s.GetBuffer(size + 1); memcpy(p, (const Byte *)data, size); p[size] = '\0'; s.ReleaseBuffer(); } return s; } IMP_IInArchive_Props IMP_IInArchive_ArcProps STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; switch(propID) { case kpidBit64: if (m_Archive.IsZip64) prop = m_Archive.IsZip64; break; case kpidComment: prop = MultiByteToUnicodeString(BytesToString(m_Archive.ArcInfo.Comment), CP_ACP); break; case kpidPhySize: prop = m_Archive.ArcInfo.GetPhySize(); break; case kpidOffset: if (m_Archive.ArcInfo.StartPosition != 0) prop = m_Archive.ArcInfo.StartPosition; break; } prop.Detach(value); COM_TRY_END return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = m_Items.Size(); return S_OK; } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; const CItemEx &item = m_Items[index]; switch(propID) { case kpidPath: prop = NItemName::GetOSName2(item.GetUnicodeString(item.Name)); break; case kpidIsDir: prop = item.IsDir(); break; case kpidSize: prop = item.UnPackSize; break; case kpidPackSize: prop = item.PackSize; break; case kpidTimeType: { FILETIME ft; UInt32 unixTime; if (item.CentralExtra.GetNtfsTime(NFileHeader::NNtfsExtra::kMTime, ft)) prop = (UInt32)NFileTimeType::kWindows; else if (item.CentralExtra.GetUnixTime(NFileHeader::NUnixTime::kMTime, unixTime)) prop = (UInt32)NFileTimeType::kUnix; else prop = (UInt32)NFileTimeType::kDOS; break; } case kpidCTime: { FILETIME ft; if (item.CentralExtra.GetNtfsTime(NFileHeader::NNtfsExtra::kCTime, ft)) prop = ft; break; } case kpidATime: { FILETIME ft; if (item.CentralExtra.GetNtfsTime(NFileHeader::NNtfsExtra::kATime, ft)) prop = ft; break; } case kpidMTime: { FILETIME utc; if (!item.CentralExtra.GetNtfsTime(NFileHeader::NNtfsExtra::kMTime, utc)) { UInt32 unixTime; if (item.CentralExtra.GetUnixTime(NFileHeader::NUnixTime::kMTime, unixTime)) NTime::UnixTimeToFileTime(unixTime, utc); else { FILETIME localFileTime; if (!NTime::DosTimeToFileTime(item.Time, localFileTime) || !LocalFileTimeToFileTime(&localFileTime, &utc)) utc.dwHighDateTime = utc.dwLowDateTime = 0; } } prop = utc; break; } case kpidAttrib: prop = item.GetWinAttributes(); break; case kpidEncrypted: prop = item.IsEncrypted(); break; case kpidComment: prop = item.GetUnicodeString(BytesToString(item.Comment)); break; case kpidCRC: if (item.IsThereCrc()) prop = item.FileCRC; break; case kpidMethod: { UInt16 methodId = item.CompressionMethod; AString method; if (item.IsEncrypted()) { if (methodId == NFileHeader::NCompressionMethod::kWzAES) { method = kAESMethod; CWzAesExtraField aesField; if (item.CentralExtra.GetWzAesField(aesField)) { method += '-'; char s[32]; ConvertUInt64ToString((aesField.Strength + 1) * 64 , s); method += s; method += ' '; methodId = aesField.Method; } } else { if (item.IsStrongEncrypted()) { CStrongCryptoField f; bool finded = false; if (item.CentralExtra.GetStrongCryptoField(f)) { for (unsigned int i = 0; i < sizeof(g_StrongCryptoPairs) / sizeof(g_StrongCryptoPairs[0]); i++) // PQR for MinGW-w64: Signed < Unsigned comparison. { const CStrongCryptoPair &pair = g_StrongCryptoPairs[i]; if (f.AlgId == pair.Id) { method += pair.Name; finded = true; break; } } } if (!finded) method += kStrongCryptoMethod; } else method += kZipCryptoMethod; method += ' '; } } if (methodId < sizeof(kMethods) / sizeof(kMethods[0])) method += kMethods[methodId]; else switch (methodId) { case NFileHeader::NCompressionMethod::kLZMA: method += kLZMAMethod; if (item.IsLzmaEOS()) method += ":EOS"; break; case NFileHeader::NCompressionMethod::kBZip2: method += kBZip2Method; break; case NFileHeader::NCompressionMethod::kJpeg: method += kJpegMethod; break; case NFileHeader::NCompressionMethod::kWavPack: method += kWavPackMethod; break; case NFileHeader::NCompressionMethod::kPPMd: method += kPPMdMethod; break; default: { char s[32]; ConvertUInt64ToString(methodId, s); method += s; } } prop = method; break; } case kpidHostOS: prop = (item.MadeByVersion.HostOS < sizeof(kHostOS) / sizeof(kHostOS[0])) ? (kHostOS[item.MadeByVersion.HostOS]) : kUnknownOS; break; case kpidUnpackVer: prop = (UInt32)item.ExtractVersion.Version; break; } prop.Detach(value); return S_OK; COM_TRY_END } class CProgressImp: public CProgressVirt { CMyComPtr _callback; public: STDMETHOD(SetTotal)(UInt64 numFiles); STDMETHOD(SetCompleted)(UInt64 numFiles); CProgressImp(IArchiveOpenCallback *callback): _callback(callback) {} }; STDMETHODIMP CProgressImp::SetTotal(UInt64 numFiles) { if (_callback) return _callback->SetTotal(&numFiles, NULL); return S_OK; } STDMETHODIMP CProgressImp::SetCompleted(UInt64 numFiles) { if (_callback) return _callback->SetCompleted(&numFiles, NULL); return S_OK; } STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *callback) { COM_TRY_BEGIN try { Close(); RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL)); RINOK(m_Archive.Open(inStream, maxCheckStartPosition)); CProgressImp progressImp(callback); return m_Archive.ReadHeaders(m_Items, &progressImp); } catch(const CInArchiveException &) { Close(); return S_FALSE; } catch(...) { Close(); throw; } COM_TRY_END } STDMETHODIMP CHandler::Close() { m_Items.Clear(); m_Archive.Close(); return S_OK; } ////////////////////////////////////// // CHandler::DecompressItems class CLzmaDecoder: public ICompressCoder, public CMyUnknownImp { NCompress::NLzma::CDecoder *DecoderSpec; CMyComPtr Decoder; public: CLzmaDecoder(); STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); MY_UNKNOWN_IMP }; CLzmaDecoder::CLzmaDecoder() { DecoderSpec = new NCompress::NLzma::CDecoder; Decoder = DecoderSpec; } HRESULT CLzmaDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) { Byte buf[9]; RINOK(ReadStream_FALSE(inStream, buf, 9)); if (buf[2] != 5 || buf[3] != 0) return E_NOTIMPL; RINOK(DecoderSpec->SetDecoderProperties2(buf + 4, 5)); return Decoder->Code(inStream, outStream, NULL, outSize, progress); } struct CMethodItem { UInt16 ZipMethod; CMyComPtr Coder; }; class CZipDecoder { NCrypto::NZip::CDecoder *_zipCryptoDecoderSpec; NCrypto::NZipStrong::CDecoder *_pkAesDecoderSpec; NCrypto::NWzAes::CDecoder *_wzAesDecoderSpec; CMyComPtr _zipCryptoDecoder; CMyComPtr _pkAesDecoder; CMyComPtr _wzAesDecoder; CFilterCoder *filterStreamSpec; CMyComPtr filterStream; CMyComPtr getTextPassword; CObjectVector methodItems; public: CZipDecoder(): _zipCryptoDecoderSpec(0), _pkAesDecoderSpec(0), _wzAesDecoderSpec(0), filterStreamSpec(0) {} HRESULT Decode( DECL_EXTERNAL_CODECS_LOC_VARS CInArchive &archive, const CItemEx &item, ISequentialOutStream *realOutStream, IArchiveExtractCallback *extractCallback, ICompressProgressInfo *compressProgress, UInt32 numThreads, Int32 &res); }; HRESULT CZipDecoder::Decode( DECL_EXTERNAL_CODECS_LOC_VARS CInArchive &archive, const CItemEx &item, ISequentialOutStream *realOutStream, IArchiveExtractCallback *extractCallback, ICompressProgressInfo *compressProgress, UInt32 numThreads, Int32 &res) { res = NExtract::NOperationResult::kDataError; CInStreamReleaser inStreamReleaser; bool needCRC = true; bool wzAesMode = false; bool pkAesMode = false; UInt16 methodId = item.CompressionMethod; if (item.IsEncrypted()) { if (item.IsStrongEncrypted()) { CStrongCryptoField f; if (item.CentralExtra.GetStrongCryptoField(f)) { pkAesMode = true; } if (!pkAesMode) { res = NExtract::NOperationResult::kUnSupportedMethod; return S_OK; } } if (methodId == NFileHeader::NCompressionMethod::kWzAES) { CWzAesExtraField aesField; if (item.CentralExtra.GetWzAesField(aesField)) { wzAesMode = true; needCRC = aesField.NeedCrc(); } } } COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC; CMyComPtr outStream = outStreamSpec; outStreamSpec->SetStream(realOutStream); outStreamSpec->Init(needCRC); UInt64 authenticationPos; CMyComPtr inStream; { UInt64 packSize = item.PackSize; if (wzAesMode) { if (packSize < NCrypto::NWzAes::kMacSize) return S_OK; packSize -= NCrypto::NWzAes::kMacSize; } UInt64 dataPos = item.GetDataPosition(); inStream.Attach(archive.CreateLimitedStream(dataPos, packSize)); authenticationPos = dataPos + packSize; } CMyComPtr cryptoFilter; if (item.IsEncrypted()) { if (wzAesMode) { CWzAesExtraField aesField; if (!item.CentralExtra.GetWzAesField(aesField)) return S_OK; methodId = aesField.Method; if (!_wzAesDecoder) { _wzAesDecoderSpec = new NCrypto::NWzAes::CDecoder; _wzAesDecoder = _wzAesDecoderSpec; } cryptoFilter = _wzAesDecoder; Byte properties = aesField.Strength; RINOK(_wzAesDecoderSpec->SetDecoderProperties2(&properties, 1)); } else if (pkAesMode) { if (!_pkAesDecoder) { _pkAesDecoderSpec = new NCrypto::NZipStrong::CDecoder; _pkAesDecoder = _pkAesDecoderSpec; } cryptoFilter = _pkAesDecoder; } else { if (!_zipCryptoDecoder) { _zipCryptoDecoderSpec = new NCrypto::NZip::CDecoder; _zipCryptoDecoder = _zipCryptoDecoderSpec; } cryptoFilter = _zipCryptoDecoder; } CMyComPtr cryptoSetPassword; RINOK(cryptoFilter.QueryInterface(IID_ICryptoSetPassword, &cryptoSetPassword)); if (!getTextPassword) extractCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getTextPassword); if (getTextPassword) { CMyComBSTR password; RINOK(getTextPassword->CryptoGetTextPassword(&password)); AString charPassword; if (wzAesMode || pkAesMode) { charPassword = UnicodeStringToMultiByte((const wchar_t *)password, CP_ACP); /* for (int i = 0;; i++) { wchar_t c = password[i]; if (c == 0) break; if (c >= 0x80) { res = NExtract::NOperationResult::kDataError; return S_OK; } charPassword += (char)c; } */ } else { // we use OEM. WinZip/Windows probably use ANSI for some files charPassword = UnicodeStringToMultiByte((const wchar_t *)password, CP_OEMCP); } HRESULT result = cryptoSetPassword->CryptoSetPassword( (const Byte *)(const char *)charPassword, charPassword.Length()); if (result != S_OK) return S_OK; } else { RINOK(cryptoSetPassword->CryptoSetPassword(0, 0)); } } int m; for (m = 0; m < methodItems.Size(); m++) if (methodItems[m].ZipMethod == methodId) break; if (m == methodItems.Size()) { CMethodItem mi; mi.ZipMethod = methodId; if (methodId == NFileHeader::NCompressionMethod::kStored) mi.Coder = new NCompress::CCopyCoder; else if (methodId == NFileHeader::NCompressionMethod::kShrunk) mi.Coder = new NCompress::NShrink::CDecoder; else if (methodId == NFileHeader::NCompressionMethod::kImploded) mi.Coder = new NCompress::NImplode::NDecoder::CCoder; else if (methodId == NFileHeader::NCompressionMethod::kLZMA) mi.Coder = new CLzmaDecoder; else if (methodId == NFileHeader::NCompressionMethod::kPPMd) mi.Coder = new NCompress::NPpmdZip::CDecoder(true); else { CMethodId szMethodID; if (methodId == NFileHeader::NCompressionMethod::kBZip2) szMethodID = kMethodId_BZip2; else { if (methodId > 0xFF) { res = NExtract::NOperationResult::kUnSupportedMethod; return S_OK; } szMethodID = kMethodId_ZipBase + (Byte)methodId; } RINOK(CreateCoder(EXTERNAL_CODECS_LOC_VARS szMethodID, mi.Coder, false)); if (mi.Coder == 0) { res = NExtract::NOperationResult::kUnSupportedMethod; return S_OK; } } m = methodItems.Add(mi); } ICompressCoder *coder = methodItems[m].Coder; { CMyComPtr setDecoderProperties; coder->QueryInterface(IID_ICompressSetDecoderProperties2, (void **)&setDecoderProperties); if (setDecoderProperties) { Byte properties = (Byte)item.Flags; RINOK(setDecoderProperties->SetDecoderProperties2(&properties, 1)); } } #ifndef _7ZIP_ST { CMyComPtr setCoderMt; coder->QueryInterface(IID_ICompressSetCoderMt, (void **)&setCoderMt); if (setCoderMt) { RINOK(setCoderMt->SetNumberOfThreads(numThreads)); } } #endif { HRESULT result = S_OK; CMyComPtr inStreamNew; if (item.IsEncrypted()) { if (!filterStream) { filterStreamSpec = new CFilterCoder; filterStream = filterStreamSpec; } filterStreamSpec->Filter = cryptoFilter; if (wzAesMode) { result = _wzAesDecoderSpec->ReadHeader(inStream); } else if (pkAesMode) { result =_pkAesDecoderSpec->ReadHeader(inStream, item.FileCRC, item.UnPackSize); if (result == S_OK) { bool passwOK; result = _pkAesDecoderSpec->CheckPassword(passwOK); if (result == S_OK && !passwOK) result = S_FALSE; } } else { result = _zipCryptoDecoderSpec->ReadHeader(inStream); } if (result == S_OK) { RINOK(filterStreamSpec->SetInStream(inStream)); inStreamReleaser.FilterCoder = filterStreamSpec; inStreamNew = filterStream; if (wzAesMode) { if (!_wzAesDecoderSpec->CheckPasswordVerifyCode()) result = S_FALSE; } } } else inStreamNew = inStream; if (result == S_OK) result = coder->Code(inStreamNew, outStream, NULL, &item.UnPackSize, compressProgress); if (result == S_FALSE) return S_OK; if (result == E_NOTIMPL) { res = NExtract::NOperationResult::kUnSupportedMethod; return S_OK; } RINOK(result); } bool crcOK = true; bool authOk = true; if (needCRC) crcOK = (outStreamSpec->GetCRC() == item.FileCRC); if (wzAesMode) { inStream.Attach(archive.CreateLimitedStream(authenticationPos, NCrypto::NWzAes::kMacSize)); if (_wzAesDecoderSpec->CheckMac(inStream, authOk) != S_OK) authOk = false; } res = ((crcOK && authOk) ? NExtract::NOperationResult::kOK : NExtract::NOperationResult::kCRCError); return S_OK; } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN CZipDecoder myDecoder; UInt64 totalUnPacked = 0, totalPacked = 0; bool allFilesMode = (numItems == (UInt32)-1); if (allFilesMode) numItems = m_Items.Size(); if(numItems == 0) return S_OK; UInt32 i; for (i = 0; i < numItems; i++) { const CItemEx &item = m_Items[allFilesMode ? i : indices[i]]; totalUnPacked += item.UnPackSize; totalPacked += item.PackSize; } RINOK(extractCallback->SetTotal(totalUnPacked)); UInt64 currentTotalUnPacked = 0, currentTotalPacked = 0; UInt64 currentItemUnPacked, currentItemPacked; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); for (i = 0; i < numItems; i++, currentTotalUnPacked += currentItemUnPacked, currentTotalPacked += currentItemPacked) { currentItemUnPacked = 0; currentItemPacked = 0; lps->InSize = currentTotalPacked; lps->OutSize = currentTotalUnPacked; RINOK(lps->SetCur()); CMyComPtr realOutStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; Int32 index = allFilesMode ? i : indices[i]; RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); CItemEx item = m_Items[index]; if (!item.FromLocal) { HRESULT res = m_Archive.ReadLocalItemAfterCdItem(item); if (res == S_FALSE) { if (item.IsDir() || realOutStream || testMode) { RINOK(extractCallback->PrepareOperation(askMode)); realOutStream.Release(); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnSupportedMethod)); } continue; } RINOK(res); } if (item.IsDir() || item.IgnoreItem()) { // if (!testMode) { RINOK(extractCallback->PrepareOperation(askMode)); realOutStream.Release(); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); } continue; } currentItemUnPacked = item.UnPackSize; currentItemPacked = item.PackSize; if (!testMode && !realOutStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); Int32 res; RINOK(myDecoder.Decode( EXTERNAL_CODECS_VARS m_Archive, item, realOutStream, extractCallback, progress, _numThreads, res)); realOutStream.Release(); RINOK(extractCallback->SetOperationResult(res)) } return S_OK; COM_TRY_END } IMPL_ISetCompressCodecsInfo }}