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