/*
 * Author: Tracy Steven Brown
 * Arizona ITLabs, LLC - www.azitlabs.com
 * Desc. Example Apache-2.2x module framework
 */

/* Required headers */
#include “httpd.h”
#include “http_config.h”
#include “http_protocol.h”
#include “ap_config.h”

#include <mysql.h>
#include <json.h>
/* module headers */
// #include "mod_ajax.h" // not used

#define ec_cmp(var, err) \
{ \
	if((var) == (err)) { \
		ap_assert(var);  \
			return(var);  \
    } \
}

/* --------------------------------------- [@] --------------------------------------- */

/* data structures and global variables */
typedef struct user_session_tag 
{
	struct {
		struct user_session_tag *next;
		struct user_session_tag *prev;
	} link;
	
	int          index     ;  /* index number for this session ............ */
	const char  *session_id;  /* unique user id ........................... */
	const char  *ip_address;  /* client IP addr ........................... */
	apr_time_t   timestamp ;  /* last access stamp ........................ */
	apr_xml_doc *xml       ;  /* complete xHTML ........................... */
	
	apr_thread_mutex_t *session_lock ; /* mutual exclusion for session .... */
    apr_thread_cond_t  *session_avail; /* conditional exclusion for session */
} user_session_t;

typedef struct session_ring_tag 
{
	user_session_t *next;
	user_session_t *prev;
} session_ring_t;

typedef struct session_list_tag 
{
	int             avail_total; /* total in-memory sessions .............. */
	int             curr_total ; /* total current active sessions ......... */
	int             smax       ; /* soft max for in-memory sessions ....... */
	int             hmax       ; /* hard max for in-memory sessions ....... */
	apr_time_t      interval   ; /* time interval for session maintenance . */
	
	session_ring_t *avail_ring ; /* total sessions ring ................... */
	session_ring_t *curr_ring  ; /* current active sessions ............... */
} session_list_t;


typedef struct config_tag 
{ 	
	session_list_t *session_list; /* pointer to all sessions .............. */
	apr_reslist_t  *sql;          /* MySQL connection pool ................ */
	/* command design pattern via anonymous union ......................... */
	union
	 {
		 struct json_object *(*getXML)(struct json_object *obj);
		 int                 (*setXML)(apr_xml_doc *xml       );
	 };
} config_t;

/* --------------------------------------- [@] --------------------------------------- */

/* prototypes & forward declarations */

/* forward declaration of the module data structure */
module AP_MODULE_DECLARE_DATA ajax_module;

/* dirConfig() is where you would specify a per-directory configuration structure. 
 * A per-directory configuration structure defines what parameters represent 
 * what behavior per specified directory. 
 */
void *dirConfig(apr_pool_t *p, char *dir);

/* mDirConfig() references directory configurations. However, this positions purpose 
 * is to supersede its counterpart above when there is a conflict between the per-directory 
 * configuration data.
 */
void *mDirConfig(apr_pool_t *p, void *base_conf, void *new_conf);

/* mDirConfig() more global than the prior two positions in that its focus is on the  
 * host itself. Recall that the Apache server encompasses one or more virtual hosts. 
 * The per-host configuration structure allows you to specify parameters that enforce  
 * specific behavior for different virtual hosts.
 */
void *hostConfig(apr_pool_t *p, server_rec *s);

/* mHostConfig() doesn't necessarily solve conflicts but rather fills in missing configuration 
 * data that may have been left out for one or more virtual hosts. In other words, it fills 
 * in the blanks with default parameters.
 */
void *mHostConfig(apr_pool_t *p, void *base_conf, void *new_conf);

/* setParams() is an example function that a command structure
 * uses when encountering a configuration directive.
 */
const char *setParams(cmd_parms *params, void *cfg, const char *arg);

/* registerHooks() is the most important position as it describes the data structure 
 * responsible for controlling the behavior of Apache from startup to shutdown and all 
 * request-processing therein. Without this position, a module cannot request interest 
 * in any aspect of Apache. This is where you register your hooks and handlers.
 */
static void registerHooks(apr_pool_t *p);

/* application specific prototypes */

int ajax_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s);

apr_status_t mysql_construct(void **sql, void *params, apr_pool_t *pool);

apr_status_t reslist_destory(void *data);

apr_status_t mysql_destruct(void *sql, void *params, apr_pool_t *pool);

const char *post_decode(request_rec *r, const char *rbuf, apr_size_t len);

int ajax_fixups(request_rec *r);

int ajax_handler(request_rec *r);

/* --------------------------------------- [@] --------------------------------------- */


void *
dirConfig(apr_pool_t *p, char *dir)
{
	return;
}

