/*
 * Copyright (c) 2005 Novell, Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, contact Novell, Inc.
 *
 * To contact Novell about this file by physical or electronic mail,
 * you may find current contact information at www.novell.com 
 *
 * Author		: Rohit Kumar
 * Email ID	: rokumar@novell.com
 * Date		: 14th July 2005
 */
 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <dirent.h>
#include <utime.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <rfb/rfb.h>
#include "rfbtightproto.h"
#include "filelistinfo.h"
#include "filetransfermsg.h"
#include "handlefiletransferrequest.h"

#define SZ_RFBBLOCKSIZE 8192


void
FreeFileTransferMsg(FileTransferMsg ftm)
{

	if(ftm.data != NULL) {
		free(ftm.data);
		ftm.data = NULL;
	}

	ftm.length = 0;

}


/******************************************************************************
 * Methods to handle file list request.
 ******************************************************************************/

int CreateFileListInfo(FileListInfoPtr pFileListInfo, char* path, int flag);
FileTransferMsg CreateFileListErrMsg(char flags);
FileTransferMsg CreateFileListMsg(FileListInfo fileListInfo, char flags);


/*
 * This is the method called by HandleFileListRequest to get the file list
 */

FileTransferMsg 
GetFileListResponseMsg(char* path, char flags)
{
	FileTransferMsg fileListMsg;
	FileListInfo fileListInfo;
	int status = -1;
	
	memset(&fileListMsg, 0, sizeof(FileTransferMsg));
	memset(&fileListInfo, 0, sizeof(FileListInfo));

	
	 /* fileListInfo can have null data if the folder is Empty 
    	or if some error condition has occured.
    	The return value is 'failure' only if some error condition has occured.
	 */
	status = CreateFileListInfo(&fileListInfo, path, !(flags  & 0x10));

	if(status == FAILURE) {
		fileListMsg = CreateFileListErrMsg(flags);
	}
	else {
		/* DisplayFileList(fileListInfo); For Debugging  */
		
		fileListMsg = CreateFileListMsg(fileListInfo, flags);
		FreeFileListInfo(fileListInfo);
	}
	
	return fileListMsg;
}

#ifndef __GNUC__
#define __FUNCTION__ "unknown"
#endif

int
CreateFileListInfo(FileListInfoPtr pFileListInfo, char* path, int flag)
{
	DIR* pDir = NULL;
	struct dirent* pDirent = NULL;
	
	if((path == NULL) || (strlen(path) == 0)) {
		/* In this case we will send the list of entries in ftp root*/
		sprintf(path, "%s%s", GetFtpRoot(), "/");
	}

	if((pDir = opendir(path)) == NULL) {
		rfbLog("File [%s]: Method [%s]: not able to open the dir\n",
				__FILE__, __FUNCTION__);
		return FAILURE; 		
	}

	while((pDirent = readdir(pDir))) {
		if(strcmp(pDirent->d_name, ".") && strcmp(pDirent->d_name, "..")) {
			struct stat stat_buf;
			/*
			int fpLen = sizeof(char)*(strlen(pDirent->d_name)+strlen(path)+2);
			*/
			char fullpath[PATH_MAX];

			memset(fullpath, 0, PATH_MAX);

			strcpy(fullpath, path);
			if(path[strlen(path)-1] != '/')
				strcat(fullpath, "/");
			strcat(fullpath, pDirent->d_name);

			if(stat(fullpath, &stat_buf) < 0) {
				rfbLog("File [%s]: Method [%s]: Reading stat for file %s failed\n", 
						__FILE__, __FUNCTION__, fullpath);
				continue;
			}

			if(S_ISDIR(stat_buf.st_mode)) {
				if(AddFileListItemInfo(pFileListInfo, pDirent->d_name, -1, 0) == 0) {
					rfbLog("File [%s]: Method [%s]: Add directory %s in the"
							" list failed\n", __FILE__, __FUNCTION__, fullpath);
					continue;
				}
			}
			else {
				if(flag) {
					if(AddFileListItemInfo(pFileListInfo, pDirent->d_name, 
												stat_buf.st_size, 
												stat_buf.st_mtime) == 0) {
						rfbLog("File [%s]: Method [%s]: Add file %s in the "
								"list failed\n", __FILE__, __FUNCTION__, fullpath);
						continue;
					}			
				}
			}
		}
	}
	if(closedir(pDir) < 0) {
	    rfbLog("File [%s]: Method [%s]: ERROR Couldn't close dir\n",
	    	__FILE__, __FUNCTION__);
	}
	
	return SUCCESS;
}


