/*!
	更新履歴

	2005/05/21 AUTH_USER, AUTH_PASSWORD, PHP_AUTH_USER, PHP_AUTH_PWに対応
*/

#include "StdAfx.h"
#include "subprocess.h"
#include "Dir.h"

#define	BUFFER_SIZE	65536

CSubProcess::CSubProcess(CServer *server,CConnectionInterrupt interrup)
: m_stdin(0), m_stdout(0), m_stderr(0), m_env(0),
  m_hReadPipe(NULL), m_hWritePipe(NULL), m_hInReadPipe(NULL),m_hInWritePipe(NULL),m_hInWriteWait(NULL),
  m_hErrReadPipe(NULL), m_hErrWritePipe(NULL),
  m_interrupt(interrup)
{
	m_server = server;
	m_prosessCreated = 0;
	m_breakFlag = 0;
	memset(&m_processInfo,0,sizeof(m_processInfo));
}

CSubProcess::~CSubProcess(void)
{
	TerminateProcess();

	::DisconnectNamedPipe(m_hInWritePipe);
	::CloseHandle(m_hReadPipe);
	::CloseHandle(m_hWritePipe);
	::CloseHandle(m_hInReadPipe);
	::CloseHandle(m_hInWritePipe);
	::CloseHandle(m_hInWriteWait);
	::CloseHandle(m_hErrReadPipe);
	::CloseHandle(m_hErrWritePipe);

	if(m_processInfo.hProcess)
		::CloseHandle(m_processInfo.hProcess);
	if(m_processInfo.hThread)
		::CloseHandle(m_processInfo.hThread);
}

//	CGIならば、CGIコマンドを取得する
CString CSubProcess::GetCGICommand(CServer *server,CString path,CString queryDecoded)
{
	//	ファイル名、拡張子の取得
	CString	fileName = path;
	CString	ext = CDir::PathToExt(fileName);

	//	設定リストを取得
	CWorkspaceAccess	cgiSetting = server->m_setting.GetAccess("CGI");
	CStringArray	cgiList;
	cgiSetting.GetAllSubNode(cgiList);

	//	各設定を処理
	for(int i=0;i<cgiList.GetSize();i++)
	{
		CWorkspaceAccess	oneSetting = cgiSetting.GetAccess(cgiList[i]);

		//	拡張子は等しいか？
		if(ext.CompareNoCase(oneSetting.GetConfig("FileExt","")) == 0)
		{
			//	ファイルヘッダ取得
			CString	header = GetFirstLine(fileName);

			if((oneSetting.GetConfig("ExecuteFile",0) == 1 && header.GetLength() >= 2 && header[0] == 'M' && header[1] == 'Z')						//	実行ファイル
			|| (oneSetting.GetConfig("ExecuteFile",0) == 0 && (oneSetting.GetConfig("FileHeader","").IsEmpty() || header.MakeLower().Find(oneSetting.GetConfig("FileHeader","").MakeLower()) != -1)))		//	それ以外
			{
				//	コマンド作成
				CWorkspace			swap;
				CWorkspaceAccess	swapAccess = swap.GetAccess("Swap");
				
				//	スペースがある場合は、""を追加
				if(fileName.Find(" ") != -1)
					swapAccess.SetConfig("FilePath","\"" + fileName + "\"");
				else
					swapAccess.SetConfig("FilePath",fileName);
				swapAccess.SetConfig("Query",queryDecoded);

				CString command = oneSetting.GetConfig("Command","");
				command = swapAccess.GetKeySwapString(command);

				return(command);
			}
		}
	}

	return("");
}


