#include "pangu_logger.h"
#include "pattern_replace.h"
#include "pangu_web_cache.h"

#include <tfe_proxy.h>
#include <tfe_stream.h>
#include <tfe_utils.h>
#include <tfe_http.h>
#include <tfe_plugin.h>

#include <MESA/Maat_rule.h>
#include <MESA/MESA_handle_logger.h>
#include <MESA/MESA_prof_load.h>
#include <MESA/stream.h>

#include <event2/event.h>
#include <event2/buffer.h>

#include <ctemplate/template.h>
#include <assert.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>


#define	MAX_SCAN_RESULT				16
#define	MAX_EDIT_ZONE_NUM			64

enum pangu_action//Bigger action number is prior.
{
	PG_ACTION_NONE = 0x00,
	PG_ACTION_MONIT = 0x01,
	PG_ACTION_FORWARD = 0x02,    /* N/A */
	PG_ACTION_REJECT = 0x10,
	PG_ACTION_DROP = 0x20,    /* N/A */
	PG_ACTION_REDIRECT = 0x30,
	PG_ACTION_RATELIMIT = 0x40,    /* N/A */
	PG_ACTION_REPLACE = 0x50,
	PG_ACTION_LOOP = 0x60,    /* N/A */
	PG_ACTION_WHITELIST = 0x80,
	__PG_ACTION_MAX
};

enum scan_table
{
	PXY_CTRL_IP,
	PXY_CTRL_HTTP_URL,
	PXY_CTRL_HTTP_REQ_HDR,
	PXY_CTRL_HTTP_REQ_BODY,
	PXY_CTRL_HTTP_RES_HDR,
	PXY_CTRL_HTTP_RES_BODY,
	__SCAN_TABLE_MAX
};

enum pangu_http_stat
{
	STAT_SESSION,
	STAT_LOG_NUM,
	STAT_ACTION_MONIT,
	STAT_ACTION_REJECT,
	STAT_ACTION_REDIRECT,
	STAT_ACTION_REPLACE,
	STAT_ACTION_WHITELSIT,
	__PG_STAT_MAX
};
struct pangu_rt
{
	Maat_feather_t maat;
	struct pangu_logger * send_logger;
	void * local_logger;
	int log_level;
	int thread_num;
	int scan_table_id[__SCAN_TABLE_MAX];
	ctemplate::Template * tpl_403, * tpl_404, * tpl_451;
	char * reject_page;
	int page_size;

	int cache_enabled;
	struct cache_handle* cache;

	screen_stat_handle_t fs_handle;
	long long stat_val[__PG_STAT_MAX];
	int fs_id[__PG_STAT_MAX];
	struct event_base* gc_evbase;
	struct event* gcev;
};
struct pangu_rt * g_pangu_rt;

#define MAAT_INPUT_JSON			0
#define MAAT_INPUT_REDIS    	1
#define MAAT_INPUT_FILE        	2

static Maat_feather_t create_maat_feather(const char * profile, const char * section, int max_thread, void * logger)
{
	Maat_feather_t target;
	int input_mode = 0, maat_stat_on = 0, maat_perf_on = 0;
	int ret = 0, scan_detail = 0, effect_interval = 60;
	char table_info[TFE_STRING_MAX] = {0}, inc_cfg_dir[TFE_STRING_MAX] = {0}, ful_cfg_dir[TFE_STRING_MAX] = {0};
	char redis_server[TFE_STRING_MAX] = {0};
	int redis_port = 0;
	int redis_db_idx = 0;
	char json_cfg_file[TFE_STRING_MAX] = {0}, maat_stat_file[TFE_STRING_MAX] = {0};
	const char * instance_name = "pangu";
	MESA_load_profile_int_def(profile, section, "MAAT_INPUT_MODE", &(input_mode), 0);
	MESA_load_profile_int_def(profile, section, "STAT_SWITCH", &(maat_stat_on), 1);
	MESA_load_profile_int_def(profile, section, "PERF_SWITCH", &(maat_perf_on), 1);

	MESA_load_profile_string_def(profile, section, "TABLE_INFO", table_info, sizeof(table_info), "");

	MESA_load_profile_string_def(profile, section, "JSON_CFG_FILE", json_cfg_file, sizeof(json_cfg_file), "");

	MESA_load_profile_string_def(profile, section, "MAAT_REDIS_SERVER", redis_server, sizeof(redis_server), "");
	MESA_load_profile_int_def(profile, section, "MAAT_REDIS_PORT", &(redis_port), 6379);
	MESA_load_profile_int_def(profile, section, "MAAT_REDIS_DB_INDEX", &(redis_db_idx), 0);

	MESA_load_profile_string_def(profile, section, "INC_CFG_DIR", inc_cfg_dir, sizeof(inc_cfg_dir), "");
	MESA_load_profile_string_def(profile, section, "FULL_CFG_DIR", ful_cfg_dir, sizeof(ful_cfg_dir), "");

	MESA_load_profile_string_def(profile, section, "STAT_FILE", maat_stat_file, sizeof(maat_stat_file), "");
	MESA_load_profile_int_def(profile, section, "EFFECT_INTERVAL_S", &(effect_interval), 60);

	effect_interval *= 1000;//convert s to ms
	assert(strlen(inc_cfg_dir) != 0 && strlen(ful_cfg_dir) != 0);

	target = Maat_feather(max_thread, table_info, logger);
	Maat_set_feather_opt(target, MAAT_OPT_INSTANCE_NAME, instance_name, strlen(instance_name) + 1);
	switch (input_mode)
	{
		case MAAT_INPUT_JSON:
			Maat_set_feather_opt(target, MAAT_OPT_JSON_FILE_PATH, json_cfg_file, strlen(json_cfg_file) + 1);
			break;
		case MAAT_INPUT_REDIS: Maat_set_feather_opt(target, MAAT_OPT_REDIS_IP, redis_server, strlen(redis_server) + 1);
			Maat_set_feather_opt(target, MAAT_OPT_REDIS_PORT, &redis_port, sizeof(redis_port));
			Maat_set_feather_opt(target, MAAT_OPT_REDIS_INDEX, &redis_db_idx, sizeof(redis_db_idx));
			break;
		case MAAT_INPUT_FILE: Maat_set_feather_opt(target, MAAT_OPT_FULL_CFG_DIR, ful_cfg_dir, strlen(ful_cfg_dir) + 1);
			Maat_set_feather_opt(target, MAAT_OPT_INC_CFG_DIR, inc_cfg_dir, strlen(inc_cfg_dir) + 1);
			break;
		default: TFE_LOG_ERROR(logger, "Invalid MAAT Input Mode: %d.", input_mode);
			goto error_out;
			break;
	}

	if (maat_stat_on)
	{
		Maat_set_feather_opt(target, MAAT_OPT_STAT_FILE_PATH, maat_stat_file, strlen(maat_stat_file) + 1);
		Maat_set_feather_opt(target, MAAT_OPT_STAT_ON, NULL, 0);
		if (maat_perf_on)
		{
			Maat_set_feather_opt(target, MAAT_OPT_PERF_ON, NULL, 0);
		}
	}

	Maat_set_feather_opt(target, MAAT_OPT_EFFECT_INVERVAL_MS, &effect_interval, sizeof(effect_interval));
	Maat_set_feather_opt(target, MAAT_OPT_SCAN_DETAIL, &scan_detail, sizeof(scan_detail));

	ret = Maat_initiate_feather(target);
	if (ret < 0)
	{
		TFE_LOG_ERROR(logger, "%s MAAT init failed.", __FUNCTION__);
		goto error_out;
	}

	return target;
error_out:
	Maat_burn_feather(target);
	return NULL;
}
static void pangu_http_gc_cb(evutil_socket_t fd, short what, void * arg)
{
	int i=0;

	for(i=0;i<__PG_STAT_MAX;i++)
	{
		FS_operate(g_pangu_rt->fs_handle, g_pangu_rt->fs_id[i], 0, FS_OP_SET, ATOMIC_READ(&(g_pangu_rt->stat_val[i])));
	}
	return;
}