FileTransferMsg
CreateFileListErrMsg(char flags)
{
	FileTransferMsg fileListMsg;
	rfbFileListDataMsg* pFLD = NULL;
	char* data = NULL;
	unsigned int length = 0;

	memset(&fileListMsg, 0, sizeof(FileTransferMsg));

	data = (char*) calloc(sizeof(rfbFileListDataMsg), sizeof(char));
	if(data == NULL) {
		return fileListMsg;
	}
	length = sizeof(rfbFileListDataMsg) * sizeof(char);
	pFLD = (rfbFileListDataMsg*) data;
	
	pFLD->type = rfbFileListData;
	pFLD->numFiles = Swap16IfLE(0);
	pFLD->dataSize = Swap16IfLE(0);
	pFLD->compressedSize = Swap16IfLE(0);
	pFLD->flags = flags | 0x80;

	fileListMsg.data = data;
	fileListMsg.length = length;

	return fileListMsg;
}


FileTransferMsg
CreateFileListMsg(FileListInfo fileListInfo, char flags)
{
	FileTransferMsg fileListMsg;
	rfbFileListDataMsg* pFLD = NULL;
	char *data = NULL, *pFileNames = NULL;
	unsigned int length = 0, dsSize = 0, i = 0;
	FileListItemSizePtr pFileListItemSize = NULL;

	memset(&fileListMsg, 0, sizeof(FileTransferMsg));
	dsSize = fileListInfo.numEntries * 8;
	length = sz_rfbFileListDataMsg + dsSize + 
			GetSumOfFileNamesLength(fileListInfo) + 
			fileListInfo.numEntries;

	data = (char*) calloc(length, sizeof(char));
	if(data == NULL) {
		return fileListMsg;
	}
	pFLD = (rfbFileListDataMsg*) data;
	pFileListItemSize = (FileListItemSizePtr) &data[sz_rfbFileListDataMsg];
	pFileNames = &data[sz_rfbFileListDataMsg + dsSize];

	pFLD->type            = rfbFileListData;
    pFLD->flags 		  = flags & 0xF0;
    pFLD->numFiles 		  = Swap16IfLE(fileListInfo.numEntries);
    pFLD->dataSize 		  = Swap16IfLE(GetSumOfFileNamesLength(fileListInfo) + 
    									fileListInfo.numEntries);
    pFLD->compressedSize  = pFLD->dataSize;

	for(i =0; i <fileListInfo.numEntries; i++) {
		pFileListItemSize[i].size = Swap32IfLE(GetFileSizeAt(fileListInfo, i));
		pFileListItemSize[i].data = Swap32IfLE(GetFileDataAt(fileListInfo, i));
		strcpy(pFileNames, GetFileNameAt(fileListInfo, i));
		
		if(i+1 < fileListInfo.numEntries)
			pFileNames += strlen(pFileNames) + 1;
	}

	fileListMsg.data 	= data;
	fileListMsg.length 	= length;

	return fileListMsg;
}


/******************************************************************************
 * Methods to handle File Download Request.
 ******************************************************************************/

FileTransferMsg CreateFileDownloadErrMsg(char* reason, unsigned int reasonLen);
FileTransferMsg CreateFileDownloadZeroSizeDataMsg(unsigned long mTime);
FileTransferMsg CreateFileDownloadBlockSizeDataMsg(unsigned short sizeFile, char *pFile);

FileTransferMsg 
GetFileDownLoadErrMsg()
{
	FileTransferMsg fileDownloadErrMsg;

	char reason[] = "An internal error on the server caused download failure";
	int reasonLen = strlen(reason);

	memset(&fileDownloadErrMsg, 0, sizeof(FileTransferMsg));
	
	fileDownloadErrMsg = CreateFileDownloadErrMsg(reason, reasonLen);

	return fileDownloadErrMsg;
}


FileTransferMsg
GetFileDownloadReadDataErrMsg()
{
	char reason[] = "Cannot open file, perhaps it is absent or is a directory";
	int reasonLen = strlen(reason);

	return CreateFileDownloadErrMsg(reason, reasonLen);

}


FileTransferMsg
GetFileDownloadLengthErrResponseMsg()
{
	char reason [] = "Path length exceeds PATH_MAX (4096) bytes";
	int reasonLen = strlen(reason);

	return CreateFileDownloadErrMsg(reason, reasonLen);
}


