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/PropVariant.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 "../Compress/ZlibDecoder.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 
Parse(const Byte * p)84 void CFork::Parse(const Byte *p)
85 {
86   Extents.Clear();
87   Size = Get64(p);
88   // ClumpSize = Get32(p + 8);
89   NumBlocks = Get32(p + 12);
90   p += 16;
91   for (unsigned i = 0; i < kNumFixedExtents; i++, p += 8)
92   {
93     CExtent e;
94     e.Pos = Get32(p);
95     e.NumBlocks = Get32(p + 4);
96     if (e.NumBlocks != 0)
97       Extents.Add(e);
98   }
99 }
100 
Calc_NumBlocks_from_Extents() const101 UInt32 CFork::Calc_NumBlocks_from_Extents() const
102 {
103   UInt32 num = 0;
104   FOR_VECTOR (i, Extents)
105   {
106     num += Extents[i].NumBlocks;
107   }
108   return num;
109 }
110 
Check_NumBlocks() const111 bool CFork::Check_NumBlocks() const
112 {
113   UInt32 num = 0;
114   FOR_VECTOR (i, Extents)
115   {
116     UInt32 next = num + Extents[i].NumBlocks;
117     if (next < num)
118       return false;
119     num = next;
120   }
121   return num == NumBlocks;
122 }
123 
124 struct CIdIndexPair
125 {
126   UInt32 ID;
127   int Index;
128 
129   int Compare(const CIdIndexPair &a) const;
130 };
131 
132 #define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; }
133 
Compare(const CIdIndexPair & a) const134 int CIdIndexPair::Compare(const CIdIndexPair &a) const
135 {
136   RINOZ(MyCompare(ID, a.ID));
137   return MyCompare(Index, a.Index);
138 }
139 
FindItemIndex(const CRecordVector<CIdIndexPair> & items,UInt32 id)140 static int FindItemIndex(const CRecordVector<CIdIndexPair> &items, UInt32 id)
141 {
142   unsigned left = 0, right = items.Size();
143   while (left != right)
144   {
145     unsigned mid = (left + right) / 2;
146     UInt32 midVal = items[mid].ID;
147     if (id == midVal)
148       return items[mid].Index;
149     if (id < midVal)
150       right = mid;
151     else
152       left = mid + 1;
153   }
154   return -1;
155 }
156 
Find_in_IdExtents(const CObjectVector<CIdExtents> & items,UInt32 id)157 static int Find_in_IdExtents(const CObjectVector<CIdExtents> &items, UInt32 id)
158 {
159   unsigned left = 0, right = items.Size();
160   while (left != right)
161   {
162     unsigned mid = (left + right) / 2;
163     UInt32 midVal = items[mid].ID;
164     if (id == midVal)
165       return mid;
166     if (id < midVal)
167       right = mid;
168     else
169       left = mid + 1;
170   }
171   return -1;
172 }
173 
Upgrade(const CObjectVector<CIdExtents> & items,UInt32 id)174 bool CFork::Upgrade(const CObjectVector<CIdExtents> &items, UInt32 id)
175 {
176   int index = Find_in_IdExtents(items, id);
177   if (index < 0)
178     return true;
179   const CIdExtents &item = items[index];
180   if (Calc_NumBlocks_from_Extents() != item.StartBlock)
181     return false;
182   Extents += item.Extents;
183   return true;
184 }
185 
186 
187 struct CVolHeader
188 {
189   Byte Header[2];
190   UInt16 Version;
191   // UInt32 Attr;
192   // UInt32 LastMountedVersion;
193   // UInt32 JournalInfoBlock;
194 
195   UInt32 CTime;
196   UInt32 MTime;
197   // UInt32 BackupTime;
198   // UInt32 CheckedTime;
199 
200   UInt32 NumFiles;
201   UInt32 NumFolders;
202   unsigned BlockSizeLog;
203   UInt32 NumBlocks;
204   UInt32 NumFreeBlocks;
205 
206   // UInt32 WriteCount;
207   // UInt32 FinderInfo[8];
208   // UInt64 VolID;
209 
GetPhySizeNArchive::NHfs::CVolHeader210   UInt64 GetPhySize() const { return (UInt64)NumBlocks << BlockSizeLog; }
GetFreeSizeNArchive::NHfs::CVolHeader211   UInt64 GetFreeSize() const { return (UInt64)NumFreeBlocks << BlockSizeLog; }
IsHfsXNArchive::NHfs::CVolHeader212   bool IsHfsX() const { return Version > 4; }
213 };
214 
HfsTimeToFileTime(UInt32 hfsTime,FILETIME & ft)215 inline void HfsTimeToFileTime(UInt32 hfsTime, FILETIME &ft)
216 {
217   UInt64 v = ((UInt64)3600 * 24 * (365 * 303 + 24 * 3) + hfsTime) * 10000000;
218   ft.dwLowDateTime = (DWORD)v;
219   ft.dwHighDateTime = (DWORD)(v >> 32);
220 }
221 
222 enum ERecordType
223 {
224   RECORD_TYPE_FOLDER = 1,
225   RECORD_TYPE_FILE,
226   RECORD_TYPE_FOLDER_THREAD,
227   RECORD_TYPE_FILE_THREAD
228 };
229 
230 struct CItem
231 {
232   UString Name;
233 
234   UInt32 ParentID;
235 
236   UInt16 Type;
237   UInt16 FileMode;
238   // UInt16 Flags;
239   // UInt32 Valence;
240   UInt32 ID;
241   UInt32 CTime;
242   UInt32 MTime;
243   // UInt32 AttrMTime;
244   UInt32 ATime;
245   // UInt32 BackupDate;
246 
247   /*
248   UInt32 OwnerID;
249   UInt32 GroupID;
250   Byte AdminFlags;
251   Byte OwnerFlags;
252   union
253   {
254     UInt32  iNodeNum;
255     UInt32  LinkCount;
256     UInt32  RawDevice;
257   } special;
258 
259   UInt32 FileType;
260   UInt32 FileCreator;
261   UInt16 FinderFlags;
262   UInt16 Point[2];
263   */
264 
265   CFork DataFork;
266   CFork ResourceFork;
267 
268   // for compressed attribute
269   UInt64 UnpackSize;
270   size_t DataPos;
271   UInt32 PackSize;
272   unsigned Method;
273   bool UseAttr;
274   bool UseInlineData;
275 
CItemNArchive::NHfs::CItem276   CItem(): UseAttr(false), UseInlineData(false) {}
IsDirNArchive::NHfs::CItem277   bool IsDir() const { return Type == RECORD_TYPE_FOLDER; }
GetForkNArchive::NHfs::CItem278   const CFork &GetFork(bool isResource) const { return (const CFork & )*(isResource ? &ResourceFork: &DataFork ); }
279 };
280 
281 struct CAttr
282 {
283   UInt32 ID;
284   UInt32 Size;
285   size_t Pos;
286   UString Name;
287 };
288 
289 struct CRef
290 {
291   unsigned ItemIndex;
292   int AttrIndex;
293   int Parent;
294   bool IsResource;
295 
IsAltStreamNArchive::NHfs::CRef296   bool IsAltStream() const { return IsResource || AttrIndex >= 0; }
CRefNArchive::NHfs::CRef297   CRef(): AttrIndex(-1), Parent(-1), IsResource(false)  {}
298 };
299 
300 class CDatabase
301 {
302   HRESULT ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream);
303   HRESULT LoadExtentFile(const CFork &fork, IInStream *inStream, CObjectVector<CIdExtents> *overflowExtentsArray);
304   HRESULT LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress);
305   HRESULT LoadCatalog(const CFork &fork, const CObjectVector<CIdExtents> *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress);
306   bool Parse_decmpgfs(const CAttr &attr, CItem &item, bool &skip);
307 public:
308   CRecordVector<CRef> Refs;
309   CObjectVector<CItem> Items;
310   CObjectVector<CAttr> Attrs;
311 
312   CByteBuffer AttrBuf;
313 
314   CVolHeader Header;
315   bool HeadersError;
316   bool ThereAreAltStreams;
317   // bool CaseSensetive;
318   UString ResFileName;
319 
320   UInt64 SpecOffset;
321   UInt64 PhySize;
322   UInt64 PhySize2;
323   UInt64 ArcFileSize;
324 
Clear()325   void Clear()
326   {
327     SpecOffset = 0;
328     PhySize = 0;
329     PhySize2 = 0;
330     ArcFileSize = 0;
331     HeadersError = false;
332     ThereAreAltStreams = false;
333     // CaseSensetive = false;
334     Refs.Clear();
335     Items.Clear();
336     Attrs.Clear();
337     AttrBuf.Free();
338   }
339 
Get_UnpackSize_of_Ref(const CRef & ref) const340   UInt64 Get_UnpackSize_of_Ref(const CRef &ref) const
341   {
342     if (ref.AttrIndex >= 0)
343       return Attrs[ref.AttrIndex].Size;
344     const CItem &item = Items[ref.ItemIndex];
345     if (item.IsDir())
346       return 0;
347     if (item.UseAttr)
348       return item.UnpackSize;
349     return item.GetFork(ref.IsResource).Size;
350   }
351 
352   void GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const;
353   HRESULT Open2(IInStream *inStream, IArchiveOpenCallback *progress);
354 };
355 
356 enum
357 {
358   kHfsID_Root                  = 1,
359   kHfsID_RootFolder            = 2,
360   kHfsID_ExtentsFile           = 3,
361   kHfsID_CatalogFile           = 4,
362   kHfsID_BadBlockFile          = 5,
363   kHfsID_AllocationFile        = 6,
364   kHfsID_StartupFile           = 7,
365   kHfsID_AttributesFile        = 8,
366   kHfsID_RepairCatalogFile     = 14,
367   kHfsID_BogusExtentFile       = 15,
368   kHfsID_FirstUserCatalogNode  = 16
369 };
370 
GetItemPath(unsigned index,NWindows::NCOM::CPropVariant & path) const371 void CDatabase::GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const
372 {
373   unsigned len = 0;
374   const unsigned kNumLevelsMax = (1 << 10);
375   int cur = index;
376   unsigned i;
377 
378   for (i = 0; i < kNumLevelsMax; i++)
379   {
380     const CRef &ref = Refs[cur];
381     const UString *s;
382 
383     if (ref.IsResource)
384       s = &ResFileName;
385     else if (ref.AttrIndex >= 0)
386       s = &Attrs[ref.AttrIndex].Name;
387     else
388       s = &Items[ref.ItemIndex].Name;
389 
390     len += s->Len();
391     len++;
392     cur = ref.Parent;
393     if (cur < 0)
394       break;
395   }
396 
397   len--;
398   wchar_t *p = path.AllocBstr(len);
399   p[len] = 0;
400   cur = index;
401 
402   for (;;)
403   {
404     const CRef &ref = Refs[cur];
405     const UString *s;
406     wchar_t delimChar = L':';
407 
408     if (ref.IsResource)
409       s = &ResFileName;
410     else if (ref.AttrIndex >= 0)
411       s = &Attrs[ref.AttrIndex].Name;
412     else
413     {
414       delimChar = WCHAR_PATH_SEPARATOR;
415       s = &Items[ref.ItemIndex].Name;
416     }
417 
418     unsigned curLen = s->Len();
419     len -= curLen;
420 
421     const wchar_t *src = (const wchar_t *)*s;
422     wchar_t *dest = p + len;
423     for (unsigned j = 0; j < curLen; j++)
424     {
425       wchar_t c = src[j];
426       // 18.06
427       if (c == CHAR_PATH_SEPARATOR || c == '/')
428         c = '_';
429       dest[j] = c;
430     }
431 
432     if (len == 0)
433       break;
434     p[--len] = delimChar;
435     cur = ref.Parent;
436   }
437 }
438 
439 // Actually we read all blocks. It can be larger than fork.Size
440 
ReadFile(const CFork & fork,CByteBuffer & buf,IInStream * inStream)441 HRESULT CDatabase::ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream)
442 {
443   if (fork.NumBlocks >= Header.NumBlocks)
444     return S_FALSE;
445   if ((ArcFileSize >> Header.BlockSizeLog) + 1 < fork.NumBlocks)
446     return S_FALSE;
447 
448   const size_t totalSize = (size_t)fork.NumBlocks << Header.BlockSizeLog;
449   if ((totalSize >> Header.BlockSizeLog) != fork.NumBlocks)
450     return S_FALSE;
451   buf.Alloc(totalSize);
452   UInt32 curBlock = 0;
453   FOR_VECTOR (i, fork.Extents)
454   {
455     if (curBlock >= fork.NumBlocks)
456       return S_FALSE;
457     const CExtent &e = fork.Extents[i];
458     if (e.Pos > Header.NumBlocks ||
459         e.NumBlocks > fork.NumBlocks - curBlock ||
460         e.NumBlocks > Header.NumBlocks - e.Pos)
461       return S_FALSE;
462     RINOK(inStream->Seek(SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog), STREAM_SEEK_SET, NULL));
463     RINOK(ReadStream_FALSE(inStream,
464         (Byte *)buf + ((size_t)curBlock << Header.BlockSizeLog),
465         (size_t)e.NumBlocks << Header.BlockSizeLog));
466     curBlock += e.NumBlocks;
467   }
468   return S_OK;
469 }
470 
471 static const unsigned kNodeDescriptor_Size = 14;
472 
473 struct CNodeDescriptor
474 {
475   UInt32 fLink;
476   // UInt32 bLink;
477   Byte Kind;
478   // Byte Height;
479   unsigned NumRecords;
480 
481   bool Parse(const Byte *p, unsigned nodeSizeLog);
482 };
483 
484 
Parse(const Byte * p,unsigned nodeSizeLog)485 bool CNodeDescriptor::Parse(const Byte *p, unsigned nodeSizeLog)
486 {
487   fLink = Get32(p);
488   // bLink = Get32(p + 4);
489   Kind = p[8];
490   // Height = p[9];
491   NumRecords = Get16(p + 10);
492 
493   const size_t nodeSize = (size_t)1 << nodeSizeLog;
494   if (kNodeDescriptor_Size + ((UInt32)NumRecords + 1) * 2 > nodeSize)
495     return false;
496   const size_t limit = nodeSize - ((UInt32)NumRecords + 1) * 2;
497 
498   p += nodeSize - 2;
499 
500   for (unsigned i = 0; i < NumRecords; i++)
501   {
502     const UInt32 offs = Get16(p);
503     p -= 2;
504     const UInt32 offsNext = Get16(p);
505     if (offs < kNodeDescriptor_Size
506         || offs >= offsNext
507         || offsNext > limit)
508       return false;
509   }
510   return true;
511 }
512 
513 struct CHeaderRec
514 {
515   // UInt16 TreeDepth;
516   // UInt32 RootNode;
517   // UInt32 LeafRecords;
518   UInt32 FirstLeafNode;
519   // UInt32 LastLeafNode;
520   unsigned NodeSizeLog;
521   // UInt16 MaxKeyLength;
522   UInt32 TotalNodes;
523   // UInt32 FreeNodes;
524   // UInt16 Reserved1;
525   // UInt32 ClumpSize;
526   // Byte BtreeType;
527   // Byte KeyCompareType;
528   // UInt32 Attributes;
529   // UInt32 Reserved3[16];
530 
531   HRESULT Parse2(const CByteBuffer &buf);
532 };
533 
Parse2(const CByteBuffer & buf)534 HRESULT CHeaderRec::Parse2(const CByteBuffer &buf)
535 {
536   if (buf.Size() < kNodeDescriptor_Size + 0x2A + 16 * 4)
537     return S_FALSE;
538   const Byte * p = (const Byte *)buf + kNodeDescriptor_Size;
539   // TreeDepth = Get16(p);
540   // RootNode = Get32(p + 2);
541   // LeafRecords = Get32(p + 6);
542   FirstLeafNode = Get32(p + 0xA);
543   // LastLeafNode = Get32(p + 0xE);
544   const UInt32 nodeSize = Get16(p + 0x12);
545 
546   unsigned i;
547   for (i = 9; ((UInt32)1 << i) != nodeSize; i++)
548     if (i == 16)
549       return S_FALSE;
550   NodeSizeLog = i;
551 
552   // MaxKeyLength = Get16(p + 0x14);
553   TotalNodes = Get32(p + 0x16);
554   // FreeNodes = Get32(p + 0x1A);
555   // Reserved1 = Get16(p + 0x1E);
556   // ClumpSize = Get32(p + 0x20);
557   // BtreeType = p[0x24];
558   // KeyCompareType = p[0x25];
559   // Attributes = Get32(p + 0x26);
560   /*
561   for (int i = 0; i < 16; i++)
562     Reserved3[i] = Get32(p + 0x2A + i * 4);
563   */
564 
565   if ((buf.Size() >> NodeSizeLog) < TotalNodes)
566     return S_FALSE;
567 
568   return S_OK;
569 }
570 
571 
572 static const Byte kNodeType_Leaf   = 0xFF;
573 // static const Byte kNodeType_Index  = 0;
574 // static const Byte kNodeType_Header = 1;
575 // static const Byte kNodeType_Mode   = 2;
576 
577 static const Byte kExtentForkType_Data = 0;
578 static const Byte kExtentForkType_Resource = 0xFF;
579 
580 /* It loads data extents from Extents Overflow File
581    Most dmg installers are not fragmented. So there are no extents in Overflow File. */
582 
LoadExtentFile(const CFork & fork,IInStream * inStream,CObjectVector<CIdExtents> * overflowExtentsArray)583 HRESULT CDatabase::LoadExtentFile(const CFork &fork, IInStream *inStream, CObjectVector<CIdExtents> *overflowExtentsArray)
584 {
585   if (fork.NumBlocks == 0)
586     return S_OK;
587   CByteBuffer buf;
588   RINOK(ReadFile(fork, buf, inStream));
589   const Byte *p = (const Byte *)buf;
590 
591   // CNodeDescriptor nodeDesc;
592   // nodeDesc.Parse(p);
593   CHeaderRec hr;
594   RINOK(hr.Parse2(buf));
595 
596   UInt32 node = hr.FirstLeafNode;
597   if (node == 0)
598     return S_OK;
599 
600   CByteArr usedBuf(hr.TotalNodes);
601   memset(usedBuf, 0, hr.TotalNodes);
602 
603   while (node != 0)
604   {
605     if (node >= hr.TotalNodes || usedBuf[node] != 0)
606       return S_FALSE;
607     usedBuf[node] = 1;
608 
609     const size_t nodeOffset = (size_t)node << hr.NodeSizeLog;
610     CNodeDescriptor desc;
611     if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog))
612       return S_FALSE;
613     if (desc.Kind != kNodeType_Leaf)
614       return S_FALSE;
615 
616     UInt32 endBlock = 0;
617 
618     for (unsigned i = 0; i < desc.NumRecords; i++)
619     {
620       const UInt32 nodeSize = (1 << hr.NodeSizeLog);
621       const Byte *r = p + nodeOffset + nodeSize - i * 2;
622       const UInt32 offs = Get16(r - 2);
623       UInt32 recSize = Get16(r - 4) - offs;
624       const unsigned kKeyLen = 10;
625 
626       if (recSize != 2 + kKeyLen + kNumFixedExtents * 8)
627         return S_FALSE;
628 
629       r = p + nodeOffset + offs;
630       if (Get16(r) != kKeyLen)
631         return S_FALSE;
632 
633       Byte forkType = r[2];
634       unsigned forkTypeIndex;
635       if (forkType == kExtentForkType_Data)
636         forkTypeIndex = 0;
637       else if (forkType == kExtentForkType_Resource)
638         forkTypeIndex = 1;
639       else
640         continue;
641       CObjectVector<CIdExtents> &overflowExtents = overflowExtentsArray[forkTypeIndex];
642 
643       UInt32 id = Get32(r + 4);
644       UInt32 startBlock = Get32(r + 8);
645       r += 2 + kKeyLen;
646 
647       bool needNew = true;
648 
649       if (overflowExtents.Size() != 0)
650       {
651         CIdExtents &e = overflowExtents.Back();
652         if (e.ID == id)
653         {
654           if (endBlock != startBlock)
655             return S_FALSE;
656           needNew = false;
657         }
658       }
659 
660       if (needNew)
661       {
662         CIdExtents &e = overflowExtents.AddNew();
663         e.ID = id;
664         e.StartBlock = startBlock;
665         endBlock = startBlock;
666       }
667 
668       CIdExtents &e = overflowExtents.Back();
669 
670       for (unsigned k = 0; k < kNumFixedExtents; k++, r += 8)
671       {
672         CExtent ee;
673         ee.Pos = Get32(r);
674         ee.NumBlocks = Get32(r + 4);
675         if (ee.NumBlocks != 0)
676         {
677           e.Extents.Add(ee);
678           endBlock += ee.NumBlocks;
679         }
680       }
681     }
682 
683     node = desc.fLink;
684   }
685   return S_OK;
686 }
687 
LoadName(const Byte * data,unsigned len,UString & dest)688 static void LoadName(const Byte *data, unsigned len, UString &dest)
689 {
690   wchar_t *p = dest.GetBuf(len);
691   unsigned i;
692   for (i = 0; i < len; i++)
693   {
694     wchar_t c = Get16(data + i * 2);
695     if (c == 0)
696       break;
697     p[i] = c;
698   }
699   p[i] = 0;
700   dest.ReleaseBuf_SetLen(i);
701 }
702 
IsNameEqualTo(const Byte * data,const char * name)703 static bool IsNameEqualTo(const Byte *data, const char *name)
704 {
705   for (unsigned i = 0;; i++)
706   {
707     char c = name[i];
708     if (c == 0)
709       return true;
710     if (Get16(data + i * 2) != (Byte)c)
711       return false;
712   }
713 }
714 
715 static const UInt32 kAttrRecordType_Inline = 0x10;
716 // static const UInt32 kAttrRecordType_Fork = 0x20;
717 // static const UInt32 kAttrRecordType_Extents = 0x30;
718 
LoadAttrs(const CFork & fork,IInStream * inStream,IArchiveOpenCallback * progress)719 HRESULT CDatabase::LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress)
720 {
721   if (fork.NumBlocks == 0)
722     return S_OK;
723 
724   RINOK(ReadFile(fork, AttrBuf, inStream));
725   const Byte *p = (const Byte *)AttrBuf;
726 
727   // CNodeDescriptor nodeDesc;
728   // nodeDesc.Parse(p);
729   CHeaderRec hr;
730   RINOK(hr.Parse2(AttrBuf));
731 
732   // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC);
733 
734   UInt32 node = hr.FirstLeafNode;
735   if (node == 0)
736     return S_OK;
737 
738   CByteArr usedBuf(hr.TotalNodes);
739   memset(usedBuf, 0, hr.TotalNodes);
740 
741   CFork resFork;
742 
743   while (node != 0)
744   {
745     if (node >= hr.TotalNodes || usedBuf[node] != 0)
746       return S_FALSE;
747     usedBuf[node] = 1;
748 
749     const size_t nodeOffset = (size_t)node << hr.NodeSizeLog;
750     CNodeDescriptor desc;
751     if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog))
752       return S_FALSE;
753     if (desc.Kind != kNodeType_Leaf)
754       return S_FALSE;
755 
756     for (unsigned i = 0; i < desc.NumRecords; i++)
757     {
758       const UInt32 nodeSize = (1 << hr.NodeSizeLog);
759       const Byte *r = p + nodeOffset + nodeSize - i * 2;
760       const UInt32 offs = Get16(r - 2);
761       UInt32 recSize = Get16(r - 4) - offs;
762       const unsigned kHeadSize = 14;
763       if (recSize < kHeadSize)
764         return S_FALSE;
765 
766       r = p + nodeOffset + offs;
767       UInt32 keyLen = Get16(r);
768 
769       // UInt16 pad = Get16(r + 2);
770       UInt32 fileID = Get32(r + 4);
771       unsigned startBlock = Get32(r + 8);
772       if (startBlock != 0)
773       {
774         // that case is still unsupported
775         HeadersError = true;
776         continue;
777       }
778       unsigned nameLen = Get16(r + 12);
779 
780       if (keyLen + 2 > recSize ||
781           keyLen != kHeadSize - 2 + nameLen * 2)
782         return S_FALSE;
783       r += kHeadSize;
784       recSize -= kHeadSize;
785 
786       const Byte *name = r;
787       r += nameLen * 2;
788       recSize -= nameLen * 2;
789 
790       if (recSize < 4)
791         return S_FALSE;
792 
793       UInt32 recordType = Get32(r);
794       if (recordType != kAttrRecordType_Inline)
795       {
796         // Probably only kAttrRecordType_Inline now is used in real HFS files
797         HeadersError = true;
798         continue;
799       }
800 
801       const UInt32 kRecordHeaderSize = 16;
802       if (recSize < kRecordHeaderSize)
803         return S_FALSE;
804       UInt32 dataSize = Get32(r + 12);
805 
806       r += kRecordHeaderSize;
807       recSize -= kRecordHeaderSize;
808 
809       if (recSize < dataSize)
810         return S_FALSE;
811 
812       CAttr &attr = Attrs.AddNew();
813       attr.ID = fileID;
814       attr.Pos = nodeOffset + offs + 2 + keyLen + kRecordHeaderSize;
815       attr.Size = dataSize;
816       LoadName(name, nameLen, attr.Name);
817 
818       if (progress && (i & 0xFFF) == 0)
819       {
820         const UInt64 numFiles = 0;
821         RINOK(progress->SetCompleted(&numFiles, NULL));
822       }
823     }
824 
825     node = desc.fLink;
826   }
827   return S_OK;
828 }
829 
830 static const UInt32 kMethod_Attr     = 3; // data stored in attribute file
831 static const UInt32 kMethod_Resource = 4; // data stored in resource fork
832 
Parse_decmpgfs(const CAttr & attr,CItem & item,bool & skip)833 bool CDatabase::Parse_decmpgfs(const CAttr &attr, CItem &item, bool &skip)
834 {
835   skip = false;
836   if (!attr.Name.IsEqualTo("com.apple.decmpfs"))
837     return true;
838   if (item.UseAttr || !item.DataFork.IsEmpty())
839     return false;
840 
841   const UInt32 k_decmpfs_headerSize = 16;
842   UInt32 dataSize = attr.Size;
843   if (dataSize < k_decmpfs_headerSize)
844     return false;
845   const Byte *r = AttrBuf + attr.Pos;
846   if (GetUi32(r) != 0x636D7066) // magic == "fpmc"
847     return false;
848   item.Method = GetUi32(r + 4);
849   item.UnpackSize = GetUi64(r + 8);
850   dataSize -= k_decmpfs_headerSize;
851   r += k_decmpfs_headerSize;
852   if (item.Method == kMethod_Resource)
853   {
854     if (dataSize != 0)
855       return false;
856     item.UseAttr = true;
857   }
858   else if (item.Method == kMethod_Attr)
859   {
860     if (dataSize == 0)
861       return false;
862     Byte b = r[0];
863     if ((b & 0xF) == 0xF)
864     {
865       dataSize--;
866       if (item.UnpackSize > dataSize)
867         return false;
868       item.DataPos = attr.Pos + k_decmpfs_headerSize + 1;
869       item.PackSize = dataSize;
870       item.UseAttr = true;
871       item.UseInlineData = true;
872     }
873     else
874     {
875       item.DataPos = attr.Pos + k_decmpfs_headerSize;
876       item.PackSize = dataSize;
877       item.UseAttr = true;
878     }
879   }
880   else
881     return false;
882   skip = true;
883   return true;
884 }
885 
LoadCatalog(const CFork & fork,const CObjectVector<CIdExtents> * overflowExtentsArray,IInStream * inStream,IArchiveOpenCallback * progress)886 HRESULT CDatabase::LoadCatalog(const CFork &fork, const CObjectVector<CIdExtents> *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress)
887 {
888   CByteBuffer buf;
889   RINOK(ReadFile(fork, buf, inStream));
890   const Byte *p = (const Byte *)buf;
891 
892   // CNodeDescriptor nodeDesc;
893   // nodeDesc.Parse(p);
894   CHeaderRec hr;
895   RINOK(hr.Parse2(buf));
896 
897   CRecordVector<CIdIndexPair> IdToIndexMap;
898 
899   const unsigned reserveSize = (unsigned)(Header.NumFolders + 1 + Header.NumFiles);
900 
901   const unsigned kBasicRecSize = 0x58;
902   const unsigned kMinRecSize = kBasicRecSize + 10;
903 
904   if ((UInt64)reserveSize * kMinRecSize < buf.Size())
905   {
906     Items.ClearAndReserve(reserveSize);
907     Refs.ClearAndReserve(reserveSize);
908     IdToIndexMap.ClearAndReserve(reserveSize);
909   }
910 
911   // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC);
912 
913   CByteArr usedBuf(hr.TotalNodes);
914   memset(usedBuf, 0, hr.TotalNodes);
915 
916   CFork resFork;
917 
918   UInt32 node = hr.FirstLeafNode;
919   UInt32 numFiles = 0;
920   UInt32 numFolders = 0;
921 
922   while (node != 0)
923   {
924     if (node >= hr.TotalNodes || usedBuf[node] != 0)
925       return S_FALSE;
926     usedBuf[node] = 1;
927 
928     const size_t nodeOffset = (size_t)node << hr.NodeSizeLog;
929     CNodeDescriptor desc;
930     if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog))
931       return S_FALSE;
932     if (desc.Kind != kNodeType_Leaf)
933       return S_FALSE;
934 
935     for (unsigned i = 0; i < desc.NumRecords; i++)
936     {
937       const UInt32 nodeSize = (1 << hr.NodeSizeLog);
938       const Byte *r = p + nodeOffset + nodeSize - i * 2;
939       const UInt32 offs = Get16(r - 2);
940       UInt32 recSize = Get16(r - 4) - offs;
941       if (recSize < 6)
942         return S_FALSE;
943 
944       r = p + nodeOffset + offs;
945       UInt32 keyLen = Get16(r);
946       UInt32 parentID = Get32(r + 2);
947       if (keyLen < 6 || (keyLen & 1) != 0 || keyLen + 2 > recSize)
948         return S_FALSE;
949       r += 6;
950       recSize -= 6;
951       keyLen -= 6;
952 
953       unsigned nameLen = Get16(r);
954       if (nameLen * 2 != (unsigned)keyLen)
955         return S_FALSE;
956       r += 2;
957       recSize -= 2;
958 
959       r += nameLen * 2;
960       recSize -= nameLen * 2;
961 
962       if (recSize < 2)
963         return S_FALSE;
964       UInt16 type = Get16(r);
965 
966       if (type != RECORD_TYPE_FOLDER &&
967           type != RECORD_TYPE_FILE)
968         continue;
969 
970       if (recSize < kBasicRecSize)
971         return S_FALSE;
972 
973       CItem &item = Items.AddNew();
974       item.ParentID = parentID;
975       item.Type = type;
976       // item.Flags = Get16(r + 2);
977       // item.Valence = Get32(r + 4);
978       item.ID = Get32(r + 8);
979       {
980         const Byte *name = r - (nameLen * 2);
981         LoadName(name, nameLen, item.Name);
982         if (item.Name.Len() <= 1)
983         {
984           if (item.Name.IsEmpty() && nameLen == 21)
985           {
986             if (GetUi32(name) == 0 &&
987                 GetUi32(name + 4) == 0 &&
988                 IsNameEqualTo(name + 8, "HFS+ Private Data"))
989             {
990               // it's folder for "Hard Links" files
991               item.Name = "[HFS+ Private Data]";
992             }
993           }
994 
995           // Some dmg files have ' ' folder item.
996           if (item.Name.IsEmpty() || item.Name[0] == L' ')
997             item.Name = "[]";
998         }
999       }
1000 
1001       item.CTime = Get32(r + 0xC);
1002       item.MTime = Get32(r + 0x10);
1003       // item.AttrMTime = Get32(r + 0x14);
1004       item.ATime = Get32(r + 0x18);
1005       // item.BackupDate = Get32(r + 0x1C);
1006 
1007       /*
1008       item.OwnerID = Get32(r + 0x20);
1009       item.GroupID = Get32(r + 0x24);
1010       item.AdminFlags = r[0x28];
1011       item.OwnerFlags = r[0x29];
1012       */
1013       item.FileMode = Get16(r + 0x2A);
1014       /*
1015       item.special.iNodeNum = Get16(r + 0x2C); // or .linkCount
1016       item.FileType = Get32(r + 0x30);
1017       item.FileCreator = Get32(r + 0x34);
1018       item.FinderFlags = Get16(r + 0x38);
1019       item.Point[0] = Get16(r + 0x3A); // v
1020       item.Point[1] = Get16(r + 0x3C); // h
1021       */
1022 
1023       // const refIndex = Refs.Size();
1024       CIdIndexPair pair;
1025       pair.ID = item.ID;
1026       pair.Index = Items.Size() - 1;
1027       IdToIndexMap.Add(pair);
1028 
1029       recSize -= kBasicRecSize;
1030       r += kBasicRecSize;
1031       if (item.IsDir())
1032       {
1033         numFolders++;
1034         if (recSize != 0)
1035           return S_FALSE;
1036       }
1037       else
1038       {
1039         numFiles++;
1040         const unsigned kForkRecSize = 16 + kNumFixedExtents * 8;
1041         if (recSize != kForkRecSize * 2)
1042           return S_FALSE;
1043 
1044         item.DataFork.Parse(r);
1045 
1046         if (!item.DataFork.UpgradeAndTest(overflowExtentsArray[0], item.ID, Header.BlockSizeLog))
1047           HeadersError = true;
1048 
1049         item.ResourceFork.Parse(r + kForkRecSize);
1050         if (!item.ResourceFork.IsEmpty())
1051         {
1052           if (!item.ResourceFork.UpgradeAndTest(overflowExtentsArray[1], item.ID, Header.BlockSizeLog))
1053             HeadersError = true;
1054           ThereAreAltStreams = true;
1055         }
1056       }
1057       if (progress && (Items.Size() & 0xFFF) == 0)
1058       {
1059         const UInt64 numItems = Items.Size();
1060         RINOK(progress->SetCompleted(&numItems, NULL));
1061       }
1062     }
1063     node = desc.fLink;
1064   }
1065 
1066   if (Header.NumFiles != numFiles ||
1067       Header.NumFolders + 1 != numFolders)
1068     HeadersError = true;
1069 
1070   IdToIndexMap.Sort2();
1071   {
1072     for (unsigned i = 1; i < IdToIndexMap.Size(); i++)
1073       if (IdToIndexMap[i - 1].ID == IdToIndexMap[i].ID)
1074         return S_FALSE;
1075   }
1076 
1077 
1078   CBoolArr skipAttr(Attrs.Size());
1079   {
1080     for (unsigned i = 0; i < Attrs.Size(); i++)
1081       skipAttr[i] = false;
1082   }
1083 
1084   {
1085     FOR_VECTOR (i, Attrs)
1086     {
1087       const CAttr &attr = Attrs[i];
1088 
1089       int itemIndex = FindItemIndex(IdToIndexMap, attr.ID);
1090       if (itemIndex < 0)
1091       {
1092         HeadersError = true;
1093         continue;
1094       }
1095       if (!Parse_decmpgfs(attr, Items[itemIndex], skipAttr[i]))
1096         HeadersError = true;
1097     }
1098   }
1099 
1100   IdToIndexMap.ClearAndReserve(Items.Size());
1101 
1102   {
1103     FOR_VECTOR (i, Items)
1104     {
1105       const CItem &item = Items[i];
1106 
1107       CIdIndexPair pair;
1108       pair.ID = item.ID;
1109       pair.Index = Refs.Size();
1110       IdToIndexMap.Add(pair);
1111 
1112       CRef ref;
1113       ref.ItemIndex = i;
1114       Refs.Add(ref);
1115 
1116       #ifdef HFS_SHOW_ALT_STREAMS
1117 
1118       if (item.ResourceFork.IsEmpty())
1119         continue;
1120       if (item.UseAttr && item.Method == kMethod_Resource)
1121         continue;
1122       CRef resRef;
1123       resRef.ItemIndex = i;
1124       resRef.IsResource = true;
1125       resRef.Parent = Refs.Size() - 1;
1126       Refs.Add(resRef);
1127 
1128       #endif
1129     }
1130   }
1131 
1132   IdToIndexMap.Sort2();
1133 
1134   {
1135     FOR_VECTOR (i, Refs)
1136     {
1137       CRef &ref = Refs[i];
1138       if (ref.IsResource)
1139         continue;
1140       CItem &item = Items[ref.ItemIndex];
1141       ref.Parent = FindItemIndex(IdToIndexMap, item.ParentID);
1142       if (ref.Parent >= 0)
1143       {
1144         if (!Items[Refs[ref.Parent].ItemIndex].IsDir())
1145         {
1146           ref.Parent = -1;
1147           HeadersError = true;
1148         }
1149       }
1150     }
1151   }
1152 
1153   #ifdef HFS_SHOW_ALT_STREAMS
1154   {
1155     FOR_VECTOR (i, Attrs)
1156     {
1157       if (skipAttr[i])
1158         continue;
1159       const CAttr &attr = Attrs[i];
1160 
1161       int refIndex = FindItemIndex(IdToIndexMap, attr.ID);
1162       if (refIndex < 0)
1163       {
1164         HeadersError = true;
1165         continue;
1166       }
1167 
1168       CRef ref;
1169       ref.AttrIndex = i;
1170       ref.Parent = refIndex;
1171       ref.ItemIndex = Refs[refIndex].ItemIndex;
1172       Refs.Add(ref);
1173     }
1174   }
1175   #endif
1176 
1177   return S_OK;
1178 }
1179 
1180 static const unsigned kHeaderPadSize = (1 << 10);
1181 static const unsigned kMainHeaderSize = 512;
1182 static const unsigned kHfsHeaderSize = kHeaderPadSize + kMainHeaderSize;
1183 
IsArc_HFS(const Byte * p,size_t size)1184 API_FUNC_static_IsArc IsArc_HFS(const Byte *p, size_t size)
1185 {
1186   if (size < kHfsHeaderSize)
1187     return k_IsArc_Res_NEED_MORE;
1188   p += kHeaderPadSize;
1189   if (p[0] == 'B' && p[1] == 'D')
1190   {
1191     if (p[0x7C] != 'H' || p[0x7C + 1] != '+')
1192       return k_IsArc_Res_NO;
1193   }
1194   else
1195   {
1196     if (p[0] != 'H' || (p[1] != '+' && p[1] != 'X'))
1197       return k_IsArc_Res_NO;
1198     UInt32 version = Get16(p + 2);
1199     if (version < 4 || version > 5)
1200       return k_IsArc_Res_NO;
1201   }
1202   return k_IsArc_Res_YES;
1203 }
1204 }
1205 
Open2(IInStream * inStream,IArchiveOpenCallback * progress)1206 HRESULT CDatabase::Open2(IInStream *inStream, IArchiveOpenCallback *progress)
1207 {
1208   Clear();
1209   Byte buf[kHfsHeaderSize];
1210   RINOK(ReadStream_FALSE(inStream, buf, kHfsHeaderSize));
1211   {
1212     for (unsigned i = 0; i < kHeaderPadSize; i++)
1213       if (buf[i] != 0)
1214         return S_FALSE;
1215   }
1216   const Byte *p = buf + kHeaderPadSize;
1217   CVolHeader &h = Header;
1218 
1219   h.Header[0] = p[0];
1220   h.Header[1] = p[1];
1221 
1222   if (p[0] == 'B' && p[1] == 'D')
1223   {
1224     /*
1225     It's header for old HFS format.
1226     We don't support old HFS format, but we support
1227     special HFS volume that contains embedded HFS+ volume
1228     */
1229 
1230     if (p[0x7C] != 'H' || p[0x7C + 1] != '+')
1231       return S_FALSE;
1232 
1233     /*
1234     h.CTime = Get32(p + 0x2);
1235     h.MTime = Get32(p + 0x6);
1236 
1237     h.NumFiles = Get32(p + 0x54);
1238     h.NumFolders = Get32(p + 0x58);
1239 
1240     if (h.NumFolders > ((UInt32)1 << 29) ||
1241         h.NumFiles > ((UInt32)1 << 30))
1242       return S_FALSE;
1243     if (progress)
1244     {
1245       UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1;
1246       RINOK(progress->SetTotal(&numFiles, NULL));
1247     }
1248     h.NumFreeBlocks = Get16(p + 0x22);
1249     */
1250 
1251     UInt32 blockSize = Get32(p + 0x14);
1252 
1253     {
1254       unsigned i;
1255       for (i = 9; ((UInt32)1 << i) != blockSize; i++)
1256         if (i == 31)
1257           return S_FALSE;
1258       h.BlockSizeLog = i;
1259     }
1260 
1261     h.NumBlocks = Get16(p + 0x12);
1262     /*
1263     we suppose that it has the follwing layout
1264     {
1265       start block with header
1266       [h.NumBlocks]
1267       end block with header
1268     }
1269     */
1270     PhySize2 = ((UInt64)h.NumBlocks + 2) << h.BlockSizeLog;
1271 
1272     UInt32 startBlock = Get16(p + 0x7C + 2);
1273     UInt32 blockCount = Get16(p + 0x7C + 4);
1274     SpecOffset = (UInt64)(1 + startBlock) << h.BlockSizeLog;
1275     UInt64 phy = SpecOffset + ((UInt64)blockCount << h.BlockSizeLog);
1276     if (PhySize2 < phy)
1277       PhySize2 = phy;
1278     RINOK(inStream->Seek(SpecOffset, STREAM_SEEK_SET, NULL));
1279     RINOK(ReadStream_FALSE(inStream, buf, kHfsHeaderSize));
1280   }
1281 
1282   if (p[0] != 'H' || (p[1] != '+' && p[1] != 'X'))
1283     return S_FALSE;
1284   h.Version = Get16(p + 2);
1285   if (h.Version < 4 || h.Version > 5)
1286     return S_FALSE;
1287 
1288   // h.Attr = Get32(p + 4);
1289   // h.LastMountedVersion = Get32(p + 8);
1290   // h.JournalInfoBlock = Get32(p + 0xC);
1291 
1292   h.CTime = Get32(p + 0x10);
1293   h.MTime = Get32(p + 0x14);
1294   // h.BackupTime = Get32(p + 0x18);
1295   // h.CheckedTime = Get32(p + 0x1C);
1296 
1297   h.NumFiles = Get32(p + 0x20);
1298   h.NumFolders = Get32(p + 0x24);
1299 
1300   if (h.NumFolders > ((UInt32)1 << 29) ||
1301       h.NumFiles > ((UInt32)1 << 30))
1302     return S_FALSE;
1303 
1304   RINOK(inStream->Seek(0, STREAM_SEEK_END, &ArcFileSize));
1305 
1306   if (progress)
1307   {
1308     const UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1;
1309     RINOK(progress->SetTotal(&numFiles, NULL));
1310   }
1311 
1312   UInt32 blockSize = Get32(p + 0x28);
1313 
1314   {
1315     unsigned i;
1316     for (i = 9; ((UInt32)1 << i) != blockSize; i++)
1317       if (i == 31)
1318         return S_FALSE;
1319     h.BlockSizeLog = i;
1320   }
1321 
1322   h.NumBlocks = Get32(p + 0x2C);
1323   h.NumFreeBlocks = Get32(p + 0x30);
1324 
1325   /*
1326   h.NextCalatlogNodeID = Get32(p + 0x40);
1327   h.WriteCount = Get32(p + 0x44);
1328   for (i = 0; i < 6; i++)
1329     h.FinderInfo[i] = Get32(p + 0x50 + i * 4);
1330   h.VolID = Get64(p + 0x68);
1331   */
1332 
1333   ResFileName = kResFileName;
1334 
1335   CFork extentsFork, catalogFork, attrFork;
1336   // allocationFork.Parse(p + 0x70 + 0x50 * 0);
1337   extentsFork.Parse(p + 0x70 + 0x50 * 1);
1338   catalogFork.Parse(p + 0x70 + 0x50 * 2);
1339   attrFork.Parse   (p + 0x70 + 0x50 * 3);
1340   // startupFork.Parse(p + 0x70 + 0x50 * 4);
1341 
1342   CObjectVector<CIdExtents> overflowExtents[2];
1343   if (!extentsFork.IsOk(Header.BlockSizeLog))
1344     HeadersError = true;
1345   else
1346   {
1347     HRESULT res = LoadExtentFile(extentsFork, inStream, overflowExtents);
1348     if (res == S_FALSE)
1349       HeadersError = true;
1350     else if (res != S_OK)
1351       return res;
1352   }
1353 
1354   if (!catalogFork.UpgradeAndTest(overflowExtents[0], kHfsID_CatalogFile, Header.BlockSizeLog))
1355     return S_FALSE;
1356 
1357   if (!attrFork.UpgradeAndTest(overflowExtents[0], kHfsID_AttributesFile, Header.BlockSizeLog))
1358     HeadersError = true;
1359   else
1360   {
1361     if (attrFork.Size != 0)
1362       RINOK(LoadAttrs(attrFork, inStream, progress));
1363   }
1364 
1365   RINOK(LoadCatalog(catalogFork, overflowExtents, inStream, progress));
1366 
1367   PhySize = Header.GetPhySize();
1368   return S_OK;
1369 }
1370 
1371 
1372 
1373 class CHandler:
1374   public IInArchive,
1375   public IArchiveGetRawProps,
1376   public IInArchiveGetStream,
1377   public CMyUnknownImp,
1378   public CDatabase
1379 {
1380   CMyComPtr<IInStream> _stream;
1381 
1382   HRESULT GetForkStream(const CFork &fork, ISequentialInStream **stream);
1383 
1384   HRESULT ExtractZlibFile(
1385       ISequentialOutStream *realOutStream,
1386       const CItem &item,
1387       NCompress::NZlib::CDecoder *_zlibDecoderSpec,
1388       CByteBuffer &buf,
1389       UInt64 progressStart,
1390       IArchiveExtractCallback *extractCallback);
1391 public:
1392   MY_UNKNOWN_IMP3(IInArchive, IArchiveGetRawProps, IInArchiveGetStream)
1393   INTERFACE_IInArchive(;)
1394   INTERFACE_IArchiveGetRawProps(;)
1395   STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
1396 };
1397 
1398 static const Byte kProps[] =
1399 {
1400   kpidPath,
1401   kpidIsDir,
1402   kpidSize,
1403   kpidPackSize,
1404   kpidCTime,
1405   kpidMTime,
1406   kpidATime,
1407   kpidPosixAttrib
1408 };
1409 
1410 static const Byte kArcProps[] =
1411 {
1412   kpidMethod,
1413   kpidClusterSize,
1414   kpidFreeSpace,
1415   kpidCTime,
1416   kpidMTime
1417 };
1418 
1419 IMP_IInArchive_Props
1420 IMP_IInArchive_ArcProps
1421 
HfsTimeToProp(UInt32 hfsTime,NWindows::NCOM::CPropVariant & prop)1422 static void HfsTimeToProp(UInt32 hfsTime, NWindows::NCOM::CPropVariant &prop)
1423 {
1424   FILETIME ft;
1425   HfsTimeToFileTime(hfsTime, ft);
1426   prop = ft;
1427 }
1428 
GetArchiveProperty(PROPID propID,PROPVARIANT * value)1429 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
1430 {
1431   COM_TRY_BEGIN
1432   NWindows::NCOM::CPropVariant prop;
1433   switch (propID)
1434   {
1435     case kpidExtension: prop = Header.IsHfsX() ? "hfsx" : "hfs"; break;
1436     case kpidMethod: prop = Header.IsHfsX() ? "HFSX" : "HFS+"; break;
1437     case kpidPhySize:
1438     {
1439       UInt64 v = SpecOffset + PhySize;
1440       if (v < PhySize2)
1441         v = PhySize2;
1442       prop = v;
1443       break;
1444     }
1445     case kpidClusterSize: prop = (UInt32)1 << Header.BlockSizeLog; break;
1446     case kpidFreeSpace: prop = (UInt64)Header.GetFreeSize(); break;
1447     case kpidMTime: HfsTimeToProp(Header.MTime, prop); break;
1448     case kpidCTime:
1449     {
1450       FILETIME localFt, ft;
1451       HfsTimeToFileTime(Header.CTime, localFt);
1452       if (LocalFileTimeToFileTime(&localFt, &ft))
1453         prop = ft;
1454       break;
1455     }
1456     case kpidIsTree: prop = true; break;
1457     case kpidErrorFlags:
1458     {
1459       UInt32 flags = 0;
1460       if (HeadersError) flags |= kpv_ErrorFlags_HeadersError;
1461       if (flags != 0)
1462         prop = flags;
1463       break;
1464     }
1465     case kpidIsAltStream: prop = ThereAreAltStreams; break;
1466   }
1467   prop.Detach(value);
1468   return S_OK;
1469   COM_TRY_END
1470 }
1471 
GetNumRawProps(UInt32 * numProps)1472 STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps)
1473 {
1474   *numProps = 0;
1475   return S_OK;
1476 }
1477 
GetRawPropInfo(UInt32,BSTR * name,PROPID * propID)1478 STDMETHODIMP CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID)
1479 {
1480   *name = NULL;
1481   *propID = 0;
1482   return S_OK;
1483 }
1484 
GetParent(UInt32 index,UInt32 * parent,UInt32 * parentType)1485 STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)
1486 {
1487   const CRef &ref = Refs[index];
1488   *parentType = ref.IsAltStream() ?
1489       NParentType::kAltStream :
1490       NParentType::kDir;
1491   *parent = (UInt32)(Int32)ref.Parent;
1492   return S_OK;
1493 }
1494 
GetRawProp(UInt32 index,PROPID propID,const void ** data,UInt32 * dataSize,UInt32 * propType)1495 STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)
1496 {
1497   *data = NULL;
1498   *dataSize = 0;
1499   *propType = 0;
1500   #ifdef MY_CPU_LE
1501   if (propID == kpidName)
1502   {
1503     const CRef &ref = Refs[index];
1504     const UString *s;
1505     if (ref.IsResource)
1506       s = &ResFileName;
1507     else if (ref.AttrIndex >= 0)
1508       s = &Attrs[ref.AttrIndex].Name;
1509     else
1510       s = &Items[ref.ItemIndex].Name;
1511     *data = (const wchar_t *)(*s);
1512     *dataSize = (s->Len() + 1) * (UInt32)sizeof(wchar_t);
1513     *propType = PROP_DATA_TYPE_wchar_t_PTR_Z_LE;
1514     return S_OK;
1515   }
1516   #else
1517   UNUSED_VAR(index);
1518   UNUSED_VAR(propID);
1519   #endif
1520   return S_OK;
1521 }
1522 
GetProperty(UInt32 index,PROPID propID,PROPVARIANT * value)1523 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
1524 {
1525   COM_TRY_BEGIN
1526   NWindows::NCOM::CPropVariant prop;
1527   const CRef &ref = Refs[index];
1528   const CItem &item = Items[ref.ItemIndex];
1529   switch (propID)
1530   {
1531     case kpidPath: GetItemPath(index, prop); break;
1532     case kpidName:
1533       const UString *s;
1534       if (ref.IsResource)
1535         s = &ResFileName;
1536       else if (ref.AttrIndex >= 0)
1537         s = &Attrs[ref.AttrIndex].Name;
1538       else
1539         s = &item.Name;
1540       prop = *s;
1541       break;
1542     case kpidPackSize:
1543       {
1544         UInt64 size;
1545         if (ref.AttrIndex >= 0)
1546           size = Attrs[ref.AttrIndex].Size;
1547         else if (item.IsDir())
1548           break;
1549         else if (item.UseAttr)
1550         {
1551           if (item.Method == kMethod_Resource)
1552             size = item.ResourceFork.NumBlocks << Header.BlockSizeLog;
1553           else
1554             size = item.PackSize;
1555         }
1556         else
1557           size = item.GetFork(ref.IsResource).NumBlocks << Header.BlockSizeLog;
1558         prop = size;
1559         break;
1560       }
1561     case kpidSize:
1562       {
1563         UInt64 size;
1564         if (ref.AttrIndex >= 0)
1565           size = Attrs[ref.AttrIndex].Size;
1566         else if (item.IsDir())
1567           break;
1568         else if (item.UseAttr)
1569           size = item.UnpackSize;
1570         else
1571           size = item.GetFork(ref.IsResource).Size;
1572         prop = size;
1573         break;
1574       }
1575     case kpidIsDir: prop = item.IsDir(); break;
1576     case kpidIsAltStream: prop = ref.IsAltStream(); break;
1577 
1578     case kpidCTime: HfsTimeToProp(item.CTime, prop); break;
1579     case kpidMTime: HfsTimeToProp(item.MTime, prop); break;
1580     case kpidATime: HfsTimeToProp(item.ATime, prop); break;
1581 
1582     case kpidPosixAttrib: if (ref.AttrIndex < 0) prop = (UInt32)item.FileMode; break;
1583   }
1584   prop.Detach(value);
1585   return S_OK;
1586   COM_TRY_END
1587 }
1588 
Open(IInStream * inStream,const UInt64 *,IArchiveOpenCallback * callback)1589 STDMETHODIMP CHandler::Open(IInStream *inStream,
1590     const UInt64 * /* maxCheckStartPosition */,
1591     IArchiveOpenCallback *callback)
1592 {
1593   COM_TRY_BEGIN
1594   Close();
1595   RINOK(Open2(inStream, callback));
1596   _stream = inStream;
1597   return S_OK;
1598   COM_TRY_END
1599 }
1600 
Close()1601 STDMETHODIMP CHandler::Close()
1602 {
1603   _stream.Release();
1604   Clear();
1605   return S_OK;
1606 }
1607 
1608 static const UInt32 kCompressionBlockSize = 1 << 16;
1609 
ExtractZlibFile(ISequentialOutStream * outStream,const CItem & item,NCompress::NZlib::CDecoder * _zlibDecoderSpec,CByteBuffer & buf,UInt64 progressStart,IArchiveExtractCallback * extractCallback)1610 HRESULT CHandler::ExtractZlibFile(
1611     ISequentialOutStream *outStream,
1612     const CItem &item,
1613     NCompress::NZlib::CDecoder *_zlibDecoderSpec,
1614     CByteBuffer &buf,
1615     UInt64 progressStart,
1616     IArchiveExtractCallback *extractCallback)
1617 {
1618   CMyComPtr<ISequentialInStream> inStream;
1619   const CFork &fork = item.ResourceFork;
1620   RINOK(GetForkStream(fork, &inStream));
1621   const unsigned kHeaderSize = 0x100 + 8;
1622   RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize));
1623   UInt32 dataPos = Get32(buf);
1624   UInt32 mapPos = Get32(buf + 4);
1625   UInt32 dataSize = Get32(buf + 8);
1626   UInt32 mapSize = Get32(buf + 12);
1627 
1628   const UInt32 kResMapSize = 50;
1629 
1630   if (mapSize != kResMapSize
1631       || dataPos + dataSize != mapPos
1632       || mapPos + mapSize != fork.Size)
1633     return S_FALSE;
1634 
1635   UInt32 dataSize2 = Get32(buf + 0x100);
1636   if (4 + dataSize2 != dataSize || dataSize2 < 8)
1637     return S_FALSE;
1638 
1639   UInt32 numBlocks = GetUi32(buf + 0x100 + 4);
1640   if (((dataSize2 - 4) >> 3) < numBlocks)
1641     return S_FALSE;
1642   if (item.UnpackSize > (UInt64)numBlocks * kCompressionBlockSize)
1643     return S_FALSE;
1644 
1645   if (item.UnpackSize + kCompressionBlockSize < (UInt64)numBlocks * kCompressionBlockSize)
1646     return S_FALSE;
1647 
1648   UInt32 tableSize = (numBlocks << 3);
1649 
1650   CByteBuffer tableBuf(tableSize);
1651 
1652   RINOK(ReadStream_FALSE(inStream, tableBuf, tableSize));
1653 
1654   UInt32 prev = 4 + tableSize;
1655 
1656   UInt32 i;
1657   for (i = 0; i < numBlocks; i++)
1658   {
1659     UInt32 offset = GetUi32(tableBuf + i * 8);
1660     UInt32 size = GetUi32(tableBuf + i * 8 + 4);
1661     if (size == 0)
1662       return S_FALSE;
1663     if (prev != offset)
1664       return S_FALSE;
1665     if (offset > dataSize2 ||
1666         size > dataSize2 - offset)
1667       return S_FALSE;
1668     prev = offset + size;
1669   }
1670 
1671   if (prev != dataSize2)
1672     return S_FALSE;
1673 
1674   CBufInStream *bufInStreamSpec = new CBufInStream;
1675   CMyComPtr<ISequentialInStream> bufInStream = bufInStreamSpec;
1676 
1677   UInt64 outPos = 0;
1678   for (i = 0; i < numBlocks; i++)
1679   {
1680     UInt64 rem = item.UnpackSize - outPos;
1681     if (rem == 0)
1682       return S_FALSE;
1683     UInt32 blockSize = kCompressionBlockSize;
1684     if (rem < kCompressionBlockSize)
1685       blockSize = (UInt32)rem;
1686 
1687     UInt32 size = GetUi32(tableBuf + i * 8 + 4);
1688 
1689     if (size > buf.Size() || size > kCompressionBlockSize + 1)
1690       return S_FALSE;
1691 
1692     RINOK(ReadStream_FALSE(inStream, buf, size));
1693 
1694     if ((buf[0] & 0xF) == 0xF)
1695     {
1696       // that code was not tested. Are there HFS archives with uncompressed block
1697       if (size - 1 != blockSize)
1698         return S_FALSE;
1699 
1700       if (outStream)
1701       {
1702         RINOK(WriteStream(outStream, buf, blockSize));
1703       }
1704     }
1705     else
1706     {
1707       UInt64 blockSize64 = blockSize;
1708       bufInStreamSpec->Init(buf, size);
1709       RINOK(_zlibDecoderSpec->Code(bufInStream, outStream, NULL, &blockSize64, NULL));
1710       if (_zlibDecoderSpec->GetOutputProcessedSize() != blockSize ||
1711           _zlibDecoderSpec->GetInputProcessedSize() != size)
1712         return S_FALSE;
1713     }
1714 
1715     outPos += blockSize;
1716     const UInt64 progressPos = progressStart + outPos;
1717     RINOK(extractCallback->SetCompleted(&progressPos));
1718   }
1719 
1720   if (outPos != item.UnpackSize)
1721     return S_FALSE;
1722 
1723   /* We check Resource Map
1724      Are there HFS files with another values in Resource Map ??? */
1725 
1726   RINOK(ReadStream_FALSE(inStream, buf, mapSize));
1727   UInt32 types = Get16(buf + 24);
1728   UInt32 names = Get16(buf + 26);
1729   UInt32 numTypes = Get16(buf + 28);
1730   if (numTypes != 0 || types != 28 || names != kResMapSize)
1731     return S_FALSE;
1732   UInt32 resType = Get32(buf + 30);
1733   UInt32 numResources = Get16(buf + 34);
1734   UInt32 resListOffset = Get16(buf + 36);
1735   if (resType != 0x636D7066) // cmpf
1736     return S_FALSE;
1737   if (numResources != 0 || resListOffset != 10)
1738     return S_FALSE;
1739 
1740   UInt32 entryId = Get16(buf + 38);
1741   UInt32 nameOffset = Get16(buf + 40);
1742   // Byte attrib = buf[42];
1743   UInt32 resourceOffset = Get32(buf + 42) & 0xFFFFFF;
1744   if (entryId != 1 || nameOffset != 0xFFFF || resourceOffset != 0)
1745     return S_FALSE;
1746 
1747   return S_OK;
1748 }
1749 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testMode,IArchiveExtractCallback * extractCallback)1750 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
1751     Int32 testMode, IArchiveExtractCallback *extractCallback)
1752 {
1753   COM_TRY_BEGIN
1754   bool allFilesMode = (numItems == (UInt32)(Int32)-1);
1755   if (allFilesMode)
1756     numItems = Refs.Size();
1757   if (numItems == 0)
1758     return S_OK;
1759   UInt32 i;
1760   UInt64 totalSize = 0;
1761   for (i = 0; i < numItems; i++)
1762   {
1763     const CRef &ref = Refs[allFilesMode ? i : indices[i]];
1764     totalSize += Get_UnpackSize_of_Ref(ref);
1765   }
1766   RINOK(extractCallback->SetTotal(totalSize));
1767 
1768   UInt64 currentTotalSize = 0, currentItemSize = 0;
1769 
1770   const size_t kBufSize = kCompressionBlockSize;
1771   CByteBuffer buf(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
1772 
1773   NCompress::NZlib::CDecoder *_zlibDecoderSpec = NULL;
1774   CMyComPtr<ICompressCoder> _zlibDecoder;
1775 
1776   for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize)
1777   {
1778     RINOK(extractCallback->SetCompleted(&currentTotalSize));
1779     UInt32 index = allFilesMode ? i : indices[i];
1780     const CRef &ref = Refs[index];
1781     const CItem &item = Items[ref.ItemIndex];
1782     currentItemSize = Get_UnpackSize_of_Ref(ref);
1783 
1784     CMyComPtr<ISequentialOutStream> realOutStream;
1785     Int32 askMode = testMode ?
1786         NExtract::NAskMode::kTest :
1787         NExtract::NAskMode::kExtract;
1788     RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
1789 
1790     if (ref.AttrIndex < 0 && item.IsDir())
1791     {
1792       RINOK(extractCallback->PrepareOperation(askMode));
1793       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
1794       continue;
1795     }
1796     if (!testMode && !realOutStream)
1797       continue;
1798     RINOK(extractCallback->PrepareOperation(askMode));
1799     UInt64 pos = 0;
1800     int res = NExtract::NOperationResult::kDataError;
1801     if (ref.AttrIndex >= 0)
1802     {
1803       res = NExtract::NOperationResult::kOK;
1804       if (realOutStream)
1805       {
1806         const CAttr &attr = Attrs[ref.AttrIndex];
1807         RINOK(WriteStream(realOutStream, AttrBuf + attr.Pos, attr.Size));
1808       }
1809     }
1810     else if (item.UseAttr)
1811     {
1812       if (item.UseInlineData)
1813       {
1814         res = NExtract::NOperationResult::kOK;
1815         if (realOutStream)
1816         {
1817           RINOK(WriteStream(realOutStream, AttrBuf + item.DataPos, (size_t)item.UnpackSize));
1818         }
1819       }
1820       else
1821       {
1822         if (!_zlibDecoder)
1823         {
1824           _zlibDecoderSpec = new NCompress::NZlib::CDecoder();
1825           _zlibDecoder = _zlibDecoderSpec;
1826         }
1827 
1828         if (item.Method == kMethod_Attr)
1829         {
1830           CBufInStream *bufInStreamSpec = new CBufInStream;
1831           CMyComPtr<ISequentialInStream> bufInStream = bufInStreamSpec;
1832           bufInStreamSpec->Init(AttrBuf + item.DataPos, item.PackSize);
1833 
1834           HRESULT hres = _zlibDecoder->Code(bufInStream, realOutStream, NULL, &item.UnpackSize, NULL);
1835           if (hres != S_FALSE)
1836           {
1837             if (hres != S_OK)
1838               return hres;
1839             if (_zlibDecoderSpec->GetOutputProcessedSize() == item.UnpackSize &&
1840                 _zlibDecoderSpec->GetInputProcessedSize() == item.PackSize)
1841               res = NExtract::NOperationResult::kOK;
1842           }
1843         }
1844         else
1845         {
1846           HRESULT hres = ExtractZlibFile(realOutStream, item, _zlibDecoderSpec, buf,
1847             currentTotalSize, extractCallback);
1848           if (hres != S_FALSE)
1849           {
1850             if (hres != S_OK)
1851               return hres;
1852             res = NExtract::NOperationResult::kOK;
1853           }
1854         }
1855       }
1856     }
1857     else
1858     {
1859       const CFork &fork = item.GetFork(ref.IsResource);
1860       if (fork.IsOk(Header.BlockSizeLog))
1861       {
1862         res = NExtract::NOperationResult::kOK;
1863         unsigned extentIndex;
1864         for (extentIndex = 0; extentIndex < fork.Extents.Size(); extentIndex++)
1865         {
1866           if (res != NExtract::NOperationResult::kOK)
1867             break;
1868           if (fork.Size == pos)
1869             break;
1870           const CExtent &e = fork.Extents[extentIndex];
1871           RINOK(_stream->Seek(SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog), STREAM_SEEK_SET, NULL));
1872           UInt64 extentRem = (UInt64)e.NumBlocks << Header.BlockSizeLog;
1873           while (extentRem != 0)
1874           {
1875             UInt64 rem = fork.Size - pos;
1876             if (rem == 0)
1877             {
1878               // Here we check that there are no extra (empty) blocks in last extent.
1879               if (extentRem >= ((UInt64)1 << Header.BlockSizeLog))
1880                 res = NExtract::NOperationResult::kDataError;
1881               break;
1882             }
1883             size_t cur = kBufSize;
1884             if (cur > rem)
1885               cur = (size_t)rem;
1886             if (cur > extentRem)
1887               cur = (size_t)extentRem;
1888             RINOK(ReadStream(_stream, buf, &cur));
1889             if (cur == 0)
1890             {
1891               res = NExtract::NOperationResult::kDataError;
1892               break;
1893             }
1894             if (realOutStream)
1895             {
1896               RINOK(WriteStream(realOutStream, buf, cur));
1897             }
1898             pos += cur;
1899             extentRem -= cur;
1900             const UInt64 processed = currentTotalSize + pos;
1901             RINOK(extractCallback->SetCompleted(&processed));
1902           }
1903         }
1904         if (extentIndex != fork.Extents.Size() || fork.Size != pos)
1905           res = NExtract::NOperationResult::kDataError;
1906       }
1907     }
1908     realOutStream.Release();
1909     RINOK(extractCallback->SetOperationResult(res));
1910   }
1911   return S_OK;
1912   COM_TRY_END
1913 }
1914 
GetNumberOfItems(UInt32 * numItems)1915 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
1916 {
1917   *numItems = Refs.Size();
1918   return S_OK;
1919 }
1920 
GetForkStream(const CFork & fork,ISequentialInStream ** stream)1921 HRESULT CHandler::GetForkStream(const CFork &fork, ISequentialInStream **stream)
1922 {
1923   *stream = 0;
1924 
1925   if (!fork.IsOk(Header.BlockSizeLog))
1926     return S_FALSE;
1927 
1928   CExtentsStream *extentStreamSpec = new CExtentsStream();
1929   CMyComPtr<ISequentialInStream> extentStream = extentStreamSpec;
1930 
1931   UInt64 rem = fork.Size;
1932   UInt64 virt = 0;
1933 
1934   FOR_VECTOR (i, fork.Extents)
1935   {
1936     const CExtent &e = fork.Extents[i];
1937     if (e.NumBlocks == 0)
1938       continue;
1939     UInt64 cur = ((UInt64)e.NumBlocks << Header.BlockSizeLog);
1940     if (cur > rem)
1941     {
1942       cur = rem;
1943       if (i != fork.Extents.Size() - 1)
1944         return S_FALSE;
1945     }
1946     CSeekExtent se;
1947     se.Phy = (UInt64)e.Pos << Header.BlockSizeLog;
1948     se.Virt = virt;
1949     virt += cur;
1950     rem -= cur;
1951     extentStreamSpec->Extents.Add(se);
1952   }
1953 
1954   if (rem != 0)
1955     return S_FALSE;
1956 
1957   CSeekExtent se;
1958   se.Phy = 0;
1959   se.Virt = virt;
1960   extentStreamSpec->Extents.Add(se);
1961   extentStreamSpec->Stream = _stream;
1962   extentStreamSpec->Init();
1963   *stream = extentStream.Detach();
1964   return S_OK;
1965 }
1966 
GetStream(UInt32 index,ISequentialInStream ** stream)1967 STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
1968 {
1969   *stream = 0;
1970 
1971   const CRef &ref = Refs[index];
1972   if (ref.AttrIndex >= 0)
1973     return S_FALSE;
1974   const CItem &item = Items[ref.ItemIndex];
1975   if (item.IsDir() || item.UseAttr)
1976     return S_FALSE;
1977 
1978   return GetForkStream(item.GetFork(ref.IsResource), stream);
1979 }
1980 
1981 static const Byte k_Signature[] = {
1982     2, 'B', 'D',
1983     4, 'H', '+', 0, 4,
1984     4, 'H', 'X', 0, 5 };
1985 
1986 REGISTER_ARC_I(
1987   "HFS", "hfs hfsx", 0, 0xE3,
1988   k_Signature,
1989   kHeaderPadSize,
1990   NArcInfoFlags::kMultiSignature,
1991   IsArc_HFS)
1992 
1993 }}
1994