// Archive/RarIn.cpp

#include "StdAfx.h"

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

#include "Common/StringConvert.h"
#include "Common/UTFConvert.h"

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

#include "../Common/FindSignature.h"

#include "RarIn.h"

#define Get16(p) GetUi16(p)
#define Get32(p) GetUi32(p)
#define Get64(p) GetUi64(p)

namespace NArchive {
namespace NRar {

static const char *k_UnexpectedEnd = "Unexpected end of archive";
static const char *k_DecryptionError = "Decryption Error";

void CInArchive::ThrowExceptionWithCode(
    CInArchiveException::CCauseType cause)
{
  throw CInArchiveException(cause);
}

HRESULT CInArchive::Open(IInStream *inStream, const UInt64 *searchHeaderSizeLimit)
{
  try
  {
    Close();
    HRESULT res = Open2(inStream, searchHeaderSizeLimit);
    if (res == S_OK)
      return res;
    Close();
    return res;
  }
  catch(...) { Close(); throw; }
}

void CInArchive::Close()
{
  m_Stream.Release();
}

HRESULT CInArchive::ReadBytesSpec(void *data, size_t *resSize)
{
  if (m_CryptoMode)
  {
    size_t size = *resSize;
    *resSize = 0;
    const Byte *bufData = m_DecryptedDataAligned;
    UInt32 bufSize = m_DecryptedDataSize;
    size_t i;
    for (i = 0; i < size && m_CryptoPos < bufSize; i++)
      ((Byte *)data)[i] = bufData[m_CryptoPos++];
    *resSize = i;
    return S_OK;
  }
  return ReadStream(m_Stream, data, resSize);
}

bool CInArchive::ReadBytesAndTestSize(void *data, UInt32 size)
{
  size_t processed = size;
  if (ReadBytesSpec(data, &processed) != S_OK)
    return false;
  return processed == size;
}

HRESULT CInArchive::Open2(IInStream *stream, const UInt64 *searchHeaderSizeLimit)
{
  m_CryptoMode = false;
  RINOK(stream->Seek(0, STREAM_SEEK_SET, &m_StreamStartPosition));
  m_Position = m_StreamStartPosition;

  UInt64 arcStartPos;
  RINOK(FindSignatureInStream(stream, NHeader::kMarker, NHeader::kMarkerSize,
      searchHeaderSizeLimit, arcStartPos));
  m_Position = arcStartPos + NHeader::kMarkerSize;
  RINOK(stream->Seek(m_Position, STREAM_SEEK_SET, NULL));
  Byte buf[NHeader::NArchive::kArchiveHeaderSize + 1];

  RINOK(ReadStream_FALSE(stream, buf, NHeader::NArchive::kArchiveHeaderSize));
  AddToSeekValue(NHeader::NArchive::kArchiveHeaderSize);


  UInt32 blockSize = Get16(buf + 5);

  _header.EncryptVersion = 0;
  _header.Flags = Get16(buf + 3);

  UInt32 headerSize = NHeader::NArchive::kArchiveHeaderSize;
  if (_header.IsThereEncryptVer())
  {
    if (blockSize <= headerSize)
      return S_FALSE;
    RINOK(ReadStream_FALSE(stream, buf + NHeader::NArchive::kArchiveHeaderSize, 1));
    AddToSeekValue(1);
    _header.EncryptVersion = buf[NHeader::NArchive::kArchiveHeaderSize];
    headerSize += 1;
  }
  if (blockSize < headerSize ||
      buf[2] != NHeader::NBlockType::kArchiveHeader ||
      (UInt32)Get16(buf) != (CrcCalc(buf + 2, headerSize - 2) & 0xFFFF))
    return S_FALSE;

  size_t commentSize = blockSize - headerSize; 
  _comment.SetCapacity(commentSize);
  RINOK(ReadStream_FALSE(stream, _comment, commentSize));
  AddToSeekValue(commentSize);
  m_Stream = stream;
  _header.StartPosition = arcStartPos;
  return S_OK;
}

void CInArchive::GetArchiveInfo(CInArchiveInfo &archiveInfo) const
{
  archiveInfo = _header;
}

static void DecodeUnicodeFileName(const char *name, const Byte *encName,
    int encSize, wchar_t *unicodeName, int maxDecSize)
{
  int encPos = 0;
  int decPos = 0;
  int flagBits = 0;
  Byte flags = 0;
  Byte highByte = encName[encPos++];
  while (encPos < encSize && decPos < maxDecSize)
  {
    if (flagBits == 0)
    {
      flags = encName[encPos++];
      flagBits = 8;
    }
    switch(flags >> 6)
    {
      case 0:
        unicodeName[decPos++] = encName[encPos++];
        break;
      case 1:
        unicodeName[decPos++] = (wchar_t)(encName[encPos++] + (highByte << 8));
        break;
      case 2:
        unicodeName[decPos++] = (wchar_t)(encName[encPos] + (encName[encPos + 1] << 8));
        encPos += 2;
        break;
      case 3:
        {
          int length = encName[encPos++];
          if (length & 0x80)
          {
            Byte correction = encName[encPos++];
            for (length = (length & 0x7f) + 2;
                length > 0 && decPos < maxDecSize; length--, decPos++)
              unicodeName[decPos] = (wchar_t)(((name[decPos] + correction) & 0xff) + (highByte << 8));
          }
          else
            for (length += 2; length > 0 && decPos < maxDecSize; length--, decPos++)
              unicodeName[decPos] = name[decPos];
        }
        break;
    }
    flags <<= 2;
    flagBits -= 2;
  }
  unicodeName[decPos < maxDecSize ? decPos : maxDecSize - 1] = 0;
}

void CInArchive::ReadName(CItemEx &item, int nameSize)
{
  item.UnicodeName.Empty();
  if (nameSize > 0)
  {
    m_NameBuffer.EnsureCapacity(nameSize + 1);
    char *buffer = (char *)m_NameBuffer;

    for (int i = 0; i < nameSize; i++)
      buffer[i] = ReadByte();

    int mainLen;
    for (mainLen = 0; mainLen < nameSize; mainLen++)
      if (buffer[mainLen] == '\0')
        break;
    buffer[mainLen] = '\0';
    item.Name = buffer;

    if(item.HasUnicodeName())
    {
      if(mainLen < nameSize)
      {
        int unicodeNameSizeMax = MyMin(nameSize, (0x400));
        _unicodeNameBuffer.EnsureCapacity(unicodeNameSizeMax + 1);
        DecodeUnicodeFileName(buffer, (const Byte *)buffer + mainLen + 1,
            nameSize - (mainLen + 1), _unicodeNameBuffer, unicodeNameSizeMax);
        item.UnicodeName = _unicodeNameBuffer;
      }
      else if (!ConvertUTF8ToUnicode(item.Name, item.UnicodeName))
        item.UnicodeName.Empty();
    }
  }
  else
    item.Name.Empty();
}

Byte CInArchive::ReadByte()
{
  if (m_CurPos >= m_PosLimit)
    throw CInArchiveException(CInArchiveException::kIncorrectArchive);
  return m_CurData[m_CurPos++];
}

UInt16 CInArchive::ReadUInt16()
{
  UInt16 value = 0;
  for (int i = 0; i < 2; i++)
  {
    Byte b = ReadByte();
    value |= (UInt16(b) << (8 * i));
  }
  return value;
}

UInt32 CInArchive::ReadUInt32()
{
  UInt32 value = 0;
  for (int i = 0; i < 4; i++)
  {
    Byte b = ReadByte();
    value |= (UInt32(b) << (8 * i));
  }
  return value;
}

void CInArchive::ReadTime(Byte mask, CRarTime &rarTime)
{
  rarTime.LowSecond = (Byte)(((mask & 4) != 0) ? 1 : 0);
  int numDigits = (mask & 3);
  rarTime.SubTime[0] = rarTime.SubTime[1] = rarTime.SubTime[2] = 0;
  for (int i = 0; i < numDigits; i++)
    rarTime.SubTime[3 - numDigits + i] = ReadByte();
}

void CInArchive::ReadHeaderReal(CItemEx &item)
{
  item.Flags = m_BlockHeader.Flags;
  item.PackSize = ReadUInt32();
  item.Size = ReadUInt32();
  item.HostOS = ReadByte();
  item.FileCRC = ReadUInt32();
  item.MTime.DosTime = ReadUInt32();
  item.UnPackVersion = ReadByte();
  item.Method = ReadByte();
  int nameSize = ReadUInt16();
  item.Attrib = ReadUInt32();

  item.MTime.LowSecond = 0;
  item.MTime.SubTime[0] =
      item.MTime.SubTime[1] =
      item.MTime.SubTime[2] = 0;

  if((item.Flags & NHeader::NFile::kSize64Bits) != 0)
  {
    item.PackSize |= ((UInt64)ReadUInt32() << 32);
    item.Size |= ((UInt64)ReadUInt32() << 32);
  }

  ReadName(item, nameSize);

  if (item.HasSalt())
    for (int i = 0; i < sizeof(item.Salt); i++)
      item.Salt[i] = ReadByte();

  // some rar archives have HasExtTime flag without field.
  if (m_CurPos < m_PosLimit && item.HasExtTime())
  {
    Byte accessMask = (Byte)(ReadByte() >> 4);
    Byte b = ReadByte();
    Byte modifMask = (Byte)(b >> 4);
    Byte createMask = (Byte)(b & 0xF);
    if ((modifMask & 8) != 0)
      ReadTime(modifMask, item.MTime);
    item.CTimeDefined = ((createMask & 8) != 0);
    if (item.CTimeDefined)
    {
      item.CTime.DosTime = ReadUInt32();
      ReadTime(createMask, item.CTime);
    }
    item.ATimeDefined = ((accessMask & 8) != 0);
    if (item.ATimeDefined)
    {
      item.ATime.DosTime = ReadUInt32();
      ReadTime(accessMask, item.ATime);
    }
  }

  UInt16 fileHeaderWithNameSize = (UInt16)m_CurPos;
  
  item.Position = m_Position;
  item.MainPartSize = fileHeaderWithNameSize;
  item.CommentSize = (UInt16)(m_BlockHeader.HeadSize - fileHeaderWithNameSize);

  if (m_CryptoMode)
    item.AlignSize = (UInt16)((16 - ((m_BlockHeader.HeadSize) & 0xF)) & 0xF);
  else
    item.AlignSize = 0;
  AddToSeekValue(m_BlockHeader.HeadSize);
}

void CInArchive::AddToSeekValue(UInt64 addValue)
{
  m_Position += addValue;
}

HRESULT CInArchive::GetNextItem(CItemEx &item, ICryptoGetTextPassword *getTextPassword, bool &decryptionError, AString &errorMessage)
{
  decryptionError = false;
  for (;;)
  {
    SeekInArchive(m_Position);
    if (!m_CryptoMode && (_header.Flags &
        NHeader::NArchive::kBlockHeadersAreEncrypted) != 0)
    {
      m_CryptoMode = false;
      if (getTextPassword == 0)
        return S_FALSE;
      if (!m_RarAES)
      {
        m_RarAESSpec = new NCrypto::NRar29::CDecoder;
        m_RarAES = m_RarAESSpec;
      }
      m_RarAESSpec->SetRar350Mode(_header.IsEncryptOld());

      // Salt
      const UInt32 kSaltSize = 8;
      Byte salt[kSaltSize];
      if(!ReadBytesAndTestSize(salt, kSaltSize))
        return S_FALSE;
      m_Position += kSaltSize;
      RINOK(m_RarAESSpec->SetDecoderProperties2(salt, kSaltSize))
      // Password
      CMyComBSTR password;
      RINOK(getTextPassword->CryptoGetTextPassword(&password))
      UString unicodePassword(password);

      CByteBuffer buffer;
      const UInt32 sizeInBytes = unicodePassword.Length() * 2;
      buffer.SetCapacity(sizeInBytes);
      for (int i = 0; i < unicodePassword.Length(); i++)
      {
        wchar_t c = unicodePassword[i];
        ((Byte *)buffer)[i * 2] = (Byte)c;
        ((Byte *)buffer)[i * 2 + 1] = (Byte)(c >> 8);
      }

      RINOK(m_RarAESSpec->CryptoSetPassword((const Byte *)buffer, sizeInBytes));

      const UInt32 kDecryptedBufferSize = (1 << 12);
      if (m_DecryptedData.GetCapacity() == 0)
      {
        const UInt32 kAlign = 16;
        m_DecryptedData.SetCapacity(kDecryptedBufferSize + kAlign);
        m_DecryptedDataAligned = (Byte *)((ptrdiff_t)((Byte *)m_DecryptedData + kAlign - 1) & ~(ptrdiff_t)(kAlign - 1));
      }
      RINOK(m_RarAES->Init());
      size_t decryptedDataSizeT = kDecryptedBufferSize;
      RINOK(ReadStream(m_Stream, m_DecryptedDataAligned, &decryptedDataSizeT));
      m_DecryptedDataSize = (UInt32)decryptedDataSizeT;
      m_DecryptedDataSize = m_RarAES->Filter(m_DecryptedDataAligned, m_DecryptedDataSize);

      m_CryptoMode = true;
      m_CryptoPos = 0;
    }

    m_FileHeaderData.EnsureCapacity(7);
    size_t processed = 7;
    RINOK(ReadBytesSpec((Byte *)m_FileHeaderData, &processed));
    if (processed != 7)
    {
      if (processed != 0)
        errorMessage = k_UnexpectedEnd;
      return S_FALSE;
    }

    m_CurData = (Byte *)m_FileHeaderData;
    m_CurPos = 0;
    m_PosLimit = 7;
    m_BlockHeader.CRC = ReadUInt16();
    m_BlockHeader.Type = ReadByte();
    m_BlockHeader.Flags = ReadUInt16();
    m_BlockHeader.HeadSize = ReadUInt16();

    if (m_BlockHeader.HeadSize < 7)
      ThrowExceptionWithCode(CInArchiveException::kIncorrectArchive);

    if (m_BlockHeader.Type == NHeader::NBlockType::kEndOfArchive)
      return S_FALSE;

    if (m_BlockHeader.Type == NHeader::NBlockType::kFileHeader)
    {
      m_FileHeaderData.EnsureCapacity(m_BlockHeader.HeadSize);
      m_CurData = (Byte *)m_FileHeaderData;
      m_PosLimit = m_BlockHeader.HeadSize;
      if (!ReadBytesAndTestSize(m_CurData + m_CurPos, m_BlockHeader.HeadSize - 7))
      {
        errorMessage = k_UnexpectedEnd;
        return S_FALSE;
      }

      ReadHeaderReal(item);
      if ((CrcCalc(m_CurData + 2,
          m_BlockHeader.HeadSize - item.CommentSize - 2) & 0xFFFF) != m_BlockHeader.CRC)
        ThrowExceptionWithCode(CInArchiveException::kFileHeaderCRCError);

      FinishCryptoBlock();
      m_CryptoMode = false;
      SeekInArchive(m_Position); // Move Position to compressed Data;
      AddToSeekValue(item.PackSize);  // m_Position points to next header;
      return S_OK;
    }
    if (m_CryptoMode && m_BlockHeader.HeadSize > (1 << 10))
    {
      decryptionError = true;
      errorMessage = k_DecryptionError;
      return S_FALSE;
    }
    if ((m_BlockHeader.Flags & NHeader::NBlock::kLongBlock) != 0)
    {
      m_FileHeaderData.EnsureCapacity(7 + 4);
      m_CurData = (Byte *)m_FileHeaderData;
      if (!ReadBytesAndTestSize(m_CurData + m_CurPos, 4))
      {
        errorMessage = k_UnexpectedEnd;
        return S_FALSE;
      }
      m_PosLimit = 7 + 4;
      UInt32 dataSize = ReadUInt32();
      AddToSeekValue(dataSize);
      if (m_CryptoMode && dataSize > (1 << 27))
      {
        decryptionError = true;
        errorMessage = k_DecryptionError;
        return S_FALSE;
      }
      m_CryptoPos = m_BlockHeader.HeadSize;
    }
    else
      m_CryptoPos = 0;
    AddToSeekValue(m_BlockHeader.HeadSize);
    FinishCryptoBlock();
    m_CryptoMode = false;
  }
}

void CInArchive::SeekInArchive(UInt64 position)
{
  m_Stream->Seek(position, STREAM_SEEK_SET, NULL);
}

ISequentialInStream* CInArchive::CreateLimitedStream(UInt64 position, UInt64 size)
{
  CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
  CMyComPtr<ISequentialInStream> inStream(streamSpec);
  SeekInArchive(position);
  streamSpec->SetStream(m_Stream);
  streamSpec->Init(size);
  return inStream.Detach();
}

}}
