/*
 * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
 *
 * The Initial Developer of the Original Code is
 * Anthony Minessale II <anthm@freeswitch.org>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Rupa Schomaker <rupa@rupa.com>
 * Yossi Neiman <mishehu@freeswitch.org>
 * Seven Du <dujinfang@gmail.com>
 *
 * mod_curl.c -- API for performing http queries
 *
 */

#include <switch.h>
#include <switch_curl.h>
#ifdef  _MSC_VER
#include <WinSock2.h>
#else
#include <sys/socket.h>
#endif


/* Prototypes */
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_curl_shutdown);
SWITCH_MODULE_RUNTIME_FUNCTION(mod_curl_runtime);
SWITCH_MODULE_LOAD_FUNCTION(mod_curl_load);

/* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime)
 * Defines a switch_loadable_module_function_table_t and a static const char[] modname
 */
SWITCH_MODULE_DEFINITION(mod_curl, mod_curl_load, mod_curl_shutdown, NULL);

static char *SYNTAX = "curl url [headers|json|content-type <mime-type>|connect-timeout <seconds>|timeout <seconds>|append_headers <header_name:header_value>[|append_headers <header_name:header_value>]|insecure|secure|[proxy <http://proxy:port>]] [get|head|post|delete|put [data]]";

#define HTTP_SENDFILE_ACK_EVENT "curl_sendfile::ack"
#define HTTP_SENDFILE_RESPONSE_SIZE 32768
#define HTTP_MAX_APPEND_HEADERS 10
#define HTTP_DEFAULT_MAX_BYTES 64000

static struct {
	switch_memory_pool_t *pool;
	switch_event_node_t *node;
	int max_bytes;
	switch_bool_t validate_certs;
} globals;

static switch_xml_config_item_t instructions[] = {
	/* parameter name        type                 reloadable   pointer                         default value     options structure */
	SWITCH_CONFIG_ITEM("max-bytes", SWITCH_CONFIG_INT, CONFIG_RELOADABLE, &globals.max_bytes, (void *) HTTP_DEFAULT_MAX_BYTES, NULL,NULL, NULL),
	SWITCH_CONFIG_ITEM("validate-certs", SWITCH_CONFIG_BOOL, CONFIG_RELOADABLE, &globals.validate_certs, SWITCH_FALSE, NULL, NULL, NULL),
	SWITCH_CONFIG_ITEM_END()
};

typedef enum {
	CSO_NONE = (1 << 0),
	CSO_EVENT = (1 << 1),
	CSO_STREAM = (1 << 2)
} curlsendfile_output_t;

struct http_data_obj {
	switch_stream_handle_t stream;
	switch_size_t bytes;
	switch_size_t max_bytes;
	switch_memory_pool_t *pool;
	int err;
	long http_response_code;
	char *http_response;
	char *cacert;
	switch_curl_slist_t *headers;
};
typedef struct http_data_obj http_data_t;

struct http_sendfile_data_obj {
	switch_memory_pool_t *pool;
	switch_file_t *file_handle;
	long http_response_code;
	char *http_response;
	switch_curl_slist_t *headers;
	char *mydata;
	char *url;
	char *identifier_str;
	char *filename_element;
	char *filename_element_name;
	char *extrapost_elements;
	switch_CURL *curl_handle;
	char *cacert;
	struct curl_httppost *formpost;
	struct curl_httppost *lastptr;
	uint8_t flags; /* This is for where to send output of the curl_sendfile commands */
	switch_stream_handle_t *stream;
	char *sendfile_response;
	switch_size_t sendfile_response_count;
};

typedef struct http_sendfile_data_obj http_sendfile_data_t;

struct data_stream {
	const char *data;
	size_t length;
};

struct callback_obj {
	switch_memory_pool_t *pool;
	char *name;
};
typedef struct callback_obj callback_t;

struct curl_options_obj {
	long connect_timeout;
	long timeout;
	int insecure;
	char *proxy;
};
typedef struct curl_options_obj curl_options_t;

static size_t file_callback(void *ptr, size_t size, size_t nmemb, void *data)
{
	register unsigned int realsize = (unsigned int) (size * nmemb);
	http_data_t *http_data = data;

	http_data->bytes += realsize;

	if (http_data->bytes > http_data->max_bytes) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Oversized file detected [%d bytes]\n", (int) http_data->bytes);
		http_data->err = 1;
		return 0;
	}

	http_data->stream.write_function(&http_data->stream, "%.*s", realsize, ptr);
	return realsize;
}

static size_t header_callback(void *ptr, size_t size, size_t nmemb, void *data)
{
	register unsigned int realsize = (unsigned int) (size * nmemb);
	http_data_t *http_data = data;
	char *header = NULL;

	header = switch_core_alloc(http_data->pool, realsize + 1);
	switch_copy_string(header, ptr, realsize);
	header[realsize] = '\0';

	http_data->headers = switch_curl_slist_append(http_data->headers, header);

	return realsize;
}

static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream)
{
	struct data_stream *dstream = (struct data_stream*)stream;
	size_t nmax = size*nmemb;
	size_t ncur = (dstream->length > nmax) ? nmax : dstream->length;
	memmove(ptr, dstream->data, ncur);
	dstream->data += ncur;
	dstream->length -= ncur;
	return ncur;
}

