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(¤tTotalSize));
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