static void pangu_http_stat_init(struct pangu_rt * pangu_runtime)
{
	int i=0;
	struct timeval gc_delay = {0, 500*1000};	//Microseconds, we set 500 miliseconds here.
	const char* spec[__PG_STAT_MAX]={0};
	spec[STAT_SESSION]="http_sess";
	spec[STAT_LOG_NUM]="log_num";
	spec[STAT_ACTION_MONIT]="monit";
	spec[STAT_ACTION_REJECT]="reject";
	spec[STAT_ACTION_REDIRECT]="redirect";
	spec[STAT_ACTION_REPLACE]="replace";
	spec[STAT_ACTION_WHITELSIT]="whitelist";

	for(i=0;i<__PG_STAT_MAX;i++)
	{
		if(spec[i]!=NULL)
		{
			pangu_runtime->fs_id[i]=FS_register(pangu_runtime->fs_handle, FS_STYLE_STATUS, FS_CALC_CURRENT, spec[i]);
		}
	}
	g_pangu_rt->gcev = event_new(pangu_runtime->gc_evbase, -1, EV_PERSIST, pangu_http_gc_cb, NULL);
    evtimer_add(g_pangu_rt->gcev, &gc_delay);

	return;
}


int pangu_http_init(struct tfe_proxy * proxy)
{
	const char * profile = "./pangu_conf/pangu_pxy.conf";
	const char * logfile = "./log/pangu_pxy.log";
	g_pangu_rt = ALLOC(struct pangu_rt, 1);
	g_pangu_rt->thread_num = tfe_proxy_get_work_thread_count();
	g_pangu_rt->gc_evbase=tfe_proxy_get_gc_evbase();
	MESA_load_profile_int_def(profile, "DEBUG", "LOG_LEVEL", &(g_pangu_rt->log_level), 0);
	g_pangu_rt->local_logger = MESA_create_runtime_log_handle(logfile, g_pangu_rt->log_level);
	g_pangu_rt->send_logger = pangu_log_handle_create(profile, "LOG", g_pangu_rt->local_logger);
	if (!g_pangu_rt->send_logger)
	{
		goto error_out;
	}
	g_pangu_rt->fs_handle = tfe_proxy_get_fs_handle();
	pangu_http_stat_init(g_pangu_rt);
	

	
	g_pangu_rt->maat = create_maat_feather(profile, "MAAT", g_pangu_rt->thread_num, g_pangu_rt->local_logger);
	if (!g_pangu_rt->maat)
	{
		goto error_out;
	}
	const char * table_name[__SCAN_TABLE_MAX];
	table_name[PXY_CTRL_IP] = "PXY_CTRL_IP";
	table_name[PXY_CTRL_HTTP_URL] = "PXY_CTRL_HTTP_URL";
	table_name[PXY_CTRL_HTTP_REQ_HDR] = "PXY_CTRL_HTTP_REQ_HDR";
	table_name[PXY_CTRL_HTTP_REQ_BODY] = "PXY_CTRL_HTTP_REQ_BODY";
	table_name[PXY_CTRL_HTTP_RES_HDR] = "PXY_CTRL_HTTP_RES_HDR";
	table_name[PXY_CTRL_HTTP_RES_BODY] = "PXY_CTRL_HTTP_RES_BODY";
	for (int i = 0; i < __SCAN_TABLE_MAX; i++)
	{

		g_pangu_rt->scan_table_id[i] = Maat_table_register(g_pangu_rt->maat, table_name[i]);
		if (g_pangu_rt->scan_table_id[i] < 0)
		{
			TFE_LOG_ERROR(NULL, "Pangu HTTP Maat table %s register failed.", table_name[i]);
			goto error_out;
		}
	}

	char page_path[256];
	memset(page_path, 0, sizeof(page_path));
	MESA_load_profile_string_def(profile, "TEMPLATE", "PAGE_403", page_path, sizeof(page_path),
		"./pangu_conf/template/HTTP403.html");
	g_pangu_rt->tpl_403 = ctemplate::Template::GetTemplate(page_path, ctemplate::DO_NOT_STRIP);
	memset(page_path, 0, sizeof(page_path));
	MESA_load_profile_string_def(profile, "TEMPLATE", "PAGE_404", page_path, sizeof(page_path),
		"./pangu_conf/template/HTTP404.html");
	g_pangu_rt->tpl_404 = ctemplate::Template::GetTemplate(page_path, ctemplate::DO_NOT_STRIP);
	memset(page_path, 0, sizeof(page_path));
	MESA_load_profile_string_def(profile, "TEMPLATE", "PAGE_451", page_path, sizeof(page_path),
		"./pangu_conf/template/HTTP451.html");
	g_pangu_rt->tpl_451 = ctemplate::Template::GetTemplate(page_path, ctemplate::DO_NOT_STRIP);

	MESA_load_profile_int_def(profile, "TANGO_CACHE", "enable_cache", &(g_pangu_rt->cache_enabled), 1);
	if(g_pangu_rt->cache_enabled)
	{
		g_pangu_rt->cache = create_web_cache_handle(profile, "TANGO_CACHE", g_pangu_rt->gc_evbase, g_pangu_rt->local_logger);
		if(!g_pangu_rt->cache)
		{
			TFE_LOG_INFO(NULL, "Tango Cache init failed.");
			goto error_out;
		}
		TFE_LOG_INFO(NULL, "Tango Cache Enabled.");
	}
	TFE_LOG_INFO(NULL, "Pangu HTTP init success.");
	return 0;

error_out:
	TFE_LOG_ERROR(NULL, "Pangu HTTP init failed.");
	return -1;
}

