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