/**************************************************************************
 *
 * JSONUTILS.C -  Utilities for Nagios CGIs for returning JSON-formatted 
 *                object data
 *
 * Copyright (c) 2013 Nagios Enterprises, LLC
 * Last Modified: 04-13-2013
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *************************************************************************/

#include "../include/config.h"
#include "../include/common.h"
#include "../include/objects.h"
#include "../include/statusdata.h"
#include "../include/comments.h"

#include "../include/cgiutils.h"
#include "../include/getcgi.h"
#include "../include/cgiauth.h"
#include "../include/jsonutils.h"

/* Multiplier to increment the buffer in json_escape_string() to avoid frequent
	repeated reallocations */
#define BUF_REALLOC_MULTIPLIER 16

const char *result_types[] = {
	"Success",
	"Unable to Allocate Memory",
	"Unable to Open File for Reading",
	"Option Invalid",
	"Option Missing",
	"Option Value Missing",
	"Option Value Invalid",
	"Option Ignored"
	};

const string_value_mapping svm_format_options[] = {
	{ "whitespace", JSON_FORMAT_WHITESPACE, 
		"Pad with whitespace to increase readability" },
	{ "enumerate", JSON_FORMAT_ENUMERATE, 
		"Use textual representations of enumerated values rather than "
		"raw numeric values" },
	{ "bitmask", JSON_FORMAT_BITMASK, 
		"Use textual representations of bitmask values rather than "
		"raw numeric values" },
	{ "duration", JSON_FORMAT_DURATION, 
		"Use textual representations (xd xh xm xs) of duration values rather "
		"than raw number of seconds" },
#if 0
	{ "datetime", JSON_FORMAT_DATETIME, 
		"Format date/time values according to the supplied strftime format "
		"or '%%Y-%%m-%%d %%H:%%M:%%S' if no format specified" },
	{ "date", JSON_FORMAT_DATE, 
		"Format dates according to the supplied strftime format or "
		"default Javascript format (number of ms since the beginning of the "
		"Unix epoch) if no format specified" },
	{ "time", JSON_FORMAT_TIME, 
		"Format times according the supplied strftime format or "
		"'%%H:%%M:%%S' in for format specified" },
#endif
	{ NULL, -1, NULL },
	};