struct replace_ctx
{
	struct replace_rule * rule;
	size_t n_rule;
	struct tfe_http_half * replacing;
	struct evbuffer * http_body;
	int actually_replaced;
};

struct pangu_http_ctx
{
	enum pangu_action action;
	char * action_para;
	scan_status_t mid;
	stream_para_t sp;

	struct Maat_rule_t * enforce_rules;
	size_t n_enforce;
	char * enforce_para;

	struct replace_ctx * rep_ctx;

	int resume_from_cache_query;
	enum cache_query_status cache_query_status;
	struct future* f_cache_query;
	struct tfe_http_session * ref_session;
	struct tfe_http_half* cached_response;
	size_t cache_result_declared_sz, cache_result_actual_sz;
	struct cache_update_context* cache_update_ctx;
	
	int thread_id;
};
void http_repl_ctx_free(struct replace_ctx* rep_ctx)
{
	for (size_t i = 0; i < rep_ctx->n_rule; i++)
	{
		FREE(&(rep_ctx->rule[i].find));
		FREE(&(rep_ctx->rule[i].replace_with));
	}
	if (rep_ctx->http_body)
	{
		evbuffer_free(rep_ctx->http_body);
		rep_ctx->http_body = NULL;
	}
	//todo destroy http_half;
	assert(rep_ctx->replacing == NULL);
	FREE(&rep_ctx);
	return;
}
static struct pangu_http_ctx * pangu_http_ctx_new(unsigned int thread_id)
{
	struct pangu_http_ctx * ctx = ALLOC(struct pangu_http_ctx, 1);
	ctx->mid = NULL;
	ctx->thread_id = (int) thread_id;
	return ctx;
}

static void pangu_http_ctx_free(struct pangu_http_ctx * ctx)
{
	if (ctx->rep_ctx)
	{
		http_repl_ctx_free(ctx->rep_ctx);
		ctx->rep_ctx = NULL;
	}
	FREE(&ctx->enforce_rules);
	FREE(&ctx->enforce_para);
	Maat_clean_status(&(ctx->mid));	
	ctx->mid = NULL;

	if(ctx->sp)
	{
		Maat_stream_scan_string_end(&(ctx->sp));
	}
	if(ctx->cache_update_ctx)
	{
		web_cache_update_end(ctx->cache_update_ctx);
		ctx->cache_update_ctx=NULL;
	}
	assert(ctx->cached_response==NULL);
	
	if(ctx->f_cache_query)
	{
		future_destroy(ctx->f_cache_query);
		ctx->f_cache_query=NULL;
	}
	FREE(&ctx);
}