FileTransferMsg
GetFileDownloadResponseMsgInBlocks(rfbClientPtr cl, rfbTightClientPtr rtcp)
{
	/* const unsigned int sz_rfbBlockSize = SZ_RFBBLOCKSIZE; */
    int numOfBytesRead = 0;
	char pBuf[SZ_RFBBLOCKSIZE];
	char* path = rtcp->rcft.rcfd.fName;

	memset(pBuf, 0, SZ_RFBBLOCKSIZE);

	if((rtcp->rcft.rcfd.downloadInProgress == FALSE) && (rtcp->rcft.rcfd.downloadFD == -1)) {
		if((rtcp->rcft.rcfd.downloadFD = open(path, O_RDONLY)) == -1) {
			rfbLog("File [%s]: Method [%s]: Error: Couldn't open file\n", 
					__FILE__, __FUNCTION__);
			return GetFileDownloadReadDataErrMsg();
		}
		rtcp->rcft.rcfd.downloadInProgress = TRUE;
	}
	if((rtcp->rcft.rcfd.downloadInProgress == TRUE) && (rtcp->rcft.rcfd.downloadFD != -1)) {
		if( (numOfBytesRead = read(rtcp->rcft.rcfd.downloadFD, pBuf, SZ_RFBBLOCKSIZE)) <= 0) {
			close(rtcp->rcft.rcfd.downloadFD);
			rtcp->rcft.rcfd.downloadFD = -1;
			rtcp->rcft.rcfd.downloadInProgress = FALSE;
			if(numOfBytesRead == 0) {
				return CreateFileDownloadZeroSizeDataMsg(rtcp->rcft.rcfd.mTime);
			}			
			return GetFileDownloadReadDataErrMsg();
		}
	return CreateFileDownloadBlockSizeDataMsg(numOfBytesRead, pBuf);
	}
	return GetFileDownLoadErrMsg();
}


FileTransferMsg
ChkFileDownloadErr(rfbClientPtr cl, rfbTightClientPtr rtcp)
{
    FileTransferMsg fileDownloadMsg;
	struct stat stat_buf;
	int sz_rfbFileSize = 0;
	char* path = rtcp->rcft.rcfd.fName;

	memset(&fileDownloadMsg, 0, sizeof(FileTransferMsg));

	if( (path == NULL) || (strlen(path) == 0) ||
		(stat(path, &stat_buf) < 0) || (!(S_ISREG(stat_buf.st_mode))) ) {

			char reason[] = "Cannot open file, perhaps it is absent or is not a regular file";
			int reasonLen = strlen(reason);

			rfbLog("File [%s]: Method [%s]: Reading stat for path %s failed\n", 
					__FILE__, __FUNCTION__, path);	
			
			fileDownloadMsg = CreateFileDownloadErrMsg(reason, reasonLen);
	}
	else {
		rtcp->rcft.rcfd.mTime = stat_buf.st_mtime;
		sz_rfbFileSize = stat_buf.st_size;
		if(sz_rfbFileSize <= 0) {
			fileDownloadMsg = CreateFileDownloadZeroSizeDataMsg(stat_buf.st_mtime);
		}

	}
	return fileDownloadMsg;
}


FileTransferMsg
CreateFileDownloadErrMsg(char* reason, unsigned int reasonLen)
{
	FileTransferMsg fileDownloadErrMsg;
	int length = sz_rfbFileDownloadFailedMsg + reasonLen + 1;
	rfbFileDownloadFailedMsg *pFDF = NULL;
	char *pFollow = NULL;
	
	char *pData = (char*) calloc(length, sizeof(char));
	memset(&fileDownloadErrMsg, 0, sizeof(FileTransferMsg));
	if(pData == NULL) {
		rfbLog("File [%s]: Method [%s]: pData is NULL\n",
				__FILE__, __FUNCTION__);	
		return fileDownloadErrMsg;
	}

	pFDF = (rfbFileDownloadFailedMsg *) pData;
	pFollow = &pData[sz_rfbFileDownloadFailedMsg];
	
	pFDF->type = rfbFileDownloadFailed;
	pFDF->reasonLen = Swap16IfLE(reasonLen);
	memcpy(pFollow, reason, reasonLen);

	fileDownloadErrMsg.data	= pData;
	fileDownloadErrMsg.length	= length;

	return fileDownloadErrMsg;
}


