Files
easy7zip/CPP/7zip/Archive/ArjHandler.cpp
Igor Pavlov 0713a3ab80 9.38
2016-05-28 00:16:53 +01:00

792 lines
18 KiB
C++

// ArjHandler.cpp
#include "StdAfx.h"
#include "../../../C/CpuArch.h"
#include "../../Common/ComTry.h"
#include "../../Common/IntToString.h"
#include "../../Common/StringConvert.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/ArjDecoder1.h"
#include "../Compress/ArjDecoder2.h"
#include "../Compress/CopyCoder.h"
#include "Common/ItemNameUtils.h"
#include "Common/OutStreamWithCRC.h"
using namespace NWindows;
#define Get16(p) GetUi16(p)
#define Get32(p) GetUi32(p)
namespace NArchive {
namespace NArj {
static const unsigned kBlockSizeMin = 30;
static const unsigned kBlockSizeMax = 2600;
static const Byte kSig0 = 0x60;
static const Byte kSig1 = 0xEA;
namespace NCompressionMethod
{
enum
{
kStored = 0,
kCompressed1a = 1,
kCompressed1b = 2,
kCompressed1c = 3,
kCompressed2 = 4,
kNoDataNoCRC = 8,
kNoData = 9
};
}
namespace NFileType
{
enum
{
kBinary = 0,
k7BitText,
kArchiveHeader,
kDirectory,
kVolumeLablel,
kChapterLabel
};
}
namespace NFlags
{
const Byte kGarbled = 1 << 0;
const Byte kAnsiPage = 1 << 1; // or (OLD_SECURED_FLAG) obsolete
const Byte kVolume = 1 << 2;
const Byte kExtFile = 1 << 3;
const Byte kPathSym = 1 << 4;
const Byte kBackup = 1 << 5; // obsolete
const Byte kSecured = 1 << 6;
const Byte kDualName = 1 << 7;
}
namespace NHostOS
{
enum EEnum
{
kMSDOS = 0, // MS-DOS, OS/2, Win32, pkarj 2.50 (FAT / VFAT / FAT32)
kPRIMOS,
kUnix,
kAMIGA,
kMac,
kOS_2,
kAPPLE_GS,
kAtari_ST,
kNext,
kVAX_VMS,
kWIN95
};
}
static const char *kHostOS[] =
{
"MSDOS"
, "PRIMOS"
, "UNIX"
, "AMIGA"
, "MAC"
, "OS/2"
, "APPLE GS"
, "ATARI ST"
, "NEXT"
, "VAX VMS"
, "WIN95"
};
struct CArcHeader
{
// Byte ArchiverVersion;
// Byte ExtractVersion;
Byte HostOS;
// Byte Flags;
// Byte SecuryVersion;
// Byte FileType;
// Byte Reserved;
UInt32 CTime;
UInt32 MTime;
UInt32 ArchiveSize;
// UInt32 SecurPos;
// UInt16 FilespecPosInFilename;
UInt16 SecurSize;
// Byte EncryptionVersion;
// Byte LastChapter;
AString Name;
AString Comment;
HRESULT Parse(const Byte *p, unsigned size);
};
API_FUNC_static_IsArc IsArc_Arj(const Byte *p, size_t size)
{
if (size < kBlockSizeMin + 4)
return k_IsArc_Res_NEED_MORE;
if (p[0] != kSig0 || p[1] != kSig1)
return k_IsArc_Res_NO;
UInt32 blockSize = Get16(p + 2);
if (blockSize < kBlockSizeMin ||
blockSize > kBlockSizeMax)
return k_IsArc_Res_NO;
p += 4;
size -= 4;
Byte headerSize = p[0];
if (headerSize < kBlockSizeMin ||
headerSize > blockSize ||
p[6] != NFileType::kArchiveHeader ||
p[28] > 8) // EncryptionVersion
return k_IsArc_Res_NO;
if (blockSize + 4 <= size)
if (Get32(p + blockSize) != CrcCalc(p, blockSize))
return k_IsArc_Res_NO;
return k_IsArc_Res_YES;
}
}
static HRESULT ReadString(const Byte *p, unsigned &size, AString &res)
{
for (unsigned i = 0; i < size;)
{
char c = (char)p[i++];
if (c == 0)
{
size = i;
res = (const char *)p;
return S_OK;
}
}
return S_FALSE;
}
HRESULT CArcHeader::Parse(const Byte *p, unsigned size)
{
Byte headerSize = p[0];
if (headerSize < kBlockSizeMin || headerSize > size)
return S_FALSE;
// ArchiverVersion = p[1];
// ExtractVersion = p[2];
HostOS = p[3];
// Flags = p[4];
// SecuryVersion = p[5];
if (p[6] != NFileType::kArchiveHeader)
return S_FALSE;
// Reserved = p[7];
CTime = Get32(p + 8);
MTime = Get32(p + 12);
ArchiveSize = Get32(p + 16); // it can be zero. (currently used only for secured archives)
// SecurPos = Get32(p + 20);
// UInt16 filespecPositionInFilename = Get16(p + 24);
SecurSize = Get16(p + 26);
// EncryptionVersion = p[28];
// LastChapter = p[29];
unsigned pos = headerSize;
unsigned size1 = size - pos;
RINOK(ReadString(p + pos, size1, Name));
pos += size1;
size1 = size - pos;
RINOK(ReadString(p + pos, size1, Comment));
pos += size1;
return S_OK;
}
struct CItem
{
AString Name;
AString Comment;
UInt32 MTime;
UInt32 PackSize;
UInt32 Size;
UInt32 FileCRC;
UInt32 SplitPos;
Byte Version;
Byte ExtractVersion;
Byte HostOS;
Byte Flags;
Byte Method;
Byte FileType;
// UInt16 FilespecPosInFilename;
UInt16 FileAccessMode;
// Byte FirstChapter;
// Byte LastChapter;
UInt64 DataPosition;
bool IsEncrypted() const { return (Flags & NFlags::kGarbled) != 0; }
bool IsDir() const { return (FileType == NFileType::kDirectory); }
bool IsSplitAfter() const { return (Flags & NFlags::kVolume) != 0; }
bool IsSplitBefore() const { return (Flags & NFlags::kExtFile) != 0; }
UInt32 GetWinAttrib() const
{
UInt32 atrrib = 0;
switch (HostOS)
{
case NHostOS::kMSDOS:
case NHostOS::kWIN95:
atrrib = FileAccessMode;
break;
}
if (IsDir())
atrrib |= FILE_ATTRIBUTE_DIRECTORY;
return atrrib;
}
HRESULT Parse(const Byte *p, unsigned size);
};
HRESULT CItem::Parse(const Byte *p, unsigned size)
{
Byte headerSize = p[0];
if (headerSize < kBlockSizeMin || headerSize > size)
return S_FALSE;
Version = p[1];
ExtractVersion = p[2];
HostOS = p[3];
Flags = p[4];
Method = p[5];
FileType = p[6];
// Reserved = p[7];
MTime = Get32(p + 8);
PackSize = Get32(p + 12);
Size = Get32(p + 16);
FileCRC = Get32(p + 20);
// FilespecPosInFilename = Get16(p + 24);
FileAccessMode = Get16(p + 26);
// FirstChapter = p[28];
// FirstChapter = p[29];
SplitPos = 0;
if (IsSplitBefore() && headerSize >= 34)
SplitPos = Get32(p + 30);
unsigned pos = headerSize;
unsigned size1 = size - pos;
RINOK(ReadString(p + pos, size1, Name));
pos += size1;
size1 = size - pos;
RINOK(ReadString(p + pos, size1, Comment));
pos += size1;
return S_OK;
}
enum EErrorType
{
k_ErrorType_OK,
k_ErrorType_Corrupted,
k_ErrorType_UnexpectedEnd,
};
class CArc
{
public:
UInt64 Processed;
EErrorType Error;
bool IsArc;
IInStream *Stream;
IArchiveOpenCallback *Callback;
UInt64 NumFiles;
CArcHeader Header;
HRESULT Open();
HRESULT GetNextItem(CItem &item, bool &filled);
void Close()
{
IsArc = false;
Error = k_ErrorType_OK;
}
private:
UInt32 _blockSize;
Byte _block[kBlockSizeMax + 4];
HRESULT ReadBlock(bool &filled, bool readSignature);
HRESULT SkipExtendedHeaders();
HRESULT Read(void *data, size_t *size);
};
HRESULT CArc::Read(void *data, size_t *size)
{
HRESULT res = ReadStream(Stream, data, size);
Processed += *size;
return res;
}
#define READ_STREAM(_dest_, _size_) \
{ size_t _processed_ = (_size_); RINOK(Read(_dest_, &_processed_)); \
if (_processed_ != (_size_)) { Error = k_ErrorType_UnexpectedEnd; return S_OK; } }
HRESULT CArc::ReadBlock(bool &filled, bool readSignature)
{
Error = k_ErrorType_OK;
filled = false;
Byte buf[4];
unsigned signSize = readSignature ? 2 : 0;
READ_STREAM(buf, signSize + 2)
if (readSignature)
if (buf[0] != kSig0 || buf[1] != kSig1)
{
Error = k_ErrorType_Corrupted;
return S_OK;
}
_blockSize = Get16(buf + signSize);
if (_blockSize == 0) // end of archive
return S_OK;
if (_blockSize < kBlockSizeMin ||
_blockSize > kBlockSizeMax)
{
Error = k_ErrorType_Corrupted;
return S_OK;
}
READ_STREAM(_block, _blockSize + 4);
if (Get32(_block + _blockSize) != CrcCalc(_block, _blockSize))
{
Error = k_ErrorType_Corrupted;
return S_OK;
}
filled = true;
return S_OK;
}
HRESULT CArc::SkipExtendedHeaders()
{
for (UInt32 i = 0;; i++)
{
bool filled;
RINOK(ReadBlock(filled, false));
if (!filled)
return S_OK;
if (Callback && (i & 0xFF) == 0)
RINOK(Callback->SetCompleted(&NumFiles, &Processed));
}
}
HRESULT CArc::Open()
{
bool filled;
RINOK(ReadBlock(filled, true));
if (!filled)
return S_FALSE;
RINOK(Header.Parse(_block, _blockSize));
IsArc = true;
return SkipExtendedHeaders();
}
HRESULT CArc::GetNextItem(CItem &item, bool &filled)
{
RINOK(ReadBlock(filled, true));
if (!filled)
return S_OK;
filled = false;
if (item.Parse(_block, _blockSize) != S_OK)
{
Error = k_ErrorType_Corrupted;
return S_OK;
}
/*
UInt32 extraData;
if ((header.Flags & NFlags::kExtFile) != 0)
extraData = GetUi32(_block + pos);
*/
RINOK(SkipExtendedHeaders());
filled = true;
return S_OK;
}
class CHandler:
public IInArchive,
public CMyUnknownImp
{
CObjectVector<CItem> _items;
CMyComPtr<IInStream> _stream;
UInt64 _phySize;
CArc _arc;
public:
MY_UNKNOWN_IMP1(IInArchive)
INTERFACE_IInArchive(;)
HRESULT Open2(IInStream *inStream, IArchiveOpenCallback *callback);
};
static const Byte kArcProps[] =
{
kpidName,
kpidCTime,
kpidMTime,
kpidHostOS,
kpidComment
};
static const Byte kProps[] =
{
kpidPath,
kpidIsDir,
kpidSize,
kpidPosition,
kpidPackSize,
kpidMTime,
kpidAttrib,
kpidEncrypted,
kpidCRC,
kpidMethod,
kpidHostOS,
kpidComment
};
IMP_IInArchive_Props
IMP_IInArchive_ArcProps
static void SetTime(UInt32 dosTime, NCOM::CPropVariant &prop)
{
if (dosTime == 0)
return;
FILETIME localFileTime, utc;
if (NTime::DosTimeToFileTime(dosTime, localFileTime))
{
if (!LocalFileTimeToFileTime(&localFileTime, &utc))
utc.dwHighDateTime = utc.dwLowDateTime = 0;
}
else
utc.dwHighDateTime = utc.dwLowDateTime = 0;
prop = utc;
}
static void SetHostOS(Byte hostOS, NCOM::CPropVariant &prop)
{
char temp[16];
const char *s = NULL;
if (hostOS < ARRAY_SIZE(kHostOS))
s = kHostOS[hostOS];
else
{
ConvertUInt32ToString(hostOS, temp);
s = temp;
}
prop = s;
}
static void SetUnicodeString(const AString &s, NCOM::CPropVariant &prop)
{
if (!s.IsEmpty())
prop = MultiByteToUnicodeString(s, CP_OEMCP);
}
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
COM_TRY_BEGIN
NCOM::CPropVariant prop;
switch (propID)
{
case kpidPhySize: prop = _phySize; break;
case kpidName: SetUnicodeString(_arc.Header.Name, prop); break;
case kpidCTime: SetTime(_arc.Header.CTime, prop); break;
case kpidMTime: SetTime(_arc.Header.MTime, prop); break;
case kpidHostOS: SetHostOS(_arc.Header.HostOS, prop); break;
case kpidComment: SetUnicodeString(_arc.Header.Comment, prop); break;
case kpidErrorFlags:
{
UInt32 v = 0;
if (!_arc.IsArc) v |= kpv_ErrorFlags_IsNotArc;
switch (_arc.Error)
{
case k_ErrorType_UnexpectedEnd: v |= kpv_ErrorFlags_UnexpectedEnd; break;
case k_ErrorType_Corrupted: v |= kpv_ErrorFlags_HeadersError; break;
}
prop = v;
break;
}
}
prop.Detach(value);
return S_OK;
COM_TRY_END
}
STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
{
*numItems = _items.Size();
return S_OK;
}
STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
{
COM_TRY_BEGIN
NCOM::CPropVariant prop;
const CItem &item = _items[index];
switch (propID)
{
case kpidPath: prop = NItemName::GetOSName(MultiByteToUnicodeString(item.Name, CP_OEMCP)); break;
case kpidIsDir: prop = item.IsDir(); break;
case kpidSize: prop = item.Size; break;
case kpidPackSize: prop = item.PackSize; break;
case kpidPosition: if (item.IsSplitBefore() || item.IsSplitAfter()) prop = (UInt64)item.SplitPos; break;
case kpidAttrib: prop = item.GetWinAttrib(); break;
case kpidEncrypted: prop = item.IsEncrypted(); break;
case kpidCRC: prop = item.FileCRC; break;
case kpidMethod: prop = item.Method; break;
case kpidHostOS: SetHostOS(item.HostOS, prop); break;
case kpidMTime: SetTime(item.MTime, prop); break;
case kpidComment: SetUnicodeString(item.Comment, prop); break;
}
prop.Detach(value);
return S_OK;
COM_TRY_END
}
HRESULT CHandler::Open2(IInStream *inStream, IArchiveOpenCallback *callback)
{
Close();
UInt64 endPos = 0;
RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos));
RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL));
_arc.Stream = inStream;
_arc.Callback = callback;
_arc.NumFiles = 0;
_arc.Processed = 0;
RINOK(_arc.Open());
_phySize = _arc.Processed;
if (_arc.Header.ArchiveSize != 0)
_phySize = (UInt64)_arc.Header.ArchiveSize + _arc.Header.SecurSize;
for (;;)
{
CItem item;
bool filled;
_arc.Error = k_ErrorType_OK;
RINOK(_arc.GetNextItem(item, filled));
if (_arc.Error != k_ErrorType_OK)
break;
if (!filled)
{
if (_arc.Error == k_ErrorType_OK)
if (_arc.Header.ArchiveSize == 0)
_phySize = _arc.Processed;
break;
}
item.DataPosition = _arc.Processed;
_items.Add(item);
UInt64 pos = item.DataPosition + item.PackSize;
if (_arc.Header.ArchiveSize == 0)
_phySize = pos;
if (pos > endPos)
{
_arc.Error = k_ErrorType_UnexpectedEnd;
break;
}
RINOK(inStream->Seek(pos, STREAM_SEEK_SET, NULL));
_arc.NumFiles = _items.Size();
_arc.Processed = pos;
if (callback && (_items.Size() & 0xFF) == 0)
{
RINOK(callback->SetCompleted(&_arc.NumFiles, &_arc.Processed));
}
}
return S_OK;
}
STDMETHODIMP CHandler::Open(IInStream *inStream,
const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback)
{
COM_TRY_BEGIN
HRESULT res;
{
res = Open2(inStream, callback);
if (res == S_OK)
{
_stream = inStream;
return S_OK;
}
}
return res;
COM_TRY_END
}
STDMETHODIMP CHandler::Close()
{
_arc.Close();
_phySize = 0;
_items.Clear();
_stream.Release();
return S_OK;
}
STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
Int32 testMode, IArchiveExtractCallback *extractCallback)
{
COM_TRY_BEGIN
UInt64 totalUnpacked = 0, totalPacked = 0;
bool allFilesMode = (numItems == (UInt32)(Int32)-1);
if (allFilesMode)
numItems = _items.Size();
if (numItems == 0)
return S_OK;
UInt32 i;
for (i = 0; i < numItems; i++)
{
const CItem &item = _items[allFilesMode ? i : indices[i]];
totalUnpacked += item.Size;
totalPacked += item.PackSize;
}
extractCallback->SetTotal(totalUnpacked);
totalUnpacked = totalPacked = 0;
UInt64 curUnpacked, curPacked;
CMyComPtr<ICompressCoder> arj1Decoder;
CMyComPtr<ICompressCoder> arj2Decoder;
NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
CLocalProgress *lps = new CLocalProgress;
CMyComPtr<ICompressProgressInfo> progress = lps;
lps->Init(extractCallback, false);
CLimitedSequentialInStream *inStreamSpec = new CLimitedSequentialInStream;
CMyComPtr<ISequentialInStream> inStream(inStreamSpec);
inStreamSpec->SetStream(_stream);
for (i = 0; i < numItems; i++, totalUnpacked += curUnpacked, totalPacked += curPacked)
{
lps->InSize = totalPacked;
lps->OutSize = totalUnpacked;
RINOK(lps->SetCur());
curUnpacked = curPacked = 0;
CMyComPtr<ISequentialOutStream> realOutStream;
Int32 askMode = testMode ?
NExtract::NAskMode::kTest :
NExtract::NAskMode::kExtract;
Int32 index = allFilesMode ? i : indices[i];
const CItem &item = _items[index];
RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
if (item.IsDir())
{
// if (!testMode)
{
RINOK(extractCallback->PrepareOperation(askMode));
RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
}
continue;
}
if (!testMode && !realOutStream)
continue;
RINOK(extractCallback->PrepareOperation(askMode));
curUnpacked = item.Size;
curPacked = item.PackSize;
{
COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC;
CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
outStreamSpec->SetStream(realOutStream);
realOutStream.Release();
outStreamSpec->Init();
inStreamSpec->Init(item.PackSize);
UInt64 pos;
_stream->Seek(item.DataPosition, STREAM_SEEK_SET, &pos);
HRESULT result = S_OK;
Int32 opRes = NExtract::NOperationResult::kOK;
if (item.IsEncrypted())
opRes = NExtract::NOperationResult::kUnsupportedMethod;
else
{
switch (item.Method)
{
case NCompressionMethod::kStored:
{
result = copyCoder->Code(inStream, outStream, NULL, NULL, progress);
if (result == S_OK && copyCoderSpec->TotalSize != item.PackSize)
result = S_FALSE;
break;
}
case NCompressionMethod::kCompressed1a:
case NCompressionMethod::kCompressed1b:
case NCompressionMethod::kCompressed1c:
{
if (!arj1Decoder)
arj1Decoder = new NCompress::NArj::NDecoder1::CCoder;
result = arj1Decoder->Code(inStream, outStream, NULL, &curUnpacked, progress);
break;
}
case NCompressionMethod::kCompressed2:
{
if (!arj2Decoder)
arj2Decoder = new NCompress::NArj::NDecoder2::CCoder;
result = arj2Decoder->Code(inStream, outStream, NULL, &curUnpacked, progress);
break;
}
default:
opRes = NExtract::NOperationResult::kUnsupportedMethod;
}
}
if (opRes == NExtract::NOperationResult::kOK)
{
if (result == S_FALSE)
opRes = NExtract::NOperationResult::kDataError;
else
{
RINOK(result);
opRes = (outStreamSpec->GetCRC() == item.FileCRC) ?
NExtract::NOperationResult::kOK:
NExtract::NOperationResult::kCRCError;
}
}
outStream.Release();
RINOK(extractCallback->SetOperationResult(opRes));
}
}
return S_OK;
COM_TRY_END
}
IMP_CreateArcIn
static CArcInfo g_ArcInfo =
{ "Arj", "arj", 0, 4,
2, { kSig0, kSig1 },
0,
0,
CreateArc, NULL, IsArc_Arj };
REGISTER_ARC(Arj)
}}