// ZipHandler.cpp #include "StdAfx.h" #include "../../../Common/ComTry.h" #include "../../../Common/StringConvert.h" #include "../../../Windows/PropVariant.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/StreamObjects.h" #include "../../Common/StreamUtils.h" #include "../../Compress/CopyCoder.h" #ifdef EXTERNAL_CODECS #ifndef SUPPORT_LZFSE #define SUPPORT_LZFSE #endif #endif #ifdef SUPPORT_LZFSE #include "../../Compress/LzfseDecoder.h" #endif #include "../../Compress/LzmaDecoder.h" #include "../../Compress/ImplodeDecoder.h" #include "../../Compress/PpmdZip.h" #include "../../Compress/ShrinkDecoder.h" #include "../../Compress/XzDecoder.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 char * const 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" }; const char * const kMethodNames1[kNumMethodNames1] = { "Store" , "Shrink" , "Reduce1" , "Reduce2" , "Reduce3" , "Reduce4" , "Implode" , NULL // "Tokenize" , "Deflate" , "Deflate64" , "PKImploding" , NULL , "BZip2" , NULL , "LZMA" , NULL , NULL , NULL , NULL , NULL , "zstd-pk" }; const char * const kMethodNames2[kNumMethodNames2] = { "zstd-wz" , "MP3" , "xz" , "Jpeg" , "WavPack" , "PPMd" , "LZFSE" // , "WzAES" }; #define kMethod_AES "AES" #define kMethod_ZipCrypto "ZipCrypto" #define kMethod_StrongCrypto "StrongCrypto" static const char * const kDeflateLevels[4] = { "Normal" , "Maximum" , "Fast" , "Fastest" }; static const CUInt32PCharPair g_HeaderCharacts[] = { { 0, "Encrypt" }, { 3, "Descriptor" }, // { 5, "Patched" }, { 6, kMethod_StrongCrypto }, { 11, "UTF8" }, { 14, "Alt" } }; struct CIdToNamePair { unsigned Id; const char *Name; }; static const CIdToNamePair k_StrongCryptoPairs[] = { { NStrongCrypto_AlgId::kDES, "DES" }, { NStrongCrypto_AlgId::kRC2old, "RC2a" }, { NStrongCrypto_AlgId::k3DES168, "3DES-168" }, { NStrongCrypto_AlgId::k3DES112, "3DES-112" }, { NStrongCrypto_AlgId::kAES128, "pkAES-128" }, { NStrongCrypto_AlgId::kAES192, "pkAES-192" }, { NStrongCrypto_AlgId::kAES256, "pkAES-256" }, { NStrongCrypto_AlgId::kRC2, "RC2" }, { NStrongCrypto_AlgId::kBlowfish, "Blowfish" }, { NStrongCrypto_AlgId::kTwofish, "Twofish" }, { NStrongCrypto_AlgId::kRC4, "RC4" } }; static const char *FindNameForId(const CIdToNamePair *pairs, unsigned num, unsigned id) { for (unsigned i = 0; i < num; i++) { const CIdToNamePair &pair = pairs[i]; if (id == pair.Id) return pair.Name; } return NULL; } static const Byte kProps[] = { kpidPath, kpidIsDir, kpidSize, kpidPackSize, kpidMTime, kpidCTime, kpidATime, kpidAttrib, // kpidPosixAttrib, kpidEncrypted, kpidComment, kpidCRC, kpidMethod, kpidCharacts, kpidHostOS, kpidUnpackVer, kpidVolumeIndex, kpidOffset // kpidIsAltStream // , kpidChangeTime // for debug // , 255 // for debug }; static const Byte kArcProps[] = { kpidEmbeddedStubSize, kpidBit64, kpidComment, kpidCharacts, kpidTotalPhySize, kpidIsVolume, kpidVolumeIndex, kpidNumVolumes }; CHandler::CHandler() { InitMethodProps(); } static AString BytesToString(const CByteBuffer &data) { AString s; s.SetFrom_CalcLen((const char *)(const Byte *)data, (unsigned)data.Size()); 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: if (m_Archive.ArcInfo.Comment.Size() != 0) prop = MultiByteToUnicodeString(BytesToString(m_Archive.ArcInfo.Comment), CP_ACP); break; case kpidPhySize: prop = m_Archive.GetPhySize(); break; case kpidOffset: prop = m_Archive.GetOffset(); break; case kpidEmbeddedStubSize: { UInt64 stubSize = m_Archive.GetEmbeddedStubSize(); if (stubSize != 0) prop = stubSize; break; } case kpidTotalPhySize: if (m_Archive.IsMultiVol) prop = m_Archive.Vols.TotalBytesSize; break; case kpidVolumeIndex: if (m_Archive.IsMultiVol) prop = (UInt32)m_Archive.Vols.StartVolIndex; break; case kpidIsVolume: if (m_Archive.IsMultiVol) prop = true; break; case kpidNumVolumes: if (m_Archive.IsMultiVol) prop = (UInt32)m_Archive.Vols.Streams.Size(); break; case kpidCharacts: { AString s; if (m_Archive.LocalsWereRead) { s.Add_OptSpaced("Local"); if (m_Archive.LocalsCenterMerged) s.Add_OptSpaced("Central"); } if (m_Archive.IsZip64) s.Add_OptSpaced("Zip64"); if (m_Archive.IsCdUnsorted) s.Add_OptSpaced("Unsorted_CD"); if (m_Archive.IsApk) s.Add_OptSpaced("apk"); if (m_Archive.ExtraMinorError) s.Add_OptSpaced("Minor_Extra_ERROR"); if (!s.IsEmpty()) prop = s; break; } case kpidWarningFlags: { UInt32 v = 0; // if (m_Archive.ExtraMinorError) v |= kpv_ErrorFlags_HeadersError; if (m_Archive.HeadersWarning) v |= kpv_ErrorFlags_HeadersError; if (v != 0) prop = v; break; } case kpidWarning: { AString s; if (m_Archive.Overflow32bit) s.Add_OptSpaced("32-bit overflow in headers"); if (m_Archive.Cd_NumEntries_Overflow_16bit) s.Add_OptSpaced("16-bit overflow for number of files in headers"); if (!s.IsEmpty()) prop = s; break; } case kpidError: { if (!m_Archive.Vols.MissingName.IsEmpty()) { UString s("Missing volume : "); s += m_Archive.Vols.MissingName; prop = s; } break; } case kpidErrorFlags: { UInt32 v = 0; if (!m_Archive.IsArc) v |= kpv_ErrorFlags_IsNotArc; if (m_Archive.HeadersError) v |= kpv_ErrorFlags_HeadersError; if (m_Archive.UnexpectedEnd) v |= kpv_ErrorFlags_UnexpectedEnd; if (m_Archive.ArcInfo.Base < 0) { /* We try to support case when we have sfx-zip with embedded stub, but the stream has access only to zip part. In that case we ignore UnavailableStart error. maybe we must show warning in that case. */ UInt64 stubSize = m_Archive.GetEmbeddedStubSize(); if (stubSize < (UInt64)-m_Archive.ArcInfo.Base) v |= kpv_ErrorFlags_UnavailableStart; } if (m_Archive.NoCentralDir) v |= kpv_ErrorFlags_UnconfirmedStart; prop = v; break; } case kpidReadOnly: { if (m_Archive.IsOpen()) if (!m_Archive.CanUpdate()) prop = true; break; } // case kpidIsAltStream: prop = true; break; } return prop.Detach(value); COM_TRY_END } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = m_Items.Size(); return S_OK; } static bool NtfsUnixTimeToProp(bool fromCentral, const CExtraBlock &extra, unsigned ntfsIndex, unsigned unixIndex, NWindows::NCOM::CPropVariant &prop) { { FILETIME ft; if (extra.GetNtfsTime(ntfsIndex, ft)) { PropVariant_SetFrom_NtfsTime(prop, ft); return true; } } { UInt32 unixTime = 0; if (!extra.GetUnixTime(fromCentral, unixIndex, unixTime)) return false; /* // we allow unixTime == 0 if (unixTime == 0) return false; */ PropVariant_SetFrom_UnixTime(prop, unixTime); return true; } } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; const CItemEx &item = m_Items[index]; const CExtraBlock &extra = item.GetMainExtra(); switch (propID) { case kpidPath: { UString res; item.GetUnicodeString(res, item.Name, false, _forceCodePage, _specifiedCodePage); NItemName::ReplaceToOsSlashes_Remove_TailSlash(res, item.Is_MadeBy_Unix() // useBackslashReplacement ); /* if (item.ParentOfAltStream >= 0) { const CItemEx &prevItem = m_Items[item.ParentOfAltStream]; UString prevName; prevItem.GetUnicodeString(prevName, prevItem.Name, false, _forceCodePage, _specifiedCodePage); NItemName::ReplaceToOsSlashes_Remove_TailSlash(prevName); if (res.IsPrefixedBy(prevName)) if (IsString1PrefixedByString2(res.Ptr(prevName.Len()), k_SpecName_NTFS_STREAM)) { res.Delete(prevName.Len(), (unsigned)strlen(k_SpecName_NTFS_STREAM)); res.Insert(prevName.Len(), L":"); } } */ prop = res; break; } case kpidIsDir: prop = item.IsDir(); break; case kpidSize: { if (!item.IsBadDescriptor()) prop = item.Size; break; } case kpidPackSize: prop = item.PackSize; break; case kpidCTime: NtfsUnixTimeToProp(item.FromCentral, extra, NFileHeader::NNtfsExtra::kCTime, NFileHeader::NUnixTime::kCTime, prop); break; case kpidATime: NtfsUnixTimeToProp(item.FromCentral, extra, NFileHeader::NNtfsExtra::kATime, NFileHeader::NUnixTime::kATime, prop); break; case kpidMTime: { if (!NtfsUnixTimeToProp(item.FromCentral, extra, NFileHeader::NNtfsExtra::kMTime, NFileHeader::NUnixTime::kMTime, prop)) { if (item.Time != 0) PropVariant_SetFrom_DosTime(prop, item.Time); } break; } case kpidTimeType: { FILETIME ft; UInt32 unixTime; UInt32 type; if (extra.GetNtfsTime(NFileHeader::NNtfsExtra::kMTime, ft)) type = NFileTimeType::kWindows; else if (extra.GetUnixTime(item.FromCentral, NFileHeader::NUnixTime::kMTime, unixTime)) type = NFileTimeType::kUnix; else type = NFileTimeType::kDOS; prop = type; break; } /* // for debug to get Dos time values: case kpidChangeTime: if (item.Time != 0) PropVariant_SetFrom_DosTime(prop, item.Time); break; // for debug // time difference (dos - utc) case 255: { if (NtfsUnixTimeToProp(item.FromCentral, extra, NFileHeader::NNtfsExtra::kMTime, NFileHeader::NUnixTime::kMTime, prop)) { FILETIME localFileTime; if (item.Time != 0 && NTime::DosTime_To_FileTime(item.Time, localFileTime)) { UInt64 t1 = FILETIME_To_UInt64(prop.filetime); UInt64 t2 = FILETIME_To_UInt64(localFileTime); prop.Set_Int64(t2 - t1); } } break; } */ case kpidAttrib: prop = item.GetWinAttrib(); break; case kpidPosixAttrib: { UInt32 attrib; if (item.GetPosixAttrib(attrib)) prop = attrib; break; } case kpidEncrypted: prop = item.IsEncrypted(); break; case kpidComment: { if (item.Comment.Size() != 0) { UString res; item.GetUnicodeString(res, BytesToString(item.Comment), true, _forceCodePage, _specifiedCodePage); prop = res; } break; } case kpidCRC: if (item.IsThereCrc()) prop = item.Crc; break; case kpidMethod: { AString m; bool isWzAes = false; unsigned id = item.Method; if (id == NFileHeader::NCompressionMethod::kWzAES) { CWzAesExtra aesField; if (extra.GetWzAes(aesField)) { m += kMethod_AES; m += '-'; m.Add_UInt32(((unsigned)aesField.Strength + 1) * 64); id = aesField.Method; isWzAes = true; } } if (item.IsEncrypted()) if (!isWzAes) { if (item.IsStrongEncrypted()) { CStrongCryptoExtra f; f.AlgId = 0; if (extra.GetStrongCrypto(f)) { const char *s = FindNameForId(k_StrongCryptoPairs, ARRAY_SIZE(k_StrongCryptoPairs), f.AlgId); if (s) m += s; else { m += kMethod_StrongCrypto; m += ':'; m.Add_UInt32(f.AlgId); } if (f.CertificateIsUsed()) m += "-Cert"; } else m += kMethod_StrongCrypto; } else m += kMethod_ZipCrypto; } m.Add_Space_if_NotEmpty(); { const char *s = NULL; if (id < kNumMethodNames1) s = kMethodNames1[id]; else { int id2 = (int)id - (int)kMethodNames2Start; if (id2 >= 0 && (unsigned)id2 < kNumMethodNames2) s = kMethodNames2[id2]; } if (s) m += s; else m.Add_UInt32(id); } { unsigned level = item.GetDeflateLevel(); if (level != 0) { if (id == NFileHeader::NCompressionMethod::kLZMA) { if (level & 1) m += ":eos"; level &= ~(unsigned)1; } else if (id == NFileHeader::NCompressionMethod::kDeflate) { m += ':'; m += kDeflateLevels[level]; level = 0; } if (level != 0) { m += ":v"; m.Add_UInt32(level); } } } prop = m; break; } case kpidCharacts: { AString s; if (item.FromLocal) { s.Add_OptSpaced("Local"); item.LocalExtra.PrintInfo(s); if (item.FromCentral) { s.Add_OptSpaced(":"); s.Add_OptSpaced("Central"); } } if (item.FromCentral) { item.CentralExtra.PrintInfo(s); } UInt32 flags = item.Flags; flags &= ~(unsigned)6; // we don't need compression related bits here. if (flags != 0) { AString s2 = FlagsToString(g_HeaderCharacts, ARRAY_SIZE(g_HeaderCharacts), flags); if (!s2.IsEmpty()) { if (!s.IsEmpty()) s.Add_OptSpaced(":"); s.Add_OptSpaced(s2); } } if (item.IsBadDescriptor()) s.Add_OptSpaced("Descriptor_ERROR"); if (!s.IsEmpty()) prop = s; break; } case kpidHostOS: { if (item.FromCentral) { // 18.06: now we use HostOS only from Central::MadeByVersion const Byte hostOS = item.MadeByVersion.HostOS; TYPE_TO_PROP(kHostOS, hostOS, prop); } break; } case kpidUnpackVer: prop = (UInt32)item.ExtractVersion.Version; break; case kpidVolumeIndex: prop = item.Disk; break; case kpidOffset: prop = item.LocalHeaderPos; break; /* case kpidIsAltStream: prop = (bool)(item.ParentOfAltStream >= 0); // item.IsAltStream(); break; case kpidName: if (item.ParentOfAltStream >= 0) { // extract name of stream here } break; */ } return prop.Detach(value); COM_TRY_END } /* STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps) { *numProps = 0; return S_OK; } STDMETHODIMP CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID) { UNUSED_VAR(index); *propID = 0; *name = 0; return S_OK; } STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType) { *parentType = NParentType::kDir; *parent = (UInt32)(Int32)-1; if (index >= m_Items.Size()) return S_OK; const CItemEx &item = m_Items[index]; if (item.ParentOfAltStream >= 0) { *parentType = NParentType::kAltStream; *parent = item.ParentOfAltStream; } return S_OK; } STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) { UNUSED_VAR(index); UNUSED_VAR(propID); *data = NULL; *dataSize = 0; *propType = 0; return S_OK; } void CHandler::MarkAltStreams(CObjectVector &items) { int prevIndex = -1; UString prevName; UString name; for (unsigned i = 0; i < items.Size(); i++) { CItemEx &item = m_Items[i]; if (item.IsAltStream()) { if (prevIndex == -1) continue; if (prevName.IsEmpty()) { const CItemEx &prevItem = m_Items[prevIndex]; prevItem.GetUnicodeString(prevName, prevItem.Name, false, _forceCodePage, _specifiedCodePage); NItemName::ReplaceToOsSlashes_Remove_TailSlash(prevName); } name.Empty(); item.GetUnicodeString(name, item.Name, false, _forceCodePage, _specifiedCodePage); NItemName::ReplaceToOsSlashes_Remove_TailSlash(name); if (name.IsPrefixedBy(prevName)) if (IsString1PrefixedByString2(name.Ptr(prevName.Len()), k_SpecName_NTFS_STREAM)) item.ParentOfAltStream = prevIndex; } else { prevIndex = i; prevName.Empty(); } } } */ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *callback) { COM_TRY_BEGIN try { Close(); HRESULT res = m_Archive.Open(inStream, maxCheckStartPosition, callback, m_Items); if (res != S_OK) { m_Items.Clear(); m_Archive.ClearRefs(); // we don't want to clear error flags } // MarkAltStreams(m_Items); return res; } catch(...) { Close(); throw; } COM_TRY_END } STDMETHODIMP CHandler::Close() { m_Items.Clear(); m_Archive.Close(); return S_OK; } class CLzmaDecoder: public ICompressCoder, public ICompressSetFinishMode, public ICompressGetInStreamProcessedSize, public CMyUnknownImp { public: NCompress::NLzma::CDecoder *DecoderSpec; CMyComPtr Decoder; MY_UNKNOWN_IMP2( ICompressSetFinishMode, ICompressGetInStreamProcessedSize) STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); STDMETHOD(SetFinishMode)(UInt32 finishMode); STDMETHOD(GetInStreamProcessedSize)(UInt64 *value); CLzmaDecoder(); }; CLzmaDecoder::CLzmaDecoder() { DecoderSpec = new NCompress::NLzma::CDecoder; Decoder = DecoderSpec; } static const unsigned kZipLzmaPropsSize = 4 + LZMA_PROPS_SIZE; HRESULT CLzmaDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) { Byte buf[kZipLzmaPropsSize]; RINOK(ReadStream_FALSE(inStream, buf, kZipLzmaPropsSize)); if (buf[2] != LZMA_PROPS_SIZE || buf[3] != 0) return E_NOTIMPL; RINOK(DecoderSpec->SetDecoderProperties2(buf + 4, LZMA_PROPS_SIZE)); UInt64 inSize2 = 0; if (inSize) { inSize2 = *inSize; if (inSize2 < kZipLzmaPropsSize) return S_FALSE; inSize2 -= kZipLzmaPropsSize; } return Decoder->Code(inStream, outStream, inSize ? &inSize2 : NULL, outSize, progress); } STDMETHODIMP CLzmaDecoder::SetFinishMode(UInt32 finishMode) { DecoderSpec->FinishStream = (finishMode != 0); return S_OK; } STDMETHODIMP CLzmaDecoder::GetInStreamProcessedSize(UInt64 *value) { *value = DecoderSpec->GetInputProcessedSize() + kZipLzmaPropsSize; return S_OK; } struct CMethodItem { unsigned 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; CLzmaDecoder *lzmaDecoderSpec; public: CZipDecoder(): _zipCryptoDecoderSpec(0), _pkAesDecoderSpec(0), _wzAesDecoderSpec(0), filterStreamSpec(0), lzmaDecoderSpec(0) {} HRESULT Decode( DECL_EXTERNAL_CODECS_LOC_VARS CInArchive &archive, const CItemEx &item, ISequentialOutStream *realOutStream, IArchiveExtractCallback *extractCallback, ICompressProgressInfo *compressProgress, #ifndef _7ZIP_ST UInt32 numThreads, UInt64 memUsage, #endif Int32 &res); }; static HRESULT SkipStreamData(ISequentialInStream *stream, ICompressProgressInfo *progress, UInt64 packSize, UInt64 unpackSize, bool &thereAreData) { thereAreData = false; const size_t kBufSize = 1 << 12; Byte buf[kBufSize]; UInt64 prev = packSize; for (;;) { size_t size = kBufSize; RINOK(ReadStream(stream, buf, &size)); if (size == 0) return S_OK; thereAreData = true; packSize += size; if ((packSize - prev) >= (1 << 22)) { prev = packSize; RINOK(progress->SetRatioInfo(&packSize, &unpackSize)); } } } class COutStreamWithPadPKCS7: public ISequentialOutStream, public CMyUnknownImp { CMyComPtr _stream; UInt64 _size; UInt64 _padPos; UInt32 _padSize; bool _padFailure; public: MY_UNKNOWN_IMP STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); void SetStream(ISequentialOutStream *stream) { _stream = stream; } void ReleaseStream() { _stream.Release(); } // padSize == 0 means (no_pad Mode) void Init(UInt64 padPos, UInt32 padSize) { _padPos = padPos; _padSize = padSize; _size = 0; _padFailure = false; } UInt64 GetSize() const { return _size; } bool WasPadFailure() const { return _padFailure; } }; STDMETHODIMP COutStreamWithPadPKCS7::Write(const void *data, UInt32 size, UInt32 *processedSize) { UInt32 written = 0; HRESULT result = S_OK; if (_size < _padPos) { const UInt64 rem = _padPos - _size; UInt32 num = size; if (num > rem) num = (UInt32)rem; result = _stream->Write(data, num, &written); _size += written; if (processedSize) *processedSize = written; if (_size != _padPos || result != S_OK) return result; size -= written; data = ((const Byte *)data) + written; } _size += size; written += size; if (processedSize) *processedSize = written; if (_padSize != 0) for (; size != 0; size--) { if (*(const Byte *)data != _padSize) _padFailure = true; data = ((const Byte *)data) + 1; } return result; } HRESULT CZipDecoder::Decode( DECL_EXTERNAL_CODECS_LOC_VARS CInArchive &archive, const CItemEx &item, ISequentialOutStream *realOutStream, IArchiveExtractCallback *extractCallback, ICompressProgressInfo *compressProgress, #ifndef _7ZIP_ST UInt32 numThreads, UInt64 memUsage, #endif Int32 &res) { res = NExtract::NOperationResult::kHeadersError; CFilterCoder::C_InStream_Releaser inStreamReleaser; CFilterCoder::C_Filter_Releaser filterReleaser; bool needCRC = true; bool wzAesMode = false; bool pkAesMode = false; bool badDescriptor = item.IsBadDescriptor(); if (badDescriptor) needCRC = false; unsigned id = item.Method; CWzAesExtra aesField; // LZFSE and WinZip's AES use same id - kWzAES. if (id == NFileHeader::NCompressionMethod::kWzAES) { if (item.GetMainExtra().GetWzAes(aesField)) { if (!item.IsEncrypted()) { res = NExtract::NOperationResult::kUnsupportedMethod; return S_OK; } wzAesMode = true; needCRC = aesField.NeedCrc(); } } if (!wzAesMode) if (item.IsEncrypted()) { if (item.IsStrongEncrypted()) { CStrongCryptoExtra f; if (!item.CentralExtra.GetStrongCrypto(f)) { res = NExtract::NOperationResult::kUnsupportedMethod; return S_OK; } pkAesMode = true; } } COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC; CMyComPtr outStream = outStreamSpec; outStreamSpec->SetStream(realOutStream); outStreamSpec->Init(needCRC); CMyComPtr packStream; CLimitedSequentialInStream *limitedStreamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(limitedStreamSpec); { UInt64 packSize = item.PackSize; if (wzAesMode) { if (packSize < NCrypto::NWzAes::kMacSize) return S_OK; packSize -= NCrypto::NWzAes::kMacSize; } RINOK(archive.GetItemStream(item, true, packStream)); if (!packStream) { res = NExtract::NOperationResult::kUnavailable; return S_OK; } limitedStreamSpec->SetStream(packStream); limitedStreamSpec->Init(packSize); } res = NExtract::NOperationResult::kDataError; CMyComPtr cryptoFilter; if (item.IsEncrypted()) { if (wzAesMode) { id = aesField.Method; if (!_wzAesDecoder) { _wzAesDecoderSpec = new NCrypto::NWzAes::CDecoder; _wzAesDecoder = _wzAesDecoderSpec; } cryptoFilter = _wzAesDecoder; if (!_wzAesDecoderSpec->SetKeyMode(aesField.Strength)) { res = NExtract::NOperationResult::kUnsupportedMethod; return S_OK; } } 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 (!cryptoSetPassword) return E_FAIL; if (!getTextPassword) extractCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getTextPassword); if (getTextPassword) { CMyComBSTR_Wipe password; RINOK(getTextPassword->CryptoGetTextPassword(&password)); AString_Wipe charPassword; if (password) { /* // 22.00: do we need UTF-8 passwords here ? if (item.IsUtf8()) // 22.00 { // throw 1; ConvertUnicodeToUTF8((LPCOLESTR)password, charPassword); } else */ { UnicodeStringToMultiByte2(charPassword, (LPCOLESTR)password, CP_ACP); } /* if (wzAesMode || pkAesMode) { } else { // PASSWORD encoding for ZipCrypto: // pkzip25 / WinZip / Windows probably use ANSI // 7-Zip < 4.43 creates ZIP archives with OEM encoding in password // 7-Zip >= 4.43 creates ZIP archives only with ASCII characters in password // 7-Zip < 17.00 uses CP_OEMCP for password decoding // 7-Zip >= 17.00 uses CP_ACP for password decoding } */ } HRESULT result = cryptoSetPassword->CryptoSetPassword( (const Byte *)(const char *)charPassword, charPassword.Len()); if (result != S_OK) { res = NExtract::NOperationResult::kWrongPassword; return S_OK; } } else { res = NExtract::NOperationResult::kWrongPassword; return S_OK; // RINOK(cryptoSetPassword->CryptoSetPassword(NULL, 0)); } } unsigned m; for (m = 0; m < methodItems.Size(); m++) if (methodItems[m].ZipMethod == id) break; if (m == methodItems.Size()) { CMethodItem mi; mi.ZipMethod = id; if (id == NFileHeader::NCompressionMethod::kStore) mi.Coder = new NCompress::CCopyCoder; else if (id == NFileHeader::NCompressionMethod::kShrink) mi.Coder = new NCompress::NShrink::CDecoder; else if (id == NFileHeader::NCompressionMethod::kImplode) mi.Coder = new NCompress::NImplode::NDecoder::CCoder; else if (id == NFileHeader::NCompressionMethod::kLZMA) { lzmaDecoderSpec = new CLzmaDecoder; mi.Coder = lzmaDecoderSpec; } else if (id == NFileHeader::NCompressionMethod::kXz) mi.Coder = new NCompress::NXz::CComDecoder; else if (id == NFileHeader::NCompressionMethod::kPPMd) mi.Coder = new NCompress::NPpmdZip::CDecoder(true); #ifdef SUPPORT_LZFSE else if (id == NFileHeader::NCompressionMethod::kWzAES) mi.Coder = new NCompress::NLzfse::CDecoder; #endif else { CMethodId szMethodID; if (id == NFileHeader::NCompressionMethod::kBZip2) szMethodID = kMethodId_BZip2; else { if (id > 0xFF) { res = NExtract::NOperationResult::kUnsupportedMethod; return S_OK; } szMethodID = kMethodId_ZipBase + (Byte)id; } RINOK(CreateCoder_Id(EXTERNAL_CODECS_LOC_VARS szMethodID, false, mi.Coder)); if (!mi.Coder) { res = NExtract::NOperationResult::kUnsupportedMethod; return S_OK; } } m = methodItems.Add(mi); } const CMethodItem &mi = methodItems[m]; ICompressCoder *coder = mi.Coder; #ifndef _7ZIP_ST { CMyComPtr setCoderMt; coder->QueryInterface(IID_ICompressSetCoderMt, (void **)&setCoderMt); if (setCoderMt) { RINOK(setCoderMt->SetNumberOfThreads(numThreads)); } } // if (memUsage != 0) { CMyComPtr setMemLimit; coder->QueryInterface(IID_ICompressSetMemLimit, (void **)&setMemLimit); if (setMemLimit) { RINOK(setMemLimit->SetMemLimit(memUsage)); } } #endif { CMyComPtr setDecoderProperties; coder->QueryInterface(IID_ICompressSetDecoderProperties2, (void **)&setDecoderProperties); if (setDecoderProperties) { Byte properties = (Byte)item.Flags; RINOK(setDecoderProperties->SetDecoderProperties2(&properties, 1)); } } bool isFullStreamExpected = (!item.HasDescriptor() || item.PackSize != 0); bool needReminderCheck = false; bool dataAfterEnd = false; bool truncatedError = false; bool lzmaEosError = false; bool headersError = false; bool padError = false; bool readFromFilter = false; const bool useUnpackLimit = (id == NFileHeader::NCompressionMethod::kStore || !item.HasDescriptor() || item.Size >= ((UInt64)1 << 32) || item.LocalExtra.IsZip64 || item.CentralExtra.IsZip64 ); { HRESULT result = S_OK; if (item.IsEncrypted()) { if (!filterStream) { filterStreamSpec = new CFilterCoder(false); filterStream = filterStreamSpec; } filterReleaser.FilterCoder = filterStreamSpec; filterStreamSpec->Filter = cryptoFilter; if (wzAesMode) { result = _wzAesDecoderSpec->ReadHeader(inStream); if (result == S_OK) { if (!_wzAesDecoderSpec->Init_and_CheckPassword()) { res = NExtract::NOperationResult::kWrongPassword; return S_OK; } } } else if (pkAesMode) { isFullStreamExpected = false; result =_pkAesDecoderSpec->ReadHeader(inStream, item.Crc, item.Size); if (result == S_OK) { bool passwOK; result = _pkAesDecoderSpec->Init_and_CheckPassword(passwOK); if (result == S_OK && !passwOK) { res = NExtract::NOperationResult::kWrongPassword; return S_OK; } } } else { result = _zipCryptoDecoderSpec->ReadHeader(inStream); if (result == S_OK) { _zipCryptoDecoderSpec->Init_BeforeDecode(); /* Info-ZIP modification to ZipCrypto format: if bit 3 of the general purpose bit flag is set, it uses high byte of 16-bit File Time. Info-ZIP code probably writes 2 bytes of File Time. We check only 1 byte. */ // UInt32 v1 = GetUi16(_zipCryptoDecoderSpec->_header + NCrypto::NZip::kHeaderSize - 2); // UInt32 v2 = (item.HasDescriptor() ? (item.Time & 0xFFFF) : (item.Crc >> 16)); Byte v1 = _zipCryptoDecoderSpec->_header[NCrypto::NZip::kHeaderSize - 1]; Byte v2 = (Byte)(item.HasDescriptor() ? (item.Time >> 8) : (item.Crc >> 24)); if (v1 != v2) { res = NExtract::NOperationResult::kWrongPassword; return S_OK; } } } } if (result == S_OK) { CMyComPtr setFinishMode; coder->QueryInterface(IID_ICompressSetFinishMode, (void **)&setFinishMode); if (setFinishMode) { RINOK(setFinishMode->SetFinishMode(BoolToUInt(true))); } const UInt64 coderPackSize = limitedStreamSpec->GetRem(); if (id == NFileHeader::NCompressionMethod::kStore && item.IsEncrypted()) { // for debug : we can disable this code (kStore + 50), if we want to test CopyCoder+Filter // here we use filter without CopyCoder readFromFilter = false; COutStreamWithPadPKCS7 *padStreamSpec = NULL; CMyComPtr padStream; UInt32 padSize = 0; if (pkAesMode) { padStreamSpec = new COutStreamWithPadPKCS7; padStream = padStreamSpec; padSize = _pkAesDecoderSpec->GetPadSize((UInt32)item.Size); padStreamSpec->SetStream(outStream); padStreamSpec->Init(item.Size, padSize); } // Here we decode minimal required size, including padding const UInt64 expectedSize = item.Size + padSize; UInt64 size = coderPackSize; if (item.Size > coderPackSize) headersError = true; else if (expectedSize != coderPackSize) { headersError = true; if (coderPackSize > expectedSize) size = expectedSize; } result = filterStreamSpec->Code(inStream, padStream ? (ISequentialOutStream *)padStream : (ISequentialOutStream *)outStream, NULL, &size, compressProgress); if (outStreamSpec->GetSize() != item.Size) truncatedError = true; if (pkAesMode) { if (padStreamSpec->GetSize() != size) truncatedError = true; if (padStreamSpec->WasPadFailure()) padError = true; } } else { if (item.IsEncrypted()) { readFromFilter = true; inStreamReleaser.FilterCoder = filterStreamSpec; RINOK(filterStreamSpec->SetInStream(inStream)); /* IFilter::Init() does nothing in all zip crypto filters. So we can call any Initialize function in CFilterCoder. */ RINOK(filterStreamSpec->Init_NoSubFilterInit()); // RINOK(filterStreamSpec->SetOutStreamSize(NULL)); } try { result = coder->Code(readFromFilter ? (ISequentialInStream *)filterStream : (ISequentialInStream *)inStream, outStream, isFullStreamExpected ? &coderPackSize : NULL, // NULL, useUnpackLimit ? &item.Size : NULL, compressProgress); } catch (...) { return E_FAIL; } if (result == S_OK) { CMyComPtr getInStreamProcessedSize; coder->QueryInterface(IID_ICompressGetInStreamProcessedSize, (void **)&getInStreamProcessedSize); if (getInStreamProcessedSize && setFinishMode) { UInt64 processed; RINOK(getInStreamProcessedSize->GetInStreamProcessedSize(&processed)); if (processed != (UInt64)(Int64)-1) { if (pkAesMode) { const UInt32 padSize = _pkAesDecoderSpec->GetPadSize((UInt32)processed); if (processed + padSize > coderPackSize) truncatedError = true; else if (processed + padSize < coderPackSize) dataAfterEnd = true; else { { // here we check PKCS7 padding data from reminder (it can be inside stream buffer in coder). CMyComPtr readInStream; coder->QueryInterface(IID_ICompressReadUnusedFromInBuf, (void **)&readInStream); // CCopyCoder() for kStore doesn't read data outside of (item.Size) if (readInStream || id == NFileHeader::NCompressionMethod::kStore) { // change pad size, if we support another block size in ZipStrong. // here we request more data to detect error with data after end. const UInt32 kBufSize = NCrypto::NZipStrong::kAesPadAllign + 16; Byte buf[kBufSize]; UInt32 processedSize = 0; if (readInStream) { RINOK(readInStream->ReadUnusedFromInBuf(buf, kBufSize, &processedSize)); } if (processedSize > padSize) dataAfterEnd = true; else { size_t processedSize2 = kBufSize - processedSize; result = ReadStream(filterStream, buf + processedSize, &processedSize2); if (result == S_OK) { processedSize2 += processedSize; if (processedSize2 > padSize) dataAfterEnd = true; else if (processedSize2 < padSize) truncatedError = true; else for (unsigned i = 0; i < padSize; i++) if (buf[i] != padSize) padError = true; } } } } } } else { if (processed < coderPackSize) { if (isFullStreamExpected) dataAfterEnd = true; } else if (processed > coderPackSize) { // that case is additional check, that can show the bugs in code (coder) truncatedError = true; } needReminderCheck = isFullStreamExpected; } } } } } if (result == S_OK && id == NFileHeader::NCompressionMethod::kLZMA) if (!lzmaDecoderSpec->DecoderSpec->CheckFinishStatus(item.IsLzmaEOS())) lzmaEosError = true; } 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.Crc); if (useUnpackLimit) if (outStreamSpec->GetSize() != item.Size) truncatedError = true; if (wzAesMode) { const UInt64 unpackSize = outStreamSpec->GetSize(); const UInt64 packSize = limitedStreamSpec->GetSize(); bool thereAreData = false; // read to the end from filter or from packed stream if (SkipStreamData(readFromFilter ? (ISequentialInStream *)filterStream : (ISequentialInStream *)inStream, compressProgress, packSize, unpackSize, thereAreData) != S_OK) authOk = false; if (needReminderCheck && thereAreData) dataAfterEnd = true; if (limitedStreamSpec->GetRem() != 0) truncatedError = true; else { limitedStreamSpec->Init(NCrypto::NWzAes::kMacSize); if (_wzAesDecoderSpec->CheckMac(inStream, authOk) != S_OK) authOk = false; } } res = NExtract::NOperationResult::kCRCError; if (crcOK && authOk) { res = NExtract::NOperationResult::kOK; if (dataAfterEnd) res = NExtract::NOperationResult::kDataAfterEnd; else if (padError) res = NExtract::NOperationResult::kCRCError; else if (truncatedError) res = NExtract::NOperationResult::kUnexpectedEnd; else if (headersError) res = NExtract::NOperationResult::kHeadersError; else if (lzmaEosError) res = NExtract::NOperationResult::kHeadersError; else if (badDescriptor) res = NExtract::NOperationResult::kUnexpectedEnd; // CheckDescriptor() supports only data descriptor with signature and // it doesn't support "old" pkzip's data descriptor without signature. // So we disable that check. /* if (item.HasDescriptor() && archive.CheckDescriptor(item) != S_OK) res = NExtract::NOperationResult::kHeadersError; */ } 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)(Int32)-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.Size; 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; UInt32 index = allFilesMode ? i : indices[i]; CItemEx item = m_Items[index]; bool isLocalOffsetOK = m_Archive.IsLocalOffsetOK(item); bool skip = !isLocalOffsetOK && !item.IsDir(); if (skip) askMode = NExtract::NAskMode::kSkip; currentItemUnPacked = item.Size; currentItemPacked = item.PackSize; RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); if (!isLocalOffsetOK) { RINOK(extractCallback->PrepareOperation(askMode)); realOutStream.Release(); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnavailable)); continue; } bool headersError = false; if (!item.FromLocal) { bool isAvail = true; HRESULT res = m_Archive.ReadLocalItemAfterCdItem(item, isAvail, headersError); if (res == S_FALSE) { if (item.IsDir() || realOutStream || testMode) { RINOK(extractCallback->PrepareOperation(askMode)); realOutStream.Release(); RINOK(extractCallback->SetOperationResult( isAvail ? NExtract::NOperationResult::kHeadersError : NExtract::NOperationResult::kUnavailable)); } continue; } RINOK(res); } if (item.IsDir()) { // if (!testMode) { RINOK(extractCallback->PrepareOperation(askMode)); realOutStream.Release(); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); } continue; } if (!testMode && !realOutStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); Int32 res; HRESULT hres = myDecoder.Decode( EXTERNAL_CODECS_VARS m_Archive, item, realOutStream, extractCallback, progress, #ifndef _7ZIP_ST _props._numThreads, _props._memUsage_Decompress, #endif res); RINOK(hres); realOutStream.Release(); if (res == NExtract::NOperationResult::kOK && headersError) res = NExtract::NOperationResult::kHeadersError; RINOK(extractCallback->SetOperationResult(res)) } lps->InSize = currentTotalPacked; lps->OutSize = currentTotalUnPacked; return lps->SetCur(); COM_TRY_END } IMPL_ISetCompressCodecsInfo }}