const string_value_mapping query_statuses[] = {
	{ "alpha", QUERY_STATUS_ALPHA, "Alpha" },
	{ "beta", QUERY_STATUS_BETA, "Beta" },
	{ "released", QUERY_STATUS_RELEASED, "Released" },
	{ "deprecated", QUERY_STATUS_DEPRECATED, "Deprecated" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_host_statuses[] = {
#ifdef JSON_NAGIOS_4X
	{ "up", SD_HOST_UP, "HOST_UP" },
	{ "down", SD_HOST_DOWN, "HOST_DOWN" },
	{ "unreachable", SD_HOST_UNREACHABLE, "HOST_UNREACHABLE" },
#else
	{ "up", HOST_UP, "HOST_UP" },
	{ "down", HOST_DOWN, "HOST_DOWN" },
	{ "unreachable", HOST_UNREACHABLE, "HOST_UNREACHABLE" },
#endif
	{ "pending", HOST_PENDING, "HOST_PENDING" },
	{ NULL, -1, NULL },
	};

/* Hard-coded values used because the HOST_UP/DOWN/UNREACHABLE
	macros are host status (and include PENDING), not host state */
const string_value_mapping svm_host_states[] = {
	{ "up", 0, "HOST_UP" },
	{ "down", 1, "HOST_DOWN" },
	{ "unreachable", 2, "HOST_UNREACHABLE" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_service_statuses[] = {
	{ "ok", SERVICE_OK, "SERVICE_OK" },
	{ "warning", SERVICE_WARNING, "SERVICE_WARNING" },
	{ "critical", SERVICE_CRITICAL, "SERVICE_CRITICAL" },
	{ "unknown", SERVICE_UNKNOWN, "SERVICE_UNKNOWN" },
	{ "pending", SERVICE_PENDING, "SERVICE_PENDING" },
	{ NULL, -1, NULL },
	};

/* Hard-coded values used because the SERVICE_OK/WARNING/CRITICAL/UNKNOWN
	macros are service status (and include PENDING), not service state */
const string_value_mapping svm_service_states[] = {
	{ "ok", 0, "SERVICE_OK" },
	{ "warning", 1, "SERVICE_WARNING" },
	{ "critical", 2, "SERVICE_CRITICAL" },
	{ "unknown", 3, "SERVICE_UNKNOWN" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_check_options[] = {
	{ "force_execution", CHECK_OPTION_FORCE_EXECUTION, "FORCE_EXECUTION" },
	{ "freshness_check", CHECK_OPTION_FRESHNESS_CHECK, "FRESHNESS_CHECK" },
	{ "orphan_check", CHECK_OPTION_ORPHAN_CHECK, "ORPHAN_CHECK" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_host_check_types[] = {
	{ "active", HOST_CHECK_ACTIVE, "ACTIVE" },
	{ "passive", HOST_CHECK_PASSIVE, "PASSIVE" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_service_check_types[] = {
	{ "active", SERVICE_CHECK_ACTIVE, "ACTIVE" },
	{ "passive", SERVICE_CHECK_PASSIVE, "PASSIVE" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_state_types[] = {
	{ "soft", SOFT_STATE, "SOFT" },
	{ "hard", HARD_STATE, "HARD" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_acknowledgement_types[] = {
	{ "none", ACKNOWLEDGEMENT_NONE, "NONE" },
	{ "normal", ACKNOWLEDGEMENT_NORMAL, "NORMAL" },
	{ "sticky", ACKNOWLEDGEMENT_STICKY, "STICKY" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_comment_types[] = {
	{ "host", HOST_COMMENT, "Host Comment" },
	{ "service", SERVICE_COMMENT, "Service Comment" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_comment_entry_types[] = {
	{ "user", USER_COMMENT, "User Comment" },
	{ "downtime", DOWNTIME_COMMENT, "Downtime Comment" },
	{ "flapping", FLAPPING_COMMENT, "Flapping Comment" },
	{ "acknowledgement", ACKNOWLEDGEMENT_COMMENT, "Acknowledgement Comment" },
	{ NULL, -1, NULL },
	};

const string_value_mapping svm_downtime_types[] = {
	{ "service", SERVICE_DOWNTIME, "Service Downtime" },
	{ "host", HOST_DOWNTIME, "Host Downtime" },
	{ "any", ANY_DOWNTIME, "Any Downtime" },
	{ NULL, -1, NULL },
	};

#ifdef JSON_NAGIOS_4X
const string_value_mapping svm_option_types[] = {
	{ "up", OPT_UP, "Up" },
	{ "down", OPT_DOWN, "Down" },
	{ "unreachable", OPT_UNREACHABLE, "Unreachable" },
	{ "ok", OPT_OK, "OK" },
	{ "unknown", OPT_UNKNOWN, "Unknown" },
	{ "warning", OPT_WARNING, "Warning" },
	{ "critical", OPT_CRITICAL, "Critical" },
	{ "recovery", OPT_RECOVERY, "Recovery" },
	{ "pending", OPT_PENDING, "Pending" },
	{ "flapping", OPT_FLAPPING, "Flapping" },
	{ "downtime", OPT_DOWNTIME, "Downtime" },
	{ NULL, -1, NULL },
	};
#endif

const string_value_mapping parent_host_extras[] = {
	{ "none", 0, "Hosts that are directly reachable by the Nagios Core host" },
	{ NULL, -1, NULL },
	};

const string_value_mapping child_host_extras[] = {
	{ "none", 0, "Hosts that have no child hosts" },
	{ NULL, -1, NULL },
	};

const string_value_mapping parent_service_extras[] = {
	{ "none", 0, "Services that have no parent services" },
	{ NULL, -1, NULL },
	};

const string_value_mapping child_service_extras[] = {
	{ "none", 0, "Services that have no child services" },
	{ NULL, -1, NULL },
	};

const char *dayofweek[7] = { "Sunday", "Monday", "Tuesday", "Wednesday",
		"Thursday", "Friday", "Saturday" };
const char *month[12] = { "January", "February", "March", "April", "May",
		"June", "July", "August", "September", "October", "November",
		"December" };

static const json_escape_pair string_escape_pairs[] = {
	{ L"\\", L"\\\\" },
	{ L"\x01", L"\\u0001" },
	{ L"\x02", L"\\u0002" },
	{ L"\x03", L"\\u0003" },
	{ L"\x04", L"\\u0004" },
	{ L"\x05", L"\\u0004" },
	{ L"\x06", L"\\u0006" },
	{ L"\a", L"\\a" },
	{ L"\b", L"\\b" },
	{ L"\t", L"\\t" },
	{ L"\n", L"\\n" },
	{ L"\v", L"\\v" },
	{ L"\f", L"\\f" },
	{ L"\r", L"\\r" },
	{ L"\x0e", L"\\u000e" },
	{ L"\x0f", L"\\u000f" },
	{ L"\x10", L"\\u0010" },
	{ L"\x11", L"\\u0011" },
	{ L"\x12", L"\\u0012" },
	{ L"\x13", L"\\u0013" },
	{ L"\x14", L"\\u0014" },
	{ L"\x15", L"\\u0015" },
	{ L"\x16", L"\\u0016" },
	{ L"\x17", L"\\u0017" },
	{ L"\x18", L"\\u0018" },
	{ L"\x19", L"\\u0019" },
	{ L"\x1a", L"\\u001a" },
	{ L"\x1b", L"\\u001b" },
	{ L"\x1c", L"\\u001c" },
	{ L"\x1d", L"\\u001d" },
	{ L"\x1e", L"\\u001e" },
	{ L"\x1f", L"\\u001f" },
	{ L"\"", L"\\\"" },
};

static const json_escape string_escapes = {
	(sizeof(string_escape_pairs) / sizeof(string_escape_pairs[0])),
	string_escape_pairs
};

const json_escape_pair percent_escape_pairs[] = {
	{ L"%", L"%%" },
};

const json_escape percent_escapes = {
	(sizeof(percent_escape_pairs) / sizeof(percent_escape_pairs[0])),
	percent_escape_pairs
};

extern char main_config_file[MAX_FILENAME_LENGTH];
extern time_t program_start;

static json_object_member * json_object_add_member(json_object *);

json_object *json_new_object(void) {
	json_object *new;
	new = calloc(1, sizeof(json_object));
	return new;
	}

void json_free_object(json_object *obj, int free_children) {

	int x;
	json_object_member **mpp;

	if(1 == free_children) {
		for(x = 0, mpp = obj->members; x < obj->member_count; x++, mpp++) {
			json_free_member(*mpp, free_children);
			}
		}
	free(obj->members);
	free(obj);
	}

json_array *json_new_array(void) {
	return (json_array *)json_new_object();
	}

void json_free_member(json_object_member *mp, int free_children) {

	if(NULL != mp->key) free(mp->key);

	switch(mp->type) {
	case JSON_TYPE_OBJECT:
	case JSON_TYPE_ARRAY:
		if(NULL != mp->value.object) {
			json_free_object(mp->value.object, free_children);
			}
		break;
	case JSON_TYPE_STRING:
		if(NULL != mp->value.string) {
			free(mp->value.string);
			}
		break;
	case JSON_TYPE_INTEGER:
	case JSON_TYPE_REAL:
	case JSON_TYPE_TIME_T:
	case JSON_TYPE_BOOLEAN:
		break;
	default:
		break;
		}

	free(mp);
	}

/* Adds a member to a JSON object and returns a pointer to the new member.
	Returns NULL on failure. */
static json_object_member * json_object_add_member(json_object *obj) {

	if(0 == obj->member_count) {
		obj->members = calloc(1, sizeof(json_object_member *)); 
		if(NULL == obj->members) {
			obj->member_count = 0;
			return NULL;
			}
		}
	else {
		obj->members = realloc(obj->members, 
				((obj->member_count + 1) * sizeof(json_object_member *)));
		if(NULL == obj->members) {
			obj->member_count = 0;
			return NULL;
			}
		}
	obj->members[ obj->member_count] = calloc(1, sizeof(json_object_member));
	if(NULL == obj->members[ obj->member_count]) {
		return NULL;
		}
	obj->member_count++;

	return obj->members[ obj->member_count - 1];
	}

void json_object_append_object(json_object *obj, char *key, json_object *value) {
	json_object_member *mp;

	if(NULL == obj) return;
	if(NULL == value) return;

	if((mp = json_object_add_member(obj)) == NULL) {
		return;
		}
	mp->type = JSON_TYPE_OBJECT;
	if(NULL != key) {
		mp->key = strdup(key);
		if(NULL == mp->key) {
			obj->member_count--;
			return;
			}
		}
	mp->value.object = value;
	}

void json_array_append_object(json_object *obj, json_object *value) {
	json_object_append_object(obj, NULL, value);
	}

void json_object_append_array(json_object *obj, char *key, json_array *value) {
	json_object_member *mp;

	if(NULL == obj) return;
	if(NULL == value) return;

	if((mp = json_object_add_member(obj)) == NULL) {
		return;
		}
	mp->type = JSON_TYPE_ARRAY;
	if(NULL != key) {
		mp->key = strdup(key);
		if(NULL == mp->key) {
			obj->member_count--;
			return;
			}
		}
	mp->value.object = value;
	}

void json_array_append_array(json_array *obj, json_array *value) {
	json_object_append_array((json_object *)obj, NULL, value);
	}

void json_object_append_integer(json_object *obj, char *key, int value) {
	json_object_member *mp;

	if(NULL == obj) return;

	if((mp = json_object_add_member(obj)) == NULL) {
		return;
		}
	mp->type = JSON_TYPE_INTEGER;
	if(NULL != key) {
		mp->key = strdup(key);
		if(NULL == mp->key) {
			obj->member_count--;
			return;
			}
		}
	mp->value.integer = value;
	}

void json_array_append_integer(json_object *obj, int value) {
	json_object_append_integer(obj, NULL, value);
	}

void json_object_append_real(json_object *obj, char *key, double value) {
	json_object_member *mp;

	if(NULL == obj) return;

	if((mp = json_object_add_member(obj)) == NULL) {
		return;
		}
	mp->type = JSON_TYPE_REAL;
	if(NULL != key) {
		mp->key = strdup(key);
		if(NULL == mp->key) {
			obj->member_count--;
			return;
			}
		}
	mp->value.real = value;
	}

void json_array_append_real(json_array *obj, double value) {
	json_object_append_real(obj, NULL, value);
	}

void json_object_append_time(json_object *obj, char *key, unsigned long value) {

	unsigned hours;
	unsigned minutes;
	unsigned seconds;

	hours = (unsigned)(value / 3600);
	value -= hours * 3600;
	minutes = (unsigned)(value / 60);
	value -= minutes * 60;
	seconds = value;

	json_object_append_string(obj, key, NULL, "%02u:%02u:%02u", hours, minutes,
			seconds);
	}

void json_array_append_time(json_array *obj, unsigned long value) {
	json_object_append_time(obj, NULL, value);
	}

void json_object_append_time_t(json_object *obj, char *key, time_t value) {
	json_object_member *mp;

	if(NULL == obj) return;

	if((mp = json_object_add_member(obj)) == NULL) {
		return;
		}
	mp->type = JSON_TYPE_TIME_T;
	if(NULL != key) {
		mp->key = strdup(key);
		if(NULL == mp->key) {
			obj->member_count--;
			return;
			}
		}
	mp->value.time = value;
	}

void json_set_time_t(json_object_member *mp, time_t value) {
	if(NULL == mp) return;
	mp->value.time = value;
	}

void json_object_append_string(json_object *obj, char *key,
		const json_escape *format_escapes, char *format, ...) {
	json_object_member *mp;
	va_list a_list;
	int		result;
	char	*escaped_format;
	char	*buf;

	if(NULL == obj) return;

	if((mp = json_object_add_member(obj)) == NULL) {
		return;
		}
	mp->type = JSON_TYPE_STRING;
	if(NULL != key) {
		mp->key = strdup(key);
		if(NULL == mp->key) {
			obj->member_count--;
			return;
			}
		}
	if((NULL != format_escapes) && (NULL != format)) {
		escaped_format = json_escape_string(format, format_escapes);
		}
	else {
		escaped_format = format;
		}
	if(NULL != escaped_format) {
		va_start(a_list, format);
		result = vasprintf(&buf, escaped_format, a_list);
		va_end(a_list);
		if(result >= 0) {
			mp->value.string = buf;
			}
		}
	if((NULL != format_escapes) && (NULL != escaped_format)) {
		/* free only if format_escapes were passed and the escaping succeeded */
		free(escaped_format);
		}
	}

void json_array_append_string(json_object *obj,
		const json_escape *format_escapes, char *format, ...) {

	va_list a_list;
	int		result;
	char	*buf;

	va_start( a_list, format);
	result = vasprintf(&buf, format, a_list);
	va_end( a_list);
	if(result >= 0) {
		json_object_append_string(obj, NULL, format_escapes, buf);
		}
	}

void json_object_append_boolean(json_object *obj, char *key, int value) {
	json_object_member *mp;

	if(NULL == obj) return;

	if((mp = json_object_add_member(obj)) == NULL) {
		return;
		}
	mp->type = JSON_TYPE_BOOLEAN;
	if(NULL != key) {
		mp->key = strdup(key);
		if(NULL == mp->key) {
			obj->member_count--;
			return;
			}
		}
	mp->value.boolean = value;
	}

void json_array_append_boolean(json_object *obj, int value) {
	json_object_append_boolean(obj, NULL, value);
	}

void json_object_append_duration(json_object *obj, char *key, 
		unsigned long value) {
	json_object_member *mp;

	if(NULL == obj) return;

	if((mp = json_object_add_member(obj)) == NULL) {
		return;
		}
	mp->type = JSON_TYPE_DURATION;
	if(NULL != key) {
		mp->key = strdup(key);
		if(NULL == mp->key) {
			obj->member_count--;
			return;
			}
		}
	mp->value.unsigned_integer = value;
	}

void json_array_append_duration(json_object *obj, unsigned long value) {
	json_object_append_duration(obj, NULL, value);
	}

/*
	Fetch an object member based on the path. The path is a dot-separated
	list of nodes. Nodes may be either a key or a zero-based array index.

	For example to return the query_time key in the result object, the
	path would be "result.query_time". To find the 2nd host host in
	the list of hosts for a hostlist query, the path would be
	"data.hostlist.1"
*/

json_object_member *json_get_object_member(json_object *root, char *path) {

	char *dot;
	char node[1024];
	int x;
	json_object_member **mpp;

	/* Parse the path to get the first node */
	dot = strchr(path, '.');
	if(NULL == dot) {	/* single node path */
		strcpy(node, path);
		}
	else {
		strncpy(node, path, (dot - path));
		node[dot - path] = '\0';
		}

	/* Loop over the members of the passed root looking for the node name */
	for(x = 0, mpp = root->members; x < root->member_count; x++, mpp++) {
		if(!strcmp((*mpp)->key, node)) {
			if(NULL == dot) { /* return this node */
				return *mpp;
				}
			else {
				switch((*mpp)->type) {
				case JSON_TYPE_OBJECT:
					return json_get_object_member((*mpp)->value.object, dot + 1);
					break;
				case JSON_TYPE_ARRAY:
					return json_get_array_member((*mpp)->value.object, dot + 1);
					break;
				default:
					/* It should never happen that we want the child of a
						childless node */
					return NULL;
					break;
					}
				}
			}
		}

	return NULL;
	}

json_object_member *json_get_array_member(json_object *root, char *path) {

	char *dot;
	char node[1024];
	int index;
	json_object_member *mp;

	/* Parse the path to get the first node */
	dot = strchr(path, '.');
	if(NULL == dot) {	/* single node path */
		strcpy(node, path);
		}
	else {
		strncpy(node, path, (dot - path));
		node[dot - path] = '\0';
		}
	index = (int)strtol(node, NULL, 10);

	/* Verify that we have a reasonable index */
	if(index < 0 || index >= root->member_count) {
		return NULL;
		}

	/* Find the requested member and deal with it appropriately */
	mp = root->members[ index];
	if(NULL == dot) { /* return this node */
		return mp;
		}
	else {
		switch(mp->type) {
		case JSON_TYPE_OBJECT:
			return json_get_object_member(mp->value.object, dot + 1);
			break;
		case JSON_TYPE_ARRAY:
			return json_get_array_member(mp->value.object, dot + 1);
			break;
		default:
			/* It should never happen that we want the child of a
				childless node */
			return NULL;
			break;
			}
		}

	return NULL;
	}

void json_object_print(json_object *obj, int padding, int whitespace,
		char *strftime_format, unsigned format_options) {
	int x;
	json_object_member **mpp;

	//indentf(padding, whitespace, "{%s", (whitespace ? "\n" : ""));
	printf( "{%s", (whitespace ? "\n" : ""));
	padding++;
	for(x = 0, mpp = obj->members; x < obj->member_count; x++, mpp++) {
		json_member_print(*mpp, padding, whitespace, strftime_format, 
				format_options);
		if(x != obj->member_count - 1) printf(",");
		if(whitespace) printf("\n");
		}
	padding--;
	indentf(padding, whitespace, "}");
}

void json_array_print(json_array *obj, int padding, int whitespace,
		char *strftime_format, unsigned format_options) {
	int x;
	json_object_member **mpp;

	printf( "[%s", (whitespace ? "\n" : ""));
	padding++;
	for(x = 0, mpp = obj->members; x < obj->member_count; x++, mpp++) {
		json_member_print(*mpp, padding, whitespace, strftime_format, 
				format_options);
		if(x != obj->member_count - 1) printf(",");
		if(whitespace) printf("\n");
		}
	padding--;
	indentf(padding, whitespace, "]");
	}

void json_member_print(json_object_member *mp, int padding, int whitespace, 
		char *strftime_format, unsigned format_options) {

	char *buf = NULL;

	switch(mp->type) {
	case JSON_TYPE_OBJECT:
		if(NULL != mp->key) {
			buf = json_escape_string(mp->key, &string_escapes);
			indentf(padding, whitespace, "\"%s\": ", buf);
			if(NULL != buf) free(buf);
			}
		else {
			indentf(padding, whitespace, "");
			}
		json_object_print(mp->value.object, padding, whitespace, 
				strftime_format, format_options);
		break;
	case JSON_TYPE_ARRAY:
		if(NULL != mp->key) {
			buf = json_escape_string(mp->key, &string_escapes);
			indentf(padding, whitespace, "\"%s\": ", buf);
			if(NULL != buf) free(buf);
			}
		else {
			indentf(padding, whitespace, "");
			}
		json_array_print(mp->value.object, padding, whitespace, strftime_format,
				format_options);
		break;
	case JSON_TYPE_INTEGER:
		json_int(padding, whitespace, mp->key, mp->value.integer);
		break;
	case JSON_TYPE_REAL:
		json_float(padding, whitespace, mp->key, mp->value.real);
		break;
	case JSON_TYPE_TIME_T:
		json_time_t(padding, whitespace, mp->key, mp->value.time, 
				strftime_format);
		break;
	case JSON_TYPE_STRING:
		json_string(padding, whitespace, mp->key, mp->value.string);
		break;
	case JSON_TYPE_BOOLEAN:
		json_boolean(padding, whitespace, mp->key, mp->value.boolean);
		break;
	case JSON_TYPE_DURATION:
		json_duration(padding, whitespace, mp->key, mp->value.unsigned_integer,
				format_options & JSON_FORMAT_DURATION);
		break;
	default:
		break;
		}
	}

void indentf(int padding, int whitespace, char *format, ...) {
	va_list a_list;
	int padvar;

	if( whitespace > 0) {
		for(padvar = 0; padvar < padding; padvar++) printf( "  ");
		}
	va_start( a_list, format);
	vprintf(format, a_list);
	va_end( a_list);
	}

json_object * json_result(time_t query_time, char *cgi, char *query,
		int query_status, time_t last_data_update, authdata *authinfo, int type,
		char *message, ...) {

	json_object *json_result;
	va_list a_list;
	char	*buf;


	json_result = json_new_object();
	json_object_append_time_t(json_result, "query_time", query_time);
	json_object_append_string(json_result, "cgi", &percent_escapes, cgi);
	if(NULL != authinfo) {
		json_object_append_string(json_result, "user", &percent_escapes,
				authinfo->username);
		}
	if(NULL != query) {
		json_object_append_string(json_result, "query", &percent_escapes,
				query);
		json_object_append_string(json_result, "query_status", &percent_escapes,
				svm_get_string_from_value(query_status, query_statuses));
		}
	json_object_append_time_t(json_result, "program_start", program_start);
	if(last_data_update != (time_t)-1) {
		json_object_append_time_t(json_result, "last_data_update",
				last_data_update);
		}
	json_object_append_integer(json_result, "type_code", type);
	json_object_append_string(json_result, "type_text", &percent_escapes,
			(char *)result_types[ type]);
	va_start( a_list, message);
	if(vasprintf(&buf, message, a_list) == -1) {
		buf = NULL;
		}
	va_end( a_list);
	json_object_append_string(json_result, "message", &percent_escapes, buf);
	if(NULL != buf) free(buf);

	return json_result;
}

json_object *json_help(option_help *help) {

	json_object *json_data = json_new_object();
	json_object *json_options = json_new_object();
	json_object *json_option;
	json_array *json_required;
	json_array *json_optional;
	json_object *json_validvalues;
	json_object *json_validvalue;
	int x;
	char **	stpp;
	string_value_mapping *svmp;

	while(NULL != help->name) {
		json_option = json_new_object();
		json_object_append_string(json_option, "label", &percent_escapes,
				(char *)help->label);
		json_object_append_string(json_option, "type", &percent_escapes,
				(char *)help->type);

		json_required = json_new_array();
		for(x = 0, stpp = (char **)help->required; 
				(( x < sizeof( help->required) / 
				sizeof( help->required[ 0])) && ( NULL != *stpp)); 
				x++, stpp++) {
			json_array_append_string(json_required, &percent_escapes, *stpp);
			}
		json_object_append_array(json_option, "required",
				json_required);

		json_optional = json_new_array();
		for(x = 0, stpp = (char **)help->optional; 
				(( x < sizeof( help->optional) / 
				sizeof( help->optional[ 0])) && ( NULL != *stpp)); 
				x++, stpp++) {
			json_array_append_string(json_optional, &percent_escapes, *stpp);
			}
		json_object_append_array(json_option, "optional",
				json_optional);

		json_object_append_string(json_option, "depends_on", 
				&percent_escapes, (char *)help->depends_on);
		json_object_append_string(json_option, "description", 
				&percent_escapes, (char *)help->description);
		if( NULL != help->valid_values) {
			json_validvalues = json_new_object();
			for(svmp = (string_value_mapping *)help->valid_values; 
					NULL != svmp->string; svmp++) {
				if( NULL != svmp->description) {
					json_validvalue = json_new_object();
					json_object_append_string(json_validvalue, "description", 
							&percent_escapes, svmp->description);
					json_object_append_object(json_validvalues, svmp->string, 
							json_validvalue);
					}
				else {
					json_array_append_string(json_validvalues, &percent_escapes,
							svmp->string);
					}
				}
			json_object_append_object(json_option, "valid_values", 
					json_validvalues);
			}
		json_object_append_object(json_options, (char *)help->name, json_option);
		help++;
		}

	json_object_append_object(json_data, "options", json_options);

	return json_data;
	}

int passes_start_and_count_limits(int start, int max, int current, int counted) {

	int result = FALSE;

	if(start > 0) {
		/* The user requested we start at a specific index */
		if(current >= start) {
			if(max > 0) {
				/* The user requested a limit on the number of items returned */
				if(counted < max) {
					result = TRUE;
					}
				}
			else {
				/* The user did not request a limit on the number of items 
					returned */
				result = TRUE;
				}
			}
		}
	else {
		/* The user did not request we start at a specific index */
		if(max > 0) {
			/* The user requested a limit on the number of items returned */
			if(counted < max) {
				result = TRUE;
				}
			}
		else {
			/* The user did not request a limit on the number of items 
					returned */
			result = TRUE;
			}
		}
	return result;
	}

void json_string(int padding, int whitespace, char *key, char *value) {

	char *keybuf = NULL;
	char *valbuf = NULL;

	valbuf = json_escape_string(value, &string_escapes);

	if( NULL == key) {
		indentf(padding, whitespace, "\"%s\"",
				(( NULL == valbuf) ? "" : valbuf));
		}
	else {
		keybuf = json_escape_string(key, &string_escapes);
		indentf(padding, whitespace, "\"%s\":%s\"%s\"", keybuf, 
				(( whitespace> 0) ? " " : ""),
				(( NULL == valbuf) ? "" : valbuf));
		}
	if(NULL != keybuf) free(keybuf);
	if(NULL != valbuf) free(valbuf);
	}

void json_boolean(int padding, int whitespace, char *key, int value) {

	char *keybuf = NULL;

	if( NULL == key) {
		indentf(padding, whitespace, "%s", 
				(( 0 == value) ? "false" : "true"));
		}
	else {
		keybuf = json_escape_string(key, &string_escapes);
		indentf(padding, whitespace, "\"%s\":%s%s", keybuf, 
				(( whitespace > 0) ? " " : ""),
				(( 0 == value) ? "false" : "true"));
		}
	if(NULL != keybuf) free(keybuf);
	}

void json_int(int padding, int whitespace, char *key, int value) {

	char *keybuf = NULL;

	if( NULL == key) {
		indentf(padding, whitespace, "%d", value); 
		}
	else {
		keybuf = json_escape_string(key, &string_escapes);
		indentf(padding, whitespace, "\"%s\":%s%d", keybuf, 
				(( whitespace > 0) ? " " : ""), value); 
		}
	if(NULL != keybuf) free(keybuf);
	}

void json_unsigned(int padding, int whitespace, char *key, 
		unsigned long long value) {

	char *keybuf = NULL;

	if( NULL == key) {
		indentf(padding, whitespace, "%llu", value);
		}
	else {
		keybuf = json_escape_string(key, &string_escapes);
		indentf(padding, whitespace, "\"%s\":%s%llu", keybuf, 
				(( whitespace > 0) ? " " : ""), value);
		}
	if(NULL != keybuf) free(keybuf);
	}

void json_float(int padding, int whitespace, char *key, double value) {

	char *keybuf = NULL;

	if( NULL == key) {
		indentf(padding, whitespace, "%.2f", value);
		}
	else {
		keybuf = json_escape_string(key, &string_escapes);
		indentf(padding, whitespace, "\"%s\":%s%.2f", keybuf, 
				(( whitespace > 0) ? " " : ""), value);
		}
	if(NULL != keybuf) free(keybuf);
	}

void json_time(int padding, int whitespace, char *key, unsigned long value) {

	char *keybuf = NULL;
	unsigned hours;
	unsigned minutes;
	unsigned seconds;

	hours = (unsigned)(value / 3600);
	value -= hours * 3600;
	minutes = (unsigned)(value / 60);
	value -= minutes * 60;
	seconds = value;

	if( NULL == key) {
		indentf(padding, whitespace, "\"%02u:%02u:%02u\"", hours, minutes,
				seconds);
		}
	else {
		keybuf = json_escape_string(key, &string_escapes);
		indentf(padding, whitespace, "\"%s\":%s\"%02u:%02u:%02u\"", keybuf, 
				(( whitespace > 0) ? " " : ""), hours, minutes,
				seconds);
		}
	if(NULL != keybuf) free(keybuf);
	}

void json_time_t(int padding, int whitespace, char *key, time_t value, 
		char *format) {

	char		*keybuf = NULL;
	char		buf[1024];
	struct tm	*tmp_tm;

	if(NULL == format) {
		snprintf(buf, sizeof(buf)-1, "%llu%s", (unsigned long long)value, 
				((unsigned long long)value > 0 ? "000" : ""));
		}
	else {
		tmp_tm = localtime(&value);
		buf[ 0] = '"';
		strftime(buf+1, sizeof(buf)-3, format, tmp_tm);
		strcat(buf, "\"");
		}

	if(NULL == key) {
		indentf(padding, whitespace, "%s", buf);
		}
	else {
		keybuf = json_escape_string(key, &string_escapes);
		indentf(padding, whitespace, "\"%s\":%s%s", keybuf, 
				(( whitespace > 0) ? " " : ""), buf);
		}
	if(NULL != keybuf) free(keybuf);
	}

void json_duration(int padding, int whitespace, char *key, unsigned long value,
		int format_duration) {

	char		*keybuf = NULL;
	char		buf[1024];
	int			days = 0;
	int			hours = 0;
	int			minutes = 0;
	int			seconds = 0;

	if(0 == format_duration) {
		snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long)value);
		}
	else {
		days = (unsigned)(value / 86400);
		value -= days * 86400;
		hours = (unsigned)(value / 3600);
		value -= hours * 3600;
		minutes = (unsigned)(value / 60);
		value -= minutes * 60;
		seconds = value;
		snprintf(buf, sizeof(buf)-1, "%ud %uh %um %us", days, hours, minutes, 
				seconds);
		}

	if( NULL == key) {
		indentf(padding, whitespace, "%s", buf);
		}
	else {
		keybuf = json_escape_string(key, &string_escapes);
		indentf(padding, whitespace, "\"%s\":%s%s%s%s", keybuf, 
				(( whitespace > 0) ? " " : ""), (format_duration ? "\"" : ""),
				buf, (format_duration ? "\"" : ""));
		}
	if(NULL != keybuf) free(keybuf);
	}

void json_enumeration(json_object *json_parent, unsigned format_options, 
		char *key, int value, const string_value_mapping *map) {

	string_value_mapping *svmp;

	if(format_options & JSON_FORMAT_ENUMERATE) {
		for(svmp = (string_value_mapping *)map; NULL != svmp->string; svmp++) {
			if( value == svmp->value) {
				json_object_append_string(json_parent, key, &percent_escapes,
						svmp->string);
				break;
				}
			}
			if( NULL == svmp->string) {
				json_object_append_string(json_parent, key, NULL,
						"Unknown value %d", svmp->value);
				}
		}
	else {
		json_object_append_integer(json_parent, key, value);
		}
	}

void json_bitmask(json_object *json_parent, unsigned format_options, char *key, 
		int value, const string_value_mapping *map) {

	json_array *json_bitmask_array;
	string_value_mapping *svmp;

	if(format_options & JSON_FORMAT_BITMASK) {
		json_bitmask_array = json_new_array();
		for(svmp = (string_value_mapping *)map; NULL != svmp->string; svmp++) {
			if( value & svmp->value) {
				json_array_append_string(json_bitmask_array, &percent_escapes,
						svmp->string);
				}
			}
		json_object_append_array(json_parent, key, json_bitmask_array);
		}
	else {
		json_object_append_integer(json_parent, key, value);
		}
	}

int parse_bitmask_cgivar(char *cgi, char *query, int query_status,
		json_object *json_parent, time_t query_time, authdata *authinfo,
		char *key, char *value, const string_value_mapping *svm,
		unsigned *var) {

	int result = RESULT_SUCCESS;
	char *option;
	char *saveptr;
	string_value_mapping *svmp;

	if(value == NULL) {
		json_object_append_object(json_parent, "result", 
				json_result(query_time, cgi, query, query_status,
				(time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING,
				"No value specified for %s option.", key));
		return RESULT_OPTION_VALUE_MISSING;
		}

	option = strtok_r(value, " ", &saveptr);
	while(NULL != option) {	
		for(svmp = (string_value_mapping *)svm; NULL != svmp->string; svmp++) {
			if( !strcmp( svmp->string, option)) {
					*var |= svmp->value;
					break;
				}
			}
		if( NULL == svmp->string) {
			json_object_append_object(json_parent, "result", 
					json_result(query_time, cgi, query, query_status,
					(time_t)-1, authinfo, RESULT_OPTION_VALUE_INVALID,
					"The %s option value '%s' is invalid.", key, option));
			result = RESULT_OPTION_VALUE_INVALID;
			break;
			}
		option = strtok_r(NULL, " ", &saveptr);
		}
	return result;
	}

int parse_enumeration_cgivar(char *cgi, char *query, int query_status,
		json_object *json_parent, time_t query_time, authdata *authinfo,
		char *key, char *value, const string_value_mapping *svm, int *var) {

	string_value_mapping *svmp;

	if(value == NULL) {
		json_object_append_object(json_parent, "result", 
				json_result(query_time, cgi, query, query_status,
				(time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING,
				"No value specified for %s option.", key));
		return RESULT_OPTION_VALUE_MISSING;
		}

	for(svmp = (string_value_mapping *)svm; NULL != svmp->string; svmp++) {
		if( !strcmp( svmp->string, value)) {
				*var = svmp->value;
				break;
			}
		}
	if( NULL == svmp->string) {
		json_object_append_object(json_parent, "result", 
				json_result(query_time, cgi, query, query_status,
				(time_t)-1, authinfo, RESULT_OPTION_VALUE_INVALID,
				"The %s option value '%s' is invalid.", key, value));
		return RESULT_OPTION_VALUE_INVALID;
		} 

	return RESULT_SUCCESS;
	}


int parse_string_cgivar(char *cgi, char *query, int query_status,
		json_object *json_parent, time_t query_time, authdata *authinfo,
		char *key, char *value, char **var) {

	if(value == NULL) {
		json_object_append_object(json_parent, "result", 
				json_result(query_time, cgi, query, query_status,
				(time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING,
				"No value specified for %s option.", key));
		return RESULT_OPTION_VALUE_MISSING;
		}

	if(NULL == (*var = strdup( value))) {
		json_object_append_object(json_parent, "result", 
				json_result(query_time, cgi, query, query_status,
				(time_t)-1, authinfo, RESULT_MEMORY_ALLOCATION_ERROR,
				"Unable to allocate memory for %s option.", key));
		return RESULT_MEMORY_ALLOCATION_ERROR;
		}

	return RESULT_SUCCESS;
	}


int parse_time_cgivar(char *cgi, char *query, int query_status,
		json_object *json_parent, time_t query_time, authdata *authinfo,
		char *key, char *value, time_t *var) {

	long long templl;

	if(value == NULL) {
		json_object_append_object(json_parent, "result", 
				json_result(query_time, cgi, query, query_status,
				(time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING,
				"No value specified for %s option.", key));
		return RESULT_OPTION_VALUE_MISSING;
		}

	if('+' == value[0]) {
		templl = strtoll(&(value[1]), NULL, 10);
		*var = (time_t)((long long)query_time + templl);
		}
	else if('-' == value[0]) {
		templl = strtoll(&(value[1]), NULL, 10);
		*var = (time_t)((long long)query_time - templl);
		}
	else {
		templl = strtoll(value, NULL, 10);
		*var = (time_t)templl;
		}

	return RESULT_SUCCESS;
	}


int parse_boolean_cgivar(char *cgi, char *query, int query_status,
		json_object *json_parent, time_t query_time, authdata *authinfo,
		char *key, char *value, int *var) {

	if(value == NULL) {
		json_object_append_object(json_parent, "result", 
				json_result(query_time, cgi, query, query_status,
				(time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING,
				"No value specified for %s option.", key));
		return ERROR;
		}

	if(!strcmp(value, "true")) {
		*var = 1;
		}
	else if(!strcmp(value, "false")) {
		*var = 0;
		}
	else {
		json_object_append_object(json_parent, "result", 
				json_result(query_time, cgi, query, query_status,
				(time_t)-1, authinfo, RESULT_OPTION_VALUE_INVALID,
				"Value for %s option must be 'true' or 'false'.", key));
		return RESULT_OPTION_VALUE_INVALID;
		}

	return RESULT_SUCCESS;
	}


int parse_int_cgivar(char *cgi, char *query, int query_status,
		json_object *json_parent, time_t query_time, authdata *authinfo,
		char *key, char *value, int *var) {

	if(value == NULL) {
		json_object_append_object(json_parent, "result", 
				json_result(query_time, cgi, query, query_status,
				(time_t)-1, authinfo, RESULT_OPTION_VALUE_MISSING,
				"No value specified for %s option.", key));
		return RESULT_OPTION_VALUE_MISSING;
		}

	*var = atoi(value);
	return RESULT_SUCCESS;
	}

int get_query_status(const int statuses[][2], int query) {
	int x;

	for(x = 0; -1 != statuses[x][0]; x++) {
		if(statuses[x][0] == query) return statuses[x][1];
		}
	return -1;
	}

char *svm_get_string_from_value(int value, const string_value_mapping *svm) {

	string_value_mapping *svmp;

	for(svmp = (string_value_mapping *)svm; NULL != svmp->string; svmp++) {
		if(svmp->value == value) return svmp->string;
		}
	return NULL;
	}

char *svm_get_description_from_value(int value, const string_value_mapping *svm) {

	string_value_mapping *svmp;

	for(svmp = (string_value_mapping *)svm; NULL != svmp->string; svmp++) {
		if(svmp->value == value) return svmp->description;
		}
	return NULL;
	}

/* Thanks to Jerry Coffin for posting the basis of this function on Stack
	Overflow */
time_t compile_time(const char *date, const char *time) {

	char buf[5];
	int year;
	int month;
	int day;
	int hour;
	int minute;
	int second;

    struct tm t;
    const char *months = "JanFebMarAprMayJunJulAugSepOctNovDec";

    sscanf(date, "%s %d %d", buf, &day, &year);
    sscanf(time, "%d:%d:%d", &hour, &minute, &second);

    month = (strstr(months, buf) - months) / 3;

    t.tm_year = year - 1900;
    t.tm_mon = month;
    t.tm_mday = day;
    t.tm_hour = hour;
    t.tm_min = minute;
    t.tm_sec = second;
    t.tm_isdst = -1;

    return mktime(&t);
}

/* Escape a string based on the values in the escapes parameter */
char *json_escape_string(const char *src, const json_escape *escapes) {

	wchar_t *wdest;		/* wide character version of the output string */
	size_t	wdest_size;	/* number of available wchars in wdest */
	size_t	wdest_len;	/* number of wchars in wdest */
	int x;
	json_escape_pair	*escp;		/* pointer to current escape pair */
	size_t	from_len;
	size_t	to_len;
	wchar_t	*fromp;		/* pointer to a found "from" string */
	long	offset;		/* offset from beginning of wdest to a "from" string */
	size_t	wchars;		/* number of wide characters to move */
	size_t	dest_len;	/* length of output string "dest" */
	char	*dest;		/* buffer containing the escaped version of src */

	/* Make sure we're passed valid parameters */
	if((NULL == src) || (NULL == escapes)) {
		return NULL;
		}

	/* Make a wide string copy of src */
	wdest_len = mbstowcs(NULL, src, 0);
	if(wdest_len <= 0) return NULL;
	if((wdest = calloc(wdest_len + 1, sizeof(wchar_t))) == NULL) {
		return NULL;
		}
	if(mbstowcs(wdest, src, wdest_len) != wdest_len) {
		free(wdest);
		return NULL;
		}
	wdest_size = wdest_len;

	/* Process each escape pair */
	for(x = 0, escp = (json_escape_pair *)escapes->pairs; x < escapes->count;
			x++, escp++) {
		from_len = wcslen(escp->from);
		to_len = wcslen(escp->to);
		fromp = wdest;
		while((fromp = wcsstr(fromp, escp->from)) != NULL) {
			offset = fromp - wdest;
			if(from_len < to_len) {
				if((wdest_size - wdest_len) < (to_len - from_len)) {
					/* If more room is needed, realloc and update variables */
					wdest_size += (to_len - from_len) * BUF_REALLOC_MULTIPLIER;
					wdest = realloc(wdest, (wdest_size + 1) * sizeof(wchar_t));
					if(NULL == wdest) return NULL;
					fromp = wdest + offset;
					}
				wchars = wdest_len - offset - from_len + 1;
				wmemmove(fromp + to_len, fromp + from_len, wchars);
				wcsncpy(fromp, escp->to, to_len);
				wdest_len += (to_len - from_len);
				fromp += to_len;
				}
			else {
				wchars = wdest_len - offset - to_len;
				memmove(fromp + to_len, fromp + from_len,
						wchars * sizeof(wchar_t));
				wcsncpy(fromp, escp->to, to_len);
				fromp += (from_len - to_len);
				wdest_len -= (from_len - to_len);
				}
			}
		}

	/* Covert the wide string back to a multibyte string */
	dest_len = wcstombs(NULL, wdest, 0);
	if(0 == dest_len) return NULL;
	if((dest = calloc(dest_len + 1, sizeof(char))) == NULL) {
		return NULL;
		}
	if(wcstombs(dest, wdest, dest_len) != dest_len) {
		free(dest);
		return NULL;
		}

	return dest;
	}
