1// OpenArchive.cpp
2
3#include "StdAfx.h"
4
5#include "Common/Wildcard.h"
6
7#include "Windows/FileDir.h"
8#include "Windows/PropVariant.h"
9
10#include "../../Common/FileStreams.h"
11#include "../../Common/StreamUtils.h"
12
13#include "DefaultName.h"
14#include "OpenArchive.h"
15
16using namespace NWindows;
17
18// Static-SFX (for Linux) can be big.
19const UInt64 kMaxCheckStartPosition = 1 << 22;
20
21HRESULT GetArchiveItemBoolProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result)
22{
23  NCOM::CPropVariant prop;
24  result = false;
25  RINOK(archive->GetProperty(index, propID, &prop));
26  if (prop.vt == VT_BOOL)
27    result = VARIANT_BOOLToBool(prop.boolVal);
28  else if (prop.vt != VT_EMPTY)
29    return E_FAIL;
30  return S_OK;
31}
32
33HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result)
34{
35  return GetArchiveItemBoolProp(archive, index, kpidIsDir, result);
36}
37
38HRESULT CArc::GetItemPath(UInt32 index, UString &result) const
39{
40  {
41    NCOM::CPropVariant prop;
42    RINOK(Archive->GetProperty(index, kpidPath, &prop));
43    if (prop.vt == VT_BSTR)
44      result = prop.bstrVal;
45    else if (prop.vt == VT_EMPTY)
46      result.Empty();
47    else
48      return E_FAIL;
49  }
50  if (result.IsEmpty())
51  {
52    result = DefaultName;
53    NCOM::CPropVariant prop;
54    RINOK(Archive->GetProperty(index, kpidExtension, &prop));
55    if (prop.vt == VT_BSTR)
56    {
57      result += L'.';
58      result += prop.bstrVal;
59    }
60    else if (prop.vt != VT_EMPTY)
61      return E_FAIL;
62  }
63  return S_OK;
64}
65
66HRESULT CArc::GetItemMTime(UInt32 index, FILETIME &ft, bool &defined) const
67{
68  NCOM::CPropVariant prop;
69  defined = false;
70  ft.dwHighDateTime = ft.dwLowDateTime = 0;
71  RINOK(Archive->GetProperty(index, kpidMTime, &prop));
72  if (prop.vt == VT_FILETIME)
73  {
74    ft = prop.filetime;
75    defined = true;
76  }
77  else if (prop.vt != VT_EMPTY)
78    return E_FAIL;
79  else if (MTimeDefined)
80  {
81    ft = MTime;
82    defined = true;
83  }
84  return S_OK;
85}
86
87#ifndef _SFX
88static inline bool TestSignature(const Byte *p1, const Byte *p2, size_t size)
89{
90  for (size_t i = 0; i < size; i++)
91    if (p1[i] != p2[i])
92      return false;
93  return true;
94}
95#endif
96
97#ifdef UNDER_CE
98static const int kNumHashBytes = 1;
99#define HASH_VAL(buf, pos) ((buf)[pos])
100#else
101static const int kNumHashBytes = 2;
102#define HASH_VAL(buf, pos) ((buf)[pos] | ((UInt32)(buf)[pos + 1] << 8))
103#endif
104
105
106HRESULT CArc::OpenStream(
107    CCodecs *codecs,
108    int formatIndex,
109    IInStream *stream,
110    ISequentialInStream *seqStream,
111    IArchiveOpenCallback *callback)
112{
113  Archive.Release();
114  ErrorMessage.Empty();
115  const UString fileName = ExtractFileNameFromPath(Path);
116  UString extension;
117  {
118    int dotPos = fileName.ReverseFind(L'.');
119    if (dotPos >= 0)
120      extension = fileName.Mid(dotPos + 1);
121  }
122  CIntVector orderIndices;
123  if (formatIndex >= 0)
124    orderIndices.Add(formatIndex);
125  else
126  {
127
128  int i;
129  int numFinded = 0;
130  for (i = 0; i < codecs->Formats.Size(); i++)
131    if (codecs->Formats[i].FindExtension(extension) >= 0)
132      orderIndices.Insert(numFinded++, i);
133    else
134      orderIndices.Add(i);
135
136  if (!stream)
137  {
138    if (numFinded != 1)
139      return E_NOTIMPL;
140    orderIndices.DeleteFrom(1);
141  }
142
143  #ifndef _SFX
144  if (orderIndices.Size() >= 2 && (numFinded == 0 || extension.CompareNoCase(L"exe") == 0))
145  {
146    CIntVector orderIndices2;
147    CByteBuffer byteBuffer;
148    const size_t kBufferSize = (1 << 21);
149    byteBuffer.SetCapacity(kBufferSize);
150    RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
151    size_t processedSize = kBufferSize;
152    RINOK(ReadStream(stream, byteBuffer, &processedSize));
153    if (processedSize == 0)
154      return S_FALSE;
155
156    const Byte *buf = byteBuffer;
157    CByteBuffer hashBuffer;
158    const UInt32 kNumVals = 1 << (kNumHashBytes * 8);
159    hashBuffer.SetCapacity(kNumVals);
160    Byte *hash = hashBuffer;
161    memset(hash, 0xFF, kNumVals);
162    Byte prevs[256];
163    if (orderIndices.Size() >= 256)
164      return S_FALSE;
165    int i;
166    for (i = 0; i < orderIndices.Size(); i++)
167    {
168      const CArcInfoEx &ai = codecs->Formats[orderIndices[i]];
169      const CByteBuffer &sig = ai.StartSignature;
170      if (sig.GetCapacity() < kNumHashBytes)
171        continue;
172      UInt32 v = HASH_VAL(sig, 0);
173      prevs[i] = hash[v];
174      hash[v] = (Byte)i;
175    }
176
177    processedSize -= (kNumHashBytes - 1);
178    for (UInt32 pos = 0; pos < processedSize; pos++)
179    {
180      for (; pos < processedSize && hash[HASH_VAL(buf, pos)] == 0xFF; pos++);
181      if (pos == processedSize)
182        break;
183      UInt32 v = HASH_VAL(buf, pos);
184      Byte *ptr = &hash[v];
185      int i = *ptr;
186      do
187      {
188        int index = orderIndices[i];
189        const CArcInfoEx &ai = codecs->Formats[index];
190        const CByteBuffer &sig = ai.StartSignature;
191        if (sig.GetCapacity() != 0 && pos + sig.GetCapacity() <= processedSize + (kNumHashBytes - 1) &&
192            TestSignature(buf + pos, sig, sig.GetCapacity()))
193        {
194          orderIndices2.Add(index);
195          orderIndices[i] = 0xFF;
196          *ptr = prevs[i];
197        }
198        else
199          ptr = &prevs[i];
200        i = *ptr;
201      }
202      while (i != 0xFF);
203    }
204
205    for (i = 0; i < orderIndices.Size(); i++)
206    {
207      int val = orderIndices[i];
208      if (val != 0xFF)
209        orderIndices2.Add(val);
210    }
211    orderIndices = orderIndices2;
212  }
213  else if (extension == L"000" || extension == L"001")
214  {
215    CByteBuffer byteBuffer;
216    const size_t kBufferSize = (1 << 10);
217    byteBuffer.SetCapacity(kBufferSize);
218    Byte *buffer = byteBuffer;
219    RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
220    size_t processedSize = kBufferSize;
221    RINOK(ReadStream(stream, buffer, &processedSize));
222    if (processedSize >= 16)
223    {
224      Byte kRarHeader[] = {0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00};
225      if (TestSignature(buffer, kRarHeader, 7) && buffer[9] == 0x73 && (buffer[10] & 1) != 0)
226      {
227        for (int i = 0; i < orderIndices.Size(); i++)
228        {
229          int index = orderIndices[i];
230          const CArcInfoEx &ai = codecs->Formats[index];
231          if (ai.Name.CompareNoCase(L"rar") != 0)
232            continue;
233          orderIndices.Delete(i--);
234          orderIndices.Insert(0, index);
235          break;
236        }
237      }
238    }
239  }
240  if (orderIndices.Size() >= 2)
241  {
242    int isoIndex = codecs->FindFormatForArchiveType(L"iso");
243    int udfIndex = codecs->FindFormatForArchiveType(L"udf");
244    int iIso = -1;
245    int iUdf = -1;
246    for (int i = 0; i < orderIndices.Size(); i++)
247    {
248      if (orderIndices[i] == isoIndex) iIso = i;
249      if (orderIndices[i] == udfIndex) iUdf = i;
250    }
251    if (iUdf > iIso && iIso >= 0)
252    {
253      orderIndices[iUdf] = isoIndex;
254      orderIndices[iIso] = udfIndex;
255    }
256  }
257
258  #endif
259  }
260
261  for (int i = 0; i < orderIndices.Size(); i++)
262  {
263    if (stream)
264    {
265      RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
266    }
267    CMyComPtr<IInArchive> archive;
268
269    FormatIndex = orderIndices[i];
270    RINOK(codecs->CreateInArchive(FormatIndex, archive));
271    if (!archive)
272      continue;
273
274    #ifdef EXTERNAL_CODECS
275    {
276      CMyComPtr<ISetCompressCodecsInfo> setCompressCodecsInfo;
277      archive.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo);
278      if (setCompressCodecsInfo)
279      {
280        RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(codecs));
281      }
282    }
283    #endif
284
285    // OutputDebugStringW(codecs->Formats[FormatIndex].Name);
286
287    HRESULT result;
288    if (stream)
289      result = archive->Open(stream, &kMaxCheckStartPosition, callback);
290    else
291    {
292      CMyComPtr<IArchiveOpenSeq> openSeq;
293      archive.QueryInterface(IID_IArchiveOpenSeq, (void **)&openSeq);
294      if (!openSeq)
295        return E_NOTIMPL;
296      result = openSeq->OpenSeq(seqStream);
297    }
298
299    if (result == S_FALSE)
300      continue;
301    RINOK(result);
302
303    {
304      NCOM::CPropVariant prop;
305      archive->GetArchiveProperty(kpidError, &prop);
306      if (prop.vt != VT_EMPTY)
307        ErrorMessage = (prop.vt == VT_BSTR) ? prop.bstrVal : L"Unknown error";
308    }
309
310    Archive = archive;
311    const CArcInfoEx &format = codecs->Formats[FormatIndex];
312    if (format.Exts.Size() == 0)
313      DefaultName = GetDefaultName2(fileName, L"", L"");
314    else
315    {
316      int subExtIndex = format.FindExtension(extension);
317      if (subExtIndex < 0)
318        subExtIndex = 0;
319      const CArcExtInfo &extInfo = format.Exts[subExtIndex];
320      DefaultName = GetDefaultName2(fileName, extInfo.Ext, extInfo.AddExt);
321    }
322    return S_OK;
323  }
324  return S_FALSE;
325}
326
327HRESULT CArc::OpenStreamOrFile(
328    CCodecs *codecs,
329    int formatIndex,
330    bool stdInMode,
331    IInStream *stream,
332    IArchiveOpenCallback *callback)
333{
334  CMyComPtr<IInStream> fileStream;
335  CMyComPtr<ISequentialInStream> seqStream;
336  if (stdInMode)
337    seqStream = new CStdInFileStream;
338  else if (!stream)
339  {
340    CInFileStream *fileStreamSpec = new CInFileStream;
341    fileStream = fileStreamSpec;
342    if (!fileStreamSpec->Open(Path))
343      return GetLastError();
344    stream = fileStream;
345  }
346
347  /*
348  if (callback)
349  {
350    UInt64 fileSize;
351    RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize));
352    RINOK(callback->SetTotal(NULL, &fileSize))
353  }
354  */
355
356  return OpenStream(codecs, formatIndex, stream, seqStream, callback);
357}
358
359HRESULT CArchiveLink::Close()
360{
361  for (int i = Arcs.Size() - 1;  i >= 0; i--)
362  {
363    RINOK(Arcs[i].Archive->Close());
364  }
365  IsOpen = false;
366  return S_OK;
367}
368
369void CArchiveLink::Release()
370{
371  while (!Arcs.IsEmpty())
372    Arcs.DeleteBack();
373}
374
375HRESULT CArchiveLink::Open(
376    CCodecs *codecs,
377    const CIntVector &formatIndices,
378    bool stdInMode,
379    IInStream *stream,
380    const UString &filePath,
381    IArchiveOpenCallback *callback)
382{
383  Release();
384  if (formatIndices.Size() >= 32)
385    return E_NOTIMPL;
386
387  HRESULT resSpec;
388
389  for (;;)
390  {
391    resSpec = S_OK;
392    int formatIndex = -1;
393    if (formatIndices.Size() >= 1)
394    {
395      if (Arcs.Size() >= formatIndices.Size())
396        break;
397      formatIndex = formatIndices[formatIndices.Size() - Arcs.Size() - 1];
398    }
399    else if (Arcs.Size() >= 32)
400      break;
401
402    if (Arcs.IsEmpty())
403    {
404      CArc arc;
405      arc.Path = filePath;
406      arc.SubfileIndex = (UInt32)(Int32)-1;
407      RINOK(arc.OpenStreamOrFile(codecs, formatIndex, stdInMode, stream, callback));
408      Arcs.Add(arc);
409      continue;
410    }
411
412    const CArc &arc = Arcs.Back();
413
414    resSpec = (formatIndices.Size() == 0 ? S_OK : E_NOTIMPL);
415
416    UInt32 mainSubfile;
417    {
418      NCOM::CPropVariant prop;
419      RINOK(arc.Archive->GetArchiveProperty(kpidMainSubfile, &prop));
420      if (prop.vt == VT_UI4)
421        mainSubfile = prop.ulVal;
422      else
423        break;
424      UInt32 numItems;
425      RINOK(arc.Archive->GetNumberOfItems(&numItems));
426      if (mainSubfile >= numItems)
427        break;
428    }
429
430
431    CMyComPtr<IInArchiveGetStream> getStream;
432    if (arc.Archive->QueryInterface(IID_IInArchiveGetStream, (void **)&getStream) != S_OK || !getStream)
433      break;
434
435    CMyComPtr<ISequentialInStream> subSeqStream;
436    if (getStream->GetStream(mainSubfile, &subSeqStream) != S_OK || !subSeqStream)
437      break;
438
439    CMyComPtr<IInStream> subStream;
440    if (subSeqStream.QueryInterface(IID_IInStream, &subStream) != S_OK || !subStream)
441      break;
442
443    CArc arc2;
444    RINOK(arc.GetItemPath(mainSubfile, arc2.Path));
445
446    CMyComPtr<IArchiveOpenSetSubArchiveName> setSubArchiveName;
447    callback->QueryInterface(IID_IArchiveOpenSetSubArchiveName, (void **)&setSubArchiveName);
448    if (setSubArchiveName)
449      setSubArchiveName->SetSubArchiveName(arc2.Path);
450
451    arc2.SubfileIndex = mainSubfile;
452    HRESULT result = arc2.OpenStream(codecs, formatIndex, subStream, NULL, callback);
453    resSpec = (formatIndices.Size() == 0 ? S_OK : S_FALSE);
454    if (result == S_FALSE)
455      break;
456    RINOK(result);
457    RINOK(arc.GetItemMTime(mainSubfile, arc2.MTime, arc2.MTimeDefined));
458    Arcs.Add(arc2);
459  }
460  IsOpen = !Arcs.IsEmpty();
461  return S_OK;
462}
463
464static void SetCallback(const UString &filePath,
465    IOpenCallbackUI *callbackUI,
466    IArchiveOpenCallback *reOpenCallback,
467    CMyComPtr<IArchiveOpenCallback> &callback)
468{
469  COpenCallbackImp *openCallbackSpec = new COpenCallbackImp;
470  callback = openCallbackSpec;
471  openCallbackSpec->Callback = callbackUI;
472  openCallbackSpec->ReOpenCallback = reOpenCallback;
473
474  UString fullName;
475  int fileNamePartStartIndex;
476  NFile::NDirectory::MyGetFullPathName(filePath, fullName, fileNamePartStartIndex);
477  openCallbackSpec->Init(
478      fullName.Left(fileNamePartStartIndex),
479      fullName.Mid(fileNamePartStartIndex));
480}
481
482HRESULT CArchiveLink::Open2(CCodecs *codecs,
483    const CIntVector &formatIndices,
484    bool stdInMode,
485    IInStream *stream,
486    const UString &filePath,
487    IOpenCallbackUI *callbackUI)
488{
489  VolumesSize = 0;
490  COpenCallbackImp *openCallbackSpec = new COpenCallbackImp;
491  CMyComPtr<IArchiveOpenCallback> callback = openCallbackSpec;
492  openCallbackSpec->Callback = callbackUI;
493
494  UString fullName, prefix, name;
495  if (!stream && !stdInMode)
496  {
497    int fileNamePartStartIndex;
498    if (!NFile::NDirectory::MyGetFullPathName(filePath, fullName, fileNamePartStartIndex))
499      return GetLastError();
500    prefix = fullName.Left(fileNamePartStartIndex);
501    name = fullName.Mid(fileNamePartStartIndex);
502    openCallbackSpec->Init(prefix, name);
503  }
504  else
505  {
506    openCallbackSpec->SetSubArchiveName(filePath);
507  }
508
509  RINOK(Open(codecs, formatIndices, stdInMode, stream, filePath, callback));
510  VolumePaths.Add(prefix + name);
511  for (int i = 0; i < openCallbackSpec->FileNames.Size(); i++)
512    VolumePaths.Add(prefix + openCallbackSpec->FileNames[i]);
513  VolumesSize = openCallbackSpec->TotalSize;
514  return S_OK;
515}
516
517HRESULT CArchiveLink::ReOpen(CCodecs *codecs, const UString &filePath,
518    IArchiveOpenCallback *callback)
519{
520  if (Arcs.Size() > 1)
521    return E_NOTIMPL;
522
523  if (Arcs.Size() == 0)
524    return Open2(codecs, CIntVector(), false, NULL, filePath, 0);
525
526  CMyComPtr<IArchiveOpenCallback> openCallbackNew;
527  SetCallback(filePath, NULL, callback, openCallbackNew);
528
529  CInFileStream *fileStreamSpec = new CInFileStream;
530  CMyComPtr<IInStream> stream(fileStreamSpec);
531  if (!fileStreamSpec->Open(filePath))
532    return GetLastError();
533  HRESULT res = GetArchive()->Open(stream, &kMaxCheckStartPosition, callback);
534  IsOpen = (res == S_OK);
535  return res;
536}
537