1 // CabHandler.cpp
2
3 #include "StdAfx.h"
4
5 // #include <stdio.h>
6
7 #include "../../../../C/Alloc.h"
8 #include "../../../../C/CpuArch.h"
9
10 #include "../../../Common/AutoPtr.h"
11 #include "../../../Common/ComTry.h"
12 #include "../../../Common/IntToString.h"
13 #include "../../../Common/StringConvert.h"
14 #include "../../../Common/UTFConvert.h"
15
16 #include "../../../Windows/PropVariant.h"
17 #include "../../../Windows/TimeUtils.h"
18
19 #include "../../Common/ProgressUtils.h"
20 #include "../../Common/StreamObjects.h"
21 #include "../../Common/StreamUtils.h"
22
23 #include "../../Compress/DeflateDecoder.h"
24 #include "../../Compress/LzxDecoder.h"
25 #include "../../Compress/QuantumDecoder.h"
26
27 #include "../Common/ItemNameUtils.h"
28
29 #include "CabBlockInStream.h"
30 #include "CabHandler.h"
31
32 using namespace NWindows;
33
34 namespace NArchive {
35 namespace NCab {
36
37 // #define CAB_DETAILS
38
39 #ifdef CAB_DETAILS
40 enum
41 {
42 kpidBlockReal = kpidUserDefined
43 };
44 #endif
45
46 static const Byte kProps[] =
47 {
48 kpidPath,
49 kpidSize,
50 kpidMTime,
51 kpidAttrib,
52 kpidMethod,
53 kpidBlock
54 #ifdef CAB_DETAILS
55 ,
56 // kpidBlockReal, // L"BlockReal",
57 kpidOffset,
58 kpidVolume
59 #endif
60 };
61
62 static const Byte kArcProps[] =
63 {
64 kpidTotalPhySize,
65 kpidMethod,
66 // kpidSolid,
67 kpidNumBlocks,
68 kpidNumVolumes,
69 kpidVolumeIndex,
70 kpidId
71 };
72
73 IMP_IInArchive_Props
74 IMP_IInArchive_ArcProps
75
76 static const char * const kMethods[] =
77 {
78 "None"
79 , "MSZip"
80 , "Quantum"
81 , "LZX"
82 };
83
84 static const unsigned kMethodNameBufSize = 32; // "Quantum:255"
85
SetMethodName(char * s,unsigned method,unsigned param)86 static void SetMethodName(char *s, unsigned method, unsigned param)
87 {
88 if (method < Z7_ARRAY_SIZE(kMethods))
89 {
90 s = MyStpCpy(s, kMethods[method]);
91 if (method != NHeader::NMethod::kLZX &&
92 method != NHeader::NMethod::kQuantum)
93 return;
94 *s++ = ':';
95 method = param;
96 }
97 ConvertUInt32ToString(method, s);
98 }
99
Z7_COM7F_IMF(CHandler::GetArchiveProperty (PROPID propID,PROPVARIANT * value))100 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
101 {
102 COM_TRY_BEGIN
103 NCOM::CPropVariant prop;
104 switch (propID)
105 {
106 case kpidMethod:
107 {
108 UInt32 mask = 0;
109 UInt32 params[2] = { 0, 0 };
110 {
111 FOR_VECTOR (v, m_Database.Volumes)
112 {
113 const CRecordVector<CFolder> &folders = m_Database.Volumes[v].Folders;
114 FOR_VECTOR (i, folders)
115 {
116 const CFolder &folder = folders[i];
117 const unsigned method = folder.GetMethod();
118 mask |= ((UInt32)1 << method);
119 if (method == NHeader::NMethod::kLZX ||
120 method == NHeader::NMethod::kQuantum)
121 {
122 const unsigned di = (method == NHeader::NMethod::kQuantum) ? 0 : 1;
123 if (params[di] < folder.MethodMinor)
124 params[di] = folder.MethodMinor;
125 }
126 }
127 }
128 }
129
130 AString s;
131
132 for (unsigned i = 0; i < kNumMethodsMax; i++)
133 {
134 if ((mask & (1 << i)) == 0)
135 continue;
136 s.Add_Space_if_NotEmpty();
137 char temp[kMethodNameBufSize];
138 SetMethodName(temp, i, params[i == NHeader::NMethod::kQuantum ? 0 : 1]);
139 s += temp;
140 }
141
142 prop = s;
143 break;
144 }
145 // case kpidSolid: prop = _database.IsSolid(); break;
146 case kpidNumBlocks:
147 {
148 UInt32 numFolders = 0;
149 FOR_VECTOR (v, m_Database.Volumes)
150 numFolders += m_Database.Volumes[v].Folders.Size();
151 prop = numFolders;
152 break;
153 }
154
155 case kpidTotalPhySize:
156 {
157 if (m_Database.Volumes.Size() > 1)
158 {
159 UInt64 sum = 0;
160 FOR_VECTOR (v, m_Database.Volumes)
161 sum += m_Database.Volumes[v].ArcInfo.Size;
162 prop = sum;
163 }
164 break;
165 }
166
167 case kpidNumVolumes:
168 prop = (UInt32)m_Database.Volumes.Size();
169 break;
170
171 case kpidVolumeIndex:
172 {
173 if (!m_Database.Volumes.IsEmpty())
174 {
175 const CDatabaseEx &db = m_Database.Volumes[0];
176 const CInArcInfo &ai = db.ArcInfo;
177 prop = (UInt32)ai.CabinetNumber;
178 }
179 break;
180 }
181
182 case kpidId:
183 {
184 if (m_Database.Volumes.Size() != 0)
185 {
186 prop = (UInt32)m_Database.Volumes[0].ArcInfo.SetID;
187 }
188 break;
189 }
190
191 case kpidOffset:
192 /*
193 if (m_Database.Volumes.Size() == 1)
194 prop = m_Database.Volumes[0].StartPosition;
195 */
196 prop = _offset;
197 break;
198
199 case kpidPhySize:
200 /*
201 if (m_Database.Volumes.Size() == 1)
202 prop = (UInt64)m_Database.Volumes[0].ArcInfo.Size;
203 */
204 prop = (UInt64)_phySize;
205 break;
206
207 case kpidErrorFlags:
208 {
209 UInt32 v = 0;
210 if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
211 if (_errorInHeaders) v |= kpv_ErrorFlags_HeadersError;
212 if (_unexpectedEnd) v |= kpv_ErrorFlags_UnexpectedEnd;
213 prop = v;
214 break;
215 }
216
217 case kpidError:
218 if (!_errorMessage.IsEmpty())
219 prop = _errorMessage;
220 break;
221
222 case kpidName:
223 {
224 if (m_Database.Volumes.Size() == 1)
225 {
226 const CDatabaseEx &db = m_Database.Volumes[0];
227 const CInArcInfo &ai = db.ArcInfo;
228 if (ai.SetID != 0)
229 {
230 AString s;
231 s.Add_UInt32(ai.SetID);
232 s.Add_Char('_');
233 s.Add_UInt32(ai.CabinetNumber + 1);
234 s += ".cab";
235 prop = s;
236 }
237 /*
238 // that code is incomplete. It gcan give accurate name of volume
239 char s[32];
240 ConvertUInt32ToString(ai.CabinetNumber + 2, s);
241 unsigned len = MyStringLen(s);
242 if (ai.IsThereNext())
243 {
244 AString fn = ai.NextArc.FileName;
245 if (fn.Len() > 4 && StringsAreEqualNoCase_Ascii(fn.RightPtr(4), ".cab"))
246 fn.DeleteFrom(fn.Len() - 4);
247 if (len < fn.Len())
248 {
249 if (strcmp(s, fn.RightPtr(len)) == 0)
250 {
251 AString s2 = fn;
252 s2.DeleteFrom(fn.Len() - len);
253 ConvertUInt32ToString(ai.CabinetNumber + 1, s);
254 s2 += s;
255 s2 += ".cab";
256 prop = GetUnicodeString(s2);
257 }
258 }
259 }
260 */
261 }
262 break;
263 }
264
265 // case kpidShortComment:
266 default: break;
267 }
268 prop.Detach(value);
269 return S_OK;
270 COM_TRY_END
271 }
272
Z7_COM7F_IMF(CHandler::GetProperty (UInt32 index,PROPID propID,PROPVARIANT * value))273 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
274 {
275 COM_TRY_BEGIN
276 NCOM::CPropVariant prop;
277
278 const CMvItem &mvItem = m_Database.Items[index];
279 const CDatabaseEx &db = m_Database.Volumes[mvItem.VolumeIndex];
280 const unsigned itemIndex = mvItem.ItemIndex;
281 const CItem &item = db.Items[itemIndex];
282 switch (propID)
283 {
284 case kpidPath:
285 {
286 UString unicodeName;
287 if (item.IsNameUTF())
288 ConvertUTF8ToUnicode(item.Name, unicodeName);
289 else
290 unicodeName = MultiByteToUnicodeString(item.Name, CP_ACP);
291 prop = (const wchar_t *)NItemName::WinPathToOsPath(unicodeName);
292 break;
293 }
294
295 case kpidIsDir: prop = item.IsDir(); break;
296 case kpidSize: prop = item.Size; break;
297 case kpidAttrib: prop = item.GetWinAttrib(); break;
298
299 case kpidMTime:
300 {
301 PropVariant_SetFrom_DosTime(prop, item.Time);
302 break;
303 }
304
305 case kpidMethod:
306 {
307 const int realFolderIndex = item.GetFolderIndex(db.Folders.Size());
308 if (realFolderIndex >= 0)
309 {
310 const CFolder &folder = db.Folders[(unsigned)realFolderIndex];
311 char s[kMethodNameBufSize];
312 SetMethodName(s, folder.GetMethod(), folder.MethodMinor);
313 prop = s;
314 }
315 break;
316 }
317
318 case kpidBlock: prop.Set_Int32((Int32)m_Database.GetFolderIndex(&mvItem)); break;
319
320 #ifdef CAB_DETAILS
321
322 // case kpidBlockReal: prop = (UInt32)item.FolderIndex; break;
323 case kpidOffset: prop = (UInt32)item.Offset; break;
324 case kpidVolume: prop = (UInt32)mvItem.VolumeIndex; break;
325
326 #endif
327
328 default: break;
329 }
330 prop.Detach(value);
331 return S_OK;
332 COM_TRY_END
333 }
334
Z7_COM7F_IMF(CHandler::Open (IInStream * inStream,const UInt64 * maxCheckStartPosition,IArchiveOpenCallback * callback))335 Z7_COM7F_IMF(CHandler::Open(IInStream *inStream,
336 const UInt64 *maxCheckStartPosition,
337 IArchiveOpenCallback *callback))
338 {
339 COM_TRY_BEGIN
340 Close();
341
342 CInArchive archive;
343 CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
344 if (callback)
345 callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);
346
347 CMyComPtr<IInStream> nextStream = inStream;
348 bool prevChecked = false;
349 UString startVolName;
350 bool startVolName_was_Requested = false;
351 UInt64 numItems = 0;
352 unsigned numTempVolumes = 0;
353 // try
354 {
355 while (nextStream)
356 {
357 CDatabaseEx db;
358 db.Stream = nextStream;
359
360 HRESULT res = archive.Open(db, maxCheckStartPosition);
361
362 _errorInHeaders |= archive.HeaderError;
363 _errorInHeaders |= archive.ErrorInNames;
364 _unexpectedEnd |= archive.UnexpectedEnd;
365
366 if (res == S_OK && !m_Database.Volumes.IsEmpty())
367 {
368 const CArchInfo &lastArc = m_Database.Volumes.Back().ArcInfo;
369 const unsigned cabNumber = db.ArcInfo.CabinetNumber;
370 if (lastArc.SetID != db.ArcInfo.SetID)
371 res = S_FALSE;
372 else if (prevChecked)
373 {
374 if (cabNumber != lastArc.CabinetNumber + 1)
375 res = S_FALSE;
376 }
377 else if (cabNumber >= lastArc.CabinetNumber)
378 res = S_FALSE;
379 else if (numTempVolumes != 0)
380 {
381 const CArchInfo &prevArc = m_Database.Volumes[numTempVolumes - 1].ArcInfo;
382 if (cabNumber != prevArc.CabinetNumber + 1)
383 res = S_FALSE;
384 }
385 }
386
387 if (archive.IsArc || res == S_OK)
388 {
389 _isArc = true;
390 if (m_Database.Volumes.IsEmpty())
391 {
392 _offset = db.StartPosition;
393 _phySize = db.ArcInfo.Size;
394 }
395 }
396
397 if (res == S_OK)
398 {
399 numItems += db.Items.Size();
400 m_Database.Volumes.Insert(prevChecked ? m_Database.Volumes.Size() : numTempVolumes, db);
401 if (!prevChecked && m_Database.Volumes.Size() > 1)
402 {
403 numTempVolumes++;
404 if (db.ArcInfo.CabinetNumber + 1 == m_Database.Volumes[numTempVolumes].ArcInfo.CabinetNumber)
405 numTempVolumes = 0;
406 }
407 }
408 else
409 {
410 if (res != S_FALSE)
411 return res;
412 if (m_Database.Volumes.IsEmpty())
413 return S_FALSE;
414 if (prevChecked)
415 break;
416 prevChecked = true;
417 if (numTempVolumes != 0)
418 {
419 m_Database.Volumes.DeleteFrontal(numTempVolumes);
420 numTempVolumes = 0;
421 }
422 }
423
424 if (callback)
425 {
426 RINOK(callback->SetCompleted(&numItems, NULL))
427 }
428
429 nextStream = NULL;
430
431 for (;;)
432 {
433 const COtherArc *otherArc = NULL;
434
435 if (!prevChecked)
436 {
437 if (numTempVolumes == 0)
438 {
439 const CInArcInfo &ai = m_Database.Volumes[0].ArcInfo;
440 if (ai.IsTherePrev())
441 otherArc = &ai.PrevArc;
442 else
443 prevChecked = true;
444 }
445 else
446 {
447 const CInArcInfo &ai = m_Database.Volumes[numTempVolumes - 1].ArcInfo;
448 if (ai.IsThereNext())
449 otherArc = &ai.NextArc;
450 else
451 {
452 prevChecked = true;
453 m_Database.Volumes.DeleteFrontal(numTempVolumes);
454 numTempVolumes = 0;
455 }
456 }
457 }
458
459 if (!otherArc)
460 {
461 const CInArcInfo &ai = m_Database.Volumes.Back().ArcInfo;
462 if (ai.IsThereNext())
463 otherArc = &ai.NextArc;
464 }
465
466 if (!otherArc)
467 break;
468 if (!openVolumeCallback)
469 break;
470 // printf("\n%s", otherArc->FileName);
471 const UString fullName = MultiByteToUnicodeString(otherArc->FileName, CP_ACP);
472
473 if (!startVolName_was_Requested)
474 {
475 // some "bad" cab example can contain the link to itself.
476 startVolName_was_Requested = true;
477 {
478 NCOM::CPropVariant prop;
479 RINOK(openVolumeCallback->GetProperty(kpidName, &prop))
480 if (prop.vt == VT_BSTR)
481 startVolName = prop.bstrVal;
482 }
483 if (fullName == startVolName)
484 break;
485 }
486
487 const HRESULT result = openVolumeCallback->GetStream(fullName, &nextStream);
488 if (result == S_OK)
489 break;
490 if (result != S_FALSE)
491 return result;
492
493 if (!_errorMessage.IsEmpty())
494 _errorMessage.Add_LF();
495 _errorMessage += "Can't open volume: ";
496 _errorMessage += fullName;
497
498 if (prevChecked)
499 break;
500 prevChecked = true;
501 if (numTempVolumes != 0)
502 {
503 m_Database.Volumes.DeleteFrontal(numTempVolumes);
504 numTempVolumes = 0;
505 }
506 }
507
508 } // read nextStream iteration
509
510 if (numTempVolumes != 0)
511 {
512 m_Database.Volumes.DeleteFrontal(numTempVolumes);
513 numTempVolumes = 0;
514 }
515 if (m_Database.Volumes.IsEmpty())
516 return S_FALSE;
517 else
518 {
519 m_Database.FillSortAndShrink();
520 if (!m_Database.Check())
521 return S_FALSE;
522 }
523 }
524 COM_TRY_END
525 return S_OK;
526 }
527
Z7_COM7F_IMF(CHandler::Close ())528 Z7_COM7F_IMF(CHandler::Close())
529 {
530 _errorMessage.Empty();
531 _isArc = false;
532 _errorInHeaders = false;
533 _unexpectedEnd = false;
534 // _mainVolIndex = -1;
535 _phySize = 0;
536 _offset = 0;
537
538 m_Database.Clear();
539 return S_OK;
540 }
541
542
543 Z7_CLASS_IMP_NOQIB_1(
544 CFolderOutStream
545 , ISequentialOutStream
546 )
547 bool m_TestMode;
548 bool TempBufMode;
549 bool m_IsOk;
550 bool m_FileIsOpen;
551
552 const CMvDatabaseEx *m_Database;
553 const CRecordVector<bool> *m_ExtractStatuses;
554
555 Byte *TempBuf;
556 UInt32 TempBufSize;
557 UInt32 TempBufWritten;
558 unsigned NumIdenticalFiles;
559
560 unsigned m_StartIndex;
561 unsigned m_CurrentIndex;
562
563 UInt32 m_RemainFileSize;
564
565 UInt64 m_FolderSize;
566 UInt64 m_PosInFolder;
567
568 CMyComPtr<IArchiveExtractCallback> m_ExtractCallback;
569 CMyComPtr<ISequentialOutStream> m_RealOutStream;
570
FreeTempBuf()571 void FreeTempBuf()
572 {
573 ::MyFree(TempBuf);
574 TempBuf = NULL;
575 }
576
577 HRESULT OpenFile();
578 HRESULT CloseFileWithResOp(Int32 resOp);
579 HRESULT CloseFile();
580 public:
581 HRESULT WriteEmptyFiles();
582
583 CFolderOutStream(): TempBuf(NULL) {}
584 ~CFolderOutStream() { FreeTempBuf(); }
585 void Init(
586 const CMvDatabaseEx *database,
587 const CRecordVector<bool> *extractStatuses,
588 unsigned startIndex,
589 UInt64 folderSize,
590 IArchiveExtractCallback *extractCallback,
591 bool testMode);
592 HRESULT FlushCorrupted(unsigned folderIndex);
593 HRESULT Unsupported();
594
595 bool NeedMoreWrite() const { return (m_FolderSize > m_PosInFolder); }
596 UInt64 GetRemain() const { return m_FolderSize - m_PosInFolder; }
597 UInt64 GetPosInFolder() const { return m_PosInFolder; }
598 };
599
600
601 void CFolderOutStream::Init(
602 const CMvDatabaseEx *database,
603 const CRecordVector<bool> *extractStatuses,
604 unsigned startIndex,
605 UInt64 folderSize,
606 IArchiveExtractCallback *extractCallback,
607 bool testMode)
608 {
609 m_Database = database;
610 m_ExtractStatuses = extractStatuses;
611 m_StartIndex = startIndex;
612 m_FolderSize = folderSize;
613
614 m_ExtractCallback = extractCallback;
615 m_TestMode = testMode;
616
617 m_CurrentIndex = 0;
618 m_PosInFolder = 0;
619 m_FileIsOpen = false;
620 m_IsOk = true;
621 TempBufMode = false;
622 NumIdenticalFiles = 0;
623 }
624
625
626 HRESULT CFolderOutStream::CloseFileWithResOp(Int32 resOp)
627 {
628 m_RealOutStream.Release();
629 m_FileIsOpen = false;
630 NumIdenticalFiles--;
631 return m_ExtractCallback->SetOperationResult(resOp);
632 }
633
634
635 HRESULT CFolderOutStream::CloseFile()
636 {
637 return CloseFileWithResOp(m_IsOk ?
638 NExtract::NOperationResult::kOK:
639 NExtract::NOperationResult::kDataError);
640 }
641
642
643 HRESULT CFolderOutStream::OpenFile()
644 {
645 if (NumIdenticalFiles == 0)
646 {
647 const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex];
648 const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];
649 unsigned numExtractItems = 0;
650 unsigned curIndex;
651
652 for (curIndex = m_CurrentIndex; curIndex < m_ExtractStatuses->Size(); curIndex++)
653 {
654 const CMvItem &mvItem2 = m_Database->Items[m_StartIndex + curIndex];
655 const CItem &item2 = m_Database->Volumes[mvItem2.VolumeIndex].Items[mvItem2.ItemIndex];
656 if (item.Offset != item2.Offset ||
657 item.Size != item2.Size ||
658 item.Size == 0)
659 break;
660 if (!m_TestMode && (*m_ExtractStatuses)[curIndex])
661 numExtractItems++;
662 }
663
664 NumIdenticalFiles = (curIndex - m_CurrentIndex);
665 if (NumIdenticalFiles == 0)
666 NumIdenticalFiles = 1;
667 TempBufMode = false;
668
669 if (numExtractItems > 1)
670 {
671 if (!TempBuf || item.Size > TempBufSize)
672 {
673 FreeTempBuf();
674 TempBuf = (Byte *)MyAlloc(item.Size);
675 TempBufSize = item.Size;
676 if (!TempBuf)
677 return E_OUTOFMEMORY;
678 }
679 TempBufMode = true;
680 TempBufWritten = 0;
681 }
682 else if (numExtractItems == 1)
683 {
684 while (NumIdenticalFiles && !(*m_ExtractStatuses)[m_CurrentIndex])
685 {
686 CMyComPtr<ISequentialOutStream> stream;
687 RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &stream, NExtract::NAskMode::kSkip))
688 if (stream)
689 return E_FAIL;
690 RINOK(m_ExtractCallback->PrepareOperation(NExtract::NAskMode::kSkip))
691 m_CurrentIndex++;
692 m_FileIsOpen = true;
693 CloseFile();
694 }
695 }
696 }
697
698 Int32 askMode = (*m_ExtractStatuses)[m_CurrentIndex] ? m_TestMode ?
699 NExtract::NAskMode::kTest :
700 NExtract::NAskMode::kExtract :
701 NExtract::NAskMode::kSkip;
702 RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &m_RealOutStream, askMode))
703 if (!m_RealOutStream && !m_TestMode)
704 askMode = NExtract::NAskMode::kSkip;
705 return m_ExtractCallback->PrepareOperation(askMode);
706 }
707
708
709 HRESULT CFolderOutStream::WriteEmptyFiles()
710 {
711 if (m_FileIsOpen)
712 return S_OK;
713 for (; m_CurrentIndex < m_ExtractStatuses->Size(); m_CurrentIndex++)
714 {
715 const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex];
716 const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];
717 const UInt64 fileSize = item.Size;
718 if (fileSize != 0)
719 return S_OK;
720 const HRESULT result = OpenFile();
721 m_RealOutStream.Release();
722 RINOK(result)
723 RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
724 }
725 return S_OK;
726 }
727
728
729 Z7_COM7F_IMF(CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize))
730 {
731 // (data == NULL) means Error_Data for solid folder flushing
732 COM_TRY_BEGIN
733
734 UInt32 realProcessed = 0;
735 if (processedSize)
736 *processedSize = 0;
737
738 while (size != 0)
739 {
740 if (m_FileIsOpen)
741 {
742 UInt32 numBytesToWrite = MyMin(m_RemainFileSize, size);
743 HRESULT res = S_OK;
744 if (numBytesToWrite != 0)
745 {
746 if (!data)
747 m_IsOk = false;
748
749 if (m_RealOutStream)
750 {
751 UInt32 processedSizeLocal = 0;
752 // 18.01 : we don't want ZEROs instead of missing data
753 if (data)
754 res = m_RealOutStream->Write((const Byte *)data, numBytesToWrite, &processedSizeLocal);
755 else
756 processedSizeLocal = numBytesToWrite;
757 numBytesToWrite = processedSizeLocal;
758 }
759
760 if (TempBufMode && TempBuf)
761 {
762 if (data)
763 {
764 memcpy(TempBuf + TempBufWritten, data, numBytesToWrite);
765 TempBufWritten += numBytesToWrite;
766 }
767 }
768 }
769 realProcessed += numBytesToWrite;
770 if (processedSize)
771 *processedSize = realProcessed;
772 if (data)
773 data = (const void *)((const Byte *)data + numBytesToWrite);
774 size -= numBytesToWrite;
775 m_RemainFileSize -= numBytesToWrite;
776 m_PosInFolder += numBytesToWrite;
777
778 if (res != S_OK)
779 return res;
780
781 if (m_RemainFileSize == 0)
782 {
783 RINOK(CloseFile())
784
785 while (NumIdenticalFiles)
786 {
787 HRESULT result = OpenFile();
788 m_FileIsOpen = true;
789 m_CurrentIndex++;
790 if (result == S_OK && m_RealOutStream && TempBuf)
791 result = WriteStream(m_RealOutStream, TempBuf, TempBufWritten);
792
793 if (!TempBuf && TempBufMode && m_RealOutStream)
794 {
795 RINOK(CloseFileWithResOp(NExtract::NOperationResult::kUnsupportedMethod))
796 }
797 else
798 {
799 RINOK(CloseFile())
800 }
801
802 RINOK(result)
803 }
804
805 TempBufMode = false;
806 }
807
808 if (realProcessed > 0)
809 break; // with this break this function works as Write-Part
810 }
811 else
812 {
813 if (m_CurrentIndex >= m_ExtractStatuses->Size())
814 {
815 // we ignore extra data;
816 realProcessed += size;
817 if (processedSize)
818 *processedSize = realProcessed;
819 m_PosInFolder += size;
820 return S_OK;
821 // return E_FAIL;
822 }
823
824 const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex];
825 const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];
826
827 m_RemainFileSize = item.Size;
828
829 const UInt32 fileOffset = item.Offset;
830
831 if (fileOffset < m_PosInFolder)
832 return E_FAIL;
833
834 if (fileOffset > m_PosInFolder)
835 {
836 const UInt32 numBytesToWrite = MyMin(fileOffset - (UInt32)m_PosInFolder, size);
837 realProcessed += numBytesToWrite;
838 if (processedSize)
839 *processedSize = realProcessed;
840 if (data)
841 data = (const void *)((const Byte *)data + numBytesToWrite);
842 size -= numBytesToWrite;
843 m_PosInFolder += numBytesToWrite;
844 }
845
846 if (fileOffset == m_PosInFolder)
847 {
848 RINOK(OpenFile())
849 m_FileIsOpen = true;
850 m_CurrentIndex++;
851 m_IsOk = true;
852 }
853 }
854 }
855
856 return WriteEmptyFiles();
857
858 COM_TRY_END
859 }
860
861
862 HRESULT CFolderOutStream::FlushCorrupted(unsigned folderIndex)
863 {
864 if (!NeedMoreWrite())
865 {
866 CMyComPtr<IArchiveExtractCallbackMessage2> callbackMessage;
867 m_ExtractCallback.QueryInterface(IID_IArchiveExtractCallbackMessage2, &callbackMessage);
868 if (callbackMessage)
869 {
870 RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, NExtract::NOperationResult::kDataError))
871 }
872 return S_OK;
873 }
874
875 for (;;)
876 {
877 if (!NeedMoreWrite())
878 return S_OK;
879 const UInt64 remain = GetRemain();
880 UInt32 size = (UInt32)1 << 20;
881 if (size > remain)
882 size = (UInt32)remain;
883 UInt32 processedSizeLocal = 0;
884 RINOK(Write(NULL, size, &processedSizeLocal))
885 }
886 }
887
888
889 HRESULT CFolderOutStream::Unsupported()
890 {
891 while (m_CurrentIndex < m_ExtractStatuses->Size())
892 {
893 const HRESULT result = OpenFile();
894 if (result != S_FALSE && result != S_OK)
895 return result;
896 m_RealOutStream.Release();
897 RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod))
898 m_CurrentIndex++;
899 }
900 return S_OK;
901 }
902
903
904 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
905 Int32 testMode, IArchiveExtractCallback *extractCallback))
906 {
907 COM_TRY_BEGIN
908 const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
909 if (allFilesMode)
910 numItems = m_Database.Items.Size();
911 if (numItems == 0)
912 return S_OK;
913 UInt64 totalUnPacked = 0;
914
915 UInt32 i;
916 int lastFolder = -2;
917 UInt64 lastFolderSize = 0;
918
919 for (i = 0; i < numItems; i++)
920 {
921 const unsigned index = allFilesMode ? i : indices[i];
922 const CMvItem &mvItem = m_Database.Items[index];
923 const CItem &item = m_Database.Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex];
924 if (item.IsDir())
925 continue;
926 const int folderIndex = m_Database.GetFolderIndex(&mvItem);
927 if (folderIndex != lastFolder)
928 totalUnPacked += lastFolderSize;
929 lastFolder = folderIndex;
930 lastFolderSize = item.GetEndOffset();
931 }
932
933 totalUnPacked += lastFolderSize;
934 RINOK(extractCallback->SetTotal(totalUnPacked))
935
936 CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
937 lps->Init(extractCallback, false);
938
939 CMyComPtr2<ICompressCoder, NCompress::NDeflate::NDecoder::CCOMCoder> deflateDecoder;
940 CMyUniquePtr<NCompress::NLzx::CDecoder> lzxDecoder;
941 CMyUniquePtr<NCompress::NQuantum::CDecoder> quantumDecoder;
942
943 CBlockPackData blockPackData;
944 if (!blockPackData.Create())
945 return E_OUTOFMEMORY;
946
947 CMyComPtr2_Create<ISequentialInStream, CBufInStream> inBufStream;
948
949 CRecordVector<bool> extractStatuses;
950
951 totalUnPacked = 0;
952 UInt64 totalPacked = 0;
953
954 for (i = 0;;)
955 {
956 lps->OutSize = totalUnPacked;
957 lps->InSize = totalPacked;
958 RINOK(lps->SetCur())
959 if (i >= numItems)
960 break;
961
962 const unsigned index = allFilesMode ? i : indices[i];
963
964 const CMvItem &mvItem = m_Database.Items[index];
965 const CDatabaseEx &db = m_Database.Volumes[mvItem.VolumeIndex];
966 const unsigned itemIndex = mvItem.ItemIndex;
967 const CItem &item = db.Items[itemIndex];
968
969 i++;
970 if (item.IsDir())
971 {
972 const Int32 askMode = testMode ?
973 NExtract::NAskMode::kTest :
974 NExtract::NAskMode::kExtract;
975 CMyComPtr<ISequentialOutStream> realOutStream;
976 RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
977 RINOK(extractCallback->PrepareOperation(askMode))
978 realOutStream.Release();
979 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
980 continue;
981 }
982
983 const int folderIndex = m_Database.GetFolderIndex(&mvItem);
984
985 if (folderIndex < 0)
986 {
987 // If we need previous archive
988 const Int32 askMode= testMode ?
989 NExtract::NAskMode::kTest :
990 NExtract::NAskMode::kExtract;
991 CMyComPtr<ISequentialOutStream> realOutStream;
992 RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
993 RINOK(extractCallback->PrepareOperation(askMode))
994 realOutStream.Release();
995 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kDataError))
996 continue;
997 }
998
999 const unsigned startIndex2 = m_Database.FolderStartFileIndex[(unsigned)folderIndex];
1000 unsigned startIndex = startIndex2;
1001 extractStatuses.Clear();
1002 for (; startIndex < index; startIndex++)
1003 extractStatuses.Add(false);
1004 extractStatuses.Add(true);
1005 startIndex++;
1006 UInt64 curUnpack = item.GetEndOffset();
1007
1008 for (; i < numItems; i++)
1009 {
1010 const unsigned indexNext = allFilesMode ? i : indices[i];
1011 const CMvItem &mvItem2 = m_Database.Items[indexNext];
1012 const CItem &item2 = m_Database.Volumes[mvItem2.VolumeIndex].Items[mvItem2.ItemIndex];
1013 if (item2.IsDir())
1014 continue;
1015 const int newFolderIndex = m_Database.GetFolderIndex(&mvItem2);
1016
1017 if (newFolderIndex != folderIndex)
1018 break;
1019 for (; startIndex < indexNext; startIndex++)
1020 extractStatuses.Add(false);
1021 extractStatuses.Add(true);
1022 startIndex++;
1023 curUnpack = item2.GetEndOffset();
1024 }
1025
1026 CMyComPtr2_Create<ISequentialOutStream, CFolderOutStream> cabFolderOutStream;
1027
1028 const int folderIndex2 = item.GetFolderIndex(db.Folders.Size());
1029 if (folderIndex2 < 0)
1030 return E_FAIL;
1031 const CFolder &folder = db.Folders[(unsigned)folderIndex2];
1032
1033 cabFolderOutStream->Init(&m_Database, &extractStatuses, startIndex2,
1034 curUnpack, extractCallback, testMode != 0);
1035
1036 HRESULT res = S_OK;
1037
1038 switch (folder.GetMethod())
1039 {
1040 case NHeader::NMethod::kNone:
1041 break;
1042
1043 case NHeader::NMethod::kMSZip:
1044 deflateDecoder.Create_if_Empty();
1045 break;
1046
1047 case NHeader::NMethod::kLZX:
1048 lzxDecoder.Create_if_Empty();
1049 res = lzxDecoder->Set_DictBits_and_Alloc(folder.MethodMinor);
1050 break;
1051
1052 case NHeader::NMethod::kQuantum:
1053 quantumDecoder.Create_if_Empty();
1054 res = quantumDecoder->SetParams(folder.MethodMinor);
1055 break;
1056
1057 default:
1058 res = E_INVALIDARG;
1059 break;
1060 }
1061
1062 if (res == E_INVALIDARG)
1063 {
1064 RINOK(cabFolderOutStream->Unsupported())
1065 totalUnPacked += curUnpack;
1066 continue;
1067 }
1068 RINOK(res)
1069
1070 {
1071 unsigned volIndex = mvItem.VolumeIndex;
1072 int locFolderIndex = item.GetFolderIndex(db.Folders.Size());
1073 bool keepHistory = false;
1074 bool keepInputBuffer = false;
1075 bool thereWasNotAlignedChunk = false;
1076
1077 for (UInt32 bl = 0; cabFolderOutStream->NeedMoreWrite();)
1078 {
1079 if (volIndex >= m_Database.Volumes.Size())
1080 {
1081 res = S_FALSE;
1082 break;
1083 }
1084
1085 const CDatabaseEx &db2 = m_Database.Volumes[volIndex];
1086 if (locFolderIndex < 0)
1087 return E_FAIL;
1088 const CFolder &folder2 = db2.Folders[(unsigned)locFolderIndex];
1089
1090 if (bl == 0)
1091 {
1092 RINOK(InStream_SeekSet(db2.Stream, db2.StartPosition + folder2.DataStart))
1093 }
1094
1095 if (bl == folder2.NumDataBlocks)
1096 {
1097 /*
1098 CFolder::NumDataBlocks (CFFOLDER::cCFData in CAB specification) is 16-bit.
1099 But there are some big CAB archives from MS that contain more
1100 than (0xFFFF) CFDATA blocks in folder.
1101 Old cab extracting software can show error (or ask next volume)
1102 but cab extracting library in new Windows ignores this error.
1103 15.00 : We also try to ignore such error, if archive is not multi-volume.
1104 */
1105 if (m_Database.Volumes.Size() > 1)
1106 {
1107 volIndex++;
1108 locFolderIndex = 0;
1109 bl = 0;
1110 continue;
1111 }
1112 }
1113
1114 bl++;
1115
1116 if (!keepInputBuffer)
1117 blockPackData.InitForNewBlock();
1118
1119 UInt32 packSize, unpackSize;
1120 res = blockPackData.Read(db2.Stream, db2.ArcInfo.GetDataBlockReserveSize(), packSize, unpackSize);
1121 if (res == S_FALSE)
1122 break;
1123 RINOK(res)
1124 keepInputBuffer = (unpackSize == 0);
1125 if (keepInputBuffer)
1126 continue;
1127
1128 const UInt64 totalUnPacked2 = totalUnPacked + cabFolderOutStream->GetPosInFolder();
1129 totalPacked += packSize;
1130
1131 if (totalUnPacked2 - lps->OutSize >= (1 << 26)
1132 || totalPacked - lps->InSize >= (1 << 24))
1133 {
1134 lps->OutSize = totalUnPacked2;
1135 lps->InSize = totalPacked;
1136 RINOK(lps->SetCur())
1137 }
1138
1139 const unsigned kBlockSizeMax = 1u << 15;
1140
1141 if (unpackSize != kBlockSizeMax)
1142 {
1143 if (unpackSize > kBlockSizeMax || thereWasNotAlignedChunk)
1144 {
1145 res = S_FALSE;
1146 break;
1147 }
1148 thereWasNotAlignedChunk = true;
1149 }
1150
1151 /* We don't try to reduce last block.
1152 Note that LZX converts data with x86 filter.
1153 and filter needs larger input data than reduced size.
1154 It's simpler to decompress full chunk here.
1155 also we need full block for quantum for more integrity checks */
1156
1157 const UInt64 unpackSize64 = unpackSize;
1158 const UInt32 packSizeChunk = blockPackData.GetPackSize();
1159
1160 switch (folder2.GetMethod())
1161 {
1162 case NHeader::NMethod::kNone:
1163 if (unpackSize != packSizeChunk)
1164 {
1165 res = S_FALSE;
1166 break;
1167 }
1168 res = WriteStream(cabFolderOutStream, blockPackData.GetData(), packSizeChunk);
1169 break;
1170
1171 case NHeader::NMethod::kMSZip:
1172 {
1173 /* v24.00 : fixed : we check 2-bytes MSZIP signature only
1174 when block was constructed from all volumes. */
1175 const Byte *packData = blockPackData.GetData();
1176 if (unpackSize > (1u << 15) + 12 /* MSZIP specification */
1177 || packSizeChunk < 2 || GetUi16(packData) != 0x4b43)
1178 {
1179 res = S_FALSE;
1180 break;
1181 }
1182 const UInt32 packSizeChunk_2 = packSizeChunk - 2;
1183 inBufStream->Init(packData + 2, packSizeChunk_2);
1184
1185 deflateDecoder->Set_KeepHistory(keepHistory);
1186 /* v9.31: now we follow MSZIP specification that requires
1187 to finish deflate stream at the end of each block.
1188 But PyCabArc can create CAB archives that don't have
1189 finish marker at the end of block.
1190 Cabarc probably ignores such errors in cab archives.
1191 Maybe we also should ignore such error?
1192 Or we should extract full file and show the warning? */
1193 deflateDecoder->Set_NeedFinishInput(true);
1194 res = deflateDecoder.Interface()->Code(inBufStream, cabFolderOutStream, NULL, &unpackSize64, NULL);
1195 if (res == S_OK)
1196 {
1197 if (!deflateDecoder->IsFinished())
1198 res = S_FALSE;
1199 if (!deflateDecoder->IsFinalBlock())
1200 res = S_FALSE;
1201 if (deflateDecoder->GetInputProcessedSize() != packSizeChunk_2)
1202 res = S_FALSE;
1203 }
1204 break;
1205 }
1206
1207 case NHeader::NMethod::kLZX:
1208 lzxDecoder->Set_KeepHistory(keepHistory);
1209 lzxDecoder->Set_KeepHistoryForNext(true);
1210 res = lzxDecoder->Code_WithExceedReadWrite(blockPackData.GetData(),
1211 packSizeChunk, unpackSize);
1212 if (res == S_OK)
1213 res = WriteStream(cabFolderOutStream,
1214 lzxDecoder->GetUnpackData(),
1215 lzxDecoder->GetUnpackSize());
1216 break;
1217
1218 case NHeader::NMethod::kQuantum:
1219 {
1220 res = quantumDecoder->Code(blockPackData.GetData(),
1221 packSizeChunk, unpackSize, keepHistory);
1222 if (res == S_OK)
1223 {
1224 const UInt32 num = unpackSize;
1225 res = WriteStream(cabFolderOutStream,
1226 quantumDecoder->GetDataPtr() - num, num);
1227 }
1228 break;
1229 }
1230 default:
1231 // it's unexpected case, because we checked method before
1232 // res = E_NOTIMPL;
1233 break;
1234 }
1235
1236 if (res != S_OK)
1237 {
1238 if (res != S_FALSE)
1239 return res;
1240 break;
1241 }
1242
1243 keepHistory = true;
1244 }
1245
1246 if (res == S_OK)
1247 {
1248 RINOK(cabFolderOutStream->WriteEmptyFiles())
1249 }
1250 }
1251
1252 if (res != S_OK || cabFolderOutStream->NeedMoreWrite())
1253 {
1254 RINOK(cabFolderOutStream->FlushCorrupted((unsigned)folderIndex2))
1255 }
1256
1257 totalUnPacked += curUnpack;
1258 }
1259
1260 return S_OK;
1261
1262 COM_TRY_END
1263 }
1264
1265
1266 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
1267 {
1268 *numItems = m_Database.Items.Size();
1269 return S_OK;
1270 }
1271
1272 }}
1273