// TarIn.cpp

#include "StdAfx.h"

#include "../../../../C/CpuArch.h"

#include "../../../Common/StringToInt.h"

#include "../../Common/StreamUtils.h"

#include "../IArchive.h"

#include "TarIn.h"

#define NUM_UNROLL_BYTES (8 * 4)

Z7_NO_INLINE static bool IsBufNonZero(const void *data, size_t size);
Z7_NO_INLINE static bool IsBufNonZero(const void *data, size_t size)
{
  const Byte *p = (const Byte *)data;

  for (; size != 0 && ((unsigned)(ptrdiff_t)p & (NUM_UNROLL_BYTES - 1)) != 0; size--)
    if (*p++ != 0)
      return true;

  if (size >= NUM_UNROLL_BYTES)
  {
    const Byte *lim = p + size;
    size &= (NUM_UNROLL_BYTES - 1);
    lim -= size;
    do
    {
      if (*(const UInt64 *)(const void *)(p        ) != 0) return true;
      if (*(const UInt64 *)(const void *)(p + 8 * 1) != 0) return true;
      if (*(const UInt64 *)(const void *)(p + 8 * 2) != 0) return true;
      if (*(const UInt64 *)(const void *)(p + 8 * 3) != 0) return true;
      // if (*(const UInt32 *)(const void *)(p        ) != 0) return true;
      // if (*(const UInt32 *)(const void *)(p + 4 * 1) != 0) return true;
      // if (*(const UInt32 *)(const void *)(p + 4 * 2) != 0) return true;
      // if (*(const UInt32 *)(const void *)(p + 4 * 3) != 0) return true;
      p += NUM_UNROLL_BYTES;
    }
    while (p != lim);
  }
  
  for (; size != 0; size--)
    if (*p++ != 0)
      return true;

  return false;
}


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, bool allowEmpty = false)
{
  res = 0;
  char sz[32];
  MyStrNCpy(sz, srcString, size);
  sz[size] = 0;
  const char *end;
  unsigned i;
  for (i = 0; sz[i] == ' '; i++);
  if (sz[i] == 0)
    return allowEmpty;
  res = ConvertOctStringToUInt64(sz + i, &end);
  return (*end == ' ' || *end == 0);
}

static bool OctalToNumber32(const char *srcString, UInt32 &res, bool allowEmpty = false)
{
  const unsigned kSize = 8;
  UInt64 res64;
  if (!OctalToNumber(srcString, kSize, res64, allowEmpty))
    return false;
  res = (UInt32)res64;
  return (res64 <= 0xFFFFFFFF);
}

#define RIF(x) { if (!(x)) return S_OK; }

static void ReadString(const char *s, unsigned size, AString &result)
{
  result.SetFrom_CalcLen(s, size);
}

static bool ParseInt64(const char *p, Int64 &val, bool &isBin)
{
  const UInt32 h = GetBe32(p);
  val = (Int64)GetBe64(p + 4);
  isBin = true;
  if (h == (UInt32)1 << 31)
    return ((val >> 63) & 1) == 0;
  if (h == (UInt32)(Int32)-1)
    return ((val >> 63) & 1) != 0;
  isBin = false;
  UInt64 u;
  const bool res = OctalToNumber(p, 12, u);
  val = (Int64)u;
  return res;
}

static bool ParseInt64_MTime(const char *p, Int64 &val, bool &isBin)
{
  // rare case tar : ZEROs in Docker-Windows TARs
  // rare case tar : spaces
  isBin = false;
  if (GetUi32(p) != 0)
  for (unsigned i = 0; i < 12; i++)
    if (p[i] != ' ')
      return ParseInt64(p, val, isBin);
  val = 0;
  return true;
}

static bool ParseSize(const char *p, UInt64 &val, bool &isBin)
{
  if (GetBe32(p) == (UInt32)1 << 31)
  {
    // GNU extension
    isBin = true;
    val = GetBe64(p + 4);
    return ((val >> 63) & 1) == 0;
  }
  isBin = false;
  return OctalToNumber(p, 12, val,
      true // 20.03: allow empty size for 'V' Label entry
      );
}

static bool ParseSize(const char *p, UInt64 &val)
{
  bool isBin;
  return ParseSize(p, val, isBin);
}

