1 // 7zHandlerOut.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/ComTry.h"
6 #include "../../../Common/StringToInt.h"
7 
8 #include "../Common/ItemNameUtils.h"
9 #include "../Common/ParseProperties.h"
10 
11 #include "7zHandler.h"
12 #include "7zOut.h"
13 #include "7zUpdate.h"
14 
15 using namespace NWindows;
16 
17 namespace NArchive {
18 namespace N7z {
19 
20 static const wchar_t *k_LZMA_Name = L"LZMA";
21 static const wchar_t *kDefaultMethodName = k_LZMA_Name;
22 static const wchar_t *k_Copy_Name = L"Copy";
23 
24 static const wchar_t *k_MatchFinder_ForHeaders = L"BT2";
25 static const UInt32 k_NumFastBytes_ForHeaders = 273;
26 static const UInt32 k_Level_ForHeaders = 5;
27 static const UInt32 k_Dictionary_ForHeaders =
28   #ifdef UNDER_CE
29   1 << 18;
30   #else
31   1 << 20;
32   #endif
33 
GetFileTimeType(UInt32 * type)34 STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type)
35 {
36   *type = NFileTimeType::kWindows;
37   return S_OK;
38 }
39 
PropsMethod_To_FullMethod(CMethodFull & dest,const COneMethodInfo & m)40 HRESULT CHandler::PropsMethod_To_FullMethod(CMethodFull &dest, const COneMethodInfo &m)
41 {
42   if (!FindMethod(
43       EXTERNAL_CODECS_VARS
44       m.MethodName, dest.Id, dest.NumInStreams, dest.NumOutStreams))
45     return E_INVALIDARG;
46   (CProps &)dest = (CProps &)m;
47   return S_OK;
48 }
49 
SetHeaderMethod(CCompressionMethodMode & headerMethod)50 HRESULT CHandler::SetHeaderMethod(CCompressionMethodMode &headerMethod)
51 {
52   if (!_compressHeaders)
53     return S_OK;
54   COneMethodInfo m;
55   m.MethodName = k_LZMA_Name;
56   m.AddPropString(NCoderPropID::kMatchFinder, k_MatchFinder_ForHeaders);
57   m.AddProp32(NCoderPropID::kLevel, k_Level_ForHeaders);
58   m.AddProp32(NCoderPropID::kNumFastBytes, k_NumFastBytes_ForHeaders);
59   m.AddProp32(NCoderPropID::kDictionarySize, k_Dictionary_ForHeaders);
60   m.AddNumThreadsProp(1);
61 
62   CMethodFull methodFull;
63   RINOK(PropsMethod_To_FullMethod(methodFull, m));
64   headerMethod.Methods.Add(methodFull);
65   return S_OK;
66 }
67 
AddDefaultMethod()68 void CHandler::AddDefaultMethod()
69 {
70   for (int i = 0; i < _methods.Size(); i++)
71   {
72     UString &methodName = _methods[0].MethodName;
73     if (methodName.IsEmpty())
74       methodName = kDefaultMethodName;
75   }
76   if (_methods.IsEmpty())
77   {
78     COneMethodInfo m;
79     m.MethodName = (GetLevel() == 0 ? k_Copy_Name : kDefaultMethodName);
80     _methods.Add(m);
81   }
82 }
83 
SetMainMethod(CCompressionMethodMode & methodMode,CObjectVector<COneMethodInfo> & methods,UInt32 numThreads)84 HRESULT CHandler::SetMainMethod(
85     CCompressionMethodMode &methodMode,
86     CObjectVector<COneMethodInfo> &methods
87     #ifndef _7ZIP_ST
88     , UInt32 numThreads
89     #endif
90     )
91 {
92   AddDefaultMethod();
93 
94   const UInt64 kSolidBytes_Min = (1 << 24);
95   const UInt64 kSolidBytes_Max = ((UInt64)1 << 32) - 1;
96 
97   bool needSolid = false;
98   for (int i = 0; i < methods.Size(); i++)
99   {
100     COneMethodInfo &oneMethodInfo = methods[i];
101     SetGlobalLevelAndThreads(oneMethodInfo
102       #ifndef _7ZIP_ST
103       , numThreads
104       #endif
105       );
106 
107     CMethodFull methodFull;
108     RINOK(PropsMethod_To_FullMethod(methodFull, oneMethodInfo));
109     methodMode.Methods.Add(methodFull);
110 
111     if (methodFull.Id != k_Copy)
112       needSolid = true;
113 
114     if (_numSolidBytesDefined)
115       continue;
116 
117     UInt32 dicSize;
118     switch (methodFull.Id)
119     {
120       case k_LZMA:
121       case k_LZMA2: dicSize = oneMethodInfo.Get_Lzma_DicSize(); break;
122       case k_PPMD: dicSize = oneMethodInfo.Get_Ppmd_MemSize(); break;
123       case k_Deflate: dicSize = (UInt32)1 << 15; break;
124       case k_BZip2: dicSize = oneMethodInfo.Get_BZip2_BlockSize(); break;
125       default: continue;
126     }
127     _numSolidBytes = (UInt64)dicSize << 7;
128     if (_numSolidBytes < kSolidBytes_Min) _numSolidBytes = kSolidBytes_Min;
129     if (_numSolidBytes > kSolidBytes_Max) _numSolidBytes = kSolidBytes_Max;
130     _numSolidBytesDefined = true;
131   }
132 
133   if (!_numSolidBytesDefined)
134     if (needSolid)
135       _numSolidBytes = kSolidBytes_Max;
136     else
137       _numSolidBytes = 0;
138   _numSolidBytesDefined = true;
139   return S_OK;
140 }
141 
GetTime(IArchiveUpdateCallback * updateCallback,int index,bool writeTime,PROPID propID,UInt64 & ft,bool & ftDefined)142 static HRESULT GetTime(IArchiveUpdateCallback *updateCallback, int index, bool writeTime, PROPID propID, UInt64 &ft, bool &ftDefined)
143 {
144   ft = 0;
145   ftDefined = false;
146   if (!writeTime)
147     return S_OK;
148   NCOM::CPropVariant prop;
149   RINOK(updateCallback->GetProperty(index, propID, &prop));
150   if (prop.vt == VT_FILETIME)
151   {
152     ft = prop.filetime.dwLowDateTime | ((UInt64)prop.filetime.dwHighDateTime << 32);
153     ftDefined = true;
154   }
155   else if (prop.vt != VT_EMPTY)
156     return E_INVALIDARG;
157   return S_OK;
158 }
159 
UpdateItems(ISequentialOutStream * outStream,UInt32 numItems,IArchiveUpdateCallback * updateCallback)160 STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
161     IArchiveUpdateCallback *updateCallback)
162 {
163   COM_TRY_BEGIN
164 
165   const CArchiveDatabaseEx *db = 0;
166   #ifdef _7Z_VOL
167   if (_volumes.Size() > 1)
168     return E_FAIL;
169   const CVolume *volume = 0;
170   if (_volumes.Size() == 1)
171   {
172     volume = &_volumes.Front();
173     db = &volume->Database;
174   }
175   #else
176   if (_inStream != 0)
177     db = &_db;
178   #endif
179 
180   CObjectVector<CUpdateItem> updateItems;
181 
182   for (UInt32 i = 0; i < numItems; i++)
183   {
184     Int32 newData, newProps;
185     UInt32 indexInArchive;
186     if (!updateCallback)
187       return E_FAIL;
188     RINOK(updateCallback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive));
189     CUpdateItem ui;
190     ui.NewProps = IntToBool(newProps);
191     ui.NewData = IntToBool(newData);
192     ui.IndexInArchive = indexInArchive;
193     ui.IndexInClient = i;
194     ui.IsAnti = false;
195     ui.Size = 0;
196 
197     if (ui.IndexInArchive != -1)
198     {
199       if (db == 0 || ui.IndexInArchive >= db->Files.Size())
200         return E_INVALIDARG;
201       const CFileItem &fi = db->Files[ui.IndexInArchive];
202       ui.Name = fi.Name;
203       ui.IsDir = fi.IsDir;
204       ui.Size = fi.Size;
205       ui.IsAnti = db->IsItemAnti(ui.IndexInArchive);
206 
207       ui.CTimeDefined = db->CTime.GetItem(ui.IndexInArchive, ui.CTime);
208       ui.ATimeDefined = db->ATime.GetItem(ui.IndexInArchive, ui.ATime);
209       ui.MTimeDefined = db->MTime.GetItem(ui.IndexInArchive, ui.MTime);
210     }
211 
212     if (ui.NewProps)
213     {
214       bool nameIsDefined;
215       bool folderStatusIsDefined;
216       {
217         NCOM::CPropVariant prop;
218         RINOK(updateCallback->GetProperty(i, kpidAttrib, &prop));
219         if (prop.vt == VT_EMPTY)
220           ui.AttribDefined = false;
221         else if (prop.vt != VT_UI4)
222           return E_INVALIDARG;
223         else
224         {
225           ui.Attrib = prop.ulVal;
226           ui.AttribDefined = true;
227         }
228       }
229 
230       // we need MTime to sort files.
231       RINOK(GetTime(updateCallback, i, WriteCTime, kpidCTime, ui.CTime, ui.CTimeDefined));
232       RINOK(GetTime(updateCallback, i, WriteATime, kpidATime, ui.ATime, ui.ATimeDefined));
233       RINOK(GetTime(updateCallback, i, true,       kpidMTime, ui.MTime, ui.MTimeDefined));
234 
235       {
236         NCOM::CPropVariant prop;
237         RINOK(updateCallback->GetProperty(i, kpidPath, &prop));
238         if (prop.vt == VT_EMPTY)
239           nameIsDefined = false;
240         else if (prop.vt != VT_BSTR)
241           return E_INVALIDARG;
242         else
243         {
244           ui.Name = NItemName::MakeLegalName(prop.bstrVal);
245           nameIsDefined = true;
246         }
247       }
248       {
249         NCOM::CPropVariant prop;
250         RINOK(updateCallback->GetProperty(i, kpidIsDir, &prop));
251         if (prop.vt == VT_EMPTY)
252           folderStatusIsDefined = false;
253         else if (prop.vt != VT_BOOL)
254           return E_INVALIDARG;
255         else
256         {
257           ui.IsDir = (prop.boolVal != VARIANT_FALSE);
258           folderStatusIsDefined = true;
259         }
260       }
261 
262       {
263         NCOM::CPropVariant prop;
264         RINOK(updateCallback->GetProperty(i, kpidIsAnti, &prop));
265         if (prop.vt == VT_EMPTY)
266           ui.IsAnti = false;
267         else if (prop.vt != VT_BOOL)
268           return E_INVALIDARG;
269         else
270           ui.IsAnti = (prop.boolVal != VARIANT_FALSE);
271       }
272 
273       if (ui.IsAnti)
274       {
275         ui.AttribDefined = false;
276 
277         ui.CTimeDefined = false;
278         ui.ATimeDefined = false;
279         ui.MTimeDefined = false;
280 
281         ui.Size = 0;
282       }
283 
284       if (!folderStatusIsDefined && ui.AttribDefined)
285         ui.SetDirStatusFromAttrib();
286     }
287 
288     if (ui.NewData)
289     {
290       NCOM::CPropVariant prop;
291       RINOK(updateCallback->GetProperty(i, kpidSize, &prop));
292       if (prop.vt != VT_UI8)
293         return E_INVALIDARG;
294       ui.Size = (UInt64)prop.uhVal.QuadPart;
295       if (ui.Size != 0 && ui.IsAnti)
296         return E_INVALIDARG;
297     }
298     updateItems.Add(ui);
299   }
300 
301   CCompressionMethodMode methodMode, headerMethod;
302 
303   HRESULT res = SetMainMethod(methodMode, _methods
304     #ifndef _7ZIP_ST
305     , _numThreads
306     #endif
307     );
308   RINOK(res);
309   methodMode.Binds = _binds;
310 
311   RINOK(SetHeaderMethod(headerMethod));
312   #ifndef _7ZIP_ST
313   methodMode.NumThreads = _numThreads;
314   headerMethod.NumThreads = 1;
315   #endif
316 
317   CMyComPtr<ICryptoGetTextPassword2> getPassword2;
318   updateCallback->QueryInterface(IID_ICryptoGetTextPassword2, (void **)&getPassword2);
319 
320   if (getPassword2)
321   {
322     CMyComBSTR password;
323     Int32 passwordIsDefined;
324     RINOK(getPassword2->CryptoGetTextPassword2(&passwordIsDefined, &password));
325     methodMode.PasswordIsDefined = IntToBool(passwordIsDefined);
326     if (methodMode.PasswordIsDefined)
327       methodMode.Password = password;
328   }
329   else
330     methodMode.PasswordIsDefined = false;
331 
332   bool compressMainHeader = _compressHeaders;  // check it
333 
334   bool encryptHeaders = false;
335 
336   if (methodMode.PasswordIsDefined)
337   {
338     if (_encryptHeadersSpecified)
339       encryptHeaders = _encryptHeaders;
340     #ifndef _NO_CRYPTO
341     else
342       encryptHeaders = _passwordIsDefined;
343     #endif
344     compressMainHeader = true;
345     if (encryptHeaders)
346     {
347       headerMethod.PasswordIsDefined = methodMode.PasswordIsDefined;
348       headerMethod.Password = methodMode.Password;
349     }
350   }
351 
352   if (numItems < 2)
353     compressMainHeader = false;
354 
355   CUpdateOptions options;
356   options.Method = &methodMode;
357   options.HeaderMethod = (_compressHeaders || encryptHeaders) ? &headerMethod : 0;
358   int level = GetLevel();
359   options.UseFilters = level != 0 && _autoFilter;
360   options.MaxFilter = level >= 8;
361 
362   options.HeaderOptions.CompressMainHeader = compressMainHeader;
363   options.HeaderOptions.WriteCTime = WriteCTime;
364   options.HeaderOptions.WriteATime = WriteATime;
365   options.HeaderOptions.WriteMTime = WriteMTime;
366 
367   options.NumSolidFiles = _numSolidFiles;
368   options.NumSolidBytes = _numSolidBytes;
369   options.SolidExtension = _solidExtension;
370   options.RemoveSfxBlock = _removeSfxBlock;
371   options.VolumeMode = _volumeMode;
372 
373   COutArchive archive;
374   CArchiveDatabase newDatabase;
375 
376   CMyComPtr<ICryptoGetTextPassword> getPassword;
377   updateCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getPassword);
378 
379   res = Update(
380       EXTERNAL_CODECS_VARS
381       #ifdef _7Z_VOL
382       volume ? volume->Stream: 0,
383       volume ? db : 0,
384       #else
385       _inStream,
386       db,
387       #endif
388       updateItems,
389       archive, newDatabase, outStream, updateCallback, options
390       #ifndef _NO_CRYPTO
391       , getPassword
392       #endif
393       );
394 
395   RINOK(res);
396 
397   updateItems.ClearAndFree();
398 
399   return archive.WriteDatabase(EXTERNAL_CODECS_VARS
400       newDatabase, options.HeaderMethod, options.HeaderOptions);
401 
402   COM_TRY_END
403 }
404 
GetBindInfoPart(UString & srcString,UInt32 & coder,UInt32 & stream)405 static HRESULT GetBindInfoPart(UString &srcString, UInt32 &coder, UInt32 &stream)
406 {
407   stream = 0;
408   int index = ParseStringToUInt32(srcString, coder);
409   if (index == 0)
410     return E_INVALIDARG;
411   srcString.Delete(0, index);
412   if (srcString[0] == 'S')
413   {
414     srcString.Delete(0);
415     int index = ParseStringToUInt32(srcString, stream);
416     if (index == 0)
417       return E_INVALIDARG;
418     srcString.Delete(0, index);
419   }
420   return S_OK;
421 }
422 
InitProps()423 void COutHandler::InitProps()
424 {
425   CMultiMethodProps::Init();
426 
427   _removeSfxBlock = false;
428   _compressHeaders = true;
429   _encryptHeadersSpecified = false;
430   _encryptHeaders = false;
431 
432   WriteCTime = false;
433   WriteATime = false;
434   WriteMTime = true;
435 
436   _volumeMode = false;
437   InitSolid();
438 }
439 
SetSolidFromString(const UString & s)440 HRESULT COutHandler::SetSolidFromString(const UString &s)
441 {
442   UString s2 = s;
443   s2.MakeUpper();
444   for (int i = 0; i < s2.Length();)
445   {
446     const wchar_t *start = ((const wchar_t *)s2) + i;
447     const wchar_t *end;
448     UInt64 v = ConvertStringToUInt64(start, &end);
449     if (start == end)
450     {
451       if (s2[i++] != 'E')
452         return E_INVALIDARG;
453       _solidExtension = true;
454       continue;
455     }
456     i += (int)(end - start);
457     if (i == s2.Length())
458       return E_INVALIDARG;
459     wchar_t c = s2[i++];
460     if (c == 'F')
461     {
462       if (v < 1)
463         v = 1;
464       _numSolidFiles = v;
465     }
466     else
467     {
468       unsigned numBits;
469       switch (c)
470       {
471         case 'B': numBits =  0; break;
472         case 'K': numBits = 10; break;
473         case 'M': numBits = 20; break;
474         case 'G': numBits = 30; break;
475         default: return E_INVALIDARG;
476       }
477       _numSolidBytes = (v << numBits);
478       _numSolidBytesDefined = true;
479     }
480   }
481   return S_OK;
482 }
483 
SetSolidFromPROPVARIANT(const PROPVARIANT & value)484 HRESULT COutHandler::SetSolidFromPROPVARIANT(const PROPVARIANT &value)
485 {
486   bool isSolid;
487   switch (value.vt)
488   {
489     case VT_EMPTY: isSolid = true; break;
490     case VT_BOOL: isSolid = (value.boolVal != VARIANT_FALSE); break;
491     case VT_BSTR:
492       if (StringToBool(value.bstrVal, isSolid))
493         break;
494       return SetSolidFromString(value.bstrVal);
495     default: return E_INVALIDARG;
496   }
497   if (isSolid)
498     InitSolid();
499   else
500     _numSolidFiles = 1;
501   return S_OK;
502 }
503 
SetProperty(const wchar_t * nameSpec,const PROPVARIANT & value)504 HRESULT COutHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value)
505 {
506   UString name = nameSpec;
507   name.MakeUpper();
508   if (name.IsEmpty())
509     return E_INVALIDARG;
510 
511   if (name[0] == L'S')
512   {
513     name.Delete(0);
514     if (name.IsEmpty())
515       return SetSolidFromPROPVARIANT(value);
516     if (value.vt != VT_EMPTY)
517       return E_INVALIDARG;
518     return SetSolidFromString(name);
519   }
520 
521   UInt32 number;
522   int index = ParseStringToUInt32(name, number);
523   UString realName = name.Mid(index);
524   if (index == 0)
525   {
526     if (name.CompareNoCase(L"RSFX") == 0) return PROPVARIANT_to_bool(value, _removeSfxBlock);
527     if (name.CompareNoCase(L"HC") == 0) return PROPVARIANT_to_bool(value, _compressHeaders);
528     if (name.CompareNoCase(L"HCF") == 0)
529     {
530       bool compressHeadersFull = true;
531       RINOK(PROPVARIANT_to_bool(value, compressHeadersFull));
532       return compressHeadersFull ? S_OK: E_INVALIDARG;
533     }
534     if (name.CompareNoCase(L"HE") == 0)
535     {
536       RINOK(PROPVARIANT_to_bool(value, _encryptHeaders));
537       _encryptHeadersSpecified = true;
538       return S_OK;
539     }
540     if (name.CompareNoCase(L"TC") == 0) return PROPVARIANT_to_bool(value, WriteCTime);
541     if (name.CompareNoCase(L"TA") == 0) return PROPVARIANT_to_bool(value, WriteATime);
542     if (name.CompareNoCase(L"TM") == 0) return PROPVARIANT_to_bool(value, WriteMTime);
543     if (name.CompareNoCase(L"V") == 0) return PROPVARIANT_to_bool(value, _volumeMode);
544   }
545   return CMultiMethodProps::SetProperty(name, value);
546 }
547 
SetProperties(const wchar_t ** names,const PROPVARIANT * values,Int32 numProps)548 STDMETHODIMP CHandler::SetProperties(const wchar_t **names, const PROPVARIANT *values, Int32 numProps)
549 {
550   COM_TRY_BEGIN
551   _binds.Clear();
552   InitProps();
553 
554   for (int i = 0; i < numProps; i++)
555   {
556     UString name = names[i];
557     name.MakeUpper();
558     if (name.IsEmpty())
559       return E_INVALIDARG;
560 
561     const PROPVARIANT &value = values[i];
562 
563     if (name[0] == 'B')
564     {
565       if (value.vt != VT_EMPTY)
566         return E_INVALIDARG;
567       name.Delete(0);
568       CBind bind;
569       RINOK(GetBindInfoPart(name, bind.OutCoder, bind.OutStream));
570       if (name[0] != ':')
571         return E_INVALIDARG;
572       name.Delete(0);
573       RINOK(GetBindInfoPart(name, bind.InCoder, bind.InStream));
574       if (!name.IsEmpty())
575         return E_INVALIDARG;
576       _binds.Add(bind);
577       continue;
578     }
579 
580     RINOK(SetProperty(name, value));
581   }
582 
583   int numEmptyMethods = GetNumEmptyMethods();
584   if (numEmptyMethods > 0)
585   {
586     int k;
587     for (k = 0; k < _binds.Size(); k++)
588     {
589       const CBind &bind = _binds[k];
590       if (bind.InCoder < (UInt32)numEmptyMethods ||
591           bind.OutCoder < (UInt32)numEmptyMethods)
592         return E_INVALIDARG;
593     }
594     for (k = 0; k < _binds.Size(); k++)
595     {
596       CBind &bind = _binds[k];
597       bind.InCoder -= (UInt32)numEmptyMethods;
598       bind.OutCoder -= (UInt32)numEmptyMethods;
599     }
600     _methods.Delete(0, numEmptyMethods);
601   }
602 
603   AddDefaultMethod();
604 
605   if (!_filterMethod.MethodName.IsEmpty())
606   {
607     for (int k = 0; k < _binds.Size(); k++)
608     {
609       CBind &bind = _binds[k];
610       bind.InCoder++;
611       bind.OutCoder++;
612     }
613     _methods.Insert(0, _filterMethod);
614   }
615 
616   for (int k = 0; k < _binds.Size(); k++)
617   {
618     const CBind &bind = _binds[k];
619     if (bind.InCoder >= (UInt32)_methods.Size() ||
620         bind.OutCoder >= (UInt32)_methods.Size())
621       return E_INVALIDARG;
622   }
623 
624   return S_OK;
625   COM_TRY_END
626 }
627 
628 }}
629