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