diff options
Diffstat (limited to 'CPP/7zip/Archive/XarHandler.cpp')
-rwxr-xr-x | CPP/7zip/Archive/XarHandler.cpp | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/CPP/7zip/Archive/XarHandler.cpp b/CPP/7zip/Archive/XarHandler.cpp new file mode 100755 index 00000000..1069d15d --- /dev/null +++ b/CPP/7zip/Archive/XarHandler.cpp @@ -0,0 +1,586 @@ +// 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/BZip2/BZip2Decoder.h" +#include "../Compress/Copy/CopyCoder.h" +#include "../Compress/Deflate/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"); + 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); + + CSequentialOutStreamImp2 *outStreamLimSpec = new CSequentialOutStreamImp2; + 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 (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 _aTestMode, IArchiveExtractCallback *extractCallback) +{ + COM_TRY_BEGIN + bool testMode = (_aTestMode != 0); + 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 ? + NArchive::NExtract::NAskMode::kTest : + NArchive::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(NArchive::NExtract::NOperationResult::kOK)); + continue; + } + } + + if (!testMode && (!realOutStream)) + continue; + RINOK(extractCallback->PrepareOperation(askMode)); + + outStreamSha1Spec->SetStream(realOutStream); + realOutStream.Release(); + + Int32 opRes = NArchive::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 == "octet-stream") + if (item.PackSize == item.Size) + coder = copyCoder; + else + opRes = NArchive::NExtract::NOperationResult::kUnSupportedMethod; + else if (item.Method == METHOD_NAME_ZLIB) + coder = zlibCoder; + else if (item.Method == "bzip2") + coder = bzip2Coder; + else + opRes = NArchive::NExtract::NOperationResult::kUnSupportedMethod; + + if (coder) + res = coder->Code(inStream, outStream, NULL, NULL, progress); + + if (res != S_OK) + { + if (!outStreamLimSpec->IsFinishedOK()) + opRes = NArchive::NExtract::NOperationResult::kDataError; + else if (res != S_FALSE) + return res; + if (opRes == NArchive::NExtract::NOperationResult::kOK) + opRes = NArchive::NExtract::NOperationResult::kDataError; + } + + if (opRes == NArchive::NExtract::NOperationResult::kOK) + { + if (outStreamLimSpec->IsFinishedOK() && + outStreamSha1Spec->GetSize() == item.Size) + { + if (!outStreamLimSpec->IsFinishedOK()) + { + opRes = NArchive::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 = NArchive::NExtract::NOperationResult::kCRCError; + } + } + else + opRes = NArchive::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) + +}} |