• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // HfsHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/CpuArch.h"
6 
7 #include "../../Common/ComTry.h"
8 #include "../../Common/MyString.h"
9 
10 #include "../../Windows/PropVariantUtils.h"
11 
12 #include "../Common/LimitedStreams.h"
13 #include "../Common/RegisterArc.h"
14 #include "../Common/StreamObjects.h"
15 #include "../Common/StreamUtils.h"
16 
17 #include "HfsHandler.h"
18 
19 /* if HFS_SHOW_ALT_STREAMS is defined, the handler will show attribute files
20    and resource forks. In most cases it looks useless. So we disable it. */
21 
22 #define HFS_SHOW_ALT_STREAMS
23 
24 #define Get16(p) GetBe16(p)
25 #define Get32(p) GetBe32(p)
26 #define Get64(p) GetBe64(p)
27 
28 namespace NArchive {
29 namespace NHfs {
30 
31 static const char * const kResFileName = "rsrc"; // "com.apple.ResourceFork";
32 
33 struct CExtent
34 {
35   UInt32 Pos;
36   UInt32 NumBlocks;
37 };
38 
39 struct CIdExtents
40 {
41   UInt32 ID;
42   UInt32 StartBlock;
43   CRecordVector<CExtent> Extents;
44 };
45 
46 struct CFork
47 {
48   UInt64 Size;
49   UInt32 NumBlocks;
50   // UInt32 ClumpSize;
51   CRecordVector<CExtent> Extents;
52 
CForkNArchive::NHfs::CFork53   CFork(): Size(0), NumBlocks(0) {}
54 
55   void Parse(const Byte *p);
56 
IsEmptyNArchive::NHfs::CFork57   bool IsEmpty() const { return Size == 0 && NumBlocks == 0 && Extents.Size() == 0; }
58 
59   UInt32 Calc_NumBlocks_from_Extents() const;
60   bool Check_NumBlocks() const;
61 
Check_Size_with_NumBlocksNArchive::NHfs::CFork62   bool Check_Size_with_NumBlocks(unsigned blockSizeLog) const
63   {
64     return Size <= ((UInt64)NumBlocks << blockSizeLog);
65   }
66 
IsOkNArchive::NHfs::CFork67   bool IsOk(unsigned blockSizeLog) const
68   {
69     // we don't check cases with extra (empty) blocks in last extent
70     return Check_NumBlocks() && Check_Size_with_NumBlocks(blockSizeLog);
71   }
72 
73   bool Upgrade(const CObjectVector<CIdExtents> &items, UInt32 id);
UpgradeAndTestNArchive::NHfs::CFork74   bool UpgradeAndTest(const CObjectVector<CIdExtents> &items, UInt32 id, unsigned blockSizeLog)
75   {
76     if (!Upgrade(items, id))
77       return false;
78     return IsOk(blockSizeLog);
79   }
80 };
81 
82 static const unsigned kNumFixedExtents = 8;
83 static const unsigned kForkRecSize = 16 + kNumFixedExtents * 8;
84 
85 
Parse(const Byte * p)86 void CFork::Parse(const Byte *p)
87 {
88   Extents.Clear();
89   Size = Get64(p);
90   // ClumpSize = Get32(p + 8);
91   NumBlocks = Get32(p + 12);
92   p += 16;
93   for (unsigned i = 0; i < kNumFixedExtents; i++, p += 8)
94   {
95     CExtent e;
96     e.Pos = Get32(p);
97     e.NumBlocks = Get32(p + 4);
98     if (e.NumBlocks != 0)
99       Extents.Add(e);
100   }
101 }
102 
Calc_NumBlocks_from_Extents() const103 UInt32 CFork::Calc_NumBlocks_from_Extents() const
104 {
105   UInt32 num = 0;
106   FOR_VECTOR (i, Extents)
107   {
108     num += Extents[i].NumBlocks;
109   }
110   return num;
111 }
112 
Check_NumBlocks() const113 bool CFork::Check_NumBlocks() const
114 {
115   UInt32 num = 0;
116   FOR_VECTOR (i, Extents)
117   {
118     UInt32 next = num + Extents[i].NumBlocks;
119     if (next < num)
120       return false;
121     num = next;
122   }
123   return num == NumBlocks;
124 }
125 
126 struct CIdIndexPair
127 {
128   UInt32 ID;
129   unsigned Index;
130 
131   int Compare(const CIdIndexPair &a) const;
132 };
133 
134 #define RINOZ(x) { const int _t_ = (x); if (_t_ != 0) return _t_; }
135 
Compare(const CIdIndexPair & a) const136 int CIdIndexPair::Compare(const CIdIndexPair &a) const
137 {
138   RINOZ(MyCompare(ID, a.ID))
139   return MyCompare(Index, a.Index);
140 }
141 
FindItemIndex(const CRecordVector<CIdIndexPair> & items,UInt32 id)142 static int FindItemIndex(const CRecordVector<CIdIndexPair> &items, UInt32 id)
143 {
144   unsigned left = 0, right = items.Size();
145   while (left != right)
146   {
147     const unsigned mid = (left + right) / 2;
148     const UInt32 midVal = items[mid].ID;
149     if (id == midVal)
150       return (int)items[mid].Index;
151     if (id < midVal)
152       right = mid;
153     else
154       left = mid + 1;
155   }
156   return -1;
157 }
158 
Find_in_IdExtents(const CObjectVector<CIdExtents> & items,UInt32 id)159 static int Find_in_IdExtents(const CObjectVector<CIdExtents> &items, UInt32 id)
160 {
161   unsigned left = 0, right = items.Size();
162   while (left != right)
163   {
164     const unsigned mid = (left + right) / 2;
165     const UInt32 midVal = items[mid].ID;
166     if (id == midVal)
167       return (int)mid;
168     if (id < midVal)
169       right = mid;
170     else
171       left = mid + 1;
172   }
173   return -1;
174 }
175 
Upgrade(const CObjectVector<CIdExtents> & items,UInt32 id)176 bool CFork::Upgrade(const CObjectVector<CIdExtents> &items, UInt32 id)
177 {
178   int index = Find_in_IdExtents(items, id);
179   if (index < 0)
180     return true;
181   const CIdExtents &item = items[index];
182   if (Calc_NumBlocks_from_Extents() != item.StartBlock)
183     return false;
184   Extents += item.Extents;
185   return true;
186 }
187 
188 
189 struct CVolHeader
190 {
191   Byte Header[2];
192   UInt16 Version;
193   // UInt32 Attr;
194   // UInt32 LastMountedVersion;
195   // UInt32 JournalInfoBlock;
196 
197   UInt32 CTime;
198   UInt32 MTime;
199   // UInt32 BackupTime;
200   // UInt32 CheckedTime;
201 
202   UInt32 NumFiles;
203   UInt32 NumFolders;
204   unsigned BlockSizeLog;
205   UInt32 NumBlocks;
206   UInt32 NumFreeBlocks;
207 
208   // UInt32 WriteCount;
209   // UInt32 FinderInfo[8];
210   // UInt64 VolID;
211 
GetPhySizeNArchive::NHfs::CVolHeader212   UInt64 GetPhySize() const { return (UInt64)NumBlocks << BlockSizeLog; }
GetFreeSizeNArchive::NHfs::CVolHeader213   UInt64 GetFreeSize() const { return (UInt64)NumFreeBlocks << BlockSizeLog; }
IsHfsXNArchive::NHfs::CVolHeader214   bool IsHfsX() const { return Version > 4; }
215 };
216 
HfsTimeToFileTime(UInt32 hfsTime,FILETIME & ft)217 inline void HfsTimeToFileTime(UInt32 hfsTime, FILETIME &ft)
218 {
219   UInt64 v = ((UInt64)3600 * 24 * (365 * 303 + 24 * 3) + hfsTime) * 10000000;
220   ft.dwLowDateTime = (DWORD)v;
221   ft.dwHighDateTime = (DWORD)(v >> 32);
222 }
223 
224 enum ERecordType
225 {
226   RECORD_TYPE_FOLDER = 1,
227   RECORD_TYPE_FILE,
228   RECORD_TYPE_FOLDER_THREAD,
229   RECORD_TYPE_FILE_THREAD
230 };
231 
232 
233 
234 // static const UInt32 kMethod_1_NO_COMPRESSION = 1; // in xattr
235 static const UInt32 kMethod_ZLIB_ATTR = 3;
236 static const UInt32 kMethod_ZLIB_RSRC = 4;
237 // static const UInt32 kMethod_DEDUP = 5; // de-dup within the generation store
238 // macos 10.10
239 static const UInt32 kMethod_LZVN_ATTR = 7;
240 static const UInt32 kMethod_LZVN_RSRC = 8;
241 static const UInt32 kMethod_COPY_ATTR = 9;
242 static const UInt32 kMethod_COPY_RSRC = 10;
243 // macos 10.11
244 // static const UInt32 kMethod_LZFSE_ATTR = 11;
245 static const UInt32 kMethod_LZFSE_RSRC = 12;
246 
247 // static const UInt32 kMethod_ZBM_RSRC = 14;
248 
249 static const char * const g_Methods[] =
250 {
251     NULL
252   , NULL
253   , NULL
254   , "ZLIB-attr"
255   , "ZLIB-rsrc"
256   , NULL
257   , NULL
258   , "LZVN-attr"
259   , "LZVN-rsrc"
260   , "COPY-attr"
261   , "COPY-rsrc"
262   , "LZFSE-attr"
263   , "LZFSE-rsrc"
264   , NULL
265   , "ZBM-rsrc"
266 };
267 
268 
269 static const Byte k_COPY_Uncompressed_Marker = 0xcc;
270 static const Byte k_LZVN_Uncompressed_Marker = 6;
271 
Parse(const Byte * p,size_t dataSize)272 void CCompressHeader::Parse(const Byte *p, size_t dataSize)
273 {
274   Clear();
275 
276   if (dataSize < k_decmpfs_HeaderSize)
277     return;
278   if (GetUi32(p) != 0x636D7066) // magic == "fpmc"
279     return;
280   Method = GetUi32(p + 4);
281   UnpackSize = GetUi64(p + 8);
282   dataSize -= k_decmpfs_HeaderSize;
283   IsCorrect = true;
284 
285   if (   Method == kMethod_ZLIB_RSRC
286       || Method == kMethod_COPY_RSRC
287       || Method == kMethod_LZVN_RSRC
288       || Method == kMethod_LZFSE_RSRC
289       // || Method == kMethod_ZBM_RSRC // for debug
290       )
291   {
292     IsResource = true;
293     if (dataSize == 0)
294       IsSupported = (
295           Method != kMethod_LZFSE_RSRC &&
296           Method != kMethod_COPY_RSRC);
297     return;
298   }
299 
300   if (   Method == kMethod_ZLIB_ATTR
301       || Method == kMethod_COPY_ATTR
302       || Method == kMethod_LZVN_ATTR
303       // || Method == kMethod_LZFSE_ATTR
304     )
305   {
306     if (dataSize == 0)
307       return;
308     const Byte b = p[k_decmpfs_HeaderSize];
309     if (   (Method == kMethod_ZLIB_ATTR && (b & 0xf) == 0xf)
310         || (Method == kMethod_COPY_ATTR && b == k_COPY_Uncompressed_Marker)
311         || (Method == kMethod_LZVN_ATTR && b == k_LZVN_Uncompressed_Marker))
312     {
313       dataSize--;
314       // if (UnpackSize > dataSize)
315       if (UnpackSize != dataSize)
316         return;
317       DataPos = k_decmpfs_HeaderSize + 1;
318       IsSupported = true;
319     }
320     else
321     {
322       if (Method != kMethod_COPY_ATTR)
323         IsSupported = true;
324       DataPos = k_decmpfs_HeaderSize;
325     }
326   }
327 }
328 
329 
MethodToProp(NWindows::NCOM::CPropVariant & prop) const330 void CCompressHeader::MethodToProp(NWindows::NCOM::CPropVariant &prop) const
331 {
332   if (!IsCorrect)
333     return;
334   const UInt32 method = Method;
335   const char *p = NULL;
336   if (method < Z7_ARRAY_SIZE(g_Methods))
337     p = g_Methods[method];
338   AString s;
339   if (p)
340     s = p;
341   else
342     s.Add_UInt32(method);
343   // if (!IsSupported) s += "-unsuported";
344   prop = s;
345 }
346 
MethodsMaskToProp(UInt32 methodsMask,NWindows::NCOM::CPropVariant & prop)347 void MethodsMaskToProp(UInt32 methodsMask, NWindows::NCOM::CPropVariant &prop)
348 {
349   FLAGS_TO_PROP(g_Methods, methodsMask, prop);
350 }
351 
352 
353 struct CItem
354 {
355   UString Name;
356 
357   UInt32 ParentID;
358 
359   UInt16 Type;
360   UInt16 FileMode;
361   // UInt16 Flags;
362   // UInt32 Valence;
363   UInt32 ID;
364   UInt32 CTime;
365   UInt32 MTime;
366   UInt32 AttrMTime;
367   UInt32 ATime;
368   // UInt32 BackupDate;
369 
370   /*
371   UInt32 OwnerID;
372   UInt32 GroupID;
373   Byte AdminFlags;
374   Byte OwnerFlags;
375   union
376   {
377     UInt32  iNodeNum;
378     UInt32  LinkCount;
379     UInt32  RawDevice;
380   } special;
381 
382   UInt32 FileType;
383   UInt32 FileCreator;
384   UInt16 FinderFlags;
385   UInt16 Point[2];
386   */
387 
388   CFork DataFork;
389   CFork ResourceFork;
390 
391   // for compressed attribute (decmpfs)
392   int decmpfs_AttrIndex;
393   CCompressHeader CompressHeader;
394 
CItemNArchive::NHfs::CItem395   CItem():
396       decmpfs_AttrIndex(-1)
397       {}
IsDirNArchive::NHfs::CItem398   bool IsDir() const { return Type == RECORD_TYPE_FOLDER; }
399   // const CFork *GetFork(bool isResource) const { return (isResource ? &ResourceFork: &DataFork); }
400 };
401 
402 
403 struct CAttr
404 {
405   UInt32 ID;
406   bool Fork_defined;
407 
408   // UInt32 Size;    // for (Fork_defined == false) case
409   // size_t DataPos; // for (Fork_defined == false) case
410   CByteBuffer Data;
411 
412   CFork Fork;
413 
414   UString Name;
415 
GetSizeNArchive::NHfs::CAttr416   UInt64 GetSize() const
417   {
418     if (Fork_defined)
419       return Fork.Size;
420     return Data.Size();
421   }
422 
CAttrNArchive::NHfs::CAttr423   CAttr():
424       Fork_defined(false)
425       // Size(0),
426       // DataPos(0),
427       {}
428 };
429 
430 
431 static const int kAttrIndex_Item     = -1;
432 static const int kAttrIndex_Resource = -2;
433 
434 struct CRef
435 {
436   unsigned ItemIndex;
437   int AttrIndex;
438   int Parent;
439 
CRefNArchive::NHfs::CRef440   CRef(): AttrIndex(kAttrIndex_Item), Parent(-1) {}
IsResourceNArchive::NHfs::CRef441   bool IsResource() const { return AttrIndex == kAttrIndex_Resource; }
IsAltStreamNArchive::NHfs::CRef442   bool IsAltStream() const { return AttrIndex != kAttrIndex_Item; }
IsItemNArchive::NHfs::CRef443   bool IsItem() const { return AttrIndex == kAttrIndex_Item; }
444 };
445 
446 
447 class CDatabase
448 {
449   HRESULT ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream);
450   HRESULT LoadExtentFile(const CFork &fork, IInStream *inStream, CObjectVector<CIdExtents> *overflowExtentsArray);
451   HRESULT LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress);
452   HRESULT LoadCatalog(const CFork &fork, const CObjectVector<CIdExtents> *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress);
453   bool Parse_decmpgfs(unsigned attrIndex, CItem &item, bool &skip);
454 public:
455   CRecordVector<CRef> Refs;
456   CObjectVector<CItem> Items;
457   CObjectVector<CAttr> Attrs;
458 
459   // CByteBuffer AttrBuf;
460 
461   CVolHeader Header;
462   bool HeadersError;
463   bool UnsupportedFeature;
464   bool ThereAreAltStreams;
465   // bool CaseSensetive;
466   UString ResFileName;
467 
468   UInt64 SpecOffset;
469   UInt64 PhySize;
470   UInt64 PhySize2;
471   UInt64 ArcFileSize;
472   UInt32 MethodsMask;
473 
Clear()474   void Clear()
475   {
476     SpecOffset = 0;
477     PhySize = 0;
478     PhySize2 = 0;
479     ArcFileSize = 0;
480     MethodsMask = 0;
481     HeadersError = false;
482     UnsupportedFeature = false;
483     ThereAreAltStreams = false;
484     // CaseSensetive = false;
485 
486     Refs.Clear();
487     Items.Clear();
488     Attrs.Clear();
489     // AttrBuf.Free();
490   }
491 
Get_UnpackSize_of_Ref(const CRef & ref) const492   UInt64 Get_UnpackSize_of_Ref(const CRef &ref) const
493   {
494     if (ref.AttrIndex >= 0)
495       return Attrs[ref.AttrIndex].GetSize();
496     const CItem &item = Items[ref.ItemIndex];
497     if (ref.IsResource())
498       return item.ResourceFork.Size;
499     if (item.IsDir())
500       return 0;
501     else if (item.CompressHeader.IsCorrect)
502       return item.CompressHeader.UnpackSize;
503     return item.DataFork.Size;
504   }
505 
506   void GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const;
507   HRESULT Open2(IInStream *inStream, IArchiveOpenCallback *progress);
508 };
509 
510 enum
511 {
512   kHfsID_Root                  = 1,
513   kHfsID_RootFolder            = 2,
514   kHfsID_ExtentsFile           = 3,
515   kHfsID_CatalogFile           = 4,
516   kHfsID_BadBlockFile          = 5,
517   kHfsID_AllocationFile        = 6,
518   kHfsID_StartupFile           = 7,
519   kHfsID_AttributesFile        = 8,
520   kHfsID_RepairCatalogFile     = 14,
521   kHfsID_BogusExtentFile       = 15,
522   kHfsID_FirstUserCatalogNode  = 16
523 };
524 
GetItemPath(unsigned index,NWindows::NCOM::CPropVariant & path) const525 void CDatabase::GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const
526 {
527   unsigned len = 0;
528   const unsigned kNumLevelsMax = (1 << 10);
529   unsigned cur = index;
530   unsigned i;
531 
532   for (i = 0; i < kNumLevelsMax; i++)
533   {
534     const CRef &ref = Refs[cur];
535     const UString *s;
536 
537     if (ref.IsResource())
538       s = &ResFileName;
539     else if (ref.AttrIndex >= 0)
540       s = &Attrs[ref.AttrIndex].Name;
541     else
542       s = &Items[ref.ItemIndex].Name;
543 
544     len += s->Len();
545     len++;
546     cur = (unsigned)ref.Parent;
547     if (ref.Parent < 0)
548       break;
549   }
550 
551   len--;
552   wchar_t *p = path.AllocBstr(len);
553   p[len] = 0;
554   cur = index;
555 
556   for (;;)
557   {
558     const CRef &ref = Refs[cur];
559     const UString *s;
560     wchar_t delimChar = L':';
561 
562     if (ref.IsResource())
563       s = &ResFileName;
564     else if (ref.AttrIndex >= 0)
565       s = &Attrs[ref.AttrIndex].Name;
566     else
567     {
568       delimChar = WCHAR_PATH_SEPARATOR;
569       s = &Items[ref.ItemIndex].Name;
570     }
571 
572     unsigned curLen = s->Len();
573     len -= curLen;
574 
575     const wchar_t *src = (const wchar_t *)*s;
576     wchar_t *dest = p + len;
577     for (unsigned j = 0; j < curLen; j++)
578     {
579       wchar_t c = src[j];
580       // 18.06
581       if (c == CHAR_PATH_SEPARATOR || c == '/')
582         c = '_';
583       dest[j] = c;
584     }
585 
586     if (len == 0)
587       break;
588     p[--len] = delimChar;
589     cur = (unsigned)ref.Parent;
590   }
591 }
592 
593 // Actually we read all blocks. It can be larger than fork.Size
594 
ReadFile(const CFork & fork,CByteBuffer & buf,IInStream * inStream)595 HRESULT CDatabase::ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream)
596 {
597   if (fork.NumBlocks >= Header.NumBlocks)
598     return S_FALSE;
599   if ((ArcFileSize >> Header.BlockSizeLog) + 1 < fork.NumBlocks)
600     return S_FALSE;
601 
602   const size_t totalSize = (size_t)fork.NumBlocks << Header.BlockSizeLog;
603   if ((totalSize >> Header.BlockSizeLog) != fork.NumBlocks)
604     return S_FALSE;
605   buf.Alloc(totalSize);
606   UInt32 curBlock = 0;
607   FOR_VECTOR (i, fork.Extents)
608   {
609     if (curBlock >= fork.NumBlocks)
610       return S_FALSE;
611     const CExtent &e = fork.Extents[i];
612     if (e.Pos > Header.NumBlocks ||
613         e.NumBlocks > fork.NumBlocks - curBlock ||
614         e.NumBlocks > Header.NumBlocks - e.Pos)
615       return S_FALSE;
616     RINOK(InStream_SeekSet(inStream, SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog)))
617     RINOK(ReadStream_FALSE(inStream,
618         (Byte *)buf + ((size_t)curBlock << Header.BlockSizeLog),
619         (size_t)e.NumBlocks << Header.BlockSizeLog))
620     curBlock += e.NumBlocks;
621   }
622   return S_OK;
623 }
624 
625 static const unsigned kNodeDescriptor_Size = 14;
626 
627 struct CNodeDescriptor
628 {
629   UInt32 fLink;
630   // UInt32 bLink;
631   Byte Kind;
632   // Byte Height;
633   unsigned NumRecords;
634 
635   bool Parse(const Byte *p, unsigned nodeSizeLog);
636 };
637 
638 
Parse(const Byte * p,unsigned nodeSizeLog)639 bool CNodeDescriptor::Parse(const Byte *p, unsigned nodeSizeLog)
640 {
641   fLink = Get32(p);
642   // bLink = Get32(p + 4);
643   Kind = p[8];
644   // Height = p[9];
645   NumRecords = Get16(p + 10);
646 
647   const size_t nodeSize = (size_t)1 << nodeSizeLog;
648   if (kNodeDescriptor_Size + ((UInt32)NumRecords + 1) * 2 > nodeSize)
649     return false;
650   const size_t limit = nodeSize - ((UInt32)NumRecords + 1) * 2;
651 
652   p += nodeSize - 2;
653 
654   for (unsigned i = 0; i < NumRecords; i++)
655   {
656     const UInt32 offs = Get16(p);
657     p -= 2;
658     const UInt32 offsNext = Get16(p);
659     if (offs < kNodeDescriptor_Size
660         || offs >= offsNext
661         || offsNext > limit)
662       return false;
663   }
664   return true;
665 }
666 
667 struct CHeaderRec
668 {
669   // UInt16 TreeDepth;
670   // UInt32 RootNode;
671   // UInt32 LeafRecords;
672   UInt32 FirstLeafNode;
673   // UInt32 LastLeafNode;
674   unsigned NodeSizeLog;
675   // UInt16 MaxKeyLength;
676   UInt32 TotalNodes;
677   // UInt32 FreeNodes;
678   // UInt16 Reserved1;
679   // UInt32 ClumpSize;
680   // Byte BtreeType;
681   // Byte KeyCompareType;
682   // UInt32 Attributes;
683   // UInt32 Reserved3[16];
684 
685   HRESULT Parse2(const CByteBuffer &buf);
686 };
687 
Parse2(const CByteBuffer & buf)688 HRESULT CHeaderRec::Parse2(const CByteBuffer &buf)
689 {
690   if (buf.Size() < kNodeDescriptor_Size + 0x2A + 16 * 4)
691     return S_FALSE;
692   const Byte * p = (const Byte *)buf + kNodeDescriptor_Size;
693   // TreeDepth = Get16(p);
694   // RootNode = Get32(p + 2);
695   // LeafRecords = Get32(p + 6);
696   FirstLeafNode = Get32(p + 0xA);
697   // LastLeafNode = Get32(p + 0xE);
698   const UInt32 nodeSize = Get16(p + 0x12);
699 
700   unsigned i;
701   for (i = 9; ((UInt32)1 << i) != nodeSize; i++)
702     if (i == 16)
703       return S_FALSE;
704   NodeSizeLog = i;
705 
706   // MaxKeyLength = Get16(p + 0x14);
707   TotalNodes = Get32(p + 0x16);
708   // FreeNodes = Get32(p + 0x1A);
709   // Reserved1 = Get16(p + 0x1E);
710   // ClumpSize = Get32(p + 0x20);
711   // BtreeType = p[0x24];
712   // KeyCompareType = p[0x25];
713   // Attributes = Get32(p + 0x26);
714   /*
715   for (int i = 0; i < 16; i++)
716     Reserved3[i] = Get32(p + 0x2A + i * 4);
717   */
718 
719   if ((buf.Size() >> NodeSizeLog) < TotalNodes)
720     return S_FALSE;
721 
722   return S_OK;
723 }
724 
725 
726 static const Byte kNodeType_Leaf   = 0xFF;
727 // static const Byte kNodeType_Index  = 0;
728 // static const Byte kNodeType_Header = 1;
729 // static const Byte kNodeType_Mode   = 2;
730 
731 static const Byte kExtentForkType_Data = 0;
732 static const Byte kExtentForkType_Resource = 0xFF;
733 
734 /* It loads data extents from Extents Overflow File
735    Most dmg installers are not fragmented. So there are no extents in Overflow File. */
736 
LoadExtentFile(const CFork & fork,IInStream * inStream,CObjectVector<CIdExtents> * overflowExtentsArray)737 HRESULT CDatabase::LoadExtentFile(const CFork &fork, IInStream *inStream, CObjectVector<CIdExtents> *overflowExtentsArray)
738 {
739   if (fork.NumBlocks == 0)
740     return S_OK;
741   CByteBuffer buf;
742   RINOK(ReadFile(fork, buf, inStream))
743   const Byte *p = (const Byte *)buf;
744 
745   // CNodeDescriptor nodeDesc;
746   // nodeDesc.Parse(p);
747   CHeaderRec hr;
748   RINOK(hr.Parse2(buf))
749 
750   UInt32 node = hr.FirstLeafNode;
751   if (node == 0)
752     return S_OK;
753   if (hr.TotalNodes == 0)
754     return S_FALSE;
755 
756   CByteArr usedBuf(hr.TotalNodes);
757   memset(usedBuf, 0, hr.TotalNodes);
758 
759   while (node != 0)
760   {
761     if (node >= hr.TotalNodes || usedBuf[node] != 0)
762       return S_FALSE;
763     usedBuf[node] = 1;
764 
765     const size_t nodeOffset = (size_t)node << hr.NodeSizeLog;
766     CNodeDescriptor desc;
767     if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog))
768       return S_FALSE;
769     if (desc.Kind != kNodeType_Leaf)
770       return S_FALSE;
771 
772     UInt32 endBlock = 0;
773 
774     for (unsigned i = 0; i < desc.NumRecords; i++)
775     {
776       const UInt32 nodeSize = ((UInt32)1 << hr.NodeSizeLog);
777       const Byte *r = p + nodeOffset + nodeSize - i * 2;
778       const UInt32 offs = Get16(r - 2);
779       UInt32 recSize = Get16(r - 4) - offs;
780       const unsigned kKeyLen = 10;
781 
782       if (recSize != 2 + kKeyLen + kNumFixedExtents * 8)
783         return S_FALSE;
784 
785       r = p + nodeOffset + offs;
786       if (Get16(r) != kKeyLen)
787         return S_FALSE;
788 
789       const Byte forkType = r[2];
790       unsigned forkTypeIndex;
791       if (forkType == kExtentForkType_Data)
792         forkTypeIndex = 0;
793       else if (forkType == kExtentForkType_Resource)
794         forkTypeIndex = 1;
795       else
796         continue;
797       CObjectVector<CIdExtents> &overflowExtents = overflowExtentsArray[forkTypeIndex];
798 
799       const UInt32 id = Get32(r + 4);
800       const UInt32 startBlock = Get32(r + 8);
801       r += 2 + kKeyLen;
802 
803       bool needNew = true;
804 
805       if (overflowExtents.Size() != 0)
806       {
807         CIdExtents &e = overflowExtents.Back();
808         if (e.ID == id)
809         {
810           if (endBlock != startBlock)
811             return S_FALSE;
812           needNew = false;
813         }
814       }
815 
816       if (needNew)
817       {
818         CIdExtents &e = overflowExtents.AddNew();
819         e.ID = id;
820         e.StartBlock = startBlock;
821         endBlock = startBlock;
822       }
823 
824       CIdExtents &e = overflowExtents.Back();
825 
826       for (unsigned k = 0; k < kNumFixedExtents; k++, r += 8)
827       {
828         CExtent ee;
829         ee.Pos = Get32(r);
830         ee.NumBlocks = Get32(r + 4);
831         if (ee.NumBlocks != 0)
832         {
833           e.Extents.Add(ee);
834           endBlock += ee.NumBlocks;
835         }
836       }
837     }
838 
839     node = desc.fLink;
840   }
841   return S_OK;
842 }
843 
LoadName(const Byte * data,unsigned len,UString & dest)844 static void LoadName(const Byte *data, unsigned len, UString &dest)
845 {
846   wchar_t *p = dest.GetBuf(len);
847   unsigned i;
848   for (i = 0; i < len; i++)
849   {
850     const wchar_t c = Get16(data + i * 2);
851     if (c == 0)
852       break;
853     p[i] = c;
854   }
855   p[i] = 0;
856   dest.ReleaseBuf_SetLen(i);
857 }
858 
IsNameEqualTo(const Byte * data,const char * name)859 static bool IsNameEqualTo(const Byte *data, const char *name)
860 {
861   for (unsigned i = 0;; i++)
862   {
863     const char c = name[i];
864     if (c == 0)
865       return true;
866     if (Get16(data + i * 2) != (Byte)c)
867       return false;
868   }
869 }
870 
871 static const UInt32 kAttrRecordType_Inline = 0x10;
872 static const UInt32 kAttrRecordType_Fork = 0x20;
873 // static const UInt32 kAttrRecordType_Extents = 0x30;
874 
LoadAttrs(const CFork & fork,IInStream * inStream,IArchiveOpenCallback * progress)875 HRESULT CDatabase::LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress)
876 {
877   if (fork.NumBlocks == 0)
878     return S_OK;
879 
880   CByteBuffer AttrBuf;
881   RINOK(ReadFile(fork, AttrBuf, inStream))
882   const Byte *p = (const Byte *)AttrBuf;
883 
884   // CNodeDescriptor nodeDesc;
885   // nodeDesc.Parse(p);
886   CHeaderRec hr;
887   RINOK(hr.Parse2(AttrBuf))
888 
889   // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC);
890 
891   UInt32 node = hr.FirstLeafNode;
892   if (node == 0)
893     return S_OK;
894   if (hr.TotalNodes == 0)
895     return S_FALSE;
896 
897   CByteArr usedBuf(hr.TotalNodes);
898   memset(usedBuf, 0, hr.TotalNodes);
899 
900   CFork resFork;
901 
902   while (node != 0)
903   {
904     if (node >= hr.TotalNodes || usedBuf[node] != 0)
905       return S_FALSE;
906     usedBuf[node] = 1;
907 
908     const size_t nodeOffset = (size_t)node << hr.NodeSizeLog;
909     CNodeDescriptor desc;
910     if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog))
911       return S_FALSE;
912     if (desc.Kind != kNodeType_Leaf)
913       return S_FALSE;
914 
915     for (unsigned i = 0; i < desc.NumRecords; i++)
916     {
917       const UInt32 nodeSize = ((UInt32)1 << hr.NodeSizeLog);
918       const Byte *r = p + nodeOffset + nodeSize - i * 2;
919       const UInt32 offs = Get16(r - 2);
920       UInt32 recSize = Get16(r - 4) - offs;
921       const unsigned kHeadSize = 14;
922       if (recSize < kHeadSize)
923         return S_FALSE;
924 
925       r = p + nodeOffset + offs;
926       const UInt32 keyLen = Get16(r);
927 
928       // UInt16 pad = Get16(r + 2);
929       const UInt32 fileID = Get32(r + 4);
930       const unsigned startBlock = Get32(r + 8);
931       if (startBlock != 0)
932       {
933         // that case is still unsupported
934         UnsupportedFeature = true;
935         continue;
936       }
937       const unsigned nameLen = Get16(r + 12);
938 
939       if (keyLen + 2 > recSize ||
940           keyLen != kHeadSize - 2 + nameLen * 2)
941         return S_FALSE;
942       r += kHeadSize;
943       recSize -= kHeadSize;
944 
945       const Byte *name = r;
946       r += nameLen * 2;
947       recSize -= nameLen * 2;
948 
949       if (recSize < 4)
950         return S_FALSE;
951 
952       const UInt32 recordType = Get32(r);
953 
954       if (progress && (Attrs.Size() & 0xFFF) == 0)
955       {
956         const UInt64 numFiles = 0;
957         RINOK(progress->SetCompleted(&numFiles, NULL))
958       }
959 
960       if (Attrs.Size() >= ((UInt32)1 << 31))
961         return S_FALSE;
962 
963       CAttr &attr = Attrs.AddNew();
964       attr.ID = fileID;
965       LoadName(name, nameLen, attr.Name);
966 
967       if (recordType == kAttrRecordType_Fork)
968       {
969         // 22.00 : some hfs files contain it;
970         /* spec: If the attribute has more than 8 extents, there will be additional
971             records (of type kAttrRecordType_Extents) for this attribute. */
972         if (recSize != 8 + kForkRecSize)
973           return S_FALSE;
974         if (Get32(r + 4) != 0) // reserved
975           return S_FALSE;
976         attr.Fork.Parse(r + 8);
977         attr.Fork_defined = true;
978         continue;
979       }
980       else if (recordType != kAttrRecordType_Inline)
981       {
982         UnsupportedFeature = true;
983         continue;
984       }
985 
986       const unsigned kRecordHeaderSize = 16;
987       if (recSize < kRecordHeaderSize)
988         return S_FALSE;
989       if (Get32(r + 4) != 0 || Get32(r + 8) != 0) // reserved
990         return S_FALSE;
991       const UInt32 dataSize = Get32(r + 12);
992 
993       r += kRecordHeaderSize;
994       recSize -= kRecordHeaderSize;
995 
996       if (recSize < dataSize)
997         return S_FALSE;
998 
999       attr.Data.CopyFrom(r, dataSize);
1000       // attr.DataPos = nodeOffset + offs + 2 + keyLen + kRecordHeaderSize;
1001       // attr.Size = dataSize;
1002     }
1003 
1004     node = desc.fLink;
1005   }
1006   return S_OK;
1007 }
1008 
1009 
Parse_decmpgfs(unsigned attrIndex,CItem & item,bool & skip)1010 bool CDatabase::Parse_decmpgfs(unsigned attrIndex, CItem &item, bool &skip)
1011 {
1012   const CAttr &attr = Attrs[attrIndex];
1013   skip = false;
1014   if (item.CompressHeader.IsCorrect || !item.DataFork.IsEmpty())
1015     return false;
1016 
1017   item.CompressHeader.Parse(attr.Data, attr.Data.Size());
1018 
1019   if (item.CompressHeader.IsCorrect)
1020   {
1021     item.decmpfs_AttrIndex = (int)attrIndex;
1022     skip = true;
1023     if (item.CompressHeader.Method < sizeof(MethodsMask) * 8)
1024       MethodsMask |= ((UInt32)1 << item.CompressHeader.Method);
1025   }
1026 
1027   return true;
1028 }
1029 
1030 
LoadCatalog(const CFork & fork,const CObjectVector<CIdExtents> * overflowExtentsArray,IInStream * inStream,IArchiveOpenCallback * progress)1031 HRESULT CDatabase::LoadCatalog(const CFork &fork, const CObjectVector<CIdExtents> *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress)
1032 {
1033   CByteBuffer buf;
1034   RINOK(ReadFile(fork, buf, inStream))
1035   const Byte *p = (const Byte *)buf;
1036 
1037   // CNodeDescriptor nodeDesc;
1038   // nodeDesc.Parse(p);
1039   CHeaderRec hr;
1040   RINOK(hr.Parse2(buf))
1041 
1042   CRecordVector<CIdIndexPair> IdToIndexMap;
1043 
1044   const unsigned reserveSize = (unsigned)(Header.NumFolders + 1 + Header.NumFiles);
1045 
1046   const unsigned kBasicRecSize = 0x58;
1047   const unsigned kMinRecSize = kBasicRecSize + 10;
1048 
1049   if ((UInt64)reserveSize * kMinRecSize < buf.Size())
1050   {
1051     Items.ClearAndReserve(reserveSize);
1052     Refs.ClearAndReserve(reserveSize);
1053     IdToIndexMap.ClearAndReserve(reserveSize);
1054   }
1055 
1056   // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC);
1057 
1058   CByteArr usedBuf(hr.TotalNodes);
1059   if (hr.TotalNodes != 0)
1060     memset(usedBuf, 0, hr.TotalNodes);
1061 
1062   CFork resFork;
1063 
1064   UInt32 node = hr.FirstLeafNode;
1065   UInt32 numFiles = 0;
1066   UInt32 numFolders = 0;
1067 
1068   while (node != 0)
1069   {
1070     if (node >= hr.TotalNodes || usedBuf[node] != 0)
1071       return S_FALSE;
1072     usedBuf[node] = 1;
1073 
1074     const size_t nodeOffset = (size_t)node << hr.NodeSizeLog;
1075     CNodeDescriptor desc;
1076     if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog))
1077       return S_FALSE;
1078     if (desc.Kind != kNodeType_Leaf)
1079       return S_FALSE;
1080 
1081     for (unsigned i = 0; i < desc.NumRecords; i++)
1082     {
1083       const UInt32 nodeSize = (1 << hr.NodeSizeLog);
1084       const Byte *r = p + nodeOffset + nodeSize - i * 2;
1085       const UInt32 offs = Get16(r - 2);
1086       UInt32 recSize = Get16(r - 4) - offs;
1087       if (recSize < 6)
1088         return S_FALSE;
1089 
1090       r = p + nodeOffset + offs;
1091       UInt32 keyLen = Get16(r);
1092       UInt32 parentID = Get32(r + 2);
1093       if (keyLen < 6 || (keyLen & 1) != 0 || keyLen + 2 > recSize)
1094         return S_FALSE;
1095       r += 6;
1096       recSize -= 6;
1097       keyLen -= 6;
1098 
1099       unsigned nameLen = Get16(r);
1100       if (nameLen * 2 != (unsigned)keyLen)
1101         return S_FALSE;
1102       r += 2;
1103       recSize -= 2;
1104 
1105       r += nameLen * 2;
1106       recSize -= nameLen * 2;
1107 
1108       if (recSize < 2)
1109         return S_FALSE;
1110       UInt16 type = Get16(r);
1111 
1112       if (type != RECORD_TYPE_FOLDER &&
1113           type != RECORD_TYPE_FILE)
1114         continue;
1115 
1116       if (recSize < kBasicRecSize)
1117         return S_FALSE;
1118 
1119       CItem &item = Items.AddNew();
1120       item.ParentID = parentID;
1121       item.Type = type;
1122       // item.Flags = Get16(r + 2);
1123       // item.Valence = Get32(r + 4);
1124       item.ID = Get32(r + 8);
1125       {
1126         const Byte *name = r - (nameLen * 2);
1127         LoadName(name, nameLen, item.Name);
1128         if (item.Name.Len() <= 1)
1129         {
1130           if (item.Name.IsEmpty() && nameLen == 21)
1131           {
1132             if (GetUi32(name) == 0 &&
1133                 GetUi32(name + 4) == 0 &&
1134                 IsNameEqualTo(name + 8, "HFS+ Private Data"))
1135             {
1136               // it's folder for "Hard Links" files
1137               item.Name = "[HFS+ Private Data]";
1138             }
1139           }
1140 
1141           // Some dmg files have ' ' folder item.
1142           if (item.Name.IsEmpty() || item.Name[0] == L' ')
1143             item.Name = "[]";
1144         }
1145       }
1146 
1147       item.CTime = Get32(r + 0xC);
1148       item.MTime = Get32(r + 0x10);
1149       item.AttrMTime = Get32(r + 0x14);
1150       item.ATime = Get32(r + 0x18);
1151       // item.BackupDate = Get32(r + 0x1C);
1152 
1153       /*
1154       item.OwnerID = Get32(r + 0x20);
1155       item.GroupID = Get32(r + 0x24);
1156       item.AdminFlags = r[0x28];
1157       item.OwnerFlags = r[0x29];
1158       */
1159       item.FileMode = Get16(r + 0x2A);
1160       /*
1161       item.special.iNodeNum = Get16(r + 0x2C); // or .linkCount
1162       item.FileType = Get32(r + 0x30);
1163       item.FileCreator = Get32(r + 0x34);
1164       item.FinderFlags = Get16(r + 0x38);
1165       item.Point[0] = Get16(r + 0x3A); // v
1166       item.Point[1] = Get16(r + 0x3C); // h
1167       */
1168 
1169       // const refIndex = Refs.Size();
1170       CIdIndexPair pair;
1171       pair.ID = item.ID;
1172       pair.Index = Items.Size() - 1;
1173       IdToIndexMap.Add(pair);
1174 
1175       recSize -= kBasicRecSize;
1176       r += kBasicRecSize;
1177       if (item.IsDir())
1178       {
1179         numFolders++;
1180         if (recSize != 0)
1181           return S_FALSE;
1182       }
1183       else
1184       {
1185         numFiles++;
1186         if (recSize != kForkRecSize * 2)
1187           return S_FALSE;
1188 
1189         item.DataFork.Parse(r);
1190 
1191         if (!item.DataFork.UpgradeAndTest(overflowExtentsArray[0], item.ID, Header.BlockSizeLog))
1192           HeadersError = true;
1193 
1194         item.ResourceFork.Parse(r + kForkRecSize);
1195         if (!item.ResourceFork.IsEmpty())
1196         {
1197           if (!item.ResourceFork.UpgradeAndTest(overflowExtentsArray[1], item.ID, Header.BlockSizeLog))
1198             HeadersError = true;
1199           // ThereAreAltStreams = true;
1200         }
1201       }
1202       if (progress && (Items.Size() & 0xFFF) == 0)
1203       {
1204         const UInt64 numItems = Items.Size();
1205         RINOK(progress->SetCompleted(&numItems, NULL))
1206       }
1207     }
1208     node = desc.fLink;
1209   }
1210 
1211   if (Header.NumFiles != numFiles ||
1212       Header.NumFolders + 1 != numFolders)
1213     HeadersError = true;
1214 
1215   IdToIndexMap.Sort2();
1216   {
1217     for (unsigned i = 1; i < IdToIndexMap.Size(); i++)
1218       if (IdToIndexMap[i - 1].ID == IdToIndexMap[i].ID)
1219         return S_FALSE;
1220   }
1221 
1222 
1223   CBoolArr skipAttr(Attrs.Size());
1224   {
1225     for (unsigned i = 0; i < Attrs.Size(); i++)
1226       skipAttr[i] = false;
1227   }
1228 
1229   {
1230     FOR_VECTOR (i, Attrs)
1231     {
1232       const CAttr &attr = Attrs[i];
1233 
1234       const int itemIndex = FindItemIndex(IdToIndexMap, attr.ID);
1235       if (itemIndex < 0)
1236       {
1237         HeadersError = true;
1238         continue;
1239       }
1240 
1241       if (attr.Name.IsEqualTo("com.apple.decmpfs"))
1242       {
1243         if (!Parse_decmpgfs(i, Items[itemIndex], skipAttr[i]))
1244           HeadersError = true;
1245       }
1246     }
1247   }
1248 
1249   IdToIndexMap.ClearAndReserve(Items.Size());
1250 
1251   {
1252     FOR_VECTOR (i, Items)
1253     {
1254       const CItem &item = Items[i];
1255 
1256       CIdIndexPair pair;
1257       pair.ID = item.ID;
1258       pair.Index = Refs.Size();
1259       IdToIndexMap.Add(pair);
1260 
1261       CRef ref;
1262       ref.ItemIndex = i;
1263       Refs.Add(ref);
1264 
1265       #ifdef HFS_SHOW_ALT_STREAMS
1266 
1267       if (item.ResourceFork.IsEmpty())
1268         continue;
1269       if (item.CompressHeader.IsSupported && item.CompressHeader.IsMethod_Resource())
1270         continue;
1271 
1272       ThereAreAltStreams = true;
1273       ref.AttrIndex = kAttrIndex_Resource;
1274       ref.Parent = (int)(Refs.Size() - 1);
1275       Refs.Add(ref);
1276 
1277       #endif
1278     }
1279   }
1280 
1281   IdToIndexMap.Sort2();
1282 
1283   {
1284     FOR_VECTOR (i, Refs)
1285     {
1286       CRef &ref = Refs[i];
1287       if (ref.IsResource())
1288         continue;
1289       const CItem &item = Items[ref.ItemIndex];
1290       ref.Parent = FindItemIndex(IdToIndexMap, item.ParentID);
1291       if (ref.Parent >= 0)
1292       {
1293         if (!Items[Refs[ref.Parent].ItemIndex].IsDir())
1294         {
1295           ref.Parent = -1;
1296           HeadersError = true;
1297         }
1298       }
1299     }
1300   }
1301 
1302   #ifdef HFS_SHOW_ALT_STREAMS
1303   {
1304     FOR_VECTOR (i, Attrs)
1305     {
1306       if (skipAttr[i])
1307         continue;
1308       const CAttr &attr = Attrs[i];
1309 
1310       const int refIndex = FindItemIndex(IdToIndexMap, attr.ID);
1311       if (refIndex < 0)
1312       {
1313         HeadersError = true;
1314         continue;
1315       }
1316 
1317       ThereAreAltStreams = true;
1318 
1319       CRef ref;
1320       ref.AttrIndex = (int)i;
1321       ref.Parent = refIndex;
1322       ref.ItemIndex = Refs[refIndex].ItemIndex;
1323       Refs.Add(ref);
1324     }
1325   }
1326   #endif
1327 
1328   return S_OK;
1329 }
1330 
1331 static const unsigned kHeaderPadSize = (1 << 10);
1332 static const unsigned kMainHeaderSize = 512;
1333 static const unsigned kHfsHeaderSize = kHeaderPadSize + kMainHeaderSize;
1334 
IsArc_HFS(const Byte * p,size_t size)1335 API_FUNC_static_IsArc IsArc_HFS(const Byte *p, size_t size)
1336 {
1337   if (size < kHfsHeaderSize)
1338     return k_IsArc_Res_NEED_MORE;
1339   p += kHeaderPadSize;
1340   if (p[0] == 'B' && p[1] == 'D')
1341   {
1342     if (p[0x7C] != 'H' || p[0x7C + 1] != '+')
1343       return k_IsArc_Res_NO;
1344   }
1345   else
1346   {
1347     if (p[0] != 'H' || (p[1] != '+' && p[1] != 'X'))
1348       return k_IsArc_Res_NO;
1349     UInt32 version = Get16(p + 2);
1350     if (version < 4 || version > 5)
1351       return k_IsArc_Res_NO;
1352   }
1353   return k_IsArc_Res_YES;
1354 }
1355 }
1356 
Open2(IInStream * inStream,IArchiveOpenCallback * progress)1357 HRESULT CDatabase::Open2(IInStream *inStream, IArchiveOpenCallback *progress)
1358 {
1359   Clear();
1360   Byte buf[kHfsHeaderSize];
1361   RINOK(ReadStream_FALSE(inStream, buf, kHfsHeaderSize))
1362   {
1363     for (unsigned i = 0; i < kHeaderPadSize; i++)
1364       if (buf[i] != 0)
1365         return S_FALSE;
1366   }
1367   const Byte *p = buf + kHeaderPadSize;
1368   CVolHeader &h = Header;
1369 
1370   h.Header[0] = p[0];
1371   h.Header[1] = p[1];
1372 
1373   if (p[0] == 'B' && p[1] == 'D')
1374   {
1375     /*
1376     It's header for old HFS format.
1377     We don't support old HFS format, but we support
1378     special HFS volume that contains embedded HFS+ volume
1379     */
1380 
1381     if (p[0x7C] != 'H' || p[0x7C + 1] != '+')
1382       return S_FALSE;
1383 
1384     /*
1385     h.CTime = Get32(p + 0x2);
1386     h.MTime = Get32(p + 0x6);
1387 
1388     h.NumFiles = Get32(p + 0x54);
1389     h.NumFolders = Get32(p + 0x58);
1390 
1391     if (h.NumFolders > ((UInt32)1 << 29) ||
1392         h.NumFiles > ((UInt32)1 << 30))
1393       return S_FALSE;
1394     if (progress)
1395     {
1396       UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1;
1397       RINOK(progress->SetTotal(&numFiles, NULL))
1398     }
1399     h.NumFreeBlocks = Get16(p + 0x22);
1400     */
1401 
1402     UInt32 blockSize = Get32(p + 0x14);
1403 
1404     {
1405       unsigned i;
1406       for (i = 9; ((UInt32)1 << i) != blockSize; i++)
1407         if (i == 31)
1408           return S_FALSE;
1409       h.BlockSizeLog = i;
1410     }
1411 
1412     h.NumBlocks = Get16(p + 0x12);
1413     /*
1414     we suppose that it has the follwing layout
1415     {
1416       start block with header
1417       [h.NumBlocks]
1418       end block with header
1419     }
1420     */
1421     PhySize2 = ((UInt64)h.NumBlocks + 2) << h.BlockSizeLog;
1422 
1423     UInt32 startBlock = Get16(p + 0x7C + 2);
1424     UInt32 blockCount = Get16(p + 0x7C + 4);
1425     SpecOffset = (UInt64)(1 + startBlock) << h.BlockSizeLog;
1426     UInt64 phy = SpecOffset + ((UInt64)blockCount << h.BlockSizeLog);
1427     if (PhySize2 < phy)
1428       PhySize2 = phy;
1429     RINOK(InStream_SeekSet(inStream, SpecOffset))
1430     RINOK(ReadStream_FALSE(inStream, buf, kHfsHeaderSize))
1431   }
1432 
1433   if (p[0] != 'H' || (p[1] != '+' && p[1] != 'X'))
1434     return S_FALSE;
1435   h.Version = Get16(p + 2);
1436   if (h.Version < 4 || h.Version > 5)
1437     return S_FALSE;
1438 
1439   // h.Attr = Get32(p + 4);
1440   // h.LastMountedVersion = Get32(p + 8);
1441   // h.JournalInfoBlock = Get32(p + 0xC);
1442 
1443   h.CTime = Get32(p + 0x10);
1444   h.MTime = Get32(p + 0x14);
1445   // h.BackupTime = Get32(p + 0x18);
1446   // h.CheckedTime = Get32(p + 0x1C);
1447 
1448   h.NumFiles = Get32(p + 0x20);
1449   h.NumFolders = Get32(p + 0x24);
1450 
1451   if (h.NumFolders > ((UInt32)1 << 29) ||
1452       h.NumFiles > ((UInt32)1 << 30))
1453     return S_FALSE;
1454 
1455   RINOK(InStream_GetSize_SeekToEnd(inStream, ArcFileSize))
1456 
1457   if (progress)
1458   {
1459     const UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1;
1460     RINOK(progress->SetTotal(&numFiles, NULL))
1461   }
1462 
1463   UInt32 blockSize = Get32(p + 0x28);
1464 
1465   {
1466     unsigned i;
1467     for (i = 9; ((UInt32)1 << i) != blockSize; i++)
1468       if (i == 31)
1469         return S_FALSE;
1470     h.BlockSizeLog = i;
1471   }
1472 
1473   h.NumBlocks = Get32(p + 0x2C);
1474   h.NumFreeBlocks = Get32(p + 0x30);
1475 
1476   /*
1477   h.NextCalatlogNodeID = Get32(p + 0x40);
1478   h.WriteCount = Get32(p + 0x44);
1479   for (i = 0; i < 6; i++)
1480     h.FinderInfo[i] = Get32(p + 0x50 + i * 4);
1481   h.VolID = Get64(p + 0x68);
1482   */
1483 
1484   ResFileName = kResFileName;
1485 
1486   CFork extentsFork, catalogFork, attrFork;
1487   // allocationFork.Parse(p + 0x70 + 0x50 * 0);
1488   extentsFork.Parse(p + 0x70 + 0x50 * 1);
1489   catalogFork.Parse(p + 0x70 + 0x50 * 2);
1490   attrFork.Parse   (p + 0x70 + 0x50 * 3);
1491   // startupFork.Parse(p + 0x70 + 0x50 * 4);
1492 
1493   CObjectVector<CIdExtents> overflowExtents[2];
1494   if (!extentsFork.IsOk(Header.BlockSizeLog))
1495     HeadersError = true;
1496   else
1497   {
1498     HRESULT res = LoadExtentFile(extentsFork, inStream, overflowExtents);
1499     if (res == S_FALSE)
1500       HeadersError = true;
1501     else if (res != S_OK)
1502       return res;
1503   }
1504 
1505   if (!catalogFork.UpgradeAndTest(overflowExtents[0], kHfsID_CatalogFile, Header.BlockSizeLog))
1506     return S_FALSE;
1507 
1508   if (!attrFork.UpgradeAndTest(overflowExtents[0], kHfsID_AttributesFile, Header.BlockSizeLog))
1509     HeadersError = true;
1510   else
1511   {
1512     if (attrFork.Size != 0)
1513       RINOK(LoadAttrs(attrFork, inStream, progress))
1514   }
1515 
1516   RINOK(LoadCatalog(catalogFork, overflowExtents, inStream, progress))
1517 
1518   PhySize = Header.GetPhySize();
1519   return S_OK;
1520 }
1521 
1522 
1523 
1524 Z7_class_CHandler_final:
1525   public IInArchive,
1526   public IArchiveGetRawProps,
1527   public IInArchiveGetStream,
1528   public CMyUnknownImp,
1529   public CDatabase
1530 {
1531   Z7_IFACES_IMP_UNK_3(
1532       IInArchive,
1533       IArchiveGetRawProps,
1534       IInArchiveGetStream)
1535 
1536   CMyComPtr<IInStream> _stream;
1537   HRESULT GetForkStream(const CFork &fork, ISequentialInStream **stream);
1538 };
1539 
1540 static const Byte kProps[] =
1541 {
1542   kpidPath,
1543   kpidIsDir,
1544   kpidSize,
1545   kpidPackSize,
1546   kpidCTime,
1547   kpidMTime,
1548   kpidATime,
1549   kpidChangeTime,
1550   kpidPosixAttrib,
1551   /*
1552   kpidUserId,
1553   kpidGroupId,
1554   */
1555 #ifdef HFS_SHOW_ALT_STREAMS
1556   kpidIsAltStream,
1557 #endif
1558   kpidMethod
1559 };
1560 
1561 static const Byte kArcProps[] =
1562 {
1563   kpidMethod,
1564   kpidCharacts,
1565   kpidClusterSize,
1566   kpidFreeSpace,
1567   kpidCTime,
1568   kpidMTime
1569 };
1570 
1571 IMP_IInArchive_Props
1572 IMP_IInArchive_ArcProps
1573 
1574 static void HfsTimeToProp(UInt32 hfsTime, NWindows::NCOM::CPropVariant &prop)
1575 {
1576   if (hfsTime == 0)
1577     return;
1578   FILETIME ft;
1579   HfsTimeToFileTime(hfsTime, ft);
1580   prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Base);
1581 }
1582 
1583 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
1584 {
1585   COM_TRY_BEGIN
1586   NWindows::NCOM::CPropVariant prop;
1587   switch (propID)
1588   {
1589     case kpidExtension: prop = Header.IsHfsX() ? "hfsx" : "hfs"; break;
1590     case kpidMethod: prop = Header.IsHfsX() ? "HFSX" : "HFS+"; break;
1591     case kpidCharacts: MethodsMaskToProp(MethodsMask, prop); break;
1592     case kpidPhySize:
1593     {
1594       UInt64 v = SpecOffset + PhySize;
1595       if (v < PhySize2)
1596         v = PhySize2;
1597       prop = v;
1598       break;
1599     }
1600     case kpidClusterSize: prop = (UInt32)1 << Header.BlockSizeLog; break;
1601     case kpidFreeSpace: prop = (UInt64)Header.GetFreeSize(); break;
1602     case kpidMTime: HfsTimeToProp(Header.MTime, prop); break;
1603     case kpidCTime:
1604     {
1605       if (Header.CTime != 0)
1606       {
1607         FILETIME localFt, ft;
1608         HfsTimeToFileTime(Header.CTime, localFt);
1609         if (LocalFileTimeToFileTime(&localFt, &ft))
1610           prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Base);
1611       }
1612       break;
1613     }
1614     case kpidIsTree: prop = true; break;
1615     case kpidErrorFlags:
1616     {
1617       UInt32 flags = 0;
1618       if (HeadersError) flags |= kpv_ErrorFlags_HeadersError;
1619       if (UnsupportedFeature) flags |= kpv_ErrorFlags_UnsupportedFeature;
1620       if (flags != 0)
1621         prop = flags;
1622       break;
1623     }
1624     case kpidIsAltStream: prop = ThereAreAltStreams; break;
1625   }
1626   prop.Detach(value);
1627   return S_OK;
1628   COM_TRY_END
1629 }
1630 
1631 Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps))
1632 {
1633   *numProps = 0;
1634   return S_OK;
1635 }
1636 
1637 Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID))
1638 {
1639   *name = NULL;
1640   *propID = 0;
1641   return S_OK;
1642 }
1643 
1644 Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType))
1645 {
1646   const CRef &ref = Refs[index];
1647   *parentType = ref.IsAltStream() ?
1648       NParentType::kAltStream :
1649       NParentType::kDir;
1650   *parent = (UInt32)(Int32)ref.Parent;
1651   return S_OK;
1652 }
1653 
1654 Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType))
1655 {
1656   *data = NULL;
1657   *dataSize = 0;
1658   *propType = 0;
1659   #ifdef MY_CPU_LE
1660   if (propID == kpidName)
1661   {
1662     const CRef &ref = Refs[index];
1663     const UString *s;
1664     if (ref.IsResource())
1665       s = &ResFileName;
1666     else if (ref.AttrIndex >= 0)
1667       s = &Attrs[ref.AttrIndex].Name;
1668     else
1669       s = &Items[ref.ItemIndex].Name;
1670     *data = (const wchar_t *)(*s);
1671     *dataSize = (s->Len() + 1) * (UInt32)sizeof(wchar_t);
1672     *propType = PROP_DATA_TYPE_wchar_t_PTR_Z_LE;
1673     return S_OK;
1674   }
1675   #else
1676   UNUSED_VAR(index)
1677   UNUSED_VAR(propID)
1678   #endif
1679   return S_OK;
1680 }
1681 
1682 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
1683 {
1684   COM_TRY_BEGIN
1685   NWindows::NCOM::CPropVariant prop;
1686   const CRef &ref = Refs[index];
1687   const CItem &item = Items[ref.ItemIndex];
1688   switch (propID)
1689   {
1690     case kpidPath: GetItemPath(index, prop); break;
1691     case kpidName:
1692     {
1693       const UString *s;
1694       if (ref.IsResource())
1695         s = &ResFileName;
1696       else if (ref.AttrIndex >= 0)
1697         s = &Attrs[ref.AttrIndex].Name;
1698       else
1699         s = &item.Name;
1700       prop = *s;
1701       break;
1702     }
1703     case kpidPackSize:
1704       {
1705         UInt64 size;
1706         if (ref.AttrIndex >= 0)
1707           size = Attrs[ref.AttrIndex].GetSize();
1708         else if (ref.IsResource())
1709           size = (UInt64)item.ResourceFork.NumBlocks << Header.BlockSizeLog;
1710         else if (item.IsDir())
1711           break;
1712         else if (item.CompressHeader.IsCorrect)
1713         {
1714           if (item.CompressHeader.IsMethod_Resource())
1715             size = (UInt64)item.ResourceFork.NumBlocks << Header.BlockSizeLog;
1716           else if (item.decmpfs_AttrIndex >= 0)
1717           {
1718             // size = item.PackSize;
1719             const CAttr &attr = Attrs[item.decmpfs_AttrIndex];
1720             size = attr.Data.Size() - item.CompressHeader.DataPos;
1721           }
1722           else
1723             size = 0;
1724         }
1725         else
1726           size = (UInt64)item.DataFork.NumBlocks << Header.BlockSizeLog;
1727         prop = size;
1728         break;
1729       }
1730     case kpidSize:
1731       {
1732         UInt64 size;
1733         if (ref.AttrIndex >= 0)
1734           size = Attrs[ref.AttrIndex].GetSize();
1735         else if (ref.IsResource())
1736           size = item.ResourceFork.Size;
1737         else if (item.IsDir())
1738           break;
1739         else if (item.CompressHeader.IsCorrect)
1740           size = item.CompressHeader.UnpackSize;
1741         else
1742           size = item.DataFork.Size;
1743         prop = size;
1744         break;
1745       }
1746     case kpidIsDir: prop = (ref.IsItem() && item.IsDir()); break;
1747     case kpidIsAltStream: prop = ref.IsAltStream(); break;
1748     case kpidCTime: HfsTimeToProp(item.CTime, prop); break;
1749     case kpidMTime: HfsTimeToProp(item.MTime, prop); break;
1750     case kpidATime: HfsTimeToProp(item.ATime, prop); break;
1751     case kpidChangeTime: HfsTimeToProp(item.AttrMTime, prop); break;
1752     case kpidPosixAttrib: if (ref.IsItem()) prop = (UInt32)item.FileMode; break;
1753     /*
1754     case kpidUserId: prop = (UInt32)item.OwnerID; break;
1755     case kpidGroupId: prop = (UInt32)item.GroupID; break;
1756     */
1757 
1758     case kpidMethod:
1759       if (ref.IsItem())
1760         item.CompressHeader.MethodToProp(prop);
1761       break;
1762   }
1763   prop.Detach(value);
1764   return S_OK;
1765   COM_TRY_END
1766 }
1767 
1768 Z7_COM7F_IMF(CHandler::Open(IInStream *inStream,
1769     const UInt64 * /* maxCheckStartPosition */,
1770     IArchiveOpenCallback *callback))
1771 {
1772   COM_TRY_BEGIN
1773   Close();
1774   RINOK(Open2(inStream, callback))
1775   _stream = inStream;
1776   return S_OK;
1777   COM_TRY_END
1778 }
1779 
1780 Z7_COM7F_IMF(CHandler::Close())
1781 {
1782   _stream.Release();
1783   Clear();
1784   return S_OK;
1785 }
1786 
1787 static const UInt32 kCompressionBlockSize = 1 << 16;
1788 
1789 CDecoder::CDecoder(bool IsAdlerOptional)
1790 {
1791   /* Some new hfs files contain zlib resource fork without Adler checksum.
1792      We do not know how we must detect case where there is Adler
1793      checksum or there is no Adler checksum.
1794   */
1795   _zlibDecoder->IsAdlerOptional = IsAdlerOptional;
1796   _lzfseDecoder->LzvnMode = true;
1797 }
1798 
1799 HRESULT CDecoder::ExtractResourceFork_ZLIB(
1800     ISequentialInStream *inStream, ISequentialOutStream *outStream,
1801     UInt64 forkSize, UInt64 unpackSize,
1802     UInt64 progressStart, IArchiveExtractCallback *extractCallback)
1803 {
1804   const unsigned kHeaderSize = 0x100 + 8;
1805 
1806   const size_t kBufSize = kCompressionBlockSize;
1807   _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
1808 
1809   RINOK(ReadStream_FALSE(inStream, _buf, kHeaderSize))
1810   Byte *buf = _buf;
1811   const UInt32 dataPos = Get32(buf);
1812   const UInt32 mapPos = Get32(buf + 4);
1813   const UInt32 dataSize = Get32(buf + 8);
1814   const UInt32 mapSize = Get32(buf + 12);
1815 
1816   const UInt32 kResMapSize = 50;
1817 
1818   if (mapSize != kResMapSize
1819       || dataPos > mapPos
1820       || dataSize != mapPos - dataPos
1821       || mapSize > forkSize
1822       || mapPos != forkSize - mapSize)
1823     return S_FALSE;
1824 
1825   const UInt32 dataSize2 = Get32(buf + 0x100);
1826   if (4 + dataSize2 != dataSize
1827       || dataSize2 < 8
1828       || dataSize2 > dataSize)
1829     return S_FALSE;
1830 
1831   const UInt32 numBlocks = GetUi32(buf + 0x100 + 4);
1832   if (((dataSize2 - 4) >> 3) < numBlocks)
1833     return S_FALSE;
1834   {
1835     const UInt64 up = unpackSize + kCompressionBlockSize - 1;
1836     if (up < unpackSize || up / kCompressionBlockSize != numBlocks)
1837       return S_FALSE;
1838   }
1839 
1840   const UInt32 tableSize = (numBlocks << 3);
1841 
1842   _tableBuf.AllocAtLeast(tableSize);
1843 
1844   RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize))
1845   const Byte *tableBuf = _tableBuf;
1846 
1847   UInt32 prev = 4 + tableSize;
1848 
1849   UInt32 i;
1850   for (i = 0; i < numBlocks; i++)
1851   {
1852     const UInt32 offs = GetUi32(tableBuf + i * 8);
1853     const UInt32 size = GetUi32(tableBuf + i * 8 + 4);
1854     if (size == 0
1855         || prev != offs
1856         || offs > dataSize2
1857         || size > dataSize2 - offs)
1858       return S_FALSE;
1859     prev = offs + size;
1860   }
1861 
1862   if (prev != dataSize2)
1863     return S_FALSE;
1864 
1865   CMyComPtr2_Create<ISequentialInStream, CBufInStream> bufInStream;
1866 
1867   // bool padError = false;
1868   UInt64 outPos = 0;
1869 
1870   for (i = 0; i < numBlocks; i++)
1871   {
1872     const UInt64 rem = unpackSize - outPos;
1873     if (rem == 0)
1874       return S_FALSE;
1875     UInt32 blockSize = kCompressionBlockSize;
1876     if (rem < kCompressionBlockSize)
1877       blockSize = (UInt32)rem;
1878 
1879     const UInt32 size = GetUi32(tableBuf + i * 8 + 4);
1880 
1881     if (size > kCompressionBlockSize + 1)
1882       return S_FALSE;
1883 
1884     RINOK(ReadStream_FALSE(inStream, buf, size))
1885 
1886     if ((buf[0] & 0xF) == 0xF)
1887     {
1888       // (buf[0] = 0xff) is marker of uncompressed block in APFS
1889       // that code was not tested in HFS
1890       if (size - 1 != blockSize)
1891         return S_FALSE;
1892 
1893       if (outStream)
1894       {
1895         RINOK(WriteStream(outStream, buf + 1, blockSize))
1896       }
1897     }
1898     else
1899     {
1900       const UInt64 blockSize64 = blockSize;
1901       bufInStream->Init(buf, size);
1902       RINOK(_zlibDecoder.Interface()->Code(bufInStream, outStream, NULL, &blockSize64, NULL))
1903       if (_zlibDecoder->GetOutputProcessedSize() != blockSize)
1904         return S_FALSE;
1905       const UInt64 inSize = _zlibDecoder->GetInputProcessedSize();
1906       if (inSize != size)
1907       {
1908         if (inSize > size)
1909           return S_FALSE;
1910         // apfs file can contain junk (non-zeros) after data block.
1911         /*
1912         if (!padError)
1913         {
1914           const Byte *p = buf + (UInt32)inSize;
1915           const Byte *e = p + (size - (UInt32)inSize);
1916           do
1917           {
1918             if (*p != 0)
1919             {
1920               padError = true;
1921               break;
1922             }
1923           }
1924           while (++p != e);
1925         }
1926         */
1927       }
1928     }
1929 
1930     outPos += blockSize;
1931     if ((i & 0xFF) == 0)
1932     {
1933       const UInt64 progressPos = progressStart + outPos;
1934       RINOK(extractCallback->SetCompleted(&progressPos))
1935     }
1936   }
1937 
1938   if (outPos != unpackSize)
1939     return S_FALSE;
1940 
1941   // if (padError) return S_FALSE;
1942 
1943   /* We check Resource Map
1944      Are there HFS files with another values in Resource Map ??? */
1945 
1946   RINOK(ReadStream_FALSE(inStream, buf, mapSize))
1947   const UInt32 types = Get16(buf + 24);
1948   const UInt32 names = Get16(buf + 26);
1949   const UInt32 numTypes = Get16(buf + 28);
1950   if (numTypes != 0 || types != 28 || names != kResMapSize)
1951     return S_FALSE;
1952   const UInt32 resType = Get32(buf + 30);
1953   const UInt32 numResources = Get16(buf + 34);
1954   const UInt32 resListOffset = Get16(buf + 36);
1955   if (resType != 0x636D7066) // cmpf
1956     return S_FALSE;
1957   if (numResources != 0 || resListOffset != 10)
1958     return S_FALSE;
1959 
1960   const UInt32 entryId = Get16(buf + 38);
1961   const UInt32 nameOffset = Get16(buf + 40);
1962   // Byte attrib = buf[42];
1963   const UInt32 resourceOffset = Get32(buf + 42) & 0xFFFFFF;
1964   if (entryId != 1 || nameOffset != 0xFFFF || resourceOffset != 0)
1965     return S_FALSE;
1966 
1967   return S_OK;
1968 }
1969 
1970 
1971 
1972 HRESULT CDecoder::ExtractResourceFork_LZFSE(
1973     ISequentialInStream *inStream, ISequentialOutStream *outStream,
1974     UInt64 forkSize, UInt64 unpackSize,
1975     UInt64 progressStart, IArchiveExtractCallback *extractCallback)
1976 {
1977   const UInt32 kNumBlocksMax = (UInt32)1 << 29;
1978   if (unpackSize >= (UInt64)kNumBlocksMax * kCompressionBlockSize)
1979     return S_FALSE;
1980   const UInt32 numBlocks = (UInt32)((unpackSize + kCompressionBlockSize - 1) / kCompressionBlockSize);
1981   const UInt32 numBlocks2 = numBlocks + 1;
1982   const UInt32 tableSize = (numBlocks2 << 2);
1983   if (tableSize > forkSize)
1984     return S_FALSE;
1985   _tableBuf.AllocAtLeast(tableSize);
1986   RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize))
1987   const Byte *tableBuf = _tableBuf;
1988 
1989   {
1990     UInt32 prev = GetUi32(tableBuf);
1991     if (prev != tableSize)
1992       return S_FALSE;
1993     for (UInt32 i = 1; i < numBlocks2; i++)
1994     {
1995       const UInt32 offs = GetUi32(tableBuf + i * 4);
1996       if (offs <= prev)
1997         return S_FALSE;
1998       prev = offs;
1999     }
2000     if (prev != forkSize)
2001       return S_FALSE;
2002   }
2003 
2004   const size_t kBufSize = kCompressionBlockSize;
2005   _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
2006 
2007   CMyComPtr2_Create<ISequentialInStream, CBufInStream> bufInStream;
2008 
2009   UInt64 outPos = 0;
2010 
2011   for (UInt32 i = 0; i < numBlocks; i++)
2012   {
2013     const UInt64 rem = unpackSize - outPos;
2014     if (rem == 0)
2015       return S_FALSE;
2016     UInt32 blockSize = kCompressionBlockSize;
2017     if (rem < kCompressionBlockSize)
2018       blockSize = (UInt32)rem;
2019 
2020     const UInt32 size =
2021         GetUi32(tableBuf + i * 4 + 4) -
2022         GetUi32(tableBuf + i * 4);
2023 
2024     if (size > kCompressionBlockSize + 1)
2025       return S_FALSE;
2026 
2027     RINOK(ReadStream_FALSE(inStream, _buf, size))
2028     const Byte *buf = _buf;
2029 
2030     if (buf[0] == k_LZVN_Uncompressed_Marker)
2031     {
2032       if (size - 1 != blockSize)
2033         return S_FALSE;
2034       if (outStream)
2035       {
2036         RINOK(WriteStream(outStream, buf + 1, blockSize))
2037       }
2038     }
2039     else
2040     {
2041       const UInt64 blockSize64 = blockSize;
2042       const UInt64 packSize64 = size;
2043       bufInStream->Init(buf, size);
2044       RINOK(_lzfseDecoder.Interface()->Code(bufInStream, outStream, &packSize64, &blockSize64, NULL))
2045       // in/out sizes were checked in Code()
2046     }
2047 
2048     outPos += blockSize;
2049     if ((i & 0xFF) == 0)
2050     {
2051       const UInt64 progressPos = progressStart + outPos;
2052       RINOK(extractCallback->SetCompleted(&progressPos))
2053     }
2054   }
2055 
2056   return S_OK;
2057 }
2058 
2059 
2060 /*
2061 static UInt32 GetUi24(const Byte *p)
2062 {
2063   return p[0] + ((UInt32)p[1] << 8) + ((UInt32)p[2] << 24);
2064 }
2065 
2066 HRESULT CDecoder::ExtractResourceFork_ZBM(
2067     ISequentialInStream *inStream, ISequentialOutStream *outStream,
2068     UInt64 forkSize, UInt64 unpackSize,
2069     UInt64 progressStart, IArchiveExtractCallback *extractCallback)
2070 {
2071   const UInt32 kNumBlocksMax = (UInt32)1 << 29;
2072   if (unpackSize >= (UInt64)kNumBlocksMax * kCompressionBlockSize)
2073     return S_FALSE;
2074   const UInt32 numBlocks = (UInt32)((unpackSize + kCompressionBlockSize - 1) / kCompressionBlockSize);
2075   const UInt32 numBlocks2 = numBlocks + 1;
2076   const UInt32 tableSize = (numBlocks2 << 2);
2077   if (tableSize > forkSize)
2078     return S_FALSE;
2079   _tableBuf.AllocAtLeast(tableSize);
2080   RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize));
2081   const Byte *tableBuf = _tableBuf;
2082 
2083   {
2084     UInt32 prev = GetUi32(tableBuf);
2085     if (prev != tableSize)
2086       return S_FALSE;
2087     for (UInt32 i = 1; i < numBlocks2; i++)
2088     {
2089       const UInt32 offs = GetUi32(tableBuf + i * 4);
2090       if (offs <= prev)
2091         return S_FALSE;
2092       prev = offs;
2093     }
2094     if (prev != forkSize)
2095       return S_FALSE;
2096   }
2097 
2098   const size_t kBufSize = kCompressionBlockSize;
2099   _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
2100 
2101   CBufInStream *bufInStream = new CBufInStream;
2102   CMyComPtr<ISequentialInStream> bufInStream = bufInStream;
2103 
2104   UInt64 outPos = 0;
2105 
2106   for (UInt32 i = 0; i < numBlocks; i++)
2107   {
2108     const UInt64 rem = unpackSize - outPos;
2109     if (rem == 0)
2110       return S_FALSE;
2111     UInt32 blockSize = kCompressionBlockSize;
2112     if (rem < kCompressionBlockSize)
2113       blockSize = (UInt32)rem;
2114 
2115     const UInt32 size =
2116         GetUi32(tableBuf + i * 4 + 4) -
2117         GetUi32(tableBuf + i * 4);
2118 
2119     // if (size > kCompressionBlockSize + 1)
2120     if (size > blockSize + 1)
2121       return S_FALSE; // we don't expect it, because encode will use uncompressed chunk
2122 
2123     RINOK(ReadStream_FALSE(inStream, _buf, size));
2124     const Byte *buf = _buf;
2125 
2126     // (size != 0)
2127     // if (size == 0) return S_FALSE;
2128 
2129     if (buf[0] == 0xFF) // uncompressed marker
2130     {
2131       if (size != blockSize + 1)
2132         return S_FALSE;
2133       if (outStream)
2134       {
2135         RINOK(WriteStream(outStream, buf + 1, blockSize));
2136       }
2137     }
2138     else
2139     {
2140       if (size < 4)
2141         return S_FALSE;
2142       if (buf[0] != 'Z' ||
2143           buf[1] != 'B' ||
2144           buf[2] != 'M' ||
2145           buf[3] != 9)
2146         return S_FALSE;
2147       // for debug:
2148       unsigned packPos = 4;
2149       unsigned unpackPos = 0;
2150       unsigned packRem = size - packPos;
2151       for (;;)
2152       {
2153         if (packRem < 6)
2154           return S_FALSE;
2155         const UInt32 packSize = GetUi24(buf + packPos);
2156         const UInt32 chunkUnpackSize = GetUi24(buf + packPos + 3);
2157         if (packSize < 6)
2158           return S_FALSE;
2159         if (packSize > packRem)
2160           return S_FALSE;
2161         if (chunkUnpackSize > blockSize - unpackPos)
2162           return S_FALSE;
2163         packPos += packSize;
2164         packRem -= packSize;
2165         unpackPos += chunkUnpackSize;
2166         if (packSize == 6)
2167         {
2168           if (chunkUnpackSize != 0)
2169             return S_FALSE;
2170           break;
2171         }
2172         if (packSize >= chunkUnpackSize + 6)
2173         {
2174           if (packSize > chunkUnpackSize + 6)
2175             return S_FALSE;
2176           // uncompressed chunk;
2177         }
2178         else
2179         {
2180           // compressed chunk
2181           const Byte *t = buf + packPos - packSize + 6;
2182           UInt32 r = packSize - 6;
2183           if (r < 9)
2184             return S_FALSE;
2185           const UInt32 v0 = GetUi24(t);
2186           const UInt32 v1 = GetUi24(t + 3);
2187           const UInt32 v2 = GetUi24(t + 6);
2188           if (v0 > v1 || v1 > v2 || v2 > packSize)
2189             return S_FALSE;
2190           // here we need the code that will decompress ZBM chunk
2191         }
2192       }
2193 
2194       if (unpackPos != blockSize)
2195         return S_FALSE;
2196 
2197       UInt32 size1 = size;
2198       if (size1 > kCompressionBlockSize)
2199       {
2200         size1 = kCompressionBlockSize;
2201         // return S_FALSE;
2202       }
2203       if (outStream)
2204       {
2205         RINOK(WriteStream(outStream, buf, size1))
2206 
2207         const UInt32 kTempSize = 1 << 16;
2208         Byte temp[kTempSize];
2209         memset(temp, 0, kTempSize);
2210 
2211         for (UInt32 k = size1; k < kCompressionBlockSize; k++)
2212         {
2213           UInt32 cur = kCompressionBlockSize - k;
2214           if (cur > kTempSize)
2215             cur = kTempSize;
2216           RINOK(WriteStream(outStream, temp, cur))
2217           k += cur;
2218         }
2219       }
2220 
2221       // const UInt64 blockSize64 = blockSize;
2222       // const UInt64 packSize64 = size;
2223       // bufInStream->Init(buf, size);
2224       // RINOK(_zbmDecoderSpec->Code(bufInStream, outStream, &packSize64, &blockSize64, NULL));
2225       // in/out sizes were checked in Code()
2226     }
2227 
2228     outPos += blockSize;
2229     if ((i & 0xFF) == 0)
2230     {
2231       const UInt64 progressPos = progressStart + outPos;
2232       RINOK(extractCallback->SetCompleted(&progressPos));
2233     }
2234   }
2235 
2236   return S_OK;
2237 }
2238 */
2239 
2240 HRESULT CDecoder::Extract(
2241     ISequentialInStream *inStreamFork, ISequentialOutStream *realOutStream,
2242     UInt64 forkSize,
2243     const CCompressHeader &compressHeader,
2244     const CByteBuffer *data,
2245     UInt64 progressStart, IArchiveExtractCallback *extractCallback,
2246     int &opRes)
2247 {
2248   opRes = NExtract::NOperationResult::kDataError;
2249 
2250   if (compressHeader.IsMethod_Uncompressed_Inline())
2251   {
2252     const size_t packSize = data->Size() - compressHeader.DataPos;
2253     if (realOutStream)
2254     {
2255       RINOK(WriteStream(realOutStream, *data + compressHeader.DataPos, packSize))
2256     }
2257     opRes = NExtract::NOperationResult::kOK;
2258     return S_OK;
2259   }
2260 
2261   if (compressHeader.Method == kMethod_ZLIB_ATTR ||
2262       compressHeader.Method == kMethod_LZVN_ATTR)
2263   {
2264     CMyComPtr2_Create<ISequentialInStream, CBufInStream> bufInStream;
2265     const size_t packSize = data->Size() - compressHeader.DataPos;
2266     bufInStream->Init(*data + compressHeader.DataPos, packSize);
2267 
2268     if (compressHeader.Method == kMethod_ZLIB_ATTR)
2269     {
2270       const HRESULT hres = _zlibDecoder.Interface()->Code(bufInStream, realOutStream,
2271           NULL, &compressHeader.UnpackSize, NULL);
2272       if (hres == S_OK)
2273         if (_zlibDecoder->GetOutputProcessedSize() == compressHeader.UnpackSize
2274             && _zlibDecoder->GetInputProcessedSize() == packSize)
2275           opRes = NExtract::NOperationResult::kOK;
2276       return hres;
2277     }
2278     {
2279       const UInt64 packSize64 = packSize;
2280       const HRESULT hres = _lzfseDecoder.Interface()->Code(bufInStream, realOutStream,
2281           &packSize64, &compressHeader.UnpackSize, NULL);
2282       if (hres == S_OK)
2283       {
2284         // in/out sizes were checked in Code()
2285         opRes = NExtract::NOperationResult::kOK;
2286       }
2287       return hres;
2288     }
2289   }
2290 
2291   HRESULT hres;
2292   if (compressHeader.Method == NHfs::kMethod_ZLIB_RSRC)
2293   {
2294     hres = ExtractResourceFork_ZLIB(
2295         inStreamFork, realOutStream,
2296         forkSize, compressHeader.UnpackSize,
2297         progressStart, extractCallback);
2298     // for debug:
2299     // hres = NCompress::CopyStream(inStreamFork, realOutStream, NULL);
2300   }
2301   else if (compressHeader.Method == NHfs::kMethod_LZVN_RSRC)
2302   {
2303     hres = ExtractResourceFork_LZFSE(
2304         inStreamFork, realOutStream,
2305         forkSize, compressHeader.UnpackSize,
2306         progressStart, extractCallback);
2307   }
2308   /*
2309   else if (compressHeader.Method == NHfs::kMethod_ZBM_RSRC)
2310   {
2311     hres = ExtractResourceFork_ZBM(
2312         inStreamFork, realOutStream,
2313         forkSize, compressHeader.UnpackSize,
2314         progressStart, extractCallback);
2315   }
2316   */
2317   else
2318   {
2319     opRes = NExtract::NOperationResult::kUnsupportedMethod;
2320     hres = S_FALSE;
2321   }
2322 
2323   if (hres == S_OK)
2324     opRes = NExtract::NOperationResult::kOK;
2325   return hres;
2326 }
2327 
2328 
2329 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
2330     Int32 testMode, IArchiveExtractCallback *extractCallback))
2331 {
2332   COM_TRY_BEGIN
2333   const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
2334   if (allFilesMode)
2335     numItems = Refs.Size();
2336   if (numItems == 0)
2337     return S_OK;
2338   UInt32 i;
2339   UInt64 totalSize = 0;
2340   for (i = 0; i < numItems; i++)
2341   {
2342     const CRef &ref = Refs[allFilesMode ? i : indices[i]];
2343     totalSize += Get_UnpackSize_of_Ref(ref);
2344   }
2345   RINOK(extractCallback->SetTotal(totalSize))
2346 
2347   UInt64 currentTotalSize = 0, currentItemSize = 0;
2348 
2349   const size_t kBufSize = kCompressionBlockSize;
2350   CByteBuffer buf(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
2351 
2352   // there are hfs without adler in zlib.
2353   CDecoder decoder(true); // IsAdlerOptional
2354 
2355   for (i = 0;; i++, currentTotalSize += currentItemSize)
2356   {
2357     RINOK(extractCallback->SetCompleted(&currentTotalSize))
2358     if (i >= numItems)
2359       break;
2360     const UInt32 index = allFilesMode ? i : indices[i];
2361     const CRef &ref = Refs[index];
2362     const CItem &item = Items[ref.ItemIndex];
2363     currentItemSize = Get_UnpackSize_of_Ref(ref);
2364 
2365     int opRes;
2366    {
2367     CMyComPtr<ISequentialOutStream> realOutStream;
2368     const Int32 askMode = testMode ?
2369         NExtract::NAskMode::kTest :
2370         NExtract::NAskMode::kExtract;
2371     RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
2372 
2373     if (ref.IsItem() && item.IsDir())
2374     {
2375       RINOK(extractCallback->PrepareOperation(askMode))
2376       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
2377       continue;
2378     }
2379     if (!testMode && !realOutStream)
2380       continue;
2381 
2382     RINOK(extractCallback->PrepareOperation(askMode))
2383 
2384     UInt64 pos = 0;
2385     opRes = NExtract::NOperationResult::kDataError;
2386     const CFork *fork = NULL;
2387 
2388     if (ref.AttrIndex >= 0)
2389     {
2390       const CAttr &attr = Attrs[ref.AttrIndex];
2391       if (attr.Fork_defined && attr.Data.Size() == 0)
2392         fork = &attr.Fork;
2393       else
2394       {
2395         opRes = NExtract::NOperationResult::kOK;
2396         if (realOutStream)
2397         {
2398           RINOK(WriteStream(realOutStream,
2399               // AttrBuf + attr.Pos, attr.Size
2400               attr.Data, attr.Data.Size()
2401               ))
2402         }
2403       }
2404     }
2405     else if (ref.IsResource())
2406       fork = &item.ResourceFork;
2407     else if (item.CompressHeader.IsSupported)
2408     {
2409       CMyComPtr<ISequentialInStream> inStreamFork;
2410       UInt64 forkSize = 0;
2411       const CByteBuffer *decmpfs_Data = NULL;
2412 
2413       if (item.CompressHeader.IsMethod_Resource())
2414       {
2415         const CFork &resourceFork = item.ResourceFork;
2416         forkSize = resourceFork.Size;
2417         GetForkStream(resourceFork, &inStreamFork);
2418       }
2419       else
2420       {
2421         const CAttr &attr = Attrs[item.decmpfs_AttrIndex];
2422         decmpfs_Data = &attr.Data;
2423       }
2424 
2425       if (inStreamFork || decmpfs_Data)
2426       {
2427         const HRESULT hres = decoder.Extract(
2428             inStreamFork, realOutStream,
2429             forkSize,
2430             item.CompressHeader,
2431             decmpfs_Data,
2432             currentTotalSize, extractCallback,
2433             opRes);
2434         if (hres != S_FALSE && hres != S_OK)
2435           return hres;
2436       }
2437     }
2438     else if (item.CompressHeader.IsCorrect)
2439       opRes = NExtract::NOperationResult::kUnsupportedMethod;
2440     else
2441       fork = &item.DataFork;
2442 
2443     if (fork)
2444     {
2445       if (fork->IsOk(Header.BlockSizeLog))
2446       {
2447         opRes = NExtract::NOperationResult::kOK;
2448         unsigned extentIndex;
2449         for (extentIndex = 0; extentIndex < fork->Extents.Size(); extentIndex++)
2450         {
2451           if (opRes != NExtract::NOperationResult::kOK)
2452             break;
2453           if (fork->Size == pos)
2454             break;
2455           const CExtent &e = fork->Extents[extentIndex];
2456           RINOK(InStream_SeekSet(_stream, SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog)))
2457           UInt64 extentRem = (UInt64)e.NumBlocks << Header.BlockSizeLog;
2458           while (extentRem != 0)
2459           {
2460             const UInt64 rem = fork->Size - pos;
2461             if (rem == 0)
2462             {
2463               // Here we check that there are no extra (empty) blocks in last extent.
2464               if (extentRem >= ((UInt64)1 << Header.BlockSizeLog))
2465                 opRes = NExtract::NOperationResult::kDataError;
2466               break;
2467             }
2468             size_t cur = kBufSize;
2469             if (cur > rem)
2470               cur = (size_t)rem;
2471             if (cur > extentRem)
2472               cur = (size_t)extentRem;
2473             RINOK(ReadStream(_stream, buf, &cur))
2474             if (cur == 0)
2475             {
2476               opRes = NExtract::NOperationResult::kDataError;
2477               break;
2478             }
2479             if (realOutStream)
2480             {
2481               RINOK(WriteStream(realOutStream, buf, cur))
2482             }
2483             pos += cur;
2484             extentRem -= cur;
2485             const UInt64 processed = currentTotalSize + pos;
2486             RINOK(extractCallback->SetCompleted(&processed))
2487           }
2488         }
2489         if (extentIndex != fork->Extents.Size() || fork->Size != pos)
2490           opRes = NExtract::NOperationResult::kDataError;
2491       }
2492     }
2493    }
2494     RINOK(extractCallback->SetOperationResult(opRes))
2495   }
2496   return S_OK;
2497   COM_TRY_END
2498 }
2499 
2500 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
2501 {
2502   *numItems = Refs.Size();
2503   return S_OK;
2504 }
2505 
2506 HRESULT CHandler::GetForkStream(const CFork &fork, ISequentialInStream **stream)
2507 {
2508   *stream = NULL;
2509 
2510   if (!fork.IsOk(Header.BlockSizeLog))
2511     return S_FALSE;
2512 
2513   CMyComPtr2<ISequentialInStream, CExtentsStream> extentStream;
2514   extentStream.Create_if_Empty();
2515 
2516   UInt64 rem = fork.Size;
2517   UInt64 virt = 0;
2518 
2519   FOR_VECTOR (i, fork.Extents)
2520   {
2521     const CExtent &e = fork.Extents[i];
2522     if (e.NumBlocks == 0)
2523       continue;
2524     UInt64 cur = ((UInt64)e.NumBlocks << Header.BlockSizeLog);
2525     if (cur > rem)
2526     {
2527       cur = rem;
2528       if (i != fork.Extents.Size() - 1)
2529         return S_FALSE;
2530     }
2531     CSeekExtent se;
2532     se.Phy = (UInt64)e.Pos << Header.BlockSizeLog;
2533     se.Virt = virt;
2534     virt += cur;
2535     rem -= cur;
2536     extentStream->Extents.Add(se);
2537   }
2538 
2539   if (rem != 0)
2540     return S_FALSE;
2541 
2542   CSeekExtent se;
2543   se.Phy = 0;
2544   se.Virt = virt;
2545   extentStream->Extents.Add(se);
2546   extentStream->Stream = _stream;
2547   extentStream->Init();
2548   *stream = extentStream.Detach();
2549   return S_OK;
2550 }
2551 
2552 Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
2553 {
2554   *stream = NULL;
2555 
2556   const CRef &ref = Refs[index];
2557   const CFork *fork = NULL;
2558   if (ref.AttrIndex >= 0)
2559   {
2560     const CAttr &attr = Attrs[ref.AttrIndex];
2561     if (!attr.Fork_defined || attr.Data.Size() != 0)
2562       return S_FALSE;
2563     fork = &attr.Fork;
2564   }
2565   else
2566   {
2567     const CItem &item = Items[ref.ItemIndex];
2568     if (ref.IsResource())
2569       fork = &item.ResourceFork;
2570     else if (item.IsDir())
2571       return S_FALSE;
2572     else if (item.CompressHeader.IsCorrect)
2573       return S_FALSE;
2574     else
2575       fork = &item.DataFork;
2576   }
2577   return GetForkStream(*fork, stream);
2578 }
2579 
2580 static const Byte k_Signature[] = {
2581     2, 'B', 'D',
2582     4, 'H', '+', 0, 4,
2583     4, 'H', 'X', 0, 5 };
2584 
2585 REGISTER_ARC_I(
2586   "HFS", "hfs hfsx", NULL, 0xE3,
2587   k_Signature,
2588   kHeaderPadSize,
2589   NArcInfoFlags::kMultiSignature,
2590   IsArc_HFS)
2591 
2592 }}
2593