Home | History | Annotate | Download | only in Common
      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/PropVariant.h"
     11 #include "../../../Windows/PropVariantConv.h"
     12 
     13 #include "../Common/ExtractingFilePath.h"
     14 
     15 #include "Extract.h"
     16 #include "SetProperties.h"
     17 
     18 using namespace NWindows;
     19 using namespace NFile;
     20 using namespace NDir;
     21 
     22 static HRESULT DecompressArchive(
     23     CCodecs *codecs,
     24     const CArchiveLink &arcLink,
     25     UInt64 packSize,
     26     const NWildcard::CCensorNode &wildcardCensor,
     27     const CExtractOptions &options,
     28     bool calcCrc,
     29     IExtractCallbackUI *callback,
     30     CArchiveExtractCallback *ecs,
     31     UString &errorMessage,
     32     UInt64 &stdInProcessed)
     33 {
     34   const CArc &arc = arcLink.Arcs.Back();
     35   stdInProcessed = 0;
     36   IInArchive *archive = arc.Archive;
     37   CRecordVector<UInt32> realIndices;
     38 
     39   UStringVector removePathParts;
     40 
     41   FString outDir = options.OutputDir;
     42   UString replaceName = arc.DefaultName;
     43 
     44   if (arcLink.Arcs.Size() > 1)
     45   {
     46     // Most "pe" archives have same name of archive subfile "[0]" or ".rsrc_1".
     47     // So it extracts different archives to one folder.
     48     // We will use top level archive name
     49     const CArc &arc0 = arcLink.Arcs[0];
     50     if (StringsAreEqualNoCase_Ascii(codecs->Formats[arc0.FormatIndex].Name, "pe"))
     51       replaceName = arc0.DefaultName;
     52   }
     53 
     54   outDir.Replace(FSTRING_ANY_MASK, us2fs(Get_Correct_FsFile_Name(replaceName)));
     55 
     56   bool elimIsPossible = false;
     57   UString elimPrefix; // only pure name without dir delimiter
     58   FString outDirReduced = outDir;
     59 
     60   if (options.ElimDup.Val && options.PathMode != NExtract::NPathMode::kAbsPaths)
     61   {
     62     UString dirPrefix;
     63     SplitPathToParts_Smart(fs2us(outDir), dirPrefix, elimPrefix);
     64     if (!elimPrefix.IsEmpty())
     65     {
     66       if (IsPathSepar(elimPrefix.Back()))
     67         elimPrefix.DeleteBack();
     68       if (!elimPrefix.IsEmpty())
     69       {
     70         outDirReduced = us2fs(dirPrefix);
     71         elimIsPossible = true;
     72       }
     73     }
     74   }
     75 
     76   bool allFilesAreAllowed = wildcardCensor.AreAllAllowed();
     77 
     78   if (!options.StdInMode)
     79   {
     80     UInt32 numItems;
     81     RINOK(archive->GetNumberOfItems(&numItems));
     82 
     83     CReadArcItem item;
     84 
     85     for (UInt32 i = 0; i < numItems; i++)
     86     {
     87       if (elimIsPossible || !allFilesAreAllowed)
     88       {
     89         RINOK(arc.GetItem(i, item));
     90       }
     91       else
     92       {
     93         #ifdef SUPPORT_ALT_STREAMS
     94         item.IsAltStream = false;
     95         if (!options.NtOptions.AltStreams.Val && arc.Ask_AltStream)
     96         {
     97           RINOK(Archive_IsItem_AltStream(arc.Archive, i, item.IsAltStream));
     98         }
     99         #endif
    100       }
    101 
    102       #ifdef SUPPORT_ALT_STREAMS
    103       if (!options.NtOptions.AltStreams.Val && item.IsAltStream)
    104         continue;
    105       #endif
    106 
    107       if (elimIsPossible)
    108       {
    109         const UString &s =
    110           #ifdef SUPPORT_ALT_STREAMS
    111             item.MainPath;
    112           #else
    113             item.Path;
    114           #endif
    115         if (!IsPath1PrefixedByPath2(s, elimPrefix))
    116           elimIsPossible = false;
    117         else
    118         {
    119           wchar_t c = s[elimPrefix.Len()];
    120           if (c == 0)
    121           {
    122             if (!item.MainIsDir)
    123               elimIsPossible = false;
    124           }
    125           else if (!IsPathSepar(c))
    126             elimIsPossible = false;
    127         }
    128       }
    129 
    130       if (!allFilesAreAllowed)
    131       {
    132         if (!CensorNode_CheckPath(wildcardCensor, item))
    133           continue;
    134       }
    135 
    136       realIndices.Add(i);
    137     }
    138 
    139     if (realIndices.Size() == 0)
    140     {
    141       callback->ThereAreNoFiles();
    142       return callback->ExtractResult(S_OK);
    143     }
    144   }
    145 
    146   if (elimIsPossible)
    147   {
    148     removePathParts.Add(elimPrefix);
    149     // outDir = outDirReduced;
    150   }
    151 
    152   #ifdef _WIN32
    153   // GetCorrectFullFsPath doesn't like "..".
    154   // outDir.TrimRight();
    155   // outDir = GetCorrectFullFsPath(outDir);
    156   #endif
    157 
    158   if (outDir.IsEmpty())
    159     outDir = FTEXT(".") FSTRING_PATH_SEPARATOR;
    160   /*
    161   #ifdef _WIN32
    162   else if (NName::IsAltPathPrefix(outDir)) {}
    163   #endif
    164   */
    165   else if (!CreateComplexDir(outDir))
    166   {
    167     HRESULT res = ::GetLastError();
    168     if (res == S_OK)
    169       res = E_FAIL;
    170     errorMessage.SetFromAscii("Can not create output directory: ");
    171     errorMessage += fs2us(outDir);
    172     return res;
    173   }
    174 
    175   ecs->Init(
    176       options.NtOptions,
    177       options.StdInMode ? &wildcardCensor : NULL,
    178       &arc,
    179       callback,
    180       options.StdOutMode, options.TestMode,
    181       outDir,
    182       removePathParts, false,
    183       packSize);
    184 
    185 
    186   #ifdef SUPPORT_LINKS
    187 
    188   if (!options.StdInMode &&
    189       !options.TestMode &&
    190       options.NtOptions.HardLinks.Val)
    191   {
    192     RINOK(ecs->PrepareHardLinks(&realIndices));
    193   }
    194 
    195   #endif
    196 
    197 
    198   HRESULT result;
    199   Int32 testMode = (options.TestMode && !calcCrc) ? 1: 0;
    200   if (options.StdInMode)
    201   {
    202     result = archive->Extract(NULL, (UInt32)(Int32)-1, testMode, ecs);
    203     NCOM::CPropVariant prop;
    204     if (archive->GetArchiveProperty(kpidPhySize, &prop) == S_OK)
    205       ConvertPropVariantToUInt64(prop, stdInProcessed);
    206   }
    207   else
    208     result = archive->Extract(&realIndices.Front(), realIndices.Size(), testMode, ecs);
    209   if (result == S_OK && !options.StdInMode)
    210     result = ecs->SetDirsTimes();
    211   return callback->ExtractResult(result);
    212 }
    213 
    214 /* v9.31: BUG was fixed:
    215    Sorted list for file paths was sorted with case insensitive compare function.
    216    But FindInSorted function did binary search via case sensitive compare function */
    217 
    218 int Find_FileName_InSortedVector(const UStringVector &fileName, const UString &name)
    219 {
    220   unsigned left = 0, right = fileName.Size();
    221   while (left != right)
    222   {
    223     unsigned mid = (left + right) / 2;
    224     const UString &midValue = fileName[mid];
    225     int compare = CompareFileNames(name, midValue);
    226     if (compare == 0)
    227       return mid;
    228     if (compare < 0)
    229       right = mid;
    230     else
    231       left = mid + 1;
    232   }
    233   return -1;
    234 }
    235 
    236 HRESULT Extract(
    237     CCodecs *codecs,
    238     const CObjectVector<COpenType> &types,
    239     const CIntVector &excludedFormats,
    240     UStringVector &arcPaths, UStringVector &arcPathsFull,
    241     const NWildcard::CCensorNode &wildcardCensor,
    242     const CExtractOptions &options,
    243     IOpenCallbackUI *openCallback,
    244     IExtractCallbackUI *extractCallback,
    245     #ifndef _SFX
    246     IHashCalc *hash,
    247     #endif
    248     UString &errorMessage,
    249     CDecompressStat &st)
    250 {
    251   st.Clear();
    252   UInt64 totalPackSize = 0;
    253   CRecordVector<UInt64> arcSizes;
    254 
    255   unsigned numArcs = options.StdInMode ? 1 : arcPaths.Size();
    256 
    257   unsigned i;
    258 
    259   for (i = 0; i < numArcs; i++)
    260   {
    261     NFind::CFileInfo fi;
    262     fi.Size = 0;
    263     if (!options.StdInMode)
    264     {
    265       const FString &arcPath = us2fs(arcPaths[i]);
    266       if (!fi.Find(arcPath))
    267         throw "there is no such archive";
    268       if (fi.IsDir())
    269         throw "can't decompress folder";
    270     }
    271     arcSizes.Add(fi.Size);
    272     totalPackSize += fi.Size;
    273   }
    274 
    275   CBoolArr skipArcs(numArcs);
    276   for (i = 0; i < numArcs; i++)
    277     skipArcs[i] = false;
    278 
    279   CArchiveExtractCallback *ecs = new CArchiveExtractCallback;
    280   CMyComPtr<IArchiveExtractCallback> ec(ecs);
    281   bool multi = (numArcs > 1);
    282   ecs->InitForMulti(multi, options.PathMode, options.OverwriteMode);
    283   #ifndef _SFX
    284   ecs->SetHashMethods(hash);
    285   #endif
    286 
    287   if (multi)
    288   {
    289     RINOK(extractCallback->SetTotal(totalPackSize));
    290   }
    291 
    292   UInt64 totalPackProcessed = 0;
    293   bool thereAreNotOpenArcs = false;
    294 
    295   for (i = 0; i < numArcs; i++)
    296   {
    297     if (skipArcs[i])
    298       continue;
    299 
    300     const UString &arcPath = arcPaths[i];
    301     NFind::CFileInfo fi;
    302     if (options.StdInMode)
    303     {
    304       fi.Size = 0;
    305       fi.Attrib = 0;
    306     }
    307     else
    308     {
    309       if (!fi.Find(us2fs(arcPath)) || fi.IsDir())
    310         throw "there is no such archive";
    311     }
    312 
    313     /*
    314     #ifndef _NO_CRYPTO
    315     openCallback->Open_Clear_PasswordWasAsked_Flag();
    316     #endif
    317     */
    318 
    319     RINOK(extractCallback->BeforeOpen(arcPath, options.TestMode));
    320     CArchiveLink arcLink;
    321 
    322     CObjectVector<COpenType> types2 = types;
    323     /*
    324     #ifndef _SFX
    325     if (types.IsEmpty())
    326     {
    327       int pos = arcPath.ReverseFind(L'.');
    328       if (pos >= 0)
    329       {
    330         UString s = arcPath.Ptr(pos + 1);
    331         int index = codecs->FindFormatForExtension(s);
    332         if (index >= 0 && s == L"001")
    333         {
    334           s = arcPath.Left(pos);
    335           pos = s.ReverseFind(L'.');
    336           if (pos >= 0)
    337           {
    338             int index2 = codecs->FindFormatForExtension(s.Ptr(pos + 1));
    339             if (index2 >= 0) // && s.CompareNoCase(L"rar") != 0
    340             {
    341               types2.Add(index2);
    342               types2.Add(index);
    343             }
    344           }
    345         }
    346       }
    347     }
    348     #endif
    349     */
    350 
    351     COpenOptions op;
    352     #ifndef _SFX
    353     op.props = &options.Properties;
    354     #endif
    355     op.codecs = codecs;
    356     op.types = &types2;
    357     op.excludedFormats = &excludedFormats;
    358     op.stdInMode = options.StdInMode;
    359     op.stream = NULL;
    360     op.filePath = arcPath;
    361 
    362     HRESULT result = arcLink.Open_Strict(op, openCallback);
    363 
    364     if (result == E_ABORT)
    365       return result;
    366 
    367     // arcLink.Set_ErrorsText();
    368     RINOK(extractCallback->OpenResult(codecs, arcLink, arcPath, result));
    369 
    370     if (result != S_OK)
    371     {
    372       thereAreNotOpenArcs = true;
    373       if (!options.StdInMode)
    374       {
    375         NFind::CFileInfo fi2;
    376         if (fi2.Find(us2fs(arcPath)))
    377           if (!fi2.IsDir())
    378             totalPackProcessed += fi2.Size;
    379       }
    380       continue;
    381     }
    382 
    383     if (!options.StdInMode)
    384     {
    385       // numVolumes += arcLink.VolumePaths.Size();
    386       // arcLink.VolumesSize;
    387 
    388       // totalPackSize -= DeleteUsedFileNamesFromList(arcLink, i + 1, arcPaths, arcPathsFull, &arcSizes);
    389       // numArcs = arcPaths.Size();
    390       if (arcLink.VolumePaths.Size() != 0)
    391       {
    392         Int64 correctionSize = arcLink.VolumesSize;
    393         FOR_VECTOR (v, arcLink.VolumePaths)
    394         {
    395           int index = Find_FileName_InSortedVector(arcPathsFull, arcLink.VolumePaths[v]);
    396           if (index >= 0)
    397           {
    398             if ((unsigned)index > i)
    399             {
    400               skipArcs[(unsigned)index] = true;
    401               correctionSize -= arcSizes[(unsigned)index];
    402             }
    403           }
    404         }
    405         if (correctionSize != 0)
    406         {
    407           Int64 newPackSize = (Int64)totalPackSize + correctionSize;
    408           if (newPackSize < 0)
    409             newPackSize = 0;
    410           totalPackSize = newPackSize;
    411           RINOK(extractCallback->SetTotal(totalPackSize));
    412         }
    413       }
    414     }
    415 
    416     /*
    417     // Now openCallback and extractCallback use same object. So we don't need to send password.
    418 
    419     #ifndef _NO_CRYPTO
    420     bool passwordIsDefined;
    421     UString password;
    422     RINOK(openCallback->Open_GetPasswordIfAny(passwordIsDefined, password));
    423     if (passwordIsDefined)
    424     {
    425       RINOK(extractCallback->SetPassword(password));
    426     }
    427     #endif
    428     */
    429 
    430     CArc &arc = arcLink.Arcs.Back();
    431     arc.MTimeDefined = (!options.StdInMode && !fi.IsDevice);
    432     arc.MTime = fi.MTime;
    433 
    434     UInt64 packProcessed;
    435     bool calcCrc =
    436         #ifndef _SFX
    437           (hash != NULL);
    438         #else
    439           false;
    440         #endif
    441 
    442     RINOK(DecompressArchive(
    443         codecs,
    444         arcLink,
    445         fi.Size + arcLink.VolumesSize,
    446         wildcardCensor,
    447         options,
    448         calcCrc,
    449         extractCallback, ecs, errorMessage, packProcessed));
    450 
    451     if (!options.StdInMode)
    452       packProcessed = fi.Size + arcLink.VolumesSize;
    453     totalPackProcessed += packProcessed;
    454     ecs->LocalProgressSpec->InSize += packProcessed;
    455     ecs->LocalProgressSpec->OutSize = ecs->UnpackSize;
    456     if (!errorMessage.IsEmpty())
    457       return E_FAIL;
    458   }
    459 
    460   if (multi || thereAreNotOpenArcs)
    461   {
    462     RINOK(extractCallback->SetTotal(totalPackSize));
    463     RINOK(extractCallback->SetCompleted(&totalPackProcessed));
    464   }
    465 
    466   st.NumFolders = ecs->NumFolders;
    467   st.NumFiles = ecs->NumFiles;
    468   st.NumAltStreams = ecs->NumAltStreams;
    469   st.UnpackSize = ecs->UnpackSize;
    470   st.AltStreams_UnpackSize = ecs->AltStreams_UnpackSize;
    471   st.NumArchives = arcPaths.Size();
    472   st.PackSize = ecs->LocalProgressSpec->InSize;
    473   return S_OK;
    474 }
    475