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