#include "StdAfx.h"
#include "CGIResponse.h"
#include "Dir.h"
#include "Threadskeleton.h"


#define	MAX_HEADER_LENGTH	8192
#define	MAX_STDERR_LENGTH	8192

//!	モジュール名
static TCHAR _MODULE_NAME[] = _T("CGIResponse");

CCGIResponse::CCGIResponse(CICGIManagerWrap cgiManager)
{
	m_cgiManager = cgiManager;
	m_cgiReadTimeout = m_cgiManager.GetCGIManagerSetting().GetCGIDataReadTimeoutTime();
	m_cgiWriteTimeout = m_cgiManager.GetCGIManagerSetting().GetCGIDataWriteTimeoutTime();
}

CCGIResponse::~CCGIResponse(void)
{
}

//////////////////////////////////////////////////////////////////////////////////////////////
//	初期化
//////////////////////////////////////////////////////////////////////////////////////////////
/*!
	初期化(例外発生厳禁！)
*/
void CCGIResponse::Start(CIConnectionToolWrap responseContext)
{
}

/*!
	停止(例外発生厳禁！)
*/
void CCGIResponse::Stop(CIConnectionToolWrap responseContext)
{
	//	終了させておく
	m_cgiProcess.TerminateSubProcess();
}

/*!
	レスポンス名取得
*/
LPCTSTR CCGIResponse::GetResponseName()
{
	return _MODULE_NAME;
}

//////////////////////////////////////////////////////////////////////////////////////////////
//	レスポンス生成
//////////////////////////////////////////////////////////////////////////////////////////////
/*!
	リクエストヘッダ設定
*/
void CCGIResponse::SetRequest(CIConnectionToolWrap responseContext)
{
	//	ターゲット取得
	CContextTargetInfo targetInfo = responseContext.GetContxet().GetTargetInfo();

	//	無い？
	if(!targetInfo.GetTargetIsExist())
		throw CServerResponseException(404, _T("指定されたファイルは存在しません"));

	//	ファイル？
	if(targetInfo.GetTargetIsDirectory() || targetInfo.GetTarget().Right(1) == "\\")
		throw CConnectionErrorException(_T("ディレクトリをファイルとして表示しようとしました"));

	//	CGIコマンド取得
	CWorkspace	commandTemp;
	CCGICommand	command = commandTemp.GetAccess("CGICommand");
	if(!m_cgiManager.GetCGICommand(targetInfo.GetTarget(), command))
		throw CConnectionErrorException(_T("CGIのコマンド取得に失敗しました"));

	//	サーバ情報設定
	m_cgiProcess.AddServerInfoToEnv(responseContext.GetIServerTool().GetIServerInfoManager().GetServerInfo());

	//	リクエスト情報設定
	m_cgiProcess.AddRequestInfoToEnv(responseContext.GetContxet());

	//	コマンド取得
	CString cgiCommand = m_cgiProcess.CreateCGICommandLine(responseContext.GetContxet(), command);

	//	コマンド実行
	if(m_cgiProcess.RunSubProcess(cgiCommand, CPathTools::PathToDir(targetInfo.GetTarget()), responseContext.GetContxet().GetName()))
		throw CConnectionErrorException(_T("CGIプロセスの起動に失敗しました"));
}

/*!
	リクエストボディ設定(繰り返し)
*/
void CCGIResponse::SetRequestBody(CIConnectionToolWrap responseContext, CBinaryData &body)
{
	//	転送実行
	if(m_cgiProcess.WriteStdIn(body, responseContext.GetIConnectionThreadStatus()->GetBreakEvent(), m_cgiWriteTimeout) <= 0)
		throw CConnectionErrorException(_T("CGIプロセスへのデータ転送に失敗しました"));
}


