xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/Cab/CabHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
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