/* 
 * Copyright (C) 2012 Yee Young Han <websearch@naver.com> (http://blog.naver.com/websearch)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

#include "SipPlatformDefine.h"
#include "FtpClient.h"
#include "FileUtility.h"
#include "StringUtility.h"
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdarg.h>

#ifdef WIN32

#include <io.h>
#define OPEN_READ_FLAG	( _O_RDONLY | _O_BINARY )
#define OPEN_WRITE_FLAG	( _O_WRONLY | _O_CREAT | _O_BINARY | _O_TRUNC )
#define OPEN_READ_MODE	_S_IREAD
#define OPEN_WRITE_MODE	( _S_IREAD | _S_IWRITE )

#define open		_open
#define read		_read
#define write		_write
#define close		_close
#define unlink	DeleteFile

#else

#include <unistd.h>
#define OPEN_READ_FLAG	O_RDONLY
#define OPEN_WRITE_FLAG	( O_WRONLY | O_CREAT | O_TRUNC )
#define OPEN_READ_MODE	( S_IRUSR | S_IRGRP | S_IROTH )
#define OPEN_WRITE_MODE ( S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH )

#endif

#include "FtpClientDirectory.hpp"
#include "FtpClientFile.hpp"
#include "FtpClientList.hpp"

CFtpClient::CFtpClient() : m_hSocket(INVALID_SOCKET), m_iServerPort(21), m_iTimeout(10), m_bUseUtf8(false)
{
}

CFtpClient::~CFtpClient()
{
	Close();
}

/**
 * @ingroup FtpStack
 * @brief FTP ¼­¹ö¿¡ ¿¬°áÇÑ´Ù.
 * @param pszServerIp FTP ¼­¹ö IP ÁÖ¼Ò
 * @param iServerPort FTP ¼­¹ö Æ÷Æ® ¹øÈ£
 * @param bUseUtf8		ÆÄÀÏ ÀÌ¸§À» UTF8 ·Î º¯È¯ÇÏ´Â°¡?
 * @returns FTP ¼­¹ö ¿¬°á¿¡ ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ±×·¸Áö ¾ÊÀ¸¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CFtpClient::Connect( const char * pszServerIp, int iServerPort, bool bUseUtf8 )
{
	if( m_hSocket != INVALID_SOCKET ) return false;

	m_hSocket = TcpConnect( pszServerIp, iServerPort );
	if( m_hSocket == INVALID_SOCKET )
	{
		CLog::Print( LOG_ERROR, "%s TcpConnect(%s:%d) error(%d)", __FUNCTION__, pszServerIp, iServerPort, GetError() );
		return false;
	}

	m_strServerIp = pszServerIp;
	m_iServerPort = iServerPort;
	m_bUseUtf8 = bUseUtf8;

	if( Recv( 220 ) == false )
	{
		Close();
		return false;
	}

	return true;
}

/**
 * @ingroup FtpStack
 * @brief FTP ¼­¹ö ¿¬°áÀ» Á¾·áÇÑ´Ù.
 */
void CFtpClient::Close()
{
	if( m_hSocket != INVALID_SOCKET )
	{
		closesocket( m_hSocket );
		m_hSocket = INVALID_SOCKET;
	}
}

