Home | History | Annotate | Download | only in Common
      1 // ArchiveExtractCallback.cpp
      2 
      3 #include "StdAfx.h"
      4 
      5 #undef sprintf
      6 #undef printf
      7 
      8 #include "../../../Common/ComTry.h"
      9 #include "../../../Common/StringConvert.h"
     10 #include "../../../Common/Wildcard.h"
     11 
     12 #include "../../../Windows/FileDir.h"
     13 #include "../../../Windows/FileFind.h"
     14 #include "../../../Windows/FileName.h"
     15 #include "../../../Windows/PropVariant.h"
     16 #include "../../../Windows/PropVariantConv.h"
     17 
     18 #if defined(_WIN32) && !defined(UNDER_CE)  && !defined(_SFX)
     19 #define _USE_SECURITY_CODE
     20 #include "../../../Windows/SecurityUtils.h"
     21 #endif
     22 
     23 #include "../../Common/FilePathAutoRename.h"
     24 
     25 #include "../Common/ExtractingFilePath.h"
     26 #include "../Common/PropIDUtils.h"
     27 
     28 #include "ArchiveExtractCallback.h"
     29 
     30 using namespace NWindows;
     31 using namespace NFile;
     32 using namespace NDir;
     33 
     34 static const char *kCantAutoRename = "Can not create file with auto name";
     35 static const char *kCantRenameFile = "Can not rename existing file";
     36 static const char *kCantDeleteOutputFile = "Can not delete output file";
     37 static const char *kCantDeleteOutputDir = "Can not delete output folder";
     38 
     39 
     40 #ifndef _SFX
     41 
     42 STDMETHODIMP COutStreamWithHash::Write(const void *data, UInt32 size, UInt32 *processedSize)
     43 {
     44   HRESULT result = S_OK;
     45   if (_stream)
     46     result = _stream->Write(data, size, &size);
     47   if (_calculate)
     48     _hash->Update(data, size);
     49   _size += size;
     50   if (processedSize)
     51     *processedSize = size;
     52   return result;
     53 }
     54 
     55 #endif
     56 
     57 #ifdef _USE_SECURITY_CODE
     58 bool InitLocalPrivileges()
     59 {
     60   NSecurity::CAccessToken token;
     61   if (!token.OpenProcessToken(GetCurrentProcess(),
     62       TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES))
     63     return false;
     64 
     65   TOKEN_PRIVILEGES tp;
     66 
     67   tp.PrivilegeCount = 1;
     68   tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
     69 
     70   if  (!::LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &tp.Privileges[0].Luid))
     71     return false;
     72   if (!token.AdjustPrivileges(&tp))
     73     return false;
     74   return (GetLastError() == ERROR_SUCCESS);
     75 }
     76 #endif
     77 
     78 #ifdef SUPPORT_LINKS
     79 
     80 int CHardLinkNode::Compare(const CHardLinkNode &a) const
     81 {
     82   if (StreamId < a.StreamId) return -1;
     83   if (StreamId > a.StreamId) return 1;
     84   return MyCompare(INode, a.INode);
     85 }
     86 
     87 HRESULT Archive_Get_HardLinkNode(IInArchive *archive, UInt32 index, CHardLinkNode &h, bool &defined)
     88 {
     89   h.INode = 0;
     90   h.StreamId = (UInt64)(Int64)-1;
     91   defined = false;
     92   {
     93     NCOM::CPropVariant prop;
     94     RINOK(archive->GetProperty(index, kpidINode, &prop));
     95     if (!ConvertPropVariantToUInt64(prop, h.INode))
     96       return S_OK;
     97   }
     98   {
     99     NCOM::CPropVariant prop;
    100     RINOK(archive->GetProperty(index, kpidStreamId, &prop));
    101     ConvertPropVariantToUInt64(prop, h.StreamId);
    102   }
    103   defined = true;
    104   return S_OK;
    105 }
    106 
    107 
    108 HRESULT CArchiveExtractCallback::PrepareHardLinks(const CRecordVector<UInt32> *realIndices)
    109 {
    110   _hardLinks.Clear();
    111 
    112   if (!_arc->Ask_INode)
    113     return S_OK;
    114 
    115   IInArchive *archive = _arc->Archive;
    116   CRecordVector<CHardLinkNode> &hardIDs = _hardLinks.IDs;
    117 
    118   {
    119     UInt32 numItems;
    120     if (realIndices)
    121       numItems = realIndices->Size();
    122     else
    123     {
    124       RINOK(archive->GetNumberOfItems(&numItems));
    125     }
    126 
    127     for (UInt32 i = 0; i < numItems; i++)
    128     {
    129       CHardLinkNode h;
    130       bool defined;
    131       RINOK(Archive_Get_HardLinkNode(archive, realIndices ? (*realIndices)[i] : i, h, defined));
    132       if (defined)
    133         hardIDs.Add(h);
    134     }
    135   }
    136 
    137   hardIDs.Sort2();
    138 
    139   {
    140     // wee keep only items that have 2 or more items
    141     unsigned k = 0;
    142     unsigned numSame = 1;
    143     for (unsigned i = 1; i < hardIDs.Size(); i++)
    144     {
    145       if (hardIDs[i].Compare(hardIDs[i - 1]) != 0)
    146         numSame = 1;
    147       else if (++numSame == 2)
    148       {
    149         if (i - 1 != k)
    150           hardIDs[k] = hardIDs[i - 1];
    151         k++;
    152       }
    153     }
    154     hardIDs.DeleteFrom(k);
    155   }
    156 
    157   _hardLinks.PrepareLinks();
    158   return S_OK;
    159 }
    160 
    161 #endif
    162 
    163 CArchiveExtractCallback::CArchiveExtractCallback():
    164     WriteCTime(true),
    165     WriteATime(true),
    166     WriteMTime(true),
    167     _multiArchives(false)
    168 {
    169   LocalProgressSpec = new CLocalProgress();
    170   _localProgress = LocalProgressSpec;
    171 
    172   #ifdef _USE_SECURITY_CODE
    173   _saclEnabled = InitLocalPrivileges();
    174   #endif
    175 }
    176 
    177 void CArchiveExtractCallback::Init(
    178     const CExtractNtOptions &ntOptions,
    179     const NWildcard::CCensorNode *wildcardCensor,
    180     const CArc *arc,
    181     IFolderArchiveExtractCallback *extractCallback2,
    182     bool stdOutMode, bool testMode,
    183     const FString &directoryPath,
    184     const UStringVector &removePathParts,
    185     UInt64 packSize)
    186 {
    187   _extractedFolderPaths.Clear();
    188   _extractedFolderIndices.Clear();
    189 
    190   #ifdef SUPPORT_LINKS
    191   _hardLinks.Clear();
    192   #endif
    193 
    194   _ntOptions = ntOptions;
    195   _wildcardCensor = wildcardCensor;
    196 
    197   _stdOutMode = stdOutMode;
    198   _testMode = testMode;
    199   _unpTotal = 1;
    200   _packTotal = packSize;
    201 
    202   _extractCallback2 = extractCallback2;
    203   _compressProgress.Release();
    204   _extractCallback2.QueryInterface(IID_ICompressProgressInfo, &_compressProgress);
    205 
    206   #ifndef _SFX
    207 
    208   _extractCallback2.QueryInterface(IID_IFolderExtractToStreamCallback, &ExtractToStreamCallback);
    209   if (ExtractToStreamCallback)
    210   {
    211     Int32 useStreams = 0;
    212     if (ExtractToStreamCallback->UseExtractToStream(&useStreams) != S_OK)
    213       useStreams = 0;
    214     if (useStreams == 0)
    215       ExtractToStreamCallback.Release();
    216   }
    217 
    218   #endif
    219 
    220   LocalProgressSpec->Init(extractCallback2, true);
    221   LocalProgressSpec->SendProgress = false;
    222 
    223   _removePathParts = removePathParts;
    224   _baseParentFolder = (UInt32)(Int32)-1;
    225   _use_baseParentFolder_mode = false;
    226 
    227   _arc = arc;
    228   _directoryPath = directoryPath;
    229   NName::NormalizeDirPathPrefix(_directoryPath);
    230   NDir::MyGetFullPathName(directoryPath, _directoryPathFull);
    231 }
    232 
    233 STDMETHODIMP CArchiveExtractCallback::SetTotal(UInt64 size)
    234 {
    235   COM_TRY_BEGIN
    236   _unpTotal = size;
    237   if (!_multiArchives && _extractCallback2)
    238     return _extractCallback2->SetTotal(size);
    239   return S_OK;
    240   COM_TRY_END
    241 }
    242 
    243 static void NormalizeVals(UInt64 &v1, UInt64 &v2)
    244 {
    245   const UInt64 kMax = (UInt64)1 << 31;
    246   while (v1 > kMax)
    247   {
    248     v1 >>= 1;
    249     v2 >>= 1;
    250   }
    251 }
    252 
    253 static UInt64 MyMultDiv64(UInt64 unpCur, UInt64 unpTotal, UInt64 packTotal)
    254 {
    255   NormalizeVals(packTotal, unpTotal);
    256   NormalizeVals(unpCur, unpTotal);
    257   if (unpTotal == 0)
    258     unpTotal = 1;
    259   return unpCur * packTotal / unpTotal;
    260 }
    261 
    262 STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64 *completeValue)
    263 {
    264   COM_TRY_BEGIN
    265   if (!_extractCallback2)
    266     return S_OK;
    267 
    268   if (_multiArchives)
    269   {
    270     if (completeValue != NULL)
    271     {
    272       UInt64 packCur = LocalProgressSpec->InSize + MyMultDiv64(*completeValue, _unpTotal, _packTotal);
    273       return _extractCallback2->SetCompleted(&packCur);
    274     }
    275   }
    276   return _extractCallback2->SetCompleted(completeValue);
    277   COM_TRY_END
    278 }
    279 
    280 STDMETHODIMP CArchiveExtractCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)
    281 {
    282   COM_TRY_BEGIN
    283   return _localProgress->SetRatioInfo(inSize, outSize);
    284   COM_TRY_END
    285 }
    286 
    287 #define IS_LETTER_CHAR(c) ((c) >= 'a' && (c) <= 'z' || (c) >= 'A' && (c) <= 'Z')
    288 
    289 static inline bool IsDriveName(const UString &s)
    290 {
    291   return s.Len() == 2 && s[1] == ':' && IS_LETTER_CHAR(s[0]);
    292 }
    293 
    294 void CArchiveExtractCallback::CreateComplexDirectory(const UStringVector &dirPathParts, FString &fullPath)
    295 {
    296   bool isAbsPath = false;
    297 
    298   if (!dirPathParts.IsEmpty())
    299   {
    300     const UString &s = dirPathParts[0];
    301     if (s.IsEmpty())
    302       isAbsPath = true;
    303     #ifdef _WIN32
    304     else
    305     {
    306       if (dirPathParts.Size() > 1 && IsDriveName(s))
    307         isAbsPath = true;
    308     }
    309     #endif
    310   }
    311 
    312   if (_pathMode == NExtract::NPathMode::kAbsPaths && isAbsPath)
    313     fullPath.Empty();
    314   else
    315     fullPath = _directoryPath;
    316 
    317   FOR_VECTOR (i, dirPathParts)
    318   {
    319     if (i > 0)
    320       fullPath += FCHAR_PATH_SEPARATOR;
    321     const UString &s = dirPathParts[i];
    322     fullPath += us2fs(s);
    323     #ifdef _WIN32
    324     if (_pathMode == NExtract::NPathMode::kAbsPaths)
    325       if (i == 0 && IsDriveName(s))
    326         continue;
    327     #endif
    328     CreateDir(fullPath);
    329   }
    330 }
    331 
    332 HRESULT CArchiveExtractCallback::GetTime(int index, PROPID propID, FILETIME &filetime, bool &filetimeIsDefined)
    333 {
    334   filetimeIsDefined = false;
    335   NCOM::CPropVariant prop;
    336   RINOK(_arc->Archive->GetProperty(index, propID, &prop));
    337   if (prop.vt == VT_FILETIME)
    338   {
    339     filetime = prop.filetime;
    340     filetimeIsDefined = (filetime.dwHighDateTime != 0 || filetime.dwLowDateTime != 0);
    341   }
    342   else if (prop.vt != VT_EMPTY)
    343     return E_FAIL;
    344   return S_OK;
    345 }
    346 
    347 HRESULT CArchiveExtractCallback::GetUnpackSize()
    348 {
    349   return _arc->GetItemSize(_index, _curSize, _curSizeDefined);
    350 }
    351 
    352 HRESULT CArchiveExtractCallback::SendMessageError(const char *message, const FString &path)
    353 {
    354   return _extractCallback2->MessageError(
    355       UString(L"ERROR: ") +
    356       GetUnicodeString(message) + L": " + fs2us(path));
    357 }
    358 
    359 HRESULT CArchiveExtractCallback::SendMessageError2(const char *message, const FString &path1, const FString &path2)
    360 {
    361   return _extractCallback2->MessageError(
    362       UString(L"ERROR: ") +
    363       GetUnicodeString(message) + UString(L": ") + fs2us(path1) + UString(L" : ") + fs2us(path2));
    364 }
    365 
    366 #ifndef _SFX
    367 
    368 STDMETHODIMP CGetProp::GetProp(PROPID propID, PROPVARIANT *value)
    369 {
    370   if (propID == kpidName)
    371   {
    372     COM_TRY_BEGIN
    373     NCOM::CPropVariant prop = Name.Ptr();
    374     prop.Detach(value);
    375     return S_OK;
    376     COM_TRY_END
    377   }
    378   return Arc->Archive->GetProperty(IndexInArc, propID, value);
    379 }
    380 
    381 #endif
    382 
    383 
    384 #ifdef SUPPORT_LINKS
    385 
    386 static UString GetDirPrefixOf(const UString &src)
    387 {
    388   UString s = src;
    389   if (!s.IsEmpty())
    390   {
    391     if (s.Back() == WCHAR_PATH_SEPARATOR)
    392       s.DeleteBack();
    393     int pos = s.ReverseFind(WCHAR_PATH_SEPARATOR);
    394     s.DeleteFrom(pos + 1);
    395   }
    396   return s;
    397 }
    398 
    399 static bool IsSafePath(const UString &path)
    400 {
    401   UStringVector parts;
    402   SplitPathToParts(path, parts);
    403   int level = 0;
    404   FOR_VECTOR(i, parts)
    405   {
    406     const UString &s = parts[i];
    407     if (s.IsEmpty())
    408       continue;
    409     if (s == L".")
    410       continue;
    411     if (s == L"..")
    412     {
    413       if (level <= 0)
    414         return false;
    415       level--;
    416     }
    417     else
    418       level++;
    419   }
    420   return level > 0;
    421 }
    422 
    423 #endif
    424 
    425 
    426 STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode)
    427 {
    428   COM_TRY_BEGIN
    429 
    430   *outStream = 0;
    431 
    432   #ifndef _SFX
    433   if (_hashStream)
    434     _hashStreamSpec->ReleaseStream();
    435   _hashStreamWasUsed = false;
    436   #endif
    437 
    438   _outFileStream.Release();
    439 
    440   _encrypted = false;
    441   _isSplit = false;
    442   _isAltStream = false;
    443   _curSize = 0;
    444   _curSizeDefined = false;
    445   _index = index;
    446 
    447   UString fullPath;
    448 
    449   IInArchive *archive = _arc->Archive;
    450   RINOK(_arc->GetItemPath(index, fullPath));
    451   RINOK(Archive_IsItem_Folder(archive, index, _fi.IsDir));
    452 
    453   _filePath = fullPath;
    454 
    455   {
    456     NCOM::CPropVariant prop;
    457     RINOK(archive->GetProperty(index, kpidPosition, &prop));
    458     if (prop.vt != VT_EMPTY)
    459     {
    460       if (prop.vt != VT_UI8)
    461         return E_FAIL;
    462       _position = prop.uhVal.QuadPart;
    463       _isSplit = true;
    464     }
    465   }
    466 
    467   #ifdef SUPPORT_LINKS
    468 
    469   bool isHardLink = false;
    470   bool isJunction = false;
    471   bool isRelative = false;
    472 
    473   UString linkPath;
    474   // RINOK(Archive_GetItemBoolProp(archive, index, kpidIsHardLink, isHardLink));
    475   // if (isHardLink)
    476   {
    477     NCOM::CPropVariant prop;
    478     RINOK(archive->GetProperty(index, kpidHardLink, &prop));
    479     if (prop.vt == VT_BSTR)
    480     {
    481       isHardLink = true;
    482       linkPath = prop.bstrVal;
    483       isRelative = false; // TAR: hard links are from root folder of archive
    484     }
    485     else if (prop.vt == VT_EMPTY)
    486     {
    487       // linkPath.Empty();
    488     }
    489     else
    490       return E_FAIL;
    491   }
    492   {
    493     NCOM::CPropVariant prop;
    494     RINOK(archive->GetProperty(index, kpidSymLink, &prop));
    495     if (prop.vt == VT_BSTR)
    496     {
    497       isHardLink = false;
    498       linkPath = prop.bstrVal;
    499       isRelative = true; // TAR: symbolic links are relative
    500     }
    501     else if (prop.vt == VT_EMPTY)
    502     {
    503       // linkPath.Empty();
    504     }
    505     else
    506       return E_FAIL;
    507   }
    508 
    509   bool isOkReparse = false;
    510 
    511   if (linkPath.IsEmpty() && _arc->GetRawProps)
    512   {
    513     const void *data;
    514     UInt32 dataSize;
    515     UInt32 propType;
    516     _arc->GetRawProps->GetRawProp(_index, kpidNtReparse, &data, &dataSize, &propType);
    517     if (dataSize != 0)
    518     {
    519       if (propType != NPropDataType::kRaw)
    520         return E_FAIL;
    521       UString s;
    522       CReparseAttr reparse;
    523       isOkReparse = reparse.Parse((const Byte *)data, dataSize);
    524       if (isOkReparse)
    525       {
    526         isHardLink = false;
    527         linkPath = reparse.GetPath();
    528         isJunction = reparse.IsMountPoint();
    529         isRelative = reparse.IsRelative();
    530         #ifndef _WIN32
    531         linkPath.Replace(WCHAR_PATH_SEPARATOR, '/', );
    532         #endif
    533       }
    534     }
    535   }
    536 
    537   if (!linkPath.IsEmpty())
    538   {
    539     #ifdef _WIN32
    540     linkPath.Replace('/', WCHAR_PATH_SEPARATOR);
    541     #endif
    542 
    543     for (;;)
    544     // while (NName::IsAbsolutePath(linkPath))
    545     {
    546       unsigned n = NName::GetRootPrefixSize(linkPath);
    547       if (n == 0)
    548         break;
    549       isRelative = false;
    550       linkPath.DeleteFrontal(n);
    551     }
    552   }
    553 
    554   if (!linkPath.IsEmpty() && !isRelative && _removePathParts.Size() != 0)
    555   {
    556     UStringVector pathParts;
    557     SplitPathToParts(linkPath, pathParts);
    558     bool badPrefix = false;
    559     FOR_VECTOR (i, _removePathParts)
    560     {
    561       if (CompareFileNames(_removePathParts[i], pathParts[i]) != 0)
    562       {
    563         badPrefix = true;
    564         break;
    565       }
    566     }
    567     if (!badPrefix)
    568       pathParts.DeleteFrontal(_removePathParts.Size());
    569     linkPath = MakePathNameFromParts(pathParts);
    570   }
    571 
    572   #endif
    573 
    574   RINOK(Archive_GetItemBoolProp(archive, index, kpidEncrypted, _encrypted));
    575 
    576   RINOK(GetUnpackSize());
    577 
    578   RINOK(Archive_IsItem_AltStream(archive, index, _isAltStream));
    579 
    580   if (!_ntOptions.AltStreams.Val && _isAltStream)
    581     return S_OK;
    582 
    583   if (_wildcardCensor)
    584   {
    585     if (!_wildcardCensor->CheckPath(_isAltStream, fullPath, !_fi.IsDir))
    586       return S_OK;
    587   }
    588 
    589 
    590   UStringVector pathParts;
    591 
    592   if (_use_baseParentFolder_mode)
    593   {
    594     int baseParent = _baseParentFolder;
    595     if (_pathMode == NExtract::NPathMode::kFullPaths ||
    596         _pathMode == NExtract::NPathMode::kAbsPaths)
    597       baseParent = -1;
    598     RINOK(_arc->GetItemPathToParent(index, baseParent, pathParts));
    599     if (_pathMode == NExtract::NPathMode::kNoPaths && !pathParts.IsEmpty())
    600       pathParts.DeleteFrontal(pathParts.Size() - 1);
    601   }
    602   else
    603   {
    604     SplitPathToParts(fullPath, pathParts);
    605 
    606     if (pathParts.IsEmpty())
    607       return E_FAIL;
    608     unsigned numRemovePathParts = 0;
    609 
    610     switch (_pathMode)
    611     {
    612       case NExtract::NPathMode::kCurPaths:
    613       {
    614         bool badPrefix = false;
    615         if (pathParts.Size() <= _removePathParts.Size())
    616           badPrefix = true;
    617         else
    618         {
    619           FOR_VECTOR (i, _removePathParts)
    620           {
    621             if (!_removePathParts[i].IsEqualToNoCase(pathParts[i]))
    622             {
    623               badPrefix = true;
    624               break;
    625             }
    626           }
    627         }
    628         if (badPrefix)
    629         {
    630           if (askExtractMode == NArchive::NExtract::NAskMode::kExtract && !_testMode)
    631             return E_FAIL;
    632         }
    633         else
    634           numRemovePathParts = _removePathParts.Size();
    635         break;
    636       }
    637       case NExtract::NPathMode::kNoPaths:
    638       {
    639         numRemovePathParts = pathParts.Size() - 1;
    640         break;
    641       }
    642       /*
    643       case NExtract::NPathMode::kFullPaths:
    644       case NExtract::NPathMode::kAbsPaths:
    645         break;
    646       */
    647     }
    648 
    649     pathParts.DeleteFrontal(numRemovePathParts);
    650   }
    651 
    652   #ifndef _SFX
    653 
    654   if (ExtractToStreamCallback)
    655   {
    656     if (!GetProp)
    657     {
    658       GetProp_Spec = new CGetProp;
    659       GetProp = GetProp_Spec;
    660     }
    661     GetProp_Spec->Arc = _arc;
    662     GetProp_Spec->IndexInArc = index;
    663     GetProp_Spec->Name = MakePathNameFromParts(pathParts);
    664 
    665     return ExtractToStreamCallback->GetStream7(GetProp_Spec->Name, _fi.IsDir, outStream, askExtractMode, GetProp);
    666   }
    667 
    668   #endif
    669 
    670   CMyComPtr<ISequentialOutStream> outStreamLoc;
    671 
    672 if (askExtractMode == NArchive::NExtract::NAskMode::kExtract && !_testMode)
    673 {
    674   if (_stdOutMode)
    675   {
    676     outStreamLoc = new CStdOutFileStream;
    677   }
    678   else
    679   {
    680     {
    681       NCOM::CPropVariant prop;
    682       RINOK(archive->GetProperty(index, kpidAttrib, &prop));
    683       if (prop.vt == VT_UI4)
    684       {
    685         _fi.Attrib = prop.ulVal;
    686         _fi.AttribDefined = true;
    687       }
    688       else if (prop.vt == VT_EMPTY)
    689         _fi.AttribDefined = false;
    690       else
    691         return E_FAIL;
    692     }
    693 
    694     RINOK(GetTime(index, kpidCTime, _fi.CTime, _fi.CTimeDefined));
    695     RINOK(GetTime(index, kpidATime, _fi.ATime, _fi.ATimeDefined));
    696     RINOK(GetTime(index, kpidMTime, _fi.MTime, _fi.MTimeDefined));
    697 
    698     bool isAnti = false;
    699     RINOK(_arc->IsItemAnti(index, isAnti));
    700 
    701     bool replace = _isAltStream ?
    702         _ntOptions.ReplaceColonForAltStream :
    703         !_ntOptions.WriteToAltStreamIfColon;
    704 
    705     if (_pathMode != NExtract::NPathMode::kAbsPaths)
    706       MakeCorrectPath(_directoryPath.IsEmpty(), pathParts, replace);
    707     Correct_IfEmptyLastPart(pathParts);
    708     UString processedPath = MakePathNameFromParts(pathParts);
    709 
    710     if (!isAnti)
    711     {
    712       if (!_fi.IsDir)
    713       {
    714         if (!pathParts.IsEmpty())
    715           pathParts.DeleteBack();
    716       }
    717 
    718       if (!pathParts.IsEmpty())
    719       {
    720         FString fullPathNew;
    721         CreateComplexDirectory(pathParts, fullPathNew);
    722         if (_fi.IsDir)
    723         {
    724           _extractedFolderPaths.Add(fullPathNew);
    725           _extractedFolderIndices.Add(index);
    726           SetDirTime(fullPathNew,
    727             (WriteCTime && _fi.CTimeDefined) ? &_fi.CTime : NULL,
    728             (WriteATime && _fi.ATimeDefined) ? &_fi.ATime : NULL,
    729             (WriteMTime && _fi.MTimeDefined) ? &_fi.MTime : (_arc->MTimeDefined ? &_arc->MTime : NULL));
    730         }
    731       }
    732     }
    733 
    734 
    735     FString fullProcessedPath = us2fs(processedPath);
    736     if (_pathMode != NExtract::NPathMode::kAbsPaths ||
    737         !NName::IsAbsolutePath(processedPath))
    738       fullProcessedPath = _directoryPath + fullProcessedPath;
    739 
    740     if (_fi.IsDir)
    741     {
    742       _diskFilePath = fullProcessedPath;
    743       if (isAnti)
    744         RemoveDir(_diskFilePath);
    745       #ifdef SUPPORT_LINKS
    746       if (linkPath.IsEmpty())
    747       #endif
    748         return S_OK;
    749     }
    750     else if (!_isSplit)
    751     {
    752     NFind::CFileInfo fileInfo;
    753     if (fileInfo.Find(fullProcessedPath))
    754     {
    755       switch (_overwriteMode)
    756       {
    757         case NExtract::NOverwriteMode::kSkip:
    758           return S_OK;
    759         case NExtract::NOverwriteMode::kAsk:
    760         {
    761           int slashPos = fullProcessedPath.ReverseFind(FTEXT('/'));
    762           #ifdef _WIN32
    763           int slash1Pos = fullProcessedPath.ReverseFind(FTEXT('\\'));
    764           slashPos = MyMax(slashPos, slash1Pos);
    765           #endif
    766           FString realFullProcessedPath = fullProcessedPath.Left(slashPos + 1) + fileInfo.Name;
    767 
    768           Int32 overwiteResult;
    769           RINOK(_extractCallback2->AskOverwrite(
    770               fs2us(realFullProcessedPath), &fileInfo.MTime, &fileInfo.Size, fullPath,
    771               _fi.MTimeDefined ? &_fi.MTime : NULL,
    772               _curSizeDefined ? &_curSize : NULL,
    773               &overwiteResult))
    774 
    775           switch (overwiteResult)
    776           {
    777             case NOverwriteAnswer::kCancel: return E_ABORT;
    778             case NOverwriteAnswer::kNo: return S_OK;
    779             case NOverwriteAnswer::kNoToAll: _overwriteMode = NExtract::NOverwriteMode::kSkip; return S_OK;
    780             case NOverwriteAnswer::kYes: break;
    781             case NOverwriteAnswer::kYesToAll: _overwriteMode = NExtract::NOverwriteMode::kOverwrite; break;
    782             case NOverwriteAnswer::kAutoRename: _overwriteMode = NExtract::NOverwriteMode::kRename; break;
    783             default:
    784               return E_FAIL;
    785           }
    786         }
    787       }
    788       if (_overwriteMode == NExtract::NOverwriteMode::kRename)
    789       {
    790         if (!AutoRenamePath(fullProcessedPath))
    791         {
    792           RINOK(SendMessageError(kCantAutoRename, fullProcessedPath));
    793           return E_FAIL;
    794         }
    795       }
    796       else if (_overwriteMode == NExtract::NOverwriteMode::kRenameExisting)
    797       {
    798         FString existPath = fullProcessedPath;
    799         if (!AutoRenamePath(existPath))
    800         {
    801           RINOK(SendMessageError(kCantAutoRename, fullProcessedPath));
    802           return E_FAIL;
    803         }
    804         // MyMoveFile can raname folders. So it's OK to use it folders too
    805         if (!MyMoveFile(fullProcessedPath, existPath))
    806         {
    807           RINOK(SendMessageError(kCantRenameFile, fullProcessedPath));
    808           return E_FAIL;
    809         }
    810       }
    811       else
    812       {
    813         if (fileInfo.IsDir())
    814         {
    815           // do we need to delete all files in folder?
    816           if (!RemoveDir(fullProcessedPath))
    817           {
    818             RINOK(SendMessageError(kCantDeleteOutputDir, fullProcessedPath));
    819             return S_OK;
    820           }
    821         }
    822         else if (!DeleteFileAlways(fullProcessedPath))
    823         {
    824           RINOK(SendMessageError(kCantDeleteOutputFile, fullProcessedPath));
    825           return S_OK;
    826           // return E_FAIL;
    827         }
    828       }
    829     }
    830     }
    831     _diskFilePath = fullProcessedPath;
    832 
    833 
    834     if (!isAnti)
    835     {
    836       #ifdef SUPPORT_LINKS
    837 
    838       if (!linkPath.IsEmpty())
    839       {
    840         #ifndef UNDER_CE
    841 
    842         UString relatPath;
    843         if (isRelative)
    844           relatPath = GetDirPrefixOf(_filePath);
    845         relatPath += linkPath;
    846 
    847         if (!IsSafePath(relatPath))
    848         {
    849           RINOK(SendMessageError("Dangerous link path was ignored", us2fs(relatPath)));
    850         }
    851         else
    852         {
    853           FString existPath;
    854           if (isHardLink || !isRelative)
    855           {
    856             if (!NName::GetFullPath(_directoryPathFull, us2fs(relatPath), existPath))
    857             {
    858               RINOK(SendMessageError("Incorrect path", us2fs(relatPath)));
    859             }
    860           }
    861           else
    862           {
    863             existPath = us2fs(linkPath);
    864           }
    865 
    866           if (!existPath.IsEmpty())
    867           {
    868             if (isHardLink)
    869             {
    870               if (!MyCreateHardLink(fullProcessedPath, existPath))
    871               {
    872                 RINOK(SendMessageError2("Can not create hard link", fullProcessedPath, existPath));
    873                 // return S_OK;
    874               }
    875             }
    876             else if (_ntOptions.SymLinks.Val)
    877             {
    878               // bool isSymLink = true; // = false for junction
    879               if (_fi.IsDir && !isRelative)
    880               {
    881                 // if it's before Vista we use Junction Point
    882                 // isJunction = true;
    883                 // convertToAbs = true;
    884               }
    885 
    886               CByteBuffer data;
    887               if (FillLinkData(data, fs2us(existPath), !isJunction))
    888               {
    889                 CReparseAttr attr;
    890                 if (!attr.Parse(data, data.Size()))
    891                 {
    892                   return E_FAIL; // "Internal conversion error";
    893                 }
    894 
    895                 if (!NFile::NIO::SetReparseData(fullProcessedPath, _fi.IsDir, data, (DWORD)data.Size()))
    896                 {
    897                   RINOK(SendMessageError("Can not set reparse data", fullProcessedPath));
    898                 }
    899               }
    900             }
    901           }
    902         }
    903 
    904         #endif
    905       }
    906       else
    907       #endif // SUPPORT_LINKS
    908       {
    909         bool needWriteFile = true;
    910 
    911         #ifdef SUPPORT_LINKS
    912         if (!_hardLinks.IDs.IsEmpty())
    913         {
    914           CHardLinkNode h;
    915           bool defined;
    916           RINOK(Archive_Get_HardLinkNode(archive, index, h, defined));
    917           if (defined)
    918           {
    919             {
    920               int linkIndex = _hardLinks.IDs.FindInSorted2(h);
    921               if (linkIndex >= 0)
    922               {
    923                 FString &hl = _hardLinks.Links[linkIndex];
    924                 if (hl.IsEmpty())
    925                   hl = fullProcessedPath;
    926                 else
    927                 {
    928                   if (!MyCreateHardLink(fullProcessedPath, hl))
    929                   {
    930                     RINOK(SendMessageError2("Can not create hard link", fullProcessedPath, hl));
    931                     return S_OK;
    932                   }
    933                   needWriteFile = false;
    934                 }
    935               }
    936             }
    937           }
    938         }
    939         #endif
    940 
    941         if (needWriteFile)
    942         {
    943           _outFileStreamSpec = new COutFileStream;
    944           CMyComPtr<ISequentialOutStream> outStreamLoc(_outFileStreamSpec);
    945           if (!_outFileStreamSpec->Open(fullProcessedPath, _isSplit ? OPEN_ALWAYS: CREATE_ALWAYS))
    946           {
    947             // if (::GetLastError() != ERROR_FILE_EXISTS || !isSplit)
    948             {
    949               RINOK(SendMessageError("Can not open output file ", fullProcessedPath));
    950               return S_OK;
    951             }
    952           }
    953           if (_isSplit)
    954           {
    955             RINOK(_outFileStreamSpec->Seek(_position, STREAM_SEEK_SET, NULL));
    956           }
    957           _outFileStream = outStreamLoc;
    958         }
    959       }
    960     }
    961 
    962     outStreamLoc = _outFileStream;
    963   }
    964 }
    965 
    966   #ifndef _SFX
    967 
    968   if (_hashStream)
    969   {
    970     if (askExtractMode == NArchive::NExtract::NAskMode::kExtract ||
    971         askExtractMode == NArchive::NExtract::NAskMode::kTest)
    972     {
    973       _hashStreamSpec->SetStream(outStreamLoc);
    974       outStreamLoc = _hashStream;
    975       _hashStreamSpec->Init(true);
    976       _hashStreamWasUsed = true;
    977     }
    978   }
    979 
    980   #endif
    981 
    982   if (outStreamLoc)
    983     *outStream = outStreamLoc.Detach();
    984   return S_OK;
    985   COM_TRY_END
    986 }
    987 
    988 STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode)
    989 {
    990   COM_TRY_BEGIN
    991 
    992   #ifndef _SFX
    993   if (ExtractToStreamCallback)
    994     return ExtractToStreamCallback->PrepareOperation7(askExtractMode);
    995   #endif
    996 
    997   _extractMode = false;
    998   switch (askExtractMode)
    999   {
   1000     case NArchive::NExtract::NAskMode::kExtract:
   1001       if (_testMode)
   1002         askExtractMode = NArchive::NExtract::NAskMode::kTest;
   1003       else
   1004         _extractMode = true;
   1005       break;
   1006   };
   1007   return _extractCallback2->PrepareOperation(_filePath, _fi.IsDir,
   1008       askExtractMode, _isSplit ? &_position: 0);
   1009   COM_TRY_END
   1010 }
   1011 
   1012 STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult)
   1013 {
   1014   COM_TRY_BEGIN
   1015 
   1016   #ifndef _SFX
   1017   if (ExtractToStreamCallback)
   1018     return ExtractToStreamCallback->SetOperationResult7(operationResult, _encrypted);
   1019   #endif
   1020 
   1021   #ifndef _SFX
   1022 
   1023   if (_hashStreamWasUsed)
   1024   {
   1025     _hashStreamSpec->_hash->Final(_fi.IsDir, _isAltStream, _filePath);
   1026     _curSize = _hashStreamSpec->GetSize();
   1027     _curSizeDefined = true;
   1028     _hashStreamSpec->ReleaseStream();
   1029     _hashStreamWasUsed = false;
   1030   }
   1031 
   1032   #endif
   1033 
   1034   if (_outFileStream)
   1035   {
   1036     _outFileStreamSpec->SetTime(
   1037         (WriteCTime && _fi.CTimeDefined) ? &_fi.CTime : NULL,
   1038         (WriteATime && _fi.ATimeDefined) ? &_fi.ATime : NULL,
   1039         (WriteMTime && _fi.MTimeDefined) ? &_fi.MTime : (_arc->MTimeDefined ? &_arc->MTime : NULL));
   1040     _curSize = _outFileStreamSpec->ProcessedSize;
   1041     _curSizeDefined = true;
   1042     RINOK(_outFileStreamSpec->Close());
   1043     _outFileStream.Release();
   1044   }
   1045 
   1046   #ifdef _USE_SECURITY_CODE
   1047   if (_ntOptions.NtSecurity.Val && _arc->GetRawProps)
   1048   {
   1049     const void *data;
   1050     UInt32 dataSize;
   1051     UInt32 propType;
   1052     _arc->GetRawProps->GetRawProp(_index, kpidNtSecure, &data, &dataSize, &propType);
   1053     if (dataSize != 0)
   1054     {
   1055       if (propType != NPropDataType::kRaw)
   1056         return E_FAIL;
   1057       if (CheckNtSecure((const Byte *)data, dataSize))
   1058       {
   1059         SECURITY_INFORMATION securInfo = DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION;
   1060         if (_saclEnabled)
   1061           securInfo |= SACL_SECURITY_INFORMATION;
   1062         ::SetFileSecurityW(fs2us(_diskFilePath), securInfo, (PSECURITY_DESCRIPTOR)(void *)data);
   1063       }
   1064     }
   1065   }
   1066   #endif
   1067 
   1068   if (!_curSizeDefined)
   1069     GetUnpackSize();
   1070   if (_curSizeDefined)
   1071   {
   1072     if (_isAltStream)
   1073       AltStreams_UnpackSize += _curSize;
   1074     else
   1075       UnpackSize += _curSize;
   1076   }
   1077 
   1078   if (_fi.IsDir)
   1079     NumFolders++;
   1080   else if (_isAltStream)
   1081     NumAltStreams++;
   1082   else
   1083     NumFiles++;
   1084 
   1085   if (_extractMode && _fi.AttribDefined)
   1086     SetFileAttrib(_diskFilePath, _fi.Attrib);
   1087   RINOK(_extractCallback2->SetOperationResult(operationResult, _encrypted));
   1088   return S_OK;
   1089   COM_TRY_END
   1090 }
   1091 
   1092 /*
   1093 STDMETHODIMP CArchiveExtractCallback::GetInStream(
   1094     const wchar_t *name, ISequentialInStream **inStream)
   1095 {
   1096   COM_TRY_BEGIN
   1097   CInFileStream *inFile = new CInFileStream;
   1098   CMyComPtr<ISequentialInStream> inStreamTemp = inFile;
   1099   if (!inFile->Open(_srcDirectoryPrefix + name))
   1100     return ::GetLastError();
   1101   *inStream = inStreamTemp.Detach();
   1102   return S_OK;
   1103   COM_TRY_END
   1104 }
   1105 */
   1106 
   1107 STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password)
   1108 {
   1109   COM_TRY_BEGIN
   1110   if (!_cryptoGetTextPassword)
   1111   {
   1112     RINOK(_extractCallback2.QueryInterface(IID_ICryptoGetTextPassword,
   1113         &_cryptoGetTextPassword));
   1114   }
   1115   return _cryptoGetTextPassword->CryptoGetTextPassword(password);
   1116   COM_TRY_END
   1117 }
   1118 
   1119 
   1120 struct CExtrRefSortPair
   1121 {
   1122   int Len;
   1123   int Index;
   1124 
   1125   int Compare(const CExtrRefSortPair &a) const;
   1126 };
   1127 
   1128 #define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; }
   1129 
   1130 int CExtrRefSortPair::Compare(const CExtrRefSortPair &a) const
   1131 {
   1132   RINOZ(-MyCompare(Len, a.Len));
   1133   return MyCompare(Index, a.Index);
   1134 }
   1135 
   1136 static int GetNumSlashes(const FChar *s)
   1137 {
   1138   for (int numSlashes = 0;;)
   1139   {
   1140     FChar c = *s++;
   1141     if (c == 0)
   1142       return numSlashes;
   1143     if (
   1144         #ifdef _WIN32
   1145         c == FTEXT('\\') ||
   1146         #endif
   1147         c == FTEXT('/'))
   1148       numSlashes++;
   1149   }
   1150 }
   1151 
   1152 HRESULT CArchiveExtractCallback::SetDirsTimes()
   1153 {
   1154   CRecordVector<CExtrRefSortPair> pairs;
   1155   pairs.ClearAndSetSize(_extractedFolderPaths.Size());
   1156   unsigned i;
   1157 
   1158   for (i = 0; i < _extractedFolderPaths.Size(); i++)
   1159   {
   1160     CExtrRefSortPair &pair = pairs[i];
   1161     pair.Index = i;
   1162     pair.Len = GetNumSlashes(_extractedFolderPaths[i]);
   1163   }
   1164 
   1165   pairs.Sort2();
   1166 
   1167   for (i = 0; i < pairs.Size(); i++)
   1168   {
   1169     int pairIndex = pairs[i].Index;
   1170     int index = _extractedFolderIndices[pairIndex];
   1171 
   1172     FILETIME CTime;
   1173     FILETIME ATime;
   1174     FILETIME MTime;
   1175 
   1176     bool CTimeDefined;
   1177     bool ATimeDefined;
   1178     bool MTimeDefined;
   1179 
   1180     RINOK(GetTime(index, kpidCTime, CTime, CTimeDefined));
   1181     RINOK(GetTime(index, kpidATime, ATime, ATimeDefined));
   1182     RINOK(GetTime(index, kpidMTime, MTime, MTimeDefined));
   1183 
   1184     // printf("\n%S", _extractedFolderPaths[pairIndex]);
   1185     SetDirTime(_extractedFolderPaths[pairIndex],
   1186       (WriteCTime && CTimeDefined) ? &CTime : NULL,
   1187       (WriteATime && ATimeDefined) ? &ATime : NULL,
   1188       (WriteMTime && MTimeDefined) ? &MTime : (_arc->MTimeDefined ? &_arc->MTime : NULL));
   1189   }
   1190   return S_OK;
   1191 }
   1192