#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;
  // we allow empty Mode value for LongName prefix items
  CHECK(OctalToNumber32(p, mode, true)) p += 8;

  // if (!OctalToNumber32(p, item.UID)) item.UID = 0;
  p += 8;
  // if (!OctalToNumber32(p, item.GID)) item.GID = 0;
  p += 8;

  UInt64 packSize;
  Int64 time;
  UInt32 checkSum;
  bool isBin;
  CHECK(ParseSize(p, packSize, isBin)) p += 12;
  CHECK(ParseInt64_MTime(p, time, isBin)) p += 12;
  CHECK(OctalToNumber32(p, checkSum))
  return k_IsArc_Res_YES;
}


HRESULT CArchive::GetNextItemReal(CItemEx &item)
{
  char buf[NFileHeader::kRecordSize];

  error = k_ErrorType_OK;
  filled = false;

  bool thereAreEmptyRecords = false;
  for (;;)
  {
    size_t processedSize = NFileHeader::kRecordSize;
    RINOK(ReadStream(SeqStream, 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 (IsBufNonZero(buf, NFileHeader::kRecordSize))
      break;
    item.HeaderSize += NFileHeader::kRecordSize;
    thereAreEmptyRecords = true;
    if (OpenCallback)
    {
      RINOK(Progress(item, 0))
    }
  }
  if (thereAreEmptyRecords)
  {
    // error = "There are data after end of archive";
    return S_OK;
  }
  
  char *p = buf;

  error = k_ErrorType_Corrupted;

  // ReadString(p, NFileHeader::kNameSize, item.Name);
  p += NFileHeader::kNameSize;

  /*
  item.Name_CouldBeReduced =
      (item.Name.Len() == NFileHeader::kNameSize ||
       item.Name.Len() == NFileHeader::kNameSize - 1);
  */

  // we allow empty Mode value for LongName prefix items
  RIF(OctalToNumber32(p, item.Mode, true)) p += 8;

  if (!OctalToNumber32(p, item.UID)) { item.UID = 0; }  p += 8;
  if (!OctalToNumber32(p, item.GID)) { item.GID = 0; }  p += 8;

  RIF(ParseSize(p, item.PackSize, item.PackSize_IsBin))
  item.Size = item.PackSize;
  item.Size_IsBin = item.PackSize_IsBin;
  p += 12;
  RIF(ParseInt64_MTime(p, item.MTime, item.MTime_IsBin)) p += 12;
  
  UInt32 checkSum;
  RIF(OctalToNumber32(p, checkSum))
  memset(p, ' ', 8); p += 8;

  item.LinkFlag = *p++;

  ReadString(p, NFileHeader::kNameSize, item.LinkName); p += NFileHeader::kNameSize;
  
  /*
  item.LinkName_CouldBeReduced =
      (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.DeviceMajor_Defined = (p[0] != 0); if (item.DeviceMajor_Defined) { RIF(OctalToNumber32(p, item.DeviceMajor)) } p += 8;
  item.DeviceMinor_Defined = (p[0] != 0); if (item.DeviceMinor_Defined) { RIF(OctalToNumber32(p, item.DeviceMinor)) } p += 8;

  if (p[0] != 0
      && item.IsMagic_ustar_5chars()
      && (item.LinkFlag != 'L' ))
  {
    item.Prefix_WasUsed = true;
    ReadString(p, NFileHeader::kPrefixSize, item.Name);
    item.Name.Add_Slash();
    unsigned i;
    for (i = 0; i < NFileHeader::kNameSize; i++)
      if (buf[i] == 0)
        break;
    item.Name.AddFrom(buf, i);
  }
  else
    ReadString(buf, NFileHeader::kNameSize, item.Name);
  
  p += NFileHeader::kPrefixSize;

  if (item.LinkFlag == NFileHeader::NLinkFlag::kHardLink)
  {
    item.PackSize = 0;
    item.Size = 0;
  }

  if (item.LinkFlag == NFileHeader::NLinkFlag::kDirectory)
  {
    // GNU tar ignores Size field, if LinkFlag is kDirectory
    // 21.02 : we set PackSize = 0 to be more compatible with GNU tar
    item.PackSize = 0;
    // item.Size = 0;
  }

  /*
    TAR standard requires sum of unsigned byte values.
    But some old TAR programs use sum of signed byte values.
    So we check both values.
  */
  // for (int y = 0; y < 100; y++) // for debug
  {
    UInt32 sum0 = 0;
    {
      for (unsigned i = 0; i < NFileHeader::kRecordSize; i++)
        sum0 += (Byte)buf[i];
    }
    if (sum0 != checkSum)
    {
      Int32 sum = 0;
      for (unsigned i = 0; i < NFileHeader::kRecordSize; i++)
        sum += (signed char)buf[i];
      if ((UInt32)sum != checkSum)
        return S_OK;
      item.IsSignedChecksum = true;
    }
  }

  item.HeaderSize += NFileHeader::kRecordSize;

  if (item.LinkFlag == NFileHeader::NLinkFlag::kSparse)
  {
    Byte isExtended = (Byte)buf[482];
    if (isExtended != 0 && isExtended != 1)
      return S_OK;
    RIF(ParseSize(buf + 483, item.Size, item.Size_IsBin))
    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(SeqStream, buf, &processedSize))
      if (processedSize != NFileHeader::kRecordSize)
      {
        error = k_ErrorType_UnexpectedEnd;
        return S_OK;
      }

      item.HeaderSize += NFileHeader::kRecordSize;

      if (OpenCallback)
      {
        RINOK(Progress(item, 0))
      }

      isExtended = (Byte)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;
  }
 
  if (item.PackSize >= (UInt64)1 << 63)
    return S_OK;

  filled = true;
  error = k_ErrorType_OK;
  return S_OK;
}


HRESULT CArchive::Progress(const CItemEx &item, UInt64 posOffset)
{
  const UInt64 pos = item.Get_DataPos() + posOffset;
  if (NumFiles - NumFiles_Prev < (1 << 16)
      // && NumRecords - NumRecords_Prev < (1 << 16)
      && pos - Pos_Prev < ((UInt32)1 << 28))
    return S_OK;
  {
    Pos_Prev = pos;
    NumFiles_Prev = NumFiles;
    // NumRecords_Prev = NumRecords;
    // Sleep(100); // for debug
    return OpenCallback->SetCompleted(&NumFiles, &pos);
  }
}


HRESULT CArchive::ReadDataToBuffer(const CItemEx &item,
    CTempBuffer &tb, size_t stringLimit)
{
  tb.Init();
  UInt64 packSize = item.Get_PackSize_Aligned();
  if (packSize == 0)
    return S_OK;

  UInt64 pos;

  {
    size_t size = stringLimit;
    if (size > packSize)
      size = (size_t)packSize;
    tb.Buffer.AllocAtLeast(size);
    size_t processedSize = size;
    const HRESULT res = ReadStream(SeqStream, tb.Buffer, &processedSize);
    pos = processedSize;
    if (processedSize != size)
    {
      error = k_ErrorType_UnexpectedEnd;
      return res;
    }
    RINOK(res)

    packSize -= size;
   
    size_t i;
    const Byte *p = tb.Buffer;
    for (i = 0; i < size; i++)
      if (p[i] == 0)
        break;
    if (i >= item.PackSize)
      tb.StringSize_IsConfirmed = true;
    if (i > item.PackSize)
    {
      tb.StringSize = (size_t)item.PackSize;
      tb.IsNonZeroTail = true;
    }
    else
    {
      tb.StringSize = i;
      if (i != size)
      {
        tb.StringSize_IsConfirmed = true;
        if (IsBufNonZero(p + i, size - i))
          tb.IsNonZeroTail = true;
      }
    }
   
    if (packSize == 0)
      return S_OK;
  }

  if (InStream)
  {
    RINOK(InStream->Seek((Int64)packSize, STREAM_SEEK_CUR, NULL))
    return S_OK;
  }
  const unsigned kBufSize = 1 << 15;
  Buffer.AllocAtLeast(kBufSize);

  do
  {
    if (OpenCallback)
    {
      RINOK(Progress(item, pos))
    }

    unsigned size = kBufSize;
    if (size > packSize)
      size = (unsigned)packSize;
    size_t processedSize = size;
    const HRESULT res = ReadStream(SeqStream, Buffer, &processedSize);
    if (processedSize != size)
    {
      error = k_ErrorType_UnexpectedEnd;
      return res;
    }
    if (!tb.IsNonZeroTail)
    {
      if (IsBufNonZero(Buffer, size))
        tb.IsNonZeroTail = true;
    }
    packSize -= size;
    pos += size;
  }
  while (packSize != 0);
  return S_OK;
}
 


struct CPaxInfo: public CPaxTimes
{
  bool DoubleTagError;
  bool TagParsingError;
  bool UnknownLines_Overflow;
  bool Size_Defined;
  bool UID_Defined;
  bool GID_Defined;
  bool Path_Defined;
  bool Link_Defined;
  bool User_Defined;
  bool Group_Defined;
  bool SCHILY_fflags_Defined;
  
  UInt64 Size;
  UInt32 UID;
  UInt32 GID;

  AString Path;
  AString Link;
  AString User;
  AString Group;
  AString UnknownLines;
  AString SCHILY_fflags;
  
  bool ParseID(const AString &val, bool &defined, UInt32 &res)
  {
    if (defined)
      DoubleTagError = true;
    if (val.IsEmpty())
      return false;
    const char *end2;
    res = ConvertStringToUInt32(val.Ptr(), &end2);
    if (*end2 != 0)
      return false;
    defined = true;
    return true;
  }

  bool ParsePax(const CTempBuffer &tb, bool isFile);
};


static bool ParsePaxTime(const AString &src, CPaxTime &pt, bool &doubleTagError)
{
  if (pt.IsDefined())
    doubleTagError = true;
  pt.Clear();
  const char *s = src.Ptr();
  bool isNegative = false;
  if (*s == '-')
  {
    isNegative = true;
    s++;
  }
  const char *end;
  {
    UInt64 sec = ConvertStringToUInt64(s, &end);
    if (s == end)
      return false;
    if (sec >= ((UInt64)1 << 63))
      return false;
    if (isNegative)
      sec = (UInt64)-(Int64)sec;
    pt.Sec = (Int64)sec;
  }
  if (*end == 0)
  {
    pt.Ns = 0;
    pt.NumDigits = 0;
    return true;
  }
  if (*end != '.')
    return false;
  s = end + 1;

  UInt32 ns = 0;
  unsigned i;
  const unsigned kNsDigits = 9;
  for (i = 0;; i++)
  {
    const char c = s[i];
    if (c == 0)
      break;
    if (c < '0' || c > '9')
      return false;
    // we ignore digits after 9 digits as GNU TAR
    if (i < kNsDigits)
    {
      ns *= 10;
      ns += (unsigned)(c - '0');
    }
  }
  pt.NumDigits = (int)(i < kNsDigits ? i : kNsDigits);
  while (i < kNsDigits)
  {
    ns *= 10;
    i++;
  }
  if (isNegative && ns != 0)
  {
    pt.Sec--;
    ns = (UInt32)1000 * 1000 * 1000 - ns;
  }
  pt.Ns = ns;
  return true;
}


bool CPaxInfo::ParsePax(const CTempBuffer &tb, bool isFile)
{
  DoubleTagError = false;
  TagParsingError = false;
  UnknownLines_Overflow = false;
  Size_Defined = false;
  UID_Defined = false;
  GID_Defined = false;
  Path_Defined = false;
  Link_Defined = false;
  User_Defined = false;
  Group_Defined = false;
  SCHILY_fflags_Defined = false;
  
  // CPaxTimes::Clear();

  const char *s = (const char *)(const void *)(const Byte *)tb.Buffer;
  size_t rem = tb.StringSize;

  Clear();

  AString name, val;

  while (rem != 0)
  {
    unsigned i;
    for (i = 0;; i++)
    {
      if (i > 24 || i >= rem) // we use limitation for size of (size) field
        return false;
      if (s[i] == ' ')
        break;
    }
    if (i == 0)
      return false;
    const char *end;
    const UInt32 size = ConvertStringToUInt32(s, &end);
    const unsigned offset = (unsigned)(end - s) + 1;
    if (size > rem
        || size <= offset + 1
        || offset != i + 1
        || s[size - 1] != '\n')
      return false;

    for (i = offset; i < size; i++)
      if (s[i] == 0)
        return false;

    for (i = offset; i < size - 1; i++)
      if (s[i] == '=')
        break;
    if (i == size - 1)
      return false;

    name.SetFrom(s + offset, i - offset);
    val.SetFrom(s + i + 1, (unsigned)(size - 1 - (i + 1)));
    
    bool parsed = false;
    if (isFile)
    {
      bool isDetectedName = true;
      // only lower case (name) is supported
      if (name.IsEqualTo("path"))
      {
        if (Path_Defined)
          DoubleTagError = true;
        Path = val;
        Path_Defined = true;
        parsed = true;
      }
      else if (name.IsEqualTo("linkpath"))
      {
        if (Link_Defined)
          DoubleTagError = true;
        Link = val;
        Link_Defined = true;
        parsed = true;
      }
      else if (name.IsEqualTo("uname"))
      {
        if (User_Defined)
          DoubleTagError = true;
        User = val;
        User_Defined = true;
        parsed = true;
      }
      else if (name.IsEqualTo("gname"))
      {
        if (Group_Defined)
          DoubleTagError = true;
        Group = val;
        Group_Defined = true;
        parsed = true;
      }
      else if (name.IsEqualTo("uid"))
      {
        parsed = ParseID(val, UID_Defined, UID);
      }
      else if (name.IsEqualTo("gid"))
      {
        parsed = ParseID(val, GID_Defined, GID);
      }
      else if (name.IsEqualTo("size"))
      {
        if (Size_Defined)
          DoubleTagError = true;
        Size_Defined = false;
        if (!val.IsEmpty())
        {
          const char *end2;
          Size = ConvertStringToUInt64(val.Ptr(), &end2);
          if (*end2 == 0)
          {
            Size_Defined = true;
            parsed = true;
          }
        }
      }
      else if (name.IsEqualTo("mtime"))
        { parsed = ParsePaxTime(val, MTime, DoubleTagError); }
      else if (name.IsEqualTo("atime"))
        { parsed = ParsePaxTime(val, ATime, DoubleTagError); }
      else if (name.IsEqualTo("ctime"))
        { parsed = ParsePaxTime(val, CTime, DoubleTagError); }
      else if (name.IsEqualTo("SCHILY.fflags"))
      {
        if (SCHILY_fflags_Defined)
          DoubleTagError = true;
        SCHILY_fflags = val;
        SCHILY_fflags_Defined = true;
        parsed = true;
      }
      else
        isDetectedName = false;
      if (isDetectedName && !parsed)
        TagParsingError = true;
    }
    if (!parsed)
    {
      if (!UnknownLines_Overflow)
      {
        const unsigned addSize = size - offset;
        if (UnknownLines.Len() + addSize < (1 << 16))
          UnknownLines.AddFrom(s + offset, addSize);
        else
          UnknownLines_Overflow = true;
      }
    }

    s += size;
    rem -= size;
  }
  return true;
}


HRESULT CArchive::ReadItem2(CItemEx &item)
{
  // CItem
  
  item.SparseBlocks.Clear();
  item.PaxTimes.Clear();

  // CItemEx

  item.HeaderSize = 0;
  item.Num_Pax_Records = 0;

  item.LongName_WasUsed = false;
  item.LongName_WasUsed_2 = false;

  item.LongLink_WasUsed = false;
  item.LongLink_WasUsed_2 = false;

  item.HeaderError = false;
  item.IsSignedChecksum = false;
  item.Prefix_WasUsed = false;
  
  item.Pax_Error = false;
  item.Pax_Overflow = false;
  item.pax_path_WasUsed = false;
  item.pax_link_WasUsed = false;
  item.pax_size_WasUsed = false;
  
  item.PaxExtra.Clear();
  item.SCHILY_fflags.Empty();

  item.EncodingCharacts.Clear();
  
  // CArchive temp variable

  NameBuf.Init();
  LinkBuf.Init();
  PaxBuf.Init();
  PaxBuf_global.Init();
  
  UInt64 numExtraRecords = 0;

  for (;;)
  {
    if (OpenCallback)
    {
      RINOK(Progress(item, 0))
    }

    RINOK(GetNextItemReal(item))

    // NumRecords++;

    if (!filled)
    {
      if (error == k_ErrorType_OK)
      if (numExtraRecords != 0
          || item.LongName_WasUsed
          || item.LongLink_WasUsed
          || item.Num_Pax_Records != 0)
        error = k_ErrorType_Corrupted;
      return S_OK;
    }
    if (error != k_ErrorType_OK)
      return S_OK;

    numExtraRecords++;

    const char lf = item.LinkFlag;
    if (lf == NFileHeader::NLinkFlag::kGnu_LongName ||
        lf == NFileHeader::NLinkFlag::kGnu_LongLink)
    {
      // GNU tar ignores item.Name after LinkFlag test
      // 22.00 : now we also ignore item.Name here
      /*
      if (item.Name != NFileHeader::kLongLink &&
          item.Name != NFileHeader::kLongLink2)
      {
        break;
        // return S_OK;
      }
      */

      CTempBuffer *tb =
          lf == NFileHeader::NLinkFlag::kGnu_LongName ?
          &NameBuf :
          &LinkBuf;
      
      /*
      if (item.PackSize > (1 << 29))
      {
        // break;
        return S_OK;
      }
      */

      const unsigned kLongNameSizeMax = (unsigned)1 << 14;
      RINOK(ReadDataToBuffer(item, *tb, kLongNameSizeMax))
      if (error != k_ErrorType_OK)
        return S_OK;

      if (lf == NFileHeader::NLinkFlag::kGnu_LongName)
      {
        item.LongName_WasUsed_2 =
        item.LongName_WasUsed;
        item.LongName_WasUsed = true;
      }
      else
      {
        item.LongLink_WasUsed_2 =
        item.LongLink_WasUsed;
        item.LongLink_WasUsed = true;
      }

      if (!tb->StringSize_IsConfirmed)
        tb->StringSize = 0;
      item.HeaderSize += item.Get_PackSize_Aligned();
      if (tb->StringSize == 0 ||
          tb->StringSize + 1 != item.PackSize)
        item.HeaderError = true;
      if (tb->IsNonZeroTail)
        item.HeaderError = true;
      continue;
    }

    if (lf == NFileHeader::NLinkFlag::kGlobal ||
        lf == NFileHeader::NLinkFlag::kPax ||
        lf == NFileHeader::NLinkFlag::kPax_2)
    {
      // GNU tar ignores item.Name after LinkFlag test
      // 22.00 : now we also ignore item.Name here
      /*
      if (item.PackSize > (UInt32)1 << 26)
      {
        break; // we don't want big PaxBuf files
        // return S_OK;
      }
      */
      const unsigned kParsingPaxSizeMax = (unsigned)1 << 26;

      const bool isStartHeader = (item.HeaderSize == NFileHeader::kRecordSize);

      CTempBuffer *tb = (lf == NFileHeader::NLinkFlag::kGlobal ? &PaxBuf_global : &PaxBuf);

      RINOK(ReadDataToBuffer(item, *tb, kParsingPaxSizeMax))
      if (error != k_ErrorType_OK)
        return S_OK;

      item.HeaderSize += item.Get_PackSize_Aligned();

      if (tb->StringSize != item.PackSize
          || tb->StringSize == 0
          || tb->IsNonZeroTail)
        item.Pax_Error = true;

      item.Num_Pax_Records++;
      if (lf != NFileHeader::NLinkFlag::kGlobal)
      {
        item.PaxExtra.RecordPath = item.Name;
        continue;
      }
      // break; // for debug
      {
        if (PaxGlobal_Defined)
          _is_PaxGlobal_Error = true;
        CPaxInfo paxInfo;
        if (paxInfo.ParsePax(PaxBuf_global, false))
        {
          PaxGlobal.RawLines = paxInfo.UnknownLines;
          PaxGlobal.RecordPath = item.Name;
          PaxGlobal_Defined = true;
        }
        else
          _is_PaxGlobal_Error = true;
        
        if (isStartHeader
            && item.Num_Pax_Records == 1
            && numExtraRecords == 1)
        {
          // we skip global pax header info after parsing
          item.HeaderPos += item.HeaderSize;
          item.HeaderSize = 0;
          item.Num_Pax_Records = 0;
          numExtraRecords = 0;
        }
        else
          _is_PaxGlobal_Error = true;
      }
      continue;
    }
    
    /*
    if (lf == NFileHeader::NLinkFlag::kDumpDir ||
        lf == NFileHeader::NLinkFlag::kSparse)
    {
      // GNU Extensions to the Archive Format
      break;
    }
    if (lf > '7' || (lf < '0' && lf != 0))
    {
      break;
      // return S_OK;
    }
    */
    break;
  }
    
  // we still use name from main header, if long_name is bad
  if (item.LongName_WasUsed && NameBuf.StringSize != 0)
  {
    NameBuf.CopyToString(item.Name);
    // item.Name_CouldBeReduced = false;
  }
  
  if (item.LongLink_WasUsed)
  {
    // we use empty link, if long_link is bad
    LinkBuf.CopyToString(item.LinkName);
    // item.LinkName_CouldBeReduced = false;
  }
  
  error = k_ErrorType_OK;
  
  if (PaxBuf.StringSize != 0)
  {
    CPaxInfo paxInfo;
    if (!paxInfo.ParsePax(PaxBuf, true))
      item.Pax_Error = true;
    else
    {
      if (paxInfo.Path_Defined) //  if (!paxInfo.Path.IsEmpty())
      {
        item.Name = paxInfo.Path;
        item.pax_path_WasUsed = true;
      }
      if (paxInfo.Link_Defined) // (!paxInfo.Link.IsEmpty())
      {
        item.LinkName = paxInfo.Link;
        item.pax_link_WasUsed = true;
      }
      if (paxInfo.User_Defined)
      {
        item.User = paxInfo.User;
        // item.pax_uname_WasUsed = true;
      }
      if (paxInfo.Group_Defined)
      {
        item.Group = paxInfo.Group;
        // item.pax_gname_WasUsed = true;
      }
      if (paxInfo.SCHILY_fflags_Defined)
      {
        item.SCHILY_fflags = paxInfo.SCHILY_fflags;
        // item.SCHILY_fflags_WasUsed = true;
      }
      if (paxInfo.UID_Defined)
      {
        item.UID = (UInt32)paxInfo.UID;
      }
      if (paxInfo.GID_Defined)
      {
        item.GID = (UInt32)paxInfo.GID;
      }
      
      if (paxInfo.Size_Defined)
      {
        const UInt64 piSize = paxInfo.Size;
        // GNU TAR ignores (item.Size) in that case
        if (item.Size != 0 && item.Size != piSize)
          item.Pax_Error = true;
        item.Size = piSize;
        item.PackSize = piSize;
        item.pax_size_WasUsed = true;
      }
      
      item.PaxTimes = paxInfo;
      item.PaxExtra.RawLines = paxInfo.UnknownLines;
      if (paxInfo.UnknownLines_Overflow)
        item.Pax_Overflow = true;
      if (paxInfo.TagParsingError)
        item.Pax_Error = true;
      if (paxInfo.DoubleTagError)
        item.Pax_Error = true;
    }
  }

  return S_OK;
}



HRESULT CArchive::ReadItem(CItemEx &item)
{
  item.HeaderPos = _phySize;
  
  const HRESULT res = ReadItem2(item);
  
  /*
  if (error == k_ErrorType_Warning)
    _is_Warning = true;
  else
  */
  
  if (error != k_ErrorType_OK)
    _error = error;
  
  RINOK(res)

  if (filled)
  {
    if (item.IsMagic_GNU())
      _are_Gnu = true;
    else if (item.IsMagic_Posix_ustar_00())
      _are_Posix = true;
    
    if (item.Num_Pax_Records != 0)
      _are_Pax = true;

    if (item.PaxTimes.MTime.IsDefined())  _are_mtime = true;
    if (item.PaxTimes.ATime.IsDefined())  _are_atime = true;
    if (item.PaxTimes.CTime.IsDefined())  _are_ctime = true;
    if (!item.SCHILY_fflags.IsEmpty())  _are_SCHILY_fflags = true;

    if (item.pax_path_WasUsed)
      _are_pax_path = true;
    if (item.pax_link_WasUsed)
      _are_pax_link = true;
    if (item.LongName_WasUsed)
      _are_LongName = true;
    if (item.LongLink_WasUsed)
      _are_LongLink = true;
    if (item.Prefix_WasUsed)
      _pathPrefix_WasUsed = true;
    /*
    if (item.IsSparse())
      _isSparse = true;
    */
    if (item.Is_PaxExtendedHeader())
      _are_Pax_Items = true;
    if (item.IsThereWarning()
        || item.HeaderError
        || item.Pax_Error)
      _is_Warning = true;
  }

  const UInt64 headerEnd = item.HeaderPos + item.HeaderSize;
  // _headersSize += headerEnd - _phySize;
  // we don't count skipped records
  _headersSize += item.HeaderSize;
  _phySize = headerEnd;
  return S_OK;
}

}}
