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