//	プロセス起動
int CSubProcess::RunSubProcess(CString command,CString workPath,CString threadID)
{
	//	パイプ初期化
	if(CreatePipe(threadID))
		throw CSubProcessException("プロセス通信用パイプの生成に失敗しました。");

	//	変数初期化
	m_env += '\0';
	m_env += '\0';

	//	プロセス情報構造体
	STARTUPINFO StartupInfo;

	::ZeroMemory(&StartupInfo, sizeof(STARTUPINFO));
	StartupInfo.cb = sizeof(STARTUPINFO);

	// ハンドルの継承を指定
	StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;

	// DOS窓を表示しない
	StartupInfo.wShowWindow = SW_HIDE;

	// 標準出力ハンドルとエラー出力ハンドルを設定
	StartupInfo.hStdOutput = m_hWritePipe;
	StartupInfo.hStdInput = m_hInReadPipe;
	StartupInfo.hStdError  = m_hErrWritePipe;


	// コンソールアプリ起動
	if (!::CreateProcess(
		NULL,
		command.GetBuffer(0),
		&m_sa,
		NULL,
		TRUE,				// ハンドルの継承
		0,	// DOS窓を表示しないための指定
		m_env.GetPtr(),
		workPath,
		&StartupInfo,
		&m_processInfo))
	{
		TRACE("ErrCode : %d\n",GetLastError());
		throw CSubProcessException("プロセスの起動に失敗しました");
	}
	m_prosessCreated = TRUE;

	//	終了まで待機
	WaitProcessTerminate();

	return(0);
}


//	プロセス終了まで待機・パイプの送受信
void CSubProcess::WaitProcessTerminate()
{
	TIMEOUT_SCOPE();

	CBuffer	buf;
	int		index = 0;

	while(ProsessRunning())
	{
		//	StdIn出力
		index += WriteStdIn(m_stdin,index);

		//	StdOutチェック
		if(ReadStdOut(buf) > 0)
			m_stdout += buf;

		//	StdErrチェック
		if(ReadStdErr(buf) > 0)
			m_stderr += buf;

		//	タイムアウト?
		if(IsTimeOut())
			throw CSubProcessException("プロセス実行中にタイムアウトしました");

		//	中断?
		if(m_interrupt.IsInterrupt())
			throw CSubProcessException("プロセス実行中に" + m_interrupt.GetInterruptInfo());


		Sleep(10);
	}

	//	StdOutチェック
	if(ReadStdOut(buf) > 0)
		m_stdout += buf;

	//	StdErrチェック
	if(ReadStdErr(buf) > 0)
		m_stderr += buf;

	//
	//	中断イベントを確認しておく(メッセージ表示のため)
	//

	//	タイムアウト?
	if(IsTimeOut())
		throw CSubProcessException("プロセス実行中にタイムアウトしました");

	//	中断?
	if(m_interrupt.IsInterrupt())
		throw CSubProcessException("プロセス実行中に" + m_interrupt.GetInterruptInfo());

}


//	StdOut取得
int CSubProcess::ReadStdOut(CBuffer &data)
{
	DWORD	stdInLen;
	if(!::PeekNamedPipe(m_hReadPipe, NULL, 0, NULL, &stdInLen, NULL))
		throw CSubProcessException("プロセス間通信に失敗しました");

	if(stdInLen > 0)
	{
		DWORD	readed;

		data.ReSize(stdInLen);
		if(!::ReadFile(m_hReadPipe,data.GetPtr(),stdInLen,&readed,NULL))
			throw CSubProcessException("プロセス間通信に失敗しました");
		data.ReSize(readed);
	
		return(readed);
	}

	data.ReSize(0);
	return(0);
}

//	StdErr取得
int CSubProcess::ReadStdErr(CBuffer &data)
{
	DWORD	stdInLen;
	if(!::PeekNamedPipe(m_hErrReadPipe, NULL, 0, NULL, &stdInLen, NULL))
		throw CSubProcessException("プロセス間通信に失敗しました");
	
	if(stdInLen > 0)
	{
		DWORD	readed;

		data.ReSize(stdInLen);
		if(!::ReadFile(m_hErrReadPipe,data.GetPtr(),stdInLen,&readed,NULL))
			throw CSubProcessException("プロセス間通信に失敗しました");
		data.ReSize(readed);

		return(readed);
	}

	data.ReSize(0);
	return(0);
}

