xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/Zip/ZipHandlerOut.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // ZipHandlerOut.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/ComTry.h"
6 #include "../../../Common/StringConvert.h"
7 #include "../../../Common/StringToInt.h"
8 
9 #include "../../../Windows/PropVariant.h"
10 #include "../../../Windows/TimeUtils.h"
11 
12 #include "../../IPassword.h"
13 
14 #include "../../Common/OutBuffer.h"
15 
16 #include "../../Crypto/WzAes.h"
17 
18 #include "../Common/ItemNameUtils.h"
19 #include "../Common/ParseProperties.h"
20 
21 #include "ZipHandler.h"
22 #include "ZipUpdate.h"
23 
24 using namespace NWindows;
25 using namespace NCOM;
26 using namespace NTime;
27 
28 namespace NArchive {
29 namespace NZip {
30 
Z7_COM7F_IMF(CHandler::GetFileTimeType (UInt32 * timeType))31 Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *timeType))
32 {
33   *timeType = TimeOptions.Prec;
34   return S_OK;
35 }
36 
IsSimpleAsciiString(const wchar_t * s)37 static bool IsSimpleAsciiString(const wchar_t *s)
38 {
39   for (;;)
40   {
41     wchar_t c = *s++;
42     if (c == 0)
43       return true;
44     if (c < 0x20 || c > 0x7F)
45       return false;
46   }
47 }
48 
49 
FindZipMethod(const char * s,const char * const * names,unsigned num)50 static int FindZipMethod(const char *s, const char * const *names, unsigned num)
51 {
52   for (unsigned i = 0; i < num; i++)
53   {
54     const char *name = names[i];
55     if (name && StringsAreEqualNoCase_Ascii(s, name))
56       return (int)i;
57   }
58   return -1;
59 }
60 
FindZipMethod(const char * s)61 static int FindZipMethod(const char *s)
62 {
63   int k = FindZipMethod(s, kMethodNames1, kNumMethodNames1);
64   if (k >= 0)
65     return k;
66   k = FindZipMethod(s, kMethodNames2, kNumMethodNames2);
67   if (k >= 0)
68     return (int)kMethodNames2Start + k;
69   return -1;
70 }
71 
72 
73 #define COM_TRY_BEGIN2 try {
74 #define COM_TRY_END2 } \
75 catch(const CSystemException &e) { return e.ErrorCode; } \
76 catch(...) { return E_OUTOFMEMORY; }
77 
GetTime(IArchiveUpdateCallback * callback,unsigned index,PROPID propID,FILETIME & filetime)78 static HRESULT GetTime(IArchiveUpdateCallback *callback, unsigned index, PROPID propID, FILETIME &filetime)
79 {
80   filetime.dwHighDateTime = filetime.dwLowDateTime = 0;
81   NCOM::CPropVariant prop;
82   RINOK(callback->GetProperty(index, propID, &prop))
83   if (prop.vt == VT_FILETIME)
84     filetime = prop.filetime;
85   else if (prop.vt != VT_EMPTY)
86     return E_INVALIDARG;
87   return S_OK;
88 }
89 
90 
Z7_COM7F_IMF(CHandler::UpdateItems (ISequentialOutStream * outStream,UInt32 numItems,IArchiveUpdateCallback * callback))91 Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
92     IArchiveUpdateCallback *callback))
93 {
94   COM_TRY_BEGIN2
95 
96   if (m_Archive.IsOpen())
97   {
98     if (!m_Archive.CanUpdate())
99       return E_NOTIMPL;
100   }
101 
102   CObjectVector<CUpdateItem> updateItems;
103   updateItems.ClearAndReserve(numItems);
104 
105   bool thereAreAesUpdates = false;
106   UInt64 largestSize = 0;
107   bool largestSizeDefined = false;
108 
109   #ifdef _WIN32
110   const UINT oemCP = GetOEMCP();
111   #endif
112 
113   UString name;
114   CUpdateItem ui;
115 
116   for (UInt32 i = 0; i < numItems; i++)
117   {
118     Int32 newData;
119     Int32 newProps;
120     UInt32 indexInArc;
121 
122     if (!callback)
123       return E_FAIL;
124 
125     RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc))
126 
127     name.Empty();
128     ui.Clear();
129 
130     ui.NewProps = IntToBool(newProps);
131     ui.NewData = IntToBool(newData);
132     ui.IndexInArc = (int)indexInArc;
133     ui.IndexInClient = i;
134 
135     bool existInArchive = (indexInArc != (UInt32)(Int32)-1);
136     if (existInArchive)
137     {
138       const CItemEx &inputItem = m_Items[indexInArc];
139       if (inputItem.IsAesEncrypted())
140         thereAreAesUpdates = true;
141       if (!IntToBool(newProps))
142         ui.IsDir = inputItem.IsDir();
143       // ui.IsAltStream = inputItem.IsAltStream();
144     }
145 
146     if (IntToBool(newProps))
147     {
148       {
149         NCOM::CPropVariant prop;
150         RINOK(callback->GetProperty(i, kpidAttrib, &prop))
151         if (prop.vt == VT_EMPTY)
152           ui.Attrib = 0;
153         else if (prop.vt != VT_UI4)
154           return E_INVALIDARG;
155         else
156           ui.Attrib = prop.ulVal;
157       }
158 
159       {
160         NCOM::CPropVariant prop;
161         RINOK(callback->GetProperty(i, kpidPath, &prop))
162         if (prop.vt == VT_EMPTY)
163         {
164           // name.Empty();
165         }
166         else if (prop.vt != VT_BSTR)
167           return E_INVALIDARG;
168         else
169           name = prop.bstrVal;
170       }
171 
172       {
173         NCOM::CPropVariant prop;
174         RINOK(callback->GetProperty(i, kpidIsDir, &prop))
175         if (prop.vt == VT_EMPTY)
176           ui.IsDir = false;
177         else if (prop.vt != VT_BOOL)
178           return E_INVALIDARG;
179         else
180           ui.IsDir = (prop.boolVal != VARIANT_FALSE);
181       }
182 
183       /*
184       {
185         bool isAltStream = false;
186         {
187           NCOM::CPropVariant prop;
188           RINOK(callback->GetProperty(i, kpidIsAltStream, &prop));
189           if (prop.vt == VT_BOOL)
190             isAltStream = (prop.boolVal != VARIANT_FALSE);
191           else if (prop.vt != VT_EMPTY)
192             return E_INVALIDARG;
193         }
194 
195         if (isAltStream)
196         {
197           if (ui.IsDir)
198             return E_INVALIDARG;
199           int delim = name.ReverseFind(L':');
200           if (delim >= 0)
201           {
202             name.Delete(delim, 1);
203             name.Insert(delim, UString(k_SpecName_NTFS_STREAM));
204             ui.IsAltStream = true;
205           }
206         }
207       }
208       */
209 
210       // 22.00 : kpidTimeType is useless here : the code was disabled
211       /*
212       {
213         CPropVariant prop;
214         RINOK(callback->GetProperty(i, kpidTimeType, &prop));
215         if (prop.vt == VT_UI4)
216           ui.NtfsTime_IsDefined = (prop.ulVal == NFileTimeType::kWindows);
217         else
218           ui.NtfsTime_IsDefined = _Write_NtfsTime;
219       }
220       */
221 
222       if (TimeOptions.Write_MTime.Val) RINOK (GetTime (callback, i, kpidMTime, ui.Ntfs_MTime))
223       if (TimeOptions.Write_ATime.Val) RINOK (GetTime (callback, i, kpidATime, ui.Ntfs_ATime))
224       if (TimeOptions.Write_CTime.Val) RINOK (GetTime (callback, i, kpidCTime, ui.Ntfs_CTime))
225 
226       if (TimeOptions.Prec != k_PropVar_TimePrec_DOS)
227       {
228         if (TimeOptions.Prec == k_PropVar_TimePrec_Unix ||
229             TimeOptions.Prec == k_PropVar_TimePrec_Base)
230           ui.Write_UnixTime = ! FILETIME_IsZero (ui.Ntfs_MTime);
231         else
232         {
233           /*
234           // if we want to store zero timestamps as zero timestamp, use the following:
235             ui.Write_NtfsTime =
236             _Write_MTime ||
237             _Write_ATime ||
238             _Write_CTime;
239           */
240 
241           // We treat zero timestamp as no timestamp
242           ui.Write_NtfsTime =
243             ! FILETIME_IsZero (ui.Ntfs_MTime) ||
244             ! FILETIME_IsZero (ui.Ntfs_ATime) ||
245             ! FILETIME_IsZero (ui.Ntfs_CTime);
246         }
247       }
248 
249       /*
250         how 0 in dos time works:
251             win10 explorer extract : some random date 1601-04-25.
252             winrar 6.10 : write time.
253             7zip : MTime of archive is used
254           how 0 in tar works:
255             winrar 6.10 : 1970
256         0 in dos field can show that there is no timestamp.
257         we write correct 1970-01-01 in dos field, to support correct extraction in Win10.
258       */
259 
260       UtcFileTime_To_LocalDosTime(ui.Ntfs_MTime, ui.Time);
261 
262       NItemName::ReplaceSlashes_OsToUnix(name);
263 
264       bool needSlash = ui.IsDir;
265       const wchar_t kSlash = L'/';
266       if (!name.IsEmpty())
267       {
268         if (name.Back() == kSlash)
269         {
270           if (!ui.IsDir)
271             return E_INVALIDARG;
272           needSlash = false;
273         }
274       }
275       if (needSlash)
276         name += kSlash;
277 
278       const UINT codePage = _forceCodePage ? _specifiedCodePage : CP_OEMCP;
279       bool tryUtf8 = true;
280 
281       /*
282         Windows 10 allows users to set UTF-8 in Region Settings via option:
283         "Beta: Use Unicode UTF-8 for worldwide language support"
284         In that case Windows uses CP_UTF8 when we use CP_OEMCP.
285         21.02 fixed:
286           we set UTF-8 mark for non-latin files for such UTF-8 mode in Windows.
287           we write additional Info-Zip Utf-8 FileName Extra for non-latin names/
288       */
289 
290       if ((codePage != CP_UTF8) &&
291         #ifdef _WIN32
292           (m_ForceLocal || !m_ForceUtf8) && (oemCP != CP_UTF8)
293         #else
294           (m_ForceLocal && !m_ForceUtf8)
295         #endif
296         )
297       {
298         bool defaultCharWasUsed;
299         ui.Name = UnicodeStringToMultiByte(name, codePage, '_', defaultCharWasUsed);
300         tryUtf8 = (!m_ForceLocal && (defaultCharWasUsed ||
301           MultiByteToUnicodeString(ui.Name, codePage) != name));
302       }
303 
304       const bool isNonLatin = !name.IsAscii();
305 
306       if (tryUtf8)
307       {
308         ui.IsUtf8 = isNonLatin;
309         ConvertUnicodeToUTF8(name, ui.Name);
310 
311         #ifndef _WIN32
312         if (ui.IsUtf8 && !CheckUTF8_AString(ui.Name))
313         {
314           // if it's non-Windows and there are non-UTF8 characters we clear UTF8-flag
315           ui.IsUtf8 = false;
316         }
317         #endif
318       }
319       else if (isNonLatin)
320         Convert_Unicode_To_UTF8_Buf(name, ui.Name_Utf);
321 
322       if (ui.Name.Len() >= (1 << 16)
323           || ui.Name_Utf.Size() >= (1 << 16) - 128)
324         return E_INVALIDARG;
325 
326       {
327         NCOM::CPropVariant prop;
328         RINOK(callback->GetProperty(i, kpidComment, &prop))
329         if (prop.vt == VT_EMPTY)
330         {
331           // ui.Comment.Free();
332         }
333         else if (prop.vt != VT_BSTR)
334           return E_INVALIDARG;
335         else
336         {
337           UString s = prop.bstrVal;
338           AString a;
339           if (ui.IsUtf8)
340             ConvertUnicodeToUTF8(s, a);
341           else
342           {
343             bool defaultCharWasUsed;
344             a = UnicodeStringToMultiByte(s, codePage, '_', defaultCharWasUsed);
345           }
346           if (a.Len() >= (1 << 16))
347             return E_INVALIDARG;
348           ui.Comment.CopyFrom((const Byte *)(const char *)a, a.Len());
349         }
350       }
351 
352 
353       /*
354       if (existInArchive)
355       {
356         const CItemEx &itemInfo = m_Items[indexInArc];
357         // ui.Commented = itemInfo.IsCommented();
358         ui.Commented = false;
359         if (ui.Commented)
360         {
361           ui.CommentRange.Position = itemInfo.GetCommentPosition();
362           ui.CommentRange.Size  = itemInfo.CommentSize;
363         }
364       }
365       else
366         ui.Commented = false;
367       */
368     }
369 
370 
371     if (IntToBool(newData))
372     {
373       UInt64 size = 0;
374       if (!ui.IsDir)
375       {
376         NCOM::CPropVariant prop;
377         RINOK(callback->GetProperty(i, kpidSize, &prop))
378         if (prop.vt != VT_UI8)
379           return E_INVALIDARG;
380         size = prop.uhVal.QuadPart;
381         if (largestSize < size)
382           largestSize = size;
383         largestSizeDefined = true;
384       }
385       ui.Size = size;
386     }
387 
388     updateItems.Add(ui);
389   }
390 
391 
392   CMyComPtr<ICryptoGetTextPassword2> getTextPassword;
393   {
394     CMyComPtr<IArchiveUpdateCallback> udateCallBack2(callback);
395     udateCallBack2.QueryInterface(IID_ICryptoGetTextPassword2, &getTextPassword);
396   }
397   CCompressionMethodMode options;
398   (CBaseProps &)options = _props;
399   options.DataSizeReduce = largestSize;
400   options.DataSizeReduce_Defined = largestSizeDefined;
401 
402   options.Password_Defined = false;
403   options.Password.Wipe_and_Empty();
404   if (getTextPassword)
405   {
406     CMyComBSTR_Wipe password;
407     Int32 passwordIsDefined;
408     RINOK(getTextPassword->CryptoGetTextPassword2(&passwordIsDefined, &password))
409     options.Password_Defined = IntToBool(passwordIsDefined);
410     if (options.Password_Defined)
411     {
412       if (!m_ForceAesMode)
413         options.IsAesMode = thereAreAesUpdates;
414 
415       if (!IsSimpleAsciiString(password))
416         return E_INVALIDARG;
417       if (password)
418         UnicodeStringToMultiByte2(options.Password, (LPCOLESTR)password, CP_OEMCP);
419       if (options.IsAesMode)
420       {
421         if (options.Password.Len() > NCrypto::NWzAes::kPasswordSizeMax)
422           return E_INVALIDARG;
423       }
424     }
425   }
426 
427 
428   int mainMethod = m_MainMethod;
429 
430   if (mainMethod < 0)
431   {
432     if (!_props._methods.IsEmpty())
433     {
434       const AString &methodName = _props._methods.Front().MethodName;
435       if (!methodName.IsEmpty())
436       {
437         mainMethod = FindZipMethod(methodName);
438         if (mainMethod < 0)
439         {
440           CMethodId methodId;
441           UInt32 numStreams;
442           bool isFilter;
443           if (FindMethod_Index(EXTERNAL_CODECS_VARS methodName, true,
444               methodId, numStreams, isFilter) < 0)
445             return E_NOTIMPL;
446           if (numStreams != 1)
447             return E_NOTIMPL;
448           if (methodId == kMethodId_BZip2)
449             mainMethod = NFileHeader::NCompressionMethod::kBZip2;
450           else
451           {
452             if (methodId < kMethodId_ZipBase)
453               return E_NOTIMPL;
454             methodId -= kMethodId_ZipBase;
455             if (methodId > 0xFF)
456               return E_NOTIMPL;
457             mainMethod = (int)methodId;
458           }
459         }
460       }
461     }
462   }
463 
464   if (mainMethod < 0)
465     mainMethod = (Byte)(((_props.GetLevel() == 0) ?
466         NFileHeader::NCompressionMethod::kStore :
467         NFileHeader::NCompressionMethod::kDeflate));
468   else
469     mainMethod = (Byte)mainMethod;
470 
471   options.MethodSequence.Add((Byte)mainMethod);
472 
473   if (mainMethod != NFileHeader::NCompressionMethod::kStore)
474     options.MethodSequence.Add(NFileHeader::NCompressionMethod::kStore);
475 
476   options.Force_SeqOutMode = _force_SeqOutMode;
477 
478   CUpdateOptions uo;
479   uo.Write_MTime = TimeOptions.Write_MTime.Val;
480   uo.Write_ATime = TimeOptions.Write_ATime.Val;
481   uo.Write_CTime = TimeOptions.Write_CTime.Val;
482   /*
483   uo.Write_NtfsTime = _Write_NtfsTime &&
484     (_Write_MTime || _Write_ATime  || _Write_CTime);
485   uo.Write_UnixTime = _Write_UnixTime;
486   */
487 
488   return Update(
489       EXTERNAL_CODECS_VARS
490       m_Items, updateItems, outStream,
491       m_Archive.IsOpen() ? &m_Archive : NULL, _removeSfxBlock,
492       uo, options, callback);
493 
494   COM_TRY_END2
495 }
496 
497 
498 
Z7_COM7F_IMF(CHandler::SetProperties (const wchar_t * const * names,const PROPVARIANT * values,UInt32 numProps))499 Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps))
500 {
501   InitMethodProps();
502 
503   for (UInt32 i = 0; i < numProps; i++)
504   {
505     UString name = names[i];
506     name.MakeLower_Ascii();
507     if (name.IsEmpty())
508       return E_INVALIDARG;
509 
510     const PROPVARIANT &prop = values[i];
511 
512     if (name.IsEqualTo_Ascii_NoCase("em"))
513     {
514       if (prop.vt != VT_BSTR)
515         return E_INVALIDARG;
516       {
517         const wchar_t *m = prop.bstrVal;
518         if (IsString1PrefixedByString2_NoCase_Ascii(m, "AES"))
519         {
520           m += 3;
521           UInt32 v = 3;
522           if (*m != 0)
523           {
524             if (*m == '-')
525               m++;
526             const wchar_t *end;
527             v = ConvertStringToUInt32(m,  &end);
528             if (*end != 0 || v % 64 != 0)
529               return E_INVALIDARG;
530             v /= 64;
531             v -= 2;
532             if (v >= 3)
533               return E_INVALIDARG;
534             v++;
535           }
536           _props.AesKeyMode = (Byte)v;
537           _props.IsAesMode = true;
538           m_ForceAesMode = true;
539         }
540         else if (StringsAreEqualNoCase_Ascii(m, "ZipCrypto"))
541         {
542           _props.IsAesMode = false;
543           m_ForceAesMode = true;
544         }
545         else
546           return E_INVALIDARG;
547       }
548     }
549 
550 
551 
552     else if (name.IsEqualTo("cl"))
553     {
554       RINOK(PROPVARIANT_to_bool(prop, m_ForceLocal))
555       if (m_ForceLocal)
556         m_ForceUtf8 = false;
557     }
558     else if (name.IsEqualTo("cu"))
559     {
560       RINOK(PROPVARIANT_to_bool(prop, m_ForceUtf8))
561       if (m_ForceUtf8)
562         m_ForceLocal = false;
563     }
564     else if (name.IsEqualTo("cp"))
565     {
566       UInt32 cp = CP_OEMCP;
567       RINOK(ParsePropToUInt32(L"", prop, cp))
568       _forceCodePage = true;
569       _specifiedCodePage = cp;
570     }
571     else if (name.IsEqualTo("rsfx"))
572     {
573       RINOK(PROPVARIANT_to_bool(prop, _removeSfxBlock))
574     }
575     else if (name.IsEqualTo("rws"))
576     {
577       RINOK(PROPVARIANT_to_bool(prop, _force_SeqOutMode))
578     }
579     else if (name.IsEqualTo("ros"))
580     {
581       RINOK(PROPVARIANT_to_bool(prop, _force_OpenSeq))
582     }
583     else
584     {
585       if (name.IsEqualTo_Ascii_NoCase("m") && prop.vt == VT_UI4)
586       {
587         UInt32 id = prop.ulVal;
588         if (id > 0xFF)
589           return E_INVALIDARG;
590         m_MainMethod = (int)id;
591       }
592       else
593       {
594         bool processed = false;
595         RINOK(TimeOptions.Parse(name, prop, processed))
596         if (!processed)
597         {
598           RINOK(_props.SetProperty(name, prop))
599         }
600       }
601       // RINOK(_props.MethodInfo.ParseParamsFromPROPVARIANT(name, prop));
602     }
603   }
604 
605   _props._methods.DeleteFrontal(_props.GetNumEmptyMethods());
606   if (_props._methods.Size() > 1)
607     return E_INVALIDARG;
608   if (_props._methods.Size() == 1)
609   {
610     const AString &methodName = _props._methods[0].MethodName;
611 
612     if (!methodName.IsEmpty())
613     {
614       const char *end;
615       UInt32 id = ConvertStringToUInt32(methodName, &end);
616       if (*end == 0 && id <= 0xFF)
617         m_MainMethod = (int)id;
618       else if (methodName.IsEqualTo_Ascii_NoCase("Copy")) // it's alias for "Store"
619         m_MainMethod = 0;
620     }
621   }
622 
623   return S_OK;
624 }
625 
626 }}
627