void *
mDirConfig(apr_pool_t *p, void *base_conf, void *new_conf)
{
	return;
}

void *
hostConfig(apr_pool_t *p, server_rec *s)
{
	return;
}

void *
mHostConfig(apr_pool_t *p, void *base_conf, void *new_conf)
{
	return;
}

/* The command directive provides a facility for you to stipulate function 
 * names that Apache will execute upon receiving information from configuration 
 * directives. The name you give your functions inside the command directive 
 * becomes a function pointer within the command directive data structure. 
 */
static const command_rec modCmds[] =
{   
	/* ******************************** */
	/* values picked up from httpd.conf */
	/* see: http_config.h for MACRO def */
	/* ******************************** */
	
	// # define AP_INIT_TAKE1(directive, func, mconfig, where, help) \
    // { directive, func, mconfig, where, TAKE1, help }
	// AP_INIT_TAKE1("MySqlHostname", setParams, (void*)CmdHostname, RSRC_CONF, "The host where the database resides"),
	{NULL}
};

const char *
setParams(cmd_parms *params, void *cfg, const char *arg)
{
	// config_t is a module specific data structure you define if needed
	// config_t *config = (config_t*)ap_get_module_config(params->server->module_config, &ajax_module);
	
	switch ((long)params->info) 
	 {
		case CmdHostname:
			// an example using 3rd field of AP_INIT_TAKE1
			// config->MySqlHostname = (char*)apr_pstrmemdup(params->pool, arg, strlen(arg));
			break;
		
		default:
			// some sort of default if needed
	 }
	return;
}

int 
ajax_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) 
{
	config_t      *config ;
	const void    *data   ;
	apr_reslist_t *reslist;
	
	config = (config_t*)apr_pcalloc(pconf, sizeof(config_t));	
	config = ap_get_module_config(s->module_config, &ajax_module);
	
	/* create the resource list */
	ec_cmp(apr_reslist_create(&reslist, 20, 35, 40, INTERVAL, mysql_construct, 
							  mysql_destruct, config, pconf  ), APR_SUCCESS);
	
	data = &reslist;
	(void)apr_pool_cleanup_register(pconf, data, reslist_destory, apr_pool_cleanup_null);
	
	config->sql = reslist;
	
	return(OK);
}


apr_status_t 
mysql_construct(void **sql, void *params, apr_pool_t *pool)
{	
	config_t *config;
	
	config = (config_t*)apr_pcalloc(pool, sizeof(params));	
	config = (config_t*)params;
	
	MYSQL *mysql;
	mysql = mysql_init(NULL);
	
	mysql_real_connect((MYSQL*)mysql, HOSTNAME, USERNAME, PASSWORD, DATABASE, 0, NULL, FLAGS);
	
	if(mysql == '\0')
		return(APR_EGENERAL);
	
	*sql = mysql;
	return(APR_SUCCESS);
}


apr_status_t 
reslist_destory(void *data)
{
	apr_status_t status;
	status = apr_reslist_destroy((apr_reslist_t*)data);
	return(status);
}


apr_status_t 
mysql_destruct(void *sql, void *params, apr_pool_t *pool) 
{ 
	if(sql == '\0')
		return(APR_SUCCESS);
	
	mysql_close((MYSQL*)sql);
	return(APR_SUCCESS);
}

