/* vi: set tabstop=4 nocindent noautoindent: */

/**
	Protocol
					by Fumi Iseki 2005 10/10
								  2009 2/5
*/



#include "protocol.h"



#ifdef CPLUSPLUS
	using namespace jbxl;
#endif



//////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Protocol Header
//

/**
tList*  get_protocol_header_list(Buffer buf, char deli, int fstline, int rcntnt)

	機能：buf内の プロトコルヘッダ（key'deli' data の形式）を分解してリストに格納する．'deli'は境界文字．
		  fstlineが TRUEの場合，ヘッダの一行目は HDLIST_FIRST_LINE_KEY のキーをつけてリストに格納する．

		  また，この関数はバッファリングなどは行わないので，ヘッダにコンテンツの最初の部分が紛れ込む可能性がある．
		  rcntntが TRUEの場合は，HDLIST_CONTENTS_KEY をキーにしてコンテンツをリストに格納する．FALSEの場合は無視．
		  コンテンツのサイズが大きく，順次読み込まれる場合，HDLIST_CONTENTS_KEYノードは複数生成される．

		  ヘッダの値が複数行になる場合，ヘッダ種別 HDLIST_CONTINUE として，次のノードに格納される．

	引数：buf	  -- ヘッダを格納した変数
		  deli	  -- ヘッダの見出し(Key)との境界文字．HTTPや SMTPでは ':'
		  fstline -- 一行目を特別扱いにするか？ 一行目が key'deli' data の形式でないプロトコル用, ex) HTTP, SIP
		  rcntnt  -- コンテンツも読むか？

	戻り値：ヘッダ情報を格納したリストへのポインタ．

*/
tList*  get_protocol_header_list(Buffer buf, char deli, int fstline, int rcntnt)
{
	tList* lp;

	if (buf.buf==NULL) return NULL;

	lp = get_protocol_header_list_seq(NULL, buf, deli, fstline, rcntnt);
	if (lp!=NULL) lp = find_tList_top(lp);

	return lp;
}





/**
Buffer  restore_protocol_header(tList* list, char* deli, int mode)

	機能：
		リスト listに保存されたデータからヘッダデータを復元する．
		mode==ON なら listに紛れ込んでいるコンテンツの内容も加えて返す．
		
		get_protocol_header_list() の逆．

*/
Buffer  restore_protocol_header(tList* list, char* deli, int mode, int* hdsz)
{
	Buffer buf;

	buf = init_Buffer();
	if (list==NULL) return buf;

	buf = make_Buffer(RECVBUFSZ);

	while(list!=NULL) {
		if (!strcmp((const char*)(list->ldat.key.buf), HDLIST_FIRST_LINE_KEY)) {
			copy_Buffer(&(list->ldat.val), &buf);
			cat_s2Buffer("\r\n", &buf);
		}
		else if (!strcmp((const char*)(list->ldat.key.buf), HDLIST_CONTINUE)) {
			buf.buf[buf.vldsz] = TAB;
			buf.vldsz++;
			cat_Buffer(&(list->ldat.val), &buf);
			cat_s2Buffer("\r\n", &buf);
		}
		else if (!strcmp((const char*)(list->ldat.key.buf), HDLIST_END_KEY)) {
			break;
		}
		else if (strcmp((const char*)(list->ldat.key.buf), HDLIST_CONTENTS_KEY)) {
			cat_Buffer(&(list->ldat.key), &buf);
			cat_s2Buffer(deli, &buf);
			cat_Buffer(&(list->ldat.val), &buf);
			cat_s2Buffer("\r\n", &buf);
		}

		list = list->next;
	}
	if (buf.vldsz>0) cat_s2Buffer("\r\n", &buf);

	if (hdsz!=NULL) *hdsz = buf.vldsz;

	if (mode==ON) {
		int nn = 1;
		tList* pl = strncmp_tList(list, HDLIST_CONTENTS_KEY, 0, nn);
		while (pl!=NULL && pl->ldat.val.buf!=NULL) {
			cat_Buffer(&(pl->ldat.val), &buf);
			pl = strncmp_tList(list, HDLIST_CONTENTS_KEY, 0, ++nn);
		}
	}

	return buf;
}





