// XarHandler.cpp #include "StdAfx.h" #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" #include "../../Common/MyLinux.h" #include "../../Common/MyXml.h" #include "../../Common/StringToInt.h" #include "../../Common/UTFConvert.h" #include "../../Windows/PropVariant.h" #include "../../Windows/TimeUtils.h" #include "../Common/LimitedStreams.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamObjects.h" #include "../Common/StreamUtils.h" #include "../Compress/BZip2Decoder.h" #include "../Compress/CopyCoder.h" #include "../Compress/ZlibDecoder.h" #include "Common/OutStreamWithSha1.h" using namespace NWindows; #define XAR_SHOW_RAW #define Get16(p) GetBe16(p) #define Get32(p) GetBe32(p) #define Get64(p) GetBe64(p) namespace NArchive { namespace NXar { static const size_t kXmlSizeMax = ((size_t )1 << 30) - (1 << 14); static const size_t kXmlPackSizeMax = kXmlSizeMax; /* #define XAR_CKSUM_NONE 0 #define XAR_CKSUM_SHA1 1 #define XAR_CKSUM_MD5 2 static const char * const k_ChecksumAlgos[] = { "None" , "SHA-1" , "MD5" }; */ #define METHOD_NAME_ZLIB "zlib" struct CFile { AString Name; AString Method; UInt64 Size; UInt64 PackSize; UInt64 Offset; UInt64 CTime; UInt64 MTime; UInt64 ATime; UInt32 Mode; AString User; AString Group; bool IsDir; bool HasData; bool ModeDefined; bool Sha1IsDefined; // bool packSha1IsDefined; Byte Sha1[SHA1_DIGEST_SIZE]; // Byte packSha1[SHA1_DIGEST_SIZE]; int Parent; CFile(): IsDir(false), HasData(false), ModeDefined(false), Sha1IsDefined(false), /* packSha1IsDefined(false), */ Parent(-1), Size(0), PackSize(0), Offset(0), CTime(0), MTime(0), ATime(0), Mode(0) {} bool IsCopyMethod() const { return Method.IsEmpty() || Method == "octet-stream"; } void UpdateTotalPackSize(UInt64 &totalSize) const { UInt64 t = Offset + PackSize; if (totalSize < t) totalSize = t; } }; class CHandler: public IInArchive, public IInArchiveGetStream, public CMyUnknownImp { UInt64 _dataStartPos; CMyComPtr _inStream; CByteArr _xml; size_t _xmlLen; CObjectVector _files; // UInt32 _checkSumAlgo; UInt64 _phySize; Int32 _mainSubfile; bool _is_pkg; HRESULT Open2(IInStream *stream); HRESULT Extract(IInStream *stream); public: MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) INTERFACE_IInArchive(;) STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; static const Byte kArcProps[] = { kpidSubType, kpidHeadersSize }; static const Byte kProps[] = { kpidPath, kpidSize, kpidPackSize, kpidMTime, kpidCTime, kpidATime, kpidPosixAttrib, kpidUser, kpidGroup, kpidMethod }; IMP_IInArchive_Props IMP_IInArchive_ArcProps #define PARSE_NUM(_num_, _dest_) \ { const char *end; _dest_ = ConvertStringToUInt32(p, &end); \ if ((unsigned)(end - p) != _num_) return 0; p += _num_ + 1; } static bool ParseUInt64(const CXmlItem &item, const char *name, UInt64 &res) { const AString s = item.GetSubStringForTag(name); if (s.IsEmpty()) return false; const char *end; res = ConvertStringToUInt64(s, &end); return *end == 0; } static UInt64 ParseTime(const CXmlItem &item, const char *name) { const AString s = item.GetSubStringForTag(name); if (s.Len() < 20) return 0; const char *p = s; if (p[ 4] != '-' || p[ 7] != '-' || p[10] != 'T' || p[13] != ':' || p[16] != ':' || p[19] != 'Z') return 0; UInt32 year, month, day, hour, min, sec; PARSE_NUM(4, year) PARSE_NUM(2, month) PARSE_NUM(2, day) PARSE_NUM(2, hour) PARSE_NUM(2, min) PARSE_NUM(2, sec) UInt64 numSecs; if (!NTime::GetSecondsSince1601(year, month, day, hour, min, sec, numSecs)) return 0; return numSecs * 10000000; } static int HexToByte(unsigned char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; return -1; } static bool ParseSha1(const CXmlItem &item, const char *name, Byte *digest) { int index = item.FindSubTag(name); if (index < 0) return false; const CXmlItem &checkItem = item.SubItems[index]; const AString style = checkItem.GetPropVal("style"); if (style == "SHA1") { const AString s = checkItem.GetSubString(); if (s.Len() != SHA1_DIGEST_SIZE * 2) return false; for (unsigned i = 0; i < s.Len(); i += 2) { int b0 = HexToByte(s[i]); int b1 = HexToByte(s[i + 1]); if (b0 < 0 || b1 < 0) return false; digest[i / 2] = (Byte)((b0 << 4) | b1); } return true; } return false; } static bool AddItem(const CXmlItem &item, CObjectVector &files, int parent) { if (!item.IsTag) return true; if (item.Name == "file") { CFile file; file.Parent = parent; parent = files.Size(); file.Name = item.GetSubStringForTag("name"); AString type = item.GetSubStringForTag("type"); if (type == "directory") file.IsDir = true; else if (type == "file") file.IsDir = false; else return false; int dataIndex = item.FindSubTag("data"); if (dataIndex >= 0 && !file.IsDir) { file.HasData = true; const CXmlItem &dataItem = item.SubItems[dataIndex]; if (!ParseUInt64(dataItem, "size", file.Size)) return false; if (!ParseUInt64(dataItem, "length", file.PackSize)) return false; if (!ParseUInt64(dataItem, "offset", file.Offset)) return false; file.Sha1IsDefined = ParseSha1(dataItem, "extracted-checksum", file.Sha1); // file.packSha1IsDefined = ParseSha1(dataItem, "archived-checksum", file.packSha1); int encodingIndex = dataItem.FindSubTag("encoding"); if (encodingIndex >= 0) { const CXmlItem &encodingItem = dataItem.SubItems[encodingIndex]; if (encodingItem.IsTag) { AString s = encodingItem.GetPropVal("style"); if (s.Len() >= 0) { AString appl = "application/"; if (s.IsPrefixedBy(appl)) { s.DeleteFrontal(appl.Len()); AString xx = "x-"; if (s.IsPrefixedBy(xx)) { s.DeleteFrontal(xx.Len()); if (s == "gzip") s = METHOD_NAME_ZLIB; } } file.Method = s; } } } } file.CTime = ParseTime(item, "ctime"); file.MTime = ParseTime(item, "mtime"); file.ATime = ParseTime(item, "atime"); { const AString s = item.GetSubStringForTag("mode"); if (s[0] == '0') { const char *end; file.Mode = ConvertOctStringToUInt32(s, &end); file.ModeDefined = (*end == 0); } } file.User = item.GetSubStringForTag("user"); file.Group = item.GetSubStringForTag("group"); files.Add(file); } FOR_VECTOR (i, item.SubItems) if (!AddItem(item.SubItems[i], files, parent)) return false; return true; } HRESULT CHandler::Open2(IInStream *stream) { const UInt32 kHeaderSize = 0x1C; Byte buf[kHeaderSize]; RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)); UInt32 size = Get16(buf + 4); // UInt32 ver = Get16(buf + 6); // == 1 if (Get32(buf) != 0x78617221 || size != kHeaderSize) return S_FALSE; UInt64 packSize = Get64(buf + 8); UInt64 unpackSize = Get64(buf + 0x10); // _checkSumAlgo = Get32(buf + 0x18); if (packSize >= kXmlPackSizeMax || unpackSize >= kXmlSizeMax) return S_FALSE; _dataStartPos = kHeaderSize + packSize; _phySize = _dataStartPos; _xml.Alloc((size_t)unpackSize + 1); _xmlLen = (size_t)unpackSize; NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder(); CMyComPtr zlibCoder = zlibCoderSpec; CLimitedSequentialInStream *inStreamLimSpec = new CLimitedSequentialInStream; CMyComPtr inStreamLim(inStreamLimSpec); inStreamLimSpec->SetStream(stream); inStreamLimSpec->Init(packSize); CBufPtrSeqOutStream *outStreamLimSpec = new CBufPtrSeqOutStream; CMyComPtr outStreamLim(outStreamLimSpec); outStreamLimSpec->Init(_xml, (size_t)unpackSize); RINOK(zlibCoder->Code(inStreamLim, outStreamLim, NULL, NULL, NULL)); if (outStreamLimSpec->GetPos() != (size_t)unpackSize) return S_FALSE; _xml[(size_t)unpackSize] = 0; if (strlen((const char *)(const Byte *)_xml) != unpackSize) return S_FALSE; CXml xml; if (!xml.Parse((const char *)(const Byte *)_xml)) return S_FALSE; if (!xml.Root.IsTagged("xar") || xml.Root.SubItems.Size() != 1) return S_FALSE; const CXmlItem &toc = xml.Root.SubItems[0]; if (!toc.IsTagged("toc")) return S_FALSE; if (!AddItem(toc, _files, -1)) return S_FALSE; UInt64 totalPackSize = 0; unsigned numMainFiles = 0; FOR_VECTOR (i, _files) { const CFile &file = _files[i]; file.UpdateTotalPackSize(totalPackSize); if (file.Name == "Payload") { _mainSubfile = i; numMainFiles++; } if (file.Name == "PackageInfo") _is_pkg = true; } if (numMainFiles > 1) _mainSubfile = -1; _phySize = _dataStartPos + totalPackSize; return S_OK; } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback * /* openArchiveCallback */) { COM_TRY_BEGIN { Close(); if (Open2(stream) != S_OK) return S_FALSE; _inStream = stream; } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Close() { _phySize = 0; _inStream.Release(); _files.Clear(); _xmlLen = 0; _xml.Free(); _mainSubfile = -1; _is_pkg = false; return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = _files.Size() #ifdef XAR_SHOW_RAW + 1 #endif ; return S_OK; } static void TimeToProp(UInt64 t, NCOM::CPropVariant &prop) { if (t != 0) { FILETIME ft; ft.dwLowDateTime = (UInt32)(t); ft.dwHighDateTime = (UInt32)(t >> 32); prop = ft; } } static void Utf8StringToProp(const AString &s, NCOM::CPropVariant &prop) { if (!s.IsEmpty()) { UString us; if (ConvertUTF8ToUnicode(s, us)) prop = us; } } STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { case kpidHeadersSize: prop = _dataStartPos; break; case kpidPhySize: prop = _phySize; break; case kpidMainSubfile: if (_mainSubfile >= 0) prop = (UInt32)_mainSubfile; break; case kpidSubType: if (_is_pkg) prop = "pkg"; break; case kpidExtension: prop = _is_pkg ? "pkg" : "xar"; break; } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; #ifdef XAR_SHOW_RAW if (index == _files.Size()) { switch (propID) { case kpidPath: prop = "[TOC].xml"; break; case kpidSize: case kpidPackSize: prop = (UInt64)_xmlLen; break; } } else #endif { const CFile &item = _files[index]; switch (propID) { case kpidMethod: Utf8StringToProp(item.Method, prop); break; case kpidPath: { AString path; int cur = index; do { const CFile &item2 = _files[cur]; if (!path.IsEmpty()) path.InsertAtFront(CHAR_PATH_SEPARATOR); if (item2.Name.IsEmpty()) path.Insert(0, "unknown"); else path.Insert(0, item2.Name); cur = item2.Parent; } while (cur >= 0); Utf8StringToProp(path, prop); break; } case kpidIsDir: prop = item.IsDir; break; case kpidSize: if (!item.IsDir) prop = item.Size; break; case kpidPackSize: if (!item.IsDir) prop = item.PackSize; break; case kpidMTime: TimeToProp(item.MTime, prop); break; case kpidCTime: TimeToProp(item.CTime, prop); break; case kpidATime: TimeToProp(item.ATime, prop); break; case kpidPosixAttrib: if (item.ModeDefined) { UInt32 mode = item.Mode; if ((mode & MY_LIN_S_IFMT) == 0) mode |= (item.IsDir ? MY_LIN_S_IFDIR : MY_LIN_S_IFREG); prop = mode; } break; case kpidUser: Utf8StringToProp(item.User, prop); break; case kpidGroup: Utf8StringToProp(item.Group, prop); break; } } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _files.Size(); if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) { UInt32 index = (allFilesMode ? i : indices[i]); #ifdef XAR_SHOW_RAW if (index == _files.Size()) totalSize += _xmlLen; else #endif totalSize += _files[index].Size; } extractCallback->SetTotal(totalSize); UInt64 currentPackTotal = 0; UInt64 currentUnpTotal = 0; UInt64 currentPackSize = 0; UInt64 currentUnpSize = 0; const UInt32 kZeroBufSize = (1 << 14); CByteBuffer zeroBuf(kZeroBufSize); memset(zeroBuf, 0, kZeroBufSize); NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); CMyComPtr copyCoder = copyCoderSpec; NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder(); CMyComPtr zlibCoder = zlibCoderSpec; NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder(); CMyComPtr bzip2Coder = bzip2CoderSpec; NCompress::NDeflate::NDecoder::CCOMCoder *deflateCoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder(); CMyComPtr deflateCoder = deflateCoderSpec; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); CLimitedSequentialInStream *inStreamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(inStreamSpec); inStreamSpec->SetStream(_inStream); CLimitedSequentialOutStream *outStreamLimSpec = new CLimitedSequentialOutStream; CMyComPtr outStream(outStreamLimSpec); COutStreamWithSha1 *outStreamSha1Spec = new COutStreamWithSha1; { CMyComPtr outStreamSha1(outStreamSha1Spec); outStreamLimSpec->SetStream(outStreamSha1); } for (i = 0; i < numItems; i++, currentPackTotal += currentPackSize, currentUnpTotal += currentUnpSize) { lps->InSize = currentPackTotal; lps->OutSize = currentUnpTotal; currentPackSize = 0; currentUnpSize = 0; RINOK(lps->SetCur()); CMyComPtr realOutStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; UInt32 index = allFilesMode ? i : indices[i]; RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); if (index < _files.Size()) { const CFile &item = _files[index]; if (item.IsDir) { RINOK(extractCallback->PrepareOperation(askMode)); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); continue; } } if (!testMode && !realOutStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); outStreamSha1Spec->SetStream(realOutStream); realOutStream.Release(); Int32 opRes = NExtract::NOperationResult::kOK; #ifdef XAR_SHOW_RAW if (index == _files.Size()) { outStreamSha1Spec->Init(false); outStreamLimSpec->Init(_xmlLen); RINOK(WriteStream(outStream, _xml, _xmlLen)); currentPackSize = currentUnpSize = _xmlLen; } else #endif { const CFile &item = _files[index]; if (item.HasData) { currentPackSize = item.PackSize; currentUnpSize = item.Size; RINOK(_inStream->Seek(_dataStartPos + item.Offset, STREAM_SEEK_SET, NULL)); inStreamSpec->Init(item.PackSize); outStreamSha1Spec->Init(item.Sha1IsDefined); outStreamLimSpec->Init(item.Size); HRESULT res = S_OK; ICompressCoder *coder = NULL; if (item.IsCopyMethod()) if (item.PackSize == item.Size) coder = copyCoder; else opRes = NExtract::NOperationResult::kUnsupportedMethod; else if (item.Method == METHOD_NAME_ZLIB) coder = zlibCoder; else if (item.Method == "bzip2") coder = bzip2Coder; else opRes = NExtract::NOperationResult::kUnsupportedMethod; if (coder) res = coder->Code(inStream, outStream, NULL, NULL, progress); if (res != S_OK) { if (!outStreamLimSpec->IsFinishedOK()) opRes = NExtract::NOperationResult::kDataError; else if (res != S_FALSE) return res; if (opRes == NExtract::NOperationResult::kOK) opRes = NExtract::NOperationResult::kDataError; } if (opRes == NExtract::NOperationResult::kOK) { if (outStreamLimSpec->IsFinishedOK() && outStreamSha1Spec->GetSize() == item.Size) { if (!outStreamLimSpec->IsFinishedOK()) { opRes = NExtract::NOperationResult::kDataError; } else if (item.Sha1IsDefined) { Byte digest[SHA1_DIGEST_SIZE]; outStreamSha1Spec->Final(digest); if (memcmp(digest, item.Sha1, SHA1_DIGEST_SIZE) != 0) opRes = NExtract::NOperationResult::kCRCError; } } else opRes = NExtract::NOperationResult::kDataError; } } } outStreamSha1Spec->ReleaseStream(); RINOK(extractCallback->SetOperationResult(opRes)); } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) { *stream = NULL; COM_TRY_BEGIN #ifdef XAR_SHOW_RAW if (index == _files.Size()) { Create_BufInStream_WithNewBuffer(_xml, _xmlLen, stream); return S_OK; } else #endif { const CFile &item = _files[index]; if (item.HasData && item.IsCopyMethod() && item.PackSize == item.Size) return CreateLimitedInStream(_inStream, _dataStartPos + item.Offset, item.Size, stream); } return S_FALSE; COM_TRY_END } static const Byte k_Signature[] = { 'x', 'a', 'r', '!', 0, 0x1C }; REGISTER_ARC_I( "Xar", "xar pkg", 0, 0xE1, k_Signature, 0, 0, NULL) }}