#include "rar.hpp"

#include "arccmt.cpp"


Archive::Archive(CommandData *InitCmd)
{
  Cmd=NULL; // Just in case we'll have an exception in 'new' below.

  DummyCmd=(InitCmd==NULL);
  Cmd=DummyCmd ? (new CommandData):InitCmd;

  OpenShared=Cmd->OpenShared;
  Format=RARFMT_NONE;
  Solid=false;
  Volume=false;
  MainComment=false;
  Locked=false;
  Signed=false;
  FirstVolume=false;
  NewNumbering=false;
  SFXSize=0;
  LatestTime.Reset();
  Protected=false;
  Encrypted=false;
  FailedHeaderDecryption=false;
  BrokenHeader=false;
  LastReadBlock=0;

  CurBlockPos=0;
  NextBlockPos=0;


  MainHead.Reset();
  CryptHead={};
  EndArcHead.Reset();

  VolNumber=0;
  VolWrite=0;
  AddingFilesSize=0;
  AddingHeadersSize=0;

  Splitting=false;
  NewArchive=false;

  SilentOpen=false;

#ifdef USE_QOPEN
  ProhibitQOpen=false;
#endif

}


Archive::~Archive()
{
  if (DummyCmd)
    delete Cmd;
}


void Archive::CheckArc(bool EnableBroken)
{
  if (!IsArchive(EnableBroken))
  {
    // If FailedHeaderDecryption is set, we already reported that archive
    // password is incorrect.
    if (!FailedHeaderDecryption)
      uiMsg(UIERROR_BADARCHIVE,FileName);
    ErrHandler.Exit(RARX_FATAL);
  }
}


#if !defined(SFX_MODULE)
void Archive::CheckOpen(const std::wstring &Name)
{
  TOpen(Name);
  CheckArc(false);
}
#endif


bool Archive::WCheckOpen(const std::wstring &Name)
{
  if (!WOpen(Name))
    return false;
  if (!IsArchive(false))
  {
    uiMsg(UIERROR_BADARCHIVE,FileName);
    Close();
    return false;
  }
  return true;
}


RARFORMAT Archive::IsSignature(const byte *D,size_t Size)
{
  RARFORMAT Type=RARFMT_NONE;
  if (Size>=1 && D[0]==0x52)
#ifndef SFX_MODULE
    if (Size>=4 && D[1]==0x45 && D[2]==0x7e && D[3]==0x5e)
      Type=RARFMT14;
    else
#endif
      if (Size>=7 && D[1]==0x61 && D[2]==0x72 && D[3]==0x21 && D[4]==0x1a && D[5]==0x07)
      {
        // We check the last signature byte, so we can return a sensible
        // warning in case we'll want to change the archive format
        // sometimes in the future.
        if (D[6]==0)
          Type=RARFMT15;
        else
          if (D[6]==1)
            Type=RARFMT50;
          else
            if (D[6]>1 && D[6]<5)
              Type=RARFMT_FUTURE;
      }
  return Type;
}