/**
Buffer  restore_protocol_contents(tList* list)

	機能：
		リスト listに保存されたデータからコンテンツを復元する．

*/
Buffer  restore_protocol_contents(tList* list)
{
	Buffer buf;
	tList* lp;
	int nn = 1;

	buf = make_Buffer(BUFSZ);

	lp = strncmp_tList(list, HDLIST_CONTENTS_KEY, 0, nn);
	while (lp!=NULL && lp->ldat.val.buf!=NULL) {
		cat_Buffer(&(lp->ldat.val), &buf);
		lp = strncmp_tList(list, HDLIST_CONTENTS_KEY, 0, ++nn);
	}

	return buf;
}





/**
void	set_protocol_contents(tList* list, Buffer contents)

	注意：プロトコルに依存するので，コンテンツサイズの再計算は行わない．


*/
void	set_protocol_contents(tList* list, Buffer contents)
{
	while (list!=NULL) {
		if (list->ldat.key.buf!=NULL && !strcmp((char*)list->ldat.key.buf, HDLIST_CONTENTS_KEY)) {
			free_Buffer(&list->ldat.val);
			list->ldat.val = dup_Buffer(contents);

			while (is_header_continue(list)) del_tList_node(list->next);								
			break;
		}

		list = list->next;
	}

	return;
}






