// ZipHandlerOut.cpp #include "StdAfx.h" #include "../../../Common/ComTry.h" #include "../../../Common/StringConvert.h" #include "../../../Common/StringToInt.h" #include "../../../Windows/PropVariant.h" #include "../../../Windows/TimeUtils.h" #include "../../IPassword.h" #include "../../Common/OutBuffer.h" #include "../../Crypto/WzAes.h" #include "../Common/ItemNameUtils.h" #include "../Common/ParseProperties.h" #include "ZipHandler.h" #include "ZipUpdate.h" using namespace NWindows; using namespace NCOM; using namespace NTime; namespace NArchive { namespace NZip { STDMETHODIMP CHandler::GetFileTimeType(UInt32 *timeType) { *timeType = TimeOptions.Prec; return S_OK; } static bool IsSimpleAsciiString(const wchar_t *s) { for (;;) { wchar_t c = *s++; if (c == 0) return true; if (c < 0x20 || c > 0x7F) return false; } } static int FindZipMethod(const char *s, const char * const *names, unsigned num) { for (unsigned i = 0; i < num; i++) { const char *name = names[i]; if (name && StringsAreEqualNoCase_Ascii(s, name)) return (int)i; } return -1; } static int FindZipMethod(const char *s) { int k = FindZipMethod(s, kMethodNames1, kNumMethodNames1); if (k >= 0) return k; k = FindZipMethod(s, kMethodNames2, kNumMethodNames2); if (k >= 0) return (int)kMethodNames2Start + k; return -1; } #define COM_TRY_BEGIN2 try { #define COM_TRY_END2 } \ catch(const CSystemException &e) { return e.ErrorCode; } \ catch(...) { return E_OUTOFMEMORY; } static HRESULT GetTime(IArchiveUpdateCallback *callback, unsigned index, PROPID propID, FILETIME &filetime) { filetime.dwHighDateTime = filetime.dwLowDateTime = 0; NCOM::CPropVariant prop; RINOK(callback->GetProperty(index, propID, &prop)); if (prop.vt == VT_FILETIME) filetime = prop.filetime; else if (prop.vt != VT_EMPTY) return E_INVALIDARG; return S_OK; } STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, IArchiveUpdateCallback *callback) { COM_TRY_BEGIN2 if (m_Archive.IsOpen()) { if (!m_Archive.CanUpdate()) return E_NOTIMPL; } CObjectVector updateItems; updateItems.ClearAndReserve(numItems); bool thereAreAesUpdates = false; UInt64 largestSize = 0; bool largestSizeDefined = false; #ifdef _WIN32 const UINT oemCP = GetOEMCP(); #endif UString name; CUpdateItem ui; for (UInt32 i = 0; i < numItems; i++) { Int32 newData; Int32 newProps; UInt32 indexInArc; if (!callback) return E_FAIL; RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc)); name.Empty(); ui.Clear(); ui.NewProps = IntToBool(newProps); ui.NewData = IntToBool(newData); ui.IndexInArc = (int)indexInArc; ui.IndexInClient = i; bool existInArchive = (indexInArc != (UInt32)(Int32)-1); if (existInArchive) { const CItemEx &inputItem = m_Items[indexInArc]; if (inputItem.IsAesEncrypted()) thereAreAesUpdates = true; if (!IntToBool(newProps)) ui.IsDir = inputItem.IsDir(); // ui.IsAltStream = inputItem.IsAltStream(); } if (IntToBool(newProps)) { { NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidAttrib, &prop)); if (prop.vt == VT_EMPTY) ui.Attrib = 0; else if (prop.vt != VT_UI4) return E_INVALIDARG; else ui.Attrib = prop.ulVal; } { NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidPath, &prop)); if (prop.vt == VT_EMPTY) { // name.Empty(); } else if (prop.vt != VT_BSTR) return E_INVALIDARG; else name = prop.bstrVal; } { NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidIsDir, &prop)); if (prop.vt == VT_EMPTY) ui.IsDir = false; else if (prop.vt != VT_BOOL) return E_INVALIDARG; else ui.IsDir = (prop.boolVal != VARIANT_FALSE); } /* { bool isAltStream = false; { NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidIsAltStream, &prop)); if (prop.vt == VT_BOOL) isAltStream = (prop.boolVal != VARIANT_FALSE); else if (prop.vt != VT_EMPTY) return E_INVALIDARG; } if (isAltStream) { if (ui.IsDir) return E_INVALIDARG; int delim = name.ReverseFind(L':'); if (delim >= 0) { name.Delete(delim, 1); name.Insert(delim, UString(k_SpecName_NTFS_STREAM)); ui.IsAltStream = true; } } } */ // 22.00 : kpidTimeType is useless here : the code was disabled /* { CPropVariant prop; RINOK(callback->GetProperty(i, kpidTimeType, &prop)); if (prop.vt == VT_UI4) ui.NtfsTime_IsDefined = (prop.ulVal == NFileTimeType::kWindows); else ui.NtfsTime_IsDefined = _Write_NtfsTime; } */ if (TimeOptions.Write_MTime.Val) RINOK (GetTime (callback, i, kpidMTime, ui.Ntfs_MTime)); if (TimeOptions.Write_ATime.Val) RINOK (GetTime (callback, i, kpidATime, ui.Ntfs_ATime)); if (TimeOptions.Write_CTime.Val) RINOK (GetTime (callback, i, kpidCTime, ui.Ntfs_CTime)); if (TimeOptions.Prec != k_PropVar_TimePrec_DOS) { if (TimeOptions.Prec == k_PropVar_TimePrec_Unix || TimeOptions.Prec == k_PropVar_TimePrec_Base) ui.Write_UnixTime = ! FILETIME_IsZero (ui.Ntfs_MTime); else { /* // if we want to store zero timestamps as zero timestamp, use the following: ui.Write_NtfsTime = _Write_MTime || _Write_ATime || _Write_CTime; */ // We treat zero timestamp as no timestamp ui.Write_NtfsTime = ! FILETIME_IsZero (ui.Ntfs_MTime) || ! FILETIME_IsZero (ui.Ntfs_ATime) || ! FILETIME_IsZero (ui.Ntfs_CTime); } } /* how 0 in dos time works: win10 explorer extract : some random date 1601-04-25. winrar 6.10 : write time. 7zip : MTime of archive is used how 0 in tar works: winrar 6.10 : 1970 0 in dos field can show that there is no timestamp. we write correct 1970-01-01 in dos field, to support correct extraction in Win10. */ UtcFileTime_To_LocalDosTime(ui.Ntfs_MTime, ui.Time); NItemName::ReplaceSlashes_OsToUnix(name); bool needSlash = ui.IsDir; const wchar_t kSlash = L'/'; if (!name.IsEmpty()) { if (name.Back() == kSlash) { if (!ui.IsDir) return E_INVALIDARG; needSlash = false; } } if (needSlash) name += kSlash; const UINT codePage = _forceCodePage ? _specifiedCodePage : CP_OEMCP; bool tryUtf8 = true; /* Windows 10 allows users to set UTF-8 in Region Settings via option: "Beta: Use Unicode UTF-8 for worldwide language support" In that case Windows uses CP_UTF8 when we use CP_OEMCP. 21.02 fixed: we set UTF-8 mark for non-latin files for such UTF-8 mode in Windows. we write additional Info-Zip Utf-8 FileName Extra for non-latin names/ */ if ((codePage != CP_UTF8) && #ifdef _WIN32 (m_ForceLocal || !m_ForceUtf8) && (oemCP != CP_UTF8) #else (m_ForceLocal && !m_ForceUtf8) #endif ) { bool defaultCharWasUsed; ui.Name = UnicodeStringToMultiByte(name, codePage, '_', defaultCharWasUsed); tryUtf8 = (!m_ForceLocal && (defaultCharWasUsed || MultiByteToUnicodeString(ui.Name, codePage) != name)); } const bool isNonLatin = !name.IsAscii(); if (tryUtf8) { ui.IsUtf8 = isNonLatin; ConvertUnicodeToUTF8(name, ui.Name); #ifndef _WIN32 if (ui.IsUtf8 && !CheckUTF8_AString(ui.Name)) { // if it's non-Windows and there are non-UTF8 characters we clear UTF8-flag ui.IsUtf8 = false; } #endif } else if (isNonLatin) Convert_Unicode_To_UTF8_Buf(name, ui.Name_Utf); if (ui.Name.Len() >= (1 << 16) || ui.Name_Utf.Size() >= (1 << 16) - 128) return E_INVALIDARG; { NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidComment, &prop)); if (prop.vt == VT_EMPTY) { // ui.Comment.Free(); } else if (prop.vt != VT_BSTR) return E_INVALIDARG; else { UString s = prop.bstrVal; AString a; if (ui.IsUtf8) ConvertUnicodeToUTF8(s, a); else { bool defaultCharWasUsed; a = UnicodeStringToMultiByte(s, codePage, '_', defaultCharWasUsed); } if (a.Len() >= (1 << 16)) return E_INVALIDARG; ui.Comment.CopyFrom((const Byte *)(const char *)a, a.Len()); } } /* if (existInArchive) { const CItemEx &itemInfo = m_Items[indexInArc]; // ui.Commented = itemInfo.IsCommented(); ui.Commented = false; if (ui.Commented) { ui.CommentRange.Position = itemInfo.GetCommentPosition(); ui.CommentRange.Size = itemInfo.CommentSize; } } else ui.Commented = false; */ } if (IntToBool(newData)) { UInt64 size = 0; if (!ui.IsDir) { NCOM::CPropVariant prop; RINOK(callback->GetProperty(i, kpidSize, &prop)); if (prop.vt != VT_UI8) return E_INVALIDARG; size = prop.uhVal.QuadPart; if (largestSize < size) largestSize = size; largestSizeDefined = true; } ui.Size = size; } updateItems.Add(ui); } CMyComPtr getTextPassword; { CMyComPtr udateCallBack2(callback); udateCallBack2.QueryInterface(IID_ICryptoGetTextPassword2, &getTextPassword); } CCompressionMethodMode options; (CBaseProps &)options = _props; options._dataSizeReduce = largestSize; options._dataSizeReduceDefined = largestSizeDefined; options.PasswordIsDefined = false; options.Password.Wipe_and_Empty(); if (getTextPassword) { CMyComBSTR_Wipe password; Int32 passwordIsDefined; RINOK(getTextPassword->CryptoGetTextPassword2(&passwordIsDefined, &password)); options.PasswordIsDefined = IntToBool(passwordIsDefined); if (options.PasswordIsDefined) { if (!m_ForceAesMode) options.IsAesMode = thereAreAesUpdates; if (!IsSimpleAsciiString(password)) return E_INVALIDARG; if (password) UnicodeStringToMultiByte2(options.Password, (LPCOLESTR)password, CP_OEMCP); if (options.IsAesMode) { if (options.Password.Len() > NCrypto::NWzAes::kPasswordSizeMax) return E_INVALIDARG; } } } int mainMethod = m_MainMethod; if (mainMethod < 0) { if (!_props._methods.IsEmpty()) { const AString &methodName = _props._methods.Front().MethodName; if (!methodName.IsEmpty()) { mainMethod = FindZipMethod(methodName); if (mainMethod < 0) { CMethodId methodId; UInt32 numStreams; if (FindMethod_Index(EXTERNAL_CODECS_VARS methodName, true, methodId, numStreams) < 0) return E_NOTIMPL; if (numStreams != 1) return E_NOTIMPL; if (methodId == kMethodId_BZip2) mainMethod = NFileHeader::NCompressionMethod::kBZip2; else { if (methodId < kMethodId_ZipBase) return E_NOTIMPL; methodId -= kMethodId_ZipBase; if (methodId > 0xFF) return E_NOTIMPL; mainMethod = (int)methodId; } } } } } if (mainMethod < 0) mainMethod = (Byte)(((_props.GetLevel() == 0) ? NFileHeader::NCompressionMethod::kStore : NFileHeader::NCompressionMethod::kDeflate)); else mainMethod = (Byte)mainMethod; options.MethodSequence.Add((Byte)mainMethod); if (mainMethod != NFileHeader::NCompressionMethod::kStore) options.MethodSequence.Add(NFileHeader::NCompressionMethod::kStore); CUpdateOptions uo; uo.Write_MTime = TimeOptions.Write_MTime.Val; uo.Write_ATime = TimeOptions.Write_ATime.Val; uo.Write_CTime = TimeOptions.Write_CTime.Val; /* uo.Write_NtfsTime = _Write_NtfsTime && (_Write_MTime || _Write_ATime || _Write_CTime); uo.Write_UnixTime = _Write_UnixTime; */ return Update( EXTERNAL_CODECS_VARS m_Items, updateItems, outStream, m_Archive.IsOpen() ? &m_Archive : NULL, _removeSfxBlock, uo, options, callback); COM_TRY_END2 } STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) { InitMethodProps(); for (UInt32 i = 0; i < numProps; i++) { UString name = names[i]; name.MakeLower_Ascii(); if (name.IsEmpty()) return E_INVALIDARG; const PROPVARIANT &prop = values[i]; if (name.IsEqualTo_Ascii_NoCase("em")) { if (prop.vt != VT_BSTR) return E_INVALIDARG; { const wchar_t *m = prop.bstrVal; if (IsString1PrefixedByString2_NoCase_Ascii(m, "aes")) { m += 3; if (StringsAreEqual_Ascii(m, "128")) _props.AesKeyMode = 1; else if (StringsAreEqual_Ascii(m, "192")) _props.AesKeyMode = 2; else if (StringsAreEqual_Ascii(m, "256") || m[0] == 0) _props.AesKeyMode = 3; else return E_INVALIDARG; _props.IsAesMode = true; m_ForceAesMode = true; } else if (StringsAreEqualNoCase_Ascii(m, "ZipCrypto")) { _props.IsAesMode = false; m_ForceAesMode = true; } else return E_INVALIDARG; } } else if (name.IsEqualTo("cl")) { RINOK(PROPVARIANT_to_bool(prop, m_ForceLocal)); if (m_ForceLocal) m_ForceUtf8 = false; } else if (name.IsEqualTo("cu")) { RINOK(PROPVARIANT_to_bool(prop, m_ForceUtf8)); if (m_ForceUtf8) m_ForceLocal = false; } else if (name.IsEqualTo("cp")) { UInt32 cp = CP_OEMCP; RINOK(ParsePropToUInt32(L"", prop, cp)); _forceCodePage = true; _specifiedCodePage = cp; } else if (name.IsEqualTo("rsfx")) { RINOK(PROPVARIANT_to_bool(prop, _removeSfxBlock)); } else { if (name.IsEqualTo_Ascii_NoCase("m") && prop.vt == VT_UI4) { UInt32 id = prop.ulVal; if (id > 0xFF) return E_INVALIDARG; m_MainMethod = (int)id; } else { bool processed = false; RINOK(TimeOptions.Parse(name, prop, processed)); if (!processed) { RINOK(_props.SetProperty(name, prop)); } } // RINOK(_props.MethodInfo.ParseParamsFromPROPVARIANT(name, prop)); } } _props._methods.DeleteFrontal(_props.GetNumEmptyMethods()); if (_props._methods.Size() > 1) return E_INVALIDARG; if (_props._methods.Size() == 1) { const AString &methodName = _props._methods[0].MethodName; if (!methodName.IsEmpty()) { const char *end; UInt32 id = ConvertStringToUInt32(methodName, &end); if (*end == 0 && id <= 0xFF) m_MainMethod = (int)id; else if (methodName.IsEqualTo_Ascii_NoCase("Copy")) // it's alias for "Store" m_MainMethod = 0; } } return S_OK; } }}