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