xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/Nsis/NsisHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // NSisHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../../C/CpuArch.h"
6 
7 #include "../../../Common/ComTry.h"
8 #include "../../../Common/IntToString.h"
9 
10 #include "../../../Windows/PropVariant.h"
11 
12 #include "../../Common/ProgressUtils.h"
13 #include "../../Common/StreamUtils.h"
14 
15 #include "../Common/ItemNameUtils.h"
16 
17 #include "NsisHandler.h"
18 
19 #define Get32(p) GetUi32(p)
20 
21 using namespace NWindows;
22 
23 namespace NArchive {
24 namespace NNsis {
25 
26 #define kBcjMethod "BCJ"
27 #define kUnknownMethod "Unknown"
28 
29 static const char * const kMethods[] =
30 {
31     "Copy"
32   , "Deflate"
33   , "BZip2"
34   , "LZMA"
35 };
36 
37 static const Byte kProps[] =
38 {
39   kpidPath,
40   kpidSize,
41   kpidPackSize,
42   kpidMTime,
43   kpidAttrib,
44   kpidMethod,
45   kpidSolid,
46   kpidOffset
47 };
48 
49 static const Byte kArcProps[] =
50 {
51   kpidMethod,
52   kpidSolid,
53   kpidBit64,
54   kpidHeadersSize,
55   kpidEmbeddedStubSize,
56   kpidSubType
57   // kpidCodePage
58 };
59 
60 IMP_IInArchive_Props
61 IMP_IInArchive_ArcProps
62 
63 
AddDictProp(AString & s,UInt32 val)64 static void AddDictProp(AString &s, UInt32 val)
65 {
66   for (unsigned i = 0; i < 32; i++)
67     if (((UInt32)1 << i) == val)
68     {
69       s.Add_UInt32(i);
70       return;
71     }
72   char c = 'b';
73   if      ((val & ((1 << 20) - 1)) == 0) { val >>= 20; c = 'm'; }
74   else if ((val & ((1 << 10) - 1)) == 0) { val >>= 10; c = 'k'; }
75   s.Add_UInt32(val);
76   s.Add_Char(c);
77 }
78 
GetMethod(bool useFilter,NMethodType::EEnum method,UInt32 dict)79 static AString GetMethod(bool useFilter, NMethodType::EEnum method, UInt32 dict)
80 {
81   AString s;
82   if (useFilter)
83   {
84     s += kBcjMethod;
85     s.Add_Space();
86   }
87   s += ((unsigned)method < Z7_ARRAY_SIZE(kMethods)) ? kMethods[(unsigned)method] : kUnknownMethod;
88   if (method == NMethodType::kLZMA)
89   {
90     s.Add_Colon();
91     AddDictProp(s, dict);
92   }
93   return s;
94 }
95 
96 /*
97 AString CHandler::GetMethod(NMethodType::EEnum method, bool useItemFilter, UInt32 dictionary) const
98 {
99   AString s;
100   if (_archive.IsSolid && _archive.UseFilter || !_archive.IsSolid && useItemFilter)
101   {
102     s += kBcjMethod;
103     s.Add_Space();
104   }
105   s += (method < Z7_ARRAY_SIZE(kMethods)) ? kMethods[method] : kUnknownMethod;
106   if (method == NMethodType::kLZMA)
107   {
108     s.Add_Colon();
109     s += GetStringForSizeValue(_archive.IsSolid ? _archive.DictionarySize: dictionary);
110   }
111   return s;
112 }
113 */
114 
Z7_COM7F_IMF(CHandler::GetArchiveProperty (PROPID propID,PROPVARIANT * value))115 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
116 {
117   COM_TRY_BEGIN
118   NCOM::CPropVariant prop;
119   switch (propID)
120   {
121     // case kpidCodePage: if (_archive.IsUnicode) prop = "UTF-16"; break;
122     case kpidSubType:
123     {
124       AString s (_archive.GetFormatDescription());
125       if (!_archive.IsInstaller)
126       {
127         s.Add_Space_if_NotEmpty();
128         s += "(Uninstall)";
129       }
130       if (!s.IsEmpty())
131         prop = s;
132       break;
133     }
134 
135     case kpidBit64: if (_archive.Is64Bit) prop = true; break;
136     case kpidMethod: prop = _methodString; break;
137     case kpidSolid: prop = _archive.IsSolid; break;
138     case kpidOffset: prop = _archive.StartOffset; break;
139     case kpidPhySize: prop = (UInt64)((UInt64)_archive.ExeStub.Size() + _archive.FirstHeader.ArcSize); break;
140     case kpidEmbeddedStubSize: prop = (UInt64)_archive.ExeStub.Size(); break;
141     case kpidHeadersSize: prop = _archive.FirstHeader.HeaderSize; break;
142 
143     case kpidErrorFlags:
144     {
145       UInt32 v = 0;
146       if (!_archive.IsArc) v |= kpv_ErrorFlags_IsNotArc;
147       if (_archive.IsTruncated()) v |= kpv_ErrorFlags_UnexpectedEnd;
148       prop = v;
149       break;
150     }
151 
152     case kpidName:
153     {
154       AString s;
155 
156       #ifdef NSIS_SCRIPT
157         if (!_archive.Name.IsEmpty())
158           s = _archive.Name;
159         if (!_archive.IsInstaller)
160         {
161           if (!s.IsEmpty())
162             s.Add_Dot();
163           s += "Uninstall";
164         }
165       #endif
166 
167       if (s.IsEmpty())
168         s = _archive.IsInstaller ? "Install" : "Uninstall";
169       s += (_archive.ExeStub.Size() == 0) ? ".nsis" : ".exe";
170 
171       prop = _archive.ConvertToUnicode(s);
172       break;
173     }
174 
175     #ifdef NSIS_SCRIPT
176     case kpidShortComment:
177     {
178       if (!_archive.BrandingText.IsEmpty())
179         prop = _archive.ConvertToUnicode(_archive.BrandingText);
180       break;
181     }
182     #endif
183   }
184   prop.Detach(value);
185   return S_OK;
186   COM_TRY_END
187 }
188 
189 
Z7_COM7F_IMF(CHandler::Open (IInStream * stream,const UInt64 * maxCheckStartPosition,IArchiveOpenCallback *))190 Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback * /* openArchiveCallback */))
191 {
192   COM_TRY_BEGIN
193   Close();
194   {
195     if (_archive.Open(stream, maxCheckStartPosition) != S_OK)
196       return S_FALSE;
197     {
198       UInt32 dict = _archive.DictionarySize;
199       if (!_archive.IsSolid)
200       {
201         FOR_VECTOR (i, _archive.Items)
202         {
203           const CItem &item = _archive.Items[i];
204           if (item.DictionarySize > dict)
205             dict = item.DictionarySize;
206         }
207       }
208       _methodString = GetMethod(_archive.UseFilter, _archive.Method, dict);
209     }
210   }
211   return S_OK;
212   COM_TRY_END
213 }
214 
Z7_COM7F_IMF(CHandler::Close ())215 Z7_COM7F_IMF(CHandler::Close())
216 {
217   _archive.Clear();
218   _archive.Release();
219   return S_OK;
220 }
221 
Z7_COM7F_IMF(CHandler::GetNumberOfItems (UInt32 * numItems))222 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
223 {
224   *numItems = _archive.Items.Size()
225   #ifdef NSIS_SCRIPT
226     + 1 + _archive.LicenseFiles.Size()
227   #endif
228   ;
229   return S_OK;
230 }
231 
GetUncompressedSize(unsigned index,UInt32 & size) const232 bool CHandler::GetUncompressedSize(unsigned index, UInt32 &size) const
233 {
234   size = 0;
235   const CItem &item = _archive.Items[index];
236   if (item.Size_Defined)
237     size = item.Size;
238   else if (_archive.IsSolid && item.EstimatedSize_Defined)
239     size = item.EstimatedSize;
240   else if (!item.IsEmptyFile)
241     return false;
242   return true;
243 }
244 
GetCompressedSize(unsigned index,UInt32 & size) const245 bool CHandler::GetCompressedSize(unsigned index, UInt32 &size) const
246 {
247   size = 0;
248   const CItem &item = _archive.Items[index];
249   if (item.CompressedSize_Defined)
250     size = item.CompressedSize;
251   else
252   {
253     if (_archive.IsSolid)
254     {
255       if (index == 0)
256         size = _archive.FirstHeader.GetDataSize();
257       else
258         return false;
259     }
260     else
261     {
262       if (!item.IsCompressed)
263         size = item.Size;
264       else
265         return false;
266     }
267   }
268   return true;
269 }
270 
271 
Z7_COM7F_IMF(CHandler::GetProperty (UInt32 index,PROPID propID,PROPVARIANT * value))272 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
273 {
274   COM_TRY_BEGIN
275   NCOM::CPropVariant prop;
276   #ifdef NSIS_SCRIPT
277   if (index >= (UInt32)_archive.Items.Size())
278   {
279     if (index == (UInt32)_archive.Items.Size())
280     {
281       switch (propID)
282       {
283         case kpidPath: prop = "[NSIS].nsi"; break;
284         case kpidSize:
285         case kpidPackSize: prop = (UInt64)_archive.Script.Len(); break;
286         case kpidSolid: prop = false; break;
287       }
288     }
289     else
290     {
291       const CLicenseFile &lic = _archive.LicenseFiles[index - (_archive.Items.Size() + 1)];
292       switch (propID)
293       {
294         case kpidPath: prop = lic.Name; break;
295         case kpidSize:
296         case kpidPackSize: prop = (UInt64)lic.Size; break;
297         case kpidSolid: prop = false; break;
298       }
299     }
300   }
301   else
302   #endif
303   {
304     const CItem &item = _archive.Items[index];
305     switch (propID)
306     {
307       case kpidOffset: prop = item.Pos; break;
308       case kpidPath:
309       {
310         UString s = NItemName::WinPathToOsPath(_archive.GetReducedName(index));
311         if (!s.IsEmpty())
312           prop = (const wchar_t *)s;
313         break;
314       }
315       case kpidSize:
316       {
317         UInt32 size;
318         if (GetUncompressedSize(index, size))
319           prop = (UInt64)size;
320         break;
321       }
322       case kpidPackSize:
323       {
324         UInt32 size;
325         if (GetCompressedSize(index, size))
326           prop = (UInt64)size;
327         break;
328       }
329       case kpidMTime:
330       {
331         if (item.MTime.dwHighDateTime > 0x01000000 &&
332             item.MTime.dwHighDateTime < 0xFF000000)
333           prop = item.MTime;
334         break;
335       }
336       case kpidAttrib:
337       {
338         if (item.Attrib_Defined)
339           prop = item.Attrib;
340         break;
341       }
342 
343       case kpidMethod:
344         if (_archive.IsSolid)
345           prop = _methodString;
346         else
347           prop = GetMethod(_archive.UseFilter, item.IsCompressed ? _archive.Method :
348               NMethodType::kCopy, item.DictionarySize);
349         break;
350 
351       case kpidSolid:  prop = _archive.IsSolid; break;
352     }
353   }
354   prop.Detach(value);
355   return S_OK;
356   COM_TRY_END
357 }
358 
359 
UninstallerPatch(const Byte * p,size_t size,Byte * dest,size_t destSize)360 static bool UninstallerPatch(const Byte *p, size_t size, Byte *dest, size_t destSize)
361 {
362   for (;;)
363   {
364     if (size < 4)
365       return false;
366     const UInt32 len = Get32(p);
367     if (len == 0)
368       return size == 4;
369     if (size < 8)
370       return false;
371     const UInt32 offs = Get32(p + 4);
372     p += 8;
373     size -= 8;
374     if (size < len || offs > destSize || len > destSize - offs)
375       return false;
376     memcpy(dest + offs, p, len);
377     p += len;
378     size -= len;
379   }
380 }
381 
382 
Z7_COM7F_IMF(CHandler::Extract (const UInt32 * indices,UInt32 numItems,Int32 testMode,IArchiveExtractCallback * extractCallback))383 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
384     Int32 testMode, IArchiveExtractCallback *extractCallback))
385 {
386   COM_TRY_BEGIN
387   const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
388   if (allFilesMode)
389     GetNumberOfItems(&numItems);
390   if (numItems == 0)
391     return S_OK;
392 
393   UInt64 totalSize = 0;
394   UInt64 solidPosMax = 0;
395 
396   UInt32 i;
397   for (i = 0; i < numItems; i++)
398   {
399     const UInt32 index = (allFilesMode ? i : indices[i]);
400 
401     #ifdef NSIS_SCRIPT
402     if (index >= _archive.Items.Size())
403     {
404       if (index == _archive.Items.Size())
405         totalSize += _archive.Script.Len();
406       else
407         totalSize += _archive.LicenseFiles[index - (_archive.Items.Size() + 1)].Size;
408     }
409     else
410     #endif
411     {
412       UInt32 size;
413       if (_archive.IsSolid)
414       {
415         GetUncompressedSize(index, size);
416         UInt64 pos = (UInt64)_archive.GetPosOfSolidItem(index) + size;
417         if (solidPosMax < pos)
418           solidPosMax = pos;
419       }
420       else
421       {
422         GetCompressedSize(index, size);
423         totalSize += size;
424       }
425     }
426   }
427 
428   extractCallback->SetTotal(totalSize + solidPosMax);
429 
430   CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
431   lps->Init(extractCallback, !_archive.IsSolid);
432 
433   if (_archive.IsSolid)
434   {
435     RINOK(_archive.SeekTo_DataStreamOffset())
436     RINOK(_archive.InitDecoder())
437     _archive.Decoder.StreamPos = 0;
438   }
439 
440   /* We use tempBuf for solid archives, if there is duplicate item.
441      We don't know uncompressed size for non-solid archives, so we can't
442      allocate exact buffer.
443      We use tempBuf also for first part (EXE stub) of unistall.exe
444      and tempBuf2 is used for second part (NSIS script). */
445 
446   CByteBuffer tempBuf;
447   CByteBuffer tempBuf2;
448 
449   /* tempPos is pos in uncompressed stream of previous item for solid archive, that
450      was written to tempBuf  */
451   UInt64 tempPos = (UInt64)(Int64)-1;
452 
453   /* prevPos is pos in uncompressed stream of previous item for solid archive.
454      It's used for test mode (where we don't need to test same file second time */
455   UInt64 prevPos =  (UInt64)(Int64)-1;
456 
457   // if there is error in solid archive, we show error for all subsequent files
458   bool solidDataError = false;
459 
460   UInt64 curTotalPacked = 0, curTotalUnpacked = 0;
461   UInt32 curPacked = 0;
462   UInt64 curUnpacked = 0;
463 
464   for (i = 0; i < numItems; i++,
465       curTotalPacked += curPacked,
466       curTotalUnpacked += curUnpacked)
467   {
468     lps->InSize = curTotalPacked;
469     lps->OutSize = curTotalUnpacked;
470     if (_archive.IsSolid)
471       lps->OutSize += _archive.Decoder.StreamPos;
472 
473     curPacked = 0;
474     curUnpacked = 0;
475     RINOK(lps->SetCur())
476 
477     // RINOK(extractCallback->SetCompleted(&currentTotalSize))
478     CMyComPtr<ISequentialOutStream> realOutStream;
479     const Int32 askMode = testMode ?
480         NExtract::NAskMode::kTest :
481         NExtract::NAskMode::kExtract;
482     const UInt32 index = allFilesMode ? i : indices[i];
483 
484     RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
485 
486     bool dataError = false;
487 
488     #ifdef NSIS_SCRIPT
489     if (index >= (UInt32)_archive.Items.Size())
490     {
491       const void *data;
492       size_t size;
493       if (index == (UInt32)_archive.Items.Size())
494       {
495         data = (const Byte *)_archive.Script;
496         size = _archive.Script.Len();
497       }
498       else
499       {
500         CLicenseFile &lic = _archive.LicenseFiles[index - (_archive.Items.Size() + 1)];
501         if (lic.Text.Size() != 0)
502           data = lic.Text;
503         else
504           data = _archive._data + lic.Offset;
505         size = lic.Size;
506       }
507       curUnpacked = size;
508       if (!testMode && !realOutStream)
509         continue;
510       RINOK(extractCallback->PrepareOperation(askMode))
511       if (realOutStream)
512         RINOK(WriteStream(realOutStream, data, size))
513     }
514     else
515     #endif
516     {
517       const CItem &item = _archive.Items[index];
518 
519       if (!_archive.IsSolid)
520         GetCompressedSize(index, curPacked);
521 
522       if (!testMode && !realOutStream)
523         continue;
524 
525       RINOK(extractCallback->PrepareOperation(askMode))
526 
527       dataError = solidDataError;
528 
529       bool needDecompress = false;
530 
531       if (!item.IsEmptyFile)
532       {
533         needDecompress = !solidDataError;
534         if (needDecompress)
535         {
536           if (testMode && _archive.IsSolid && _archive.GetPosOfSolidItem(index) == prevPos)
537             needDecompress = false;
538         }
539       }
540 
541       if (needDecompress)
542       {
543         bool writeToTemp = false;
544         bool readFromTemp = false;
545 
546         if (!_archive.IsSolid)
547         {
548           RINOK(_archive.SeekToNonSolidItem(index))
549         }
550         else
551         {
552           UInt64 pos = _archive.GetPosOfSolidItem(index);
553           if (pos < _archive.Decoder.StreamPos)
554           {
555             if (pos != tempPos)
556               solidDataError = dataError = true;
557             readFromTemp = true;
558           }
559           else
560           {
561             HRESULT res = _archive.Decoder.SetToPos(pos, lps);
562             if (res != S_OK)
563             {
564               if (res != S_FALSE)
565                 return res;
566               solidDataError = dataError = true;
567             }
568             else if (!testMode && i + 1 < numItems)
569             {
570               const UInt32 next = allFilesMode ? i + 1 : indices[i + 1];
571               if (next < _archive.Items.Size())
572               {
573                 // next cannot be IsEmptyFile
574                 const UInt64 nextPos = _archive.GetPosOfSolidItem(next);
575                 if (nextPos == pos)
576                 {
577                   writeToTemp = true;
578                   tempPos = pos;
579                 }
580               }
581             }
582           }
583           prevPos = pos;
584         }
585 
586         /* nsis 3.08 can use (PatchSize == 0) for uninstaller without patched section */
587 
588         const bool is_PatchedUninstaller = item.Is_PatchedUninstaller();
589 
590         if (!dataError)
591         {
592           // UInt32 unpackSize = 0;
593           // bool unpackSize_Defined = false;
594           bool writeToTemp1 = writeToTemp;
595           if (is_PatchedUninstaller)
596           {
597             // unpackSize = item.PatchSize;
598             // unpackSize_Defined = true;
599             if (!readFromTemp)
600               writeToTemp = true;
601             writeToTemp1 = writeToTemp;
602             if (_archive.ExeStub.Size() == 0)
603             {
604               if (writeToTemp1 && !readFromTemp)
605                 tempBuf.Free();
606               writeToTemp1 = false;
607             }
608           }
609 
610           if (readFromTemp)
611           {
612             if (realOutStream && !is_PatchedUninstaller)
613               RINOK(WriteStream(realOutStream, tempBuf, tempBuf.Size()))
614           }
615           else
616           {
617             UInt32 curUnpacked32 = 0;
618             const HRESULT res = _archive.Decoder.Decode(
619                 writeToTemp1 ? &tempBuf : NULL,
620                 is_PatchedUninstaller, item.PatchSize,
621                 is_PatchedUninstaller ? NULL : (ISequentialOutStream *)realOutStream,
622                 lps,
623                 curPacked, curUnpacked32);
624             curUnpacked = curUnpacked32;
625             if (_archive.IsSolid)
626               curUnpacked = 0;
627             if (res != S_OK)
628             {
629               if (res != S_FALSE)
630                 return res;
631               dataError = true;
632               if (_archive.IsSolid)
633                 solidDataError = true;
634             }
635           }
636         }
637 
638         if (!dataError && is_PatchedUninstaller)
639         {
640           if (_archive.ExeStub.Size() != 0)
641           {
642             CByteBuffer destBuf = _archive.ExeStub;
643             dataError = !UninstallerPatch(tempBuf, tempBuf.Size(), destBuf, destBuf.Size());
644             if (realOutStream)
645               RINOK(WriteStream(realOutStream, destBuf, destBuf.Size()))
646           }
647 
648           if (readFromTemp)
649           {
650             if (realOutStream)
651               RINOK(WriteStream(realOutStream, tempBuf2, tempBuf2.Size()))
652           }
653           else
654           {
655             UInt32 curPacked2 = 0;
656             UInt32 curUnpacked2 = 0;
657 
658             if (!_archive.IsSolid)
659             {
660               RINOK(_archive.SeekTo(_archive.GetPosOfNonSolidItem(index) + 4 + curPacked ))
661             }
662 
663             const HRESULT res = _archive.Decoder.Decode(
664                 writeToTemp ? &tempBuf2 : NULL,
665                 false, 0,
666                 realOutStream,
667                 lps,
668                 curPacked2, curUnpacked2);
669             curPacked += curPacked2;
670             if (!_archive.IsSolid)
671               curUnpacked += curUnpacked2;
672             if (res != S_OK)
673             {
674               if (res != S_FALSE)
675                 return res;
676               dataError = true;
677               if (_archive.IsSolid)
678                 solidDataError = true;
679             }
680           }
681         }
682       }
683     }
684     realOutStream.Release();
685     RINOK(extractCallback->SetOperationResult(dataError ?
686         NExtract::NOperationResult::kDataError :
687         NExtract::NOperationResult::kOK))
688   }
689   return S_OK;
690   COM_TRY_END
691 }
692 
693 }}
694