Imported Upstream version 9.20
[platform/upstream/7zip.git] / CPP / 7zip / Archive / Zip / ZipHandlerOut.cpp
1 // ZipHandlerOut.cpp\r
2 \r
3 #include "StdAfx.h"\r
4 \r
5 #include "Common/ComTry.h"\r
6 #include "Common/StringConvert.h"\r
7 #include "Common/StringToInt.h"\r
8 \r
9 #include "Windows/PropVariant.h"\r
10 #include "Windows/Time.h"\r
11 \r
12 #include "../../IPassword.h"\r
13 \r
14 #include "../../Common/OutBuffer.h"\r
15 \r
16 #include "../../Crypto/WzAes.h"\r
17 \r
18 #include "../Common/ItemNameUtils.h"\r
19 #include "../Common/ParseProperties.h"\r
20 \r
21 #include "ZipHandler.h"\r
22 #include "ZipUpdate.h"\r
23 \r
24 using namespace NWindows;\r
25 using namespace NCOM;\r
26 using namespace NTime;\r
27 \r
28 namespace NArchive {\r
29 namespace NZip {\r
30 \r
31 static const UInt32 kLzAlgoX1 = 0;\r
32 static const UInt32 kLzAlgoX5 = 1;\r
33 \r
34 static const UInt32 kDeflateNumPassesX1  = 1;\r
35 static const UInt32 kDeflateNumPassesX7  = 3;\r
36 static const UInt32 kDeflateNumPassesX9  = 10;\r
37 \r
38 static const UInt32 kDeflateNumFastBytesX1 = 32;\r
39 static const UInt32 kDeflateNumFastBytesX7 = 64;\r
40 static const UInt32 kDeflateNumFastBytesX9 = 128;\r
41 \r
42 static const wchar_t *kLzmaMatchFinderX1 = L"HC4";\r
43 static const wchar_t *kLzmaMatchFinderX5 = L"BT4";\r
44 \r
45 static const UInt32 kLzmaNumFastBytesX1 = 32;\r
46 static const UInt32 kLzmaNumFastBytesX7 = 64;\r
47 \r
48 static const UInt32 kLzmaDicSizeX1 = 1 << 16;\r
49 static const UInt32 kLzmaDicSizeX3 = 1 << 20;\r
50 static const UInt32 kLzmaDicSizeX5 = 1 << 24;\r
51 static const UInt32 kLzmaDicSizeX7 = 1 << 25;\r
52 static const UInt32 kLzmaDicSizeX9 = 1 << 26;\r
53 \r
54 static const UInt32 kBZip2NumPassesX1 = 1;\r
55 static const UInt32 kBZip2NumPassesX7 = 2;\r
56 static const UInt32 kBZip2NumPassesX9 = 7;\r
57 \r
58 static const UInt32 kBZip2DicSizeX1 = 100000;\r
59 static const UInt32 kBZip2DicSizeX3 = 500000;\r
60 static const UInt32 kBZip2DicSizeX5 = 900000;\r
61 \r
62 STDMETHODIMP CHandler::GetFileTimeType(UInt32 *timeType)\r
63 {\r
64   *timeType = NFileTimeType::kDOS;\r
65   return S_OK;\r
66 }\r
67 \r
68 static bool IsAsciiString(const UString &s)\r
69 {\r
70   for (int i = 0; i < s.Length(); i++)\r
71   {\r
72     wchar_t c = s[i];\r
73     if (c < 0x20 || c > 0x7F)\r
74       return false;\r
75   }\r
76   return true;\r
77 }\r
78 \r
79 #define COM_TRY_BEGIN2 try {\r
80 #define COM_TRY_END2 } \\r
81 catch(const CSystemException &e) { return e.ErrorCode; } \\r
82 catch(...) { return E_OUTOFMEMORY; }\r
83 \r
84 static HRESULT GetTime(IArchiveUpdateCallback *callback, int index, PROPID propID, FILETIME &filetime)\r
85 {\r
86   filetime.dwHighDateTime = filetime.dwLowDateTime = 0;\r
87   NCOM::CPropVariant prop;\r
88   RINOK(callback->GetProperty(index, propID, &prop));\r
89   if (prop.vt == VT_FILETIME)\r
90     filetime = prop.filetime;\r
91   else if (prop.vt != VT_EMPTY)\r
92     return E_INVALIDARG;\r
93   return S_OK;\r
94 }\r
95 \r
96 STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,\r
97     IArchiveUpdateCallback *callback)\r
98 {\r
99   COM_TRY_BEGIN2\r
100   CObjectVector<CUpdateItem> updateItems;\r
101   bool thereAreAesUpdates = false;\r
102   for (UInt32 i = 0; i < numItems; i++)\r
103   {\r
104     CUpdateItem ui;\r
105     Int32 newData;\r
106     Int32 newProperties;\r
107     UInt32 indexInArchive;\r
108     if (!callback)\r
109       return E_FAIL;\r
110     RINOK(callback->GetUpdateItemInfo(i, &newData, &newProperties, &indexInArchive));\r
111     ui.NewProperties = IntToBool(newProperties);\r
112     ui.NewData = IntToBool(newData);\r
113     ui.IndexInArchive = indexInArchive;\r
114     ui.IndexInClient = i;\r
115     bool existInArchive = (indexInArchive != (UInt32)-1);\r
116     if (existInArchive && newData)\r
117       if (m_Items[indexInArchive].IsAesEncrypted())\r
118         thereAreAesUpdates = true;\r
119 \r
120     if (IntToBool(newProperties))\r
121     {\r
122       UString name;\r
123       {\r
124         NCOM::CPropVariant prop;\r
125         RINOK(callback->GetProperty(i, kpidAttrib, &prop));\r
126         if (prop.vt == VT_EMPTY)\r
127           ui.Attributes = 0;\r
128         else if (prop.vt != VT_UI4)\r
129           return E_INVALIDARG;\r
130         else\r
131           ui.Attributes = prop.ulVal;\r
132       }\r
133 \r
134       {\r
135         NCOM::CPropVariant prop;\r
136         RINOK(callback->GetProperty(i, kpidPath, &prop));\r
137         if (prop.vt == VT_EMPTY)\r
138           name.Empty();\r
139         else if (prop.vt != VT_BSTR)\r
140           return E_INVALIDARG;\r
141         else\r
142           name = prop.bstrVal;\r
143       }\r
144       {\r
145         NCOM::CPropVariant prop;\r
146         RINOK(callback->GetProperty(i, kpidIsDir, &prop));\r
147         if (prop.vt == VT_EMPTY)\r
148           ui.IsDir = false;\r
149         else if (prop.vt != VT_BOOL)\r
150           return E_INVALIDARG;\r
151         else\r
152           ui.IsDir = (prop.boolVal != VARIANT_FALSE);\r
153       }\r
154 \r
155       {\r
156         CPropVariant prop;\r
157         RINOK(callback->GetProperty(i, kpidTimeType, &prop));\r
158         if (prop.vt == VT_UI4)\r
159           ui.NtfsTimeIsDefined = (prop.ulVal == NFileTimeType::kWindows);\r
160         else\r
161           ui.NtfsTimeIsDefined = m_WriteNtfsTimeExtra;\r
162       }\r
163       RINOK(GetTime(callback, i, kpidMTime, ui.NtfsMTime));\r
164       RINOK(GetTime(callback, i, kpidATime, ui.NtfsATime));\r
165       RINOK(GetTime(callback, i, kpidCTime, ui.NtfsCTime));\r
166 \r
167       {\r
168         FILETIME localFileTime = { 0, 0 };\r
169         if (ui.NtfsMTime.dwHighDateTime != 0 ||\r
170             ui.NtfsMTime.dwLowDateTime != 0)\r
171           if (!FileTimeToLocalFileTime(&ui.NtfsMTime, &localFileTime))\r
172             return E_INVALIDARG;\r
173         FileTimeToDosTime(localFileTime, ui.Time);\r
174       }\r
175 \r
176       name = NItemName::MakeLegalName(name);\r
177       bool needSlash = ui.IsDir;\r
178       const wchar_t kSlash = L'/';\r
179       if (!name.IsEmpty())\r
180       {\r
181         if (name[name.Length() - 1] == kSlash)\r
182         {\r
183           if (!ui.IsDir)\r
184             return E_INVALIDARG;\r
185           needSlash = false;\r
186         }\r
187       }\r
188       if (needSlash)\r
189         name += kSlash;\r
190 \r
191       bool tryUtf8 = true;\r
192       if (m_ForceLocal || !m_ForceUtf8)\r
193       {\r
194         bool defaultCharWasUsed;\r
195         ui.Name = UnicodeStringToMultiByte(name, CP_OEMCP, '_', defaultCharWasUsed);\r
196         tryUtf8 = (!m_ForceLocal && (defaultCharWasUsed ||\r
197           MultiByteToUnicodeString(ui.Name, CP_OEMCP) != name));\r
198       }\r
199 \r
200       if (tryUtf8)\r
201       {\r
202         int i;\r
203         for (i = 0; i < name.Length() && (unsigned)name[i] < 0x80; i++);\r
204         ui.IsUtf8 = (i != name.Length());\r
205         if (!ConvertUnicodeToUTF8(name, ui.Name))\r
206           return E_INVALIDARG;\r
207       }\r
208 \r
209       if (ui.Name.Length() >= (1 << 16))\r
210         return E_INVALIDARG;\r
211 \r
212       ui.IndexInClient = i;\r
213       /*\r
214       if (existInArchive)\r
215       {\r
216         const CItemEx &itemInfo = m_Items[indexInArchive];\r
217         // ui.Commented = itemInfo.IsCommented();\r
218         ui.Commented = false;\r
219         if (ui.Commented)\r
220         {\r
221           ui.CommentRange.Position = itemInfo.GetCommentPosition();\r
222           ui.CommentRange.Size  = itemInfo.CommentSize;\r
223         }\r
224       }\r
225       else\r
226         ui.Commented = false;\r
227       */\r
228     }\r
229     if (IntToBool(newData))\r
230     {\r
231       UInt64 size;\r
232       {\r
233         NCOM::CPropVariant prop;\r
234         RINOK(callback->GetProperty(i, kpidSize, &prop));\r
235         if (prop.vt != VT_UI8)\r
236           return E_INVALIDARG;\r
237         size = prop.uhVal.QuadPart;\r
238       }\r
239       ui.Size = size;\r
240     }\r
241     updateItems.Add(ui);\r
242   }\r
243 \r
244   CMyComPtr<ICryptoGetTextPassword2> getTextPassword;\r
245   {\r
246     CMyComPtr<IArchiveUpdateCallback> udateCallBack2(callback);\r
247     udateCallBack2.QueryInterface(IID_ICryptoGetTextPassword2, &getTextPassword);\r
248   }\r
249   CCompressionMethodMode options;\r
250 \r
251   if (getTextPassword)\r
252   {\r
253     CMyComBSTR password;\r
254     Int32 passwordIsDefined;\r
255     RINOK(getTextPassword->CryptoGetTextPassword2(&passwordIsDefined, &password));\r
256     options.PasswordIsDefined = IntToBool(passwordIsDefined);\r
257     if (options.PasswordIsDefined)\r
258     {\r
259       options.IsAesMode = (m_ForceAesMode ? m_IsAesMode : thereAreAesUpdates);\r
260       options.AesKeyMode = m_AesKeyMode;\r
261 \r
262       if (!IsAsciiString((const wchar_t *)password))\r
263         return E_INVALIDARG;\r
264       if (options.IsAesMode)\r
265       {\r
266         if (options.Password.Length() > NCrypto::NWzAes::kPasswordSizeMax)\r
267           return E_INVALIDARG;\r
268       }\r
269       options.Password = UnicodeStringToMultiByte((const wchar_t *)password, CP_OEMCP);\r
270     }\r
271   }\r
272   else\r
273     options.PasswordIsDefined = false;\r
274 \r
275   int level = m_Level;\r
276   if (level < 0)\r
277     level = 5;\r
278   \r
279   Byte mainMethod;\r
280   if (m_MainMethod < 0)\r
281     mainMethod = (Byte)(((level == 0) ?\r
282         NFileHeader::NCompressionMethod::kStored :\r
283         NFileHeader::NCompressionMethod::kDeflated));\r
284   else\r
285     mainMethod = (Byte)m_MainMethod;\r
286   options.MethodSequence.Add(mainMethod);\r
287   if (mainMethod != NFileHeader::NCompressionMethod::kStored)\r
288     options.MethodSequence.Add(NFileHeader::NCompressionMethod::kStored);\r
289   bool isDeflate = (mainMethod == NFileHeader::NCompressionMethod::kDeflated) ||\r
290       (mainMethod == NFileHeader::NCompressionMethod::kDeflated64);\r
291   bool isLZMA = (mainMethod == NFileHeader::NCompressionMethod::kLZMA);\r
292   bool isLz = (isLZMA || isDeflate);\r
293   options.NumPasses = m_NumPasses;\r
294   options.DicSize = m_DicSize;\r
295   options.NumFastBytes = m_NumFastBytes;\r
296   options.NumMatchFinderCycles = m_NumMatchFinderCycles;\r
297   options.NumMatchFinderCyclesDefined = m_NumMatchFinderCyclesDefined;\r
298   options.Algo = m_Algo;\r
299   options.MemSize = m_MemSize;\r
300   options.Order = m_Order;\r
301   #ifndef _7ZIP_ST\r
302   options.NumThreads = _numThreads;\r
303   #endif\r
304   if (isLz)\r
305   {\r
306     if (isDeflate)\r
307     {\r
308       if (options.NumPasses == 0xFFFFFFFF)\r
309         options.NumPasses = (level >= 9 ? kDeflateNumPassesX9 :\r
310                             (level >= 7 ? kDeflateNumPassesX7 :\r
311                                           kDeflateNumPassesX1));\r
312       if (options.NumFastBytes == 0xFFFFFFFF)\r
313         options.NumFastBytes = (level >= 9 ? kDeflateNumFastBytesX9 :\r
314                                (level >= 7 ? kDeflateNumFastBytesX7 :\r
315                                              kDeflateNumFastBytesX1));\r
316     }\r
317     else if (isLZMA)\r
318     {\r
319       if (options.DicSize == 0xFFFFFFFF)\r
320         options.DicSize =\r
321           (level >= 9 ? kLzmaDicSizeX9 :\r
322           (level >= 7 ? kLzmaDicSizeX7 :\r
323           (level >= 5 ? kLzmaDicSizeX5 :\r
324           (level >= 3 ? kLzmaDicSizeX3 :\r
325                         kLzmaDicSizeX1))));\r
326 \r
327       if (options.NumFastBytes == 0xFFFFFFFF)\r
328         options.NumFastBytes = (level >= 7 ? kLzmaNumFastBytesX7 :\r
329                                              kLzmaNumFastBytesX1);\r
330 \r
331       options.MatchFinder =\r
332         (level >= 5 ? kLzmaMatchFinderX5 :\r
333                       kLzmaMatchFinderX1);\r
334     }\r
335 \r
336     if (options.Algo == 0xFFFFFFFF)\r
337         options.Algo = (level >= 5 ? kLzAlgoX5 :\r
338                                      kLzAlgoX1);\r
339   }\r
340   if (mainMethod == NFileHeader::NCompressionMethod::kBZip2)\r
341   {\r
342     if (options.NumPasses == 0xFFFFFFFF)\r
343       options.NumPasses = (level >= 9 ? kBZip2NumPassesX9 :\r
344                           (level >= 7 ? kBZip2NumPassesX7 :\r
345                                         kBZip2NumPassesX1));\r
346     if (options.DicSize == 0xFFFFFFFF)\r
347       options.DicSize = (level >= 5 ? kBZip2DicSizeX5 :\r
348                         (level >= 3 ? kBZip2DicSizeX3 :\r
349                                       kBZip2DicSizeX1));\r
350   }\r
351   if (mainMethod == NFileHeader::NCompressionMethod::kPPMd)\r
352   {\r
353     int level2 = level;\r
354     if (level2 < 1) level2 = 1;\r
355     if (level2 > 9) level2 = 9;\r
356 \r
357     if (options.MemSize == 0xFFFFFFFF)\r
358       options.MemSize = (1 << (19 + (level2 > 8 ? 8 : level2)));\r
359 \r
360     if (options.Order == 0xFFFFFFFF)\r
361       options.Order = 3 + level2;\r
362 \r
363     if (options.Algo == 0xFFFFFFFF)\r
364       options.Algo = (level2 >= 7 ? 1 : 0);\r
365   }\r
366 \r
367   return Update(\r
368       EXTERNAL_CODECS_VARS\r
369       m_Items, updateItems, outStream,\r
370       m_Archive.IsOpen() ? &m_Archive : NULL, &options, callback);\r
371   COM_TRY_END2\r
372 }\r
373 \r
374 STDMETHODIMP CHandler::SetProperties(const wchar_t **names, const PROPVARIANT *values, Int32 numProperties)\r
375 {\r
376   #ifndef _7ZIP_ST\r
377   const UInt32 numProcessors = NSystem::GetNumberOfProcessors();\r
378   _numThreads = numProcessors;\r
379   #endif\r
380   InitMethodProperties();\r
381   for (int i = 0; i < numProperties; i++)\r
382   {\r
383     UString name = UString(names[i]);\r
384     name.MakeUpper();\r
385     if (name.IsEmpty())\r
386       return E_INVALIDARG;\r
387 \r
388     const PROPVARIANT &prop = values[i];\r
389 \r
390     if (name[0] == L'X')\r
391     {\r
392       UInt32 level = 9;\r
393       RINOK(ParsePropValue(name.Mid(1), prop, level));\r
394       m_Level = level;\r
395       continue;\r
396     }\r
397     else if (name == L"M")\r
398     {\r
399       if (prop.vt == VT_BSTR)\r
400       {\r
401         UString m = prop.bstrVal;\r
402         m.MakeUpper();\r
403         if (m == L"COPY") m_MainMethod = NFileHeader::NCompressionMethod::kStored;\r
404         else if (m == L"DEFLATE") m_MainMethod = NFileHeader::NCompressionMethod::kDeflated;\r
405         else if (m == L"DEFLATE64") m_MainMethod = NFileHeader::NCompressionMethod::kDeflated64;\r
406         else if (m == L"BZIP2") m_MainMethod = NFileHeader::NCompressionMethod::kBZip2;\r
407         else if (m == L"LZMA") m_MainMethod = NFileHeader::NCompressionMethod::kLZMA;\r
408         else if (m == L"PPMD") m_MainMethod = NFileHeader::NCompressionMethod::kPPMd;\r
409         else return E_INVALIDARG;\r
410       }\r
411       else if (prop.vt == VT_UI4)\r
412       {\r
413         switch(prop.ulVal)\r
414         {\r
415           case NFileHeader::NCompressionMethod::kStored:\r
416           case NFileHeader::NCompressionMethod::kDeflated:\r
417           case NFileHeader::NCompressionMethod::kDeflated64:\r
418           case NFileHeader::NCompressionMethod::kBZip2:\r
419           case NFileHeader::NCompressionMethod::kLZMA:\r
420             m_MainMethod = (Byte)prop.ulVal;\r
421             break;\r
422           default:\r
423             return E_INVALIDARG;\r
424         }\r
425       }\r
426       else\r
427         return E_INVALIDARG;\r
428     }\r
429     else if (name.Left(2) == L"EM")\r
430     {\r
431       if (prop.vt == VT_BSTR)\r
432       {\r
433         UString valueString = prop.bstrVal;\r
434         valueString.MakeUpper();\r
435         if (valueString.Left(3) == L"AES")\r
436         {\r
437           valueString = valueString.Mid(3);\r
438           if (valueString == L"128")\r
439             m_AesKeyMode = 1;\r
440           else if (valueString == L"192")\r
441             m_AesKeyMode = 2;\r
442           else if (valueString == L"256" || valueString.IsEmpty())\r
443             m_AesKeyMode = 3;\r
444           else\r
445             return E_INVALIDARG;\r
446           m_IsAesMode = true;\r
447           m_ForceAesMode = true;\r
448         }\r
449         else if (valueString == L"ZIPCRYPTO")\r
450         {\r
451           m_IsAesMode = false;\r
452           m_ForceAesMode = true;\r
453         }\r
454         else\r
455           return E_INVALIDARG;\r
456       }\r
457       else\r
458         return E_INVALIDARG;\r
459     }\r
460     else if (name[0] == L'D')\r
461     {\r
462       UInt32 dicSize = kBZip2DicSizeX5;\r
463       RINOK(ParsePropDictionaryValue(name.Mid(1), prop, dicSize));\r
464       m_DicSize = dicSize;\r
465     }\r
466     else if (name.Left(3) == L"MEM")\r
467     {\r
468       UInt32 memSize = 1 << 24;\r
469       RINOK(ParsePropDictionaryValue(name.Mid(3), prop, memSize));\r
470       m_MemSize = memSize;\r
471     }\r
472     else if (name[0] == L'O')\r
473     {\r
474       UInt32 order = 8;\r
475       RINOK(ParsePropValue(name.Mid(1), prop, order));\r
476       m_Order = order;\r
477     }\r
478     else if (name.Left(4) == L"PASS")\r
479     {\r
480       UInt32 num = kDeflateNumPassesX9;\r
481       RINOK(ParsePropValue(name.Mid(4), prop, num));\r
482       m_NumPasses = num;\r
483     }\r
484     else if (name.Left(2) == L"FB")\r
485     {\r
486       UInt32 num = kDeflateNumFastBytesX9;\r
487       RINOK(ParsePropValue(name.Mid(2), prop, num));\r
488       m_NumFastBytes = num;\r
489     }\r
490     else if (name.Left(2) == L"MC")\r
491     {\r
492       UInt32 num = 0xFFFFFFFF;\r
493       RINOK(ParsePropValue(name.Mid(2), prop, num));\r
494       m_NumMatchFinderCycles = num;\r
495       m_NumMatchFinderCyclesDefined = true;\r
496     }\r
497     else if (name.Left(2) == L"MT")\r
498     {\r
499       #ifndef _7ZIP_ST\r
500       RINOK(ParseMtProp(name.Mid(2), prop, numProcessors, _numThreads));\r
501       #endif\r
502     }\r
503     else if (name.Left(1) == L"A")\r
504     {\r
505       UInt32 num = kLzAlgoX5;\r
506       RINOK(ParsePropValue(name.Mid(1), prop, num));\r
507       m_Algo = num;\r
508     }\r
509     else if (name.CompareNoCase(L"TC") == 0)\r
510     {\r
511       RINOK(SetBoolProperty(m_WriteNtfsTimeExtra, prop));\r
512     }\r
513     else if (name.CompareNoCase(L"CL") == 0)\r
514     {\r
515       RINOK(SetBoolProperty(m_ForceLocal, prop));\r
516       if (m_ForceLocal)\r
517         m_ForceUtf8 = false;\r
518     }\r
519     else if (name.CompareNoCase(L"CU") == 0)\r
520     {\r
521       RINOK(SetBoolProperty(m_ForceUtf8, prop));\r
522       if (m_ForceUtf8)\r
523         m_ForceLocal = false;\r
524     }\r
525     else\r
526       return E_INVALIDARG;\r
527   }\r
528   return S_OK;\r
529 }\r
530 \r
531 }}\r