FileTransferMsg
CreateFileDownloadZeroSizeDataMsg(unsigned long mTime)
{
	FileTransferMsg fileDownloadZeroSizeDataMsg;
	int length = sz_rfbFileDownloadDataMsg + sizeof(int);
	rfbFileDownloadDataMsg *pFDD = NULL;
	char *pFollow = NULL;
	
	char *pData = (char*) calloc(length, sizeof(char));
	memset(&fileDownloadZeroSizeDataMsg, 0, sizeof(FileTransferMsg));
	if(pData == NULL) {
		rfbLog("File [%s]: Method [%s]: pData is NULL\n",
				__FILE__, __FUNCTION__);	
		return fileDownloadZeroSizeDataMsg;
	}

	pFDD = (rfbFileDownloadDataMsg *) pData;
	pFollow = &pData[sz_rfbFileDownloadDataMsg];
	
	pFDD->type = rfbFileDownloadData;
	pFDD->compressLevel = 0;
	pFDD->compressedSize = Swap16IfLE(0);
	pFDD->realSize = Swap16IfLE(0);
	
	memcpy(pFollow, &mTime, sizeof(unsigned long));

	fileDownloadZeroSizeDataMsg.data	= pData;
	fileDownloadZeroSizeDataMsg.length	= length;

	return fileDownloadZeroSizeDataMsg;

}


FileTransferMsg
CreateFileDownloadBlockSizeDataMsg(unsigned short sizeFile, char *pFile)
{
	FileTransferMsg fileDownloadBlockSizeDataMsg;
	int length = sz_rfbFileDownloadDataMsg + sizeFile;
	rfbFileDownloadDataMsg *pFDD = NULL;
	char *pFollow = NULL;
	
	char *pData = (char*) calloc(length, sizeof(char));
	memset(&fileDownloadBlockSizeDataMsg, 0, sizeof(FileTransferMsg));
	if(NULL == pData) {
		rfbLog("File [%s]: Method [%s]: pData is NULL\n",
				__FILE__, __FUNCTION__);	
		return fileDownloadBlockSizeDataMsg;
	}

	pFDD = (rfbFileDownloadDataMsg *) pData;
	pFollow = &pData[sz_rfbFileDownloadDataMsg];
	
	pFDD->type = rfbFileDownloadData;
	pFDD->compressLevel = 0;
	pFDD->compressedSize = Swap16IfLE(sizeFile);
	pFDD->realSize = Swap16IfLE(sizeFile);
	
	memcpy(pFollow, pFile, sizeFile);

	fileDownloadBlockSizeDataMsg.data	= pData;
	fileDownloadBlockSizeDataMsg.length	= length;

	return fileDownloadBlockSizeDataMsg;

}


/******************************************************************************
 * Methods to handle file upload request
 ******************************************************************************/

FileTransferMsg CreateFileUploadErrMsg(char* reason, unsigned int reasonLen);

FileTransferMsg 
GetFileUploadLengthErrResponseMsg()
{
	char reason [] = "Path length exceeds PATH_MAX (4096) bytes";
	int reasonLen = strlen(reason);

	return CreateFileUploadErrMsg(reason, reasonLen);
}


