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/PropVariant.h"
11#include "../../../Windows/PropVariantConv.h"
12
13#include "../Common/ExtractingFilePath.h"
14
15#include "Extract.h"
16#include "SetProperties.h"
17
18using namespace NWindows;
19using namespace NFile;
20using namespace NDir;
21
22static HRESULT DecompressArchive(
23    CCodecs *codecs,
24    const CArchiveLink &arcLink,
25    UInt64 packSize,
26    const NWildcard::CCensorNode &wildcardCensor,
27    const CExtractOptions &options,
28    bool calcCrc,
29    IExtractCallbackUI *callback,
30    CArchiveExtractCallback *ecs,
31    UString &errorMessage,
32    UInt64 &stdInProcessed)
33{
34  const CArc &arc = arcLink.Arcs.Back();
35  stdInProcessed = 0;
36  IInArchive *archive = arc.Archive;
37  CRecordVector<UInt32> realIndices;
38
39  UStringVector removePathParts;
40
41  FString outDir = options.OutputDir;
42  UString replaceName = arc.DefaultName;
43
44  if (arcLink.Arcs.Size() > 1)
45  {
46    // Most "pe" archives have same name of archive subfile "[0]" or ".rsrc_1".
47    // So it extracts different archives to one folder.
48    // We will use top level archive name
49    const CArc &arc0 = arcLink.Arcs[0];
50    if (StringsAreEqualNoCase_Ascii(codecs->Formats[arc0.FormatIndex].Name, "pe"))
51      replaceName = arc0.DefaultName;
52  }
53
54  outDir.Replace(FSTRING_ANY_MASK, us2fs(GetCorrectFsPath(replaceName)));
55
56  bool elimIsPossible = false;
57  UString elimPrefix; // only pure name without dir delimiter
58  FString outDirReduced = outDir;
59
60  if (options.ElimDup.Val)
61  {
62    UString dirPrefix;
63    SplitPathToParts_Smart(fs2us(outDir), dirPrefix, elimPrefix);
64    if (!elimPrefix.IsEmpty())
65    {
66      if (IsCharDirLimiter(elimPrefix.Back()))
67        elimPrefix.DeleteBack();
68      if (!elimPrefix.IsEmpty())
69      {
70        outDirReduced = us2fs(dirPrefix);
71        elimIsPossible = true;
72      }
73    }
74  }
75
76  if (!options.StdInMode)
77  {
78    UInt32 numItems;
79    RINOK(archive->GetNumberOfItems(&numItems));
80
81    UString filePath;
82
83    for (UInt32 i = 0; i < numItems; i++)
84    {
85      RINOK(arc.GetItemPath(i, filePath));
86
87      if (elimIsPossible && options.ElimDup.Val)
88      {
89        if (!IsPath1PrefixedByPath2(filePath, elimPrefix))
90          elimIsPossible = false;
91        else
92        {
93          wchar_t c = filePath[elimPrefix.Len()];
94          if (c != 0 && !IsCharDirLimiter(c))
95            elimIsPossible = false;
96        }
97      }
98
99      bool isFolder;
100      RINOK(Archive_IsItem_Folder(archive, i, isFolder));
101      bool isAltStream;
102      RINOK(Archive_IsItem_AltStream(archive, i, isAltStream));
103      if (!options.NtOptions.AltStreams.Val && isAltStream)
104        continue;
105      if (!wildcardCensor.CheckPath(isAltStream, filePath, !isFolder))
106        continue;
107      realIndices.Add(i);
108    }
109
110    if (realIndices.Size() == 0)
111    {
112      callback->ThereAreNoFiles();
113      return callback->ExtractResult(S_OK);
114    }
115  }
116
117  if (elimIsPossible)
118    outDir = outDirReduced;
119
120  #ifdef _WIN32
121  // GetCorrectFullFsPath doesn't like "..".
122  // outDir.TrimRight();
123  // outDir = GetCorrectFullFsPath(outDir);
124  #endif
125
126  if (outDir.IsEmpty())
127    outDir = FString(FTEXT(".")) + FString(FSTRING_PATH_SEPARATOR);
128  else
129    if (!CreateComplexDir(outDir))
130    {
131      HRESULT res = ::GetLastError();
132      if (res == S_OK)
133        res = E_FAIL;
134      errorMessage = ((UString)L"Can not create output directory ") + fs2us(outDir);
135      return res;
136    }
137
138  ecs->Init(
139      options.NtOptions,
140      options.StdInMode ? &wildcardCensor : NULL,
141      &arc,
142      callback,
143      options.StdOutMode, options.TestMode,
144      outDir,
145      removePathParts,
146      packSize);
147
148
149  #ifdef SUPPORT_LINKS
150
151  if (!options.StdInMode &&
152      !options.TestMode &&
153      options.NtOptions.HardLinks.Val)
154  {
155    RINOK(ecs->PrepareHardLinks(&realIndices));
156  }
157
158  #endif
159
160
161  HRESULT result;
162  Int32 testMode = (options.TestMode && !calcCrc) ? 1: 0;
163  if (options.StdInMode)
164  {
165    result = archive->Extract(NULL, (UInt32)(Int32)-1, testMode, ecs);
166    NCOM::CPropVariant prop;
167    if (archive->GetArchiveProperty(kpidPhySize, &prop) == S_OK)
168      ConvertPropVariantToUInt64(prop, stdInProcessed);
169  }
170  else
171    result = archive->Extract(&realIndices.Front(), realIndices.Size(), testMode, ecs);
172  if (result == S_OK && !options.StdInMode)
173    result = ecs->SetDirsTimes();
174  return callback->ExtractResult(result);
175}
176
177/* v9.31: BUG was fixed:
178   Sorted list for file paths was sorted with case insensitive compare function.
179   But FindInSorted function did binary search via case sensitive compare function */
180
181int Find_FileName_InSortedVector(const UStringVector &fileName, const UString &name)
182{
183  unsigned left = 0, right = fileName.Size();
184  while (left != right)
185  {
186    unsigned mid = (left + right) / 2;
187    const UString &midValue = fileName[mid];
188    int compare = CompareFileNames(name, midValue);
189    if (compare == 0)
190      return mid;
191    if (compare < 0)
192      right = mid;
193    else
194      left = mid + 1;
195  }
196  return -1;
197}
198
199HRESULT Extract(
200    CCodecs *codecs,
201    const CObjectVector<COpenType> &types,
202    const CIntVector &excludedFormats,
203    UStringVector &arcPaths, UStringVector &arcPathsFull,
204    const NWildcard::CCensorNode &wildcardCensor,
205    const CExtractOptions &options,
206    IOpenCallbackUI *openCallback,
207    IExtractCallbackUI *extractCallback,
208    #ifndef _SFX
209    IHashCalc *hash,
210    #endif
211    UString &errorMessage,
212    CDecompressStat &stat)
213{
214  stat.Clear();
215  UInt64 totalPackSize = 0;
216  CRecordVector<UInt64> arcSizes;
217
218  unsigned numArcs = options.StdInMode ? 1 : arcPaths.Size();
219
220  unsigned i;
221  for (i = 0; i < numArcs; i++)
222  {
223    NFind::CFileInfo fi;
224    fi.Size = 0;
225    if (!options.StdInMode)
226    {
227      const FString &arcPath = us2fs(arcPaths[i]);
228      if (!fi.Find(arcPath))
229        throw "there is no such archive";
230      if (fi.IsDir())
231        throw "can't decompress folder";
232    }
233    arcSizes.Add(fi.Size);
234    totalPackSize += fi.Size;
235  }
236
237  CBoolArr skipArcs(numArcs);
238  for (i = 0; i < numArcs; i++)
239    skipArcs[i] = false;
240
241  CArchiveExtractCallback *ecs = new CArchiveExtractCallback;
242  CMyComPtr<IArchiveExtractCallback> ec(ecs);
243  bool multi = (numArcs > 1);
244  ecs->InitForMulti(multi, options.PathMode, options.OverwriteMode);
245  #ifndef _SFX
246  ecs->SetHashMethods(hash);
247  #endif
248
249  if (multi)
250  {
251    RINOK(extractCallback->SetTotal(totalPackSize));
252  }
253
254  UInt64 totalPackProcessed = 0;
255  bool thereAreNotOpenArcs = false;
256
257  for (i = 0; i < numArcs; i++)
258  {
259    if (skipArcs[i])
260      continue;
261
262    const UString &arcPath = arcPaths[i];
263    NFind::CFileInfo fi;
264    if (options.StdInMode)
265    {
266      fi.Size = 0;
267      fi.Attrib = 0;
268    }
269    else
270    {
271      if (!fi.Find(us2fs(arcPath)) || fi.IsDir())
272        throw "there is no such archive";
273    }
274
275    #ifndef _NO_CRYPTO
276    openCallback->Open_ClearPasswordWasAskedFlag();
277    #endif
278
279    RINOK(extractCallback->BeforeOpen(arcPath));
280    CArchiveLink arcLink;
281
282    CObjectVector<COpenType> types2 = types;
283    /*
284    #ifndef _SFX
285    if (types.IsEmpty())
286    {
287      int pos = arcPath.ReverseFind(L'.');
288      if (pos >= 0)
289      {
290        UString s = arcPath.Ptr(pos + 1);
291        int index = codecs->FindFormatForExtension(s);
292        if (index >= 0 && s == L"001")
293        {
294          s = arcPath.Left(pos);
295          pos = s.ReverseFind(L'.');
296          if (pos >= 0)
297          {
298            int index2 = codecs->FindFormatForExtension(s.Ptr(pos + 1));
299            if (index2 >= 0) // && s.CompareNoCase(L"rar") != 0
300            {
301              types2.Add(index2);
302              types2.Add(index);
303            }
304          }
305        }
306      }
307    }
308    #endif
309    */
310
311    COpenOptions op;
312    #ifndef _SFX
313    op.props = &options.Properties;
314    #endif
315    op.codecs = codecs;
316    op.types = &types2;
317    op.excludedFormats = &excludedFormats;
318    op.stdInMode = options.StdInMode;
319    op.stream = NULL;
320    op.filePath = arcPath;
321    HRESULT result = arcLink.Open2(op, openCallback);
322
323    if (result == E_ABORT)
324      return result;
325
326    bool crypted = false;
327    #ifndef _NO_CRYPTO
328    crypted = openCallback->Open_WasPasswordAsked();
329    #endif
330
331    if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0)
332      result = S_FALSE;
333
334    // arcLink.Set_ErrorsText();
335    RINOK(extractCallback->OpenResult(arcPath, result, crypted));
336
337
338    {
339      FOR_VECTOR (r, arcLink.Arcs)
340      {
341        const CArc &arc = arcLink.Arcs[r];
342        const CArcErrorInfo &er = arc.ErrorInfo;
343        if (er.IsThereErrorOrWarning())
344        {
345          RINOK(extractCallback->SetError(r, arc.Path,
346              er.GetErrorFlags(), er.ErrorMessage,
347              er.GetWarningFlags(), er.WarningMessage));
348        }
349      }
350    }
351
352    if (result != S_OK)
353    {
354      thereAreNotOpenArcs = true;
355      if (!options.StdInMode)
356      {
357        NFind::CFileInfo fi;
358        if (fi.Find(us2fs(arcPath)))
359          if (!fi.IsDir())
360            totalPackProcessed += fi.Size;
361      }
362      continue;
363    }
364
365    if (!options.StdInMode)
366    {
367      // numVolumes += arcLink.VolumePaths.Size();
368      // arcLink.VolumesSize;
369
370      // totalPackSize -= DeleteUsedFileNamesFromList(arcLink, i + 1, arcPaths, arcPathsFull, &arcSizes);
371      // numArcs = arcPaths.Size();
372      if (arcLink.VolumePaths.Size() != 0)
373      {
374        Int64 correctionSize = arcLink.VolumesSize;
375        FOR_VECTOR (v, arcLink.VolumePaths)
376        {
377          int index = Find_FileName_InSortedVector(arcPathsFull, arcLink.VolumePaths[v]);
378          if (index >= 0)
379          {
380            if ((unsigned)index > i)
381            {
382              skipArcs[index] = true;
383              correctionSize -= arcSizes[index];
384            }
385          }
386        }
387        if (correctionSize != 0)
388        {
389          Int64 newPackSize = (Int64)totalPackSize + correctionSize;
390          if (newPackSize < 0)
391            newPackSize = 0;
392          totalPackSize = newPackSize;
393          RINOK(extractCallback->SetTotal(totalPackSize));
394        }
395      }
396    }
397
398    #ifndef _NO_CRYPTO
399    bool passwordIsDefined;
400    UString password;
401    RINOK(openCallback->Open_GetPasswordIfAny(passwordIsDefined, password));
402    if (passwordIsDefined)
403    {
404      RINOK(extractCallback->SetPassword(password));
405    }
406    #endif
407
408    FOR_VECTOR (k, arcLink.Arcs)
409    {
410      const CArc &arc = arcLink.Arcs[k];
411      const CArcErrorInfo &er = arc.ErrorInfo;
412
413      if (er.ErrorFormatIndex >= 0)
414      {
415        RINOK(extractCallback->OpenTypeWarning(arc.Path,
416            codecs->GetFormatNamePtr(arc.FormatIndex),
417            codecs->GetFormatNamePtr(er.ErrorFormatIndex)))
418        /*
419        UString s = L"Can not open the file as [" + codecs->Formats[arc.ErrorFormatIndex].Name + L"] archive\n";
420        s += L"The file is open as [" + codecs->Formats[arc.FormatIndex].Name + L"] archive";
421        RINOK(extractCallback->MessageError(s));
422        */
423      }
424      {
425        const UString &s = er.ErrorMessage;
426        if (!s.IsEmpty())
427        {
428          RINOK(extractCallback->MessageError(s));
429        }
430      }
431    }
432
433    CArc &arc = arcLink.Arcs.Back();
434    arc.MTimeDefined = (!options.StdInMode && !fi.IsDevice);
435    arc.MTime = fi.MTime;
436
437    UInt64 packProcessed;
438    bool calcCrc =
439        #ifndef _SFX
440          (hash != NULL);
441        #else
442          false;
443        #endif
444
445    RINOK(DecompressArchive(
446        codecs,
447        arcLink,
448        fi.Size + arcLink.VolumesSize,
449        wildcardCensor,
450        options,
451        calcCrc,
452        extractCallback, ecs, errorMessage, packProcessed));
453    if (!options.StdInMode)
454      packProcessed = fi.Size + arcLink.VolumesSize;
455    totalPackProcessed += packProcessed;
456    ecs->LocalProgressSpec->InSize += packProcessed;
457    ecs->LocalProgressSpec->OutSize = ecs->UnpackSize;
458    if (!errorMessage.IsEmpty())
459      return E_FAIL;
460  }
461
462  if (multi || thereAreNotOpenArcs)
463  {
464    RINOK(extractCallback->SetTotal(totalPackSize));
465    RINOK(extractCallback->SetCompleted(&totalPackProcessed));
466  }
467  stat.NumFolders = ecs->NumFolders;
468  stat.NumFiles = ecs->NumFiles;
469  stat.NumAltStreams = ecs->NumAltStreams;
470  stat.UnpackSize = ecs->UnpackSize;
471  stat.AltStreams_UnpackSize = ecs->AltStreams_UnpackSize;
472  stat.NumArchives = arcPaths.Size();
473  stat.PackSize = ecs->LocalProgressSpec->InSize;
474  return S_OK;
475}
476