/*
 * curltest.c
 *
 * repeatedly sends the XML string "heartbeat" (included from curltest.h
 * because of its size) to http_url or https_url (if using SSL)
 */
#include <stdio.h>
#include <sys/types.h>
#include <curl/curl.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glib.h>


#include "curltest.h" /* defines static strings, too long for this file */

/*static char url[] = "https://agent:agent@127.0.0.1:18443/heartbeat"; */
static char https_url[] = "https://agent:agent@localhost:18443/heartbeat";
static char http_url[] = "http://agent:agent@localhost:18080/heartbeat";

static int			curl_glob_arg = CURL_GLOBAL_ALL;
static boolean_t	no_ssl = FALSE;
static boolean_t	do_cleanup = TRUE;
static boolean_t	isUrlDefault = TRUE;
static char			*url = https_url;

static char			*post_data = heartbeat;
static int			post_size = sizeof (heartbeat);

static size_t
write_data(void *buffer, size_t G_GNUC_UNUSED size, size_t nmemb, void *userp)
{
	GString *s = userp;

	g_string_append_len(s, buffer, nmemb);
	
	return nmemb;
}

static void
timestamp(char *loc, int loc_sz, boolean_t for_xml)
{
	time_t	now;
	struct tm *now_tm;

	now = time(NULL);
	if (!for_xml) {
		now_tm = localtime(&now);

		sprintf(loc, "%d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", now_tm->tm_year + 1900, 
				now_tm->tm_mon+1, now_tm->tm_mday,
				now_tm->tm_hour, now_tm->tm_min, now_tm->tm_sec);
	} else {
		now_tm = localtime(&now);

		/*
		 * this needs to be in sync with <agentUtc>..</agentUtc> string
		 * in curltest.h
		 */
		sprintf(loc, "%d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d", now_tm->tm_year + 1900, 
				now_tm->tm_mon+1, now_tm->tm_mday,
				now_tm->tm_hour, now_tm->tm_min, now_tm->tm_sec);
		loc[19] = '.'; /* hack: sprintf overwrites this with \0, which we need to get rid of */
	}
}

