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 
18 using namespace NWindows;
19 using namespace NFile;
20 using namespace NDir;
21 
DecompressArchive(CCodecs * codecs,const CArchiveLink & arcLink,UInt64 packSize,const NWildcard::CCensorNode & wildcardCensor,const CExtractOptions & options,bool calcCrc,IExtractCallbackUI * callback,CArchiveExtractCallback * ecs,UString & errorMessage,UInt64 & stdInProcessed)22 static 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 
Find_FileName_InSortedVector(const UStringVector & fileName,const UString & name)181 int 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 
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,IHashCalc * hash,UString & errorMessage,CDecompressStat & stat)199 HRESULT 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