inline void addr_tfe2sapp(const struct tfe_stream_addr * tfe_addr, struct ipaddr * sapp_addr)
{
	if(tfe_addr->addrtype==TFE_ADDR_STREAM_TUPLE4_V4||tfe_addr->addrtype==TFE_ADDR_IPV4)
	{
		sapp_addr->addrtype = ADDR_TYPE_IPV4;
	}
	else
	{
		sapp_addr->addrtype=ADDR_TYPE_IPV6;
	}
	sapp_addr->paddr = (char *) tfe_addr->paddr;
	return;
}
int action_cmp(enum pangu_action a1, enum pangu_action a2)
{
	int weight[__PG_ACTION_MAX];
	memset(weight, 0, sizeof(weight));
	weight[PG_ACTION_MONIT]		=	0;
	weight[PG_ACTION_REPLACE]	=	1;
	weight[PG_ACTION_REDIRECT]	=	2;
	weight[PG_ACTION_REJECT]	=	3;
	weight[PG_ACTION_WHITELIST]	=	4;
	return weight[a1]-weight[a2];
}
//enforce_rules[0] contains execute action.
static enum pangu_action decide_ctrl_action(const struct Maat_rule_t * hit_rules, size_t n_hit,
	struct Maat_rule_t ** enforce_rules, size_t * n_enforce)
{
	size_t n_monit = 0, exist_enforce_num = 0, i = 0;
	const struct Maat_rule_t * prior_rule = hit_rules;
	struct Maat_rule_t monit_rule[n_hit];
	enum pangu_action prior_action = PG_ACTION_NONE;

	for (i = 0; i < n_hit; i++)
	{
		if ((enum pangu_action) hit_rules[i].action == PG_ACTION_MONIT)
		{
			memcpy(monit_rule + n_monit, hit_rules + i, sizeof(struct Maat_rule_t));
			n_monit++;
		}
		if (action_cmp((enum pangu_action) hit_rules[i].action, prior_action)>0)
		{
			prior_rule = hit_rules + i;
			prior_action = (enum pangu_action) hit_rules[i].action;
		}
		else if (action_cmp((enum pangu_action) hit_rules[i].action, prior_action) == 0)
		{
			if (hit_rules[i].config_id > prior_rule->config_id)
			{
				prior_rule = hit_rules + i;
			}
		}
		else
		{
			continue;
		}
	}

	if (prior_action == PG_ACTION_WHITELIST)
	{
		return PG_ACTION_WHITELIST;
	}

	exist_enforce_num = *n_enforce;
	if (prior_action == PG_ACTION_MONIT)
	{
		*n_enforce += n_monit;
	}
	else
	{
		*n_enforce += n_monit + 1;
	}

	*enforce_rules = (struct Maat_rule_t *) realloc(*enforce_rules, sizeof(struct Maat_rule_t) * (*n_enforce));

	if (prior_action == PG_ACTION_MONIT)
	{
		memcpy(*enforce_rules + exist_enforce_num, monit_rule, n_monit * sizeof(struct Maat_rule_t));
	}
	else
	{
		memcpy(*enforce_rules + exist_enforce_num, prior_rule, sizeof(struct Maat_rule_t));
		memcpy(*enforce_rules + exist_enforce_num + 1, monit_rule, n_monit * sizeof(struct Maat_rule_t));
	}

	return prior_action;
}
//HTML template is downloaded from https://github.com/AndiDittrich/HttpErrorPages
static void html_generate(int status_code, int cfg_id, const char* msg, char ** page_buff, size_t * page_size)
{
	ctemplate::TemplateDictionary dict("pg_page_dict");	//dict is automatically finalized after function returned.
	dict.SetIntValue("cfg_id", cfg_id);
	dict.SetValue("msg", msg);
	std::string output;
	ctemplate::Template * tpl = NULL;

	switch (status_code)
	{
		case 403: tpl = g_pangu_rt->tpl_403;
			break;
		case 404: tpl = g_pangu_rt->tpl_404;
			break;
		case 451: tpl = g_pangu_rt->tpl_451;
			break;
		default: return;
	}

	tpl->Expand(&output, &dict);
	*page_size = output.length() + 1;
	*page_buff = tfe_strdup(output.c_str());
}

static void html_free(char ** page_buff)
{
	FREE(page_buff);
	return;
}
static void cache_query_on_succ(future_result_t * result, void * user)
{
	struct pangu_http_ctx * ctx = (struct pangu_http_ctx *)user;
	struct cached_meta* meta=NULL;
	enum cache_query_result_type type=cache_query_result_get_type(result);
	const unsigned char* data=NULL;
	size_t data_sz=0;
	char temp[TFE_STRING_MAX];

	switch(type)
	{
		case CACHE_QUERY_RESULT_META:
			meta=cache_query_result_get_header(result);
			ctx->cache_result_declared_sz=meta->content_length;
			ctx->resume_from_cache_query=1; 
			tfe_http_session_resume(ctx->ref_session);
			
			ctx->cached_response=tfe_http_session_response_create(ctx->ref_session, 200);		
			tfe_http_nonstd_field_write(ctx->cached_response, "X-Cache-Lookup", "Hit From TFE");
			tfe_http_std_field_write(ctx->cached_response, TFE_HTTP_CONT_TYPE, meta->content_type);
			snprintf(temp, sizeof(temp), "%lu", meta->content_length);
			tfe_http_std_field_write(ctx->cached_response, TFE_HTTP_CONT_LENGTH, temp);
			
			tfe_http_session_response_set(ctx->ref_session, ctx->cached_response);
			tfe_http_half_write_body_begin(ctx->cached_response, 1);
			
			cache_query_free_meta(meta);			
			meta=NULL;
			break;
		case CACHE_QUERY_RESULT_DATA:
			data_sz=cache_query_result_get_data(result, &data);
			tfe_http_half_write_body_data(ctx->cached_response, data, data_sz);
			ctx->cache_result_actual_sz+=data_sz;
			break;
		case CACHE_QUERY_RESULT_END:
			assert(ctx->cached_response!=NULL);
			ctx->cache_query_status=WEB_CACHE_HIT;
			printf("cache query hit: %s\n", ctx->ref_session->req->req_spec.url);
			tfe_http_half_write_body_end(ctx->cached_response);
			//ownership has been transferred to http session, set to NULL.
			ctx->cached_response=NULL;
			assert(ctx->cache_result_actual_sz==ctx->cache_result_declared_sz);
			future_destroy(ctx->f_cache_query);
			ctx->f_cache_query=NULL;
			break;
		case CACHE_QUERY_RESULT_MISS:
			ctx->cache_query_status=WEB_CACHE_NOT_HIT;
			printf("cache query miss: %s\n", ctx->ref_session->req->req_spec.url);		
			ctx->resume_from_cache_query=1;
			tfe_http_session_resume(ctx->ref_session);
			future_destroy(ctx->f_cache_query);
			ctx->f_cache_query=NULL;
			break;
		default:
			break;
	}
	return;
}
static void cache_query_on_fail(enum e_future_error err, const char * what, void * user)
{
	struct pangu_http_ctx * ctx = (struct pangu_http_ctx *)user;
	future_destroy(ctx->f_cache_query);
	ctx->f_cache_query=NULL;
	ctx->cache_query_status=WEB_CACHE_NOT_HIT;
	ctx->resume_from_cache_query=1;
	if(!ctx->cached_response)
	{
		tfe_http_session_resume(ctx->ref_session);
	}
	else
	{
		tfe_http_half_write_body_end(ctx->cached_response);
		ctx->cached_response=NULL;
	}

	printf("cache query failed: %s %s\n", ctx->ref_session->req->req_spec.url, what);
}
void http_replace(const struct tfe_stream * stream, const struct tfe_http_session * session,
	enum tfe_http_event events, const unsigned char * body_frag, size_t frag_size, struct pangu_http_ctx * ctx)
{
	struct tfe_http_session * to_write_sess = NULL;
	char * rewrite_buff = NULL;
	size_t rewrite_sz = 0;