static http_data_t *do_lookup_url(switch_memory_pool_t *pool, const char *url, const char *method, const char *data, const char *content_type, char *append_headers[], curl_options_t *options)
{
	switch_CURL *curl_handle = NULL;
	long httpRes = 0;
	http_data_t *http_data = NULL;
	switch_curl_slist_t *headers = NULL;
	struct data_stream dstream = { NULL };

	assert(options);

	http_data = switch_core_alloc(pool, sizeof(http_data_t));
	memset(http_data, 0, sizeof(http_data_t));
	http_data->pool = pool;

	http_data->max_bytes = globals.max_bytes;
	SWITCH_STANDARD_STREAM(http_data->stream);

	if (!method) {
		method = "get";
	}

	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "method: %s, url: %s, content-type: %s\n", method, url, content_type);
	curl_handle = switch_curl_easy_init();

	if (options->connect_timeout) {
		switch_curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, options->connect_timeout);
	}

	if (options->timeout) {
		switch_curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, options->timeout);
	}

	if (options->proxy) {
		switch_curl_easy_setopt(curl_handle, CURLOPT_PROXY, options->proxy);
	}

	if (!strncasecmp(url, "https", 5)) {
		http_data->cacert = switch_core_sprintf(http_data->pool, "%s%scacert.pem", SWITCH_GLOBAL_dirs.certs_dir, SWITCH_PATH_SEPARATOR);

		if (switch_file_exists(http_data->cacert, http_data->pool) == SWITCH_STATUS_SUCCESS) {
			switch_curl_easy_setopt(curl_handle, CURLOPT_CAINFO, http_data->cacert);
		} else {
			http_data->cacert = NULL;
			if (options->insecure) {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Not verifying TLS cert for %s; connection is not secure\n", url);
				switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
				switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
			} else {
				switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1);
				switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 1);
			}
		}
	}

	if (append_headers) {
		int ah_index = 0;
		while(append_headers[ah_index] && *append_headers[ah_index]) {
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "CURL append_header_%d: %s\n", ah_index, append_headers[ah_index]);
			headers = switch_curl_slist_append(headers, append_headers[ah_index]);
			ah_index++;
		}
	}

	if (!strcasecmp(method, "head")) {
		switch_curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1);
	} else if (!strcasecmp(method, "post")) {
		switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, strlen(data));
		switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, (void *) data);
		if (content_type) {
			char *ct = switch_mprintf("Content-Type: %s", content_type);
			headers = switch_curl_slist_append(headers, ct);
			switch_safe_free(ct);
		}
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Post data: %s\n", data);
	} else if (!strcasecmp(method, "patch")) {
		switch_curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "PATCH");
		switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, strlen(data));
		switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, (void *) data);
		if (content_type) {
			char *ct = switch_mprintf("Content-Type: %s", content_type);
			headers = switch_curl_slist_append(headers, ct);
			switch_safe_free(ct);
		}
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "PATCH data: %s\n", data);
	} else if (!strcasecmp(method, "delete")) {
		switch_curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "DELETE");
		switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, strlen(data));
		switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, (void *) data);
		if (content_type) {
			char *ct = switch_mprintf("Content-Type: %s", content_type);
			headers = switch_curl_slist_append(headers, ct);
			switch_safe_free(ct);
		}
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "DELETE data: %s\n", data);
	} else if (!strcasecmp(method, "put")) {
		dstream.data = data;
		dstream.length = strlen(data);
		switch_curl_easy_setopt(curl_handle, CURLOPT_UPLOAD, 1);
		switch_curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_callback);
		switch_curl_easy_setopt(curl_handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t)dstream.length);
		switch_curl_easy_setopt(curl_handle, CURLOPT_READDATA, (void *) &dstream);
		if (content_type) {
			char *ct = switch_mprintf("Content-Type: %s", content_type);
			headers = switch_curl_slist_append(headers, ct);
			headers = switch_curl_slist_append(headers, "Expect:");
			switch_safe_free(ct);
		}
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "PUT data: %s\n", data);
	} else {
		switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 1);
	}

	if (headers) {
		switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
	}

	switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
	switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 15);
	switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
	switch_curl_easy_setopt(curl_handle, CURLOPT_URL, url);
	switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
	switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, file_callback);
	switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) http_data);
	switch_curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, header_callback);
	switch_curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, (void *) http_data);
	switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "freeswitch-curl/1.0");

	switch_curl_easy_perform(curl_handle);
	switch_curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &httpRes);
	switch_curl_easy_cleanup(curl_handle);
	switch_curl_slist_free_all(headers);

	if (http_data->stream.data && !zstr((char *) http_data->stream.data) && strcmp(" ", http_data->stream.data)) {

		http_data->http_response = switch_core_strdup(pool, http_data->stream.data);
	}

	http_data->http_response_code = httpRes;

	switch_safe_free(http_data->stream.data);
	return http_data;
}