/**
tList*  get_protocol_header_list_seq(tList* lp, Buffer buf, char deli, int fstline, int rcntnt)

	機能：buf内の プロトコルヘッダ（key'deli' data の形式）を分解してリストに格納する．'deli'は境界文字．
		　シーケンシャルに随時呼び出すことが可能．
		  lp==NULL で状態（静的変数）がリセットされので，最初は lpを NULLにすること．

		  fstlineが TRUEの場合，ヘッダの一行目は HDLIST_FIRST_LINE_KEY のキーをつけてリストに格納する．

		  また，この関数はバッファリングなどは行わないので，ヘッダにコンテンツの最初の部分が紛れ込む可能性がある．
		  rcntntが TRUEの場合は，HDLIST_CONTENTS_KEY をキーにしてコンテンツをリストに格納する．FALSEの場合は無視．
		  コンテンツのサイズが大きく，順次読み込まれる場合，HDLIST_CONTENTS_KEYノードは複数生成される．

		  ヘッダの値が複数行になる場合，ヘッダ種別 HDLIST_CONTINUE として，次のノードに格納される．

		  lp!=NULL または fstlineがFALSE の場合は一行目の処理は行わない．

	引数：lp	  -- ヘッダ情報を格納するリストへのポインタ．
					 NULLの場合はリストが新しく作成される．NULLでない場合はそのリストにヘッダ情報が追加される．
		  buf	 -- ヘッダを格納した変数
		  deli	-- ヘッダの見出し(Key)との境界文字．HTTPや SMTPでは ':'
		  fstline -- 一行目を特別扱いにするか？ 一行目が key'deli' data の形式でないプロトコル用, ex) HTTP, SIP
		  rcntnt  -- コンテンツも読むか？

	戻り値：一番最後に作成したリストノードへのポインタ．
			リストのトップを得るには find_tList_top(tList* pp) を用いる．

	変数：in_contents コンテンツ内を処理中であることを表す．
		  crlfCount   処理中の CR,LFの数

*/
tList*  get_protocol_header_list_seq(tList* lp, Buffer buf, char deli, int fstline, int rcntnt)
{
	static int crlfCount  = 0;
	static int inContents = FALSE;
	int	i=0, n=0, size;
	Buffer key, data;


	if (buf.buf==NULL) return NULL;

	size = buf.vldsz;
	data = make_Buffer(size+1);
	key  = make_Buffer(LBUF);

	if (lp==NULL) {
		crlfCount  = 0;
		inContents = FALSE;
	}


	// FIRST LINE 
	if (fstline && lp==NULL) {
		while(buf.buf[i]!=0x0a && buf.buf[i]!='\0' && i<size && n<size) {
			data.buf[n++] = buf.buf[i++];
		}
		if (data.buf[n-1]==0x0d) {
			i--;
			n--;
		}

		data.buf[n] = '\0';
		data.vldsz  = n;
		copy_s2Buffer(HDLIST_FIRST_LINE_KEY, &key);
		lp = add_tList_node_Buffer(NULL, key, data);

		if (buf.buf[i]=='\0' || i==size) {
			free_Buffer(&key);
			free_Buffer(&data);
			return lp;
		}
		clear_Buffer(&key);
		clear_Buffer(&data);
	}


	// HEADER
	while(buf.buf[i]!='\0' && i<size && !inContents) {
		// Check Previous Line's CR & LF
		if (i+1<size && buf.buf[i]==0x0d && buf.buf[i+1]==0x0a) {
			i = i + 2;
			if (crlfCount==1) {			// for previous called function
				copy_s2Buffer(HDLIST_END_KEY, &key);
				lp = add_tList_node_Buffer(lp, key, data);
				crlfCount  = 0;
				inContents = TRUE;
				break;	
			}
			else crlfCount = 1;
		}
		else if (buf.buf[i]==0x0a) {
			i = i + 1;
			if (crlfCount==1) {			// for previous called function
				copy_s2Buffer(HDLIST_END_KEY, &key);
				lp = add_tList_node_Buffer(lp, key, data);
				crlfCount  = 0;
				inContents = TRUE;
				break;	
			}
			else crlfCount = 1;
		}

		// 
		if (i+1<size && buf.buf[i]==0x0d && buf.buf[i+1]==0x0a) {
			i = i + 2;
			if (crlfCount==1) {
				copy_s2Buffer(HDLIST_END_KEY, &key);
				lp = add_tList_node_Buffer(lp, key, data);
				crlfCount  = 0;
				inContents = TRUE;
				break;	
			}
		}
		else if (i<size && buf.buf[i]==0x0a) {
			i = i + 1;
			if (crlfCount==1) {
				copy_s2Buffer(HDLIST_END_KEY, &key);
				lp = add_tList_node_Buffer(lp, key, data);
				crlfCount  = 0;
				inContents = TRUE;
				break;	
			}
		}

		if (buf.buf[i]=='\0' || i==size) break;


		// HEADER KEY
		n = 0;
		if (buf.buf[i]==TAB || buf.buf[i]==' ') {
			copy_s2Buffer(HDLIST_CONTINUE, &key);
		}
		else {
			while(buf.buf[i]!=deli && buf.buf[i]!='\0' && i<size && n<LBUF) {
				key.buf[n++] = buf.buf[i++];
			}
			key.buf[n] = '\0';
			key.vldsz  = n;
		}
		while ((buf.buf[i]==deli ||buf.buf[i]==' '||buf.buf[i]==TAB) && i<size) i++;


		// HEADER VALUE
		n = 0;
		while(buf.buf[i]!=0x0a && buf.buf[i]!='\0' && i<size && n<size) {
			data.buf[n++] = buf.buf[i++];
		}
		if (data.buf[n-1]==0x0d) {
			i--;
			n--;
		}
		data.buf[n] = '\0';
		data.vldsz  = n;

		lp = add_tList_node_Buffer(lp, key, data);

		crlfCount = 0;
		clear_Buffer(&key);
		clear_Buffer(&data);
	}
	

	// CONTENTS (Text or Binary)
	if (rcntnt && i<size && inContents) {
		crlfCount = 0;
		clear_Buffer(&key);
		clear_Buffer(&data);
		copy_s2Buffer(HDLIST_CONTENTS_KEY, &key);

		n = 0;
		while(i<size && n<size) {
	   		data.buf[n++] = buf.buf[i++];
		}
		data.vldsz = n;

		lp = add_tList_node_Buffer(lp, key, data);
	}
	
	free_Buffer(&key);
	free_Buffer(&data);

	return lp;
}