static void
do_curl_thing(CURL **cu)
{
	CURL				*curl = *cu;
	struct curl_slist	*httpheaders = NULL;
	CURLcode			res;
	gchar				*tmp_char;
	char				errbuf[CURL_ERROR_SIZE];
	GString				*resp;
	long				http_code = 0;
	boolean_t			success = TRUE;
	char				time_str[30];
	static time_t		last_cleanup = 0;
	static int			consecutive_successes = 0;
	
	errbuf[0] = '\0';

	if (post_data != NULL) {
		httpheaders = curl_slist_append(httpheaders,
			"User-Agent: MySQL Enterprise Agent/");

		assert(httpheaders != NULL);
		tmp_char = g_strdup_printf("Content-Type: application/xml");
		httpheaders = curl_slist_append(httpheaders, tmp_char);

		g_free(tmp_char); tmp_char = NULL;

		/* get current timestamp into xml ... this is a hack */
		/* make sure offset is in sync with curltest.h */
		timestamp(&post_data[51], 30, TRUE);
		curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); 
		curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_size);
	}

	curl_easy_setopt(curl, CURLOPT_URL, url);

	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
	curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);

	resp = g_string_new(NULL); /* the write buffer */
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, resp); 

	if (no_ssl) {
		curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_NONE);
	} else {
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
		curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT);
	}

	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 4);
	curl_easy_setopt(curl, CURLOPT_UNRESTRICTED_AUTH, 1);
	
	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
	curl_easy_setopt(curl, CURLOPT_FORBID_REUSE, 1);
	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10); 
	curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120);
	
	curl_easy_setopt(curl, CURLOPT_POST, 1);

	/* for test, not in MySQL agent */
	if (post_data == NULL)
		curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);

	res = curl_easy_perform(curl);
	timestamp(time_str, sizeof (time_str), FALSE);
	if (CURLE_OK != res) {
		long curl_errno = 0;
#if LIBCURL_VERSION_NUM > 0x070c02
		curl_easy_getinfo(curl, CURLINFO_OS_ERRNO, &curl_errno);
#endif
		if (res == CURLE_UNSUPPORTED_PROTOCOL) {
			fprintf(stderr, "%s you requested a unsupported protocol: %s\n",
					time_str, errbuf);
			exit(1);
		} else if (curl_errno != 0) {
			fprintf(stderr, "%s curl_easy_perform() failed: %s (curl-error = "
			    "'%s' (%u), os-error = '%s' (%ld))\n", 
					time_str, errbuf, 
					curl_easy_strerror(res), res, 
					strerror(curl_errno), curl_errno);
		} else {
			fprintf(stderr, "%s curl_easy_perform() failed: %s "
			    "(curl-error = '%s' (%u))\n", 
					time_str, errbuf, 
					curl_easy_strerror(res), res);
		}
		success = FALSE;
	} else if (CURLE_OK != curl_easy_getinfo(curl, CURLINFO_HTTP_CODE, &http_code)) {
		 fprintf(stderr, "%s curl_easy_getinfo(CURLINFO_HTTP_CODE) failed "
		     "(code = %u)\n", time_str, res);
		success = FALSE;
	} else if (200 != http_code) {
		if (http_code == 401) {
			fprintf(stderr, "%s -- received HTTP-status: %ld (failed):"
			    " authentication credentials incorrect\n", time_str, http_code);
			fprintf(stderr, "-- received error-msg: %s\n",
			    resp->str);
		} else {
			fprintf(stderr, "%s -- received HTTP-status: %ld (failed)\n",
			    time_str, http_code);
			fprintf(stderr, "-- received error-msg: %s\n",
			    resp->str);
		}

		success = FALSE;
	} 

	curl_slist_free_all(httpheaders);

	/*
	 * whenever we fail, reset curl connection in the hope of 
	 * keeping mem footprint low.
	 * every 5 minutes or more, reset whole curl state
	 */
	if (FALSE == success) {
		consecutive_successes = 0;
		if (do_cleanup == FALSE)
			goto out;
		time_t	now;
		curl_easy_cleanup(curl);
		now = time(NULL);
		if (last_cleanup == 0) {
			last_cleanup = now;
		} else if (now - last_cleanup > (5 * 60)) {
			curl_global_cleanup();
			if (curl_global_init(curl_glob_arg) != 0) {
				fprintf(stderr, "periodic re-global-init failed\n");
				exit(2);
			}
		}
		*cu = curl_easy_init();
	}

	if (success && (consecutive_successes++ % 10 == 0)) 
		printf("%s successfully reconnected to %s, %d bytes response\n",
				time_str, url, resp->len);
out:
	g_string_free(resp, TRUE);
}

CURL *curl;
static void
cleanup(int sig)
{
	curl_easy_cleanup(curl);
	curl_global_cleanup();
	exit(0);
}

static void
Usage(char *name)
{
	fprintf(stderr, "Usage: %s [-scr] [-u url]\n"
			"\t-s ... no ssl (default: use ssl)\n"
			"\t-c ... no curl cleanup after error\n"
			"\t-p ... no post data (get only)\n"
			"\t-r ... use \"resync\" xml message instead of longer heartbeat\n"
			"\t-u url ... use url instead of default\n", name);
	exit(1);
}

int
main(int argc, char **argv)
{
	int	c;

	while ((c = getopt(argc, argv, "spcru:")) != -1) {
		switch (c) {
		case 'u': url = optarg;
				isUrlDefault = FALSE;
				break;
		case 's': no_ssl = TRUE;
				curl_glob_arg = CURL_GLOBAL_NOTHING;
				/* only change url if it's not in argv (see -u) */
				if (url == https_url)
					url = http_url;
				break;
		case 'p': post_data = NULL;
				break;
		case 'r': post_data = xml_resync;
				post_size = sizeof (xml_resync);
				break;
		case 'c': do_cleanup = FALSE;
				break;
		default: Usage(argv[0]);
				/* notreached */
				break;
		}
	}

	signal(SIGINT, cleanup);

	if (curl_global_init(curl_glob_arg) != 0) {
		fprintf(stderr, "cannot curl_global_init()\n");
		exit(1);
	}
	
	curl = curl_easy_init();
	assert(curl != NULL);

	for (;;) {
		do_curl_thing(&curl);
		sleep(5);
	}

}