	to_write_sess = tfe_http_session_allow_write(session);
	if (to_write_sess == NULL)    //fail to wirte, abandon.
	{
		TFE_STREAM_LOG_INFO(stream, "tfe_http_session_allow_write() %s failed.", session->req->req_spec.uri);
		tfe_http_session_detach(session); return;
	}

	struct replace_ctx * rep_ctx = ctx->rep_ctx;
	if (ctx->rep_ctx == NULL)
	{
		/* we must determinate the replace action on HTTP header, otherwise,
		 * the header has been forwarded, only replace the body but not modify header will raise exception */
		if ((events & EV_HTTP_REQ_HDR) || (events & EV_HTTP_RESP_HDR))
		{
			ctx->rep_ctx = rep_ctx = ALLOC(struct replace_ctx, 1);
			rep_ctx->rule = ALLOC(struct replace_rule, MAX_EDIT_ZONE_NUM);
			rep_ctx->n_rule = format_replace_rule(ctx->enforce_para, rep_ctx->rule, MAX_EDIT_ZONE_NUM);
		}
		else
		{
			TFE_STREAM_LOG_INFO(stream, "Can only setup replace on REQ/RESP headers, detached.");
			tfe_http_session_detach(session); return;
		}
	}

	struct tfe_http_half * in_req_half = session->req;
	struct tfe_http_half * in_resp_half = session->resp;
	struct tfe_http_req_spec * in_req_spec = &in_req_half->req_spec;
	struct tfe_http_resp_spec * in_resp_spec = &in_resp_half->resp_spec;

	if ((events & EV_HTTP_REQ_HDR) || (events & EV_HTTP_RESP_HDR))
	{
		char * rewrite_uri = NULL;
		size_t rewrite_uri_sz=0;
		if (tfe_http_in_request(events))
		{
			rewrite_uri_sz = execute_replace_rule(in_req_spec->uri, strlen(in_req_spec->uri),
				kZoneRequestUri, rep_ctx->rule, rep_ctx->n_rule, &rewrite_uri);
			if(rewrite_uri_sz>0) rep_ctx->actually_replaced=1;
			rep_ctx->replacing = tfe_http_session_request_create(to_write_sess, in_req_spec->method,
				rewrite_uri_sz >0 ? rewrite_uri : in_req_spec->uri);

			tfe_http_session_request_set(to_write_sess, rep_ctx->replacing);
		}
		else
		{
			rep_ctx->replacing = tfe_http_session_response_create(to_write_sess, in_resp_spec->resp_code);
			tfe_http_session_response_set(to_write_sess, rep_ctx->replacing);
		}

		if (rewrite_uri != NULL)
		{
			FREE(&rewrite_uri);
		}

		enum replace_zone zone = tfe_http_in_request(events) ? kZoneRequestHeaders : kZoneResponseHeader;
		struct tfe_http_half * in_half = tfe_http_in_request(events) ? in_req_half : in_resp_half;

		struct http_field_name in_header_field{};
		const char * in_header_value = NULL;
		void * iterator = NULL;

		while (true)
		{
			if ((in_header_value = tfe_http_field_iterate(in_half, &iterator, &in_header_field)) == NULL)
			{
				break;
			}

			rewrite_buff = NULL;
			rewrite_sz = 0;
			rewrite_sz=execute_replace_rule(in_header_value,
				strlen(in_header_value), zone, rep_ctx->rule, rep_ctx->n_rule, &rewrite_buff);

			if(rewrite_sz>0) rep_ctx->actually_replaced=1;			
			tfe_http_field_write(rep_ctx->replacing, &in_header_field, rewrite_sz>0? rewrite_buff : in_header_value);
			if(rewrite_buff != NULL)
			{
				FREE(&rewrite_buff);
			}
		}
	}

	if ((events & EV_HTTP_REQ_BODY_BEGIN) || (events & EV_HTTP_RESP_BODY_BEGIN))
	{
		assert(rep_ctx->http_body == NULL);
		rep_ctx->http_body = evbuffer_new();
	}

