#include "StdAfx.h"
#include "HTTPHeaderParser.h"
#include "TextTools.h"
#include "UrlTools.h"

/*!
	リクエストヘッダーの解析を行う

	CParseRequestHeaderException をスローする
*/
void CHTTPHeaderParser::ParseRequestHeader(CUTF8String &header, CContextRequestInfo requestInfo)
{
	//	下準備
	requestInfo.Clear();

	CUTF8String	line;
	CUTF8String	method;
	CUTF8String	url;
	CUTF8String	protocol;
	CUTF8String	version;

	CUTF8String	object;
	CUTF8String	query;
	CUTF8String	queryDecoded;

	/*
		リクエストの処理
	*/
	//	リクエスト取り出し
	if(CTextTools::GetLine(header, line)==0 || line.IsEmpty())
		throw CParseRequestHeaderException(_U16("不正なHTTPリクエストヘッダを検出しました"));
	
	//	メソッド切り出し
	if(CTextTools::GetToken(line, method, _U8(" "))==0 || method.IsEmpty())
		throw CParseRequestHeaderException(_U16("不正なHTTPリクエストヘッダを検出しました"));

	//	URL切り出し
	if(CTextTools::GetToken(line, url, _U8(" "))==0 || url.IsEmpty())
		throw CParseRequestHeaderException(_U16("不正なHTTPリクエストヘッダを検出しました"));

	//	プロトコル
	if(CTextTools::GetToken(line, protocol, _U8("/"))==0 || protocol != _U8("HTTP"))
		throw CParseRequestHeaderException(_U16("不正なHTTPリクエストヘッダを検出しました"));

	//	バージョン
	version = line;
	if(version.IsEmpty())
		throw CParseRequestHeaderException(_U16("不正なHTTPリクエストヘッダを検出しました"));

	//	URIObject
	object = CUrlTools::GetUrlObject(url);
	if(object.IsEmpty())
		throw CParseRequestHeaderException(_U16("不正なHTTPリクエストヘッダを検出しました"));

	//	Query (QueryはEmptyでもかまわない)
	query = CUrlTools::GetQuery(url);
	
	//	設定
	requestInfo.SetMethod(method);
	requestInfo.SetURL(url);
	requestInfo.SetProtocol(version);
	requestInfo.SetVersion(version);


	/*
		Urlの処理
	*/
	//	デコード
	CBinaryData	decodedUrl;
	CUrlTools::UrlDecode(object, decodedUrl);

	//	UTF8へ文字コード変換
	if(CUrlTools::UrlConvertToUTF8(decodedUrl, object))
		throw CParseRequestHeaderException(_U16("Urlの文字コード判別に失敗しました"));

	//	正規化
	object = CUrlTools::NormalizeUrl(object);
	if(object.IsEmpty())
		throw CParseRequestHeaderException(_U16("不正なUrlを検出しました"));

	//	Queryをデコード
	if(!query.IsEmpty())
	{
		CBinaryData	decodedQuery;
		CUrlTools::UrlDecode(query, decodedQuery);

		//	UTF8へ文字コード変換
		CUrlTools::UrlConvertToUTF8(decodedQuery, object);
	}

	//	設定
	requestInfo.SetObject(object);
	requestInfo.SetQuery(query);
	requestInfo.SetQueryDecoded(queryDecoded);


	/*
		オプションヘッダー取得
	*/
	CContextRequestOption options = requestInfo.GetRequestOption();
	CUTF8String	key;
	while(1)
	{
		if(CTextTools::GetLine(header,line)==0)
			break;

		//	key
		if(CTextTools::GetToken(line,key,_U8(": "))==0)		//	xxx: xxxx
			if(CTextTools::GetToken(line,key,_U8(":"))==0)	//	xxx:xxxx
				continue;

		//	data
		if(line.IsEmpty() || key.IsEmpty())
			continue;

		//	セット
		options.SetKeyStr(key,line);
	}
}