static char *print_json(switch_memory_pool_t *pool, http_data_t *http_data)
{
	cJSON *top = cJSON_CreateObject(),
	      *headers = cJSON_CreateArray();
	char *data = NULL;
	char tmp[32], *f = NULL;
	switch_curl_slist_t *header = http_data->headers;

	if(!top || !headers) {
		cJSON_Delete(headers);

		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to alloc memory for cJSON structures.\n");
		goto curl_json_output_end;
	}

	switch_snprintf(tmp, sizeof(tmp), "%ld", http_data->http_response_code);
	cJSON_AddItemToObject(top, "status_code", cJSON_CreateString(tmp));
	if (http_data->http_response) {
		cJSON_AddItemToObject(top, "body", cJSON_CreateString(http_data->http_response));
	}

	/* parse header data */
	while (header) {
		cJSON *obj = NULL;
		/* remove trailing \r */
		if ((data =  strrchr(header->data, '\r'))) {
			*data = '\0';
		}

		if (zstr(header->data)) {
			header = header->next;
			continue;
		}

		if ((data = strchr(header->data, ':'))) {
			*data = '\0';
			data++;
			while (*data == ' ' && *data != '\0') {
				data++;
			}
			obj = cJSON_CreateObject();
			cJSON_AddItemToObject(obj, "key", cJSON_CreateString(header->data));
			cJSON_AddItemToObject(obj, "value", cJSON_CreateString(data));
			cJSON_AddItemToArray(headers, obj);
		} else {
			if (!strncmp("HTTP", header->data, 4)) {
				char *argv[3] = { 0 };
				int argc;
				if ((argc = switch_separate_string(header->data, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) {
					if (argc > 2) {
						cJSON_AddItemToObject(top, "version", cJSON_CreateString(argv[0]));
						cJSON_AddItemToObject(top, "phrase", cJSON_CreateString(argv[2]));
					} else {
						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unparsable header: argc: %d\n", argc);
					}
				} else {
					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Starts with HTTP but not parsable: %s\n", header->data);
				}
			} else {
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unparsable header: %s\n", header->data);
			}
		}
		header = header->next;
	}
	cJSON_AddItemToObject(top, "headers", headers);
	f = cJSON_PrintUnformatted(top);
	data = switch_core_strdup(pool, f);
	switch_safe_free(f);

curl_json_output_end:
	cJSON_Delete(top);		/* should free up all children */
	return data;
}

static size_t http_sendfile_response_callback(void *ptr, size_t size, size_t nmemb, void *data)
{
	register unsigned int realsize = (unsigned int) (size * nmemb);
	http_sendfile_data_t *http_data = data;

	if(http_data->sendfile_response_count + realsize < HTTP_SENDFILE_RESPONSE_SIZE)
	{
		// I'm not sure why we need the (realsize+1) here, but it truncates the data by 1 char if I don't do this
		switch_copy_string(&http_data->sendfile_response[http_data->sendfile_response_count], ptr, (realsize+1));
		http_data->sendfile_response_count += realsize;
	}
	else
	{
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Response page is more than %d bytes long, truncating.\n", HTTP_SENDFILE_RESPONSE_SIZE);
		realsize = 0;
	}

	return realsize;
}

// This function and do_lookup_url functions could possibly be merged together.  Or at least have do_lookup_url call this up as part of the initialization routine as it is a subset of the operations.
static void http_sendfile_initialize_curl(http_sendfile_data_t *http_data)
{
	uint8_t count;
	http_data->curl_handle = curl_easy_init();

	if (!strncasecmp(http_data->url, "https", 5))
	{
		http_data->cacert = switch_core_sprintf(http_data->pool, "%s%scacert.pem", SWITCH_GLOBAL_dirs.certs_dir, SWITCH_PATH_SEPARATOR);

		if (switch_file_exists(http_data->cacert, http_data->pool) == SWITCH_STATUS_SUCCESS) {
			switch_curl_easy_setopt(http_data->curl_handle, CURLOPT_CAINFO, http_data->cacert);
		} else {
			http_data->cacert = NULL;
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Not verifying TLS cert for %s; connection is not secure\n", http_data->url);
			curl_easy_setopt(http_data->curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
			curl_easy_setopt(http_data->curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
		}
	}

	/* From the docs:
	 * Optionally, you can provide data to POST using the CURLOPT_READFUNCTION and CURLOPT_READDATA
	 * options but then you must make sure to not set CURLOPT_POSTFIELDS to anything but NULL
	 * curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, strlen(data));
	 * curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, (void *) data);
	 */

	// switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Post data: %s\n", data);

	curl_easy_setopt(http_data->curl_handle, CURLOPT_FOLLOWLOCATION, 1);
	curl_easy_setopt(http_data->curl_handle, CURLOPT_MAXREDIRS, 15);
	curl_easy_setopt(http_data->curl_handle, CURLOPT_URL, http_data->url);
	curl_easy_setopt(http_data->curl_handle, CURLOPT_NOSIGNAL, 1);
	curl_easy_setopt(http_data->curl_handle, CURLOPT_USERAGENT, "freeswitch-curl/1.0");

	http_data->sendfile_response = switch_core_alloc(http_data->pool, sizeof(char) * HTTP_SENDFILE_RESPONSE_SIZE);
	memset(http_data->sendfile_response, 0, sizeof(char) * HTTP_SENDFILE_RESPONSE_SIZE);

	// Set the function where we will copy out the response body data to
	curl_easy_setopt(http_data->curl_handle, CURLOPT_WRITEFUNCTION, http_sendfile_response_callback);
	curl_easy_setopt(http_data->curl_handle, CURLOPT_WRITEDATA, (void *) http_data);

	/* Add the file to upload as a POST form field */
	curl_formadd(&http_data->formpost, &http_data->lastptr, CURLFORM_COPYNAME, http_data->filename_element_name, CURLFORM_FILE, http_data->filename_element, CURLFORM_END);

	if(!zstr(http_data->extrapost_elements))
	{
		// Now to parse out the individual post element/value pairs
		char *argv[64] = { 0 }; // Probably don't need 64 but eh does it really use that much memory?
		uint32_t argc = 0;
		char *temp_extrapost = switch_core_strdup(http_data->pool, http_data->extrapost_elements);

		argc = switch_separate_string(temp_extrapost, '&', argv, (sizeof(argv) / sizeof(argv[0])));

		for(count = 0; count < argc; count++)
		{
			char *argv2[4] = { 0 };
			uint32_t argc2 = switch_separate_string(argv[count], '=', argv2, (sizeof(argv2) / sizeof(argv2[0])));

			if(argc2 == 2) {
				switch_url_decode(argv2[0]);
				switch_url_decode(argv2[1]);
				curl_formadd(&http_data->formpost, &http_data->lastptr, CURLFORM_COPYNAME, argv2[0], CURLFORM_COPYCONTENTS, argv2[1], CURLFORM_END);
			}
		}
	}

	/* Fill in the submit field too, even if this isn't really needed */
	curl_formadd(&http_data->formpost, &http_data->lastptr, CURLFORM_COPYNAME, "submit", CURLFORM_COPYCONTENTS, "or_die", CURLFORM_END);

	/* what URL that receives this POST */
	curl_easy_setopt(http_data->curl_handle, CURLOPT_HTTPPOST, http_data->formpost);

	// This part actually fires off the curl, captures the HTTP response code, and then frees up the handle.
	curl_easy_perform(http_data->curl_handle);
	curl_easy_getinfo(http_data->curl_handle, CURLINFO_RESPONSE_CODE, &http_data->http_response_code);

	curl_easy_cleanup(http_data->curl_handle);

	// Clean up the form data from POST
	curl_formfree(http_data->formpost);
}

static switch_status_t http_sendfile_test_file_open(http_sendfile_data_t *http_data, switch_event_t *event)
{
	switch_status_t retval = switch_file_open(&http_data->file_handle, http_data->filename_element, SWITCH_FOPEN_READ, SWITCH_FPROT_UREAD,http_data->pool);
	if(retval != SWITCH_STATUS_SUCCESS)
	{
		if(switch_test_flag(http_data, CSO_EVENT))
		{
			if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, HTTP_SENDFILE_ACK_EVENT) == SWITCH_STATUS_SUCCESS)
			{
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Command-Execution-Identifier", http_data->identifier_str);
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Filename", http_data->filename_element);
				switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "File-Access", "Failure");
				switch_event_fire(&event);
				switch_event_destroy(&event);
			}
			else
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to create event to notify of failure to open file %s\n", http_data->filename_element);
		}

		if((switch_test_flag(http_data, CSO_STREAM) || switch_test_flag(http_data, CSO_NONE)) && http_data->stream)
			http_data->stream->write_function(http_data->stream, "-Err Unable to open file %s\n", http_data->filename_element);

		if(switch_test_flag(http_data, CSO_NONE) && !http_data->stream)
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "curl_sendfile: Unable to open file %s\n", http_data->filename_element);
	}

	return retval;
}