/**
tList*  get_protocol_header_list_file(char* fname, char deli, int fstline, int rcntnt)

	機能：ファイル fname からヘッダ情報を読み込んで分解し，リストに格納して返す．

	引数：fname   -- 読み込むファイル名
		  deli	-- ヘッダの見出し(Key)との境界文字．HTTPや SMTPでは ':'
		  fstline -- 一行目を特別扱いにするか？ 一行目が key'deli' data の形式でないプロトコル用, ex) HTTP, SIP
					 'deli'は境界文字
		  rcntnt  -- コンテンツも読むか？

	戻り値：ヘッダ情報を格納したリストへのポインタ．

*/
tList*  get_protocol_header_list_file(char* fname, char deli, int fstline, int rcntnt)
{
	Buffer buf;
	tList* lp;

	if (fname==NULL) return NULL;

	buf = read_Buffer_file(fname);
	lp  = get_protocol_header_list(buf, deli, fstline, rcntnt);

	return lp;
}





//////////////////////////////////////////////////////////////////////////////////////////////////
// Search

/**
Buffer  search_protocol_header(tList* list, char* key, int no) 

	機能：ヘッダリストの中から no番目の keyノードを探し出し，値（ldat.val.buf）のコピーを返す．
		  値が複数行に渡る場合は，\r\n を挿入して行を結合する．
		  検索では keyの長さに合わせる．また, keyはケースインセンシティブ．

	引数：list -- 検索対象のヘッダ方向を格納したリスト
		  key  -- ヘッダ種別．
		  no   -- 同じヘッダ種別のノードが複数ある場合，何番目のノードかを指定する．１から数える．

	戻り値：
			要free
*/
Buffer  search_protocol_header(tList* list, char* key, int no) 
{
	tList* pp;
	Buffer buf;

	buf = init_Buffer();
	if (list==NULL || key==NULL) return buf;
	
	pp = strncasecmp_tList(list, key, 0, no);		// 完全一致
	if (pp!=NULL) {
		buf = dup_Buffer(pp->ldat.val);

		while (is_header_continue(pp)) {
			cat_s2Buffer("\r\n", &buf);
			pp = pp->next;
			cat_Buffer(&(pp->ldat.val), &buf);
		}
	}
	
	return buf;
}





/**
Buffer  search_protocol_header_item(tList* list, char* key, int no, char deli, int nm) 

	機能：key をキーにした no番目のノードの値の中で，deliを区切りにした nm番目の項目（文字列）を返す．

	引数：list -- 検索対象のヘッダ方向を格納したリスト
		  key  -- ヘッダ種別．
		  no   -- 同じヘッダ種別が複数ある場合，何番目のノードかを指定する．１から数える．
		  deli -- ノード値（文字列）の区切り文字．
		　nm   -- deli を区切り文字として何番目の項目か？ 1から数える．

	戻り値：
		 指定した項目（文字列）のコピー．

*/
Buffer  search_protocol_header_item(tList* list, char* key, int no, char deli, int nm) 
{
	Buffer buf, itm;

	buf = search_protocol_header(list, key, no);
	if (buf.buf==NULL) return buf;
	
	itm = cawk_Buffer(buf, deli, nm);
	free_Buffer(&buf);

	return itm;
}





/**
Buffer  search_protocol_header_value(tList* list, char* key, char* data, int no) 

	機能：ヘッダリストの中から no番目の keyノードを探し出し，dataで始まるノードの値（ldat.val.buf）のコピーを返す．
		  key, dataはケースインセンシティブ．data が複数行に継続している場合は，継続している行も単独の行として検査される．

	引数：list -- 検索対象のヘッダ方向を格納したリスト
		  key  -- ヘッダ種別．
	　　　data -- 検索するヘッダ値の最初の文字．NULL なら全てと一致．
		  no   -- 同じヘッダ種別のノードが複数ある場合，何番目のノードかを指定する．１から数える．
	
	戻り値：一致したノードのノード値のコピー．

*/
Buffer  search_protocol_header_value(tList* list, char* key, char* data, int no) 
{
	Buffer buf;
	char*  str;
	int	   len, nm;

	buf = init_Buffer();
	if (list==NULL || key==NULL) return buf;

	if (data==NULL) {
		buf = search_protocol_header(list, key, no);
		return buf;
	}

	buf = init_Buffer();
	len = (int)strlen(data); 
	
	nm = 0;
	while (list!=NULL) {
		if (list->ldat.key.buf!=NULL && !strcasecmp((char*)list->ldat.key.buf, key)) {
			str = (char*)list->ldat.val.buf;

			if (str!=NULL && !strncasecmp(str, data, len)) {
				nm++;
				if (no==nm) {
					buf = make_Buffer_bystr(str);
					return buf;
				}
			}

			while (is_header_continue(list)) {
				list = list->next;
				str  = (char*)list->ldat.val.buf;
				if (str!=NULL && !strncasecmp(str, data, len)) {
					nm++;
					if (no==nm) {
						buf = make_Buffer_bystr(str);
						return buf;
					}
				}
			}
		}

		list = list->next;
	}

	return buf;
}