const char *
post_decode(request_rec *r, const char *rbuf, apr_size_t len)
{
 	int n;
 	apr_status_t status;
	
 	if(rbuf[0] == '\0')
 		return(rbuf);
 	
 	struct apr_hash_t *index = apr_hash_make(r->pool);
	
	apr_hash_set(index, (const void*)"%20", APR_HASH_KEY_STRING, (const void*)" ");
	apr_hash_set(index, (const void*)"%21", APR_HASH_KEY_STRING, (const void*)"!");
	apr_hash_set(index, (const void*)"%22", APR_HASH_KEY_STRING, (const void*)"\"");
	apr_hash_set(index, (const void*)"%23", APR_HASH_KEY_STRING, (const void*)"#");
	apr_hash_set(index, (const void*)"%24", APR_HASH_KEY_STRING, (const void*)"$");
	apr_hash_set(index, (const void*)"%25", APR_HASH_KEY_STRING, (const void*)"%");
	apr_hash_set(index, (const void*)"%26", APR_HASH_KEY_STRING, (const void*)"&");
	apr_hash_set(index, (const void*)"%27", APR_HASH_KEY_STRING, (const void*)"'");
	apr_hash_set(index, (const void*)"%28", APR_HASH_KEY_STRING, (const void*)"(");
	apr_hash_set(index, (const void*)"%29", APR_HASH_KEY_STRING, (const void*)")");
	apr_hash_set(index, (const void*)"%2A", APR_HASH_KEY_STRING, (const void*)"*");
	apr_hash_set(index, (const void*)"%2B", APR_HASH_KEY_STRING, (const void*)"+");
	apr_hash_set(index, (const void*)"%2C", APR_HASH_KEY_STRING, (const void*)",");
	apr_hash_set(index, (const void*)"%2D", APR_HASH_KEY_STRING, (const void*)"-");
	apr_hash_set(index, (const void*)"%2E", APR_HASH_KEY_STRING, (const void*)".");
	apr_hash_set(index, (const void*)"%2F", APR_HASH_KEY_STRING, (const void*)"/");
	apr_hash_set(index, (const void*)"%3A", APR_HASH_KEY_STRING, (const void*)":");
	apr_hash_set(index, (const void*)"%3B", APR_HASH_KEY_STRING, (const void*)";");
	apr_hash_set(index, (const void*)"%3C", APR_HASH_KEY_STRING, (const void*)"<");
	apr_hash_set(index, (const void*)"%3D", APR_HASH_KEY_STRING, (const void*)"=");
	apr_hash_set(index, (const void*)"%3E", APR_HASH_KEY_STRING, (const void*)">");
	apr_hash_set(index, (const void*)"%3F", APR_HASH_KEY_STRING, (const void*)"?");
	apr_hash_set(index, (const void*)"%40", APR_HASH_KEY_STRING, (const void*)"@");
	apr_hash_set(index, (const void*)"%5E", APR_HASH_KEY_STRING, (const void*)"^");
	apr_hash_set(index, (const void*)"%60", APR_HASH_KEY_STRING, (const void*)"_");
	apr_hash_set(index, (const void*)"%61", APR_HASH_KEY_STRING, (const void*)"`");
	apr_hash_set(index, (const void*)"%7B", APR_HASH_KEY_STRING, (const void*)"{");
	apr_hash_set(index, (const void*)"%7C", APR_HASH_KEY_STRING, (const void*)"|");
	apr_hash_set(index, (const void*)"%7D", APR_HASH_KEY_STRING, (const void*)"}");
	apr_hash_set(index, (const void*)"%7E", APR_HASH_KEY_STRING, (const void*)"~");
	apr_hash_set(index, (const void*)"%0D", APR_HASH_KEY_STRING, (const void*)"\r");
	apr_hash_set(index, (const void*)"%0A", APR_HASH_KEY_STRING, (const void*)"\n");
	
	ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "hash is built");
	
	char *nbuf = (char*)apr_pcalloc(r->pool, len);
	
	int k = 0;
	for(n = 0; n < len ; ++n && ++k)
	 {
		if(rbuf[n] == '%')
		 {
			char ibuf[] = { rbuf[n], rbuf[n+1], rbuf[n+2], '\0' };
			
			void *val;
			const void *key = (void*)&ibuf[0];
			if((val = apr_hash_get(index, key, APR_HASH_KEY_STRING)) != NULL)
			 {
				char *nstr = (char*)val;
				nbuf[k] = nstr[0];
				n += 2;
			 }
		 }
		else
		 {
			if(isgraph(rbuf[n]))
			 {
				if(rbuf[n] == '+')
					nbuf[k] = ' ';
				else
					nbuf[k] = rbuf[n];
			 }
		 }
	 }
	return(nbuf);
}