static void http_sendfile_success_report(http_sendfile_data_t *http_data, switch_event_t *event)
{
	if(switch_test_flag(http_data, CSO_EVENT))
	{
		if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, HTTP_SENDFILE_ACK_EVENT) == SWITCH_STATUS_SUCCESS)
		{
			char *code_as_string = switch_core_alloc(http_data->pool, 16);
			memset(code_as_string, 0, 16);
			switch_snprintf(code_as_string, 16, "%d", http_data->http_response_code);

			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Command-Execution-Identifier", http_data->identifier_str);
			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Filename", http_data->filename_element);
			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "File-Access", "Success");
			switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "REST-HTTP-Code", code_as_string);
			switch_event_add_body(event, "%s", http_data->sendfile_response);

			switch_event_fire(&event);
			switch_event_destroy(&event);
		}
		else
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to create a event to report on success of curl_sendfile.\n");
	}

	if((switch_test_flag(http_data, CSO_STREAM) || switch_test_flag(http_data, CSO_NONE) || switch_test_flag(http_data, CSO_EVENT)) && http_data->stream)
	{
		if(http_data->http_response_code == 200)
			http_data->stream->write_function(http_data->stream, "+200 Ok\n");
		else
			http_data->stream->write_function(http_data->stream, "-%d Err\n", http_data->http_response_code);

		if(http_data->sendfile_response_count && switch_test_flag(http_data, CSO_STREAM))
			http_data->stream->write_function(http_data->stream, "%s\n", http_data->sendfile_response);
	}

	if(switch_test_flag(http_data, CSO_NONE) && !http_data->stream)
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Sending of file %s to url %s resulted with code %lu\n", http_data->filename_element, http_data->url, http_data->http_response_code);
}

