xref: /third_party/lzma/CPP/7zip/UI/Common/Extract.cpp (revision 370b324c)
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
21using namespace NWindows;
22using namespace NFile;
23using namespace NDir;
24
25
26static 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
38static 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
243int Find_FileName_InSortedVector(const UStringVector &fileNames, const UString &name);
244int 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
264HRESULT 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