int 
ajax_fixups(request_rec *r)
{
	int             i              ;
	char           *token          ; 
	char           *token2         ;
	char           *session_id     ; 
	char           *ip_address     ;
	config_t       *config         ;
	user_session_t *session        ;
	const char     *o_path = r->uri;
    
	config     = (config_t*      )apr_pcalloc(r->pool, sizeof(config_t                 ));
	session    = (user_session_t*)apr_pcalloc(r->pool, sizeof(user_session_t           ));
	ip_address = (char*          )apr_pcalloc(r->pool, sizeof(r->connection->remote_ip ));
	
	ip_address = r->connection->remote_ip;
	config     = ap_get_module_config(r->server->module_config, &ajax_module);
	
	for(i = 0; *o_path && (token = ap_getword(r->pool, &o_path, '/')); i++)
	 {
		if(*token && strlen(token) < 1)
			break;
		
		if(token[0] != '\0')
			apr_table_set(r->notes, (char*)apr_psprintf(r->pool, "dir%i", i), 
						  (char*)apr_psprintf(r->pool, "%s", token ? token : "/"));
		
		/* custom processing of the directory string */
		/* URI = /(int)/(string)/(params) */
		
		switch(i)
		 {
			case 1:
				/* this is the root directory, no data - give it to Apache core */
				r->handler = "core_handler";
				break;
				
			case 2:
				/* an integer should be here */
				switch(atoi(token))
				 {
					/* int flags interpretation */
					case 'a':
						/* no processing, set handler */
						r->handler = "ajax_handler";
						break;
						
					case 'b':
						/* requesting data */
						token2 = ap_getword(r->pool, &o_path, '/');
						apr_table_set(r->notes, "requesting", (char*)apr_psprintf(r->pool, "%s", 
																				  token2 ? token2 : "(null)"));
						r->handler = "ajax_handler";
						break;
						
					case 'c':
						/* submitting data */
						token2 = ap_getword(r->pool, &o_path, '/');
						apr_table_set(r->notes, "submitting", (char*)apr_psprintf(r->pool, "%s", 
																				  token2 ? token2 : "(null)"));
						
						r->handler = "ajax_handler";
						break;
						
					default:
						return(OK);
				 }
				break;
				
			case 3:
				/* case 2 snatched up the command string, so this becomes params */
				apr_table_set(r->notes, "params", (char*)apr_psprintf(r->pool, "%s", token ? token : "(null)"));
				r->handler = "ajax_handler";
				break;
		 }
	 }
	
	if(strlen(apr_table_get(r->headers_in, "Cookies: ")) > 0)
		session_id = (char*)apr_table_get(r->headers_in, "Cookies: ");
	
	if(session_id[0] == '\0')
	 {
		session_id          = (char*)apr_table_get(r->subprocess_env, "UNIQUE_ID");
		session->session_id = (char*)apr_pstrmemdup(r->pool, session_id, strlen(session_id));
		session->ip_address = (char*)apr_pstrmemdup(r->pool, ip_address, strlen(ip_address));
		
		(void)push_session(config->session_list, session);
	 }
	
	return(OK);
}

int 
ajax_handler(request_rec *r)
{
	int                  test     ;
	char                *data     ;
	char                *val      ;
	config_t            *config   ;
	user_session_t      *session  ;
	struct json_object  *serverPkg;
	session_list_t      *session_list;
	
	config       = (config_t*           )apr_pcalloc(r->pool, sizeof(config_t       ));
	session      = (user_session_t*     )apr_pcalloc(r->pool, sizeof(user_session_t ));
	serverPkg    = (struct json_object* )apr_pcalloc(r->pool, sizeof(serverPkg      ));
	session_list = (session_list_t*     )apr_pcalloc(r->pool, sizeof(session_list_t ));
	
	data[0]    = '\0';
	config     = ap_get_module_config(r->server->module_config, &ajax_module);											
	
	session_list = config->session_list;
	
	for(session = APR_RING_FIRST(session_list->curr_ring); 
		!APR_RING_SENTINEL(session_list->curr_ring, user_session_tag, link); 
		session = APR_RING_NEXT(session, link))
	 {
		/* session processing here ... */
	 }
	
	/* check the method for POST */
	if(r->method_number == M_POST)
	 {
	 	int len = 0;
	 	
	 	if((test = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR)) != OK)
	 		return(test);
	 	
	 	/* prepare to receive payload */
		if(!ap_should_client_block(r))
			return(HTTP_NO_CONTENT);
		
		if(r->remaining > 0)
		 {
			len = (int)r->remaining;
			while(len > 0)
			 {
				char rbuf[len];
				
				/* receive the payload */
				len = ap_get_client_block(r, rbuf, len);
				
				if(data[0] == '\0')
					data = (char*)apr_pstrmemdup(r->pool, rbuf, len);
				else
					data = (char*)apr_pstrcat(r->pool, data, rbuf, NULL);
			 }
			
			const char *nbuf = post_decode(r, data, strlen(data));
			if(nbuf[0] == '\0')
				return(HTTP_INTERNAL_SERVER_ERROR);
			
			/* parse the decoded POST string storing kay/val in the notes table */
			while(*nbuf && (val = ap_getword(r->pool, &nbuf, '&')))
			 {
				const char *key     = ap_getword_nc(r->pool, &val, '=');
				const char *new_key = (char*)apr_pstrmemdup(r->pool, key, strlen(key));
				const char *new_val = (char*)apr_pstrmemdup(r->pool, val, strlen(val));
				
				if(strlen(new_key) > 0)
				 {			  
					if(strlen(new_val) > 0)
						apr_table_set(r->notes, new_key, new_val);
					else
						apr_table_set(r->notes, new_key, "(null)");
				 }
			 }
			
			const char *json_key = apr_psprintf(r->pool, "%s", apr_table_get(r->notes, "json") ? 
												apr_table_get(r->notes, "json") : "(null)");
			
			if((test = apr_strnatcmp(json_key, "(null)")) == 0)
				return(HTTP_NOT_IMPLEMENTED);
			
			char *json_key_nc = (char*)apr_pstrmemdup(r->pool, json_key, strlen(json_key));
			
			struct json_object *clientPkg = json_tokener_parse(json_key_nc);
			
			char *key, *skey;
			struct json_object *val;
			struct lh_entry *entry;
			
			for(entry = json_object_get_object(clientPkg)->head; entry != NULL; entry = entry->next)
			 { 
				if(entry)
				 { 
					key = (char*)entry->k; 
					val = (struct json_object*)entry->v;
					
					if((test = apr_strnatcmp(key, "json")) == 0)
					 {
						skey = apr_psprintf(r->pool, "%s", json_object_get_string(val) ?
											json_object_get_string(val) : "(null)");
						
						if((test = apr_strnatcmp(skey, "(null)")) == 0)
							return(HTTP_UNPROCESSABLE_ENTITY);
						
						/* instantiating the server-side controller */
						serverPkg = ajax_controller((void*)val);
					 }
				 }
			 }
		 }
	 }
	else
	 {
	 	/* Ajax requests should be POST requests for two-way data-exchange */
	 	return(DECLINED);
	 }
	
	ap_rputs(json_object_to_json_string(serverPkg), r);
	return(OK);
}

