#include "rar.hpp"

#include "unrar.h"
#include "unicode.hpp"
#include "encname.hpp"

// arcread.cpp
unrar_err_t Archive::ReadHeader()
{
	CurBlockPos=Tell();

#ifndef SFX_MODULE
	if (OldFormat)
	{
		ReadOldHeader();

		if ( Raw.Size() == 0 )
			return unrar_err_arc_eof; // right at end of file

		if ( Raw.PaddedSize() > 0 ) // added check
			return unrar_err_corrupt; // missing data

		return unrar_ok;
	}
#endif

	Raw.Reset();

	// (removed decryption)

	Raw.Read(SIZEOF_SHORTBLOCKHEAD);
	if (Raw.Size()==0)
	{
		return unrar_err_arc_eof; // right at end of file
	}
	
	Raw.Get(ShortBlock.HeadCRC);
	byte HeadType;
	Raw.Get(HeadType);
	ShortBlock.HeadType=(HEADER_TYPE)HeadType;
	Raw.Get(ShortBlock.Flags);
	Raw.Get(ShortBlock.HeadSize);
	if (ShortBlock.HeadSize<SIZEOF_SHORTBLOCKHEAD)
	{
		return unrar_err_corrupt; // invalid HeadSize
	}
	
	// catches unknown block types and reading something that isn't even a header
	check( MARK_HEAD <= ShortBlock.HeadType && ShortBlock.HeadType <= ENDARC_HEAD );

	if (ShortBlock.HeadType==COMM_HEAD)
		Raw.Read(SIZEOF_COMMHEAD-SIZEOF_SHORTBLOCKHEAD);
	else
		if (ShortBlock.HeadType==MAIN_HEAD && (ShortBlock.Flags & MHD_COMMENT)!=0)
			Raw.Read(SIZEOF_NEWMHD-SIZEOF_SHORTBLOCKHEAD);
		else
			Raw.Read(ShortBlock.HeadSize-SIZEOF_SHORTBLOCKHEAD);

	if ( Raw.PaddedSize() > 0 ) // fewer than requested bytes read above?
		return unrar_err_corrupt; // missing data

	NextBlockPos=CurBlockPos+ShortBlock.HeadSize;

	switch(ShortBlock.HeadType)
	{
	case MAIN_HEAD:
		*(BaseBlock *)&NewMhd=ShortBlock;
		Raw.Get(NewMhd.HighPosAV);
		Raw.Get(NewMhd.PosAV);
		check( Raw.ReadPos == Raw.DataSize ); // we should have read all fields
		break;
	case FILE_HEAD:
	case NEWSUB_HEAD:
		{
			FileHeader *hd=ShortBlock.HeadType==FILE_HEAD ? &NewLhd:&SubHead;
			*(BaseBlock *)hd=ShortBlock;
			Raw.Get(hd->PackSize);
			Raw.Get(hd->UnpSize);
			Raw.Get(hd->HostOS);
			Raw.Get(hd->FileCRC);
			Raw.Get(hd->FileTime);
			Raw.Get(hd->UnpVer);
			Raw.Get(hd->Method);
			Raw.Get(hd->NameSize);
			Raw.Get(hd->FileAttr);
			if (hd->Flags & LHD_LARGE)
			{
				Raw.Get(hd->HighPackSize);
				Raw.Get(hd->HighUnpSize);
			}
			else
			{
				hd->HighPackSize=hd->HighUnpSize=0;
				if (hd->UnpSize==0xffffffff)
				{
					// TODO: what the heck is this for anyway?
					hd->UnpSize=0;
					hd->HighUnpSize=0x7fffffff;
				}
			}
			hd->FullPackSize=int32to64(hd->HighPackSize,hd->PackSize);
			hd->FullUnpSize=int32to64(hd->HighUnpSize,hd->UnpSize);

			if ( int32to64( 1, 0 ) == 0 && (hd->HighPackSize || hd->HighUnpSize) )
				return unrar_err_huge;

			char (&FileName) [sizeof hd->FileName] = hd->FileName; // eliminated local buffer
			int NameSize=Min(hd->NameSize,sizeof(FileName)-1);
			Raw.Get((byte *)FileName,NameSize);
			FileName[NameSize]=0;

			if (hd->HeadType==NEWSUB_HEAD)
			{
				// have to adjust this, even through we're ignoring this block
				NextBlockPos+=hd->FullPackSize;
				break;
			}
			else
				if (hd->HeadType==FILE_HEAD)
				{
					if (hd->Flags & LHD_UNICODE)
					{
						EncodeFileName NameCoder;
						int Length=strlen(FileName);
						if (Length==hd->NameSize)
						{
							UtfToWide(FileName,hd->FileNameW,sizeof(hd->FileNameW)/sizeof(hd->FileNameW[0])-1);
							WideToChar(hd->FileNameW,hd->FileName,sizeof(hd->FileName)/sizeof(hd->FileName[0])-1);
							ExtToInt(hd->FileName,hd->FileName);
						}
						else
						{
							Length++;
							NameCoder.Decode(FileName,(byte *)FileName+Length,
									hd->NameSize-Length,hd->FileNameW,
									sizeof(hd->FileNameW)/sizeof(hd->FileNameW[0]));
						}
						if (*hd->FileNameW==0)
							hd->Flags &= ~LHD_UNICODE;
					}
					else
						*hd->FileNameW=0;
					
					ConvertUnknownHeader();
				}
			if (hd->Flags & LHD_SALT)
				Raw.Get(hd->Salt,SALT_SIZE);
			hd->mtime.SetDos(hd->FileTime);
			if (hd->Flags & LHD_EXTTIME)
			{
				ushort Flags;
				Raw.Get(Flags);
				// Ignore additional time information
				for (int I=0;I<4;I++)
				{
					uint rmode=Flags>>(3-I)*4;
					if ((rmode & 8)==0)
						continue;
					if (I!=0)
					{
						uint DosTime;
						Raw.Get(DosTime);
					}

					// skip time info
		            int count=rmode&3;
		            for (int J=0;J<count;J++)
					{
						byte CurByte;
						Raw.Get(CurByte);
					}
				}
			}
			NextBlockPos+=hd->FullPackSize;
			bool CRCProcessedOnly=(hd->Flags & LHD_COMMENT)!=0;
			HeaderCRC=~Raw.GetCRC(CRCProcessedOnly)&0xffff;
			if (hd->HeadCRC!=HeaderCRC)
				return unrar_err_corrupt;
			check( CRCProcessedOnly == false ); // I need to test on archives where this doesn't hold
			check( Raw.ReadPos == Raw.DataSize ); // we should have read all fields
		}
		break;
#ifndef SFX_MODULE
	// Handle these block types just so we can adjust NextBlockPos properly
	case PROTECT_HEAD:
		Raw.Get(ProtectHead.DataSize);
		NextBlockPos+=ProtectHead.DataSize;
		break;
	case SUB_HEAD:
		Raw.Get(SubBlockHead.DataSize);
		NextBlockPos+=SubBlockHead.DataSize;
		break;
#endif
	default:
		if (ShortBlock.Flags & LONG_BLOCK)
		{
			uint DataSize;
			Raw.Get(DataSize);
			NextBlockPos+=DataSize;
		}
		break;
	}
	HeaderCRC=~Raw.GetCRC(false)&0xffff;
	CurHeaderType=ShortBlock.HeadType;
	// (removed decryption)

	if (NextBlockPos<CurBlockPos+Raw.Size())
		return unrar_err_corrupt; // next block isn't past end of current block's header
	
	// If pos went negative, then unrar_pos_t is only 32 bits and it overflowed
	if ( NextBlockPos < 0 )
		return unrar_err_huge;
	
	return unrar_ok;
}