//	StdIn出力
int CSubProcess::WriteStdIn(CBuffer &data,int startIndex)
{
	DWORD	dataLen = data.GetSize() - startIndex;
	char	*dataPtr = data.GetPtr() + startIndex;

	if(dataLen <= 0)
		return(0);

	//	出力は終了したか？
	if(::WaitForSingleObject(m_hInWriteWait,0) == WAIT_OBJECT_0)
	{
		DWORD writeLen = BUFFER_SIZE;
		if(dataLen < writeLen)
			writeLen = dataLen;

		memset(&m_ovrlapped,0,sizeof(m_ovrlapped));
		m_ovrlapped.hEvent = m_hInWriteWait;

		DWORD	temp;

		//	出力する
		if(!::WriteFile(m_hInWritePipe,dataPtr,writeLen,&temp,&m_ovrlapped) && GetLastError() != ERROR_IO_PENDING)
			throw CSubProcessException("プロセス間通信に失敗しました");

		return(writeLen);
	}
	return(0);
}


//	パイプ初期化
int CSubProcess::CreatePipe(CString threadID)
{
	// セキュリティ属性(ハンドル継承を指定)
	m_sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	m_sa.lpSecurityDescriptor = NULL;
	m_sa.bInheritHandle = TRUE;

	// 標準出力用パイプ作成
	if(!::CreatePipe(&m_hReadPipe, &m_hWritePipe, &m_sa, BUFFER_SIZE))
		return(-1);
	
	// エラー出力パイプの作成
	if(!::CreatePipe(&m_hErrReadPipe, &m_hErrWritePipe, &m_sa, BUFFER_SIZE))
		return(-1);

	// 標準入力パイプの作成
	m_hInWritePipe = CreateNamedPipe("\\\\.\\pipe\\_04WebServer_CGISSI_" + threadID,PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
               PIPE_TYPE_BYTE, 1, BUFFER_SIZE, BUFFER_SIZE, 1000, NULL);

	if(m_hInWritePipe == INVALID_HANDLE_VALUE )
		return(-1);

	m_hInReadPipe = CreateFile("\\\\.\\pipe\\_04WebServer_CGISSI_" + threadID,GENERIC_READ, 0, &m_sa, OPEN_EXISTING, 0, NULL);
	if(m_hInReadPipe == INVALID_HANDLE_VALUE )
		return(-1);

	if(!::ConnectNamedPipe(m_hInWritePipe,NULL) && GetLastError() != ERROR_PIPE_CONNECTED)
		return(-1);

	m_hInWriteWait = CreateEvent(&m_sa,FALSE,TRUE,NULL);
	if(m_hInWriteWait == INVALID_HANDLE_VALUE )
		return(-1);

	return(0);
}


//	プロセスが実行中か取得する
int CSubProcess::ProsessRunning()
{
	if(!m_prosessCreated)
		return(0);

	DWORD ret = ::WaitForSingleObject(m_processInfo.hProcess, 0);

	if(ret == WAIT_TIMEOUT)
		return(1);
	return(0);
}

//	プロセス終了
void CSubProcess::TerminateProcess()
{
	//	実行中なら、停止する
	if(ProsessRunning())
	{
		::TerminateProcess(m_processInfo.hProcess,-1);
	}

	m_prosessCreated = 0;
}


//	強制終了
void CSubProcess::Break()
{
	TerminateProcess();
}


//	タイムアウト
void CSubProcess::OnTimeOut()
{
	Break();
}


//	環境の追加
void CSubProcess::AddEnvironment(CString env)
{
	int index = env.Find("=");

	if(index != env.GetLength() - 1)
	{
		m_env += env;
		m_env += '\0';
	}
}


