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(¤tTotalSize))
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