/*!
	レスポンスヘッダの生成を行う
*/
CUTF8String CHTTPHeaderParser::GetResponseHeader(CContextResponseInfo responseInfo)
{
	CContextResponseOption responseOptions = responseInfo.GetResponseOption();

	//	レスポンスライン
	CUTF8String	header,line;
	line.Format(_U8("HTTP/1.1 %s\r\n"), GetResponseText(responseInfo.GetResponseCode(), responseInfo.GetResponseText()));
	header += line;

	//	オプション
	CUTF8StringArray	keyList;
	responseOptions.GetAllKey(keyList);
	for(int i=0;i<keyList.GetCount();i++)
	{
		if(!responseOptions.GetKeyStr(keyList[i], _U8("")).IsEmpty())
		{
			line.Format(_U16("%s: %s\r\n"), keyList[i],responseOptions.GetKeyStr(keyList[i], _U8("")));
			header += line;
		}
	}

	//	CGI用ヘッダ
	header += responseInfo.GetCGIHeader();
	header.TrimRight();
	header += _U8("\r\n\r\n");

	return(header);
}


/*!
	レスポンステキスト取得
*/
CUTF8String CHTTPHeaderParser::GetResponseText(int code, CUTF8String responseText)
{
	//	responseTextが設定されている？
	if(!responseText.IsEmpty())
	{
		responseText.Replace(_U8("\r"), _U8(""));
		responseText.Replace(_U8("\n"), _U8(""));

		//	数字指定？
		if(responseText.GetLength() > 4 && responseText[3] == ' ' && CTextTools::StrTol(responseText.Left(3)) >= 100)
			return(responseText);
		else
			return(CUTF8String::GetFormat(_U16("%d %s"),code,responseText));
	}

	//	設定
	switch(code)
	{
		//	成功
		case	100:
			return(_U8("100 Continue"));
		case	101:
			return(_U8("101 Switching Protocols"));
		case	200:
			return(_U8("200 OK"));
		case	201:
			return(_U8("201 Created"));
		case	202:
			return(_U8("202 Accepted"));
		case	203:
			return(_U8("203 Non-Authoritative Information"));
		case	204:
			return(_U8("204 No Content"));
		case	205:
			return(_U8("205 Reset Content"));
		case	206:
			return(_U8("206 Partial Content"));
		case	207:
			return(_U8("207 Multi-Status"));

		//	エラー（30xは自動に処理される）
		case	300:
			return(_U8("300 Multiple Choices"));
		case	301:
			return(_U8("301 Moved Permanently"));
		case	302:
			return(_U8("302 Found"));
		case	303:
			return(_U8("303 See Other"));
		case	304:
			return(_U8("304 Not Modified"));
		case	305:
			return(_U8("305 Use Proxy"));
		case	306:
			return(_U8("306 (Unused)"));
		case	307:
			return(_U8("307 Temporary Redirect"));


		case	400:
			return(_U8("400 Bad Request"));
		case	401:
			return(_U8("401 Unauthorized"));
		case	402:
			return(_U8("402 Payment Required"));
		case	403:
			return(_U8("403 Forbidden"));
		case	404:
			return(_U8("404 Not Found"));
		case	405:
			return(_U8("405 Method Not Allowed"));
		case	406:
			return(_U8("406 Not Acceptable"));
		case	407:
			return(_U8("407 Proxy Authentication Required"));
		case	408:
			return(_U8("408 Request Timeout"));
		case	409:
			return(_U8("409 Conflict"));
		case	410:
			return(_U8("410 Gone"));
		case	411:
			return(_U8("411 Length Required"));
		case	412:
			return(_U8("412 Precondition Failed"));
		case	413:
			return(_U8("413 Request Entity Too Large"));
		case	414:
			return(_U8("414 Request-URI Too Long"));
		case	415:
			return(_U8("415 Unsupported Media Type"));
		case	416:
			return(_U8("416 Requested Range Not Satisfiable"));


		case	500:
			return(_U8("500 Internal Server Error"));
		case	501:
			return(_U8("501 Not Implemented"));
		case	502:
			return(_U8("502 Bad Gateway"));
		case	503:
			return(_U8("503 Service Unavailable"));
		case	504:
			return(_U8("504 Gateway Timeout"));
		case	505:
			return(_U8("505 HTTP Version Not Supported"));
	}

	CUTF8String	unknown;
	unknown.Format(_U16("%d UNKNOWN"),code);
	return(unknown);
}
