xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/XarHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // XarHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/Sha256.h"
6 #include "../../../C/Sha512.h"
7 #include "../../../C/CpuArch.h"
8 
9 #include "../../Common/ComTry.h"
10 #include "../../Common/MyLinux.h"
11 #include "../../Common/MyXml.h"
12 #include "../../Common/StringToInt.h"
13 #include "../../Common/UTFConvert.h"
14 
15 #include "../../Windows/PropVariant.h"
16 #include "../../Windows/TimeUtils.h"
17 
18 #include "../Common/LimitedStreams.h"
19 #include "../Common/ProgressUtils.h"
20 #include "../Common/RegisterArc.h"
21 #include "../Common/StreamObjects.h"
22 #include "../Common/StreamUtils.h"
23 
24 #include "../Compress/BZip2Decoder.h"
25 #include "../Compress/CopyCoder.h"
26 #include "../Compress/ZlibDecoder.h"
27 
28 #include "Common/OutStreamWithSha1.h"
29 
30 using namespace NWindows;
31 
32 #define XAR_SHOW_RAW
33 
34 #define Get16(p) GetBe16(p)
35 #define Get32(p) GetBe32(p)
36 #define Get64(p) GetBe64(p)
37 
38 namespace NArchive {
39 namespace NXar {
40 
41 Z7_CLASS_IMP_NOQIB_1(
42   CInStreamWithSha256
43   , ISequentialInStream
44 )
45   bool _sha512Mode;
46   CMyComPtr<ISequentialInStream> _stream;
47   CAlignedBuffer1 _sha256;
48   CAlignedBuffer1 _sha512;
49   UInt64 _size;
50 
Sha256()51   CSha256 *Sha256() { return (CSha256 *)(void *)(Byte *)_sha256; }
Sha512()52   CSha512 *Sha512() { return (CSha512 *)(void *)(Byte *)_sha512; }
53 public:
54   CInStreamWithSha256():
55       _sha256(sizeof(CSha256)),
56       _sha512(sizeof(CSha512))
57       {}
58   void SetStream(ISequentialInStream *stream) { _stream = stream;  }
59   void Init(bool sha512Mode)
60   {
61     _sha512Mode = sha512Mode;
62     _size = 0;
63     if (sha512Mode)
64       Sha512_Init(Sha512(), SHA512_DIGEST_SIZE);
65     else
66       Sha256_Init(Sha256());
67   }
68   void ReleaseStream() { _stream.Release(); }
69   UInt64 GetSize() const { return _size; }
70   void Final256(Byte *digest) { Sha256_Final(Sha256(), digest); }
71   void Final512(Byte *digest) { Sha512_Final(Sha512(), digest, SHA512_DIGEST_SIZE); }
72 };
73 
74 Z7_COM7F_IMF(CInStreamWithSha256::Read(void *data, UInt32 size, UInt32 *processedSize))
75 {
76   UInt32 realProcessedSize;
77   const HRESULT result = _stream->Read(data, size, &realProcessedSize);
78   _size += realProcessedSize;
79   if (_sha512Mode)
80     Sha512_Update(Sha512(), (const Byte *)data, realProcessedSize);
81   else
82     Sha256_Update(Sha256(), (const Byte *)data, realProcessedSize);
83   if (processedSize)
84     *processedSize = realProcessedSize;
85   return result;
86 }
87 
88 
89 Z7_CLASS_IMP_NOQIB_1(
90   COutStreamWithSha256
91   , ISequentialOutStream
92 )
93   bool _sha512Mode;
94   CMyComPtr<ISequentialOutStream> _stream;
95   CAlignedBuffer1 _sha256;
96   CAlignedBuffer1 _sha512;
97   UInt64 _size;
98 
99   CSha256 *Sha256() { return (CSha256 *)(void *)(Byte *)_sha256; }
100   CSha512 *Sha512() { return (CSha512 *)(void *)(Byte *)_sha512; }
101 public:
102   COutStreamWithSha256():
103       _sha256(sizeof(CSha256)),
104       _sha512(sizeof(CSha512))
105       {}
106   void SetStream(ISequentialOutStream *stream) { _stream = stream; }
107   void ReleaseStream() { _stream.Release(); }
108   void Init(bool sha512Mode)
109   {
110     _sha512Mode = sha512Mode;
111     _size = 0;
112     if (sha512Mode)
113       Sha512_Init(Sha512(), SHA512_DIGEST_SIZE);
114     else
115       Sha256_Init(Sha256());
116   }
117   UInt64 GetSize() const { return _size; }
118   void Final256(Byte *digest) { Sha256_Final(Sha256(), digest); }
119   void Final512(Byte *digest) { Sha512_Final(Sha512(), digest, SHA512_DIGEST_SIZE); }
120 };
121 
122 Z7_COM7F_IMF(COutStreamWithSha256::Write(const void *data, UInt32 size, UInt32 *processedSize))
123 {
124   HRESULT result = S_OK;
125   if (_stream)
126     result = _stream->Write(data, size, &size);
127   // if (_calculate)
128   if (_sha512Mode)
129     Sha512_Update(Sha512(), (const Byte *)data, size);
130   else
131     Sha256_Update(Sha256(), (const Byte *)data, size);
132   _size += size;
133   if (processedSize)
134     *processedSize = size;
135   return result;
136 }
137 
138 // we limit supported xml sizes:
139 // ((size_t)1 << (sizeof(size_t) / 2 + 28)) - (1u << 14);
140 static const size_t kXmlSizeMax = ((size_t)1 << 30) - (1u << 14);
141 static const size_t kXmlPackSizeMax = kXmlSizeMax;
142 
143 #define XAR_CKSUM_NONE    0
144 #define XAR_CKSUM_SHA1    1
145 #define XAR_CKSUM_MD5     2
146 #define XAR_CKSUM_SHA256  3
147 #define XAR_CKSUM_SHA512  4
148 // #define XAR_CKSUM_OTHER   3
149 // fork version of xar can use (3) as special case,
150 // where name of hash is stored as string at the end of header
151 // we do not support such hash still.
152 
153 static const char * const k_ChecksumNames[] =
154 {
155     "NONE"
156   , "SHA1"
157   , "MD5"
158   , "SHA256"
159   , "SHA512"
160 };
161 
162 static unsigned GetHashSize(int algo)
163 {
164   if (algo <= XAR_CKSUM_NONE || algo > XAR_CKSUM_SHA512)
165     return 0;
166   if (algo == XAR_CKSUM_SHA1)
167     return SHA1_DIGEST_SIZE;
168   return (16u >> XAR_CKSUM_MD5) << algo;
169 }
170 
171 #define METHOD_NAME_ZLIB  "zlib"
172 
173 static int Find_ChecksumId_for_Name(const AString &style)
174 {
175   for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_ChecksumNames); i++)
176   {
177     // old xars used upper case in "style"
178     // new xars use  lower case in "style"
179     if (style.IsEqualTo_Ascii_NoCase(k_ChecksumNames[i]))
180       return (int)i;
181   }
182   return -1;
183 }
184 
185 
186 struct CCheckSum
187 {
188   int AlgoNumber;
189   bool Error;
190   CByteBuffer Data;
191   AString Style;
192 
193   CCheckSum(): AlgoNumber(-1), Error(false) {}
194   void AddNameToString(AString &s) const;
195 };
196 
197 void CCheckSum::AddNameToString(AString &s) const
198 {
199   if (Style.IsEmpty())
200     s.Add_OptSpaced("NO-CHECKSUM");
201   else
202   {
203     s.Add_OptSpaced(Style);
204     if (Error)
205       s += "-ERROR";
206   }
207 }
208 
209 
210 struct CFile
211 {
212   bool IsDir;
213   bool Is_SymLink;
214   bool HasData;
215   bool Mode_Defined;
216   bool INode_Defined;
217   bool UserId_Defined;
218   bool GroupId_Defined;
219   // bool Device_Defined;
220   bool Id_Defined;
221 
222   int Parent;
223   UInt32 Mode;
224 
225   UInt64 Size;
226   UInt64 PackSize;
227   UInt64 Offset;
228   UInt64 MTime;
229   UInt64 CTime;
230   UInt64 ATime;
231   UInt64 INode;
232   UInt64 UserId;
233   UInt64 GroupId;
234   // UInt64 Device;
235 
236   AString Name;
237   AString Method;
238   AString User;
239   AString Group;
240   // AString Id;
241   AString Type;
242   AString Link;
243   // AString LinkType;
244   // AString LinkFrom;
245 
246   UInt64 Id;
247   CCheckSum extracted_checksum;
248   CCheckSum archived_checksum;
249 
250   CFile(int parent):
251       IsDir(false),
252       Is_SymLink(false),
253       HasData(false),
254       Mode_Defined(false),
255       INode_Defined(false),
256       UserId_Defined(false),
257       GroupId_Defined(false),
258       // Device_Defined(false),
259       Id_Defined(false),
260       Parent(parent),
261       Mode(0),
262       Size(0), PackSize(0), Offset(0),
263       MTime(0), CTime(0), ATime(0),
264       INode(0)
265       {}
266 
267   bool IsCopyMethod() const
268   {
269     return Method.IsEmpty() || Method == "octet-stream";
270   }
271 
272   void UpdateTotalPackSize(UInt64 &totalSize) const
273   {
274     const UInt64 t = Offset + PackSize;
275     if (t >= Offset)
276     if (totalSize < t)
277         totalSize = t;
278   }
279 };
280 
281 
282 Z7_CLASS_IMP_CHandler_IInArchive_2(
283     IArchiveGetRawProps,
284     IInArchiveGetStream
285 )
286   bool _is_pkg;
287   bool _toc_CrcError;
288   CObjectVector<CFile> _files;
289   CMyComPtr<IInStream> _inStream;
290   UInt64 _dataStartPos;
291   UInt64 _phySize;
292   CAlignedBuffer _xmlBuf;
293   size_t _xmlLen;
294   // UInt64 CreationTime;
295   AString CreationTime_String;
296   UInt32 _checkSumAlgo;
297   Int32 _mainSubfile;
298 
299   HRESULT Open2(IInStream *stream);
300 };
301 
302 
303 static const Byte kArcProps[] =
304 {
305   kpidSubType,
306   // kpidHeadersSize,
307   kpidMethod,
308   kpidCTime
309 };
310 
311 // #define kpidLinkType 250
312 // #define kpidLinkFrom 251
313 
314 static const Byte kProps[] =
315 {
316   kpidPath,
317   kpidSize,
318   kpidPackSize,
319   kpidMTime,
320   kpidCTime,
321   kpidATime,
322   kpidPosixAttrib,
323   kpidType,
324   kpidUser,
325   kpidGroup,
326   kpidUserId,
327   kpidGroupId,
328   kpidINode,
329   // kpidDeviceMajor,
330   // kpidDeviceMinor,
331   kpidSymLink,
332   // kpidLinkType,
333   // kpidLinkFrom,
334   kpidMethod,
335   kpidId,
336   kpidOffset
337 };
338 
339 IMP_IInArchive_Props
340 IMP_IInArchive_ArcProps
341 
342 static bool ParseUInt64(const CXmlItem &item, const char *name, UInt64 &res)
343 {
344   const AString s (item.GetSubStringForTag(name));
345   if (s.IsEmpty())
346     return false;
347   const char *end;
348   res = ConvertStringToUInt64(s, &end);
349   return *end == 0;
350 }
351 
352 
353 #define PARSE_NUM(_num_, _dest_) \
354     { const char *end; _dest_ = ConvertStringToUInt32(p, &end); \
355     if ((unsigned)(end - p) != _num_) return 0; \
356     p += _num_ + 1; }
357 
358 static UInt64 ParseTime(const CXmlItem &item, const char *name /* , bool z_isRequired */ )
359 {
360   const AString s (item.GetSubStringForTag(name));
361   if (s.Len() < 20 /* (z_isRequired ? 20u : 19u) */)
362     return 0;
363   const char *p = s;
364   if (p[ 4] != '-' ||
365       p[ 7] != '-' ||
366       p[10] != 'T' ||
367       p[13] != ':' ||
368       p[16] != ':')
369     return 0;
370   // if (z_isRequired)
371   if (p[19] != 'Z')
372     return 0;
373   UInt32 year, month, day, hour, min, sec;
374   PARSE_NUM(4, year)
375   PARSE_NUM(2, month)
376   PARSE_NUM(2, day)
377   PARSE_NUM(2, hour)
378   PARSE_NUM(2, min)
379   PARSE_NUM(2, sec)
380   UInt64 numSecs;
381   if (!NTime::GetSecondsSince1601(year, month, day, hour, min, sec, numSecs))
382     return 0;
383   return numSecs * 10000000;
384 }
385 
386 
387 static void ParseChecksum(const CXmlItem &item, const char *name, CCheckSum &checksum)
388 {
389   const CXmlItem *checkItem = item.FindSubTag_GetPtr(name);
390   if (!checkItem)
391     return; // false;
392   checksum.Style = checkItem->GetPropVal("style");
393   const AString s (checkItem->GetSubString());
394   if ((s.Len() & 1) == 0 && s.Len() <= (2u << 7)) // 1024-bit max
395   {
396     const size_t size = s.Len() / 2;
397     CByteBuffer temp(size);
398     if ((size_t)(ParseHexString(s, temp) - temp) == size)
399     {
400       checksum.Data = temp;
401       const int index = Find_ChecksumId_for_Name(checksum.Style);
402       if (index >= 0 && checksum.Data.Size() == GetHashSize(index))
403       {
404         checksum.AlgoNumber = index;
405         return;
406       }
407     }
408   }
409   checksum.Error = true;
410 }
411 
412 
413 static bool AddItem(const CXmlItem &item, CObjectVector<CFile> &files, int parent, int level)
414 {
415   if (!item.IsTag)
416     return true;
417   if (level >= 1024)
418     return false;
419   if (item.Name == "file")
420   {
421     CFile file(parent);
422     parent = (int)files.Size();
423     {
424       const AString id = item.GetPropVal("id");
425       const char *end;
426       file.Id = ConvertStringToUInt64(id, &end);
427       if (*end == 0)
428         file.Id_Defined = true;
429     }
430     file.Name = item.GetSubStringForTag("name");
431     z7_xml_DecodeString(file.Name);
432     {
433       const CXmlItem *typeItem = item.FindSubTag_GetPtr("type");
434       if (typeItem)
435       {
436         file.Type = typeItem->GetSubString();
437         // file.LinkFrom = typeItem->GetPropVal("link");
438         if (file.Type == "directory")
439           file.IsDir = true;
440         else
441         {
442           // file.IsDir = false;
443           /*
444           else if (file.Type == "file")
445           {}
446           else if (file.Type == "hardlink")
447           {}
448           else
449           */
450           if (file.Type == "symlink")
451             file.Is_SymLink = true;
452           // file.IsDir = false;
453         }
454       }
455     }
456     {
457       const CXmlItem *linkItem = item.FindSubTag_GetPtr("link");
458       if (linkItem)
459       {
460         // file.LinkType = linkItem->GetPropVal("type");
461         file.Link = linkItem->GetSubString();
462         z7_xml_DecodeString(file.Link);
463       }
464     }
465 
466     const CXmlItem *dataItem = item.FindSubTag_GetPtr("data");
467     if (dataItem && !file.IsDir)
468     {
469       file.HasData = true;
470       if (!ParseUInt64(*dataItem, "size", file.Size))
471         return false;
472       if (!ParseUInt64(*dataItem, "length", file.PackSize))
473         return false;
474       if (!ParseUInt64(*dataItem, "offset", file.Offset))
475         return false;
476       ParseChecksum(*dataItem, "extracted-checksum", file.extracted_checksum);
477       ParseChecksum(*dataItem, "archived-checksum",  file.archived_checksum);
478       const CXmlItem *encodingItem = dataItem->FindSubTag_GetPtr("encoding");
479       if (encodingItem)
480       {
481         AString s (encodingItem->GetPropVal("style"));
482         if (!s.IsEmpty())
483         {
484           const AString appl ("application/");
485           if (s.IsPrefixedBy(appl))
486           {
487             s.DeleteFrontal(appl.Len());
488             const AString xx ("x-");
489             if (s.IsPrefixedBy(xx))
490             {
491               s.DeleteFrontal(xx.Len());
492               if (s == "gzip")
493                 s = METHOD_NAME_ZLIB;
494             }
495           }
496           file.Method = s;
497         }
498       }
499     }
500 
501     file.INode_Defined = ParseUInt64(item, "inode", file.INode);
502     file.UserId_Defined = ParseUInt64(item, "uid", file.UserId);
503     file.GroupId_Defined = ParseUInt64(item, "gid", file.GroupId);
504     // file.Device_Defined = ParseUInt64(item, "deviceno", file.Device);
505     file.MTime = ParseTime(item, "mtime"); // z_IsRequied = true
506     file.CTime = ParseTime(item, "ctime");
507     file.ATime = ParseTime(item, "atime");
508     {
509       const AString s (item.GetSubStringForTag("mode"));
510       if (s[0] == '0')
511       {
512         const char *end;
513         file.Mode = ConvertOctStringToUInt32(s, &end);
514         file.Mode_Defined = (*end == 0);
515       }
516     }
517     file.User = item.GetSubStringForTag("user");
518     file.Group = item.GetSubStringForTag("group");
519 
520     files.Add(file);
521   }
522 
523   FOR_VECTOR (i, item.SubItems)
524     if (!AddItem(item.SubItems[i], files, parent, level + 1))
525       return false;
526   return true;
527 }
528 
529 
530 
531 struct CInStreamWithHash
532 {
533   CMyComPtr2_Create<ISequentialInStream, CInStreamWithSha1> inStreamSha1;
534   CMyComPtr2_Create<ISequentialInStream, CInStreamWithSha256> inStreamSha256;
535   CMyComPtr2_Create<ISequentialInStream, CLimitedSequentialInStream> inStreamLim;
536 
537   void SetStreamAndInit(ISequentialInStream *stream, int algo);
538   bool CheckHash(int algo, const Byte *digest_from_arc) const;
539 };
540 
541 
542 void CInStreamWithHash::SetStreamAndInit(ISequentialInStream *stream, int algo)
543 {
544   if (algo == XAR_CKSUM_SHA1)
545   {
546     inStreamSha1->SetStream(stream);
547     inStreamSha1->Init();
548     stream = inStreamSha1;
549   }
550   else if (algo == XAR_CKSUM_SHA256
551         || algo == XAR_CKSUM_SHA512)
552   {
553     inStreamSha256->SetStream(stream);
554     inStreamSha256->Init(algo == XAR_CKSUM_SHA512);
555     stream = inStreamSha256;
556   }
557   inStreamLim->SetStream(stream);
558 }
559 
560 bool CInStreamWithHash::CheckHash(int algo, const Byte *digest_from_arc) const
561 {
562   if (algo == XAR_CKSUM_SHA1)
563   {
564     Byte digest[SHA1_DIGEST_SIZE];
565     inStreamSha1->Final(digest);
566     if (memcmp(digest, digest_from_arc, sizeof(digest)) != 0)
567       return false;
568   }
569   else if (algo == XAR_CKSUM_SHA256)
570   {
571     Byte digest[SHA256_DIGEST_SIZE];
572     inStreamSha256->Final256(digest);
573     if (memcmp(digest, digest_from_arc, sizeof(digest)) != 0)
574       return false;
575   }
576   else if (algo == XAR_CKSUM_SHA512)
577   {
578     Byte digest[SHA512_DIGEST_SIZE];
579     inStreamSha256->Final512(digest);
580     if (memcmp(digest, digest_from_arc, sizeof(digest)) != 0)
581       return false;
582   }
583   return true;
584 }
585 
586 
587 HRESULT CHandler::Open2(IInStream *stream)
588 {
589   const unsigned kHeaderSize = 28;
590   UInt32 buf32[kHeaderSize / sizeof(UInt32)];
591   RINOK(ReadStream_FALSE(stream, buf32, kHeaderSize))
592   const unsigned headerSize = Get16((const Byte *)(const void *)buf32 + 4);
593   // xar library now writes 1 to version field.
594   // some old xars could have version == 0 ?
595   // specification allows (headerSize != 28),
596   // but we don't expect big value in (headerSize).
597   // so we restrict (headerSize) with 64 bytes to reduce false open.
598   const unsigned kHeaderSize_MAX = 64;
599   if (Get32(buf32) != 0x78617221  // signature: "xar!"
600       || headerSize < kHeaderSize
601       || headerSize > kHeaderSize_MAX
602       || Get16((const Byte *)(const void *)buf32 + 6) > 1 // version
603       )
604     return S_FALSE;
605   _checkSumAlgo = Get32(buf32 + 6);
606   const UInt64 packSize = Get64(buf32 + 2);
607   const UInt64 unpackSize = Get64(buf32 + 4);
608   if (packSize >= kXmlPackSizeMax ||
609       unpackSize >= kXmlSizeMax)
610     return S_FALSE;
611   /* some xar archives can have padding bytes at offset 28,
612      or checksum algorithm name at offset 28 (in xar fork, if cksum_alg==3)
613      But we didn't see such xar archives.
614   */
615   if (headerSize != kHeaderSize)
616   {
617     RINOK(InStream_SeekSet(stream, headerSize))
618   }
619   _dataStartPos = headerSize + packSize;
620   _phySize = _dataStartPos;
621 
622   _xmlBuf.Alloc((size_t)unpackSize + 1);
623   if (!_xmlBuf.IsAllocated())
624     return E_OUTOFMEMORY;
625   _xmlLen = (size_t)unpackSize;
626 
627   CInStreamWithHash hashStream;
628   {
629     CMyComPtr2_Create<ICompressCoder, NCompress::NZlib::CDecoder> zlibCoder;
630     hashStream.SetStreamAndInit(stream, (int)(unsigned)_checkSumAlgo);
631     hashStream.inStreamLim->Init(packSize);
632     CMyComPtr2_Create<ISequentialOutStream, CBufPtrSeqOutStream> outStreamLim;
633     outStreamLim->Init(_xmlBuf, (size_t)unpackSize);
634     RINOK(zlibCoder.Interface()->Code(hashStream.inStreamLim, outStreamLim, NULL, &unpackSize, NULL))
635     if (outStreamLim->GetPos() != (size_t)unpackSize)
636       return S_FALSE;
637   }
638   _xmlBuf[(size_t)unpackSize] = 0;
639   if (strlen((const char *)(const Byte *)_xmlBuf) != (size_t)unpackSize)
640     return S_FALSE;
641   CXml xml;
642   if (!xml.Parse((const char *)(const Byte *)_xmlBuf))
643     return S_FALSE;
644 
645   if (!xml.Root.IsTagged("xar") || xml.Root.SubItems.Size() != 1)
646     return S_FALSE;
647   const CXmlItem &toc = xml.Root.SubItems[0];
648   if (!toc.IsTagged("toc"))
649     return S_FALSE;
650 
651   // CreationTime = ParseTime(toc, "creation-time", false); // z_IsRequied
652   CreationTime_String = toc.GetSubStringForTag("creation-time");
653   {
654     // we suppose that offset of checksum is always 0;
655     // but [TOC].xml contains exact offset value in <checksum> block.
656     const UInt64 offset = 0;
657     const unsigned hashSize = GetHashSize((int)(unsigned)_checkSumAlgo);
658     if (hashSize)
659     {
660       /*
661       const CXmlItem *csItem = toc.FindSubTag_GetPtr("checksum");
662       if (csItem)
663       {
664         const int checkSumAlgo2 = Find_ChecksumId_for_Name(csItem->GetPropVal("style"));
665         UInt64 csSize, csOffset;
666         if (ParseUInt64(*csItem, "size", csSize) &&
667             ParseUInt64(*csItem, "offset", csOffset)  &&
668             csSize == hashSize &&
669             (unsigned)checkSumAlgo2 == _checkSumAlgo)
670           offset = csOffset;
671       }
672       */
673       CByteBuffer digest_from_arc(hashSize);
674       RINOK(InStream_SeekSet(stream, _dataStartPos + offset))
675       RINOK(ReadStream_FALSE(stream, digest_from_arc, hashSize))
676       if (!hashStream.CheckHash((int)(unsigned)_checkSumAlgo, digest_from_arc))
677         _toc_CrcError = true;
678     }
679   }
680 
681   if (!AddItem(toc, _files,
682       -1, // parent
683       0)) // level
684     return S_FALSE;
685 
686   UInt64 totalPackSize = 0;
687   unsigned numMainFiles = 0;
688 
689   FOR_VECTOR (i, _files)
690   {
691     const CFile &file = _files[i];
692     file.UpdateTotalPackSize(totalPackSize);
693     if (file.Parent == -1)
694     {
695       if (file.Name == "Payload" || file.Name == "Content")
696       {
697         _mainSubfile = (Int32)(int)i;
698         numMainFiles++;
699       }
700       else if (file.Name == "PackageInfo")
701         _is_pkg = true;
702     }
703   }
704 
705   if (numMainFiles > 1)
706     _mainSubfile = -1;
707 
708   const UInt64 k_PhySizeLim = (UInt64)1 << 62;
709   _phySize = (totalPackSize > k_PhySizeLim - _dataStartPos) ?
710       k_PhySizeLim :
711       _dataStartPos + totalPackSize;
712 
713   return S_OK;
714 }
715 
716 Z7_COM7F_IMF(CHandler::Open(IInStream *stream,
717     const UInt64 * /* maxCheckStartPosition */,
718     IArchiveOpenCallback * /* openArchiveCallback */))
719 {
720   COM_TRY_BEGIN
721   {
722     Close();
723     RINOK(Open2(stream))
724     _inStream = stream;
725   }
726   return S_OK;
727   COM_TRY_END
728 }
729 
730 Z7_COM7F_IMF(CHandler::Close())
731 {
732   _phySize = 0;
733   _dataStartPos = 0;
734   _inStream.Release();
735   _files.Clear();
736   _xmlLen = 0;
737   _xmlBuf.Free();
738   _mainSubfile = -1;
739   _is_pkg = false;
740   _toc_CrcError = false;
741   _checkSumAlgo = 0;
742   // CreationTime = 0;
743   CreationTime_String.Empty();
744   return S_OK;
745 }
746 
747 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
748 {
749   *numItems = _files.Size()
750 #ifdef XAR_SHOW_RAW
751     + 1
752 #endif
753   ;
754   return S_OK;
755 }
756 
757 static void TimeToProp(UInt64 t, NCOM::CPropVariant &prop)
758 {
759   if (t != 0)
760   {
761     FILETIME ft;
762     ft.dwLowDateTime = (UInt32)(t);
763     ft.dwHighDateTime = (UInt32)(t >> 32);
764     prop = ft;
765   }
766 }
767 
768 static void Utf8StringToProp(const AString &s, NCOM::CPropVariant &prop)
769 {
770   if (!s.IsEmpty())
771   {
772     UString us;
773     ConvertUTF8ToUnicode(s, us);
774     prop = us;
775   }
776 }
777 
778 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
779 {
780   COM_TRY_BEGIN
781   NCOM::CPropVariant prop;
782   switch (propID)
783   {
784     // case kpidHeadersSize: prop = _dataStartPos; break;
785     case kpidPhySize: prop = _phySize; break;
786     case kpidMainSubfile: if (_mainSubfile >= 0) prop = (UInt32)_mainSubfile; break;
787     case kpidSubType: if (_is_pkg) prop = "pkg"; break;
788     case kpidExtension: prop = _is_pkg ? "pkg" : "xar"; break;
789     case kpidCTime:
790     {
791       // it's local time. We can transfer it to UTC time, if we use FILETIME.
792       // TimeToProp(CreationTime, prop); break;
793       if (!CreationTime_String.IsEmpty())
794         prop = CreationTime_String;
795       break;
796     }
797     case kpidMethod:
798     {
799       AString s;
800       if (_checkSumAlgo < Z7_ARRAY_SIZE(k_ChecksumNames))
801         s = k_ChecksumNames[_checkSumAlgo];
802       else
803       {
804         s += "Checksum";
805         s.Add_UInt32(_checkSumAlgo);
806       }
807       prop = s;
808       break;
809     }
810     case kpidWarningFlags:
811     {
812       UInt32 v = 0;
813       if (_toc_CrcError) v |= kpv_ErrorFlags_CrcError;
814       prop = v;
815       break;
816     }
817     case kpidINode: prop = true; break;
818     case kpidIsTree: prop = true; break;
819   }
820   prop.Detach(value);
821   return S_OK;
822   COM_TRY_END
823 }
824 
825 
826 /*
827 inline UInt32 MY_dev_major(UInt64 dev)
828 {
829   return ((UInt32)(dev >> 8) & (UInt32)0xfff) | ((UInt32)(dev >> 32) & ~(UInt32)0xfff);
830 }
831 inline UInt32 MY_dev_minor(UInt64 dev)
832 {
833   return ((UInt32)(dev) & 0xff) | ((UInt32)(dev >> 12) & ~(UInt32)0xff);
834 }
835 */
836 
837 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
838 {
839   COM_TRY_BEGIN
840   NCOM::CPropVariant prop;
841 
842 #ifdef XAR_SHOW_RAW
843   if (index >= _files.Size())
844   {
845     switch (propID)
846     {
847       case kpidName:
848       case kpidPath:
849         prop = "[TOC].xml"; break;
850       case kpidSize:
851       case kpidPackSize: prop = (UInt64)_xmlLen; break;
852     }
853   }
854   else
855 #endif
856   {
857     const CFile &item = _files[index];
858     switch (propID)
859     {
860       case kpidPath:
861       {
862         AString path;
863         unsigned cur = index;
864         for (;;)
865         {
866           const CFile &item2 = _files[cur];
867           if (!path.IsEmpty())
868             path.InsertAtFront(CHAR_PATH_SEPARATOR);
869 // #define XAR_EMPTY_NAME_REPLACEMENT "[]"
870           if (item2.Name.IsEmpty())
871           {
872             AString s('[');
873             s.Add_UInt32(cur);
874             s.Add_Char(']');
875             path.Insert(0, s);
876           }
877           else
878             path.Insert(0, item2.Name);
879           if (item2.Parent < 0)
880             break;
881           cur = (unsigned)item2.Parent;
882         }
883         Utf8StringToProp(path, prop);
884         break;
885       }
886 
887       case kpidName:
888       {
889         if (item.Name.IsEmpty())
890         {
891           AString s('[');
892           s.Add_UInt32(index);
893           s.Add_Char(']');
894           prop = s;
895         }
896         else
897           Utf8StringToProp(item.Name, prop);
898         break;
899       }
900 
901       case kpidIsDir: prop = item.IsDir; break;
902 
903       case kpidSize:     if (item.HasData && !item.IsDir) prop = item.Size; break;
904       case kpidPackSize: if (item.HasData && !item.IsDir) prop = item.PackSize; break;
905 
906       case kpidMethod:
907       {
908         if (item.HasData)
909         {
910           AString s = item.Method;
911           item.extracted_checksum.AddNameToString(s);
912           item.archived_checksum.AddNameToString(s);
913           Utf8StringToProp(s, prop);
914         }
915         break;
916       }
917 
918       case kpidMTime: TimeToProp(item.MTime, prop); break;
919       case kpidCTime: TimeToProp(item.CTime, prop); break;
920       case kpidATime: TimeToProp(item.ATime, prop); break;
921 
922       case kpidPosixAttrib:
923         if (item.Mode_Defined)
924         {
925           UInt32 mode = item.Mode;
926           if ((mode & MY_LIN_S_IFMT) == 0)
927             mode |= (
928                 item.Is_SymLink ? MY_LIN_S_IFLNK :
929                 item.IsDir      ? MY_LIN_S_IFDIR :
930                                   MY_LIN_S_IFREG);
931           prop = mode;
932         }
933         break;
934 
935       case kpidType:  Utf8StringToProp(item.Type, prop); break;
936       case kpidUser:  Utf8StringToProp(item.User, prop); break;
937       case kpidGroup: Utf8StringToProp(item.Group, prop); break;
938       case kpidSymLink: if (item.Is_SymLink) Utf8StringToProp(item.Link, prop); break;
939 
940       case kpidUserId:  if (item.UserId_Defined)  prop = item.UserId;   break;
941       case kpidGroupId: if (item.GroupId_Defined) prop = item.GroupId;  break;
942       case kpidINode:   if (item.INode_Defined)   prop = item.INode;    break;
943       case kpidId:      if (item.Id_Defined)      prop = item.Id;       break;
944       // Utf8StringToProp(item.Id, prop);
945       /*
946       case kpidDeviceMajor: if (item.Device_Defined) prop = (UInt32)MY_dev_major(item.Device);  break;
947       case kpidDeviceMinor: if (item.Device_Defined) prop = (UInt32)MY_dev_minor(item.Device);  break;
948       case kpidLinkType:
949         if (!item.LinkType.IsEmpty())
950           Utf8StringToProp(item.LinkType, prop);
951         break;
952       case kpidLinkFrom:
953         if (!item.LinkFrom.IsEmpty())
954           Utf8StringToProp(item.LinkFrom, prop);
955         break;
956       */
957       case kpidOffset:
958         if (item.HasData)
959           prop = _dataStartPos + item.Offset;
960         break;
961     }
962   }
963   prop.Detach(value);
964   return S_OK;
965   COM_TRY_END
966 }
967 
968 
969 // for debug:
970 // #define Z7_XAR_SHOW_CHECKSUM_PACK
971 
972 #ifdef Z7_XAR_SHOW_CHECKSUM_PACK
973 enum
974 {
975   kpidChecksumPack = kpidUserDefined
976 };
977 #endif
978 
979 static const Byte kRawProps[] =
980 {
981   kpidChecksum
982 #ifdef Z7_XAR_SHOW_CHECKSUM_PACK
983   , kpidCRC // instead of kpidUserDefined / kpidCRC
984 #endif
985 };
986 
987 Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps))
988 {
989   *numProps = Z7_ARRAY_SIZE(kRawProps);
990   return S_OK;
991 }
992 
993 Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID))
994 {
995   *propID = kRawProps[index];
996   *name = NULL;
997 
998 #ifdef Z7_XAR_SHOW_CHECKSUM_PACK
999   if (index != 0)
1000   {
1001     *propID = kpidChecksumPack;
1002     *name = NWindows::NCOM::AllocBstrFromAscii("archived-checksum");
1003   }
1004 #endif
1005   return S_OK;
1006 }
1007 
1008 Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType))
1009 {
1010   *parentType = NParentType::kDir;
1011   *parent = (UInt32)(Int32)-1;
1012 #ifdef XAR_SHOW_RAW
1013   if (index >= _files.Size())
1014     return S_OK;
1015 #endif
1016   {
1017     const CFile &item = _files[index];
1018     *parent = (UInt32)(Int32)item.Parent;
1019   }
1020   return S_OK;
1021 }
1022 
1023 
1024 Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType))
1025 {
1026   *data = NULL;
1027   *dataSize = 0;
1028   *propType = 0;
1029 
1030   // COM_TRY_BEGIN
1031   NCOM::CPropVariant prop;
1032 
1033   if (propID == kpidChecksum)
1034   {
1035 #ifdef XAR_SHOW_RAW
1036     if (index >= _files.Size())
1037     {
1038       // case kpidPath: prop = "[TOC].xml"; break;
1039     }
1040     else
1041 #endif
1042     {
1043       const CFile &item = _files[index];
1044       const size_t size = item.extracted_checksum.Data.Size();
1045       if (size != 0)
1046       {
1047         *dataSize = (UInt32)size;
1048         *propType = NPropDataType::kRaw;
1049         *data = item.extracted_checksum.Data;
1050       }
1051     }
1052   }
1053 
1054 #ifdef Z7_XAR_SHOW_CHECKSUM_PACK
1055   if (propID == kpidChecksumPack)
1056   {
1057 #ifdef XAR_SHOW_RAW
1058     if (index >= _files.Size())
1059     {
1060       // we can show digest check sum here
1061     }
1062     else
1063 #endif
1064     {
1065       const CFile &item = _files[index];
1066       const size_t size = (UInt32)item.archived_checksum.Data.Size();
1067       if (size != 0)
1068       {
1069         *dataSize = (UInt32)size;
1070         *propType = NPropDataType::kRaw;
1071         *data = item.archived_checksum.Data;
1072       }
1073     }
1074   }
1075 #endif
1076   return S_OK;
1077 }
1078 
1079 
1080 
1081 
1082 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
1083     Int32 testMode, IArchiveExtractCallback *extractCallback))
1084 {
1085   COM_TRY_BEGIN
1086   const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
1087   if (allFilesMode)
1088     numItems = _files.Size()
1089 #ifdef XAR_SHOW_RAW
1090     + 1
1091 #endif
1092     ;
1093   if (numItems == 0)
1094     return S_OK;
1095   UInt64 totalSize = 0;
1096   UInt32 i;
1097   for (i = 0; i < numItems; i++)
1098   {
1099     const UInt32 index = allFilesMode ? i : indices[i];
1100 #ifdef XAR_SHOW_RAW
1101     if (index >= _files.Size())
1102       totalSize += _xmlLen;
1103     else
1104 #endif
1105       totalSize += _files[index].Size;
1106   }
1107   RINOK(extractCallback->SetTotal(totalSize))
1108 
1109   CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
1110   lps->Init(extractCallback, false);
1111   CInStreamWithHash inHashStream;
1112   CMyComPtr2_Create<ISequentialOutStream, COutStreamWithSha1> outStreamSha1;
1113   CMyComPtr2_Create<ISequentialOutStream, COutStreamWithSha256> outStreamSha256;
1114   CMyComPtr2_Create<ISequentialOutStream, CLimitedSequentialOutStream> outStreamLim;
1115   CMyComPtr2_Create<ICompressCoder, NCompress::CCopyCoder> copyCoder;
1116   CMyComPtr2_Create<ICompressCoder, NCompress::NZlib::CDecoder> zlibCoder;
1117   CMyComPtr2_Create<ICompressCoder, NCompress::NBZip2::CDecoder> bzip2Coder;
1118   bzip2Coder->FinishMode = true;
1119 
1120   UInt64 cur_PackSize, cur_UnpSize;
1121 
1122   for (i = 0;; i++,
1123       lps->InSize += cur_PackSize,
1124       lps->OutSize += cur_UnpSize)
1125   {
1126     cur_PackSize = 0;
1127     cur_UnpSize = 0;
1128     RINOK(lps->SetCur())
1129     if (i >= numItems)
1130       break;
1131 
1132     CMyComPtr<ISequentialOutStream> realOutStream;
1133     const Int32 askMode = testMode ?
1134         NExtract::NAskMode::kTest :
1135         NExtract::NAskMode::kExtract;
1136     const UInt32 index = allFilesMode ? i : indices[i];
1137     RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
1138 
1139     if (index < _files.Size())
1140     {
1141       const CFile &item = _files[index];
1142       if (item.IsDir)
1143       {
1144         RINOK(extractCallback->PrepareOperation(askMode))
1145         realOutStream.Release();
1146         RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
1147         continue;
1148       }
1149     }
1150 
1151     if (!testMode && !realOutStream)
1152       continue;
1153     RINOK(extractCallback->PrepareOperation(askMode))
1154 
1155     Int32 opRes = NExtract::NOperationResult::kOK;
1156 
1157 #ifdef XAR_SHOW_RAW
1158     if (index >= _files.Size())
1159     {
1160       cur_PackSize = cur_UnpSize = _xmlLen;
1161       if (realOutStream)
1162         RINOK(WriteStream(realOutStream, _xmlBuf, _xmlLen))
1163       realOutStream.Release();
1164     }
1165     else
1166 #endif
1167     {
1168       const CFile &item = _files[index];
1169       if (!item.HasData)
1170         realOutStream.Release();
1171       else
1172       {
1173         cur_PackSize = item.PackSize;
1174         cur_UnpSize = item.Size;
1175 
1176         RINOK(InStream_SeekSet(_inStream, _dataStartPos + item.Offset))
1177 
1178         inHashStream.SetStreamAndInit(_inStream, item.archived_checksum.AlgoNumber);
1179         inHashStream.inStreamLim->Init(item.PackSize);
1180 
1181         const int checksum_method = item.extracted_checksum.AlgoNumber;
1182         if (checksum_method == XAR_CKSUM_SHA1)
1183         {
1184           outStreamLim->SetStream(outStreamSha1);
1185           outStreamSha1->SetStream(realOutStream);
1186           outStreamSha1->Init();
1187         }
1188         else if (checksum_method == XAR_CKSUM_SHA256
1189               || checksum_method == XAR_CKSUM_SHA512)
1190         {
1191           outStreamLim->SetStream(outStreamSha256);
1192           outStreamSha256->SetStream(realOutStream);
1193           outStreamSha256->Init(checksum_method == XAR_CKSUM_SHA512);
1194         }
1195         else
1196           outStreamLim->SetStream(realOutStream);
1197 
1198         realOutStream.Release();
1199 
1200         // outStreamSha1->Init(item.Sha1IsDefined);
1201 
1202         outStreamLim->Init(item.Size);
1203         HRESULT res = S_OK;
1204 
1205         ICompressCoder *coder = NULL;
1206         if (item.IsCopyMethod())
1207         {
1208           if (item.PackSize == item.Size)
1209             coder = copyCoder;
1210           else
1211             opRes = NExtract::NOperationResult::kUnsupportedMethod;
1212         }
1213         else if (item.Method == METHOD_NAME_ZLIB)
1214           coder = zlibCoder;
1215         else if (item.Method == "bzip2")
1216           coder = bzip2Coder;
1217         else
1218           opRes = NExtract::NOperationResult::kUnsupportedMethod;
1219 
1220         if (coder)
1221           res = coder->Code(inHashStream.inStreamLim, outStreamLim, NULL, &item.Size, lps);
1222 
1223         if (res != S_OK)
1224         {
1225           if (!outStreamLim->IsFinishedOK())
1226             opRes = NExtract::NOperationResult::kDataError;
1227           else if (res != S_FALSE)
1228             return res;
1229           if (opRes == NExtract::NOperationResult::kOK)
1230             opRes = NExtract::NOperationResult::kDataError;
1231         }
1232 
1233         if (opRes == NExtract::NOperationResult::kOK)
1234         {
1235           if (outStreamLim->IsFinishedOK())
1236           {
1237             if (checksum_method == XAR_CKSUM_SHA1)
1238             {
1239               Byte digest[SHA1_DIGEST_SIZE];
1240               outStreamSha1->Final(digest);
1241               if (memcmp(digest, item.extracted_checksum.Data, SHA1_DIGEST_SIZE) != 0)
1242                 opRes = NExtract::NOperationResult::kCRCError;
1243             }
1244             else if (checksum_method == XAR_CKSUM_SHA256)
1245             {
1246               Byte digest[SHA256_DIGEST_SIZE];
1247               outStreamSha256->Final256(digest);
1248               if (memcmp(digest, item.extracted_checksum.Data, sizeof(digest)) != 0)
1249                 opRes = NExtract::NOperationResult::kCRCError;
1250             }
1251             else if (checksum_method == XAR_CKSUM_SHA512)
1252             {
1253               Byte digest[SHA512_DIGEST_SIZE];
1254               outStreamSha256->Final512(digest);
1255               if (memcmp(digest, item.extracted_checksum.Data, sizeof(digest)) != 0)
1256                 opRes = NExtract::NOperationResult::kCRCError;
1257             }
1258             if (opRes == NExtract::NOperationResult::kOK)
1259               if (!inHashStream.CheckHash(
1260                     item.archived_checksum.AlgoNumber,
1261                     item.archived_checksum.Data))
1262                 opRes = NExtract::NOperationResult::kCRCError;
1263           }
1264           else
1265             opRes = NExtract::NOperationResult::kDataError;
1266         }
1267         if (checksum_method == XAR_CKSUM_SHA1)
1268           outStreamSha1->ReleaseStream();
1269         else if (checksum_method == XAR_CKSUM_SHA256)
1270           outStreamSha256->ReleaseStream();
1271       }
1272       outStreamLim->ReleaseStream();
1273     }
1274     RINOK(extractCallback->SetOperationResult(opRes))
1275   }
1276   return S_OK;
1277   COM_TRY_END
1278 }
1279 
1280 Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
1281 {
1282   *stream = NULL;
1283   COM_TRY_BEGIN
1284 #ifdef XAR_SHOW_RAW
1285   if (index >= _files.Size())
1286   {
1287     Create_BufInStream_WithNewBuffer(_xmlBuf, _xmlLen, stream);
1288     return S_OK;
1289   }
1290   else
1291 #endif
1292   {
1293     const CFile &item = _files[index];
1294     if (item.HasData && item.IsCopyMethod() && item.PackSize == item.Size)
1295       return CreateLimitedInStream(_inStream, _dataStartPos + item.Offset, item.Size, stream);
1296   }
1297   return S_FALSE;
1298   COM_TRY_END
1299 }
1300 
1301 // 0x1c == 28 is expected header size value for most archives.
1302 // but we want to support another (rare case) headers sizes.
1303 // so we must reduce signature to 4 or 5 bytes.
1304 static const Byte k_Signature[] =
1305 //  { 'x', 'a', 'r', '!', 0, 0x1C, 0 };
1306     { 'x', 'a', 'r', '!', 0 };
1307 
1308 REGISTER_ARC_I(
1309   "Xar", "xar pkg xip", NULL, 0xE1,
1310   k_Signature,
1311   0,
1312   0,
1313   NULL)
1314 
1315 }}
1316