// Rar.Read()s are checked by caller of ReadOldHeader() (see above)
#ifndef SFX_MODULE
int Archive::ReadOldHeader()
{
	Raw.Reset();
	if (CurBlockPos<=SFXSize)
	{
		Raw.Read(SIZEOF_OLDMHD);
		Raw.Get(OldMhd.Mark,4);
		Raw.Get(OldMhd.HeadSize);
		Raw.Get(OldMhd.Flags);
		NextBlockPos=CurBlockPos+OldMhd.HeadSize;
		CurHeaderType=MAIN_HEAD;
	}
	else
	{
		OldFileHeader OldLhd;
		Raw.Read(SIZEOF_OLDLHD);
		NewLhd.HeadType=FILE_HEAD;
		Raw.Get(NewLhd.PackSize);
		Raw.Get(NewLhd.UnpSize);
		Raw.Get(OldLhd.FileCRC);
		Raw.Get(NewLhd.HeadSize);
		Raw.Get(NewLhd.FileTime);
		Raw.Get(OldLhd.FileAttr);
		Raw.Get(OldLhd.Flags);
		Raw.Get(OldLhd.UnpVer);
		Raw.Get(OldLhd.NameSize);
		Raw.Get(OldLhd.Method);

		NewLhd.Flags=OldLhd.Flags|LONG_BLOCK;
		NewLhd.UnpVer=(OldLhd.UnpVer==2) ? 13 : 10;
		NewLhd.Method=OldLhd.Method+0x30;
		NewLhd.NameSize=OldLhd.NameSize;
		NewLhd.FileAttr=OldLhd.FileAttr;
		NewLhd.FileCRC=OldLhd.FileCRC;
		NewLhd.FullPackSize=NewLhd.PackSize;
		NewLhd.FullUnpSize=NewLhd.UnpSize;

		NewLhd.mtime.SetDos(NewLhd.FileTime);
		// (removed other times)

		Raw.Read(OldLhd.NameSize);
		Raw.Get((byte *)NewLhd.FileName,OldLhd.NameSize);
		NewLhd.FileName[OldLhd.NameSize]=0;
		// (removed name case conversion)
		*NewLhd.FileNameW=0;

		if (Raw.Size()!=0)
			NextBlockPos=CurBlockPos+NewLhd.HeadSize+NewLhd.PackSize;
		CurHeaderType=FILE_HEAD;
	}
	return(NextBlockPos>CurBlockPos ? Raw.Size():0);
}
#endif

