| #include "rar.hpp" |
| |
| CmdExtract::CmdExtract(CommandData *Cmd) |
| { |
| CmdExtract::Cmd=Cmd; |
| |
| *ArcName=0; |
| |
| *DestFileName=0; |
| |
| TotalFileCount=0; |
| Unp=new Unpack(&DataIO); |
| #ifdef RAR_SMP |
| Unp->SetThreads(Cmd->Threads); |
| #endif |
| } |
| |
| |
| CmdExtract::~CmdExtract() |
| { |
| delete Unp; |
| } |
| |
| |
| void CmdExtract::DoExtract() |
| { |
| #if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) |
| Fat32=NotFat32=false; |
| #endif |
| PasswordCancelled=false; |
| DataIO.SetCurrentCommand(Cmd->Command[0]); |
| |
| FindData FD; |
| while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) |
| if (FindFile::FastFind(ArcName,&FD)) |
| DataIO.TotalArcSize+=FD.Size; |
| |
| Cmd->ArcNames.Rewind(); |
| while (Cmd->GetArcName(ArcName,ASIZE(ArcName))) |
| { |
| if (Cmd->ManualPassword) |
| Cmd->Password.Clean(); // Clean user entered password before processing next archive. |
| while (true) |
| { |
| EXTRACT_ARC_CODE Code=ExtractArchive(); |
| if (Code!=EXTRACT_ARC_REPEAT) |
| break; |
| } |
| if (FindFile::FastFind(ArcName,&FD)) |
| DataIO.ProcessedArcSize+=FD.Size; |
| } |
| |
| // Clean user entered password. Not really required, just for extra safety. |
| if (Cmd->ManualPassword) |
| Cmd->Password.Clean(); |
| |
| if (TotalFileCount==0 && Cmd->Command[0]!='I' && |
| ErrHandler.GetErrorCode()!=RARX_BADPWD) // Not in case of wrong archive password. |
| { |
| if (!PasswordCancelled) |
| uiMsg(UIERROR_NOFILESTOEXTRACT,ArcName); |
| |
| // Other error codes may explain a reason of "no files extracted" clearer, |
| // so set it only if no other errors found (wrong mask set by user). |
| if (ErrHandler.GetErrorCode()==RARX_SUCCESS) |
| ErrHandler.SetErrorCode(RARX_NOFILES); |
| } |
| else |
| if (!Cmd->DisableDone) |
| { |
| if (Cmd->Command[0]=='I') |
| mprintf(St(MDone)); |
| else |
| if (ErrHandler.GetErrorCount()==0) |
| mprintf(St(MExtrAllOk)); |
| else |
| mprintf(St(MExtrTotalErr),ErrHandler.GetErrorCount()); |
| } |
| } |
| |
| |
| void CmdExtract::ExtractArchiveInit(Archive &Arc) |
| { |
| DataIO.UnpArcSize=Arc.FileLength(); |
| |
| FileCount=0; |
| MatchedArgs=0; |
| #ifndef SFX_MODULE |
| FirstFile=true; |
| #endif |
| |
| GlobalPassword=Cmd->Password.IsSet(); |
| |
| DataIO.UnpVolume=false; |
| |
| PrevProcessed=false; |
| AllMatchesExact=true; |
| ReconstructDone=false; |
| AnySolidDataUnpackedWell=false; |
| |
| StartTime.SetCurrentTime(); |
| } |
| |
| |
| EXTRACT_ARC_CODE CmdExtract::ExtractArchive() |
| { |
| Archive Arc(Cmd); |
| if (!Arc.WOpen(ArcName)) |
| return EXTRACT_ARC_NEXT; |
| |
| if (!Arc.IsArchive(true)) |
| { |
| #if !defined(SFX_MODULE) && !defined(RARDLL) |
| if (CmpExt(ArcName,L"rev")) |
| { |
| wchar FirstVolName[NM]; |
| VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),true); |
| |
| // If several volume names from same volume set are specified |
| // and current volume is not first in set and first volume is present |
| // and specified too, let's skip the current volume. |
| if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) && |
| Cmd->ArcNames.Search(FirstVolName,false)) |
| return EXTRACT_ARC_NEXT; |
| RecVolumesTest(Cmd,NULL,ArcName); |
| TotalFileCount++; // Suppress "No files to extract" message. |
| return EXTRACT_ARC_NEXT; |
| } |
| #endif |
| |
| mprintf(St(MNotRAR),ArcName); |
| |
| #ifndef SFX_MODULE |
| if (CmpExt(ArcName,L"rar")) |
| #endif |
| ErrHandler.SetErrorCode(RARX_WARNING); |
| return EXTRACT_ARC_NEXT; |
| } |
| |
| if (Arc.FailedHeaderDecryption) // Bad archive password. |
| return EXTRACT_ARC_NEXT; |
| |
| #ifndef SFX_MODULE |
| if (Arc.Volume && !Arc.FirstVolume) |
| { |
| wchar FirstVolName[NM]; |
| VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),Arc.NewNumbering); |
| |
| // If several volume names from same volume set are specified |
| // and current volume is not first in set and first volume is present |
| // and specified too, let's skip the current volume. |
| if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) && |
| Cmd->ArcNames.Search(FirstVolName,false)) |
| return EXTRACT_ARC_NEXT; |
| } |
| #endif |
| |
| int64 VolumeSetSize=0; // Total size of volumes after the current volume. |
| |
| if (Arc.Volume) |
| { |
| // Calculate the total size of all accessible volumes. |
| // This size is necessary to display the correct total progress indicator. |
| |
| wchar NextName[NM]; |
| wcsncpyz(NextName,Arc.FileName,ASIZE(NextName)); |
| |
| while (true) |
| { |
| // First volume is already added to DataIO.TotalArcSize |
| // in initial TotalArcSize calculation in DoExtract. |
| // So we skip it and start from second volume. |
| NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering); |
| FindData FD; |
| if (FindFile::FastFind(NextName,&FD)) |
| VolumeSetSize+=FD.Size; |
| else |
| break; |
| } |
| DataIO.TotalArcSize+=VolumeSetSize; |
| } |
| |
| ExtractArchiveInit(Arc); |
| |
| if (*Cmd->Command=='T' || *Cmd->Command=='I') |
| Cmd->Test=true; |
| |
| |
| if (*Cmd->Command=='I') |
| { |
| Cmd->DisablePercentage=true; |
| } |
| else |
| uiStartArchiveExtract(!Cmd->Test,ArcName); |
| |
| Arc.ViewComment(); |
| |
| |
| while (1) |
| { |
| size_t Size=Arc.ReadHeader(); |
| |
| |
| bool Repeat=false; |
| if (!ExtractCurrentFile(Arc,Size,Repeat)) |
| { |
| if (Repeat) |
| { |
| // If we started extraction from not first volume and need to |
| // restart it from first, we must correct DataIO.TotalArcSize |
| // for correct total progress display. We subtract the size |
| // of current volume and all volumes after it and add the size |
| // of new (first) volume. |
| FindData OldArc,NewArc; |
| if (FindFile::FastFind(Arc.FileName,&OldArc) && |
| FindFile::FastFind(ArcName,&NewArc)) |
| DataIO.TotalArcSize-=VolumeSetSize+OldArc.Size-NewArc.Size; |
| return EXTRACT_ARC_REPEAT; |
| } |
| else |
| break; |
| } |
| } |
| |
| |
| #if !defined(SFX_MODULE) && !defined(RARDLL) |
| if (Cmd->Test && Arc.Volume) |
| RecVolumesTest(Cmd,&Arc,ArcName); |
| #endif |
| |
| return EXTRACT_ARC_NEXT; |
| } |
| |
| |
| bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat) |
| { |
| wchar Command=Cmd->Command[0]; |
| if (HeaderSize==0) |
| { |
| if (DataIO.UnpVolume) |
| { |
| #ifdef NOVOLUME |
| return false; |
| #else |
| // Supposing we unpack an old RAR volume without the end of archive |
| // record and last file is not split between volumes. |
| if (!MergeArchive(Arc,&DataIO,false,Command)) |
| { |
| ErrHandler.SetErrorCode(RARX_WARNING); |
| return false; |
| } |
| #endif |
| } |
| else |
| return false; |
| } |
| |
| HEADER_TYPE HeaderType=Arc.GetHeaderType(); |
| if (HeaderType!=HEAD_FILE) |
| { |
| #ifndef SFX_MODULE |
| if (Arc.Format==RARFMT15 && HeaderType==HEAD3_OLDSERVICE && PrevProcessed) |
| SetExtraInfo20(Cmd,Arc,DestFileName); |
| #endif |
| if (HeaderType==HEAD_SERVICE && PrevProcessed) |
| SetExtraInfo(Cmd,Arc,DestFileName); |
| if (HeaderType==HEAD_ENDARC) |
| { |
| if (Arc.EndArcHead.NextVolume) |
| { |
| #ifndef NOVOLUME |
| if (!MergeArchive(Arc,&DataIO,false,Command)) |
| { |
| ErrHandler.SetErrorCode(RARX_WARNING); |
| return false; |
| } |
| #endif |
| Arc.Seek(Arc.CurBlockPos,SEEK_SET); |
| return true; |
| } |
| else |
| return false; |
| } |
| Arc.SeekToNext(); |
| return true; |
| } |
| PrevProcessed=false; |
| |
| // We can get negative sizes in corrupt archive and it is unacceptable |
| // for size comparisons in ComprDataIO::UnpRead, where we cast sizes |
| // to size_t and can exceed another read or available size. We could fix it |
| // when reading an archive. But we prefer to do it here, because this |
| // function is called directly in unrar.dll, so we fix bad parameters |
| // passed to dll. Also we want to see real negative sizes in the listing |
| // of corrupt archive. To prevent uninitialized data access perform |
| // these checks after rejecting zero length and non-file headers above. |
| if (Arc.FileHead.PackSize<0) |
| Arc.FileHead.PackSize=0; |
| if (Arc.FileHead.UnpSize<0) |
| Arc.FileHead.UnpSize=0; |
| |
| if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact) |
| return false; |
| |
| int MatchType=MATCH_WILDSUBPATH; |
| |
| bool EqualNames=false; |
| wchar MatchedArg[NM]; |
| int MatchNumber=Cmd->IsProcessFile(Arc.FileHead,&EqualNames,MatchType,MatchedArg,ASIZE(MatchedArg)); |
| bool MatchFound=MatchNumber!=0; |
| #ifndef SFX_MODULE |
| if (Cmd->ExclPath==EXCL_BASEPATH) |
| { |
| wcsncpyz(Cmd->ArcPath,MatchedArg,ASIZE(Cmd->ArcPath)); |
| *PointToName(Cmd->ArcPath)=0; |
| if (IsWildcard(Cmd->ArcPath)) // Cannot correctly process path*\* masks here. |
| *Cmd->ArcPath=0; |
| } |
| #endif |
| if (MatchFound && !EqualNames) |
| AllMatchesExact=false; |
| |
| Arc.ConvertAttributes(); |
| |
| #if !defined(SFX_MODULE) && !defined(RARDLL) |
| if (Arc.FileHead.SplitBefore && FirstFile) |
| { |
| wchar CurVolName[NM]; |
| wcsncpyz(CurVolName,ArcName,ASIZE(CurVolName)); |
| VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),Arc.NewNumbering); |
| |
| if (wcsicomp(ArcName,CurVolName)!=0 && FileExist(ArcName)) |
| { |
| wcsncpyz(Cmd->ArcName,ArcName,ASIZE(ArcName)); // For GUI "Delete archive after extraction". |
| // If first volume name does not match the current name and if such |
| // volume name really exists, let's unpack from this first volume. |
| Repeat=true; |
| return false; |
| } |
| #ifndef RARDLL |
| if (!ReconstructDone) |
| { |
| ReconstructDone=true; |
| if (RecVolumesRestore(Cmd,Arc.FileName,true)) |
| { |
| Repeat=true; |
| return false; |
| } |
| } |
| #endif |
| wcsncpyz(ArcName,CurVolName,ASIZE(ArcName)); |
| } |
| #endif |
| |
| wchar ArcFileName[NM]; |
| ConvertPath(Arc.FileHead.FileName,ArcFileName); |
| |
| if (Arc.FileHead.Version) |
| { |
| if (Cmd->VersionControl!=1 && !EqualNames) |
| { |
| if (Cmd->VersionControl==0) |
| MatchFound=false; |
| int Version=ParseVersionFileName(ArcFileName,false); |
| if (Cmd->VersionControl-1==Version) |
| ParseVersionFileName(ArcFileName,true); |
| else |
| MatchFound=false; |
| } |
| } |
| else |
| if (!Arc.IsArcDir() && Cmd->VersionControl>1) |
| MatchFound=false; |
| |
| DataIO.UnpVolume=Arc.FileHead.SplitAfter; |
| DataIO.NextVolumeMissing=false; |
| |
| Arc.Seek(Arc.NextBlockPos-Arc.FileHead.PackSize,SEEK_SET); |
| |
| bool ExtrFile=false; |
| bool SkipSolid=false; |
| |
| #ifndef SFX_MODULE |
| if (FirstFile && (MatchFound || Arc.Solid) && Arc.FileHead.SplitBefore) |
| { |
| if (MatchFound) |
| { |
| uiMsg(UIERROR_NEEDPREVVOL,Arc.FileName,ArcFileName); |
| #ifdef RARDLL |
| Cmd->DllError=ERAR_BAD_DATA; |
| #endif |
| ErrHandler.SetErrorCode(RARX_OPEN); |
| } |
| MatchFound=false; |
| } |
| |
| FirstFile=false; |
| #endif |
| |
| if (MatchFound || (SkipSolid=Arc.Solid)!=0) |
| { |
| // First common call of uiStartFileExtract. It is done before overwrite |
| // prompts, so if SkipSolid state is changed below, we'll need to make |
| // additional uiStartFileExtract calls with updated parameters. |
| if (!uiStartFileExtract(ArcFileName,!Cmd->Test,Cmd->Test && Command!='I',SkipSolid)) |
| return false; |
| |
| ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName)); |
| |
| // DestFileName can be set empty in case of excessive -ap switch. |
| ExtrFile=!SkipSolid && *DestFileName!=0 && !Arc.FileHead.SplitBefore; |
| |
| if ((Cmd->FreshFiles || Cmd->UpdateFiles) && (Command=='E' || Command=='X')) |
| { |
| FindData FD; |
| if (FindFile::FastFind(DestFileName,&FD)) |
| { |
| if (FD.mtime >= Arc.FileHead.mtime) |
| { |
| // If directory already exists and its modification time is newer |
| // than start of extraction, it is likely it was created |
| // when creating a path to one of already extracted items. |
| // In such case we'll better update its time even if archived |
| // directory is older. |
| |
| if (!FD.IsDir || FD.mtime<StartTime) |
| ExtrFile=false; |
| } |
| } |
| else |
| if (Cmd->FreshFiles) |
| ExtrFile=false; |
| } |
| |
| if (!CheckUnpVer(Arc,ArcFileName)) |
| { |
| ErrHandler.SetErrorCode(RARX_FATAL); |
| #ifdef RARDLL |
| Cmd->DllError=ERAR_UNKNOWN_FORMAT; |
| #endif |
| Arc.SeekToNext(); |
| return !Arc.Solid; // Can try extracting next file only in non-solid archive. |
| } |
| |
| while (true) // Repeat the password prompt for wrong and empty passwords. |
| { |
| if (Arc.FileHead.Encrypted) |
| { |
| // Stop archive extracting if user cancelled a password prompt. |
| #ifdef RARDLL |
| if (!ExtrDllGetPassword()) |
| { |
| Cmd->DllError=ERAR_MISSING_PASSWORD; |
| return false; |
| } |
| #else |
| if (!ExtrGetPassword(Arc,ArcFileName)) |
| { |
| PasswordCancelled=true; |
| return false; |
| } |
| #endif |
| } |
| |
| // Set a password before creating the file, so we can skip creating |
| // in case of wrong password. |
| SecPassword FilePassword=Cmd->Password; |
| #if defined(_WIN_ALL) && !defined(SFX_MODULE) |
| ConvertDosPassword(Arc,FilePassword); |
| #endif |
| |
| byte PswCheck[SIZE_PSWCHECK]; |
| DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword, |
| Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL, |
| Arc.FileHead.InitV,Arc.FileHead.Lg2Count, |
| Arc.FileHead.HashKey,PswCheck); |
| |
| // If header is damaged, we cannot rely on password check value, |
| // because it can be damaged too. |
| if (Arc.FileHead.Encrypted && Arc.FileHead.UsePswCheck && |
| memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0 && |
| !Arc.BrokenHeader) |
| { |
| if (GlobalPassword) // For -p<pwd> or Ctrl+P. |
| { |
| // This message is used by Android GUI to reset cached passwords. |
| // Update appropriate code if changed. |
| uiMsg(UIERROR_BADPSW,ArcFileName); |
| } |
| else // For passwords entered manually. |
| { |
| // This message is used by Android GUI and Windows GUI and SFX to |
| // reset cached passwords. Update appropriate code if changed. |
| uiMsg(UIWAIT_BADPSW,ArcFileName); |
| Cmd->Password.Clean(); |
| |
| // Avoid new requests for unrar.dll to prevent the infinite loop |
| // if app always returns the same password. |
| #ifndef RARDLL |
| continue; // Request a password again. |
| #endif |
| } |
| #ifdef RARDLL |
| // If we already have ERAR_EOPEN as result of missing volume, |
| // we should not replace it with less precise ERAR_BAD_PASSWORD. |
| if (Cmd->DllError!=ERAR_EOPEN) |
| Cmd->DllError=ERAR_BAD_PASSWORD; |
| #endif |
| ErrHandler.SetErrorCode(RARX_BADPWD); |
| ExtrFile=false; |
| } |
| break; |
| } |
| |
| #ifdef RARDLL |
| if (*Cmd->DllDestName!=0) |
| wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName)); |
| #endif |
| |
| File CurFile; |
| #if defined(CHROMIUM_UNRAR) |
| // Since extraction is done in a sandbox, this must extract to the temp file |
| // handle instead of the default. |
| CurFile.SetFileHandle(Arc.GetTempFileHandle()); |
| #endif |
| |
| bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE; |
| if (LinkEntry && Arc.FileHead.RedirType!=FSREDIR_FILECOPY) |
| { |
| if (ExtrFile && Command!='P' && !Cmd->Test) |
| { |
| // Overwrite prompt for symbolic and hard links. |
| bool UserReject=false; |
| if (FileExist(DestFileName) && !UserReject) |
| FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); |
| if (UserReject) |
| ExtrFile=false; |
| } |
| } |
| else |
| if (Arc.IsArcDir()) |
| { |
| if (!ExtrFile || Command=='P' || Command=='I' || Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH) |
| return true; |
| TotalFileCount++; |
| ExtrCreateDir(Arc,ArcFileName); |
| // It is important to not increment MatchedArgs here, so we extract |
| // dir with its entire contents and not dir record only even if |
| // dir record precedes files. |
| return true; |
| } |
| else |
| if (ExtrFile) // Create files and file copies (FSREDIR_FILECOPY). |
| ExtrFile=ExtrCreateFile(Arc,CurFile); |
| |
| if (!ExtrFile && Arc.Solid) |
| { |
| SkipSolid=true; |
| ExtrFile=true; |
| |
| // We changed SkipSolid, so we need to call uiStartFileExtract |
| // with "Skip" parameter to change the operation status |
| // from "extracting" to "skipping". For example, it can be necessary |
| // if user answered "No" to overwrite prompt when unpacking |
| // a solid archive. |
| if (!uiStartFileExtract(ArcFileName,false,false,true)) |
| return false; |
| } |
| if (ExtrFile) |
| { |
| // Set it in test mode, so we also test subheaders such as NTFS streams |
| // after tested file. |
| if (Cmd->Test) |
| PrevProcessed=true; |
| |
| bool TestMode=Cmd->Test || SkipSolid; // Unpack to memory, not to disk. |
| |
| if (!SkipSolid) |
| { |
| if (!TestMode && Command!='P' && CurFile.IsDevice()) |
| { |
| uiMsg(UIERROR_INVALIDNAME,Arc.FileName,DestFileName); |
| ErrHandler.WriteError(Arc.FileName,DestFileName); |
| } |
| TotalFileCount++; |
| } |
| FileCount++; |
| if (Command!='I') |
| { |
| if (SkipSolid) |
| mprintf(St(MExtrSkipFile),ArcFileName); |
| else |
| switch(Cmd->Test ? 'T':Command) // "Test" can be also enabled by -t switch. |
| { |
| case 'T': |
| mprintf(St(MExtrTestFile),ArcFileName); |
| break; |
| #ifndef SFX_MODULE |
| case 'P': |
| mprintf(St(MExtrPrinting),ArcFileName); |
| break; |
| #endif |
| case 'X': |
| case 'E': |
| mprintf(St(MExtrFile),DestFileName); |
| break; |
| } |
| } |
| if (!Cmd->DisablePercentage) |
| mprintf(L" "); |
| |
| DataIO.CurUnpRead=0; |
| DataIO.CurUnpWrite=0; |
| DataIO.UnpHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads); |
| DataIO.PackedDataHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads); |
| DataIO.SetPackedSizeToRead(Arc.FileHead.PackSize); |
| DataIO.SetFiles(&Arc,&CurFile); |
| DataIO.SetTestMode(TestMode); |
| DataIO.SetSkipUnpCRC(SkipSolid); |
| |
| #if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT) |
| if (!TestMode && !Arc.BrokenHeader && |
| Arc.FileHead.UnpSize>0xffffffff && (Fat32 || !NotFat32)) |
| { |
| if (!Fat32) // Not detected yet. |
| NotFat32=!(Fat32=IsFAT(Cmd->ExtrPath)); |
| if (Fat32) |
| uiMsg(UIMSG_FAT32SIZE); // Inform user about FAT32 size limit. |
| } |
| #endif |
| |
| if (!TestMode && !Arc.BrokenHeader && |
| (Arc.FileHead.PackSize<<11)>Arc.FileHead.UnpSize && |
| (Arc.FileHead.UnpSize<100000000 || Arc.FileLength()>Arc.FileHead.PackSize)) |
| CurFile.Prealloc(Arc.FileHead.UnpSize); |
| |
| CurFile.SetAllowDelete(!Cmd->KeepBroken); |
| |
| bool FileCreateMode=!TestMode && !SkipSolid && Command!='P'; |
| bool ShowChecksum=true; // Display checksum verification result. |
| |
| bool LinkSuccess=true; // Assume success for test mode. |
| if (LinkEntry) |
| { |
| FILE_SYSTEM_REDIRECT Type=Arc.FileHead.RedirType; |
| |
| if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY) |
| { |
| wchar NameExisting[NM]; |
| ExtrPrepareName(Arc,Arc.FileHead.RedirName,NameExisting,ASIZE(NameExisting)); |
| if (FileCreateMode && *NameExisting!=0) // *NameExisting can be 0 in case of excessive -ap switch. |
| { |
| if (Type==FSREDIR_HARDLINK) |
| LinkSuccess=ExtractHardlink(DestFileName,NameExisting,ASIZE(NameExisting)); |
| else |
| LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,DestFileName,NameExisting,ASIZE(NameExisting)); |
| } |
| } |
| else |
| if (Type==FSREDIR_UNIXSYMLINK || Type==FSREDIR_WINSYMLINK || Type==FSREDIR_JUNCTION) |
| { |
| if (FileCreateMode) |
| LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName); |
| } |
| else |
| { |
| uiMsg(UIERROR_UNKNOWNEXTRA, Arc.FileName, DestFileName); |
| LinkSuccess=false; |
| } |
| |
| if (!LinkSuccess || (Arc.Format==RARFMT15 && !FileCreateMode)) |
| { |
| // RAR 5.x links have a valid data checksum even in case of |
| // failure, because they do not store any data. |
| // We do not want to display "OK" in this case. |
| // For 4.x symlinks we verify the checksum only when extracting, |
| // but not when testing an archive. |
| ShowChecksum=false; |
| } |
| PrevProcessed=FileCreateMode && LinkSuccess; |
| } |
| else |
| if (!Arc.FileHead.SplitBefore) |
| { |
| if (Arc.FileHead.Method==0) |
| UnstoreFile(DataIO,Arc.FileHead.UnpSize); |
| else |
| { |
| Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid); |
| Unp->SetDestSize(Arc.FileHead.UnpSize); |
| #ifndef SFX_MODULE |
| if (Arc.Format!=RARFMT50 && Arc.FileHead.UnpVer<=15) |
| Unp->DoUnpack(15,FileCount>1 && Arc.Solid); |
| else |
| #endif |
| Unp->DoUnpack(Arc.FileHead.UnpVer,Arc.FileHead.Solid); |
| } |
| } |
| |
| Arc.SeekToNext(); |
| |
| // We check for "split after" flag to detect partially extracted files |
| // from incomplete volume sets. For them file header contains packed |
| // data hash, which must not be compared against unpacked data hash |
| // to prevent accidental match. Moreover, for -m0 volumes packed data |
| // hash would match truncated unpacked data hash and lead to fake "OK" |
| // in incomplete volume set. |
| bool ValidCRC=!Arc.FileHead.SplitAfter && DataIO.UnpHash.Cmp(&Arc.FileHead.FileHash,Arc.FileHead.UseHashKey ? Arc.FileHead.HashKey:NULL); |
| |
| // We set AnySolidDataUnpackedWell to true if we found at least one |
| // valid non-zero solid file in preceding solid stream. If it is true |
| // and if current encrypted file is broken, we do not need to hint |
| // about a wrong password and can report CRC error only. |
| if (!Arc.FileHead.Solid) |
| AnySolidDataUnpackedWell=false; // Reset the flag, because non-solid file is found. |
| else |
| if (Arc.FileHead.Method!=0 && Arc.FileHead.UnpSize>0 && ValidCRC) |
| AnySolidDataUnpackedWell=true; |
| |
| bool BrokenFile=false; |
| |
| // Checksum is not calculated in skip solid mode for performance reason. |
| if (!SkipSolid && ShowChecksum) |
| { |
| if (ValidCRC) |
| { |
| if (Command!='P' && Command!='I') |
| mprintf(L"%s%s ",Cmd->DisablePercentage ? L" ":L"\b\b\b\b\b ", |
| Arc.FileHead.FileHash.Type==HASH_NONE ? L" ?":St(MOk)); |
| } |
| else |
| { |
| if (Arc.FileHead.Encrypted && (!Arc.FileHead.UsePswCheck || |
| Arc.BrokenHeader) && !AnySolidDataUnpackedWell) |
| uiMsg(UIERROR_CHECKSUMENC,Arc.FileName,ArcFileName); |
| else |
| uiMsg(UIERROR_CHECKSUM,Arc.FileName,ArcFileName); |
| BrokenFile=true; |
| ErrHandler.SetErrorCode(RARX_CRC); |
| #ifdef RARDLL |
| // If we already have ERAR_EOPEN as result of missing volume |
| // or ERAR_BAD_PASSWORD for RAR5 wrong password, |
| // we should not replace it with less precise ERAR_BAD_DATA. |
| if (Cmd->DllError!=ERAR_EOPEN && Cmd->DllError!=ERAR_BAD_PASSWORD) |
| Cmd->DllError=ERAR_BAD_DATA; |
| #endif |
| } |
| } |
| else |
| mprintf(L"\b\b\b\b\b "); |
| |
| if (!TestMode && (Command=='X' || Command=='E') && |
| (!LinkEntry || (Arc.FileHead.RedirType==FSREDIR_FILECOPY && LinkSuccess)) && |
| (!BrokenFile || Cmd->KeepBroken)) |
| { |
| // We could preallocate more space that really written to broken file. |
| if (BrokenFile) |
| CurFile.Truncate(); |
| |
| #if defined(_WIN_ALL) || defined(_EMX) |
| if (Cmd->ClearArc) |
| Arc.FileHead.FileAttr&=~FILE_ATTRIBUTE_ARCHIVE; |
| #endif |
| |
| |
| CurFile.SetOpenFileTime( |
| Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, |
| Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime, |
| Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); |
| CurFile.Close(); |
| #if defined(_WIN_ALL) && !defined(SFX_MODULE) |
| if (Cmd->SetCompressedAttr && |
| (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0) |
| SetFileCompression(CurFile.FileName,true); |
| #endif |
| SetFileHeaderExtra(Cmd,Arc,CurFile.FileName); |
| |
| CurFile.SetCloseFileTime( |
| Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, |
| Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); |
| if (!Cmd->IgnoreGeneralAttr && !SetFileAttr(CurFile.FileName,Arc.FileHead.FileAttr)) |
| uiMsg(UIERROR_FILEATTR,Arc.FileName,CurFile.FileName); |
| |
| PrevProcessed=true; |
| } |
| } |
| } |
| // It is important to increment it for files, but not dirs. So we extract |
| // dir with its entire contents, not just dir record only even if dir |
| // record precedes files. |
| if (MatchFound) |
| MatchedArgs++; |
| if (DataIO.NextVolumeMissing) |
| return false; |
| if (!ExtrFile) |
| { |
| if (!Arc.Solid) |
| Arc.SeekToNext(); |
| else |
| if (!SkipSolid) |
| return false; |
| } |
| return true; |
| } |
| |
| |
| void CmdExtract::UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize) |
| { |
| Array<byte> Buffer(File::CopyBufferSize()); |
| while (true) |
| { |
| int ReadSize=DataIO.UnpRead(&Buffer[0],Buffer.Size()); |
| if (ReadSize<=0) |
| break; |
| int WriteSize=ReadSize<DestUnpSize ? ReadSize:(int)DestUnpSize; |
| if (WriteSize>0) |
| { |
| DataIO.UnpWrite(&Buffer[0],WriteSize); |
| DestUnpSize-=WriteSize; |
| } |
| } |
| } |
| |
| |
| bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize) |
| { |
| SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives. |
| |
| File Existing; |
| if (!Existing.WOpen(NameExisting)) |
| { |
| uiMsg(UIERROR_FILECOPY,ArcName,NameExisting,NameNew); |
| uiMsg(UIERROR_FILECOPYHINT,ArcName); |
| #ifdef RARDLL |
| Cmd->DllError=ERAR_EREFERENCE; |
| #endif |
| return false; |
| } |
| |
| Array<char> Buffer(0x100000); |
| int64 CopySize=0; |
| |
| while (true) |
| { |
| Wait(); |
| int ReadSize=Existing.Read(&Buffer[0],Buffer.Size()); |
| if (ReadSize==0) |
| break; |
| New.Write(&Buffer[0],ReadSize); |
| CopySize+=ReadSize; |
| } |
| |
| return true; |
| } |
| |
| |
| void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize) |
| { |
| wcsncpyz(DestName,Cmd->ExtrPath,DestSize); |
| |
| if (*Cmd->ExtrPath!=0) |
| { |
| wchar LastChar=*PointToLastChar(Cmd->ExtrPath); |
| // We need IsPathDiv check here to correctly handle Unix forward slash |
| // in the end of destination path in Windows: rar x arc dest/ |
| // IsDriveDiv is needed for current drive dir: rar x arc d: |
| if (!IsPathDiv(LastChar) && !IsDriveDiv(LastChar)) |
| { |
| // Destination path can be without trailing slash if it come from GUI shell. |
| AddEndSlash(DestName,DestSize); |
| } |
| } |
| |
| #ifndef SFX_MODULE |
| if (Cmd->AppendArcNameToPath) |
| { |
| wcsncatz(DestName,PointToName(Arc.FirstVolumeName),DestSize); |
| SetExt(DestName,NULL,DestSize); |
| AddEndSlash(DestName,DestSize); |
| } |
| #endif |
| |
| #ifndef SFX_MODULE |
| size_t ArcPathLength=wcslen(Cmd->ArcPath); |
| if (ArcPathLength>0) |
| { |
| size_t NameLength=wcslen(ArcFileName); |
| |
| // Earlier we compared lengths only here, but then noticed a cosmetic bug |
| // in WinRAR. When extracting a file reference from subfolder with |
| // "Extract relative paths", so WinRAR sets ArcPath, if reference target |
| // is missing, error message removed ArcPath both from reference and target |
| // names. If target was stored in another folder, its name looked wrong. |
| if (NameLength>=ArcPathLength && |
| wcsnicompc(Cmd->ArcPath,ArcFileName,ArcPathLength)==0 && |
| (IsPathDiv(Cmd->ArcPath[ArcPathLength-1]) || |
| IsPathDiv(ArcFileName[ArcPathLength]) || ArcFileName[ArcPathLength]==0)) |
| { |
| ArcFileName+=Min(ArcPathLength,NameLength); |
| while (IsPathDiv(*ArcFileName)) |
| ArcFileName++; |
| if (*ArcFileName==0) // Excessive -ap switch. |
| { |
| *DestName=0; |
| return; |
| } |
| } |
| } |
| #endif |
| |
| wchar Command=Cmd->Command[0]; |
| // Use -ep3 only in systems, where disk letters are exist, not in Unix. |
| bool AbsPaths=Cmd->ExclPath==EXCL_ABSPATH && Command=='X' && IsDriveDiv(':'); |
| |
| // We do not use any user specified destination paths when extracting |
| // absolute paths in -ep3 mode. |
| if (AbsPaths) |
| *DestName=0; |
| |
| if (Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH) |
| wcsncatz(DestName,PointToName(ArcFileName),DestSize); |
| else |
| wcsncatz(DestName,ArcFileName,DestSize); |
| |
| #ifdef _WIN_ALL |
| // Must do after Cmd->ArcPath processing above, so file name and arc path |
| // trailing spaces are in sync. |
| if (!Cmd->AllowIncompatNames) |
| MakeNameCompatible(DestName); |
| #endif |
| |
| wchar DiskLetter=toupperw(DestName[0]); |
| |
| if (AbsPaths) |
| { |
| if (DestName[1]=='_' && IsPathDiv(DestName[2]) && |
| DiskLetter>='A' && DiskLetter<='Z') |
| DestName[1]=':'; |
| else |
| if (DestName[0]=='_' && DestName[1]=='_') |
| { |
| // Convert __server\share to \\server\share. |
| DestName[0]=CPATHDIVIDER; |
| DestName[1]=CPATHDIVIDER; |
| } |
| } |
| } |
| |
| |
| #ifdef RARDLL |
| bool CmdExtract::ExtrDllGetPassword() |
| { |
| if (!Cmd->Password.IsSet()) |
| { |
| if (Cmd->Callback!=NULL) |
| { |
| wchar PasswordW[MAXPASSWORD]; |
| *PasswordW=0; |
| if (Cmd->Callback(UCM_NEEDPASSWORDW,Cmd->UserData,(LPARAM)PasswordW,ASIZE(PasswordW))==-1) |
| *PasswordW=0; |
| if (*PasswordW==0) |
| { |
| char PasswordA[MAXPASSWORD]; |
| *PasswordA=0; |
| if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1) |
| *PasswordA=0; |
| GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW)); |
| cleandata(PasswordA,sizeof(PasswordA)); |
| } |
| Cmd->Password.Set(PasswordW); |
| cleandata(PasswordW,sizeof(PasswordW)); |
| Cmd->ManualPassword=true; |
| } |
| if (!Cmd->Password.IsSet()) |
| return false; |
| } |
| return true; |
| } |
| #endif |
| |
| |
| #ifndef RARDLL |
| bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName) |
| { |
| if (!Cmd->Password.IsSet()) |
| { |
| if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)/* || !Cmd->Password.IsSet()*/) |
| { |
| // Suppress "test is ok" message if user cancelled the password prompt. |
| uiMsg(UIERROR_INCERRCOUNT); |
| return false; |
| } |
| Cmd->ManualPassword=true; |
| } |
| #if !defined(SILENT) |
| else |
| if (!GlobalPassword && !Arc.FileHead.Solid) |
| { |
| eprintf(St(MUseCurPsw),ArcFileName); |
| switch(Cmd->AllYes ? 1 : Ask(St(MYesNoAll))) |
| { |
| case -1: |
| ErrHandler.Exit(RARX_USERBREAK); |
| case 2: |
| if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password)) |
| return false; |
| break; |
| case 3: |
| GlobalPassword=true; |
| break; |
| } |
| } |
| #endif |
| return true; |
| } |
| #endif |
| |
| |
| #if defined(_WIN_ALL) && !defined(SFX_MODULE) |
| void CmdExtract::ConvertDosPassword(Archive &Arc,SecPassword &DestPwd) |
| { |
| if (Arc.Format==RARFMT15 && Arc.FileHead.HostOS==HOST_MSDOS) |
| { |
| // We need the password in OEM encoding if file was encrypted by |
| // native RAR/DOS (not extender based). Let's make the conversion. |
| wchar PlainPsw[MAXPASSWORD]; |
| Cmd->Password.Get(PlainPsw,ASIZE(PlainPsw)); |
| char PswA[MAXPASSWORD]; |
| CharToOemBuffW(PlainPsw,PswA,ASIZE(PswA)); |
| PswA[ASIZE(PswA)-1]=0; |
| CharToWide(PswA,PlainPsw,ASIZE(PlainPsw)); |
| DestPwd.Set(PlainPsw); |
| cleandata(PlainPsw,sizeof(PlainPsw)); |
| cleandata(PswA,sizeof(PswA)); |
| } |
| } |
| #endif |
| |
| |
| void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName) |
| { |
| if (Cmd->Test) |
| { |
| mprintf(St(MExtrTestFile),ArcFileName); |
| mprintf(L" %s",St(MOk)); |
| return; |
| } |
| |
| MKDIR_CODE MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); |
| bool DirExist=false; |
| if (MDCode!=MKDIR_SUCCESS) |
| { |
| DirExist=FileExist(DestFileName); |
| if (DirExist && !IsDir(GetFileAttr(DestFileName))) |
| { |
| // File with name same as this directory exists. Propose user |
| // to overwrite it. |
| bool UserReject; |
| FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime); |
| DirExist=false; |
| } |
| if (!DirExist) |
| { |
| CreatePath(DestFileName,true); |
| MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); |
| if (MDCode!=MKDIR_SUCCESS) |
| { |
| wchar OrigName[ASIZE(DestFileName)]; |
| wcsncpyz(OrigName,DestFileName,ASIZE(OrigName)); |
| MakeNameUsable(DestFileName,true); |
| CreatePath(DestFileName,true); |
| MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr); |
| #ifndef SFX_MODULE |
| if (MDCode==MKDIR_SUCCESS) |
| uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); |
| #endif |
| } |
| } |
| } |
| if (MDCode==MKDIR_SUCCESS) |
| { |
| mprintf(St(MCreatDir),DestFileName); |
| mprintf(L" %s",St(MOk)); |
| PrevProcessed=true; |
| } |
| else |
| if (DirExist) |
| { |
| if (!Cmd->IgnoreGeneralAttr) |
| SetFileAttr(DestFileName,Arc.FileHead.FileAttr); |
| PrevProcessed=true; |
| } |
| else |
| { |
| uiMsg(UIERROR_DIRCREATE,Arc.FileName,DestFileName); |
| ErrHandler.SysErrMsg(); |
| #ifdef RARDLL |
| Cmd->DllError=ERAR_ECREATE; |
| #endif |
| ErrHandler.SetErrorCode(RARX_CREATE); |
| } |
| if (PrevProcessed) |
| { |
| #if defined(_WIN_ALL) && !defined(SFX_MODULE) |
| if (Cmd->SetCompressedAttr && |
| (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0 && WinNT()) |
| SetFileCompression(DestFileName,true); |
| #endif |
| SetFileHeaderExtra(Cmd,Arc,DestFileName); |
| SetDirTime(DestFileName, |
| Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime, |
| Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime, |
| Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime); |
| } |
| } |
| |
| |
| bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile) |
| { |
| bool Success=true; |
| wchar Command=Cmd->Command[0]; |
| #if !defined(SFX_MODULE) |
| if (Command=='P') |
| CurFile.SetHandleType(FILE_HANDLESTD); |
| #endif |
| if ((Command=='E' || Command=='X') && !Cmd->Test) |
| { |
| bool UserReject; |
| // Specify "write only" mode to avoid OpenIndiana NAS problems |
| // with SetFileTime and read+write files. |
| if (!FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) |
| { |
| Success=false; |
| if (!UserReject) |
| { |
| ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName); |
| #ifdef RARDLL |
| Cmd->DllError=ERAR_ECREATE; |
| #endif |
| if (!IsNameUsable(DestFileName)) |
| { |
| uiMsg(UIMSG_CORRECTINGNAME,Arc.FileName); |
| |
| wchar OrigName[ASIZE(DestFileName)]; |
| wcsncpyz(OrigName,DestFileName,ASIZE(OrigName)); |
| |
| MakeNameUsable(DestFileName,true); |
| |
| CreatePath(DestFileName,true); |
| if (FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true)) |
| { |
| #ifndef SFX_MODULE |
| uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName); |
| #endif |
| Success=true; |
| } |
| else |
| ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName); |
| } |
| } |
| } |
| } |
| return Success; |
| } |
| |
| |
| bool CmdExtract::CheckUnpVer(Archive &Arc,const wchar *ArcFileName) |
| { |
| bool WrongVer; |
| if (Arc.Format==RARFMT50) // Both SFX and RAR can unpack RAR 5.0 archives. |
| WrongVer=Arc.FileHead.UnpVer>VER_UNPACK5; |
| else |
| { |
| #ifdef SFX_MODULE // SFX can unpack only RAR 2.9 archives. |
| WrongVer=Arc.FileHead.UnpVer!=VER_UNPACK; |
| #else // All formats since 1.3 for RAR. |
| WrongVer=Arc.FileHead.UnpVer<13 || Arc.FileHead.UnpVer>VER_UNPACK; |
| #endif |
| } |
| |
| // We can unpack stored files regardless of compression version field. |
| if (Arc.FileHead.Method==0) |
| WrongVer=false; |
| |
| if (WrongVer) |
| { |
| ErrHandler.UnknownMethodMsg(Arc.FileName,ArcFileName); |
| uiMsg(UIERROR_NEWERRAR,Arc.FileName); |
| } |
| return !WrongVer; |
| } |