// TarIn.cpp

#include "StdAfx.h"

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

#include "Common/StringToInt.h"

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

#include "TarIn.h"

namespace NArchive {
namespace NTar {
 
static void MyStrNCpy(char *dest, const char *src, int size)
{
  for (int i = 0; i < size; i++)
  {
    char c = src[i];
    dest[i] = c;
    if (c == 0)
      break;
  }
}

static bool OctalToNumber(const char *srcString, int size, UInt64 &res)
{
  char sz[32];
  MyStrNCpy(sz, srcString, size);
  sz[size] = 0;
  const char *end;
  int i;
  for (i = 0; sz[i] == ' '; i++);
  res = ConvertOctStringToUInt64(sz + i, &end);
  return (*end == ' ' || *end == 0);
}

static bool OctalToNumber32(const char *srcString, int 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_FALSE; }

static bool IsRecordLast(const char *buf)
{
  for (int i = 0; i < NFileHeader::kRecordSize; i++)
    if (buf[i] != 0)
      return false;
  return true;
}

static void ReadString(const char *s, int size, AString &result)
{
  char temp[NFileHeader::kRecordSize + 1];
  MyStrNCpy(temp, s, size);
  temp[size] = '\0';
  result = temp;
}

static HRESULT GetNextItemReal(ISequentialInStream *stream, bool &filled, CItemEx &item, AString &error)
{
  char buf[NFileHeader::kRecordSize];
  char *p = buf;

  error.Empty();
  filled = false;

  bool thereAreEmptyRecords = false;
  for (;;)
  {
    size_t processedSize = NFileHeader::kRecordSize;
    RINOK(ReadStream(stream, buf, &processedSize));
    if (processedSize == 0)
    {
      if (!thereAreEmptyRecords )
        error = "There are no trailing zero-filled records";
      return S_OK;
    }
    if (processedSize != NFileHeader::kRecordSize)
    {
      error = "There is no correct record at the end of archive";
      return S_OK;
    }
    item.HeaderSize += NFileHeader::kRecordSize;
    if (!IsRecordLast(buf))
      break;
    thereAreEmptyRecords = true;
  }
  if (thereAreEmptyRecords)
  {
    error = "There are data after end of archive";
    return S_OK;
  }
  
  ReadString(p, NFileHeader::kNameSize, item.Name); p += NFileHeader::kNameSize;

  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;

  if (GetBe32(p) == (UInt32)1 << 31)
  {
    // GNU extension
    item.Size = GetBe64(p + 4);
  }
  else
  {
    RIF(OctalToNumber(p, 12, item.Size));
  }
  p += 12;
  RIF(OctalToNumber32(p, 12, item.MTime)); p += 12;
  
  UInt32 checkSum;
  RIF(OctalToNumber32(p, 8, checkSum));
  memcpy(p, NFileHeader::kCheckSumBlanks, 8); p += 8;

  item.LinkFlag = *p++;

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

  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); RIF(OctalToNumber32(p, 8, item.DeviceMajor)); p += 8;
  item.DeviceMinorDefined = (p[0] != 0); RIF(OctalToNumber32(p, 8, item.DeviceMinor)); p += 8;

  AString prefix;
  ReadString(p, NFileHeader::kPrefixSize, prefix);
  p += NFileHeader::kPrefixSize;
  if (!prefix.IsEmpty() && item.IsMagic() &&
      (item.LinkFlag != 'L' /* || prefix != "00000000000" */ ))
    item.Name = prefix + AString('/') + item.Name;

  if (item.LinkFlag == NFileHeader::NLinkFlag::kLink)
    item.Size = 0;
 
  UInt32 checkSumReal = 0;
  for (int i = 0; i < NFileHeader::kRecordSize; i++)
    checkSumReal += (Byte)buf[i];
  
  if (checkSumReal != checkSum)
    return S_FALSE;

  filled = true;
  return S_OK;
}

HRESULT ReadItem(ISequentialInStream *stream, bool &filled, CItemEx &item, AString &error)
{
  item.HeaderSize = 0;
  bool flagL = false;
  bool flagK = false;
  AString nameL;
  AString nameK;
  for (;;)
  {
    RINOK(GetNextItemReal(stream, filled, item, error));
    if (!filled)
      return S_OK;
    if (item.LinkFlag == 'L' || // NEXT file has a long name
        item.LinkFlag == 'K') // NEXT file has a long linkname
    {
      AString *name;
      if (item.LinkFlag == 'L')
        { if (flagL) return S_FALSE; flagL = true; name = &nameL; }
      else
        { if (flagK) return S_FALSE; flagK = true; name = &nameK; }

      if (item.Name.Compare(NFileHeader::kLongLink) != 0 &&
          item.Name.Compare(NFileHeader::kLongLink2) != 0)
        return S_FALSE;
      if (item.Size > (1 << 14))
        return S_FALSE;
      int packSize = (int)item.GetPackSize();
      char *buf = name->GetBuffer(packSize);
      RINOK(ReadStream_FALSE(stream, buf, packSize));
      item.HeaderSize += packSize;
      buf[(size_t)item.Size] = '\0';
      name->ReleaseBuffer();
      continue;
    }
    if (item.LinkFlag == 'g' || item.LinkFlag == 'x' || item.LinkFlag == 'X')
    {
      // pax Extended Header
    }
    else if (item.LinkFlag == NFileHeader::NLinkFlag::kDumpDir)
    {
      // GNU Extensions to the Archive Format
    }
    else if (item.LinkFlag > '7' || (item.LinkFlag < '0' && item.LinkFlag != 0))
      return S_FALSE;
    if (flagL) item.Name = nameL;
    if (flagK) item.LinkName = nameK;
    return S_OK;
  }
}

}}
