7zHandlerOut.cpp revision baa3858d3f5d128a5c8466b700098109edcad5f2
1// 7zHandlerOut.cpp
2
3#include "StdAfx.h"
4
5#include "../../../Windows/PropVariant.h"
6
7#include "../../../Common/ComTry.h"
8#include "../../../Common/StringToInt.h"
9
10#include "../../ICoder.h"
11
12#include "../Common/ItemNameUtils.h"
13#include "../Common/ParseProperties.h"
14
15#include "7zHandler.h"
16#include "7zOut.h"
17#include "7zUpdate.h"
18
19using namespace NWindows;
20
21namespace NArchive {
22namespace N7z {
23
24static const wchar_t *kLZMAMethodName = L"LZMA";
25static const wchar_t *kCopyMethod = L"Copy";
26static const wchar_t *kDefaultMethodName = kLZMAMethodName;
27
28static const UInt32 kLzmaAlgorithmX5 = 1;
29static const wchar_t *kLzmaMatchFinderForHeaders = L"BT2";
30static const UInt32 kDictionaryForHeaders =
31  #ifdef UNDER_CE
32  1 << 18
33  #else
34  1 << 20
35  #endif
36;
37static const UInt32 kNumFastBytesForHeaders = 273;
38static const UInt32 kAlgorithmForHeaders = kLzmaAlgorithmX5;
39
40static inline bool IsCopyMethod(const UString &methodName)
41  { return (methodName.CompareNoCase(kCopyMethod) == 0); }
42
43STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type)
44{
45  *type = NFileTimeType::kWindows;
46  return S_OK;
47}
48
49HRESULT CHandler::SetCompressionMethod(
50    CCompressionMethodMode &methodMode,
51    CCompressionMethodMode &headerMethod)
52{
53  HRESULT res = SetCompressionMethod(methodMode, _methods
54  #ifndef _7ZIP_ST
55  , _numThreads
56  #endif
57  );
58  RINOK(res);
59  methodMode.Binds = _binds;
60
61  if (_compressHeaders)
62  {
63    // headerMethod.Methods.Add(methodMode.Methods.Back());
64
65    CObjectVector<COneMethodInfo> headerMethodInfoVector;
66    COneMethodInfo oneMethodInfo;
67    oneMethodInfo.MethodName = kLZMAMethodName;
68    {
69      CProp prop;
70      prop.Id = NCoderPropID::kMatchFinder;
71      prop.Value = kLzmaMatchFinderForHeaders;
72      oneMethodInfo.Props.Add(prop);
73    }
74    {
75      CProp prop;
76      prop.Id = NCoderPropID::kAlgorithm;
77      prop.Value = kAlgorithmForHeaders;
78      oneMethodInfo.Props.Add(prop);
79    }
80    {
81      CProp prop;
82      prop.Id = NCoderPropID::kNumFastBytes;
83      prop.Value = (UInt32)kNumFastBytesForHeaders;
84      oneMethodInfo.Props.Add(prop);
85    }
86    {
87      CProp prop;
88      prop.Id = NCoderPropID::kDictionarySize;
89      prop.Value = (UInt32)kDictionaryForHeaders;
90      oneMethodInfo.Props.Add(prop);
91    }
92    headerMethodInfoVector.Add(oneMethodInfo);
93    HRESULT res = SetCompressionMethod(headerMethod, headerMethodInfoVector
94      #ifndef _7ZIP_ST
95      , 1
96      #endif
97    );
98    RINOK(res);
99  }
100  return S_OK;
101}
102
103HRESULT CHandler::SetCompressionMethod(
104    CCompressionMethodMode &methodMode,
105    CObjectVector<COneMethodInfo> &methodsInfo
106    #ifndef _7ZIP_ST
107    , UInt32 numThreads
108    #endif
109    )
110{
111  UInt32 level = _level;
112
113  if (methodsInfo.IsEmpty())
114  {
115    COneMethodInfo oneMethodInfo;
116    oneMethodInfo.MethodName = ((level == 0) ? kCopyMethod : kDefaultMethodName);
117    methodsInfo.Add(oneMethodInfo);
118  }
119
120  bool needSolid = false;
121  for(int i = 0; i < methodsInfo.Size(); i++)
122  {
123    COneMethodInfo &oneMethodInfo = methodsInfo[i];
124    SetCompressionMethod2(oneMethodInfo
125      #ifndef _7ZIP_ST
126      , numThreads
127      #endif
128      );
129
130    if (!IsCopyMethod(oneMethodInfo.MethodName))
131      needSolid = true;
132
133    CMethodFull methodFull;
134
135    if (!FindMethod(
136        EXTERNAL_CODECS_VARS
137        oneMethodInfo.MethodName, methodFull.Id, methodFull.NumInStreams, methodFull.NumOutStreams))
138      return E_INVALIDARG;
139    methodFull.Props = oneMethodInfo.Props;
140    methodMode.Methods.Add(methodFull);
141
142    if (!_numSolidBytesDefined)
143    {
144      for (int j = 0; j < methodFull.Props.Size(); j++)
145      {
146        const CProp &prop = methodFull.Props[j];
147        if ((prop.Id == NCoderPropID::kDictionarySize ||
148             prop.Id == NCoderPropID::kUsedMemorySize) && prop.Value.vt == VT_UI4)
149        {
150          _numSolidBytes = ((UInt64)prop.Value.ulVal) << 7;
151          const UInt64 kMinSize = (1 << 24);
152          if (_numSolidBytes < kMinSize)
153            _numSolidBytes = kMinSize;
154          _numSolidBytesDefined = true;
155          break;
156        }
157      }
158    }
159  }
160
161  if (!needSolid && !_numSolidBytesDefined)
162  {
163    _numSolidBytesDefined = true;
164    _numSolidBytes  = 0;
165  }
166  return S_OK;
167}
168
169static HRESULT GetTime(IArchiveUpdateCallback *updateCallback, int index, bool writeTime, PROPID propID, UInt64 &ft, bool &ftDefined)
170{
171  ft = 0;
172  ftDefined = false;
173  if (!writeTime)
174    return S_OK;
175  NCOM::CPropVariant prop;
176  RINOK(updateCallback->GetProperty(index, propID, &prop));
177  if (prop.vt == VT_FILETIME)
178  {
179    ft = prop.filetime.dwLowDateTime | ((UInt64)prop.filetime.dwHighDateTime << 32);
180    ftDefined = true;
181  }
182  else if (prop.vt != VT_EMPTY)
183    return E_INVALIDARG;
184  return S_OK;
185}
186
187STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
188    IArchiveUpdateCallback *updateCallback)
189{
190  COM_TRY_BEGIN
191
192  const CArchiveDatabaseEx *db = 0;
193  #ifdef _7Z_VOL
194  if (_volumes.Size() > 1)
195    return E_FAIL;
196  const CVolume *volume = 0;
197  if (_volumes.Size() == 1)
198  {
199    volume = &_volumes.Front();
200    db = &volume->Database;
201  }
202  #else
203  if (_inStream != 0)
204    db = &_db;
205  #endif
206
207  CObjectVector<CUpdateItem> updateItems;
208
209  for (UInt32 i = 0; i < numItems; i++)
210  {
211    Int32 newData, newProps;
212    UInt32 indexInArchive;
213    if (!updateCallback)
214      return E_FAIL;
215    RINOK(updateCallback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive));
216    CUpdateItem ui;
217    ui.NewProps = IntToBool(newProps);
218    ui.NewData = IntToBool(newData);
219    ui.IndexInArchive = indexInArchive;
220    ui.IndexInClient = i;
221    ui.IsAnti = false;
222    ui.Size = 0;
223
224    if (ui.IndexInArchive != -1)
225    {
226      if (db == 0 || ui.IndexInArchive >= db->Files.Size())
227        return E_INVALIDARG;
228      const CFileItem &fi = db->Files[ui.IndexInArchive];
229      ui.Name = fi.Name;
230      ui.IsDir = fi.IsDir;
231      ui.Size = fi.Size;
232      ui.IsAnti = db->IsItemAnti(ui.IndexInArchive);
233
234      ui.CTimeDefined = db->CTime.GetItem(ui.IndexInArchive, ui.CTime);
235      ui.ATimeDefined = db->ATime.GetItem(ui.IndexInArchive, ui.ATime);
236      ui.MTimeDefined = db->MTime.GetItem(ui.IndexInArchive, ui.MTime);
237    }
238
239    if (ui.NewProps)
240    {
241      bool nameIsDefined;
242      bool folderStatusIsDefined;
243      {
244        NCOM::CPropVariant prop;
245        RINOK(updateCallback->GetProperty(i, kpidAttrib, &prop));
246        if (prop.vt == VT_EMPTY)
247          ui.AttribDefined = false;
248        else if (prop.vt != VT_UI4)
249          return E_INVALIDARG;
250        else
251        {
252          ui.Attrib = prop.ulVal;
253          ui.AttribDefined = true;
254        }
255      }
256
257      // we need MTime to sort files.
258      RINOK(GetTime(updateCallback, i, WriteCTime, kpidCTime, ui.CTime, ui.CTimeDefined));
259      RINOK(GetTime(updateCallback, i, WriteATime, kpidATime, ui.ATime, ui.ATimeDefined));
260      RINOK(GetTime(updateCallback, i, true,       kpidMTime, ui.MTime, ui.MTimeDefined));
261
262      {
263        NCOM::CPropVariant prop;
264        RINOK(updateCallback->GetProperty(i, kpidPath, &prop));
265        if (prop.vt == VT_EMPTY)
266          nameIsDefined = false;
267        else if (prop.vt != VT_BSTR)
268          return E_INVALIDARG;
269        else
270        {
271          ui.Name = NItemName::MakeLegalName(prop.bstrVal);
272          nameIsDefined = true;
273        }
274      }
275      {
276        NCOM::CPropVariant prop;
277        RINOK(updateCallback->GetProperty(i, kpidIsDir, &prop));
278        if (prop.vt == VT_EMPTY)
279          folderStatusIsDefined = false;
280        else if (prop.vt != VT_BOOL)
281          return E_INVALIDARG;
282        else
283        {
284          ui.IsDir = (prop.boolVal != VARIANT_FALSE);
285          folderStatusIsDefined = true;
286        }
287      }
288
289      {
290        NCOM::CPropVariant prop;
291        RINOK(updateCallback->GetProperty(i, kpidIsAnti, &prop));
292        if (prop.vt == VT_EMPTY)
293          ui.IsAnti = false;
294        else if (prop.vt != VT_BOOL)
295          return E_INVALIDARG;
296        else
297          ui.IsAnti = (prop.boolVal != VARIANT_FALSE);
298      }
299
300      if (ui.IsAnti)
301      {
302        ui.AttribDefined = false;
303
304        ui.CTimeDefined = false;
305        ui.ATimeDefined = false;
306        ui.MTimeDefined = false;
307
308        ui.Size = 0;
309      }
310
311      if (!folderStatusIsDefined && ui.AttribDefined)
312        ui.SetDirStatusFromAttrib();
313    }
314
315    if (ui.NewData)
316    {
317      NCOM::CPropVariant prop;
318      RINOK(updateCallback->GetProperty(i, kpidSize, &prop));
319      if (prop.vt != VT_UI8)
320        return E_INVALIDARG;
321      ui.Size = (UInt64)prop.uhVal.QuadPart;
322      if (ui.Size != 0 && ui.IsAnti)
323        return E_INVALIDARG;
324    }
325    updateItems.Add(ui);
326  }
327
328  CCompressionMethodMode methodMode, headerMethod;
329  RINOK(SetCompressionMethod(methodMode, headerMethod));
330  #ifndef _7ZIP_ST
331  methodMode.NumThreads = _numThreads;
332  headerMethod.NumThreads = 1;
333  #endif
334
335  CMyComPtr<ICryptoGetTextPassword2> getPassword2;
336  updateCallback->QueryInterface(IID_ICryptoGetTextPassword2, (void **)&getPassword2);
337
338  if (getPassword2)
339  {
340    CMyComBSTR password;
341    Int32 passwordIsDefined;
342    RINOK(getPassword2->CryptoGetTextPassword2(&passwordIsDefined, &password));
343    methodMode.PasswordIsDefined = IntToBool(passwordIsDefined);
344    if (methodMode.PasswordIsDefined)
345      methodMode.Password = password;
346  }
347  else
348    methodMode.PasswordIsDefined = false;
349
350  bool compressMainHeader = _compressHeaders;  // check it
351
352  bool encryptHeaders = false;
353
354  if (methodMode.PasswordIsDefined)
355  {
356    if (_encryptHeadersSpecified)
357      encryptHeaders = _encryptHeaders;
358    #ifndef _NO_CRYPTO
359    else
360      encryptHeaders = _passwordIsDefined;
361    #endif
362    compressMainHeader = true;
363    if (encryptHeaders)
364    {
365      headerMethod.PasswordIsDefined = methodMode.PasswordIsDefined;
366      headerMethod.Password = methodMode.Password;
367    }
368  }
369
370  if (numItems < 2)
371    compressMainHeader = false;
372
373  CUpdateOptions options;
374  options.Method = &methodMode;
375  options.HeaderMethod = (_compressHeaders || encryptHeaders) ? &headerMethod : 0;
376  options.UseFilters = _level != 0 && _autoFilter;
377  options.MaxFilter = _level >= 8;
378
379  options.HeaderOptions.CompressMainHeader = compressMainHeader;
380  options.HeaderOptions.WriteCTime = WriteCTime;
381  options.HeaderOptions.WriteATime = WriteATime;
382  options.HeaderOptions.WriteMTime = WriteMTime;
383
384  options.NumSolidFiles = _numSolidFiles;
385  options.NumSolidBytes = _numSolidBytes;
386  options.SolidExtension = _solidExtension;
387  options.RemoveSfxBlock = _removeSfxBlock;
388  options.VolumeMode = _volumeMode;
389
390  COutArchive archive;
391  CArchiveDatabase newDatabase;
392
393  CMyComPtr<ICryptoGetTextPassword> getPassword;
394  updateCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getPassword);
395
396  HRESULT res = Update(
397      EXTERNAL_CODECS_VARS
398      #ifdef _7Z_VOL
399      volume ? volume->Stream: 0,
400      volume ? db : 0,
401      #else
402      _inStream,
403      db,
404      #endif
405      updateItems,
406      archive, newDatabase, outStream, updateCallback, options
407      #ifndef _NO_CRYPTO
408      , getPassword
409      #endif
410      );
411
412  RINOK(res);
413
414  updateItems.ClearAndFree();
415
416  return archive.WriteDatabase(EXTERNAL_CODECS_VARS
417      newDatabase, options.HeaderMethod, options.HeaderOptions);
418
419  COM_TRY_END
420}
421
422static HRESULT GetBindInfoPart(UString &srcString, UInt32 &coder, UInt32 &stream)
423{
424  stream = 0;
425  int index = ParseStringToUInt32(srcString, coder);
426  if (index == 0)
427    return E_INVALIDARG;
428  srcString.Delete(0, index);
429  if (srcString[0] == 'S')
430  {
431    srcString.Delete(0);
432    int index = ParseStringToUInt32(srcString, stream);
433    if (index == 0)
434      return E_INVALIDARG;
435    srcString.Delete(0, index);
436  }
437  return S_OK;
438}
439
440static HRESULT GetBindInfo(UString &srcString, CBind &bind)
441{
442  RINOK(GetBindInfoPart(srcString, bind.OutCoder, bind.OutStream));
443  if (srcString[0] != ':')
444    return E_INVALIDARG;
445  srcString.Delete(0);
446  RINOK(GetBindInfoPart(srcString, bind.InCoder, bind.InStream));
447  if (!srcString.IsEmpty())
448    return E_INVALIDARG;
449  return S_OK;
450}
451
452STDMETHODIMP CHandler::SetProperties(const wchar_t **names, const PROPVARIANT *values, Int32 numProperties)
453{
454  COM_TRY_BEGIN
455  _binds.Clear();
456  BeforeSetProperty();
457
458  for (int i = 0; i < numProperties; i++)
459  {
460    UString name = names[i];
461    name.MakeUpper();
462    if (name.IsEmpty())
463      return E_INVALIDARG;
464
465    const PROPVARIANT &value = values[i];
466
467    if (name[0] == 'B')
468    {
469      name.Delete(0);
470      CBind bind;
471      RINOK(GetBindInfo(name, bind));
472      _binds.Add(bind);
473      continue;
474    }
475
476    RINOK(SetProperty(name, value));
477  }
478
479  return S_OK;
480  COM_TRY_END
481}
482
483}}
484