#define HTTP_SENDFILE_APP_SYNTAX "<url> <filenameParamName=filepath> [nopost|postparam1=foo&postparam2=bar... [event|none  [identifier ]]]"
SWITCH_STANDARD_APP(http_sendfile_app_function)
{
	switch_event_t *event = NULL;
	char *argv[10] = { 0 }, *argv2[10] = { 0 };
	int argc = 0, argc2 = 0;
	http_sendfile_data_t *http_data = NULL;
	switch_memory_pool_t *pool = switch_core_session_get_pool(session);
	switch_channel_t *channel = switch_core_session_get_channel(session);

	assert(channel != NULL);

	http_data = switch_core_alloc(pool, sizeof(http_sendfile_data_t));
	memset(http_data, 0, sizeof(http_sendfile_data_t));

	http_data->pool = pool;

	// Either the parameters are provided on the data="" or else they are provided as chanvars.  No mixing & matching
	if(!zstr(data))
	{
		http_data->mydata = switch_core_strdup(http_data->pool, data);

		if ((argc = switch_separate_string(http_data->mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0])))))
		{
			uint8_t i = 0;

			if (argc < 2 || argc > 5)
				goto http_sendfile_app_usage;

			http_data->url = switch_core_strdup(http_data->pool, argv[i++]);

			switch_url_decode(argv[i]);
			argc2 = switch_separate_string(argv[i++], '=', argv2, (sizeof(argv2) / sizeof(argv2[0])));

			if(argc2 == 2)
			{
				http_data->filename_element_name = switch_core_strdup(pool, argv2[0]);
				http_data->filename_element = switch_core_strdup(pool, argv2[1]);
			}
			else
				goto http_sendfile_app_usage;

			if(argc > 2)
			{
				http_data->extrapost_elements = switch_core_strdup(pool, argv[i++]);

				if(argc > 3)
				{
					if(!strncasecmp(argv[i++], "event", 5))
					{
						switch_set_flag(http_data, CSO_EVENT);
						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Setting output to event handler.\n");
					}

					if(argc > 4)
					{
						if(strncasecmp(argv[i], "uuid", 4))
							http_data->identifier_str = switch_core_session_get_uuid(session);
						else
							http_data->identifier_str = switch_core_strdup(pool, argv[i++]);
					}
				}
			}
		}
	}
	else
	{
		char *send_output = (char *) switch_channel_get_variable_dup(channel, "curl_sendfile_report", SWITCH_TRUE, -1);
		char *identifier = (char *) switch_channel_get_variable_dup(channel, "curl_sendfile_identifier", SWITCH_TRUE, -1);

		http_data->url = (char *) switch_channel_get_variable_dup(channel, "curl_sendfile_url", SWITCH_TRUE, -1);
		http_data->filename_element_name = (char *) switch_channel_get_variable_dup(channel, "curl_sendfile_filename_element", SWITCH_TRUE, -1);
		http_data->filename_element = (char *) switch_channel_get_variable_dup(channel, "curl_sendfile_filename", SWITCH_TRUE, -1);
		http_data->extrapost_elements = (char *) switch_channel_get_variable_dup(channel, "curl_sendfile_extrapost", SWITCH_TRUE, -1);


		if(zstr(http_data->url) || zstr(http_data->filename_element) || zstr(http_data->filename_element_name))
			goto http_sendfile_app_usage;

		if(!zstr(send_output))
		{
			if(!strncasecmp(send_output, "event", 5))
				switch_set_flag(http_data, CSO_EVENT);
			else if(!strncasecmp(send_output, "none", 4))
				switch_set_flag(http_data, CSO_NONE);
			else
			{
				switch_set_flag(http_data, CSO_NONE);
				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Invalid parameter %s specified for curl_sendfile_report.  Setting default of 'none'.\n", send_output);
			}
		}
		else
		{
			switch_set_flag(http_data, CSO_NONE);
			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "No parameter specified for curl_sendfile_report.  Setting default of 'none'.\n");
		}

		if(!zstr(identifier))
		{
			if(!strncasecmp(identifier, "uuid", 4))
				http_data->identifier_str = switch_core_session_get_uuid(session);
			else if(!zstr(identifier))
				http_data->identifier_str = identifier;
		}
	}

	switch_url_decode(http_data->filename_element_name);
	switch_url_decode(http_data->filename_element);

	// We need to check the file now...
	if(http_sendfile_test_file_open(http_data, event) != SWITCH_STATUS_SUCCESS)
		goto http_sendfile_app_done;

	switch_file_close(http_data->file_handle);

	switch_url_decode(http_data->url);

	http_sendfile_initialize_curl(http_data);

	http_sendfile_success_report(http_data, event);

	goto http_sendfile_app_done;

http_sendfile_app_usage:
	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failure:  Usage: <data=\"%s\">\nOr you can set chanvars curl_senfile_url, curl_sendfile_filename_element, curl_sendfile_filename, curl_sendfile_extrapost\n", HTTP_SENDFILE_APP_SYNTAX);