	if ((events & EV_HTTP_REQ_BODY_CONT) || (events & EV_HTTP_RESP_BODY_CONT))
	{
		evbuffer_add(rep_ctx->http_body, body_frag, frag_size);
	}

	if ((events & EV_HTTP_REQ_BODY_END) || (events & EV_HTTP_RESP_BODY_END))
	{
		char * __http_body = (char *) evbuffer_pullup(rep_ctx->http_body, -1);
		size_t __http_body_len = evbuffer_get_length(rep_ctx->http_body);

		enum replace_zone r_zone = tfe_http_in_request(events) ? kZoneRequestBody : kZoneResponseBody;
		
		rewrite_buff = NULL;
		rewrite_sz = 0;
		
		rewrite_sz = execute_replace_rule(__http_body, __http_body_len, r_zone,
									rep_ctx->rule, rep_ctx->n_rule, &rewrite_buff);
		

		if (rewrite_sz >0 )
		{
			tfe_http_half_append_body(rep_ctx->replacing, rewrite_buff, rewrite_sz, 0);
			rep_ctx->actually_replaced=1;
		}
		else
		{
			tfe_http_half_append_body(rep_ctx->replacing, __http_body, __http_body_len, 0);
		}

		if (rewrite_buff != NULL)
		{
			FREE(&rewrite_buff);
		}

		if (rep_ctx->http_body != NULL)
		{
			evbuffer_free(rep_ctx->http_body);
			rep_ctx->http_body = NULL;
		}
	}

	if ((events & EV_HTTP_REQ_END) || (events & EV_HTTP_RESP_END))
	{
		tfe_http_half_append_body(rep_ctx->replacing, NULL, 0, 0);
		rep_ctx->replacing = NULL;
	}
}

static void http_reject(const struct tfe_http_session * session, enum tfe_http_event events,
	struct pangu_http_ctx * ctx)
{
	int resp_code = 0, ret = 0;
	struct tfe_http_half * response = NULL;
	char * page_buff = NULL;
	size_t page_size = 0;

	char cont_len_str[16];
	char msg[TFE_STRING_MAX];
	struct tfe_http_session * to_write_sess = NULL;

	ret = sscanf(ctx->enforce_para, "code=%d;content=%[^\n]", &resp_code, msg);
	if (ret != 2)
	{
		TFE_LOG_ERROR(g_pangu_rt->local_logger, "Invalid reject rule %d paramter %s",
			ctx->enforce_rules[0].config_id, ctx->enforce_para);

		ctx->action = PG_ACTION_NONE;
		goto error_out;
	}

	to_write_sess = tfe_http_session_allow_write(session);
	response = tfe_http_session_response_create(to_write_sess, resp_code);

	html_generate(resp_code, ctx->enforce_rules[0].config_id, msg, &page_buff, &page_size);
	tfe_http_std_field_write(response, TFE_HTTP_CONT_TYPE, "text/html; charset=utf-8");
	snprintf(cont_len_str, sizeof(cont_len_str), "%lu", page_size);
	tfe_http_std_field_write(response, TFE_HTTP_CONT_LENGTH, cont_len_str);

	tfe_http_half_append_body(response, page_buff, page_size, 0);
	tfe_http_half_append_body(response, NULL, 0, 0);
	tfe_http_session_response_set(to_write_sess, response);
	tfe_http_session_detach(session);

error_out:
	html_free(&page_buff);
}

static void http_redirect(const struct tfe_http_session * session, enum tfe_http_event events,
	struct pangu_http_ctx * ctx)
{
	int resp_code = 0, ret = 0;
	char * url = NULL;

	struct tfe_http_half * response = NULL;
	struct tfe_http_session * to_write = NULL;

	url = ALLOC(char, ctx->enforce_rules[0].serv_def_len);
	ret = sscanf(ctx->enforce_para, "code=%d;url=%[^;]", &resp_code, url);

	if (ret != 2)
	{
		TFE_LOG_ERROR(g_pangu_rt->local_logger, "Invalid redirect rule %d paramter %s",
			ctx->enforce_rules[0].config_id, ctx->enforce_para);
		goto error_out;
	}

	to_write = tfe_http_session_allow_write(session);
	if (to_write == NULL)
	{
		assert(0);
	}

	response = tfe_http_session_response_create(to_write, resp_code);
	tfe_http_std_field_write(response, TFE_HTTP_LOCATION, url);
	tfe_http_half_append_body(response, NULL, 0, 0);

	tfe_http_session_response_set(to_write, response);
	tfe_http_session_detach(session);

error_out:
	free(url);
}

