5 #include "../../../C/CpuArch.h"
\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
12 #include "Windows/PropVariant.h"
\r
13 #include "Windows/Time.h"
\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
21 #include "../Compress/BZip2Decoder.h"
\r
22 #include "../Compress/CopyCoder.h"
\r
23 #include "../Compress/ZlibDecoder.h"
\r
25 #include "Common/OutStreamWithSha1.h"
\r
27 #define XAR_SHOW_RAW
\r
29 #define Get16(p) GetBe16(p)
\r
30 #define Get32(p) GetBe32(p)
\r
31 #define Get64(p) GetBe64(p)
\r
33 namespace NArchive {
\r
54 // bool packSha1IsDefined;
\r
55 // Byte packSha1[20];
\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
66 public CMyUnknownImp
\r
68 UInt64 _dataStartPos;
\r
69 CMyComPtr<IInStream> _inStream;
\r
71 CObjectVector<CFile> _files;
\r
73 HRESULT Open2(IInStream *stream);
\r
74 HRESULT Extract(IInStream *stream);
\r
76 MY_UNKNOWN_IMP1(IInArchive)
\r
77 INTERFACE_IInArchive(;)
\r
80 const UInt32 kXmlSizeMax = ((UInt32)1 << 30) - (1 << 14);
\r
82 STATPROPSTG kProps[] =
\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
93 IMP_IInArchive_Props
\r
94 IMP_IInArchive_ArcProps_NO
\r
96 static bool ParseNumber(const char *s, int size, UInt32 &res)
\r
99 res = (UInt32)ConvertStringToUInt64(s, &end);
\r
100 return (end - s == size);
\r
103 static bool ParseUInt64(const CXmlItem &item, const char *name, UInt64 &res)
\r
105 AString s = item.GetSubStringForTag(name);
\r
107 res = ConvertStringToUInt64(s, &end);
\r
108 return (end - (const char *)s == s.Length());
\r
111 static UInt64 ParseTime(const CXmlItem &item, const char *name)
\r
113 AString s = item.GetSubStringForTag(name);
\r
114 if (s.Length() < 20)
\r
117 if (p[ 4] != '-' || p[ 7] != '-' || p[10] != 'T' ||
\r
118 p[13] != ':' || p[16] != ':' || p[19] != 'Z')
\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
129 if (!NWindows::NTime::GetSecondsSince1601(year, month, day, hour, min, sec, numSecs))
\r
131 return numSecs * 10000000;
\r
134 static bool HexToByte(char c, Byte &res)
\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
143 #define METHOD_NAME_ZLIB "zlib"
\r
145 static bool ParseSha1(const CXmlItem &item, const char *name, Byte *digest)
\r
147 int index = item.FindSubTag(name);
\r
150 const CXmlItem &checkItem = item.SubItems[index];
\r
151 AString style = checkItem.GetPropertyValue("style");
\r
152 if (style == "SHA1")
\r
154 AString s = checkItem.GetSubString();
\r
155 if (s.Length() != 40)
\r
157 for (int i = 0; i < s.Length(); i += 2)
\r
160 if (!HexToByte(s[i], b0) || !HexToByte(s[i + 1], b1))
\r
162 digest[i / 2] = (b0 << 4) | b1;
\r
169 static bool AddItem(const CXmlItem &item, CObjectVector<CFile> &files, int parent)
\r
173 if (item.Name == "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
182 else if (type == "file")
\r
183 file.IsDir = false;
\r
187 int dataIndex = item.FindSubTag("data");
\r
188 if (dataIndex >= 0 && !file.IsDir)
\r
190 file.HasData = true;
\r
191 const CXmlItem &dataItem = item.SubItems[dataIndex];
\r
192 if (!ParseUInt64(dataItem, "size", file.Size))
\r
194 if (!ParseUInt64(dataItem, "length", file.PackSize))
\r
196 if (!ParseUInt64(dataItem, "offset", file.Offset))
\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
203 const CXmlItem &encodingItem = dataItem.SubItems[encodingIndex];
\r
204 if (encodingItem.IsTag)
\r
206 AString s = encodingItem.GetPropertyValue("style");
\r
207 if (s.Length() >= 0)
\r
209 AString appl = "application/";
\r
210 if (s.Left(appl.Length()) == appl)
\r
212 s = s.Mid(appl.Length());
\r
214 if (s.Left(xx.Length()) == xx)
\r
216 s = s.Mid(xx.Length());
\r
218 s = METHOD_NAME_ZLIB;
\r
227 file.CTime = ParseTime(item, "ctime");
\r
228 file.MTime = ParseTime(item, "mtime");
\r
229 file.ATime = ParseTime(item, "atime");
\r
232 for (int i = 0; i < item.SubItems.Size(); i++)
\r
233 if (!AddItem(item.SubItems[i], files, parent))
\r
238 HRESULT CHandler::Open2(IInStream *stream)
\r
240 UInt64 archiveStartPos;
\r
241 RINOK(stream->Seek(0, STREAM_SEEK_SET, &archiveStartPos));
\r
243 const UInt32 kHeaderSize = 0x1C;
\r
244 Byte buf[kHeaderSize];
\r
245 RINOK(ReadStream_FALSE(stream, buf, kHeaderSize));
\r
247 UInt32 size = Get16(buf + 4);
\r
248 // UInt32 ver = Get16(buf + 6); // == 0
\r
249 if (Get32(buf) != 0x78617221 || size != kHeaderSize)
\r
252 UInt64 packSize = Get64(buf + 8);
\r
253 UInt64 unpackSize = Get64(buf + 0x10);
\r
254 // UInt32 checkSumAlogo = Get32(buf + 0x18);
\r
256 if (unpackSize >= kXmlSizeMax)
\r
259 _dataStartPos = archiveStartPos + kHeaderSize + packSize;
\r
261 char *ss = _xml.GetBuffer((int)unpackSize + 1);
\r
263 NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder();
\r
264 CMyComPtr<ICompressCoder> zlibCoder = zlibCoderSpec;
\r
266 CLimitedSequentialInStream *inStreamLimSpec = new CLimitedSequentialInStream;
\r
267 CMyComPtr<ISequentialInStream> inStreamLim(inStreamLimSpec);
\r
268 inStreamLimSpec->SetStream(stream);
\r
269 inStreamLimSpec->Init(packSize);
\r
271 CBufPtrSeqOutStream *outStreamLimSpec = new CBufPtrSeqOutStream;
\r
272 CMyComPtr<ISequentialOutStream> outStreamLim(outStreamLimSpec);
\r
273 outStreamLimSpec->Init((Byte *)ss, (size_t)unpackSize);
\r
275 RINOK(zlibCoder->Code(inStreamLim, outStreamLim, NULL, NULL, NULL));
\r
277 if (outStreamLimSpec->GetPos() != (size_t)unpackSize)
\r
280 ss[(size_t)unpackSize] = 0;
\r
281 _xml.ReleaseBuffer();
\r
284 if (!xml.Parse(_xml))
\r
287 if (!xml.Root.IsTagged("xar") || xml.Root.SubItems.Size() != 1)
\r
289 const CXmlItem &toc = xml.Root.SubItems[0];
\r
290 if (!toc.IsTagged("toc"))
\r
292 if (!AddItem(toc, _files, -1))
\r
297 STDMETHODIMP CHandler::Open(IInStream *stream,
\r
298 const UInt64 * /* maxCheckStartPosition */,
\r
299 IArchiveOpenCallback * /* openArchiveCallback */)
\r
304 if (Open2(stream) != S_OK)
\r
306 _inStream = stream;
\r
312 STDMETHODIMP CHandler::Close()
\r
314 _inStream.Release();
\r
320 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
\r
322 *numItems = _files.Size()
\r
323 #ifdef XAR_SHOW_RAW
\r
330 static void TimeToProp(UInt64 t, NWindows::NCOM::CPropVariant &prop)
\r
335 ft.dwLowDateTime = (UInt32)(t);
\r
336 ft.dwHighDateTime = (UInt32)(t >> 32);
\r
341 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
\r
344 NWindows::NCOM::CPropVariant prop;
\r
346 #ifdef XAR_SHOW_RAW
\r
347 if ((int)index == _files.Size())
\r
351 case kpidPath: prop = L"[TOC].xml"; break;
\r
353 case kpidPackSize: prop = (UInt64)_xml.Length(); break;
\r
359 const CFile &item = _files[index];
\r
365 if (!item.Method.IsEmpty() && ConvertUTF8ToUnicode(item.Method, name))
\r
375 const CFile &item = _files[cur];
\r
376 AString s = item.Name;
\r
379 if (path.IsEmpty())
\r
382 path = s + CHAR_PATH_SEPARATOR + path;
\r
388 if (ConvertUTF8ToUnicode(path, name))
\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
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
402 prop.Detach(value);
\r
407 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
\r
408 Int32 testMode, IArchiveExtractCallback *extractCallback)
\r
411 bool allFilesMode = (numItems == (UInt32)-1);
\r
413 numItems = _files.Size();
\r
416 UInt64 totalSize = 0;
\r
418 for (i = 0; i < numItems; i++)
\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
426 totalSize += _files[index].Size;
\r
428 extractCallback->SetTotal(totalSize);
\r
430 UInt64 currentPackTotal = 0;
\r
431 UInt64 currentUnpTotal = 0;
\r
432 UInt64 currentPackSize = 0;
\r
433 UInt64 currentUnpSize = 0;
\r
435 const UInt32 kZeroBufSize = (1 << 14);
\r
436 CByteBuffer zeroBuf;
\r
437 zeroBuf.SetCapacity(kZeroBufSize);
\r
438 memset(zeroBuf, 0, kZeroBufSize);
\r
440 NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
\r
441 CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
\r
443 NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder();
\r
444 CMyComPtr<ICompressCoder> zlibCoder = zlibCoderSpec;
\r
446 NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder();
\r
447 CMyComPtr<ICompressCoder> bzip2Coder = bzip2CoderSpec;
\r
449 NCompress::NDeflate::NDecoder::CCOMCoder *deflateCoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder();
\r
450 CMyComPtr<ICompressCoder> deflateCoder = deflateCoderSpec;
\r
452 CLocalProgress *lps = new CLocalProgress;
\r
453 CMyComPtr<ICompressProgressInfo> progress = lps;
\r
454 lps->Init(extractCallback, false);
\r
456 CLimitedSequentialInStream *inStreamSpec = new CLimitedSequentialInStream;
\r
457 CMyComPtr<ISequentialInStream> inStream(inStreamSpec);
\r
458 inStreamSpec->SetStream(_inStream);
\r
461 CLimitedSequentialOutStream *outStreamLimSpec = new CLimitedSequentialOutStream;
\r
462 CMyComPtr<ISequentialOutStream> outStream(outStreamLimSpec);
\r
464 COutStreamWithSha1 *outStreamSha1Spec = new COutStreamWithSha1;
\r
466 CMyComPtr<ISequentialOutStream> outStreamSha1(outStreamSha1Spec);
\r
467 outStreamLimSpec->SetStream(outStreamSha1);
\r
470 for (i = 0; i < numItems; i++, currentPackTotal += currentPackSize, currentUnpTotal += currentUnpSize)
\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
484 if (index < _files.Size())
\r
486 const CFile &item = _files[index];
\r
489 RINOK(extractCallback->PrepareOperation(askMode));
\r
490 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
\r
495 if (!testMode && !realOutStream)
\r
497 RINOK(extractCallback->PrepareOperation(askMode));
\r
499 outStreamSha1Spec->SetStream(realOutStream);
\r
500 realOutStream.Release();
\r
502 Int32 opRes = NExtract::NOperationResult::kOK;
\r
503 #ifdef XAR_SHOW_RAW
\r
504 if (index == _files.Size())
\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
514 const CFile &item = _files[index];
\r
517 currentPackSize = item.PackSize;
\r
518 currentUnpSize = item.Size;
\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
526 ICompressCoder *coder = NULL;
\r
527 if (item.Method.IsEmpty() || item.Method == "octet-stream")
\r
528 if (item.PackSize == item.Size)
\r
531 opRes = NExtract::NOperationResult::kUnSupportedMethod;
\r
532 else if (item.Method == METHOD_NAME_ZLIB)
\r
534 else if (item.Method == "bzip2")
\r
535 coder = bzip2Coder;
\r
537 opRes = NExtract::NOperationResult::kUnSupportedMethod;
\r
540 res = coder->Code(inStream, outStream, NULL, NULL, progress);
\r
544 if (!outStreamLimSpec->IsFinishedOK())
\r
545 opRes = NExtract::NOperationResult::kDataError;
\r
546 else if (res != S_FALSE)
\r
548 if (opRes == NExtract::NOperationResult::kOK)
\r
549 opRes = NExtract::NOperationResult::kDataError;
\r
552 if (opRes == NExtract::NOperationResult::kOK)
\r
554 if (outStreamLimSpec->IsFinishedOK() &&
\r
555 outStreamSha1Spec->GetSize() == item.Size)
\r
557 if (!outStreamLimSpec->IsFinishedOK())
\r
559 opRes = NExtract::NOperationResult::kDataError;
\r
561 else if (item.Sha1IsDefined)
\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
570 opRes = NExtract::NOperationResult::kDataError;
\r
574 outStreamSha1Spec->ReleaseStream();
\r
575 RINOK(extractCallback->SetOperationResult(opRes));
\r
581 static IInArchive *CreateArc() { return new NArchive::NXar::CHandler; }
\r
583 static CArcInfo g_ArcInfo =
\r
584 { L"Xar", L"xar", 0, 0xE1, { 'x', 'a', 'r', '!', 0, 0x1C }, 6, false, CreateArc, 0 };
\r