1// ArchiveOpenCallback.cpp
2
3#include "StdAfx.h"
4
5#include "../../../Common/ComTry.h"
6
7#include "../../../Windows/FileName.h"
8#include "../../../Windows/PropVariant.h"
9#include "../../../Windows/System.h"
10
11#include "../../Common/StreamUtils.h"
12
13#include "ArchiveOpenCallback.h"
14
15// #define DEBUG_VOLUMES
16
17#ifdef DEBUG_VOLUMES
18#include <stdio.h>
19#endif
20
21
22#ifdef DEBUG_VOLUMES
23  #define PRF(x) x
24#else
25  #define PRF(x)
26#endif
27
28using namespace NWindows;
29
30HRESULT COpenCallbackImp::Init2(const FString &folderPrefix, const FString &fileName)
31{
32  Volumes.Init();
33  FileNames.Clear();
34  FileNames_WasUsed.Clear();
35  FileSizes.Clear();
36  _subArchiveMode = false;
37  // TotalSize = 0;
38  PasswordWasAsked = false;
39  _folderPrefix = folderPrefix;
40  if (!_fileInfo.Find_FollowLink(_folderPrefix + fileName))
41  {
42    // throw 20121118;
43    return GetLastError_noZero_HRESULT();
44  }
45  return S_OK;
46}
47
48Z7_COM7F_IMF(COpenCallbackImp::SetSubArchiveName(const wchar_t *name))
49{
50  _subArchiveMode = true;
51  _subArchiveName = name;
52  // TotalSize = 0;
53  return S_OK;
54}
55
56Z7_COM7F_IMF(COpenCallbackImp::SetTotal(const UInt64 *files, const UInt64 *bytes))
57{
58  COM_TRY_BEGIN
59  if (ReOpenCallback)
60    return ReOpenCallback->SetTotal(files, bytes);
61  if (!Callback)
62    return S_OK;
63  return Callback->Open_SetTotal(files, bytes);
64  COM_TRY_END
65}
66
67Z7_COM7F_IMF(COpenCallbackImp::SetCompleted(const UInt64 *files, const UInt64 *bytes))
68{
69  COM_TRY_BEGIN
70  if (ReOpenCallback)
71    return ReOpenCallback->SetCompleted(files, bytes);
72  if (!Callback)
73    return S_OK;
74  return Callback->Open_SetCompleted(files, bytes);
75  COM_TRY_END
76}
77
78
79Z7_COM7F_IMF(COpenCallbackImp::GetProperty(PROPID propID, PROPVARIANT *value))
80{
81  COM_TRY_BEGIN
82  NCOM::CPropVariant prop;
83  if (_subArchiveMode)
84    switch (propID)
85    {
86      case kpidName: prop = _subArchiveName; break;
87      // case kpidSize:  prop = _subArchiveSize; break; // we don't use it now
88    }
89  else
90    switch (propID)
91    {
92      case kpidName:  prop = fs2us(_fileInfo.Name); break;
93      case kpidIsDir:  prop = _fileInfo.IsDir(); break;
94      case kpidSize:  prop = _fileInfo.Size; break;
95      case kpidAttrib:  prop = (UInt32)_fileInfo.GetWinAttrib(); break;
96      case kpidPosixAttrib:  prop = (UInt32)_fileInfo.GetPosixAttrib(); break;
97      case kpidCTime:  PropVariant_SetFrom_FiTime(prop, _fileInfo.CTime); break;
98      case kpidATime:  PropVariant_SetFrom_FiTime(prop, _fileInfo.ATime); break;
99      case kpidMTime:  PropVariant_SetFrom_FiTime(prop, _fileInfo.MTime); break;
100    }
101  prop.Detach(value);
102  return S_OK;
103  COM_TRY_END
104}
105
106
107// ---------- CInFileStreamVol ----------
108
109Z7_CLASS_IMP_COM_2(
110  CInFileStreamVol
111  , IInStream
112  , IStreamGetSize
113)
114  Z7_IFACE_COM7_IMP(ISequentialInStream)
115public:
116  unsigned FileIndex;
117  COpenCallbackImp *OpenCallbackImp;
118  CMyComPtr<IArchiveOpenCallback> OpenCallbackRef;
119
120  HRESULT EnsureOpen()
121  {
122    return OpenCallbackImp->Volumes.EnsureOpen(FileIndex);
123  }
124
125  ~CInFileStreamVol()
126  {
127    if (OpenCallbackRef)
128      OpenCallbackImp->AtCloseFile(FileIndex);
129  }
130};
131
132
133void CMultiStreams::InsertToList(unsigned index)
134{
135  {
136    CSubStream &s = Streams[index];
137    s.Next = Head;
138    s.Prev = -1;
139  }
140  if (Head != -1)
141    Streams[(unsigned)Head].Prev = (int)index;
142  else
143  {
144    // if (Tail != -1) throw 1;
145    Tail = (int)index;
146  }
147  Head = (int)index;
148  NumListItems++;
149}
150
151// s must bee in List
152void CMultiStreams::RemoveFromList(CSubStream &s)
153{
154  if (s.Next != -1) Streams[(unsigned)s.Next].Prev = s.Prev; else Tail = s.Prev;
155  if (s.Prev != -1) Streams[(unsigned)s.Prev].Next = s.Next; else Head = s.Next;
156  s.Next = -1; // optional
157  s.Prev = -1; // optional
158  NumListItems--;
159}
160
161void CMultiStreams::CloseFile(unsigned index)
162{
163  CSubStream &s = Streams[index];
164  if (s.Stream)
165  {
166    s.Stream.Release();
167    RemoveFromList(s);
168    // s.InFile->Close();
169    // s.IsOpen = false;
170   #ifdef DEBUG_VOLUMES
171    static int numClosing = 0;
172    numClosing++;
173    printf("\nCloseFile %u, total_closes = %u, num_open_files = %u\n", index, numClosing, NumListItems);
174   #endif
175  }
176}
177
178void CMultiStreams::Init()
179{
180  Head = -1;
181  Tail = -1;
182  NumListItems = 0;
183  Streams.Clear();
184}
185
186CMultiStreams::CMultiStreams():
187    Head(-1),
188    Tail(-1),
189    NumListItems(0)
190{
191  NumOpenFiles_AllowedMax = NSystem::Get_File_OPEN_MAX_Reduced_for_3_tasks();
192  PRF(printf("\nNumOpenFiles_Limit = %u\n", NumOpenFiles_AllowedMax));
193}
194
195
196HRESULT CMultiStreams::PrepareToOpenNew()
197{
198  if (NumListItems < NumOpenFiles_AllowedMax)
199    return S_OK;
200  if (Tail == -1)
201    return E_FAIL;
202  CMultiStreams::CSubStream &tailStream = Streams[(unsigned)Tail];
203  RINOK(InStream_GetPos(tailStream.Stream, tailStream.LocalPos))
204  CloseFile((unsigned)Tail);
205  return S_OK;
206}
207
208
209HRESULT CMultiStreams::EnsureOpen(unsigned index)
210{
211  CMultiStreams::CSubStream &s = Streams[index];
212  if (s.Stream)
213  {
214    if ((int)index != Head)
215    {
216      RemoveFromList(s);
217      InsertToList(index);
218    }
219  }
220  else
221  {
222    RINOK(PrepareToOpenNew())
223    {
224      CInFileStream *inFile = new CInFileStream;
225      CMyComPtr<IInStream> inStreamTemp = inFile;
226      if (!inFile->Open(s.Path))
227        return GetLastError_noZero_HRESULT();
228      s.FileSpec = inFile;
229      s.Stream = s.FileSpec;
230      InsertToList(index);
231    }
232    // s.IsOpen = true;
233    if (s.LocalPos != 0)
234    {
235      RINOK(s.Stream->Seek((Int64)s.LocalPos, STREAM_SEEK_SET, &s.LocalPos))
236    }
237   #ifdef DEBUG_VOLUMES
238    static int numOpens = 0;
239    numOpens++;
240    printf("\n-- %u, ReOpen, total_reopens = %u, num_open_files = %u\n", index, numOpens, NumListItems);
241   #endif
242  }
243  return S_OK;
244}
245
246
247Z7_COM7F_IMF(CInFileStreamVol::Read(void *data, UInt32 size, UInt32 *processedSize))
248{
249  if (processedSize)
250    *processedSize = 0;
251  if (size == 0)
252    return S_OK;
253  RINOK(EnsureOpen())
254  CMultiStreams::CSubStream &s = OpenCallbackImp->Volumes.Streams[FileIndex];
255  PRF(printf("\n== %u, Read =%u \n", FileIndex, size));
256  return s.Stream->Read(data, size, processedSize);
257}
258
259Z7_COM7F_IMF(CInFileStreamVol::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition))
260{
261  // if (seekOrigin >= 3) return STG_E_INVALIDFUNCTION;
262  RINOK(EnsureOpen())
263  CMultiStreams::CSubStream &s = OpenCallbackImp->Volumes.Streams[FileIndex];
264  PRF(printf("\n-- %u, Seek seekOrigin=%u Seek =%u\n", FileIndex, seekOrigin, (unsigned)offset));
265  return s.Stream->Seek(offset, seekOrigin, newPosition);
266}
267
268Z7_COM7F_IMF(CInFileStreamVol::GetSize(UInt64 *size))
269{
270  RINOK(EnsureOpen())
271  CMultiStreams::CSubStream &s = OpenCallbackImp->Volumes.Streams[FileIndex];
272  return s.FileSpec->GetSize(size);
273}
274
275
276// from ArchiveExtractCallback.cpp
277bool IsSafePath(const UString &path);
278
279Z7_COM7F_IMF(COpenCallbackImp::GetStream(const wchar_t *name, IInStream **inStream))
280{
281  COM_TRY_BEGIN
282  *inStream = NULL;
283
284  if (_subArchiveMode)
285    return S_FALSE;
286  if (Callback)
287  {
288    RINOK(Callback->Open_CheckBreak())
289  }
290
291  UString name2 = name;
292
293
294  #ifndef Z7_SFX
295
296  #ifdef _WIN32
297  name2.Replace(L'/', WCHAR_PATH_SEPARATOR);
298  #endif
299
300  // if (!allowAbsVolPaths)
301  if (!IsSafePath(name2))
302    return S_FALSE;
303
304  #ifdef _WIN32
305  /* WIN32 allows wildcards in Find() function
306     and doesn't allow wildcard in File.Open()
307     so we can work without the following wildcard check here */
308  if (name2.Find(L'*') >= 0)
309    return S_FALSE;
310  {
311    unsigned startPos = 0;
312    if (name2.IsPrefixedBy_Ascii_NoCase("\\\\?\\"))
313      startPos = 3;
314    if (name2.Find(L'?', startPos) >= 0)
315      return S_FALSE;
316  }
317  #endif
318
319  #endif
320
321
322  FString fullPath;
323  if (!NFile::NName::GetFullPath(_folderPrefix, us2fs(name2), fullPath))
324    return S_FALSE;
325  if (!_fileInfo.Find_FollowLink(fullPath))
326    return S_FALSE;
327  if (_fileInfo.IsDir())
328    return S_FALSE;
329
330  CMultiStreams::CSubStream s;
331
332  {
333    CInFileStream *inFile = new CInFileStream;
334    CMyComPtr<IInStream> inStreamTemp = inFile;
335    if (!inFile->Open(fullPath))
336      return GetLastError_noZero_HRESULT();
337    RINOK(Volumes.PrepareToOpenNew())
338    s.FileSpec = inFile;
339    s.Stream = s.FileSpec;
340    s.Path = fullPath;
341    // s.Size = _fileInfo.Size;
342    // s.IsOpen = true;
343  }
344
345  const unsigned fileIndex = Volumes.Streams.Add(s);
346  Volumes.InsertToList(fileIndex);
347
348  FileSizes.Add(_fileInfo.Size);
349  FileNames.Add(name2);
350  FileNames_WasUsed.Add(true);
351
352  CInFileStreamVol *inFile = new CInFileStreamVol;
353  CMyComPtr<IInStream> inStreamTemp = inFile;
354  inFile->FileIndex = fileIndex;
355  inFile->OpenCallbackImp = this;
356  inFile->OpenCallbackRef = this;
357  // TotalSize += _fileInfo.Size;
358  *inStream = inStreamTemp.Detach();
359  return S_OK;
360  COM_TRY_END
361}
362
363
364#ifndef Z7_NO_CRYPTO
365Z7_COM7F_IMF(COpenCallbackImp::CryptoGetTextPassword(BSTR *password))
366{
367  COM_TRY_BEGIN
368  if (ReOpenCallback)
369  {
370    Z7_DECL_CMyComPtr_QI_FROM(
371        ICryptoGetTextPassword,
372        getTextPassword, ReOpenCallback)
373    if (getTextPassword)
374      return getTextPassword->CryptoGetTextPassword(password);
375  }
376  if (!Callback)
377    return E_NOTIMPL;
378  PasswordWasAsked = true;
379  return Callback->Open_CryptoGetTextPassword(password);
380  COM_TRY_END
381}
382#endif
383
384// IProgress
385Z7_COM7F_IMF(COpenCallbackImp::SetTotal(const UInt64 /* total */))
386{
387  return S_OK;
388}
389
390Z7_COM7F_IMF(COpenCallbackImp::SetCompleted(const UInt64 * /* completed */))
391{
392  return S_OK;
393}
394