xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/Zip/ZipItem.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // Archive/ZipItem.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../../C/CpuArch.h"
6 #include "../../../../C/7zCrc.h"
7 
8 #include "../../../Common/IntToString.h"
9 #include "../../../Common/MyLinux.h"
10 #include "../../../Common/StringConvert.h"
11 
12 #include "../../../Windows/PropVariantUtils.h"
13 
14 #include "../Common/ItemNameUtils.h"
15 
16 #include "ZipItem.h"
17 
18 namespace NArchive {
19 namespace NZip {
20 
21 using namespace NFileHeader;
22 
23 
24 /*
25 const char *k_SpecName_NTFS_STREAM = "@@NTFS@STREAM@";
26 const char *k_SpecName_MAC_RESOURCE_FORK = "@@MAC@RESOURCE-FORK@";
27 */
28 
29 static const CUInt32PCharPair g_ExtraTypes[] =
30 {
31   { NExtraID::kZip64, "Zip64" },
32   { NExtraID::kNTFS, "NTFS" },
33   { NExtraID::kUnix0, "UNIX" },
34   { NExtraID::kStrongEncrypt, "StrongCrypto" },
35   { NExtraID::kUnixTime, "UT" },
36   { NExtraID::kUnix1, "UX" },
37   { NExtraID::kUnix2, "Ux" },
38   { NExtraID::kUnixN, "ux" },
39   { NExtraID::kIzUnicodeComment, "uc" },
40   { NExtraID::kIzUnicodeName, "up" },
41   { NExtraID::kIzNtSecurityDescriptor, "SD" },
42   { NExtraID::kWzAES, "WzAES" },
43   { NExtraID::kApkAlign, "ApkAlign" }
44 };
45 
PrintInfo(AString & s) const46 void CExtraSubBlock::PrintInfo(AString &s) const
47 {
48   for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_ExtraTypes); i++)
49   {
50     const CUInt32PCharPair &pair = g_ExtraTypes[i];
51     if (pair.Value == ID)
52     {
53       s += pair.Name;
54       if (ID == NExtraID::kUnixTime)
55       {
56         if (Data.Size() >= 1)
57         {
58           s.Add_Colon();
59           const Byte flags = Data[0];
60           if (flags & 1) s.Add_Char('M');
61           if (flags & 2) s.Add_Char('A');
62           if (flags & 4) s.Add_Char('C');
63           const UInt32 size = (UInt32)(Data.Size()) - 1;
64           if (size % 4 == 0)
65           {
66             s.Add_Colon();
67             s.Add_UInt32(size / 4);
68           }
69         }
70       }
71       /*
72       if (ID == NExtraID::kApkAlign && Data.Size() >= 2)
73       {
74         char sz[32];
75         sz[0] = ':';
76         ConvertUInt32ToHex(GetUi16(Data), sz + 1);
77         s += sz;
78         for (unsigned j = 2; j < Data.Size(); j++)
79         {
80           char sz[32];
81           sz[0] = '-';
82           ConvertUInt32ToHex(Data[j], sz + 1);
83           s += sz;
84         }
85       }
86       */
87       return;
88     }
89   }
90   {
91     char sz[16];
92     sz[0] = '0';
93     sz[1] = 'x';
94     ConvertUInt32ToHex(ID, sz + 2);
95     s += sz;
96   }
97 }
98 
99 
PrintInfo(AString & s) const100 void CExtraBlock::PrintInfo(AString &s) const
101 {
102   if (Error)
103     s.Add_OptSpaced("Extra_ERROR");
104 
105   if (MinorError)
106     s.Add_OptSpaced("Minor_Extra_ERROR");
107 
108   if (IsZip64 || IsZip64_Error)
109   {
110     s.Add_OptSpaced("Zip64");
111     if (IsZip64_Error)
112       s += "_ERROR";
113   }
114 
115   FOR_VECTOR (i, SubBlocks)
116   {
117     s.Add_Space_if_NotEmpty();
118     SubBlocks[i].PrintInfo(s);
119   }
120 }
121 
122 
ExtractNtfsTime(unsigned index,FILETIME & ft) const123 bool CExtraSubBlock::ExtractNtfsTime(unsigned index, FILETIME &ft) const
124 {
125   ft.dwHighDateTime = ft.dwLowDateTime = 0;
126   UInt32 size = (UInt32)Data.Size();
127   if (ID != NExtraID::kNTFS || size < 32)
128     return false;
129   const Byte *p = (const Byte *)Data;
130   p += 4; // for reserved
131   size -= 4;
132   while (size > 4)
133   {
134     UInt16 tag = GetUi16(p);
135     unsigned attrSize = GetUi16(p + 2);
136     p += 4;
137     size -= 4;
138     if (attrSize > size)
139       attrSize = size;
140 
141     if (tag == NNtfsExtra::kTagTime && attrSize >= 24)
142     {
143       p += 8 * index;
144       ft.dwLowDateTime = GetUi32(p);
145       ft.dwHighDateTime = GetUi32(p + 4);
146       return true;
147     }
148     p += attrSize;
149     size -= attrSize;
150   }
151   return false;
152 }
153 
Extract_UnixTime(bool isCentral,unsigned index,UInt32 & res) const154 bool CExtraSubBlock::Extract_UnixTime(bool isCentral, unsigned index, UInt32 &res) const
155 {
156   /* Info-Zip :
157      The central-header extra field contains the modification
158      time only, or no timestamp at all.
159      Size of Data is used to flag its presence or absence
160      If "Flags" indicates that Modtime is present in the local header
161      field, it MUST be present in the central header field, too
162   */
163 
164   res = 0;
165   UInt32 size = (UInt32)Data.Size();
166   if (ID != NExtraID::kUnixTime || size < 5)
167     return false;
168   const Byte *p = (const Byte *)Data;
169   const Byte flags = *p++;
170   size--;
171   if (isCentral)
172   {
173     if (index != NUnixTime::kMTime ||
174         (flags & (1 << NUnixTime::kMTime)) == 0 ||
175         size < 4)
176       return false;
177     res = GetUi32(p);
178     return true;
179   }
180   for (unsigned i = 0; i < 3; i++)
181     if ((flags & (1 << i)) != 0)
182     {
183       if (size < 4)
184         return false;
185       if (index == i)
186       {
187         res = GetUi32(p);
188         return true;
189       }
190       p += 4;
191       size -= 4;
192     }
193   return false;
194 }
195 
196 
197 // Info-ZIP's abandoned "Unix1 timestamps & owner ID info"
198 
Extract_Unix01_Time(unsigned index,UInt32 & res) const199 bool CExtraSubBlock::Extract_Unix01_Time(unsigned index, UInt32 &res) const
200 {
201   res = 0;
202   const unsigned offset = index * 4;
203   if (Data.Size() < offset + 4)
204     return false;
205   if (ID != NExtraID::kUnix0 &&
206       ID != NExtraID::kUnix1)
207     return false;
208   const Byte *p = (const Byte *)Data + offset;
209   res = GetUi32(p);
210   return true;
211 }
212 
213 /*
214 // PKWARE's Unix "extra" is similar to Info-ZIP's abandoned "Unix1 timestamps"
215 bool CExtraSubBlock::Extract_Unix_Time(unsigned index, UInt32 &res) const
216 {
217   res = 0;
218   const unsigned offset = index * 4;
219   if (ID != NExtraID::kUnix0 || Data.Size() < offset)
220     return false;
221   const Byte *p = (const Byte *)Data + offset;
222   res = GetUi32(p);
223   return true;
224 }
225 */
226 
GetNtfsTime(unsigned index,FILETIME & ft) const227 bool CExtraBlock::GetNtfsTime(unsigned index, FILETIME &ft) const
228 {
229   FOR_VECTOR (i, SubBlocks)
230   {
231     const CExtraSubBlock &sb = SubBlocks[i];
232     if (sb.ID == NFileHeader::NExtraID::kNTFS)
233       return sb.ExtractNtfsTime(index, ft);
234   }
235   return false;
236 }
237 
GetUnixTime(bool isCentral,unsigned index,UInt32 & res) const238 bool CExtraBlock::GetUnixTime(bool isCentral, unsigned index, UInt32 &res) const
239 {
240   {
241     FOR_VECTOR (i, SubBlocks)
242     {
243       const CExtraSubBlock &sb = SubBlocks[i];
244       if (sb.ID == NFileHeader::NExtraID::kUnixTime)
245         return sb.Extract_UnixTime(isCentral, index, res);
246     }
247   }
248 
249   switch (index)
250   {
251     case NUnixTime::kMTime: index = NUnixExtra::kMTime; break;
252     case NUnixTime::kATime: index = NUnixExtra::kATime; break;
253     default: return false;
254   }
255 
256   {
257     FOR_VECTOR (i, SubBlocks)
258     {
259       const CExtraSubBlock &sb = SubBlocks[i];
260       if (sb.ID == NFileHeader::NExtraID::kUnix0 ||
261           sb.ID == NFileHeader::NExtraID::kUnix1)
262         return sb.Extract_Unix01_Time(index, res);
263     }
264   }
265   return false;
266 }
267 
268 
IsDir() const269 bool CLocalItem::IsDir() const
270 {
271   return NItemName::HasTailSlash(Name, GetCodePage());
272 }
273 
IsDir() const274 bool CItem::IsDir() const
275 {
276   // FIXME: we can check InfoZip UTF-8 name at first.
277   if (NItemName::HasTailSlash(Name, GetCodePage()))
278     return true;
279 
280   Byte hostOS = GetHostOS();
281 
282   if (Size == 0 && PackSize == 0 && !Name.IsEmpty() && Name.Back() == '\\')
283   {
284     // do we need to use CharPrevExA?
285     // .NET Framework 4.5 : System.IO.Compression::CreateFromDirectory() probably writes backslashes to headers?
286     // so we support that case
287     switch (hostOS)
288     {
289       case NHostOS::kFAT:
290       case NHostOS::kNTFS:
291       case NHostOS::kHPFS:
292       case NHostOS::kVFAT:
293         return true;
294       default: break;
295     }
296   }
297 
298   if (!FromCentral)
299     return false;
300 
301   UInt16 highAttrib = (UInt16)((ExternalAttrib >> 16 ) & 0xFFFF);
302 
303   switch (hostOS)
304   {
305     case NHostOS::kAMIGA:
306       switch (highAttrib & NAmigaAttrib::kIFMT)
307       {
308         case NAmigaAttrib::kIFDIR: return true;
309         case NAmigaAttrib::kIFREG: return false;
310         default: return false; // change it throw kUnknownAttributes;
311       }
312     case NHostOS::kFAT:
313     case NHostOS::kNTFS:
314     case NHostOS::kHPFS:
315     case NHostOS::kVFAT:
316       return ((ExternalAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0);
317     case NHostOS::kAtari:
318     case NHostOS::kMac:
319     case NHostOS::kVMS:
320     case NHostOS::kVM_CMS:
321     case NHostOS::kAcorn:
322     case NHostOS::kMVS:
323       return false; // change it throw kUnknownAttributes;
324     case NHostOS::kUnix:
325       return MY_LIN_S_ISDIR(highAttrib);
326     default:
327       return false;
328   }
329 }
330 
GetWinAttrib() const331 UInt32 CItem::GetWinAttrib() const
332 {
333   UInt32 winAttrib = 0;
334   switch (GetHostOS())
335   {
336     case NHostOS::kFAT:
337     case NHostOS::kNTFS:
338       if (FromCentral)
339         winAttrib = ExternalAttrib;
340       break;
341     case NHostOS::kUnix:
342       // do we need to clear 16 low bits in this case?
343       if (FromCentral)
344       {
345         /*
346           Some programs write posix attributes in high 16 bits of ExternalAttrib
347           Also some programs can write additional marker flag:
348             0x8000 - p7zip
349             0x4000 - Zip in MacOS
350             no marker - Info-Zip
351 
352           Client code has two options to detect posix field:
353             1) check 0x8000 marker. In that case we must add 0x8000 marker here.
354             2) check that high 4 bits (file type bits in posix field) of attributes are not zero.
355         */
356 
357         winAttrib = ExternalAttrib & 0xFFFF0000;
358 
359         // #ifndef _WIN32
360         winAttrib |= 0x8000; // add posix mode marker
361         // #endif
362       }
363       break;
364     default: break;
365   }
366   if (IsDir()) // test it;
367     winAttrib |= FILE_ATTRIBUTE_DIRECTORY;
368   return winAttrib;
369 }
370 
GetPosixAttrib(UInt32 & attrib) const371 bool CItem::GetPosixAttrib(UInt32 &attrib) const
372 {
373   // some archivers can store PosixAttrib in high 16 bits even with HostOS=FAT.
374   if (FromCentral && GetHostOS() == NHostOS::kUnix)
375   {
376     attrib = ExternalAttrib >> 16;
377     return (attrib != 0);
378   }
379   attrib = 0;
380   if (IsDir())
381     attrib = MY_LIN_S_IFDIR;
382   return false;
383 }
384 
385 
CheckIzUnicode(const AString & s) const386 bool CExtraSubBlock::CheckIzUnicode(const AString &s) const
387 {
388   size_t size = Data.Size();
389   if (size < 1 + 4)
390     return false;
391   const Byte *p = (const Byte *)Data;
392   if (p[0] > 1)
393     return false;
394   if (CrcCalc(s, s.Len()) != GetUi32(p + 1))
395     return false;
396   size -= 5;
397   p += 5;
398   for (size_t i = 0; i < size; i++)
399     if (p[i] == 0)
400       return false;
401   return Check_UTF8_Buf((const char *)(const void *)p, size, false);
402 }
403 
404 
GetUnicodeString(UString & res,const AString & s,bool isComment,bool useSpecifiedCodePage,UINT codePage) const405 void CItem::GetUnicodeString(UString &res, const AString &s, bool isComment, bool useSpecifiedCodePage, UINT codePage) const
406 {
407   bool isUtf8 = IsUtf8();
408   // bool ignore_Utf8_Errors = true;
409 
410   if (!isUtf8)
411   {
412     {
413       const unsigned id = isComment ?
414           NFileHeader::NExtraID::kIzUnicodeComment:
415           NFileHeader::NExtraID::kIzUnicodeName;
416       const CObjectVector<CExtraSubBlock> &subBlocks = GetMainExtra().SubBlocks;
417 
418       FOR_VECTOR (i, subBlocks)
419       {
420         const CExtraSubBlock &sb = subBlocks[i];
421         if (sb.ID == id)
422         {
423           if (sb.CheckIzUnicode(s))
424           {
425             // const unsigned kIzUnicodeHeaderSize = 5;
426             if (Convert_UTF8_Buf_To_Unicode(
427                 (const char *)(const void *)(const Byte *)sb.Data + 5,
428                 sb.Data.Size() - 5, res))
429               return;
430           }
431           break;
432         }
433       }
434     }
435 
436     if (useSpecifiedCodePage)
437       isUtf8 = (codePage == CP_UTF8);
438     #ifdef _WIN32
439     else if (GetHostOS() == NFileHeader::NHostOS::kUnix)
440     {
441       /* Some ZIP archives in Unix use UTF-8 encoding without Utf8 flag in header.
442          We try to get name as UTF-8.
443          Do we need to do it in POSIX version also? */
444       isUtf8 = true;
445 
446       /* 21.02: we want to ignore UTF-8 errors to support file paths that are mixed
447           of UTF-8 and non-UTF-8 characters. */
448       // ignore_Utf8_Errors = false;
449       // ignore_Utf8_Errors = true;
450     }
451     #endif
452   }
453 
454 
455   if (isUtf8)
456   {
457     ConvertUTF8ToUnicode(s, res);
458     return;
459   }
460 
461   MultiByteToUnicodeString2(res, s, useSpecifiedCodePage ? codePage : GetCodePage());
462 }
463 
464 }}
465