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 
Write(const void * data,UInt32 size,UInt32 * processedSize)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
InitLocalPrivileges()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 
Compare(const CHardLinkNode & a) const80 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 
Archive_Get_HardLinkNode(IInArchive * archive,UInt32 index,CHardLinkNode & h,bool & defined)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 
PrepareHardLinks(const CRecordVector<UInt32> * realIndices)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 
CArchiveExtractCallback()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 
Init(const CExtractNtOptions & ntOptions,const NWildcard::CCensorNode * wildcardCensor,const CArc * arc,IFolderArchiveExtractCallback * extractCallback2,bool stdOutMode,bool testMode,const FString & directoryPath,const UStringVector & removePathParts,UInt64 packSize)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 
SetTotal(UInt64 size)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 
NormalizeVals(UInt64 & v1,UInt64 & v2)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 
MyMultDiv64(UInt64 unpCur,UInt64 unpTotal,UInt64 packTotal)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 
SetCompleted(const UInt64 * completeValue)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 
SetRatioInfo(const UInt64 * inSize,const UInt64 * outSize)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 
IsDriveName(const UString & s)289 static inline bool IsDriveName(const UString &s)
290 {
291   return s.Len() == 2 && s[1] == ':' && IS_LETTER_CHAR(s[0]);
292 }
293 
CreateComplexDirectory(const UStringVector & dirPathParts,FString & fullPath)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 
GetTime(int index,PROPID propID,FILETIME & filetime,bool & filetimeIsDefined)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 
GetUnpackSize()347 HRESULT CArchiveExtractCallback::GetUnpackSize()
348 {
349   return _arc->GetItemSize(_index, _curSize, _curSizeDefined);
350 }
351 
SendMessageError(const char * message,const FString & path)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 
SendMessageError2(const char * message,const FString & path1,const FString & path2)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 
GetProp(PROPID propID,PROPVARIANT * value)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 
GetDirPrefixOf(const UString & src)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 
IsSafePath(const UString & path)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 
GetStream(UInt32 index,ISequentialOutStream ** outStream,Int32 askExtractMode)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 
PrepareOperation(Int32 askExtractMode)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 
SetOperationResult(Int32 operationResult)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 
CryptoGetTextPassword(BSTR * password)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 
Compare(const CExtrRefSortPair & a) const1130 int CExtrRefSortPair::Compare(const CExtrRefSortPair &a) const
1131 {
1132   RINOZ(-MyCompare(Len, a.Len));
1133   return MyCompare(Index, a.Index);
1134 }
1135 
GetNumSlashes(const FChar * s)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 
SetDirsTimes()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