1 // ZipHandlerOut.cpp
2
3 #include "StdAfx.h"
4
5 #include "../../../Common/ComTry.h"
6 #include "../../../Common/StringConvert.h"
7 #include "../../../Common/StringToInt.h"
8
9 #include "../../../Windows/PropVariant.h"
10 #include "../../../Windows/TimeUtils.h"
11
12 #include "../../IPassword.h"
13
14 #include "../../Common/OutBuffer.h"
15
16 #include "../../Crypto/WzAes.h"
17
18 #include "../Common/ItemNameUtils.h"
19 #include "../Common/ParseProperties.h"
20
21 #include "ZipHandler.h"
22 #include "ZipUpdate.h"
23
24 using namespace NWindows;
25 using namespace NCOM;
26 using namespace NTime;
27
28 namespace NArchive {
29 namespace NZip {
30
Z7_COM7F_IMF(CHandler::GetFileTimeType (UInt32 * timeType))31 Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *timeType))
32 {
33 *timeType = TimeOptions.Prec;
34 return S_OK;
35 }
36
IsSimpleAsciiString(const wchar_t * s)37 static bool IsSimpleAsciiString(const wchar_t *s)
38 {
39 for (;;)
40 {
41 wchar_t c = *s++;
42 if (c == 0)
43 return true;
44 if (c < 0x20 || c > 0x7F)
45 return false;
46 }
47 }
48
49
FindZipMethod(const char * s,const char * const * names,unsigned num)50 static int FindZipMethod(const char *s, const char * const *names, unsigned num)
51 {
52 for (unsigned i = 0; i < num; i++)
53 {
54 const char *name = names[i];
55 if (name && StringsAreEqualNoCase_Ascii(s, name))
56 return (int)i;
57 }
58 return -1;
59 }
60
FindZipMethod(const char * s)61 static int FindZipMethod(const char *s)
62 {
63 int k = FindZipMethod(s, kMethodNames1, kNumMethodNames1);
64 if (k >= 0)
65 return k;
66 k = FindZipMethod(s, kMethodNames2, kNumMethodNames2);
67 if (k >= 0)
68 return (int)kMethodNames2Start + k;
69 return -1;
70 }
71
72
73 #define COM_TRY_BEGIN2 try {
74 #define COM_TRY_END2 } \
75 catch(const CSystemException &e) { return e.ErrorCode; } \
76 catch(...) { return E_OUTOFMEMORY; }
77
GetTime(IArchiveUpdateCallback * callback,unsigned index,PROPID propID,FILETIME & filetime)78 static HRESULT GetTime(IArchiveUpdateCallback *callback, unsigned index, PROPID propID, FILETIME &filetime)
79 {
80 filetime.dwHighDateTime = filetime.dwLowDateTime = 0;
81 NCOM::CPropVariant prop;
82 RINOK(callback->GetProperty(index, propID, &prop))
83 if (prop.vt == VT_FILETIME)
84 filetime = prop.filetime;
85 else if (prop.vt != VT_EMPTY)
86 return E_INVALIDARG;
87 return S_OK;
88 }
89
90
Z7_COM7F_IMF(CHandler::UpdateItems (ISequentialOutStream * outStream,UInt32 numItems,IArchiveUpdateCallback * callback))91 Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
92 IArchiveUpdateCallback *callback))
93 {
94 COM_TRY_BEGIN2
95
96 if (m_Archive.IsOpen())
97 {
98 if (!m_Archive.CanUpdate())
99 return E_NOTIMPL;
100 }
101
102 CObjectVector<CUpdateItem> updateItems;
103 updateItems.ClearAndReserve(numItems);
104
105 bool thereAreAesUpdates = false;
106 UInt64 largestSize = 0;
107 bool largestSizeDefined = false;
108
109 #ifdef _WIN32
110 const UINT oemCP = GetOEMCP();
111 #endif
112
113 UString name;
114 CUpdateItem ui;
115
116 for (UInt32 i = 0; i < numItems; i++)
117 {
118 Int32 newData;
119 Int32 newProps;
120 UInt32 indexInArc;
121
122 if (!callback)
123 return E_FAIL;
124
125 RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc))
126
127 name.Empty();
128 ui.Clear();
129
130 ui.NewProps = IntToBool(newProps);
131 ui.NewData = IntToBool(newData);
132 ui.IndexInArc = (int)indexInArc;
133 ui.IndexInClient = i;
134
135 bool existInArchive = (indexInArc != (UInt32)(Int32)-1);
136 if (existInArchive)
137 {
138 const CItemEx &inputItem = m_Items[indexInArc];
139 if (inputItem.IsAesEncrypted())
140 thereAreAesUpdates = true;
141 if (!IntToBool(newProps))
142 ui.IsDir = inputItem.IsDir();
143 // ui.IsAltStream = inputItem.IsAltStream();
144 }
145
146 if (IntToBool(newProps))
147 {
148 {
149 NCOM::CPropVariant prop;
150 RINOK(callback->GetProperty(i, kpidAttrib, &prop))
151 if (prop.vt == VT_EMPTY)
152 ui.Attrib = 0;
153 else if (prop.vt != VT_UI4)
154 return E_INVALIDARG;
155 else
156 ui.Attrib = prop.ulVal;
157 }
158
159 {
160 NCOM::CPropVariant prop;
161 RINOK(callback->GetProperty(i, kpidPath, &prop))
162 if (prop.vt == VT_EMPTY)
163 {
164 // name.Empty();
165 }
166 else if (prop.vt != VT_BSTR)
167 return E_INVALIDARG;
168 else
169 name = prop.bstrVal;
170 }
171
172 {
173 NCOM::CPropVariant prop;
174 RINOK(callback->GetProperty(i, kpidIsDir, &prop))
175 if (prop.vt == VT_EMPTY)
176 ui.IsDir = false;
177 else if (prop.vt != VT_BOOL)
178 return E_INVALIDARG;
179 else
180 ui.IsDir = (prop.boolVal != VARIANT_FALSE);
181 }
182
183 /*
184 {
185 bool isAltStream = false;
186 {
187 NCOM::CPropVariant prop;
188 RINOK(callback->GetProperty(i, kpidIsAltStream, &prop));
189 if (prop.vt == VT_BOOL)
190 isAltStream = (prop.boolVal != VARIANT_FALSE);
191 else if (prop.vt != VT_EMPTY)
192 return E_INVALIDARG;
193 }
194
195 if (isAltStream)
196 {
197 if (ui.IsDir)
198 return E_INVALIDARG;
199 int delim = name.ReverseFind(L':');
200 if (delim >= 0)
201 {
202 name.Delete(delim, 1);
203 name.Insert(delim, UString(k_SpecName_NTFS_STREAM));
204 ui.IsAltStream = true;
205 }
206 }
207 }
208 */
209
210 // 22.00 : kpidTimeType is useless here : the code was disabled
211 /*
212 {
213 CPropVariant prop;
214 RINOK(callback->GetProperty(i, kpidTimeType, &prop));
215 if (prop.vt == VT_UI4)
216 ui.NtfsTime_IsDefined = (prop.ulVal == NFileTimeType::kWindows);
217 else
218 ui.NtfsTime_IsDefined = _Write_NtfsTime;
219 }
220 */
221
222 if (TimeOptions.Write_MTime.Val) RINOK (GetTime (callback, i, kpidMTime, ui.Ntfs_MTime))
223 if (TimeOptions.Write_ATime.Val) RINOK (GetTime (callback, i, kpidATime, ui.Ntfs_ATime))
224 if (TimeOptions.Write_CTime.Val) RINOK (GetTime (callback, i, kpidCTime, ui.Ntfs_CTime))
225
226 if (TimeOptions.Prec != k_PropVar_TimePrec_DOS)
227 {
228 if (TimeOptions.Prec == k_PropVar_TimePrec_Unix ||
229 TimeOptions.Prec == k_PropVar_TimePrec_Base)
230 ui.Write_UnixTime = ! FILETIME_IsZero (ui.Ntfs_MTime);
231 else
232 {
233 /*
234 // if we want to store zero timestamps as zero timestamp, use the following:
235 ui.Write_NtfsTime =
236 _Write_MTime ||
237 _Write_ATime ||
238 _Write_CTime;
239 */
240
241 // We treat zero timestamp as no timestamp
242 ui.Write_NtfsTime =
243 ! FILETIME_IsZero (ui.Ntfs_MTime) ||
244 ! FILETIME_IsZero (ui.Ntfs_ATime) ||
245 ! FILETIME_IsZero (ui.Ntfs_CTime);
246 }
247 }
248
249 /*
250 how 0 in dos time works:
251 win10 explorer extract : some random date 1601-04-25.
252 winrar 6.10 : write time.
253 7zip : MTime of archive is used
254 how 0 in tar works:
255 winrar 6.10 : 1970
256 0 in dos field can show that there is no timestamp.
257 we write correct 1970-01-01 in dos field, to support correct extraction in Win10.
258 */
259
260 UtcFileTime_To_LocalDosTime(ui.Ntfs_MTime, ui.Time);
261
262 NItemName::ReplaceSlashes_OsToUnix(name);
263
264 bool needSlash = ui.IsDir;
265 const wchar_t kSlash = L'/';
266 if (!name.IsEmpty())
267 {
268 if (name.Back() == kSlash)
269 {
270 if (!ui.IsDir)
271 return E_INVALIDARG;
272 needSlash = false;
273 }
274 }
275 if (needSlash)
276 name += kSlash;
277
278 const UINT codePage = _forceCodePage ? _specifiedCodePage : CP_OEMCP;
279 bool tryUtf8 = true;
280
281 /*
282 Windows 10 allows users to set UTF-8 in Region Settings via option:
283 "Beta: Use Unicode UTF-8 for worldwide language support"
284 In that case Windows uses CP_UTF8 when we use CP_OEMCP.
285 21.02 fixed:
286 we set UTF-8 mark for non-latin files for such UTF-8 mode in Windows.
287 we write additional Info-Zip Utf-8 FileName Extra for non-latin names/
288 */
289
290 if ((codePage != CP_UTF8) &&
291 #ifdef _WIN32
292 (m_ForceLocal || !m_ForceUtf8) && (oemCP != CP_UTF8)
293 #else
294 (m_ForceLocal && !m_ForceUtf8)
295 #endif
296 )
297 {
298 bool defaultCharWasUsed;
299 ui.Name = UnicodeStringToMultiByte(name, codePage, '_', defaultCharWasUsed);
300 tryUtf8 = (!m_ForceLocal && (defaultCharWasUsed ||
301 MultiByteToUnicodeString(ui.Name, codePage) != name));
302 }
303
304 const bool isNonLatin = !name.IsAscii();
305
306 if (tryUtf8)
307 {
308 ui.IsUtf8 = isNonLatin;
309 ConvertUnicodeToUTF8(name, ui.Name);
310
311 #ifndef _WIN32
312 if (ui.IsUtf8 && !CheckUTF8_AString(ui.Name))
313 {
314 // if it's non-Windows and there are non-UTF8 characters we clear UTF8-flag
315 ui.IsUtf8 = false;
316 }
317 #endif
318 }
319 else if (isNonLatin)
320 Convert_Unicode_To_UTF8_Buf(name, ui.Name_Utf);
321
322 if (ui.Name.Len() >= (1 << 16)
323 || ui.Name_Utf.Size() >= (1 << 16) - 128)
324 return E_INVALIDARG;
325
326 {
327 NCOM::CPropVariant prop;
328 RINOK(callback->GetProperty(i, kpidComment, &prop))
329 if (prop.vt == VT_EMPTY)
330 {
331 // ui.Comment.Free();
332 }
333 else if (prop.vt != VT_BSTR)
334 return E_INVALIDARG;
335 else
336 {
337 UString s = prop.bstrVal;
338 AString a;
339 if (ui.IsUtf8)
340 ConvertUnicodeToUTF8(s, a);
341 else
342 {
343 bool defaultCharWasUsed;
344 a = UnicodeStringToMultiByte(s, codePage, '_', defaultCharWasUsed);
345 }
346 if (a.Len() >= (1 << 16))
347 return E_INVALIDARG;
348 ui.Comment.CopyFrom((const Byte *)(const char *)a, a.Len());
349 }
350 }
351
352
353 /*
354 if (existInArchive)
355 {
356 const CItemEx &itemInfo = m_Items[indexInArc];
357 // ui.Commented = itemInfo.IsCommented();
358 ui.Commented = false;
359 if (ui.Commented)
360 {
361 ui.CommentRange.Position = itemInfo.GetCommentPosition();
362 ui.CommentRange.Size = itemInfo.CommentSize;
363 }
364 }
365 else
366 ui.Commented = false;
367 */
368 }
369
370
371 if (IntToBool(newData))
372 {
373 UInt64 size = 0;
374 if (!ui.IsDir)
375 {
376 NCOM::CPropVariant prop;
377 RINOK(callback->GetProperty(i, kpidSize, &prop))
378 if (prop.vt != VT_UI8)
379 return E_INVALIDARG;
380 size = prop.uhVal.QuadPart;
381 if (largestSize < size)
382 largestSize = size;
383 largestSizeDefined = true;
384 }
385 ui.Size = size;
386 }
387
388 updateItems.Add(ui);
389 }
390
391
392 CMyComPtr<ICryptoGetTextPassword2> getTextPassword;
393 {
394 CMyComPtr<IArchiveUpdateCallback> udateCallBack2(callback);
395 udateCallBack2.QueryInterface(IID_ICryptoGetTextPassword2, &getTextPassword);
396 }
397 CCompressionMethodMode options;
398 (CBaseProps &)options = _props;
399 options.DataSizeReduce = largestSize;
400 options.DataSizeReduce_Defined = largestSizeDefined;
401
402 options.Password_Defined = false;
403 options.Password.Wipe_and_Empty();
404 if (getTextPassword)
405 {
406 CMyComBSTR_Wipe password;
407 Int32 passwordIsDefined;
408 RINOK(getTextPassword->CryptoGetTextPassword2(&passwordIsDefined, &password))
409 options.Password_Defined = IntToBool(passwordIsDefined);
410 if (options.Password_Defined)
411 {
412 if (!m_ForceAesMode)
413 options.IsAesMode = thereAreAesUpdates;
414
415 if (!IsSimpleAsciiString(password))
416 return E_INVALIDARG;
417 if (password)
418 UnicodeStringToMultiByte2(options.Password, (LPCOLESTR)password, CP_OEMCP);
419 if (options.IsAesMode)
420 {
421 if (options.Password.Len() > NCrypto::NWzAes::kPasswordSizeMax)
422 return E_INVALIDARG;
423 }
424 }
425 }
426
427
428 int mainMethod = m_MainMethod;
429
430 if (mainMethod < 0)
431 {
432 if (!_props._methods.IsEmpty())
433 {
434 const AString &methodName = _props._methods.Front().MethodName;
435 if (!methodName.IsEmpty())
436 {
437 mainMethod = FindZipMethod(methodName);
438 if (mainMethod < 0)
439 {
440 CMethodId methodId;
441 UInt32 numStreams;
442 bool isFilter;
443 if (FindMethod_Index(EXTERNAL_CODECS_VARS methodName, true,
444 methodId, numStreams, isFilter) < 0)
445 return E_NOTIMPL;
446 if (numStreams != 1)
447 return E_NOTIMPL;
448 if (methodId == kMethodId_BZip2)
449 mainMethod = NFileHeader::NCompressionMethod::kBZip2;
450 else
451 {
452 if (methodId < kMethodId_ZipBase)
453 return E_NOTIMPL;
454 methodId -= kMethodId_ZipBase;
455 if (methodId > 0xFF)
456 return E_NOTIMPL;
457 mainMethod = (int)methodId;
458 }
459 }
460 }
461 }
462 }
463
464 if (mainMethod < 0)
465 mainMethod = (Byte)(((_props.GetLevel() == 0) ?
466 NFileHeader::NCompressionMethod::kStore :
467 NFileHeader::NCompressionMethod::kDeflate));
468 else
469 mainMethod = (Byte)mainMethod;
470
471 options.MethodSequence.Add((Byte)mainMethod);
472
473 if (mainMethod != NFileHeader::NCompressionMethod::kStore)
474 options.MethodSequence.Add(NFileHeader::NCompressionMethod::kStore);
475
476 options.Force_SeqOutMode = _force_SeqOutMode;
477
478 CUpdateOptions uo;
479 uo.Write_MTime = TimeOptions.Write_MTime.Val;
480 uo.Write_ATime = TimeOptions.Write_ATime.Val;
481 uo.Write_CTime = TimeOptions.Write_CTime.Val;
482 /*
483 uo.Write_NtfsTime = _Write_NtfsTime &&
484 (_Write_MTime || _Write_ATime || _Write_CTime);
485 uo.Write_UnixTime = _Write_UnixTime;
486 */
487
488 return Update(
489 EXTERNAL_CODECS_VARS
490 m_Items, updateItems, outStream,
491 m_Archive.IsOpen() ? &m_Archive : NULL, _removeSfxBlock,
492 uo, options, callback);
493
494 COM_TRY_END2
495 }
496
497
498
Z7_COM7F_IMF(CHandler::SetProperties (const wchar_t * const * names,const PROPVARIANT * values,UInt32 numProps))499 Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps))
500 {
501 InitMethodProps();
502
503 for (UInt32 i = 0; i < numProps; i++)
504 {
505 UString name = names[i];
506 name.MakeLower_Ascii();
507 if (name.IsEmpty())
508 return E_INVALIDARG;
509
510 const PROPVARIANT &prop = values[i];
511
512 if (name.IsEqualTo_Ascii_NoCase("em"))
513 {
514 if (prop.vt != VT_BSTR)
515 return E_INVALIDARG;
516 {
517 const wchar_t *m = prop.bstrVal;
518 if (IsString1PrefixedByString2_NoCase_Ascii(m, "AES"))
519 {
520 m += 3;
521 UInt32 v = 3;
522 if (*m != 0)
523 {
524 if (*m == '-')
525 m++;
526 const wchar_t *end;
527 v = ConvertStringToUInt32(m, &end);
528 if (*end != 0 || v % 64 != 0)
529 return E_INVALIDARG;
530 v /= 64;
531 v -= 2;
532 if (v >= 3)
533 return E_INVALIDARG;
534 v++;
535 }
536 _props.AesKeyMode = (Byte)v;
537 _props.IsAesMode = true;
538 m_ForceAesMode = true;
539 }
540 else if (StringsAreEqualNoCase_Ascii(m, "ZipCrypto"))
541 {
542 _props.IsAesMode = false;
543 m_ForceAesMode = true;
544 }
545 else
546 return E_INVALIDARG;
547 }
548 }
549
550
551
552 else if (name.IsEqualTo("cl"))
553 {
554 RINOK(PROPVARIANT_to_bool(prop, m_ForceLocal))
555 if (m_ForceLocal)
556 m_ForceUtf8 = false;
557 }
558 else if (name.IsEqualTo("cu"))
559 {
560 RINOK(PROPVARIANT_to_bool(prop, m_ForceUtf8))
561 if (m_ForceUtf8)
562 m_ForceLocal = false;
563 }
564 else if (name.IsEqualTo("cp"))
565 {
566 UInt32 cp = CP_OEMCP;
567 RINOK(ParsePropToUInt32(L"", prop, cp))
568 _forceCodePage = true;
569 _specifiedCodePage = cp;
570 }
571 else if (name.IsEqualTo("rsfx"))
572 {
573 RINOK(PROPVARIANT_to_bool(prop, _removeSfxBlock))
574 }
575 else if (name.IsEqualTo("rws"))
576 {
577 RINOK(PROPVARIANT_to_bool(prop, _force_SeqOutMode))
578 }
579 else if (name.IsEqualTo("ros"))
580 {
581 RINOK(PROPVARIANT_to_bool(prop, _force_OpenSeq))
582 }
583 else
584 {
585 if (name.IsEqualTo_Ascii_NoCase("m") && prop.vt == VT_UI4)
586 {
587 UInt32 id = prop.ulVal;
588 if (id > 0xFF)
589 return E_INVALIDARG;
590 m_MainMethod = (int)id;
591 }
592 else
593 {
594 bool processed = false;
595 RINOK(TimeOptions.Parse(name, prop, processed))
596 if (!processed)
597 {
598 RINOK(_props.SetProperty(name, prop))
599 }
600 }
601 // RINOK(_props.MethodInfo.ParseParamsFromPROPVARIANT(name, prop));
602 }
603 }
604
605 _props._methods.DeleteFrontal(_props.GetNumEmptyMethods());
606 if (_props._methods.Size() > 1)
607 return E_INVALIDARG;
608 if (_props._methods.Size() == 1)
609 {
610 const AString &methodName = _props._methods[0].MethodName;
611
612 if (!methodName.IsEmpty())
613 {
614 const char *end;
615 UInt32 id = ConvertStringToUInt32(methodName, &end);
616 if (*end == 0 && id <= 0xFF)
617 m_MainMethod = (int)id;
618 else if (methodName.IsEqualTo_Ascii_NoCase("Copy")) // it's alias for "Store"
619 m_MainMethod = 0;
620 }
621 }
622
623 return S_OK;
624 }
625
626 }}
627