/**
Buffer  search_protocol_header_partvalue(tList* list, char* key, char* data, int no) 

	機能：ヘッダリストの中から no番目の keyノードを探し出し，dataの文字列を含むノードの値（ldat.val.buf）のコピーを返す．
		  key, dataはケースインセンシティブ．data が複数行に継続している場合は，継続している行も単独の行として検査される．

	引数：list -- 検索対象のヘッダ方向を格納したリスト
		  key  -- ヘッダ種別．
	　　　data -- 検索するヘッダ値の最初の文字．NULL なら全てと一致．
		  no   -- 同じヘッダ種別のノードが複数ある場合，何番目のノードかを指定する．１から数える．

	戻り値：一致したノードのノード値のコピー．
*/
Buffer  search_protocol_header_partvalue(tList* list, char* key, char* data, int no) 
{
	Buffer buf;
	char*  str;
	int	   len, nm;

	buf = init_Buffer();
	if (list==NULL || key==NULL) return buf;

	if (data==NULL) {
		buf = search_protocol_header(list, key, no);
		return buf;
	}

	buf = init_Buffer();
	len = (int)strlen(data); 
	
	nm = 0;
	while (list!=NULL) {
		if (list->ldat.key.buf!=NULL && !strcasecmp((char*)list->ldat.key.buf, key)) {
			str = (char*)list->ldat.val.buf;

			if (str!=NULL && strstrcase(str, data)) {
				nm++;
				if (no==nm) {
					buf = make_Buffer_bystr(str);
					return buf;
				}
			}

			while (is_header_continue(list)) {
				list = list->next;
				str  = (char*)list->ldat.val.buf;
				if (str!=NULL && strstrcase(str, data)) {
					nm++;
					if (no==nm) {
						buf = make_Buffer_bystr(str);
						return buf;
					}
				}
			}
		}

		list = list->next;
	}

	return buf;
}








//////////////////////////////////////////////////////////////////////////////////////////////////
// Set

/*
int 	set_protocol_header(tList* list, char* key, char* value, int no, int add_mode)

	機能：リスト(lt)中の no番目の keyのノード値に valueを設定する．
		  no が 0以下の場合は，全ての keyノードの値に対して設定が行われる．
		  keyノードが存在せず，かつ mode==ON の場合は，リストの最後（コンテンツの前）に追加される．

		  set_value_tList() との違いは，追加時の追加の仕方のみ．(in Lib/tlist.c)

	引数：
		list   -- 処理対象のリスト
		key	   -- 設定を行うノードのキー部
		value  -- 設定される文字列．
		no	   -- 何個目のノードに対して設定を行うか．1から数える．
				  0以下の場合はkeyが一致するすべてのノードに対して設定を行う．
		add_mod - この値がON かつ指定したノードが無い場合，ノードリストの最後（ただしコンテンツの前）に追加する．

   戻り値：
		設定されたノードの数．指定されたノードが存在しない場合は（追加された場合も）0
		負数の場合はエラー．

*/
int 	set_protocol_header(tList* list, char* key, int no, char* value, int add_mode)
{
	int cn = set_value_tList(list, key, no, value, OFF);

	// Not Found
	if (add_mode==ON && cn==0) {
		tList* pm = strncmp_tList(list, HDLIST_END_KEY, 0, 1);
		if (pm!=NULL && pm->prev!=NULL) {
			add_tList_node_str(pm->prev, key, value);
		}
		else {
			add_tList_node_str(list, key, value);
		}
	}

	return cn;
}