bool Archive::IsArchive(bool EnableBroken)
{
  Encrypted=false;
  BrokenHeader=false; // Might be left from previous volume.
  
#ifndef SFX_MODULE
  if (IsDevice())
  {
    uiMsg(UIERROR_INVALIDNAME,FileName,FileName);
    return false;
  }
#endif
  if (Read(MarkHead.Mark,SIZEOF_MARKHEAD3)!=SIZEOF_MARKHEAD3)
    return false;
  SFXSize=0;
  
  RARFORMAT Type;
  if ((Type=IsSignature(MarkHead.Mark,SIZEOF_MARKHEAD3))!=RARFMT_NONE)
  {
    Format=Type;
    if (Format==RARFMT14)
      Seek(Tell()-SIZEOF_MARKHEAD3,SEEK_SET);
  }
  else
  {
    std::vector<char> Buffer(MAXSFXSIZE);
    long CurPos=(long)Tell();
    int ReadSize=Read(Buffer.data(),Buffer.size()-16);
    for (int I=0;I<ReadSize;I++)
      if (Buffer[I]==0x52 && (Type=IsSignature((byte *)&Buffer[I],ReadSize-I))!=RARFMT_NONE)
      {
        Format=Type;
        if (Format==RARFMT14 && I>0 && CurPos<28 && ReadSize>31)
        {
          char *D=&Buffer[28-CurPos];
          if (D[0]!=0x52 || D[1]!=0x53 || D[2]!=0x46 || D[3]!=0x58)
            continue;
        }
        SFXSize=CurPos+I;
        Seek(SFXSize,SEEK_SET);
        if (Format==RARFMT15 || Format==RARFMT50)
          Read(MarkHead.Mark,SIZEOF_MARKHEAD3);
        break;
      }
    if (SFXSize==0)
      return false;
  }
  if (Format==RARFMT_FUTURE)
  {
    uiMsg(UIERROR_NEWRARFORMAT,FileName);
    return false;
  }
  if (Format==RARFMT50) // RAR 5.0 signature is by one byte longer.
  {
    if (Read(MarkHead.Mark+SIZEOF_MARKHEAD3,1)!=1 || MarkHead.Mark[SIZEOF_MARKHEAD3]!=0)
      return false;
    MarkHead.HeadSize=SIZEOF_MARKHEAD5;
  }
  else
    MarkHead.HeadSize=SIZEOF_MARKHEAD3;

#ifdef RARDLL
  // If callback function is not set, we cannot get the password,
  // so we skip the initial header processing for encrypted header archive.
  // It leads to skipped archive comment, but the rest of archive data
  // is processed correctly.
  if (Cmd->Callback==NULL)
    SilentOpen=true;
#endif

  bool HeadersLeft; // Any headers left to read.
  bool StartFound=false; // Main or encryption headers found.
  // Skip the archive encryption header if any and read the main header.
  while ((HeadersLeft=(ReadHeader()!=0))==true) // Additional parentheses to silence Clang.
  {
    SeekToNext();

    HEADER_TYPE Type=GetHeaderType();
    // In RAR 5.0 we need to quit after reading HEAD_CRYPT if we wish to
    // avoid the password prompt.
    StartFound=Type==HEAD_MAIN || SilentOpen && Type==HEAD_CRYPT;
    if (StartFound)
      break;
  }

  
  // We should not do it for EnableBroken or we'll get 'not RAR archive'
  // messages when extracting encrypted archives with wrong password.
  if (FailedHeaderDecryption && !EnableBroken)
    return false;

  if (BrokenHeader || !StartFound) // Main archive header is corrupt or missing.
  {
    if (!FailedHeaderDecryption) // If not reported a wrong password already.
      uiMsg(UIERROR_MHEADERBROKEN,FileName);
    if (!EnableBroken)
      return false;
  }

  MainComment=MainHead.CommentInHeader;

  // If we process non-encrypted archive or can request a password,
  // we set 'first volume' flag based on file attributes below.
  // It is necessary for RAR 2.x archives, which did not have 'first volume'
  // flag in main header. Also for all RAR formats we need to scan until
  // first file header to set "comment" flag when reading service header.
  // Unless we are in silent mode, we need to know about presence of comment
  // immediately after IsArchive call.
  if (HeadersLeft && (!SilentOpen || !Encrypted) && IsSeekable())
  {
    int64 SavePos=Tell();
    int64 SaveCurBlockPos=CurBlockPos,SaveNextBlockPos=NextBlockPos;
    HEADER_TYPE SaveCurHeaderType=CurHeaderType;

    while (ReadHeader()!=0)
    {
      HEADER_TYPE HeaderType=GetHeaderType();
      if (HeaderType==HEAD_SERVICE)
      {
        // If we have a split service headers, it surely indicates non-first
        // volume. But not split service header does not guarantee the first
        // volume, because we can have split file after non-split archive
        // comment. So we do not quit from loop here.
        FirstVolume=Volume && !SubHead.SplitBefore;
      }
      else
        if (HeaderType==HEAD_FILE)
        {
          FirstVolume=Volume && !FileHead.SplitBefore;
          break;
        }
        else
          if (HeaderType==HEAD_ENDARC) // Might happen if archive contains only a split service header.
            break;
      SeekToNext();
    }
    CurBlockPos=SaveCurBlockPos;
    NextBlockPos=SaveNextBlockPos;
    CurHeaderType=SaveCurHeaderType;
    Seek(SavePos,SEEK_SET);
  }
  if (!Volume || FirstVolume)
    FirstVolumeName=FileName;

  return true;
}




void Archive::SeekToNext()
{
  Seek(NextBlockPos,SEEK_SET);
}






// Calculate the block size including encryption fields and padding if any.
uint Archive::FullHeaderSize(size_t Size)
{
  if (Encrypted)
  {
    Size = ALIGN_VALUE(Size, CRYPT_BLOCK_SIZE); // Align to encryption block size.
    if (Format == RARFMT50)
      Size += SIZE_INITV;
    else
      Size += SIZE_SALT30;
  }
  return uint(Size);
}




#ifdef USE_QOPEN
bool Archive::Open(const std::wstring &Name,uint Mode)
{
  // Important if we reuse Archive object and it has virtual QOpen
  // file position not matching real. For example, for 'l -v volname'.
  QOpen.Unload();

  return File::Open(Name,Mode);
}


int Archive::Read(void *Data,size_t Size)
{
  size_t Result;
  if (QOpen.Read(Data,Size,Result))
    return (int)Result;
  return File::Read(Data,Size);
}


void Archive::Seek(int64 Offset,int Method)
{
  if (!QOpen.Seek(Offset,Method))
    File::Seek(Offset,Method);
}


int64 Archive::Tell()
{
  int64 QPos;
  if (QOpen.Tell(&QPos))
    return QPos;
  return File::Tell();
}
#endif


// Return 0 if dictionary size is invalid. If size is RAR7 only, return
// the adjusted nearest bottom value. Return header flags in Flags.
uint64 Archive::GetWinSize(uint64 Size,uint &Flags)
{
  Flags=0;
  // Allow 128 KB - 1 TB range.
  if (Size<0x20000 || Size>0x10000000000ULL)
    return 0;
  uint64 Pow2=0x20000; // Power of 2 dictionary size.
  for (;2*Pow2<=Size;Pow2*=2)
    Flags+=FCI_DICT_BIT0;
  if (Size==Pow2)
    return Size;  // If 'Size' is the power of 2, return it as is.

  // Get the number of Pow2/32 to add to Pow2 for nearest value not exceeding 'Size'.
  uint64 Fraction=(Size-Pow2)/(Pow2/32);
  Flags+=(uint)Fraction*FCI_DICT_FRACT0;
  return Pow2+Fraction*(Pow2/32);
}