http_sendfile_app_done:
	if (http_data->headers)
	{
		switch_curl_slist_free_all(http_data->headers);
	}

	return;
}

#define HTTP_SENDFILE_SYNTAX "<url> <filenameParamName=filepath> [nopost|postparam1=foo&postparam2=bar... [event|stream|both|none  [identifier ]]]"
SWITCH_STANDARD_API(http_sendfile_function)
{
	switch_status_t status = SWITCH_STATUS_FALSE;
	switch_bool_t new_memory_pool = SWITCH_FALSE;
	char *argv[10] = { 0 }, *argv2[10] = { 0 };
	int argc = 0, argc2 = 0;
	http_sendfile_data_t *http_data = NULL;
	switch_memory_pool_t *pool = NULL;
	switch_event_t *event = NULL;

	if(zstr(cmd))
	{
		status = SWITCH_STATUS_SUCCESS;
		goto http_sendfile_usage;
	}
	if(session)
	{
		pool = switch_core_session_get_pool(session);
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "We're using a session's memory pool for curl_sendfile.  Maybe we should consider always making a new memory pool?\n");
	}
	else
	{
		switch_core_new_memory_pool(&pool);
		new_memory_pool = SWITCH_TRUE;  // So we can properly destroy the memory pool
	}

	http_data = switch_core_alloc(pool, sizeof(http_sendfile_data_t));
	memset(http_data, 0, sizeof(http_sendfile_data_t));

	http_data->mydata = switch_core_strdup(pool, cmd);
	http_data->stream = stream;
	http_data->pool = pool;

	// stream->write_function(stream,"\ncmd is %s\nmydata is %s\n", cmd, http_data->mydata);

	if ((argc = switch_separate_string(http_data->mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0])))))
	{
		uint8_t i = 0;

		if (argc < 2 || argc > 5)
		{
			status = SWITCH_STATUS_SUCCESS;
			goto http_sendfile_usage;
		}

		http_data->url = switch_core_strdup(pool, argv[i++]);

		switch_url_decode(argv[i]);
		argc2 = switch_separate_string(argv[i++], '=', argv2, (sizeof(argv2) / sizeof(argv2[0])));

		if(argc2 == 2)
		{
			http_data->filename_element_name = switch_core_strdup(pool, argv2[0]);
			http_data->filename_element = switch_core_strdup(pool, argv2[1]);
		}
		else
			goto http_sendfile_usage;

		switch_url_decode(http_data->filename_element_name);
		switch_url_decode(http_data->filename_element);

		if(argc > 2)
		{
			http_data->extrapost_elements = switch_core_strdup(pool, argv[i++]);

			if(argc > 3)
			{
				if(!strncasecmp(argv[i], "event", 5))
					switch_set_flag(http_data, CSO_EVENT);
				else if(!strncasecmp(argv[i], "stream", 6))
					switch_set_flag(http_data, CSO_STREAM);
				else if(!strncasecmp(argv[i], "both", 4))
				{
					switch_set_flag(http_data, CSO_EVENT);
					switch_set_flag(http_data, CSO_STREAM);
				}
				else
				{
					if(strncasecmp(argv[i], "none", 4))
						switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Invalid 4th parameter set for curl_sendfile.  Defaulting to \"none\"\n");

					switch_set_flag(http_data, CSO_NONE);
				}

				i++;

				if(argc > 4)
					http_data->identifier_str = switch_core_strdup(pool, argv[i++]);
			}
		}
	}

	// We need to check the file now...
	if(http_sendfile_test_file_open(http_data, event) != SWITCH_STATUS_SUCCESS)
		goto http_sendfile_done;


	switch_file_close(http_data->file_handle);

	switch_url_decode(http_data->url);

	http_sendfile_initialize_curl(http_data);

	http_sendfile_success_report(http_data, event);

	status = SWITCH_STATUS_SUCCESS;
	goto http_sendfile_done;

http_sendfile_usage:
	stream->write_function(stream, "-USAGE\n%s\n", HTTP_SENDFILE_SYNTAX);
	goto http_sendfile_done;

http_sendfile_done:
	if (http_data && http_data->headers)
	{
		switch_curl_slist_free_all(http_data->headers);
	}

	if (new_memory_pool == SWITCH_TRUE)
	{
		switch_core_destroy_memory_pool(&pool);
	}

	return status;
}

