1 // LzhHandler.cpp
2
3 #include "StdAfx.h"
4
5 #include "../../../C/CpuArch.h"
6
7 #include "../../Common/AutoPtr.h"
8 #include "../../Common/ComTry.h"
9 #include "../../Common/MyBuffer.h"
10 #include "../../Common/StringConvert.h"
11
12 #include "../../Windows/PropVariant.h"
13 #include "../../Windows/PropVariantUtils.h"
14 #include "../../Windows/TimeUtils.h"
15
16 #include "../ICoder.h"
17
18 #include "../Common/LimitedStreams.h"
19 #include "../Common/ProgressUtils.h"
20 #include "../Common/RegisterArc.h"
21 #include "../Common/StreamUtils.h"
22
23 #include "../Compress/CopyCoder.h"
24 #include "../Compress/LzhDecoder.h"
25
26 #include "IArchive.h"
27
28 #include "Common/ItemNameUtils.h"
29
30 using namespace NWindows;
31 using namespace NTime;
32
33 #define Get16(p) GetUi16(p)
34 #define Get32(p) GetUi32(p)
35
36
37 // CRC-16 (-IBM, -ANSI). The poly is 0x8005 (x^16 + x^15 + x^2 + 1)
38
39 static const UInt16 kCrc16Poly = 0xA001;
40
41 MY_ALIGN(64)
42 static UInt16 g_LzhCrc16Table[256];
43
44 #define CRC16_UPDATE_BYTE(crc, b) (g_LzhCrc16Table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
45
46 UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size);
LzhCrc16Update(UInt32 crc,const void * data,size_t size)47 UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size)
48 {
49 const Byte *p = (const Byte *)data;
50 const Byte *pEnd = p + size;
51 for (; p != pEnd; p++)
52 crc = CRC16_UPDATE_BYTE(crc, *p);
53 return crc;
54 }
55
56 static struct CLzhCrc16TableInit
57 {
CLzhCrc16TableInitCLzhCrc16TableInit58 CLzhCrc16TableInit()
59 {
60 for (UInt32 i = 0; i < 256; i++)
61 {
62 UInt32 r = i;
63 for (unsigned j = 0; j < 8; j++)
64 r = (r >> 1) ^ (kCrc16Poly & ((UInt32)0 - (r & 1)));
65 g_LzhCrc16Table[i] = (UInt16)r;
66 }
67 }
68 } g_LzhCrc16TableInit;
69
70
71 namespace NArchive {
72 namespace NLzh{
73
74 const unsigned kMethodIdSize = 5;
75
76 const Byte kExtIdFileName = 0x01;
77 const Byte kExtIdDirName = 0x02;
78 const Byte kExtIdUnixTime = 0x54;
79
80 struct CExtension
81 {
82 Byte Type;
83 CByteBuffer Data;
84
GetStringNArchive::NLzh::CExtension85 AString GetString() const
86 {
87 AString s;
88 s.SetFrom_CalcLen((const char *)(const Byte *)Data, (unsigned)Data.Size());
89 return s;
90 }
91 };
92
93 const UInt32 kBasicPartSize = 22;
94
IsArc_Lzh(const Byte * p,size_t size)95 API_FUNC_static_IsArc IsArc_Lzh(const Byte *p, size_t size)
96 {
97 if (size < 2 + kBasicPartSize)
98 return k_IsArc_Res_NEED_MORE;
99 if (p[2] != '-' || p[3] != 'l' || p[4] != 'h' || p[6] != '-')
100 return k_IsArc_Res_NO;
101 Byte n = p[5];
102 if (n != 'd')
103 if (n < '0' || n > '7')
104 return k_IsArc_Res_NO;
105 return k_IsArc_Res_YES;
106 }
107 }
108
109 struct CItem
110 {
111 AString Name;
112 Byte Method[kMethodIdSize];
113 Byte Attributes;
114 Byte Level;
115 Byte OsId;
116 UInt32 PackSize;
117 UInt32 Size;
118 UInt32 ModifiedTime;
119 UInt16 CRC;
120 CObjectVector<CExtension> Extensions;
121
IsValidMethodNArchive::CItem122 bool IsValidMethod() const { return (Method[0] == '-' && Method[1] == 'l' && Method[4] == '-'); }
IsLhMethodNArchive::CItem123 bool IsLhMethod() const {return (IsValidMethod() && Method[2] == 'h'); }
IsDirNArchive::CItem124 bool IsDir() const {return (IsLhMethod() && Method[3] == 'd'); }
125
IsCopyMethodNArchive::CItem126 bool IsCopyMethod() const
127 {
128 return (IsLhMethod() && Method[3] == '0') ||
129 (IsValidMethod() && Method[2] == 'z' && Method[3] == '4');
130 }
131
IsLh1GroupMethodNArchive::CItem132 bool IsLh1GroupMethod() const
133 {
134 if (!IsLhMethod())
135 return false;
136 switch (Method[3])
137 {
138 case '1':
139 return true;
140 }
141 return false;
142 }
143
IsLh4GroupMethodNArchive::CItem144 bool IsLh4GroupMethod() const
145 {
146 if (!IsLhMethod())
147 return false;
148 switch (Method[3])
149 {
150 case '4':
151 case '5':
152 case '6':
153 case '7':
154 return true;
155 }
156 return false;
157 }
158
GetNumDictBitsNArchive::CItem159 unsigned GetNumDictBits() const
160 {
161 if (!IsLhMethod())
162 return 0;
163 switch (Method[3])
164 {
165 case '1': return 12;
166 case '2': return 13;
167 case '3': return 13;
168 case '4': return 12;
169 case '5': return 13;
170 case '6': return 15;
171 case '7': return 16;
172 }
173 return 0;
174 }
175
FindExtNArchive::CItem176 int FindExt(Byte type) const
177 {
178 FOR_VECTOR (i, Extensions)
179 if (Extensions[i].Type == type)
180 return (int)i;
181 return -1;
182 }
183
GetUnixTimeNArchive::CItem184 bool GetUnixTime(UInt32 &value) const
185 {
186 value = 0;
187 int index = FindExt(kExtIdUnixTime);
188 if (index < 0
189 || Extensions[index].Data.Size() < 4)
190 {
191 if (Level == 2)
192 {
193 value = ModifiedTime;
194 return true;
195 }
196 return false;
197 }
198 const Byte *data = (const Byte *)(Extensions[index].Data);
199 value = GetUi32(data);
200 return true;
201 }
202
GetDirNameNArchive::CItem203 AString GetDirName() const
204 {
205 int index = FindExt(kExtIdDirName);
206 if (index < 0)
207 return AString();
208 return Extensions[index].GetString();
209 }
210
GetFileNameNArchive::CItem211 AString GetFileName() const
212 {
213 int index = FindExt(kExtIdFileName);
214 if (index < 0)
215 return Name;
216 return Extensions[index].GetString();
217 }
218
GetNameNArchive::CItem219 AString GetName() const
220 {
221 AString s (GetDirName());
222 const char kDirSeparator = '\\';
223 // check kDirSeparator in Linux
224 s.Replace((char)(Byte)0xFF, kDirSeparator);
225 if (!s.IsEmpty() && s.Back() != kDirSeparator)
226 s += kDirSeparator;
227 s += GetFileName();
228 return s;
229 }
230 };
231
ReadUInt16(const Byte * p,UInt16 & v)232 static const Byte *ReadUInt16(const Byte *p, UInt16 &v)
233 {
234 v = Get16(p);
235 return p + 2;
236 }
237
ReadString(const Byte * p,size_t size,AString & s)238 static const Byte *ReadString(const Byte *p, size_t size, AString &s)
239 {
240 s.Empty();
241 for (size_t i = 0; i < size; i++)
242 {
243 const Byte c = p[i];
244 if (c == 0)
245 break;
246 s += (char)c;
247 }
248 return p + size;
249 }
250
CalcSum(const Byte * data,size_t size)251 static Byte CalcSum(const Byte *data, size_t size)
252 {
253 Byte sum = 0;
254 for (size_t i = 0; i < size; i++)
255 sum = (Byte)(sum + data[i]);
256 return sum;
257 }
258
GetNextItem(ISequentialInStream * stream,bool & filled,CItem & item)259 static HRESULT GetNextItem(ISequentialInStream *stream, bool &filled, CItem &item)
260 {
261 filled = false;
262
263 size_t processedSize = 2;
264 Byte startHeader[2];
265 RINOK(ReadStream(stream, startHeader, &processedSize))
266 if (processedSize == 0)
267 return S_OK;
268 if (processedSize == 1)
269 return (startHeader[0] == 0) ? S_OK: S_FALSE;
270 if (startHeader[0] == 0 && startHeader[1] == 0)
271 return S_OK;
272
273 Byte header[256];
274 processedSize = kBasicPartSize;
275 RINOK(ReadStream(stream, header, &processedSize))
276 if (processedSize != kBasicPartSize)
277 return (startHeader[0] == 0) ? S_OK: S_FALSE;
278
279 const Byte *p = header;
280 memcpy(item.Method, p, kMethodIdSize);
281 if (!item.IsValidMethod())
282 return S_OK;
283 p += kMethodIdSize;
284 item.PackSize = Get32(p);
285 item.Size = Get32(p + 4);
286 item.ModifiedTime = Get32(p + 8);
287 item.Attributes = p[12];
288 item.Level = p[13];
289 p += 14;
290 if (item.Level > 2)
291 return S_FALSE;
292 UInt32 headerSize;
293 if (item.Level < 2)
294 {
295 headerSize = startHeader[0];
296 if (headerSize < kBasicPartSize)
297 return S_FALSE;
298 RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, headerSize - kBasicPartSize))
299 if (startHeader[1] != CalcSum(header, headerSize))
300 return S_FALSE;
301 const size_t nameLength = *p++;
302 if ((size_t)(p - header) + nameLength + 2 > headerSize)
303 return S_FALSE;
304 p = ReadString(p, nameLength, item.Name);
305 }
306 else
307 headerSize = startHeader[0] | ((UInt32)startHeader[1] << 8);
308 p = ReadUInt16(p, item.CRC);
309 if (item.Level != 0)
310 {
311 if (item.Level == 2)
312 {
313 RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, 2))
314 }
315 if ((size_t)(p - header) + 3 > headerSize)
316 return S_FALSE;
317 item.OsId = *p++;
318 UInt16 nextSize;
319 p = ReadUInt16(p, nextSize);
320 while (nextSize != 0)
321 {
322 if (nextSize < 3)
323 return S_FALSE;
324 if (item.Level == 1)
325 {
326 if (item.PackSize < nextSize)
327 return S_FALSE;
328 item.PackSize -= nextSize;
329 }
330 if (item.Extensions.Size() >= (1 << 8))
331 return S_FALSE;
332 CExtension ext;
333 RINOK(ReadStream_FALSE(stream, &ext.Type, 1))
334 nextSize = (UInt16)(nextSize - 3);
335 ext.Data.Alloc(nextSize);
336 RINOK(ReadStream_FALSE(stream, (Byte *)ext.Data, nextSize))
337 item.Extensions.Add(ext);
338 Byte hdr2[2];
339 RINOK(ReadStream_FALSE(stream, hdr2, 2))
340 ReadUInt16(hdr2, nextSize);
341 }
342 }
343 filled = true;
344 return S_OK;
345 }
346
347
348 static const CUInt32PCharPair g_OsPairs[] =
349 {
350 { 0, "MS-DOS" },
351 { 'M', "MS-DOS" },
352 { '2', "OS/2" },
353 { '9', "OS9" },
354 { 'K', "OS/68K" },
355 { '3', "OS/386" },
356 { 'H', "HUMAN" },
357 { 'U', "UNIX" },
358 { 'C', "CP/M" },
359 { 'F', "FLEX" },
360 { 'm', "Mac" },
361 { 'R', "Runser" },
362 { 'T', "TownsOS" },
363 { 'X', "XOSK" },
364 { 'w', "Windows 95" },
365 { 'W', "Windows NT" },
366 { 'J', "Java VM" }
367 };
368
369
370 static const Byte kProps[] =
371 {
372 kpidPath,
373 kpidIsDir,
374 kpidSize,
375 kpidPackSize,
376 kpidMTime,
377 // kpidAttrib,
378 kpidCRC,
379 kpidMethod,
380 kpidHostOS
381 };
382
383
384 Z7_CLASS_IMP_NOQIB_1(
385 COutStreamWithCRC
386 , ISequentialOutStream
387 )
388 UInt32 _crc;
389 CMyComPtr<ISequentialOutStream> _stream;
390 public:
391 void Init(ISequentialOutStream *stream)
392 {
393 _stream = stream;
394 _crc = 0;
395 }
396 void ReleaseStream() { _stream.Release(); }
397 UInt32 GetCRC() const { return _crc; }
398 };
399
400 Z7_COM7F_IMF(COutStreamWithCRC::Write(const void *data, UInt32 size, UInt32 *processedSize))
401 {
402 HRESULT res = S_OK;
403 if (_stream)
404 res = _stream->Write(data, size, &size);
405 _crc = LzhCrc16Update(_crc, data, size);
406 if (processedSize)
407 *processedSize = size;
408 return res;
409 }
410
411
412 struct CItemEx: public CItem
413 {
414 UInt64 DataPosition;
415 };
416
417
418 Z7_CLASS_IMP_CHandler_IInArchive_0
419
420 CObjectVector<CItemEx> _items;
421 CMyComPtr<IInStream> _stream;
422 UInt64 _phySize;
423 UInt32 _errorFlags;
424 bool _isArc;
425 public:
426 CHandler();
427 };
428
429 IMP_IInArchive_Props
430 IMP_IInArchive_ArcProps_NO_Table
431
432 CHandler::CHandler() {}
433
434 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
435 {
436 *numItems = _items.Size();
437 return S_OK;
438 }
439
440 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
441 {
442 NCOM::CPropVariant prop;
443 switch (propID)
444 {
445 case kpidPhySize: prop = _phySize; break;
446
447 case kpidErrorFlags:
448 UInt32 v = _errorFlags;
449 if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
450 prop = v;
451 break;
452 }
453 prop.Detach(value);
454 return S_OK;
455 }
456
457 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
458 {
459 COM_TRY_BEGIN
460 NCOM::CPropVariant prop;
461 const CItemEx &item = _items[index];
462 switch (propID)
463 {
464 case kpidPath:
465 {
466 UString s = NItemName::WinPathToOsPath(MultiByteToUnicodeString(item.GetName(), CP_OEMCP));
467 if (!s.IsEmpty())
468 {
469 if (s.Back() == WCHAR_PATH_SEPARATOR)
470 s.DeleteBack();
471 prop = s;
472 }
473 break;
474 }
475 case kpidIsDir: prop = item.IsDir(); break;
476 case kpidSize: prop = (UInt64)item.Size; break;
477 case kpidPackSize: prop = (UInt64)item.PackSize; break;
478 case kpidCRC: prop = (UInt32)item.CRC; break;
479 case kpidHostOS: PAIR_TO_PROP(g_OsPairs, item.OsId, prop); break;
480 case kpidMTime:
481 {
482 UInt32 unixTime;
483 if (item.GetUnixTime(unixTime))
484 PropVariant_SetFrom_UnixTime(prop, unixTime);
485 else
486 PropVariant_SetFrom_DosTime(prop, item.ModifiedTime);
487 break;
488 }
489 // case kpidAttrib: prop = (UInt32)item.Attributes; break;
490 case kpidMethod:
491 {
492 char method2[kMethodIdSize + 1];
493 method2[kMethodIdSize] = 0;
494 memcpy(method2, item.Method, kMethodIdSize);
495 prop = method2;
496 break;
497 }
498 }
499 prop.Detach(value);
500 return S_OK;
501 COM_TRY_END
502 }
503
504 Z7_COM7F_IMF(CHandler::Open(IInStream *stream,
505 const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback))
506 {
507 COM_TRY_BEGIN
508 Close();
509 try
510 {
511 _items.Clear();
512
513 UInt64 endPos;
514 bool needSetTotal = true;
515
516 RINOK(InStream_AtBegin_GetSize(stream, endPos))
517
518 for (;;)
519 {
520 CItemEx item;
521 bool filled;
522 const HRESULT res = GetNextItem(stream, filled, item);
523 RINOK(InStream_GetPos(stream, item.DataPosition))
524 if (res == S_FALSE)
525 {
526 _errorFlags = kpv_ErrorFlags_HeadersError;
527 break;
528 }
529
530 if (res != S_OK)
531 return S_FALSE;
532 _phySize = item.DataPosition;
533 if (!filled)
534 break;
535 _items.Add(item);
536
537 _isArc = true;
538
539 UInt64 newPostion;
540 RINOK(stream->Seek(item.PackSize, STREAM_SEEK_CUR, &newPostion))
541 if (newPostion > endPos)
542 {
543 _phySize = endPos;
544 _errorFlags = kpv_ErrorFlags_UnexpectedEnd;
545 break;
546 }
547 _phySize = newPostion;
548 if (callback)
549 {
550 if (needSetTotal)
551 {
552 RINOK(callback->SetTotal(NULL, &endPos))
553 needSetTotal = false;
554 }
555 if (_items.Size() % 100 == 0)
556 {
557 const UInt64 numFiles = _items.Size();
558 const UInt64 numBytes = item.DataPosition;
559 RINOK(callback->SetCompleted(&numFiles, &numBytes))
560 }
561 }
562 }
563 if (_items.IsEmpty())
564 return S_FALSE;
565
566 _stream = stream;
567 }
568 catch(...)
569 {
570 return S_FALSE;
571 }
572 COM_TRY_END
573 return S_OK;
574 }
575
576 Z7_COM7F_IMF(CHandler::Close())
577 {
578 _isArc = false;
579 _phySize = 0;
580 _errorFlags = 0;
581 _items.Clear();
582 _stream.Release();
583 return S_OK;
584 }
585
586 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
587 Int32 testMode, IArchiveExtractCallback *extractCallback))
588 {
589 COM_TRY_BEGIN
590 const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
591 if (allFilesMode)
592 numItems = _items.Size();
593 if (numItems == 0)
594 return S_OK;
595 UInt64 totalUnPacked = 0 /* , totalPacked = 0 */;
596 UInt32 i;
597 for (i = 0; i < numItems; i++)
598 {
599 const CItemEx &item = _items[allFilesMode ? i : indices[i]];
600 totalUnPacked += item.Size;
601 // totalPacked += item.PackSize;
602 }
603 RINOK(extractCallback->SetTotal(totalUnPacked))
604
605 UInt32 cur_Unpacked, cur_Packed;
606
607 CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
608 lps->Init(extractCallback, false);
609 CMyUniquePtr<NCompress::NLzh::NDecoder::CCoder> lzhDecoder;
610 CMyComPtr2_Create<ICompressCoder, NCompress::CCopyCoder> copyCoder;
611 CMyComPtr2_Create<ISequentialInStream, CLimitedSequentialInStream> inStream;
612 inStream->SetStream(_stream);
613
614 for (i = 0;; i++,
615 lps->OutSize += cur_Unpacked,
616 lps->InSize += cur_Packed)
617 {
618 cur_Unpacked = 0;
619 cur_Packed = 0;
620 RINOK(lps->SetCur())
621 if (i >= numItems)
622 break;
623
624 Int32 opRes;
625 {
626 CMyComPtr<ISequentialOutStream> realOutStream;
627 const Int32 askMode = testMode ?
628 NExtract::NAskMode::kTest :
629 NExtract::NAskMode::kExtract;
630 const UInt32 index = allFilesMode ? i : indices[i];
631 const CItemEx &item = _items[index];
632 RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
633
634 if (item.IsDir())
635 {
636 // if (!testMode)
637 {
638 RINOK(extractCallback->PrepareOperation(askMode))
639 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
640 }
641 continue;
642 }
643
644 if (!testMode && !realOutStream)
645 continue;
646
647 RINOK(extractCallback->PrepareOperation(askMode))
648 cur_Unpacked = item.Size;
649 cur_Packed = item.PackSize;
650
651 CMyComPtr2_Create<ISequentialOutStream, COutStreamWithCRC> outStream;
652 outStream->Init(realOutStream);
653 realOutStream.Release();
654
655 RINOK(InStream_SeekSet(_stream, item.DataPosition))
656
657 inStream->Init(item.PackSize);
658
659 HRESULT res = S_OK;
660 opRes = NExtract::NOperationResult::kOK;
661
662 if (item.IsCopyMethod())
663 {
664 res = copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps);
665 if (res == S_OK && copyCoder->TotalSize != item.PackSize)
666 res = S_FALSE;
667 }
668 else if (item.IsLh4GroupMethod())
669 {
670 lzhDecoder.Create_if_Empty();
671 // lzhDecoder->FinishMode = true;
672 lzhDecoder->SetDictSize((UInt32)1 << item.GetNumDictBits());
673 res = lzhDecoder->Code(inStream, outStream, cur_Unpacked, lps);
674 if (res == S_OK && lzhDecoder->GetInputProcessedSize() != item.PackSize)
675 res = S_FALSE;
676 }
677 /*
678 else if (item.IsLh1GroupMethod())
679 {
680 if (!lzh1Decoder)
681 {
682 lzh1DecoderSpec = new NCompress::NLzh1::NDecoder::CCoder;
683 lzh1Decoder = lzh1DecoderSpec;
684 }
685 lzh1DecoderSpec->SetDictionary(item.GetNumDictBits());
686 res = lzh1Decoder->Code(inStream, outStream, NULL, &cur_Unpacked, progress);
687 }
688 */
689 else
690 opRes = NExtract::NOperationResult::kUnsupportedMethod;
691
692 if (opRes == NExtract::NOperationResult::kOK)
693 {
694 if (res == S_FALSE)
695 opRes = NExtract::NOperationResult::kDataError;
696 else
697 {
698 RINOK(res)
699 if (outStream->GetCRC() != item.CRC)
700 opRes = NExtract::NOperationResult::kCRCError;
701 }
702 }
703 }
704 RINOK(extractCallback->SetOperationResult(opRes))
705 }
706 return S_OK;
707 COM_TRY_END
708 }
709
710 static const Byte k_Signature[] = { '-', 'l', 'h' };
711
712 REGISTER_ARC_I(
713 "Lzh", "lzh lha", NULL, 6,
714 k_Signature,
715 2,
716 0,
717 IsArc_Lzh)
718
719 }}
720