423 lines
10 KiB
C++
423 lines
10 KiB
C++
|
// TarIn.cpp
|
||
|
|
||
|
#include "StdAfx.h"
|
||
|
|
||
|
#include "../../../../C/CpuArch.h"
|
||
|
|
||
|
#include "../../../Common/StringToInt.h"
|
||
|
|
||
|
#include "../../Common/StreamUtils.h"
|
||
|
|
||
|
#include "../IArchive.h"
|
||
|
|
||
|
#include "TarIn.h"
|
||
|
|
||
|
namespace NArchive {
|
||
|
namespace NTar {
|
||
|
|
||
|
static void MyStrNCpy(char *dest, const char *src, unsigned size)
|
||
|
{
|
||
|
for (unsigned i = 0; i < size; i++)
|
||
|
{
|
||
|
char c = src[i];
|
||
|
dest[i] = c;
|
||
|
if (c == 0)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static bool OctalToNumber(const char *srcString, unsigned size, UInt64 &res)
|
||
|
{
|
||
|
char sz[32];
|
||
|
MyStrNCpy(sz, srcString, size);
|
||
|
sz[size] = 0;
|
||
|
const char *end;
|
||
|
unsigned i;
|
||
|
for (i = 0; sz[i] == ' '; i++);
|
||
|
res = ConvertOctStringToUInt64(sz + i, &end);
|
||
|
if (end == sz + i)
|
||
|
return false;
|
||
|
return (*end == ' ' || *end == 0);
|
||
|
}
|
||
|
|
||
|
static bool OctalToNumber32(const char *srcString, unsigned size, UInt32 &res)
|
||
|
{
|
||
|
UInt64 res64;
|
||
|
if (!OctalToNumber(srcString, size, res64))
|
||
|
return false;
|
||
|
res = (UInt32)res64;
|
||
|
return (res64 <= 0xFFFFFFFF);
|
||
|
}
|
||
|
|
||
|
#define RIF(x) { if (!(x)) return S_OK; }
|
||
|
|
||
|
/*
|
||
|
static bool IsEmptyData(const char *buf, size_t size)
|
||
|
{
|
||
|
for (unsigned i = 0; i < size; i++)
|
||
|
if (buf[i] != 0)
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
static bool IsRecordLast(const char *buf)
|
||
|
{
|
||
|
for (unsigned i = 0; i < NFileHeader::kRecordSize; i++)
|
||
|
if (buf[i] != 0)
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static void ReadString(const char *s, unsigned size, AString &result)
|
||
|
{
|
||
|
char temp[NFileHeader::kRecordSize + 1];
|
||
|
MyStrNCpy(temp, s, size);
|
||
|
temp[size] = '\0';
|
||
|
result = temp;
|
||
|
}
|
||
|
|
||
|
static bool ParseInt64(const char *p, Int64 &val)
|
||
|
{
|
||
|
UInt32 h = GetBe32(p);
|
||
|
val = GetBe64(p + 4);
|
||
|
if (h == (UInt32)1 << 31)
|
||
|
return ((val >> 63) & 1) == 0;
|
||
|
if (h == (UInt32)(Int32)-1)
|
||
|
return ((val >> 63) & 1) != 0;
|
||
|
UInt64 uv;
|
||
|
bool res = OctalToNumber(p, 12, uv);
|
||
|
val = uv;
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static bool ParseSize(const char *p, UInt64 &val)
|
||
|
{
|
||
|
if (GetBe32(p) == (UInt32)1 << 31)
|
||
|
{
|
||
|
// GNU extension
|
||
|
val = GetBe64(p + 4);
|
||
|
return ((val >> 63) & 1) == 0;
|
||
|
}
|
||
|
return OctalToNumber(p, 12, val);
|
||
|
}
|
||
|
|
||
|
#define CHECK(x) { if (!(x)) return k_IsArc_Res_NO; }
|
||
|
|
||
|
API_FUNC_IsArc IsArc_Tar(const Byte *p2, size_t size)
|
||
|
{
|
||
|
if (size < NFileHeader::kRecordSize)
|
||
|
return k_IsArc_Res_NEED_MORE;
|
||
|
|
||
|
const char *p = (const char *)p2;
|
||
|
p += NFileHeader::kNameSize;
|
||
|
|
||
|
UInt32 mode;
|
||
|
CHECK(OctalToNumber32(p, 8, mode)); p += 8;
|
||
|
|
||
|
// if (!OctalToNumber32(p, 8, item.UID)) item.UID = 0;
|
||
|
p += 8;
|
||
|
// if (!OctalToNumber32(p, 8, item.GID)) item.GID = 0;
|
||
|
p += 8;
|
||
|
|
||
|
UInt64 packSize;
|
||
|
Int64 time;
|
||
|
UInt32 checkSum;
|
||
|
CHECK(ParseSize(p, packSize)); p += 12;
|
||
|
CHECK(ParseInt64(p, time)); p += 12;
|
||
|
CHECK(OctalToNumber32(p, 8, checkSum));
|
||
|
return k_IsArc_Res_YES;
|
||
|
}
|
||
|
|
||
|
static HRESULT GetNextItemReal(ISequentialInStream *stream, bool &filled, CItemEx &item, EErrorType &error)
|
||
|
{
|
||
|
char buf[NFileHeader::kRecordSize];
|
||
|
char *p = buf;
|
||
|
|
||
|
error = k_ErrorType_OK;
|
||
|
filled = false;
|
||
|
|
||
|
bool thereAreEmptyRecords = false;
|
||
|
for (;;)
|
||
|
{
|
||
|
size_t processedSize = NFileHeader::kRecordSize;
|
||
|
RINOK(ReadStream(stream, buf, &processedSize));
|
||
|
if (processedSize == 0)
|
||
|
{
|
||
|
if (!thereAreEmptyRecords)
|
||
|
error = k_ErrorType_UnexpectedEnd; // "There are no trailing zero-filled records";
|
||
|
return S_OK;
|
||
|
}
|
||
|
if (processedSize != NFileHeader::kRecordSize)
|
||
|
{
|
||
|
if (!thereAreEmptyRecords)
|
||
|
error = k_ErrorType_UnexpectedEnd; // error = "There is no correct record at the end of archive";
|
||
|
else
|
||
|
{
|
||
|
/*
|
||
|
if (IsEmptyData(buf, processedSize))
|
||
|
error = k_ErrorType_UnexpectedEnd;
|
||
|
else
|
||
|
{
|
||
|
// extraReadSize = processedSize;
|
||
|
// error = k_ErrorType_Corrupted; // some data after the end tail zeros
|
||
|
}
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
if (!IsRecordLast(buf))
|
||
|
break;
|
||
|
item.HeaderSize += NFileHeader::kRecordSize;
|
||
|
thereAreEmptyRecords = true;
|
||
|
}
|
||
|
if (thereAreEmptyRecords)
|
||
|
{
|
||
|
// error = "There are data after end of archive";
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
error = k_ErrorType_Corrupted;
|
||
|
ReadString(p, NFileHeader::kNameSize, item.Name); p += NFileHeader::kNameSize;
|
||
|
item.NameCouldBeReduced =
|
||
|
(item.Name.Len() == NFileHeader::kNameSize ||
|
||
|
item.Name.Len() == NFileHeader::kNameSize - 1);
|
||
|
|
||
|
RIF(OctalToNumber32(p, 8, item.Mode)); p += 8;
|
||
|
|
||
|
if (!OctalToNumber32(p, 8, item.UID)) item.UID = 0; p += 8;
|
||
|
if (!OctalToNumber32(p, 8, item.GID)) item.GID = 0; p += 8;
|
||
|
|
||
|
RIF(ParseSize(p, item.PackSize));
|
||
|
item.Size = item.PackSize;
|
||
|
p += 12;
|
||
|
RIF(ParseInt64(p, item.MTime)); p += 12;
|
||
|
|
||
|
UInt32 checkSum;
|
||
|
RIF(OctalToNumber32(p, 8, checkSum));
|
||
|
memset(p, ' ', 8); p += 8;
|
||
|
|
||
|
item.LinkFlag = *p++;
|
||
|
|
||
|
ReadString(p, NFileHeader::kNameSize, item.LinkName); p += NFileHeader::kNameSize;
|
||
|
item.LinkNameCouldBeReduced =
|
||
|
(item.LinkName.Len() == NFileHeader::kNameSize ||
|
||
|
item.LinkName.Len() == NFileHeader::kNameSize - 1);
|
||
|
|
||
|
memcpy(item.Magic, p, 8); p += 8;
|
||
|
|
||
|
ReadString(p, NFileHeader::kUserNameSize, item.User); p += NFileHeader::kUserNameSize;
|
||
|
ReadString(p, NFileHeader::kGroupNameSize, item.Group); p += NFileHeader::kGroupNameSize;
|
||
|
|
||
|
item.DeviceMajorDefined = (p[0] != 0); if (item.DeviceMajorDefined) { RIF(OctalToNumber32(p, 8, item.DeviceMajor)); } p += 8;
|
||
|
item.DeviceMinorDefined = (p[0] != 0); if (item.DeviceMinorDefined) { RIF(OctalToNumber32(p, 8, item.DeviceMinor)); } p += 8;
|
||
|
|
||
|
if (p[0] != 0)
|
||
|
{
|
||
|
AString prefix;
|
||
|
ReadString(p, NFileHeader::kPrefixSize, prefix);
|
||
|
if (!prefix.IsEmpty()
|
||
|
&& item.IsUstarMagic()
|
||
|
&& (item.LinkFlag != 'L' /* || prefix != "00000000000" */ ))
|
||
|
item.Name = prefix + '/' + item.Name;
|
||
|
}
|
||
|
|
||
|
p += NFileHeader::kPrefixSize;
|
||
|
|
||
|
if (item.LinkFlag == NFileHeader::NLinkFlag::kHardLink)
|
||
|
{
|
||
|
item.PackSize = 0;
|
||
|
item.Size = 0;
|
||
|
}
|
||
|
/*
|
||
|
TAR standard requires sum of unsigned byte values.
|
||
|
But some TAR programs use sum of signed byte values.
|
||
|
So we check both values.
|
||
|
*/
|
||
|
UInt32 checkSumReal = 0;
|
||
|
Int32 checkSumReal_Signed = 0;
|
||
|
for (unsigned i = 0; i < NFileHeader::kRecordSize; i++)
|
||
|
{
|
||
|
char c = buf[i];
|
||
|
checkSumReal_Signed += (signed char)c;
|
||
|
checkSumReal += (Byte)buf[i];
|
||
|
}
|
||
|
|
||
|
if (checkSumReal != checkSum)
|
||
|
{
|
||
|
if ((UInt32)checkSumReal_Signed != checkSum)
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
item.HeaderSize += NFileHeader::kRecordSize;
|
||
|
|
||
|
if (item.LinkFlag == NFileHeader::NLinkFlag::kSparse)
|
||
|
{
|
||
|
Byte isExtended = buf[482];
|
||
|
if (isExtended != 0 && isExtended != 1)
|
||
|
return S_OK;
|
||
|
RIF(ParseSize(buf + 483, item.Size));
|
||
|
UInt64 min = 0;
|
||
|
for (unsigned i = 0; i < 4; i++)
|
||
|
{
|
||
|
p = buf + 386 + 24 * i;
|
||
|
if (GetBe32(p) == 0)
|
||
|
{
|
||
|
if (isExtended != 0)
|
||
|
return S_OK;
|
||
|
break;
|
||
|
}
|
||
|
CSparseBlock sb;
|
||
|
RIF(ParseSize(p, sb.Offset));
|
||
|
RIF(ParseSize(p + 12, sb.Size));
|
||
|
item.SparseBlocks.Add(sb);
|
||
|
if (sb.Offset < min || sb.Offset > item.Size)
|
||
|
return S_OK;
|
||
|
if ((sb.Offset & 0x1FF) != 0 || (sb.Size & 0x1FF) != 0)
|
||
|
return S_OK;
|
||
|
min = sb.Offset + sb.Size;
|
||
|
if (min < sb.Offset)
|
||
|
return S_OK;
|
||
|
}
|
||
|
if (min > item.Size)
|
||
|
return S_OK;
|
||
|
|
||
|
while (isExtended != 0)
|
||
|
{
|
||
|
size_t processedSize = NFileHeader::kRecordSize;
|
||
|
RINOK(ReadStream(stream, buf, &processedSize));
|
||
|
if (processedSize != NFileHeader::kRecordSize)
|
||
|
{
|
||
|
error = k_ErrorType_UnexpectedEnd;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
item.HeaderSize += NFileHeader::kRecordSize;
|
||
|
isExtended = buf[21 * 24];
|
||
|
if (isExtended != 0 && isExtended != 1)
|
||
|
return S_OK;
|
||
|
for (unsigned i = 0; i < 21; i++)
|
||
|
{
|
||
|
p = buf + 24 * i;
|
||
|
if (GetBe32(p) == 0)
|
||
|
{
|
||
|
if (isExtended != 0)
|
||
|
return S_OK;
|
||
|
break;
|
||
|
}
|
||
|
CSparseBlock sb;
|
||
|
RIF(ParseSize(p, sb.Offset));
|
||
|
RIF(ParseSize(p + 12, sb.Size));
|
||
|
item.SparseBlocks.Add(sb);
|
||
|
if (sb.Offset < min || sb.Offset > item.Size)
|
||
|
return S_OK;
|
||
|
if ((sb.Offset & 0x1FF) != 0 || (sb.Size & 0x1FF) != 0)
|
||
|
return S_OK;
|
||
|
min = sb.Offset + sb.Size;
|
||
|
if (min < sb.Offset)
|
||
|
return S_OK;
|
||
|
}
|
||
|
}
|
||
|
if (min > item.Size)
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
filled = true;
|
||
|
error = k_ErrorType_OK;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
HRESULT ReadItem(ISequentialInStream *stream, bool &filled, CItemEx &item, EErrorType &error)
|
||
|
{
|
||
|
item.HeaderSize = 0;
|
||
|
bool flagL = false;
|
||
|
bool flagK = false;
|
||
|
AString nameL;
|
||
|
AString nameK;
|
||
|
|
||
|
for (;;)
|
||
|
{
|
||
|
RINOK(GetNextItemReal(stream, filled, item, error));
|
||
|
if (!filled)
|
||
|
{
|
||
|
if (error == k_ErrorType_OK && (flagL || flagK))
|
||
|
error = k_ErrorType_Corrupted;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
if (error != k_ErrorType_OK)
|
||
|
return S_OK;
|
||
|
|
||
|
if (item.LinkFlag == NFileHeader::NLinkFlag::kGnu_LongName || // file contains a long name
|
||
|
item.LinkFlag == NFileHeader::NLinkFlag::kGnu_LongLink) // file contains a long linkname
|
||
|
{
|
||
|
AString *name;
|
||
|
if (item.LinkFlag == NFileHeader::NLinkFlag::kGnu_LongName)
|
||
|
{ if (flagL) return S_OK; flagL = true; name = &nameL; }
|
||
|
else
|
||
|
{ if (flagK) return S_OK; flagK = true; name = &nameK; }
|
||
|
|
||
|
if (item.Name != NFileHeader::kLongLink &&
|
||
|
item.Name != NFileHeader::kLongLink2)
|
||
|
return S_OK;
|
||
|
if (item.PackSize > (1 << 14))
|
||
|
return S_OK;
|
||
|
unsigned packSize = (unsigned)item.GetPackSizeAligned();
|
||
|
char *buf = name->GetBuf(packSize);
|
||
|
size_t processedSize = packSize;
|
||
|
HRESULT res = ReadStream(stream, buf, &processedSize);
|
||
|
item.HeaderSize += (unsigned)processedSize;
|
||
|
name->ReleaseBuf_CalcLen((unsigned)item.PackSize);
|
||
|
RINOK(res);
|
||
|
if (processedSize != packSize)
|
||
|
{
|
||
|
error = k_ErrorType_UnexpectedEnd;
|
||
|
return S_OK;
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
switch (item.LinkFlag)
|
||
|
{
|
||
|
case 'g':
|
||
|
case 'x':
|
||
|
case 'X':
|
||
|
{
|
||
|
// pax Extended Header
|
||
|
break;
|
||
|
}
|
||
|
case NFileHeader::NLinkFlag::kDumpDir:
|
||
|
{
|
||
|
break;
|
||
|
// GNU Extensions to the Archive Format
|
||
|
}
|
||
|
case NFileHeader::NLinkFlag::kSparse:
|
||
|
{
|
||
|
break;
|
||
|
// GNU Extensions to the Archive Format
|
||
|
}
|
||
|
default:
|
||
|
if (item.LinkFlag > '7' || (item.LinkFlag < '0' && item.LinkFlag != 0))
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
if (flagL)
|
||
|
{
|
||
|
item.Name = nameL;
|
||
|
item.NameCouldBeReduced = false;
|
||
|
}
|
||
|
|
||
|
if (flagK)
|
||
|
{
|
||
|
item.LinkName = nameK;
|
||
|
item.LinkNameCouldBeReduced = false;
|
||
|
}
|
||
|
|
||
|
error = k_ErrorType_OK;
|
||
|
return S_OK;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}}
|