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