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