static void 
registerHooks(apr_pool_t *p)
{
	/* **************************** */
	/* configuration specific hooks */
	/* **************************** */
	// ap_hook_header_parser(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_pre_config(handler, NULL, NULL, APR_HOOK_MIDDLE);
	ap_hook_post_config(ajax_post_config, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_open_logs(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_child_init(handler, NULL, NULL, APR_HOOK_MIDDLE);
	ap_hook_handler(ajax_handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_quick_handler(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_optional_fn_retrieve(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_test_config(handler, NULL, NULL, APR_HOOK_MIDDLE);
	
	/* ************************* */
	/* connection specific hooks */
	/* ************************* */
	// ap_hook_create_connection(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_process_connection(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_pre_connection(handler, NULL, NULL, APR_HOOK_MIDDLE);
	
	/* ************************ */
	/* error log specific hooks */
	/* ************************ */
	// ap_hook_error_log(handler, NULL, NULL, APR_HOOK_MIDDLE);
	
	/* ************************************* */	
	/* multiprocessing module specific hooks */
	/* ************************************* */
#if AP_ENABLE_EXCEPTION_HOOK
	// ap_hook_fatal_exception(handler, NULL, NULL, APR_HOOK_MIDDLE);
#endif
	// ap_hook_pre_mpm(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_monitor(handler, NULL, NULL, APR_HOOK_MIDDLE);
 
	/* *********************** */
	/* protocol specific hooks */
	/* *********************** */
	// ap_hook_post_read_request(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_log_transaction(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_insert_error_filter(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_http_schema(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_default_port(handler, NULL, NULL, APR_HOOK_MIDDLE);
	
	/* ********************** */
	/* request specific hooks */
	/* ********************** */
	// ap_hook_translate_name(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_map_to_storage(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_check_user_id(handler, NULL, NULL, APR_HOOK_MIDDLE);
	ap_hook_fixups(ajax_fixups, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_type_checker(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_access_checker(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_auth_checker(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_insert_filter(handler, NULL, NULL, APR_HOOK_MIDDLE);
	// ap_hook_create_request(handler, NULL, NULL, APR_HOOK_MIDDLE);

	/* ******************* */
	/* core specific hooks */
	/* ******************* */
	// ap_hook_get_mgmt_items(handler, NULL, NULL, APR_HOOK_MIDDLE);

	/* ******************************* */
	/* operating system specific hooks */
	/* ******************************* */
	// ap_hook_get_suexec_identity(handler, NULL, NULL, APR_HOOK_MIDDLE);
}

/* --------------------------------------- [@] --------------------------------------- */

/* framework data structure */
module AP_MODULE_DECLARE_DATA ajax_module = {
	STANDARD20_MODULE_STUFF, /* an Apache helper macro .......... */
	dirConfig,			     /* directory specific configurations */
	mDirConfig,			     /* merge directory configurations .. */
	hostConfig,			     /* host specific configuration ..... */
	mHostConfig,		     /* merge host configurations ....... */
	modCmds,			     /* configuration directive commands  */
	registerHooks		     /* module specific hooks/handlers .. */
};


