| // XarHandler.cpp |
| |
| #include "StdAfx.h" |
| |
| #include "../../../C/CpuArch.h" |
| |
| #include "Common/ComTry.h" |
| #include "Common/MyXml.h" |
| #include "Common/StringToInt.h" |
| #include "Common/UTFConvert.h" |
| |
| #include "Windows/PropVariant.h" |
| #include "Windows/Time.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" |
| |
| #define XAR_SHOW_RAW |
| |
| #define Get16(p) GetBe16(p) |
| #define Get32(p) GetBe32(p) |
| #define Get64(p) GetBe64(p) |
| |
| namespace NArchive { |
| namespace NXar { |
| |
| struct CFile |
| { |
| AString Name; |
| AString Method; |
| UInt64 Size; |
| UInt64 PackSize; |
| UInt64 Offset; |
| |
| // UInt32 mode; |
| UInt64 CTime; |
| UInt64 MTime; |
| UInt64 ATime; |
| |
| bool IsDir; |
| bool HasData; |
| |
| bool Sha1IsDefined; |
| Byte Sha1[20]; |
| // bool packSha1IsDefined; |
| // Byte packSha1[20]; |
| |
| int Parent; |
| |
| CFile(): IsDir(false), HasData(false), Sha1IsDefined(false), |
| /* packSha1IsDefined(false), */ |
| Parent(-1), Size(0), PackSize(0), CTime(0), MTime(0), ATime(0) {} |
| }; |
| |
| class CHandler: |
| public IInArchive, |
| public CMyUnknownImp |
| { |
| UInt64 _dataStartPos; |
| CMyComPtr<IInStream> _inStream; |
| AString _xml; |
| CObjectVector<CFile> _files; |
| |
| HRESULT Open2(IInStream *stream); |
| HRESULT Extract(IInStream *stream); |
| public: |
| MY_UNKNOWN_IMP1(IInArchive) |
| INTERFACE_IInArchive(;) |
| }; |
| |
| const UInt32 kXmlSizeMax = ((UInt32)1 << 30) - (1 << 14); |
| |
| STATPROPSTG kProps[] = |
| { |
| { NULL, kpidPath, VT_BSTR}, |
| { NULL, kpidSize, VT_UI8}, |
| { NULL, kpidPackSize, VT_UI8}, |
| { NULL, kpidMTime, VT_FILETIME}, |
| { NULL, kpidCTime, VT_FILETIME}, |
| { NULL, kpidATime, VT_FILETIME}, |
| { NULL, kpidMethod, VT_BSTR} |
| }; |
| |
| IMP_IInArchive_Props |
| IMP_IInArchive_ArcProps_NO |
| |
| static bool ParseNumber(const char *s, int size, UInt32 &res) |
| { |
| const char *end; |
| res = (UInt32)ConvertStringToUInt64(s, &end); |
| return (end - s == size); |
| } |
| |
| static bool ParseUInt64(const CXmlItem &item, const char *name, UInt64 &res) |
| { |
| AString s = item.GetSubStringForTag(name); |
| const char *end; |
| res = ConvertStringToUInt64(s, &end); |
| return (end - (const char *)s == s.Length()); |
| } |
| |
| static UInt64 ParseTime(const CXmlItem &item, const char *name) |
| { |
| AString s = item.GetSubStringForTag(name); |
| if (s.Length() < 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; |
| if (!ParseNumber(p, 4, year )) return 0; |
| if (!ParseNumber(p + 5, 2, month)) return 0; |
| if (!ParseNumber(p + 8, 2, day )) return 0; |
| if (!ParseNumber(p + 11, 2, hour )) return 0; |
| if (!ParseNumber(p + 14, 2, min )) return 0; |
| if (!ParseNumber(p + 17, 2, sec )) return 0; |
| |
| UInt64 numSecs; |
| if (!NWindows::NTime::GetSecondsSince1601(year, month, day, hour, min, sec, numSecs)) |
| return 0; |
| return numSecs * 10000000; |
| } |
| |
| static bool HexToByte(char c, Byte &res) |
| { |
| if (c >= '0' && c <= '9') res = c - '0'; |
| else if (c >= 'A' && c <= 'F') res = c - 'A' + 10; |
| else if (c >= 'a' && c <= 'f') res = c - 'a' + 10; |
| else return false; |
| return true; |
| } |
| |
| #define METHOD_NAME_ZLIB "zlib" |
| |
| 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]; |
| AString style = checkItem.GetPropertyValue("style"); |
| if (style == "SHA1") |
| { |
| AString s = checkItem.GetSubString(); |
| if (s.Length() != 40) |
| return false; |
| for (int i = 0; i < s.Length(); i += 2) |
| { |
| Byte b0, b1; |
| if (!HexToByte(s[i], b0) || !HexToByte(s[i + 1], b1)) |
| return false; |
| digest[i / 2] = (b0 << 4) | b1; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| static bool AddItem(const CXmlItem &item, CObjectVector<CFile> &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.GetPropertyValue("style"); |
| if (s.Length() >= 0) |
| { |
| AString appl = "application/"; |
| if (s.Left(appl.Length()) == appl) |
| { |
| s = s.Mid(appl.Length()); |
| AString xx = "x-"; |
| if (s.Left(xx.Length()) == xx) |
| { |
| s = s.Mid(xx.Length()); |
| 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"); |
| files.Add(file); |
| } |
| for (int i = 0; i < item.SubItems.Size(); i++) |
| if (!AddItem(item.SubItems[i], files, parent)) |
| return false; |
| return true; |
| } |
| |
| HRESULT CHandler::Open2(IInStream *stream) |
| { |
| UInt64 archiveStartPos; |
| RINOK(stream->Seek(0, STREAM_SEEK_SET, &archiveStartPos)); |
| |
| const UInt32 kHeaderSize = 0x1C; |
| Byte buf[kHeaderSize]; |
| RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)); |
| |
| UInt32 size = Get16(buf + 4); |
| // UInt32 ver = Get16(buf + 6); // == 0 |
| if (Get32(buf) != 0x78617221 || size != kHeaderSize) |
| return S_FALSE; |
| |
| UInt64 packSize = Get64(buf + 8); |
| UInt64 unpackSize = Get64(buf + 0x10); |
| // UInt32 checkSumAlogo = Get32(buf + 0x18); |
| |
| if (unpackSize >= kXmlSizeMax) |
| return S_FALSE; |
| |
| _dataStartPos = archiveStartPos + kHeaderSize + packSize; |
| |
| char *ss = _xml.GetBuffer((int)unpackSize + 1); |
| |
| NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder(); |
| CMyComPtr<ICompressCoder> zlibCoder = zlibCoderSpec; |
| |
| CLimitedSequentialInStream *inStreamLimSpec = new CLimitedSequentialInStream; |
| CMyComPtr<ISequentialInStream> inStreamLim(inStreamLimSpec); |
| inStreamLimSpec->SetStream(stream); |
| inStreamLimSpec->Init(packSize); |
| |
| CBufPtrSeqOutStream *outStreamLimSpec = new CBufPtrSeqOutStream; |
| CMyComPtr<ISequentialOutStream> outStreamLim(outStreamLimSpec); |
| outStreamLimSpec->Init((Byte *)ss, (size_t)unpackSize); |
| |
| RINOK(zlibCoder->Code(inStreamLim, outStreamLim, NULL, NULL, NULL)); |
| |
| if (outStreamLimSpec->GetPos() != (size_t)unpackSize) |
| return S_FALSE; |
| |
| ss[(size_t)unpackSize] = 0; |
| _xml.ReleaseBuffer(); |
| |
| CXml xml; |
| if (!xml.Parse(_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; |
| 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() |
| { |
| _inStream.Release(); |
| _files.Clear(); |
| _xml.Empty(); |
| 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, NWindows::NCOM::CPropVariant &prop) |
| { |
| if (t != 0) |
| { |
| FILETIME ft; |
| ft.dwLowDateTime = (UInt32)(t); |
| ft.dwHighDateTime = (UInt32)(t >> 32); |
| prop = ft; |
| } |
| } |
| |
| STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) |
| { |
| COM_TRY_BEGIN |
| NWindows::NCOM::CPropVariant prop; |
| |
| #ifdef XAR_SHOW_RAW |
| if ((int)index == _files.Size()) |
| { |
| switch(propID) |
| { |
| case kpidPath: prop = L"[TOC].xml"; break; |
| case kpidSize: |
| case kpidPackSize: prop = (UInt64)_xml.Length(); break; |
| } |
| } |
| else |
| #endif |
| { |
| const CFile &item = _files[index]; |
| switch(propID) |
| { |
| case kpidMethod: |
| { |
| UString name; |
| if (!item.Method.IsEmpty() && ConvertUTF8ToUnicode(item.Method, name)) |
| prop = name; |
| break; |
| } |
| case kpidPath: |
| { |
| AString path; |
| int cur = index; |
| do |
| { |
| const CFile &item = _files[cur]; |
| AString s = item.Name; |
| if (s.IsEmpty()) |
| s = "unknown"; |
| if (path.IsEmpty()) |
| path = s; |
| else |
| path = s + CHAR_PATH_SEPARATOR + path; |
| cur = item.Parent; |
| } |
| while (cur >= 0); |
| |
| UString name; |
| if (ConvertUTF8ToUnicode(path, name)) |
| prop = name; |
| 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; |
| } |
| } |
| 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)-1); |
| if (allFilesMode) |
| numItems = _files.Size(); |
| if (numItems == 0) |
| return S_OK; |
| UInt64 totalSize = 0; |
| UInt32 i; |
| for (i = 0; i < numItems; i++) |
| { |
| int index = (int)(allFilesMode ? i : indices[i]); |
| #ifdef XAR_SHOW_RAW |
| if (index == _files.Size()) |
| totalSize += _xml.Length(); |
| 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; |
| zeroBuf.SetCapacity(kZeroBufSize); |
| memset(zeroBuf, 0, kZeroBufSize); |
| |
| NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); |
| CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec; |
| |
| NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder(); |
| CMyComPtr<ICompressCoder> zlibCoder = zlibCoderSpec; |
| |
| NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder(); |
| CMyComPtr<ICompressCoder> bzip2Coder = bzip2CoderSpec; |
| |
| NCompress::NDeflate::NDecoder::CCOMCoder *deflateCoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder(); |
| CMyComPtr<ICompressCoder> deflateCoder = deflateCoderSpec; |
| |
| CLocalProgress *lps = new CLocalProgress; |
| CMyComPtr<ICompressProgressInfo> progress = lps; |
| lps->Init(extractCallback, false); |
| |
| CLimitedSequentialInStream *inStreamSpec = new CLimitedSequentialInStream; |
| CMyComPtr<ISequentialInStream> inStream(inStreamSpec); |
| inStreamSpec->SetStream(_inStream); |
| |
| |
| CLimitedSequentialOutStream *outStreamLimSpec = new CLimitedSequentialOutStream; |
| CMyComPtr<ISequentialOutStream> outStream(outStreamLimSpec); |
| |
| COutStreamWithSha1 *outStreamSha1Spec = new COutStreamWithSha1; |
| { |
| CMyComPtr<ISequentialOutStream> 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<ISequentialOutStream> realOutStream; |
| Int32 askMode = testMode ? |
| NExtract::NAskMode::kTest : |
| NExtract::NAskMode::kExtract; |
| Int32 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(_xml.Length()); |
| RINOK(WriteStream(outStream, (const char *)_xml, _xml.Length())); |
| currentPackSize = currentUnpSize = _xml.Length(); |
| } |
| 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.Method.IsEmpty() || item.Method == "octet-stream") |
| 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[NCrypto::NSha1::kDigestSize]; |
| outStreamSha1Spec->Final(digest); |
| if (memcmp(digest, item.Sha1, NCrypto::NSha1::kDigestSize) != 0) |
| opRes = NExtract::NOperationResult::kCRCError; |
| } |
| } |
| else |
| opRes = NExtract::NOperationResult::kDataError; |
| } |
| } |
| } |
| outStreamSha1Spec->ReleaseStream(); |
| RINOK(extractCallback->SetOperationResult(opRes)); |
| } |
| return S_OK; |
| COM_TRY_END |
| } |
| |
| static IInArchive *CreateArc() { return new NArchive::NXar::CHandler; } |
| |
| static CArcInfo g_ArcInfo = |
| { L"Xar", L"xar", 0, 0xE1, { 'x', 'a', 'r', '!', 0, 0x1C }, 6, false, CreateArc, 0 }; |
| |
| REGISTER_ARC(Xar) |
| |
| }} |