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