1 // Extract.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../../C/Sort.h"
6 
7 #include "../../../Common/StringConvert.h"
8 
9 #include "../../../Windows/FileDir.h"
10 #include "../../../Windows/FileName.h"
11 #include "../../../Windows/ErrorMsg.h"
12 #include "../../../Windows/PropVariant.h"
13 #include "../../../Windows/PropVariantConv.h"
14 
15 #include "../Common/ExtractingFilePath.h"
16 #include "../Common/HashCalc.h"
17 
18 #include "Extract.h"
19 #include "SetProperties.h"
20 
21 using namespace NWindows;
22 using namespace NFile;
23 using namespace NDir;
24 
25 
SetErrorMessage(const char *message, const FString &path, HRESULT errorCode, UString &s)26 static void SetErrorMessage(const char *message,
27     const FString &path, HRESULT errorCode,
28     UString &s)
29 {
30   s = message;
31   s += " : ";
32   s += NError::MyFormatMessage(errorCode);
33   s += " : ";
34   s += fs2us(path);
35 }
36 
37 
DecompressArchive( CCodecs *codecs, const CArchiveLink &arcLink, UInt64 packSize, const NWildcard::CCensorNode &wildcardCensor, const CExtractOptions &options, bool calcCrc, IExtractCallbackUI *callback, IFolderArchiveExtractCallback *callbackFAE, CArchiveExtractCallback *ecs, UString &errorMessage, UInt64 &stdInProcessed)38 static HRESULT DecompressArchive(
39     CCodecs *codecs,
40     const CArchiveLink &arcLink,
41     UInt64 packSize,
42     const NWildcard::CCensorNode &wildcardCensor,
43     const CExtractOptions &options,
44     bool calcCrc,
45     IExtractCallbackUI *callback,
46     IFolderArchiveExtractCallback *callbackFAE,
47     CArchiveExtractCallback *ecs,
48     UString &errorMessage,
49     UInt64 &stdInProcessed)
50 {
51   const CArc &arc = arcLink.Arcs.Back();
52   stdInProcessed = 0;
53   IInArchive *archive = arc.Archive;
54   CRecordVector<UInt32> realIndices;
55 
56   UStringVector removePathParts;
57 
58   FString outDir = options.OutputDir;
59   UString replaceName = arc.DefaultName;
60 
61   if (arcLink.Arcs.Size() > 1)
62   {
63     // Most "pe" archives have same name of archive subfile "[0]" or ".rsrc_1".
64     // So it extracts different archives to one folder.
65     // We will use top level archive name
66     const CArc &arc0 = arcLink.Arcs[0];
67     if (arc0.FormatIndex >= 0 && StringsAreEqualNoCase_Ascii(codecs->Formats[(unsigned)arc0.FormatIndex].Name, "pe"))
68       replaceName = arc0.DefaultName;
69   }
70 
71   outDir.Replace(FString("*"), us2fs(Get_Correct_FsFile_Name(replaceName)));
72 
73   bool elimIsPossible = false;
74   UString elimPrefix; // only pure name without dir delimiter
75   FString outDirReduced = outDir;
76 
77   if (options.ElimDup.Val && options.PathMode != NExtract::NPathMode::kAbsPaths)
78   {
79     UString dirPrefix;
80     SplitPathToParts_Smart(fs2us(outDir), dirPrefix, elimPrefix);
81     if (!elimPrefix.IsEmpty())
82     {
83       if (IsPathSepar(elimPrefix.Back()))
84         elimPrefix.DeleteBack();
85       if (!elimPrefix.IsEmpty())
86       {
87         outDirReduced = us2fs(dirPrefix);
88         elimIsPossible = true;
89       }
90     }
91   }
92 
93   const bool allFilesAreAllowed = wildcardCensor.AreAllAllowed();
94 
95   if (!options.StdInMode)
96   {
97     UInt32 numItems;
98     RINOK(archive->GetNumberOfItems(&numItems))
99 
100     CReadArcItem item;
101 
102     for (UInt32 i = 0; i < numItems; i++)
103     {
104       if (elimIsPossible
105           || !allFilesAreAllowed
106           || options.ExcludeDirItems
107           || options.ExcludeFileItems)
108       {
109         RINOK(arc.GetItem(i, item))
110         if (item.IsDir ? options.ExcludeDirItems : options.ExcludeFileItems)
111           continue;
112       }
113       else
114       {
115         #ifdef SUPPORT_ALT_STREAMS
116         item.IsAltStream = false;
117         if (!options.NtOptions.AltStreams.Val && arc.Ask_AltStream)
118         {
119           RINOK(Archive_IsItem_AltStream(arc.Archive, i, item.IsAltStream))
120         }
121         #endif
122       }
123 
124       #ifdef SUPPORT_ALT_STREAMS
125       if (!options.NtOptions.AltStreams.Val && item.IsAltStream)
126         continue;
127       #endif
128 
129       if (elimIsPossible)
130       {
131         const UString &s =
132           #ifdef SUPPORT_ALT_STREAMS
133             item.MainPath;
134           #else
135             item.Path;
136           #endif
137         if (!IsPath1PrefixedByPath2(s, elimPrefix))
138           elimIsPossible = false;
139         else
140         {
141           wchar_t c = s[elimPrefix.Len()];
142           if (c == 0)
143           {
144             if (!item.MainIsDir)
145               elimIsPossible = false;
146           }
147           else if (!IsPathSepar(c))
148             elimIsPossible = false;
149         }
150       }
151 
152       if (!allFilesAreAllowed)
153       {
154         if (!CensorNode_CheckPath(wildcardCensor, item))
155           continue;
156       }
157 
158       realIndices.Add(i);
159     }
160 
161     if (realIndices.Size() == 0)
162     {
163       callback->ThereAreNoFiles();
164       return callback->ExtractResult(S_OK);
165     }
166   }
167 
168   if (elimIsPossible)
169   {
170     removePathParts.Add(elimPrefix);
171     // outDir = outDirReduced;
172   }
173 
174   #ifdef _WIN32
175   // GetCorrectFullFsPath doesn't like "..".
176   // outDir.TrimRight();
177   // outDir = GetCorrectFullFsPath(outDir);
178   #endif
179 
180   if (outDir.IsEmpty())
181     outDir = "." STRING_PATH_SEPARATOR;
182   /*
183   #ifdef _WIN32
184   else if (NName::IsAltPathPrefix(outDir)) {}
185   #endif
186   */
187   else if (!CreateComplexDir(outDir))
188   {
189     const HRESULT res = GetLastError_noZero_HRESULT();
190     SetErrorMessage("Cannot create output directory", outDir, res, errorMessage);
191     return res;
192   }
193 
194   ecs->Init(
195       options.NtOptions,
196       options.StdInMode ? &wildcardCensor : NULL,
197       &arc,
198       callbackFAE,
199       options.StdOutMode, options.TestMode,
200       outDir,
201       removePathParts, false,
202       packSize);
203 
204 
205   #ifdef SUPPORT_LINKS
206 
207   if (!options.StdInMode &&
208       !options.TestMode &&
209       options.NtOptions.HardLinks.Val)
210   {
211     RINOK(ecs->PrepareHardLinks(&realIndices))
212   }
213 
214   #endif
215 
216 
217   HRESULT result;
218   const Int32 testMode = (options.TestMode && !calcCrc) ? 1: 0;
219 
220   CArchiveExtractCallback_Closer ecsCloser(ecs);
221 
222   if (options.StdInMode)
223   {
224     result = archive->Extract(NULL, (UInt32)(Int32)-1, testMode, ecs);
225     NCOM::CPropVariant prop;
226     if (archive->GetArchiveProperty(kpidPhySize, &prop) == S_OK)
227       ConvertPropVariantToUInt64(prop, stdInProcessed);
228   }
229   else
230     result = archive->Extract(&realIndices.Front(), realIndices.Size(), testMode, ecs);
231 
232   const HRESULT res2 = ecsCloser.Close();
233   if (result == S_OK)
234     result = res2;
235 
236   return callback->ExtractResult(result);
237 }
238 
239 /* v9.31: BUG was fixed:
240    Sorted list for file paths was sorted with case insensitive compare function.
241    But FindInSorted function did binary search via case sensitive compare function */
242 
243 int Find_FileName_InSortedVector(const UStringVector &fileNames, const UString &name);
Find_FileName_InSortedVector(const UStringVector &fileNames, const UString &name)244 int Find_FileName_InSortedVector(const UStringVector &fileNames, const UString &name)
245 {
246   unsigned left = 0, right = fileNames.Size();
247   while (left != right)
248   {
249     const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2);
250     const UString &midVal = fileNames[mid];
251     const int comp = CompareFileNames(name, midVal);
252     if (comp == 0)
253       return (int)mid;
254     if (comp < 0)
255       right = mid;
256     else
257       left = mid + 1;
258   }
259   return -1;
260 }
261 
262 
263 
Extract( CCodecs *codecs, const CObjectVector<COpenType> &types, const CIntVector &excludedFormats, UStringVector &arcPaths, UStringVector &arcPathsFull, const NWildcard::CCensorNode &wildcardCensor, const CExtractOptions &options, IOpenCallbackUI *openCallback, IExtractCallbackUI *extractCallback, IFolderArchiveExtractCallback *faeCallback, IHashCalc *hash, UString &errorMessage, CDecompressStat &st)264 HRESULT Extract(
265     // DECL_EXTERNAL_CODECS_LOC_VARS
266     CCodecs *codecs,
267     const CObjectVector<COpenType> &types,
268     const CIntVector &excludedFormats,
269     UStringVector &arcPaths, UStringVector &arcPathsFull,
270     const NWildcard::CCensorNode &wildcardCensor,
271     const CExtractOptions &options,
272     IOpenCallbackUI *openCallback,
273     IExtractCallbackUI *extractCallback,
274     IFolderArchiveExtractCallback *faeCallback,
275     #ifndef Z7_SFX
276     IHashCalc *hash,
277     #endif
278     UString &errorMessage,
279     CDecompressStat &st)
280 {
281   st.Clear();
282   UInt64 totalPackSize = 0;
283   CRecordVector<UInt64> arcSizes;
284 
285   unsigned numArcs = options.StdInMode ? 1 : arcPaths.Size();
286 
287   unsigned i;
288 
289   for (i = 0; i < numArcs; i++)
290   {
291     NFind::CFileInfo fi;
292     fi.Size = 0;
293     if (!options.StdInMode)
294     {
295       const FString arcPath = us2fs(arcPaths[i]);
296       if (!fi.Find_FollowLink(arcPath))
297       {
298         const HRESULT errorCode = GetLastError_noZero_HRESULT();
299         SetErrorMessage("Cannot find archive file", arcPath, errorCode, errorMessage);
300         return errorCode;
301       }
302       if (fi.IsDir())
303       {
304         HRESULT errorCode = E_FAIL;
305         SetErrorMessage("The item is a directory", arcPath, errorCode, errorMessage);
306         return errorCode;
307       }
308     }
309     arcSizes.Add(fi.Size);
310     totalPackSize += fi.Size;
311   }
312 
313   CBoolArr skipArcs(numArcs);
314   for (i = 0; i < numArcs; i++)
315     skipArcs[i] = false;
316 
317   CArchiveExtractCallback *ecs = new CArchiveExtractCallback;
318   CMyComPtr<IArchiveExtractCallback> ec(ecs);
319 
320   const bool multi = (numArcs > 1);
321 
322   ecs->InitForMulti(multi,
323       options.PathMode,
324       options.OverwriteMode,
325       options.ZoneMode,
326       false // keepEmptyDirParts
327       );
328   #ifndef Z7_SFX
329   ecs->SetHashMethods(hash);
330   #endif
331 
332   if (multi)
333   {
334     RINOK(faeCallback->SetTotal(totalPackSize))
335   }
336 
337   UInt64 totalPackProcessed = 0;
338   bool thereAreNotOpenArcs = false;
339 
340   for (i = 0; i < numArcs; i++)
341   {
342     if (skipArcs[i])
343       continue;
344 
345     ecs->InitBeforeNewArchive();
346 
347     const UString &arcPath = arcPaths[i];
348     NFind::CFileInfo fi;
349     if (options.StdInMode)
350     {
351       // do we need ctime and mtime?
352       fi.ClearBase();
353       fi.Size = 0; // (UInt64)(Int64)-1;
354       fi.SetAsFile();
355       // NTime::GetCurUtc_FiTime(fi.MTime);
356       // fi.CTime = fi.ATime = fi.MTime;
357     }
358     else
359     {
360       if (!fi.Find_FollowLink(us2fs(arcPath)) || fi.IsDir())
361       {
362         const HRESULT errorCode = GetLastError_noZero_HRESULT();
363         SetErrorMessage("Cannot find archive file", us2fs(arcPath), errorCode, errorMessage);
364         return errorCode;
365       }
366     }
367 
368     /*
369     #ifndef Z7_NO_CRYPTO
370     openCallback->Open_Clear_PasswordWasAsked_Flag();
371     #endif
372     */
373 
374     RINOK(extractCallback->BeforeOpen(arcPath, options.TestMode))
375     CArchiveLink arcLink;
376 
377     CObjectVector<COpenType> types2 = types;
378     /*
379     #ifndef Z7_SFX
380     if (types.IsEmpty())
381     {
382       int pos = arcPath.ReverseFind(L'.');
383       if (pos >= 0)
384       {
385         UString s = arcPath.Ptr(pos + 1);
386         int index = codecs->FindFormatForExtension(s);
387         if (index >= 0 && s == L"001")
388         {
389           s = arcPath.Left(pos);
390           pos = s.ReverseFind(L'.');
391           if (pos >= 0)
392           {
393             int index2 = codecs->FindFormatForExtension(s.Ptr(pos + 1));
394             if (index2 >= 0) // && s.CompareNoCase(L"rar") != 0
395             {
396               types2.Add(index2);
397               types2.Add(index);
398             }
399           }
400         }
401       }
402     }
403     #endif
404     */
405 
406     COpenOptions op;
407     #ifndef Z7_SFX
408     op.props = &options.Properties;
409     #endif
410     op.codecs = codecs;
411     op.types = &types2;
412     op.excludedFormats = &excludedFormats;
413     op.stdInMode = options.StdInMode;
414     op.stream = NULL;
415     op.filePath = arcPath;
416 
417     HRESULT result = arcLink.Open_Strict(op, openCallback);
418 
419     if (result == E_ABORT)
420       return result;
421 
422     // arcLink.Set_ErrorsText();
423     RINOK(extractCallback->OpenResult(codecs, arcLink, arcPath, result))
424 
425     if (result != S_OK)
426     {
427       thereAreNotOpenArcs = true;
428       if (!options.StdInMode)
429         totalPackProcessed += fi.Size;
430       continue;
431     }
432 
433    #if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX)
434     if (options.ZoneMode != NExtract::NZoneIdMode::kNone
435         && !options.StdInMode)
436     {
437       ReadZoneFile_Of_BaseFile(us2fs(arcPath), ecs->ZoneBuf);
438     }
439    #endif
440 
441 
442     if (arcLink.Arcs.Size() != 0)
443     {
444       if (arcLink.GetArc()->IsHashHandler(op))
445       {
446         if (!options.TestMode)
447         {
448           /* real Extracting to files is possible.
449              But user can think that hash archive contains real files.
450              So we block extracting here. */
451           // v23.00 : we don't break process.
452           RINOK(extractCallback->OpenResult(codecs, arcLink, arcPath, E_NOTIMPL))
453           thereAreNotOpenArcs = true;
454           if (!options.StdInMode)
455             totalPackProcessed += fi.Size;
456           continue;
457           // return E_NOTIMPL; // before v23
458         }
459         FString dirPrefix = us2fs(options.HashDir);
460         if (dirPrefix.IsEmpty())
461         {
462           if (!NFile::NDir::GetOnlyDirPrefix(us2fs(arcPath), dirPrefix))
463           {
464             // return GetLastError_noZero_HRESULT();
465           }
466         }
467         if (!dirPrefix.IsEmpty())
468           NName::NormalizeDirPathPrefix(dirPrefix);
469         ecs->DirPathPrefix_for_HashFiles = dirPrefix;
470       }
471     }
472 
473     if (!options.StdInMode)
474     {
475       // numVolumes += arcLink.VolumePaths.Size();
476       // arcLink.VolumesSize;
477 
478       // totalPackSize -= DeleteUsedFileNamesFromList(arcLink, i + 1, arcPaths, arcPathsFull, &arcSizes);
479       // numArcs = arcPaths.Size();
480       if (arcLink.VolumePaths.Size() != 0)
481       {
482         Int64 correctionSize = (Int64)arcLink.VolumesSize;
483         FOR_VECTOR (v, arcLink.VolumePaths)
484         {
485           int index = Find_FileName_InSortedVector(arcPathsFull, arcLink.VolumePaths[v]);
486           if (index >= 0)
487           {
488             if ((unsigned)index > i)
489             {
490               skipArcs[(unsigned)index] = true;
491               correctionSize -= arcSizes[(unsigned)index];
492             }
493           }
494         }
495         if (correctionSize != 0)
496         {
497           Int64 newPackSize = (Int64)totalPackSize + correctionSize;
498           if (newPackSize < 0)
499             newPackSize = 0;
500           totalPackSize = (UInt64)newPackSize;
501           RINOK(faeCallback->SetTotal(totalPackSize))
502         }
503       }
504     }
505 
506     /*
507     // Now openCallback and extractCallback use same object. So we don't need to send password.
508 
509     #ifndef Z7_NO_CRYPTO
510     bool passwordIsDefined;
511     UString password;
512     RINOK(openCallback->Open_GetPasswordIfAny(passwordIsDefined, password))
513     if (passwordIsDefined)
514     {
515       RINOK(extractCallback->SetPassword(password))
516     }
517     #endif
518     */
519 
520     CArc &arc = arcLink.Arcs.Back();
521     arc.MTime.Def = !options.StdInMode
522         #ifdef _WIN32
523         && !fi.IsDevice
524         #endif
525         ;
526     if (arc.MTime.Def)
527       arc.MTime.Set_From_FiTime(fi.MTime);
528 
529     UInt64 packProcessed;
530     const bool calcCrc =
531         #ifndef Z7_SFX
532           (hash != NULL);
533         #else
534           false;
535         #endif
536 
537     RINOK(DecompressArchive(
538         codecs,
539         arcLink,
540         fi.Size + arcLink.VolumesSize,
541         wildcardCensor,
542         options,
543         calcCrc,
544         extractCallback, faeCallback, ecs,
545         errorMessage, packProcessed))
546 
547     if (!options.StdInMode)
548       packProcessed = fi.Size + arcLink.VolumesSize;
549     totalPackProcessed += packProcessed;
550     ecs->LocalProgressSpec->InSize += packProcessed;
551     ecs->LocalProgressSpec->OutSize = ecs->UnpackSize;
552     if (!errorMessage.IsEmpty())
553       return E_FAIL;
554   }
555 
556   if (multi || thereAreNotOpenArcs)
557   {
558     RINOK(faeCallback->SetTotal(totalPackSize))
559     RINOK(faeCallback->SetCompleted(&totalPackProcessed))
560   }
561 
562   st.NumFolders = ecs->NumFolders;
563   st.NumFiles = ecs->NumFiles;
564   st.NumAltStreams = ecs->NumAltStreams;
565   st.UnpackSize = ecs->UnpackSize;
566   st.AltStreams_UnpackSize = ecs->AltStreams_UnpackSize;
567   st.NumArchives = arcPaths.Size();
568   st.PackSize = ecs->LocalProgressSpec->InSize;
569   return S_OK;
570 }
571