/**
int 	replace_protocol_header(tList* list, char* key, char* srcval, char* value, int no)

	機能：リスト(lt)中の no番目の keyノードの値の srcvalの部分を value に置き換える．
		  no が 0以下の場合は，全ての keyノードの値に対して置き換えが行われる．

		  replace_valute_tList() と同じ関数 (in Lib/tlist.c)

	引数：
		list   -- 処理対象のリスト
		key	   -- 置き換えを行うノードのキー部
		srcval -- 置き換え対象の文字列．
		value  -- 置き換えを行う文字列．
		no	   -- 何個目の項目を置き換えるか．1から数える．0以下の場合はkeyが一致する全ての項目を置き換える

	戻り値：
		変更されたノードの数．指定されたノードが存在しないは（追加された場合も）0
		負数の場合はエラー．

	#define replace_protocol_header(l, k, n, s, d)	replace_value_tList((l), (k), (n), (s), (d))
*/





/**
int		set_protocol_header_item(tList* list, char* key, int no, char deli, int nm, char* value)

	機能：key をキーにした no番目のノードの値の中で，deliを区切りにした nm番目の項目（文字列）に
		　value を設定する．

	引数：list -- 検索対象のヘッダ方向を格納したリスト
		  key  -- ヘッダ種別．
		  no   -- 同じヘッダ種別が複数ある場合，何番目のノードかを指定する．１から数える．
				  noが 0以下の場合は keyが一致する全てのノードに対して操作を行う．
		  deli -- ノード値（文字列）の区切り文字．
		　nm   -- deli を区切り文字として何番目の項目か？ 1から数える．
		  value - 設定する文字列．

	戻り値：設定を行ったノード数．

int		set_protocol_header_item(tList* list, char* key, int no, char deli, int nm, char* value)
*/





/**
int		replace_protocol_header_item(tList* list, char* key, int no, char deli, int nm, char* srcval, char* value)

	機能：key をキーにした no番目のノードの値の中で，deliを区切りにした nm番目の項目（文字列）の
		　srcval部分を valueで置き換える．

	引数：list  -- 検索対象のヘッダ方向を格納したリスト
		  key   -- ヘッダ種別．
		  no	-- 同じヘッダ種別が複数ある場合，何番目のノードかを指定する．１から数える．
				   noが 0以下の場合は keyが一致する全てのノードに対して操作を行う．
		  deli  -- ノード値（文字列）の区切り文字．
		　nm	-- deli を区切り文字として何番目の項目か？ 1から数える．
		  srcval - 置き換え対象の文字列．NULLなら指定した項目の文字列全体
		  value  - 置き換える文字列．

	戻り値：置き換えを行ったノード数．

int		replace_protocol_header_item(tList* list, char* key, int no, char deli, int nm, char* srcval, char* value)
*/





/**
int search_crlfcrlf(char* mesg) 

	機能：文字列中の空行を探す．改行コードは 0x0d, 0x0a または 0x0a

	戻り値：空行後の次の行の先頭の位置
			-1 の場合は空行無し．

*/
int  search_crlfcrlf(char* mesg) 
{
	int cr = 0;		// dummy
	int lf = 0;
	int i;

	if (mesg==NULL)	return -2;
	if (mesg[0]==0x0a) return 1;
	if (mesg[0]==0x0d && mesg[1]==0x0a) return 2;

	i = 0;
	while(mesg[i]!='\0') {
		if	  (mesg[i]==0x0d) cr++;
		else if (mesg[i]==0x0a) lf++;
		else {
			cr = lf = 0;
		}

		if (lf==2) return i+1;
		i++;
	}
	return -1;
}




/**
int  is_header_continue(tList* pp)

	機能：pp が指しているヘッダ値が次のリストへ続いているかどうか判定する．
		  プロトコル上では，ヘッダ値が複数行に渡る場合に相当する．

*/
int  is_header_continue(tList* pp)
{
	if (pp==NULL || pp->next==NULL || pp->next->ldat.key.buf==NULL)	return FALSE;
	if (!strcmp((const char*)pp->next->ldat.key.buf, HDLIST_CONTINUE)) return TRUE;
	return FALSE;
}