// (removed name case and attribute conversion)

bool Archive::IsArcDir()
{
	return((NewLhd.Flags & LHD_WINDOWMASK)==LHD_DIRECTORY);
}


bool Archive::IsArcLabel()
{
	return(NewLhd.HostOS<=HOST_WIN32 && (NewLhd.FileAttr & 8));
}

// TODO: use '\\' on Windows?
char const CPATHDIVIDER = '/';
#define charnext(s) ((s)+1)

void Archive::ConvertUnknownHeader()
{
	if (NewLhd.UnpVer<20 && (NewLhd.FileAttr & 0x10))
		NewLhd.Flags|=LHD_DIRECTORY;
	if (NewLhd.HostOS>=HOST_MAX)
	{
		if ((NewLhd.Flags & LHD_WINDOWMASK)==LHD_DIRECTORY)
			NewLhd.FileAttr=0x10;
		else
			NewLhd.FileAttr=0x20;
	}
	{
		for (char *s=NewLhd.FileName;*s!=0;s=charnext(s))
		{
			if (*s=='/' || *s=='\\')
				*s=CPATHDIVIDER;
		}
	}
	// (removed Apple Unicode handling)
	for (wchar *s=NewLhd.FileNameW;*s!=0;s++)
	{
		if (*s=='/' || *s=='\\')
			*s=CPATHDIVIDER;
	}
}