| // 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 { |
| |
| Z7_COM7F_IMF(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; |
| } |
| |
| |
| Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, |
| IArchiveUpdateCallback *callback)) |
| { |
| COM_TRY_BEGIN2 |
| |
| if (m_Archive.IsOpen()) |
| { |
| if (!m_Archive.CanUpdate()) |
| return E_NOTIMPL; |
| } |
| |
| CObjectVector<CUpdateItem> 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<ICryptoGetTextPassword2> getTextPassword; |
| { |
| CMyComPtr<IArchiveUpdateCallback> udateCallBack2(callback); |
| udateCallBack2.QueryInterface(IID_ICryptoGetTextPassword2, &getTextPassword); |
| } |
| CCompressionMethodMode options; |
| (CBaseProps &)options = _props; |
| options.DataSizeReduce = largestSize; |
| options.DataSizeReduce_Defined = largestSizeDefined; |
| |
| options.Password_Defined = false; |
| options.Password.Wipe_and_Empty(); |
| if (getTextPassword) |
| { |
| CMyComBSTR_Wipe password; |
| Int32 passwordIsDefined; |
| RINOK(getTextPassword->CryptoGetTextPassword2(&passwordIsDefined, &password)) |
| options.Password_Defined = IntToBool(passwordIsDefined); |
| if (options.Password_Defined) |
| { |
| 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; |
| bool isFilter; |
| if (FindMethod_Index(EXTERNAL_CODECS_VARS methodName, true, |
| methodId, numStreams, isFilter) < 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); |
| |
| options.Force_SeqOutMode = _force_SeqOutMode; |
| |
| 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 |
| } |
| |
| |
| |
| Z7_COM7F_IMF(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("rws")) |
| { |
| RINOK(PROPVARIANT_to_bool(prop, _force_SeqOutMode)) |
| } |
| else if (name.IsEqualTo("ros")) |
| { |
| RINOK(PROPVARIANT_to_bool(prop, _force_OpenSeq)) |
| } |
| 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; |
| } |
| |
| }} |