/*!
	レスポンス生成
*/
void CCGIResponse::BuildResponse(CIConnectionToolWrap responseContext)
{
	CContextResponseInfo	responseInfo = responseContext.GetContxet().GetResponseInfo();

	//	バッファ準備
	CString header;
	CBinaryData	readBuf;
	m_cgiHeaderBuffer.ReSize(0);

	//	CGIヘッダを待つ
	while(1)
	{
		if(m_cgiProcess.ReadStdOut(readBuf, MAX_HEADER_LENGTH, responseContext.GetIConnectionThreadStatus()->GetBreakEvent(), m_cgiReadTimeout) <= 0)
			ThrowCGINoHeaderError();

		//	追加
		m_cgiHeaderBuffer.AppendBinary(readBuf);

		//	ヘッダ検出
		header = m_cgiProcess.GetHeaderBlock(m_cgiHeaderBuffer);
		if(!header.IsEmpty())
			break;

		//	サイズチェック
		if(m_cgiHeaderBuffer.GetSize() >= MAX_HEADER_LENGTH)
			ThrowCGINoHeaderError();
	}

	//	CGIヘッダ分析
	CWorkspace		cgiHeaderRoot;
	CCGIHeader		cgiHeader = cgiHeaderRoot.GetAccess("CGIHeader");
	m_cgiProcess.ParseCGIHeader(header, cgiHeader);

	//	移動?
	if(cgiHeader.GetLocation() != "")
	{
		//	移動
		CServerResponseException	exception(301, cgiHeader.GetLocation() + " に移動してください");
		exception.AddAdditionalHeader("Location", cgiHeader.GetLocation());
		throw exception;
	}
	
	//	ContentTypeは出力されてる？	
	if(cgiHeader.GetContentType() != "")
		responseInfo.GetResponseOption().SetContentType(cgiHeader.GetContentType());
	else
		ThrowCGINoHeaderError();

	//	特殊なステータス？
	if(cgiHeader.GetStatus() != 0)
	{
		if(cgiHeader.GetStatus() < 100 || cgiHeader.GetStatus() >= 600)
			throw CConnectionErrorException(_T("CGIが異常なステータスコードを出力しました"));
		responseInfo.SetResponseCode(cgiHeader.GetStatus());
	}
	else
		responseInfo.SetResponseCode(200);

	//	サイズ指定？
	if(cgiHeader.GetContentLength() > 0)
		responseInfo.GetResponseOption().SetContentLength(cgiHeader.GetContentLength());
	else
		responseInfo.GetResponseOption().ClearContentLength();

	//	拡張オプションを設定
	responseInfo.SetCGIHeader(cgiHeader.GetExtraHeader());
}

/*!
	レスポンスヘッダ取得
*/
void CCGIResponse::GetResponse(CIConnectionToolWrap responseContext)
{
	//	作業なし
}

/*!
	レスポンスボディ取得
*/
int CCGIResponse::GetResponseBody(CIConnectionToolWrap responseContext, CBinaryData &body, int blockSize)
{
	//	ヘッダ読み込み時の残りを出力
	if(m_cgiHeaderBuffer.GetSize() > 0)
	{
		body.ReSize(0);
		body.AppendBinary(m_cgiHeaderBuffer);
		m_cgiHeaderBuffer.Empty();
		return(body.GetSize());
	}

	//	読み出し
	int ret = m_cgiProcess.ReadStdOut(body, blockSize, responseContext.GetIConnectionThreadStatus()->GetBreakEvent(), m_cgiReadTimeout);
	if(ret < 0)
		throw CConnectionErrorException(_T("CGIプロセスからのデータ転送に失敗しました"));

	return(ret);
}

//////////////////////////////////////////////////////////////////////////////////////////////
//	ツール関数
//////////////////////////////////////////////////////////////////////////////////////////////
/*!
	CGI起動エラーを出力
*/
void CCGIResponse::ThrowCGINoHeaderError()
{
	//	可能ならSTDERRを出力
	CBinaryData	err;
	if(m_cgiProcess.PeekStdErr(err, MAX_STDERR_LENGTH) > 0)
		throw CServerResponseException(500, _T("CGIヘッダが出力されませんでした"), err.GetCString());

	//	付加情報なし
	throw CServerResponseException(500, _T("CGIヘッダが出力されませんでした"));
}


//////////////////////////////////////////////////////////////////////////////////////////////
//	取得
//////////////////////////////////////////////////////////////////////////////////////////////
/*!
	自分のアイテムか？
*/
int CCGIResponseFactory::IsYourRequest(CContext context)
{
	//	CGI有効？
	if(!context.GetTargetInfo().GetPathInfo().GetOption().GetEnableCGI())
		return(0);

	//	CGI?
	return m_cgiManager.TargetIsCGI(context.GetTargetInfo().GetTarget());
}
