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