enum pangu_action http_scan(const struct tfe_http_session * session, enum tfe_http_event events,
	const unsigned char * body_frag, size_t frag_size, struct pangu_http_ctx * ctx)
{
	void * interator = NULL;
	const char * field_val = NULL;
	struct http_field_name field_name;
	struct Maat_rule_t result[MAX_SCAN_RESULT];
	char buff[TFE_STRING_MAX], * p = NULL;
	int scan_ret = 0, table_id = 0, read_rule_ret = 0;
	size_t hit_cnt = 0, i = 0;

	if (events & EV_HTTP_REQ_HDR)
	{
		const char * str_url = session->req->req_spec.url;
		int str_url_length = (int) (strlen(session->req->req_spec.url));

		scan_ret = Maat_full_scan_string(g_pangu_rt->maat, g_pangu_rt->scan_table_id[PXY_CTRL_HTTP_URL],
			CHARSET_UTF8, str_url, str_url_length, result, NULL, MAX_SCAN_RESULT, &(ctx->mid), ctx->thread_id);

		if (scan_ret > 0)
		{
			hit_cnt += scan_ret;
		}
	}

	if ((events & EV_HTTP_REQ_HDR) || (events & EV_HTTP_RESP_HDR))
	{
		table_id = events & EV_HTTP_REQ_HDR ? g_pangu_rt->scan_table_id[PXY_CTRL_HTTP_REQ_HDR] : g_pangu_rt
			->scan_table_id[PXY_CTRL_HTTP_RES_HDR];
		while (hit_cnt < MAX_SCAN_RESULT)
		{
			field_val = tfe_http_field_iterate(session->req, &interator, &field_name);
			if (field_val == NULL)
			{
				break;
			}

			const char * str_field_name = http_field_to_string(&field_name);
			scan_ret = Maat_set_scan_status(g_pangu_rt->maat, &(ctx->mid), MAAT_SET_SCAN_DISTRICT,
				str_field_name, strlen(str_field_name));

			assert(scan_ret == 0);
			scan_ret = Maat_full_scan_string(g_pangu_rt->maat, table_id,
				CHARSET_UTF8, field_val, strlen(field_val),
				result + hit_cnt, NULL, MAX_SCAN_RESULT - hit_cnt, &(ctx->mid), ctx->thread_id);
			if (scan_ret > 0)
			{
				hit_cnt += scan_ret;
			}
		}
	}
	if ((events & EV_HTTP_REQ_BODY_BEGIN) | (events & EV_HTTP_RESP_BODY_BEGIN))
	{
		assert(ctx->sp == NULL);
		table_id = events & EV_HTTP_REQ_BODY_BEGIN ? g_pangu_rt->scan_table_id[PXY_CTRL_HTTP_REQ_BODY] : g_pangu_rt
			->scan_table_id[PXY_CTRL_HTTP_RES_BODY];
		ctx->sp = Maat_stream_scan_string_start(g_pangu_rt->maat, table_id, ctx->thread_id);
	}
	if (body_frag != NULL)
	{
		scan_ret = Maat_stream_scan_string(&(ctx->sp), CHARSET_UTF8, (const char *) body_frag, (int) frag_size,
			result + hit_cnt, NULL, MAX_SCAN_RESULT - hit_cnt, &(ctx->mid));
		if (scan_ret > 0)
		{
			hit_cnt += scan_ret;
		}
	}
	if ((events & EV_HTTP_REQ_BODY_END) | (events & EV_HTTP_RESP_BODY_END))
	{
		Maat_stream_scan_string_end(&(ctx->sp));
		ctx->sp = NULL;
	}

	if (hit_cnt > 0)
	{
		ctx->action = decide_ctrl_action(result, hit_cnt, &ctx->enforce_rules, &ctx->n_enforce);
		size_t __serv_def_len = (size_t)ctx->enforce_rules[0].serv_def_len;
		ctx->enforce_para = ALLOC(char, __serv_def_len);

		if (__serv_def_len > MAX_SERVICE_DEFINE_LEN)
		{
			read_rule_ret = Maat_read_rule(g_pangu_rt->maat, &ctx->enforce_rules[0], MAAT_RULE_SERV_DEFINE,
				ctx->enforce_para, ctx->enforce_rules[0].serv_def_len);
			assert(read_rule_ret == ctx->enforce_rules[0].serv_def_len);
		}
		else
		{
			strcpy(ctx->enforce_para, ctx->enforce_rules[0].service_defined);
		}

		if (hit_cnt > 1)
		{
			p = buff;
			for (i = 0; i < hit_cnt; i++)
			{
				p += snprintf(p, sizeof(buff) - (p - buff), "%d:", result[i].config_id);
			}

			*p = '\0';
			TFE_LOG_INFO(g_pangu_rt->local_logger, "Multiple rules matched: url=%s num=%lu ids=%s execute=%d.",
				session->req->req_spec.url, hit_cnt, buff, ctx->enforce_rules[0].config_id);
		}
	}

	return ctx->action;
}
void enforce_control_policy(const struct tfe_stream * stream, const struct tfe_http_session * session,
					enum tfe_http_event events, const unsigned char * body_frag, size_t frag_size, unsigned int thread_id,
					struct pangu_http_ctx * ctx)
{
	if(ctx->action==PG_ACTION_NONE||ctx->action==PG_ACTION_MONIT)
	{
		//ctx->action changed in http_scan.
		http_scan(session, events, body_frag, frag_size, ctx);
	}
	switch (ctx->action)
	{
		case PG_ACTION_NONE:
			break;
		case PG_ACTION_MONIT:
			ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_MONIT]));
			//send log on close.
			break;
		case PG_ACTION_REJECT: 
			http_reject(session, events, ctx);
			ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_REJECT]));
			break;
		case PG_ACTION_REDIRECT: 
			http_redirect(session, events, ctx);
			ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_REDIRECT]));
			break;
		case PG_ACTION_REPLACE:
			http_replace(stream, session, events, body_frag, frag_size, ctx);
			ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_REPLACE]));
			break;
		case PG_ACTION_WHITELIST: 
			tfe_http_session_detach(session);
			ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_WHITELSIT]));
			break;
		default: assert(0);
			break;
	}
	return;
}
void cache_query(const struct tfe_http_session * session,	unsigned int thread_id, struct pangu_http_ctx * ctx)
{
	ctx->f_cache_query=future_create("cache_down", cache_query_on_succ, cache_query_on_fail, ctx);
	ctx->cache_query_status=async_web_cache_query(g_pangu_rt->cache, thread_id, session->req, ctx->f_cache_query);
	if(ctx->cache_query_status==WEB_CACHE_QUERING)
	{
		ctx->ref_session=tfe_http_session_allow_write(session);
		tfe_http_session_suspend(ctx->ref_session);
	}
	else
	{
		future_destroy(ctx->f_cache_query);
		ctx->f_cache_query=NULL;
	}
}
void cache_update(const struct tfe_http_session * session, enum tfe_http_event events, 
								const unsigned char * body_frag, size_t frag_size,
								unsigned int thread_id, struct pangu_http_ctx * ctx)
{

	if(events & EV_HTTP_RESP_BODY_BEGIN && ctx->cache_query_status == WEB_CACHE_NOT_HIT)
	{
		ctx->cache_update_ctx=web_cache_update_start(g_pangu_rt->cache, thread_id, session);
		if(ctx->cache_update_ctx==NULL)
		{		
			printf("cache update forbidden: %s\n", ctx->ref_session->req->req_spec.url);
		}
	}
	if(events & EV_HTTP_RESP_BODY_CONT && ctx->cache_update_ctx!=NULL)
	{
		web_cache_update(ctx->cache_update_ctx, body_frag, frag_size);
	}
	if(events & EV_HTTP_RESP_BODY_END && ctx->cache_update_ctx!=NULL)
	{
		web_cache_update_end(ctx->cache_update_ctx);
		ctx->cache_update_ctx=NULL;	
		//printf("cache update success: %s\n", ctx->ref_session->req->req_spec.url);
	}

}						