SWITCH_STANDARD_APP(curl_app_function)
{
	switch_status_t status = SWITCH_STATUS_SUCCESS;

	char *argv[10] = { 0 };
	int argc;
	char *mydata = NULL;

	switch_memory_pool_t *pool = switch_core_session_get_pool(session);
	switch_channel_t *channel = switch_core_session_get_channel(session);
	char *url = NULL;
	char *method = NULL;
	char *postdata = "";
	char *content_type = NULL;
	switch_bool_t do_headers = SWITCH_FALSE;
	switch_bool_t do_json = SWITCH_FALSE;
	http_data_t *http_data = NULL;
	switch_curl_slist_t *slist = NULL;
	switch_stream_handle_t stream = { 0 };
	int i = 0;
	curl_options_t options = { .insecure = !globals.validate_certs };
	const char *curl_timeout;
	char *append_headers[HTTP_MAX_APPEND_HEADERS + 1] = { 0 };
	int ah_index = 0;

	if (!(mydata = switch_core_session_strdup(session, data))) {
		return;
	}

	if ((argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) {
		if (argc == 0) {
			switch_goto_status(SWITCH_STATUS_SUCCESS, usage);
		}

		url = switch_core_strdup(pool, argv[0]);

		for (i = 1; i < argc; i++) {
			if (!strcasecmp("headers", argv[i])) {
				do_headers = SWITCH_TRUE;
			} else if (!strcasecmp("json", argv[i])) {
				do_json = SWITCH_TRUE;
			} else if (!strcasecmp("get", argv[i]) || !strcasecmp("head", argv[i])) {
				method = switch_core_strdup(pool, argv[i]);
			} else if (!strcasecmp("post", argv[i]) || !strcasecmp("patch", argv[i]) || !strcasecmp("put", argv[i]) || !strcasecmp("delete", argv[i])) {
				method = argv[i];
				if (++i < argc) {
					postdata = switch_core_strdup(pool, argv[i]);
					switch_url_decode(postdata);
				} else {
					postdata = "";
				}
			} else if (!strcasecmp("content-type", argv[i])) {
				if (++i < argc) {
					content_type = switch_core_strdup(pool, argv[i]);
				}
			} else if (!strcasecmp("append_headers", argv[i])) {
				if (++i < argc) {
					if (ah_index == HTTP_MAX_APPEND_HEADERS) continue;
					append_headers[ah_index++] = argv[i];
				}
			} else if (!strcasecmp("insecure", argv[i])) {
				options.insecure = 1;
			} else if (!strcasecmp("secure", argv[i])) {
				options.insecure = 0;
			} else if (!strcasecmp("proxy", argv[i])) {
				if (++i < argc) {
					options.proxy = argv[i];
				}
			}
		}
	}

	curl_timeout = switch_channel_get_variable(channel, "curl_connect_timeout");

	if (curl_timeout) options.connect_timeout = atoi(curl_timeout);

	curl_timeout = switch_channel_get_variable(channel, "curl_timeout");

	if (curl_timeout) options.timeout = atoi(curl_timeout);


	http_data = do_lookup_url(pool, url, method, postdata, content_type, append_headers, &options);

	if (do_json) {
		switch_channel_set_variable(channel, "curl_response_data", print_json(pool, http_data));
	} else {
		SWITCH_STANDARD_STREAM(stream);
		if (do_headers) {
			slist = http_data->headers;
			while (slist) {
				stream.write_function(&stream, "%s\n", slist->data);
				slist = slist->next;
			}
			stream.write_function(&stream, "\n");
		}
		stream.write_function(&stream, "%s", http_data->http_response ? http_data->http_response : "");
		switch_channel_set_variable(channel, "curl_response_data", stream.data);
	}
	switch_channel_set_variable(channel, "curl_response_code", switch_core_sprintf(pool, "%ld", http_data->http_response_code));
	switch_channel_set_variable(channel, "curl_method", method);

	switch_goto_status(SWITCH_STATUS_SUCCESS, done);

  usage:
	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Usage: %s\n", SYNTAX);
	switch_goto_status(status, done);

  done:
	switch_safe_free(stream.data);
	if (http_data && http_data->headers) {
		switch_curl_slist_free_all(http_data->headers);
	}
	if (!session && pool) {
		switch_core_destroy_memory_pool(&pool);
	}
}

SWITCH_STANDARD_API(curl_function)
{
	switch_status_t status;
	char *argv[10] = { 0 };
	int argc;
	char *mydata = NULL;
	char *url = NULL;
	char *method = NULL;
	char *postdata = "";
	char *content_type = NULL;
	switch_bool_t do_headers = SWITCH_FALSE;
	switch_bool_t do_json = SWITCH_FALSE;
	switch_curl_slist_t *slist = NULL;
	http_data_t *http_data = NULL;
	int i = 0;
	char *append_headers[HTTP_MAX_APPEND_HEADERS + 1] = { 0 };
	int ah_index = 0;

	switch_memory_pool_t *pool = NULL;
	curl_options_t options = { .insecure = !globals.validate_certs };

	if (zstr(cmd)) {
		switch_goto_status(SWITCH_STATUS_SUCCESS, usage);
	}

	if (session) {
		pool = switch_core_session_get_pool(session);
	} else {
		switch_core_new_memory_pool(&pool);
	}

	mydata = strdup(cmd);
	if ((argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) {
		if (argc < 1) {
			switch_goto_status(SWITCH_STATUS_SUCCESS, usage);
		}

		url = switch_core_strdup(pool, argv[0]);

		for (i = 1; i < argc; i++) {
			if (!strcasecmp("headers", argv[i])) {
				do_headers = SWITCH_TRUE;
			} else if (!strcasecmp("json", argv[i])) {
				do_json = SWITCH_TRUE;
			} else if (!strcasecmp("get", argv[i]) || !strcasecmp("head", argv[i])) {
				method = switch_core_strdup(pool, argv[i]);
			} else if (!strcasecmp("post", argv[i]) || !strcasecmp("patch", argv[i]) || !strcasecmp("put", argv[i]) || !strcasecmp("delete", argv[i])) {
				method = argv[i];
				if (++i < argc) {
					postdata = switch_core_strdup(pool, argv[i]);
					switch_url_decode(postdata);
				} else {
					postdata = "";
				}
			} else if (!strcasecmp("content-type", argv[i])) {
				if (++i < argc) {
					content_type = switch_core_strdup(pool, argv[i]);
				}
			} else if (!strcasecmp("connect-timeout", argv[i])) {
				if (++i < argc) {
					int tmp = atoi(argv[i]);

					if (tmp > 0) {
						options.connect_timeout = tmp;
					} else {
						switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Invalid connect-timeout!\n");
					}
				}
			} else if (!strcasecmp("timeout", argv[i])) {
				if (++i < argc) {
					int tmp = atoi(argv[i]);

					if (tmp > 0) {
						options.timeout = tmp;
					} else {
						switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Invalid timeout!\n");
					}
				}
			} else if (!strcasecmp("append_headers", argv[i])) {
				if (++i < argc) {
					if (ah_index == HTTP_MAX_APPEND_HEADERS) continue;
					append_headers[ah_index++] = argv[i];
				}
			} else if (!strcasecmp("insecure", argv[i])) {
				options.insecure = 1;
			} else if (!strcasecmp("secure", argv[i])) {
				options.insecure = 0;
			}  else if (!strcasecmp("proxy", argv[i])) {
				if (++i < argc) {
					options.proxy = argv[i];
				}
			}
		}

		http_data = do_lookup_url(pool, url, method, postdata, content_type, append_headers, &options);
		if (do_json) {
			stream->write_function(stream, "%s", print_json(pool, http_data));
		} else {
			if (do_headers) {
				slist = http_data->headers;
				while (slist) {
					stream->write_function(stream, "%s\n", slist->data);
					slist = slist->next;
				}
				stream->write_function(stream, "\n");
			}
			stream->write_function(stream, "%s", http_data->http_response ? http_data->http_response : "");
		}
	}
	switch_goto_status(SWITCH_STATUS_SUCCESS, done);

  usage:
	stream->write_function(stream, "-ERR\n%s\n", SYNTAX);
	switch_goto_status(status, done);

  done:
	if (http_data && http_data->headers) {
		switch_curl_slist_free_all(http_data->headers);
	}
	switch_safe_free(mydata);
	if (!session && pool) {
		switch_core_destroy_memory_pool(&pool);
	}
	return status;
}

static void do_config(switch_bool_t reload)
{
	globals.max_bytes = HTTP_DEFAULT_MAX_BYTES;

	switch_xml_config_parse_module_settings("curl.conf", reload, instructions);
}

static void event_handler(switch_event_t *event)
{
	do_config(SWITCH_TRUE);
}

/* Macro expands to: switch_status_t mod_cidlookup_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */
SWITCH_MODULE_LOAD_FUNCTION(mod_curl_load)
{
	switch_api_interface_t *api_interface;
	switch_application_interface_t *app_interface;

	memset(&globals, 0, sizeof(globals));

	if (switch_event_reserve_subclass(HTTP_SENDFILE_ACK_EVENT) != SWITCH_STATUS_SUCCESS) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!\n", HTTP_SENDFILE_ACK_EVENT);
		return SWITCH_STATUS_TERM;
	}

	if ((switch_event_bind_removable(modname, SWITCH_EVENT_RELOADXML, NULL, event_handler, NULL, &globals.node) != SWITCH_STATUS_SUCCESS)) {
		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't bind event!\n");
		switch_event_free_subclass(HTTP_SENDFILE_ACK_EVENT);
		return SWITCH_STATUS_TERM;
	}

	/* connect my internal structure to the blank pointer passed to me */
	*module_interface = switch_loadable_module_create_module_interface(pool, modname);

	globals.pool = pool;

	do_config(SWITCH_FALSE);

	SWITCH_ADD_API(api_interface, "curl", "curl API", curl_function, SYNTAX);
	SWITCH_ADD_APP(app_interface, "curl", "Perform a http request", "Perform a http request",
				   curl_app_function, SYNTAX, SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC);

	SWITCH_ADD_API(api_interface, "curl_sendfile", "curl_sendfile API", http_sendfile_function, HTTP_SENDFILE_SYNTAX);
	SWITCH_ADD_APP(app_interface, "curl_sendfile", "Send a file and some optional post variables via HTTP", "Send a file and some optional post variables via HTTP",
				   http_sendfile_app_function, HTTP_SENDFILE_APP_SYNTAX, SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC);
	/* indicate that the module should continue to be loaded */
	return SWITCH_STATUS_SUCCESS;
}

/*
  Called when the system shuts down
  Macro expands to: switch_status_t mod_cidlookup_shutdown() */
SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_curl_shutdown)
{

	switch_event_free_subclass(HTTP_SENDFILE_ACK_EVENT);

	switch_xml_config_cleanup(instructions);

	switch_event_unbind(&globals.node);

	/* Cleanup dynamically allocated config settings */
	return SWITCH_STATUS_SUCCESS;
}

/* For Emacs:
 * Local Variables:
 * mode:c
 * indent-tabs-mode:t
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet
 */
