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