// Archive/TarIn.cpp

#include "StdAfx.h"

#include "TarIn.h"
#include "TarHeader.h"

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

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

namespace NArchive {
namespace NTar {
 
HRESULT CInArchive::ReadBytes(void *data, UInt32 size, UInt32 &processedSize)
{
  RINOK(ReadStream(m_Stream, data, size, &processedSize));
  m_Position += processedSize;
  return S_OK;
}

HRESULT CInArchive::Open(IInStream *inStream)
{
  RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &m_Position));
  m_Stream = inStream;
  return S_OK;
}

static bool OctalToNumber(const char *srcString, int size, UInt64 &res)
{
  char sz[32];
  strncpy(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 *record)
{
  for (int i = 0; i < NFileHeader::kRecordSize; i++)
    if (record[i] != 0)
      return false;
  return true;
}

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

static char GetHex(Byte value)
{
  return (value < 10) ? ('0' + value) : ('A' + (value - 10));
}

HRESULT CInArchive::GetNextItemReal(bool &filled, CItemEx &item)
{
  item.LongLinkSize = 0;
  // NFileHeader::CRecord record;
  char record[NFileHeader::kRecordSize];
  char *cur = record;

  filled = false;

  UInt32 processedSize;
  item.HeaderPosition = m_Position;
  RINOK(ReadBytes(record, NFileHeader::kRecordSize, processedSize));
  if (processedSize == 0 || 
      (processedSize == NFileHeader::kRecordSize && IsRecordLast(record)))
    return S_OK;
  if (processedSize < NFileHeader::kRecordSize)
    return S_FALSE;
  
  // NFileHeader::CHeader &header = record.Header;
  
  AString name;
  ReadString(cur, NFileHeader::kNameSize, name);
  cur += NFileHeader::kNameSize;

  item.Name.Empty();
  int i;
  for (i = 0; i < name.Length(); i++)
  {
    char c = name[i];
    if (((Byte)c) < 0x08)
    {
      return S_FALSE;
    }
    if (((Byte)c) < 0x20)
    {
      item.Name += '[';
      item.Name += GetHex(((Byte)c) >> 4);
      item.Name += GetHex(((Byte)c) & 0xF);
      item.Name += ']';
    }
    else
      item.Name += c;
  }

  RIF(OctalToNumber32(cur, 8, item.Mode));
  cur += 8;

  if (!OctalToNumber32(cur, 8, item.UID))
    item.UID = 0;
  cur += 8;

  if (!OctalToNumber32(cur, 8, item.GID))
    item.GID = 0;
  cur += 8;

  RIF(OctalToNumber(cur, 12, item.Size));
  cur += 12;

  RIF(OctalToNumber32(cur, 12, item.ModificationTime));
  cur += 12;
  
  UInt32 checkSum;
  RIF(OctalToNumber32(cur, 8, checkSum));
  memmove(cur, NFileHeader::kCheckSumBlanks, 8);
  cur += 8;

  item.LinkFlag = *cur++;

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

  memmove(item.Magic, cur, 8);
  cur += 8;

  ReadString(cur, NFileHeader::kUserNameSize, item.UserName);
  cur += NFileHeader::kUserNameSize;
  ReadString(cur, NFileHeader::kUserNameSize, item.GroupName);
  cur += NFileHeader::kUserNameSize;

  item.DeviceMajorDefined = (cur[0] != 0);
  RIF(OctalToNumber32(cur, 8, item.DeviceMajor));
  cur += 8;
 
  item.DeviceMinorDefined = (cur[0] != 0);
  RIF(OctalToNumber32(cur, 8, item.DeviceMinor));
  cur += 8;

  AString prefix;
  ReadString(cur, NFileHeader::kPrefixSize, prefix);
  cur += NFileHeader::kPrefixSize;
  if (!prefix.IsEmpty() && item.IsMagic())
    item.Name = prefix + AString('/') + item.Name;

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

  filled = true;
  return S_OK;
}

HRESULT CInArchive::GetNextItem(bool &filled, CItemEx &item)
{
  RINOK(GetNextItemReal(filled, item));
  if (!filled)
    return S_OK;
  // GNUtar extension
  if (item.LinkFlag == 'L')
  {
    if (item.Name.Compare(NFileHeader::kLongLink) != 0)
      return S_FALSE;
    UInt64 headerPosition = item.HeaderPosition;

    UInt32 processedSize;
    AString fullName;
    char *buffer = fullName.GetBuffer((UInt32)item.Size + 1);
    RINOK(ReadBytes(buffer, (UInt32)item.Size, processedSize));
    buffer[item.Size] = '\0';
    fullName.ReleaseBuffer();
    if (processedSize != item.Size)
      return S_FALSE;
    RINOK(Skeep((0 - item.Size) & 0x1FF));
    RINOK(GetNextItemReal(filled, item));
    item.Name = fullName;
    item.LongLinkSize = item.HeaderPosition - headerPosition;
    item.HeaderPosition = headerPosition;
  }
  else if (item.LinkFlag == 'g' || item.LinkFlag == 'x')
  {
    // pax Extended Header
    return S_OK;
  }
  else if (item.LinkFlag > '7' || (item.LinkFlag < '0' && item.LinkFlag != 0))
    return S_FALSE;
  return S_OK;
}

HRESULT CInArchive::Skeep(UInt64 numBytes)
{
  UInt64 newPostion;
  RINOK(m_Stream->Seek(numBytes, STREAM_SEEK_CUR, &newPostion));
  m_Position += numBytes;
  if (m_Position != newPostion)
    return E_FAIL;
  return S_OK;
}


HRESULT CInArchive::SkeepDataRecords(UInt64 dataSize)
{
  return Skeep((dataSize + 0x1FF) & (~((UInt64)0x1FF)));
}

}}
