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 21using namespace NWindows; 22using namespace NFile; 23using namespace NDir; 24 25 26static 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 38static 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 243int Find_FileName_InSortedVector(const UStringVector &fileNames, const UString &name); 244int 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 264HRESULT 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