//	CGI環境の追加
void CSubProcess::AddCGIServerEnvironment(CServer *server,CWorkspaceAccess context,CRequest request,CAlias alias)
{
	CWorkspace			env;
	CWorkspaceAccess	envAccess = env.GetAccess("Env");
	GetCGIServerEnvironment(envAccess,server,context,request,alias);

	CStringArray	list;
	envAccess.GetAllKey(list);

	for(int i=0;i<list.GetSize();i++)
	{
		AddEnvironment(list[i] + "=" + envAccess.GetConfig(list[i],""));
	}

	//	元の環境変数
	for(int i=0;_environ[i] && i<256;++i)
		AddEnvironment(_environ[i]);
}

/*!
 *	環境領域取得
 */
void CSubProcess::GetCGIServerEnvironment(CWorkspaceAccess env,CServer *server,CWorkspaceAccess context,CRequest request,CAlias alias)
{
	env.Clear();

	CString	str;

	//
	//	ウェブサーバ情報に関する環境変数リスト
	//

	//	CGI プログラム を起動したサーバソフトウエアの名前とバージョン情報。
	env.SetConfig("SERVER_SOFTWARE",server->m_serverInfo.GetConfig("ServerSignature",""));

	//	サーバのシグニチャ
	str.Format("<address>%s</address>",server->m_serverInfo.GetConfig("ServerSignature",""));
	env.SetConfig("SERVER_SIGNATURE",str);

	//	サーバのホスト名、ドメイン名、またはIPアドレス情報。 
	env.SetConfig("SERVER_NAME",server->m_serverInfo.GetConfig("ServerHostName",""));

	//	サーバが使用するCGIのレビジョン情報
	env.SetConfig("GATEWAY_INTERFACE","CGI/1.1");

	//	プロトコルの名前とレビジョン情報。
	env.SetConfig("SERVER_PROTOCOL","HTTP/1.1");

	//	サーバのアドレス
	env.SetConfig("SERVER_ADDR",server->m_serverInfo.GetConfig("ServerAddress",""));

	//	ポート番号。 
	env.SetConfig("SERVER_PORT",server->m_serverInfo.GetConfig("ServerPort",""));

	//	サーバ名（クライアントから取得）
	if(!request.GetOptionAccess().GetConfig("Host","").IsEmpty())
		env.SetConfig("HTTP_HOST",request.GetOptionAccess().GetConfig("Host",""));
 




	//
	//	CGI 環境の情報に関する環境変数のリスト
	//

	//	リクエストを送信した方法（メソッド）。GETやPOSTなど。 
	env.SetConfig("REQUEST_METHOD",request.GetMethod());

	//	POSTから標準入力に送信されたデータのバイト数。 
	env.SetConfig("CONTENT_LENGTH",request.GetOptionAccess().GetConfig("Content-length","0",TRUE));
 
	//	POSTとPUTなどのクエリに添付されたデータのMIMEタイプ。
	env.SetConfig("CONTENT_TYPE",request.GetOptionAccess().GetConfig("Content-Type","",TRUE));

	//	クエリとして送られたURL情報のクエスチョン( ? )以降の情報。データは『URLエンコード』されています。 
	env.SetConfig("QUERY_STRING",request.GetQuery());

 

	//	仮想パス
	env.SetConfig("SCRIPT_NAME",request.GetURIObject());

	//	URI
	env.SetConfig("REQUEST_URI",request.GetURI());

	//	PATH_INFO,PATH_TRANSLATED
	if(alias.IsExtarExist())
	{
		//	エクストラパス
		str = alias.GetExtraPath();
		str.Replace("\\","/");
		env.SetConfig("PATH_INFO",str);

		//	エクストラパス（ローカルパスに変換）
		str = CDir::RemoveBackSlash(alias.GetPathInfo().GetConfig("LocalPath","Z:\\")) + alias.GetExtraPath();
		str.Replace("\\","/");
		env.SetConfig("PATH_TRANSLATED",str);

		//	絶対パス。
		str = alias.GetExtraTarget();
		str.Replace("\\","/");
		env.SetConfig("SCRIPT_FILENAME",str);

		//	仮想パスの再設定
		CString	uri = request.GetURIObject();
		if(uri.GetLength() > alias.GetExtraPath().GetLength())
			uri = uri.Left(uri.GetLength() - alias.GetExtraPath().GetLength());
		env.SetConfig("SCRIPT_NAME", uri);

	}
	else
	{
		//	絶対パス。
		str = alias.GetTarget();
		str.Replace("\\","/");
		env.SetConfig("SCRIPT_FILENAME",str);
	}


	//
	//	クライアントに関する環境変数のリスト
	//

	//	ユーザを認証するときに使用する認証メソッド。ウェブサーバとリモートホストがIDENTD認証デーモン（ユーザ認証プロトコル）を実行しているときだけ値がある。 
	env.SetConfig("AUTH_TYPE","basic");

	//	クライアントが受け付けることができるMIMEタイプのリスト。 
	env.SetConfig("HTTP_ACCEPT",request.GetOptionAccess().GetConfig("Accept","*/*",TRUE));

	//	クライアントがリクエストを発行するときに使用するブラウザ名 
	env.SetConfig("HTTP_USER_AGENT",request.GetOptionAccess().GetConfig("User-Agent","*/*",TRUE));
 
	//	呼び出し元のURL。アドレスバーからの直接入力、ブックマークからのアクセスの際には値がありません。 
	env.SetConfig("HTTP_REFERER",request.GetOptionAccess().GetConfig("Referer","",TRUE));

	//	リモートホストのIPアドレス情報。 
	env.SetConfig("REMOTE_ADDR",context.GetConfig("Host",""));

	//	リモートホストのホスト名情報。 
	if(server->m_setting.GetConfig("CGIEnvironment::EnableGetHostName",0))
	{
		CIPAddress	ip = context.GetConfig("Host","");
		env.SetConfig("REMOTE_HOST",ip.GetHostName());
	}

	//	ユーザの認証名。ウェブサーバとリモートホストがIDENTD認証デーモン（ユーザ認証プロトコル）を実行しているときだけ値があります。 
	env.SetConfig("REMOTE_USER",request.GetUser());

	//	ユーザの認証名。ウェブサーバとリモートホストがIDENTD認証デーモン（ユーザ認証プロトコル）を実行しているときだけ値があります。 
	env.SetConfig("AUTH_USER",request.GetUser());

 	//	ユーザのパスワード。ウェブサーバとリモートホストがIDENTD認証デーモン（ユーザ認証プロトコル）を実行しているときだけ値があります。 
	env.SetConfig("AUTH_PASSWORD",request.GetPasswd());


	//	ユーザの認証名 
	env.SetConfig("PHP_AUTH_USER",request.GetUser());

 	//	ユーザのパスワード
	env.SetConfig("PHP_AUTH_PW",request.GetPasswd());


	//
	//	プロクシ
	//

	//	プロキシサーバーの情報が設定されます
	env.SetConfig("HTTP_VIA",request.GetOptionAccess().GetConfig("Via","",TRUE));

	//	ロキシサーバーの情報が設定されます。
	env.SetConfig("HTTP_FORWARDED",request.GetOptionAccess().GetConfig("Forwarded","",TRUE));

	//	プロキシサーバーへの指示情報
	env.SetConfig("HTTP_PROXY_CONNECTION",request.GetOptionAccess().GetConfig("Proxy-Connection","",TRUE));


	//
	//	その他
	//

	//	クッキー
	env.SetConfig("HTTP_COOKIE",request.GetOptionAccess().GetConfig("Cookie","",TRUE));

}


/*!
 *	１行取得
 */
CString	CSubProcess::GetFirstLine(CString path)
{
	CString	ret;
	FILE	*text;

	if(!fopen_s(&text, path,"rb"))
	{
		char *buf = ret.GetBuffer(256);

		if(buf)
			fgets(buf,255,text);

		fclose(text);
		ret.ReleaseBuffer();
		return(ret);
	}
	else
		return("");
}