/**
 * @ingroup FtpStack
 * @brief FTP ¼­¹ö¿¡ ·Î±×ÀÎÇÑ´Ù.
 * @param pszUserId			¾ÆÀÌµð
 * @param pszPassWord		ºñ¹Ð¹øÈ£
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ±×·¸Áö ¾ÊÀ¸¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CFtpClient::Login( const char * pszUserId, const char * pszPassWord )
{
	if( Send( "USER %s", pszUserId ) == false || 
			Recv( 331 ) == false || 
			Send( "PASS %s", pszPassWord ) == false ||
			Recv( 230 ) == false )
	{
		return false;
	}

	return true;
}

/**
 * @ingroup FtpStack
 * @brief FTP ¼­¹ö·Î binary type ¹× passive mode ¸í·ÉÀ» Àü¼ÛÇÑ´Ù.
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ±×·¸Áö ¾ÊÀ¸¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CFtpClient::SendBinaryPassive( )
{
	if( Send( "TYPE I" ) == false ||
			Recv( 200 ) == false )
	{
		return false;
	}

	CFtpResponse clsRes;

	if( Send( "PASV" ) == false ||
			Recv( clsRes, 227 ) == false )
	{
		return false;
	}

	if( clsRes.GetIpPort( m_strDataIp, m_iDataPort ) == false )
	{
		return false;
	}

	return true;
}

/**
 * @ingroup FtpStack
 * @brief FTP ¼­¹ö·Î ¸í·ÉÀ» Àü¼ÛÇÑ´Ù.
 * @param fmt			¸í·É 
 * @param	...			¸í·É ÀÎÀÚ
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ±×·¸Áö ¾ÊÀ¸¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CFtpClient::Send( const char * fmt, ... )
{
	if( m_hSocket == INVALID_SOCKET )
	{
		CLog::Print( LOG_ERROR, "%s not connected", __FUNCTION__ );
		return false;
	}

	va_list		ap;
	char			szSendBuf[8192];
	int				iSendLen;

	va_start( ap, fmt );
	iSendLen = vsnprintf( szSendBuf, sizeof(szSendBuf)-3, fmt, ap );
	va_end( ap );

	snprintf( szSendBuf + iSendLen, sizeof(szSendBuf) - iSendLen, "\r\n" );
	iSendLen += 2;
	
	if( TcpSend( m_hSocket, szSendBuf, iSendLen ) != iSendLen )
	{
		CLog::Print( LOG_ERROR, "%s TcpSend(%s:%d) [%.*s] error(%d)", __FUNCTION__, m_strServerIp.c_str(), m_iServerPort, iSendLen, szSendBuf, GetError() );
		Close();
		return false;
	}

	CLog::Print( LOG_NETWORK, "TcpSend(%s:%d) [%.*s]", m_strServerIp.c_str(), m_iServerPort, iSendLen, szSendBuf );

	return true;
}

/**
 * @ingroup FtpStack
 * @brief FTP ¼­¹öÀÇ ÀÀ´äÀ» ¼ö½ÅÇÑ´Ù.
 * @param clsResponse ÀÀ´ä ¸Þ½ÃÁö¸¦ ÆÄ½ÌÇÏ´Â °´Ã¼
 * @param iWantCode		¿øÇÏ´Â ÀÀ´ä ÄÚµå
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ±×·¸Áö ¾ÊÀ¸¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CFtpClient::Recv( CFtpResponse & clsResponse, int iWantCode )
{
	if( m_hSocket == INVALID_SOCKET )
	{
		CLog::Print( LOG_ERROR, "%s not connected", __FUNCTION__ );
		return false;
	}

	char	szRecvBuf[8192];
	int		n, iPos;

	if( m_strRecvBuf.empty() == false )
	{
		iPos = clsResponse.Parse( m_strRecvBuf.c_str(), (int)m_strRecvBuf.length() );
		if( iPos > 0 )
		{
			m_strRecvBuf.erase( 0, iPos );
			goto CHECK_WANT_CODE;
		}
	}

	while( 1 )
	{
		n = TcpRecv( m_hSocket, szRecvBuf, sizeof(szRecvBuf), m_iTimeout );
		if( n <= 0 )
		{
			CLog::Print( LOG_ERROR, "%s TcpRecv(%s) error(%d)", __FUNCTION__, m_strRecvBuf.c_str(), GetError() );
			Close();
			return false;
		}

		CLog::Print( LOG_NETWORK, "TcpRecv(%s:%d) [%.*s]", m_strServerIp.c_str(), m_iServerPort, n, szRecvBuf );

		m_strRecvBuf.append( szRecvBuf, n );
		iPos = clsResponse.Parse( m_strRecvBuf.c_str(), (int)m_strRecvBuf.length() );
		if( iPos > 0 )
		{
			m_strRecvBuf.erase( 0, iPos );

			if( clsResponse.m_iCode != 0 ) break;
		}
	}

CHECK_WANT_CODE:
	if( iWantCode )
	{
		if( iWantCode == 150 )
		{
			if( clsResponse.m_iCode != 150 && clsResponse.m_iCode != 125 )
			{
				CLog::Print( LOG_ERROR, "%s reply code(%d) != want code(%d)", __FUNCTION__, clsResponse.m_iCode, iWantCode );
				return false;
			}
		}
		else if( clsResponse.m_iCode != iWantCode )
		{
			CLog::Print( LOG_ERROR, "%s reply code(%d) != want code(%d)", __FUNCTION__, clsResponse.m_iCode, iWantCode );
			return false;
		}
	}

	return true;
}

/**
 * @ingroup FtpStack
 * @brief FTP ¼­¹öÀÇ ÀÀ´äÀ» ¼ö½ÅÇÑ´Ù.
 * @param iWantCode ¿øÇÏ´Â ÀÀ´ä ÄÚµå
 * @returns ¼º°øÇÏ¸é true ¸¦ ¸®ÅÏÇÏ°í ±×·¸Áö ¾ÊÀ¸¸é false ¸¦ ¸®ÅÏÇÑ´Ù.
 */
bool CFtpClient::Recv( int iWantCode )
{
	CFtpResponse clsRes;

	return Recv( clsRes, iWantCode );
}
