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