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