Imported Upstream version 9.20
[platform/upstream/7zip.git] / CPP / 7zip / Archive / XarHandler.cpp
1 // XarHandler.cpp\r
2 \r
3 #include "StdAfx.h"\r
4 \r
5 #include "../../../C/CpuArch.h"\r
6 \r
7 #include "Common/ComTry.h"\r
8 #include "Common/MyXml.h"\r
9 #include "Common/StringToInt.h"\r
10 #include "Common/UTFConvert.h"\r
11 \r
12 #include "Windows/PropVariant.h"\r
13 #include "Windows/Time.h"\r
14 \r
15 #include "../Common/LimitedStreams.h"\r
16 #include "../Common/ProgressUtils.h"\r
17 #include "../Common/RegisterArc.h"\r
18 #include "../Common/StreamObjects.h"\r
19 #include "../Common/StreamUtils.h"\r
20 \r
21 #include "../Compress/BZip2Decoder.h"\r
22 #include "../Compress/CopyCoder.h"\r
23 #include "../Compress/ZlibDecoder.h"\r
24 \r
25 #include "Common/OutStreamWithSha1.h"\r
26 \r
27 #define XAR_SHOW_RAW\r
28 \r
29 #define Get16(p) GetBe16(p)\r
30 #define Get32(p) GetBe32(p)\r
31 #define Get64(p) GetBe64(p)\r
32 \r
33 namespace NArchive {\r
34 namespace NXar {\r
35 \r
36 struct CFile\r
37 {\r
38   AString Name;\r
39   AString Method;\r
40   UInt64 Size;\r
41   UInt64 PackSize;\r
42   UInt64 Offset;\r
43   \r
44   // UInt32 mode;\r
45   UInt64 CTime;\r
46   UInt64 MTime;\r
47   UInt64 ATime;\r
48   \r
49   bool IsDir;\r
50   bool HasData;\r
51 \r
52   bool Sha1IsDefined;\r
53   Byte Sha1[20];\r
54   // bool packSha1IsDefined;\r
55   // Byte packSha1[20];\r
56 \r
57   int Parent;\r
58 \r
59   CFile(): IsDir(false), HasData(false), Sha1IsDefined(false),\r
60     /* packSha1IsDefined(false), */\r
61     Parent(-1), Size(0), PackSize(0), CTime(0), MTime(0), ATime(0) {}\r
62 };\r
63 \r
64 class CHandler:\r
65   public IInArchive,\r
66   public CMyUnknownImp\r
67 {\r
68   UInt64 _dataStartPos;\r
69   CMyComPtr<IInStream> _inStream;\r
70   AString _xml;\r
71   CObjectVector<CFile> _files;\r
72 \r
73   HRESULT Open2(IInStream *stream);\r
74   HRESULT Extract(IInStream *stream);\r
75 public:\r
76   MY_UNKNOWN_IMP1(IInArchive)\r
77   INTERFACE_IInArchive(;)\r
78 };\r
79 \r
80 const UInt32 kXmlSizeMax = ((UInt32)1 << 30) - (1 << 14);\r
81 \r
82 STATPROPSTG kProps[] =\r
83 {\r
84   { NULL, kpidPath, VT_BSTR},\r
85   { NULL, kpidSize, VT_UI8},\r
86   { NULL, kpidPackSize, VT_UI8},\r
87   { NULL, kpidMTime, VT_FILETIME},\r
88   { NULL, kpidCTime, VT_FILETIME},\r
89   { NULL, kpidATime, VT_FILETIME},\r
90   { NULL, kpidMethod, VT_BSTR}\r
91 };\r
92 \r
93 IMP_IInArchive_Props\r
94 IMP_IInArchive_ArcProps_NO\r
95 \r
96 static bool ParseNumber(const char *s, int size, UInt32 &res)\r
97 {\r
98   const char *end;\r
99   res = (UInt32)ConvertStringToUInt64(s, &end);\r
100   return (end - s == size);\r
101 }\r
102 \r
103 static bool ParseUInt64(const CXmlItem &item, const char *name, UInt64 &res)\r
104 {\r
105   AString s = item.GetSubStringForTag(name);\r
106   const char *end;\r
107   res = ConvertStringToUInt64(s, &end);\r
108   return (end - (const char *)s == s.Length());\r
109 }\r
110 \r
111 static UInt64 ParseTime(const CXmlItem &item, const char *name)\r
112 {\r
113   AString s = item.GetSubStringForTag(name);\r
114   if (s.Length() < 20)\r
115     return 0;\r
116   const char *p = s;\r
117   if (p[ 4] != '-' || p[ 7] != '-' || p[10] != 'T' ||\r
118       p[13] != ':' || p[16] != ':' || p[19] != 'Z')\r
119     return 0;\r
120   UInt32 year, month, day, hour, min, sec;\r
121   if (!ParseNumber(p,      4, year )) return 0;\r
122   if (!ParseNumber(p + 5,  2, month)) return 0;\r
123   if (!ParseNumber(p + 8,  2, day  )) return 0;\r
124   if (!ParseNumber(p + 11, 2, hour )) return 0;\r
125   if (!ParseNumber(p + 14, 2, min  )) return 0;\r
126   if (!ParseNumber(p + 17, 2, sec  )) return 0;\r
127   \r
128   UInt64 numSecs;\r
129   if (!NWindows::NTime::GetSecondsSince1601(year, month, day, hour, min, sec, numSecs))\r
130     return 0;\r
131   return numSecs * 10000000;\r
132 }\r
133 \r
134 static bool HexToByte(char c, Byte &res)\r
135 {\r
136   if      (c >= '0' && c <= '9') res = c - '0';\r
137   else if (c >= 'A' && c <= 'F') res = c - 'A' + 10;\r
138   else if (c >= 'a' && c <= 'f') res = c - 'a' + 10;\r
139   else return false;\r
140   return true;\r
141 }\r
142 \r
143 #define METHOD_NAME_ZLIB "zlib"\r
144 \r
145 static bool ParseSha1(const CXmlItem &item, const char *name, Byte *digest)\r
146 {\r
147   int index = item.FindSubTag(name);\r
148   if (index  < 0)\r
149     return false;\r
150   const CXmlItem &checkItem = item.SubItems[index];\r
151   AString style = checkItem.GetPropertyValue("style");\r
152   if (style == "SHA1")\r
153   {\r
154     AString s = checkItem.GetSubString();\r
155     if (s.Length() != 40)\r
156       return false;\r
157     for (int i = 0; i < s.Length(); i += 2)\r
158     {\r
159       Byte b0, b1;\r
160       if (!HexToByte(s[i], b0) || !HexToByte(s[i + 1], b1))\r
161         return false;\r
162       digest[i / 2] = (b0 << 4) | b1;\r
163     }\r
164     return true;\r
165   }\r
166   return false;\r
167 }\r
168 \r
169 static bool AddItem(const CXmlItem &item, CObjectVector<CFile> &files, int parent)\r
170 {\r
171   if (!item.IsTag)\r
172     return true;\r
173   if (item.Name == "file")\r
174   {\r
175     CFile file;\r
176     file.Parent = parent;\r
177     parent = files.Size();\r
178     file.Name = item.GetSubStringForTag("name");\r
179     AString type = item.GetSubStringForTag("type");\r
180     if (type == "directory")\r
181       file.IsDir = true;\r
182     else if (type == "file")\r
183       file.IsDir = false;\r
184     else\r
185       return false;\r
186 \r
187     int dataIndex = item.FindSubTag("data");\r
188     if (dataIndex >= 0 && !file.IsDir)\r
189     {\r
190       file.HasData = true;\r
191       const CXmlItem &dataItem = item.SubItems[dataIndex];\r
192       if (!ParseUInt64(dataItem, "size", file.Size))\r
193         return false;\r
194       if (!ParseUInt64(dataItem, "length", file.PackSize))\r
195         return false;\r
196       if (!ParseUInt64(dataItem, "offset", file.Offset))\r
197         return false;\r
198       file.Sha1IsDefined = ParseSha1(dataItem, "extracted-checksum", file.Sha1);\r
199       // file.packSha1IsDefined = ParseSha1(dataItem, "archived-checksum",  file.packSha1);\r
200       int encodingIndex = dataItem.FindSubTag("encoding");\r
201       if (encodingIndex >= 0)\r
202       {\r
203         const CXmlItem &encodingItem = dataItem.SubItems[encodingIndex];\r
204         if (encodingItem.IsTag)\r
205         {\r
206           AString s = encodingItem.GetPropertyValue("style");\r
207           if (s.Length() >= 0)\r
208           {\r
209             AString appl = "application/";\r
210             if (s.Left(appl.Length()) == appl)\r
211             {\r
212               s = s.Mid(appl.Length());\r
213               AString xx = "x-";\r
214               if (s.Left(xx.Length()) == xx)\r
215               {\r
216                 s = s.Mid(xx.Length());\r
217                 if (s == "gzip")\r
218                   s = METHOD_NAME_ZLIB;\r
219               }\r
220             }\r
221             file.Method = s;\r
222           }\r
223         }\r
224       }\r
225     }\r
226 \r
227     file.CTime = ParseTime(item, "ctime");\r
228     file.MTime = ParseTime(item, "mtime");\r
229     file.ATime = ParseTime(item, "atime");\r
230     files.Add(file);\r
231   }\r
232   for (int i = 0; i < item.SubItems.Size(); i++)\r
233     if (!AddItem(item.SubItems[i], files, parent))\r
234       return false;\r
235   return true;\r
236 }\r
237 \r
238 HRESULT CHandler::Open2(IInStream *stream)\r
239 {\r
240   UInt64 archiveStartPos;\r
241   RINOK(stream->Seek(0, STREAM_SEEK_SET, &archiveStartPos));\r
242 \r
243   const UInt32 kHeaderSize = 0x1C;\r
244   Byte buf[kHeaderSize];\r
245   RINOK(ReadStream_FALSE(stream, buf, kHeaderSize));\r
246 \r
247   UInt32 size = Get16(buf + 4);\r
248   // UInt32 ver = Get16(buf + 6); // == 0\r
249   if (Get32(buf) != 0x78617221 || size != kHeaderSize)\r
250     return S_FALSE;\r
251 \r
252   UInt64 packSize = Get64(buf + 8);\r
253   UInt64 unpackSize = Get64(buf + 0x10);\r
254   // UInt32 checkSumAlogo = Get32(buf + 0x18);\r
255 \r
256   if (unpackSize >= kXmlSizeMax)\r
257     return S_FALSE;\r
258 \r
259   _dataStartPos = archiveStartPos + kHeaderSize + packSize;\r
260 \r
261   char *ss = _xml.GetBuffer((int)unpackSize + 1);\r
262 \r
263   NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder();\r
264   CMyComPtr<ICompressCoder> zlibCoder = zlibCoderSpec;\r
265 \r
266   CLimitedSequentialInStream *inStreamLimSpec = new CLimitedSequentialInStream;\r
267   CMyComPtr<ISequentialInStream> inStreamLim(inStreamLimSpec);\r
268   inStreamLimSpec->SetStream(stream);\r
269   inStreamLimSpec->Init(packSize);\r
270 \r
271   CBufPtrSeqOutStream *outStreamLimSpec = new CBufPtrSeqOutStream;\r
272   CMyComPtr<ISequentialOutStream> outStreamLim(outStreamLimSpec);\r
273   outStreamLimSpec->Init((Byte *)ss, (size_t)unpackSize);\r
274 \r
275   RINOK(zlibCoder->Code(inStreamLim, outStreamLim, NULL, NULL, NULL));\r
276 \r
277   if (outStreamLimSpec->GetPos() != (size_t)unpackSize)\r
278     return S_FALSE;\r
279 \r
280   ss[(size_t)unpackSize] = 0;\r
281   _xml.ReleaseBuffer();\r
282 \r
283   CXml xml;\r
284   if (!xml.Parse(_xml))\r
285     return S_FALSE;\r
286   \r
287   if (!xml.Root.IsTagged("xar") || xml.Root.SubItems.Size() != 1)\r
288     return S_FALSE;\r
289   const CXmlItem &toc = xml.Root.SubItems[0];\r
290   if (!toc.IsTagged("toc"))\r
291     return S_FALSE;\r
292   if (!AddItem(toc, _files, -1))\r
293     return S_FALSE;\r
294   return S_OK;\r
295 }\r
296 \r
297 STDMETHODIMP CHandler::Open(IInStream *stream,\r
298     const UInt64 * /* maxCheckStartPosition */,\r
299     IArchiveOpenCallback * /* openArchiveCallback */)\r
300 {\r
301   COM_TRY_BEGIN\r
302   {\r
303     Close();\r
304     if (Open2(stream) != S_OK)\r
305       return S_FALSE;\r
306     _inStream = stream;\r
307   }\r
308   return S_OK;\r
309   COM_TRY_END\r
310 }\r
311 \r
312 STDMETHODIMP CHandler::Close()\r
313 {\r
314   _inStream.Release();\r
315   _files.Clear();\r
316   _xml.Empty();\r
317   return S_OK;\r
318 }\r
319 \r
320 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)\r
321 {\r
322   *numItems = _files.Size()\r
323     #ifdef XAR_SHOW_RAW\r
324     + 1\r
325     #endif\r
326   ;\r
327   return S_OK;\r
328 }\r
329 \r
330 static void TimeToProp(UInt64 t, NWindows::NCOM::CPropVariant &prop)\r
331 {\r
332   if (t != 0)\r
333   {\r
334     FILETIME ft;\r
335     ft.dwLowDateTime = (UInt32)(t);\r
336     ft.dwHighDateTime = (UInt32)(t >> 32);\r
337     prop = ft;\r
338   }\r
339 }\r
340 \r
341 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)\r
342 {\r
343   COM_TRY_BEGIN\r
344   NWindows::NCOM::CPropVariant prop;\r
345   \r
346   #ifdef XAR_SHOW_RAW\r
347   if ((int)index == _files.Size())\r
348   {\r
349     switch(propID)\r
350     {\r
351       case kpidPath: prop = L"[TOC].xml"; break;\r
352       case kpidSize:\r
353       case kpidPackSize: prop = (UInt64)_xml.Length(); break;\r
354     }\r
355   }\r
356   else\r
357   #endif\r
358   {\r
359     const CFile &item = _files[index];\r
360     switch(propID)\r
361     {\r
362       case kpidMethod:\r
363       {\r
364         UString name;\r
365         if (!item.Method.IsEmpty() && ConvertUTF8ToUnicode(item.Method, name))\r
366           prop = name;\r
367         break;\r
368       }\r
369       case kpidPath:\r
370       {\r
371         AString path;\r
372         int cur = index;\r
373         do\r
374         {\r
375           const CFile &item = _files[cur];\r
376           AString s = item.Name;\r
377           if (s.IsEmpty())\r
378             s = "unknown";\r
379           if (path.IsEmpty())\r
380             path = s;\r
381           else\r
382             path = s + CHAR_PATH_SEPARATOR + path;\r
383           cur = item.Parent;\r
384         }\r
385         while (cur >= 0);\r
386 \r
387         UString name;\r
388         if (ConvertUTF8ToUnicode(path, name))\r
389           prop = name;\r
390         break;\r
391       }\r
392       \r
393       case kpidIsDir:  prop = item.IsDir; break;\r
394       case kpidSize:      if (!item.IsDir) prop = item.Size; break;\r
395       case kpidPackSize:  if (!item.IsDir) prop = item.PackSize; break;\r
396       \r
397       case kpidMTime:  TimeToProp(item.MTime, prop); break;\r
398       case kpidCTime:  TimeToProp(item.CTime, prop); break;\r
399       case kpidATime:  TimeToProp(item.ATime, prop); break;\r
400     }\r
401   }\r
402   prop.Detach(value);\r
403   return S_OK;\r
404   COM_TRY_END\r
405 }\r
406 \r
407 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,\r
408     Int32 testMode, IArchiveExtractCallback *extractCallback)\r
409 {\r
410   COM_TRY_BEGIN\r
411   bool allFilesMode = (numItems == (UInt32)-1);\r
412   if (allFilesMode)\r
413     numItems = _files.Size();\r
414   if (numItems == 0)\r
415     return S_OK;\r
416   UInt64 totalSize = 0;\r
417   UInt32 i;\r
418   for (i = 0; i < numItems; i++)\r
419   {\r
420     int index = (int)(allFilesMode ? i : indices[i]);\r
421     #ifdef XAR_SHOW_RAW\r
422     if (index == _files.Size())\r
423       totalSize += _xml.Length();\r
424     else\r
425     #endif\r
426       totalSize += _files[index].Size;\r
427   }\r
428   extractCallback->SetTotal(totalSize);\r
429 \r
430   UInt64 currentPackTotal = 0;\r
431   UInt64 currentUnpTotal = 0;\r
432   UInt64 currentPackSize = 0;\r
433   UInt64 currentUnpSize = 0;\r
434 \r
435   const UInt32 kZeroBufSize = (1 << 14);\r
436   CByteBuffer zeroBuf;\r
437   zeroBuf.SetCapacity(kZeroBufSize);\r
438   memset(zeroBuf, 0, kZeroBufSize);\r
439   \r
440   NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();\r
441   CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;\r
442 \r
443   NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder();\r
444   CMyComPtr<ICompressCoder> zlibCoder = zlibCoderSpec;\r
445   \r
446   NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder();\r
447   CMyComPtr<ICompressCoder> bzip2Coder = bzip2CoderSpec;\r
448 \r
449   NCompress::NDeflate::NDecoder::CCOMCoder *deflateCoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder();\r
450   CMyComPtr<ICompressCoder> deflateCoder = deflateCoderSpec;\r
451 \r
452   CLocalProgress *lps = new CLocalProgress;\r
453   CMyComPtr<ICompressProgressInfo> progress = lps;\r
454   lps->Init(extractCallback, false);\r
455 \r
456   CLimitedSequentialInStream *inStreamSpec = new CLimitedSequentialInStream;\r
457   CMyComPtr<ISequentialInStream> inStream(inStreamSpec);\r
458   inStreamSpec->SetStream(_inStream);\r
459 \r
460   \r
461   CLimitedSequentialOutStream *outStreamLimSpec = new CLimitedSequentialOutStream;\r
462   CMyComPtr<ISequentialOutStream> outStream(outStreamLimSpec);\r
463 \r
464   COutStreamWithSha1 *outStreamSha1Spec = new COutStreamWithSha1;\r
465   {\r
466     CMyComPtr<ISequentialOutStream> outStreamSha1(outStreamSha1Spec);\r
467     outStreamLimSpec->SetStream(outStreamSha1);\r
468   }\r
469 \r
470   for (i = 0; i < numItems; i++, currentPackTotal += currentPackSize, currentUnpTotal += currentUnpSize)\r
471   {\r
472     lps->InSize = currentPackTotal;\r
473     lps->OutSize = currentUnpTotal;\r
474     currentPackSize = 0;\r
475     currentUnpSize = 0;\r
476     RINOK(lps->SetCur());\r
477     CMyComPtr<ISequentialOutStream> realOutStream;\r
478     Int32 askMode = testMode ?\r
479         NExtract::NAskMode::kTest :\r
480         NExtract::NAskMode::kExtract;\r
481     Int32 index = allFilesMode ? i : indices[i];\r
482     RINOK(extractCallback->GetStream(index, &realOutStream, askMode));\r
483     \r
484     if (index < _files.Size())\r
485     {\r
486       const CFile &item = _files[index];\r
487       if (item.IsDir)\r
488       {\r
489         RINOK(extractCallback->PrepareOperation(askMode));\r
490         RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));\r
491         continue;\r
492       }\r
493     }\r
494 \r
495     if (!testMode && !realOutStream)\r
496       continue;\r
497     RINOK(extractCallback->PrepareOperation(askMode));\r
498 \r
499     outStreamSha1Spec->SetStream(realOutStream);\r
500     realOutStream.Release();\r
501 \r
502     Int32 opRes = NExtract::NOperationResult::kOK;\r
503     #ifdef XAR_SHOW_RAW\r
504     if (index == _files.Size())\r
505     {\r
506       outStreamSha1Spec->Init(false);\r
507       outStreamLimSpec->Init(_xml.Length());\r
508       RINOK(WriteStream(outStream, (const char *)_xml, _xml.Length()));\r
509       currentPackSize = currentUnpSize = _xml.Length();\r
510     }\r
511     else\r
512     #endif\r
513     {\r
514       const CFile &item = _files[index];\r
515       if (item.HasData)\r
516       {\r
517         currentPackSize = item.PackSize;\r
518         currentUnpSize = item.Size;\r
519         \r
520         RINOK(_inStream->Seek(_dataStartPos + item.Offset, STREAM_SEEK_SET, NULL));\r
521         inStreamSpec->Init(item.PackSize);\r
522         outStreamSha1Spec->Init(item.Sha1IsDefined);\r
523         outStreamLimSpec->Init(item.Size);\r
524         HRESULT res = S_OK;\r
525         \r
526         ICompressCoder *coder = NULL;\r
527         if (item.Method.IsEmpty() || item.Method == "octet-stream")\r
528           if (item.PackSize == item.Size)\r
529             coder = copyCoder;\r
530           else\r
531             opRes = NExtract::NOperationResult::kUnSupportedMethod;\r
532         else if (item.Method == METHOD_NAME_ZLIB)\r
533           coder = zlibCoder;\r
534         else if (item.Method == "bzip2")\r
535           coder = bzip2Coder;\r
536         else\r
537           opRes = NExtract::NOperationResult::kUnSupportedMethod;\r
538         \r
539         if (coder)\r
540           res = coder->Code(inStream, outStream, NULL, NULL, progress);\r
541         \r
542         if (res != S_OK)\r
543         {\r
544           if (!outStreamLimSpec->IsFinishedOK())\r
545             opRes = NExtract::NOperationResult::kDataError;\r
546           else if (res != S_FALSE)\r
547             return res;\r
548           if (opRes == NExtract::NOperationResult::kOK)\r
549             opRes = NExtract::NOperationResult::kDataError;\r
550         }\r
551 \r
552         if (opRes == NExtract::NOperationResult::kOK)\r
553         {\r
554           if (outStreamLimSpec->IsFinishedOK() &&\r
555               outStreamSha1Spec->GetSize() == item.Size)\r
556           {\r
557             if (!outStreamLimSpec->IsFinishedOK())\r
558             {\r
559               opRes = NExtract::NOperationResult::kDataError;\r
560             }\r
561             else if (item.Sha1IsDefined)\r
562             {\r
563               Byte digest[NCrypto::NSha1::kDigestSize];\r
564               outStreamSha1Spec->Final(digest);\r
565               if (memcmp(digest, item.Sha1, NCrypto::NSha1::kDigestSize) != 0)\r
566                 opRes = NExtract::NOperationResult::kCRCError;\r
567             }\r
568           }\r
569           else\r
570             opRes = NExtract::NOperationResult::kDataError;\r
571         }\r
572       }\r
573     }\r
574     outStreamSha1Spec->ReleaseStream();\r
575     RINOK(extractCallback->SetOperationResult(opRes));\r
576   }\r
577   return S_OK;\r
578   COM_TRY_END\r
579 }\r
580 \r
581 static IInArchive *CreateArc() { return new NArchive::NXar::CHandler; }\r
582 \r
583 static CArcInfo g_ArcInfo =\r
584   { L"Xar", L"xar", 0, 0xE1, { 'x', 'a', 'r', '!', 0, 0x1C }, 6, false, CreateArc, 0 };\r
585 \r
586 REGISTER_ARC(Xar)\r
587 \r
588 }}\r