FileTransferMsg
ChkFileUploadErr(rfbClientPtr cl, rfbTightClientPtr rtcp)
{
    FileTransferMsg fileUploadErrMsg;

	memset(&fileUploadErrMsg, 0, sizeof(FileTransferMsg));
	if( (rtcp->rcft.rcfu.fName == NULL) ||
		(strlen(rtcp->rcft.rcfu.fName) == 0) ||
		((rtcp->rcft.rcfu.uploadFD = creat(rtcp->rcft.rcfu.fName, 
		S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) == -1)) {

			char reason[] = "Could not create file";
			int reasonLen = strlen(reason);
			fileUploadErrMsg = CreateFileUploadErrMsg(reason, reasonLen);
	}
	else
		rtcp->rcft.rcfu.uploadInProgress = TRUE;
	
	return fileUploadErrMsg;
}


FileTransferMsg
GetFileUploadCompressedLevelErrMsg()
{
	char reason[] = "Server does not support data compression on upload";
	int reasonLen = strlen(reason);

	return CreateFileUploadErrMsg(reason, reasonLen);
}


FileTransferMsg
ChkFileUploadWriteErr(rfbClientPtr cl, rfbTightClientPtr rtcp, char* pBuf)
{
	FileTransferMsg ftm;
	unsigned long numOfBytesWritten = 0;

	memset(&ftm, 0, sizeof(FileTransferMsg));

	numOfBytesWritten = write(rtcp->rcft.rcfu.uploadFD, pBuf, rtcp->rcft.rcfu.fSize);

	if(numOfBytesWritten != rtcp->rcft.rcfu.fSize) {		
		char reason[] = "Error writing file data";
		int reasonLen = strlen(reason);
		ftm = CreateFileUploadErrMsg(reason, reasonLen);
		CloseUndoneFileTransfer(cl, rtcp);
	}		
	return ftm;
}


void
FileUpdateComplete(rfbClientPtr cl, rfbTightClientPtr rtcp)
{
	/* Here we are settimg the modification and access time of the file */
	/* Windows code stes mod/access/creation time of the file */
	struct utimbuf utb;

	utb.actime = utb.modtime = rtcp->rcft.rcfu.mTime;
	if(utime(rtcp->rcft.rcfu.fName, &utb) == -1) {
		rfbLog("File [%s]: Method [%s]: Setting the modification/access"
				" time for the file <%s> failed\n", __FILE__, 
				__FUNCTION__, rtcp->rcft.rcfu.fName);
	}

	if(rtcp->rcft.rcfu.uploadFD != -1) {
		close(rtcp->rcft.rcfu.uploadFD);
		rtcp->rcft.rcfu.uploadFD = -1;
		rtcp->rcft.rcfu.uploadInProgress = FALSE;
	}
}


FileTransferMsg
CreateFileUploadErrMsg(char* reason, unsigned int reasonLen)
{
	FileTransferMsg fileUploadErrMsg;
	int length = sz_rfbFileUploadCancelMsg + reasonLen;
	rfbFileUploadCancelMsg *pFDF = NULL;
	char *pFollow = NULL;
	
	char *pData = (char*) calloc(length, sizeof(char));
	memset(&fileUploadErrMsg, 0, sizeof(FileTransferMsg));
	if(pData == NULL) {
		rfbLog("File [%s]: Method [%s]: pData is NULL\n",
				__FILE__, __FUNCTION__);	
		return fileUploadErrMsg;
	}

	pFDF = (rfbFileUploadCancelMsg *) pData;
	pFollow = &pData[sz_rfbFileUploadCancelMsg];
	
	pFDF->type = rfbFileUploadCancel;
	pFDF->reasonLen = Swap16IfLE(reasonLen);
	memcpy(pFollow, reason, reasonLen);

	fileUploadErrMsg.data		= pData;
	fileUploadErrMsg.length		= length;

	return fileUploadErrMsg;
}


/******************************************************************************
 * Method to cancel File Transfer operation.
 ******************************************************************************/

void
CloseUndoneFileTransfer(rfbClientPtr cl, rfbTightClientPtr rtcp)
{
	/* TODO :: File Upload case is not handled currently */
	/* TODO :: In case of concurrency we need to use Critical Section */

	if(cl == NULL)
		return;

	
	if(rtcp->rcft.rcfu.uploadInProgress == TRUE) {
		rtcp->rcft.rcfu.uploadInProgress = FALSE;

		if(rtcp->rcft.rcfu.uploadFD != -1) {
			close(rtcp->rcft.rcfu.uploadFD);
			rtcp->rcft.rcfu.uploadFD = -1;
		}

		if(unlink(rtcp->rcft.rcfu.fName) == -1) {
			rfbLog("File [%s]: Method [%s]: Delete operation on file <%s> failed\n", 
					__FILE__, __FUNCTION__, rtcp->rcft.rcfu.fName);
		}

		memset(rtcp->rcft.rcfu.fName, 0 , PATH_MAX);
	}
	
	if(rtcp->rcft.rcfd.downloadInProgress == TRUE) {
		rtcp->rcft.rcfd.downloadInProgress = FALSE;

		if(rtcp->rcft.rcfd.downloadFD != -1) {			
			close(rtcp->rcft.rcfd.downloadFD);
			rtcp->rcft.rcfd.downloadFD = -1;
		}
		memset(rtcp->rcft.rcfd.fName, 0 , PATH_MAX);
	}
}


/******************************************************************************
 * Method to handle create directory request.
 ******************************************************************************/

void
CreateDirectory(char* dirName)
{
	if(dirName == NULL) return;

	if(mkdir(dirName, 0700) == -1) {
		rfbLog("File [%s]: Method [%s]: Create operation for directory <%s> failed\n", 
				__FILE__, __FUNCTION__, dirName);
	}
}