void pangu_on_http_begin(const struct tfe_stream * stream,
	const struct tfe_http_session * session, unsigned int thread_id, void ** pme)
{
	struct pangu_http_ctx * ctx = *(struct pangu_http_ctx **) pme;
	struct Maat_rule_t result[MAX_SCAN_RESULT];
	struct ipaddr sapp_addr;
	int hit_cnt = 0;
	assert(ctx == NULL);
	ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_SESSION]));
	ctx = pangu_http_ctx_new(thread_id);
	addr_tfe2sapp(stream->addr, &sapp_addr);
	hit_cnt = Maat_scan_proto_addr(g_pangu_rt->maat, g_pangu_rt->scan_table_id[PXY_CTRL_IP], &sapp_addr, 0,
		result, MAX_SCAN_RESULT, &(ctx->mid), (int) thread_id);

	if (hit_cnt > 0)
	{
		ctx->action = decide_ctrl_action(result, hit_cnt, &ctx->enforce_rules, &ctx->n_enforce);
	}
	if (ctx->action == PG_ACTION_WHITELIST)
	{
		tfe_http_session_detach(session);
	}

	*pme = ctx;
	return;
}

void pangu_on_http_end(const struct tfe_stream * stream,
	const struct tfe_http_session * session, unsigned int thread_id, void ** pme)
{
	struct pangu_http_ctx * ctx = *(struct pangu_http_ctx **) pme;
	int i=0, j=0,ret=0;
	if(ctx->action == PG_ACTION_REPLACE && ctx->rep_ctx->actually_replaced==0)
	{
		for(i=0; i< ctx->n_enforce; i++)
		{
			if(ctx->enforce_rules[i].action == PG_ACTION_REPLACE)
			{
				if(i+1 > ctx->n_enforce)
				{
					memmove(ctx->enforce_rules+i, ctx->enforce_rules+i+1, sizeof(struct Maat_rule_t));
				}
				j++;
			}
		}
		ctx->n_enforce-=j;
		if(ctx->n_enforce==0)
		{
			ctx->action = PG_ACTION_NONE;
			FREE(&(ctx->enforce_rules));
		}
	}
	struct pangu_log log_msg =  {.stream=stream, .http=session, .result=ctx->enforce_rules, .result_num=ctx->n_enforce};
	if (ctx->action != PG_ACTION_NONE&& !(ctx->action == PG_ACTION_REPLACE && ctx->n_enforce==1 && ctx->rep_ctx->actually_replaced==0))
	{
		ret=pangu_send_log(g_pangu_rt->send_logger, &log_msg);
		ATOMIC_ADD(&(g_pangu_rt->stat_val[STAT_LOG_NUM]), ret);
	}
	pangu_http_ctx_free(ctx);
	*pme = NULL;
	return;
}

void pangu_on_http_data(const struct tfe_stream * stream, const struct tfe_http_session * session,
	enum tfe_http_event events, const unsigned char * body_frag, size_t frag_size, unsigned int thread_id, void ** pme)
{
	struct pangu_http_ctx * ctx = *(struct pangu_http_ctx **) pme;
	
	if(events & EV_HTTP_REQ_HDR && ctx->resume_from_cache_query && ctx->cache_query_status == WEB_CACHE_HIT)
	{
		//resume from cache query.		
		assert(ctx->action==PG_ACTION_NONE);
		tfe_http_session_detach(session);
		return;
	}
	if(!ctx->resume_from_cache_query)
	{
		enforce_control_policy(stream, session, events, body_frag, frag_size,thread_id, ctx);
	}
	if(ctx->action != PG_ACTION_NONE)
	{
		return;
	}
	if(g_pangu_rt->cache_enabled)
	{
		if(events & EV_HTTP_REQ_HDR && !ctx->resume_from_cache_query)
		{
			cache_query(session, thread_id, ctx);
		}
		if(!tfe_http_in_request(events))
		{
			cache_update(session, events, body_frag, frag_size, thread_id, ctx);
		}		
	}	
	return;
}



struct tfe_plugin pangu_http_spec = {
	.symbol=NULL,
	.type = TFE_PLUGIN_TYPE_BUSINESS,
	.on_init = pangu_http_init,
	.on_deinit = NULL,
	.on_open = NULL,
	.on_data = NULL,
	.on_close = NULL,
	.on_session_begin=pangu_on_http_begin,
	.on_session_data=pangu_on_http_data,
	.on_session_end=pangu_on_http_end
};
TFE_PLUGIN_REGISTER(pangu_http, pangu_http_spec)

