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