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