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