diff --git a/src/mod/applications/mod_curl/mod_curl.c b/src/mod/applications/mod_curl/mod_curl.c index d9bb285522..b1a5cb0393 100644 --- a/src/mod/applications/mod_curl/mod_curl.c +++ b/src/mod/applications/mod_curl/mod_curl.c @@ -24,6 +24,7 @@ * Contributor(s): * * Rupa Schomaker + * Yossi Neiman * * mod_curl.c -- API for performing http queries * @@ -32,6 +33,7 @@ #include #include #include +#include /* Prototypes */ SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_curl_shutdown); @@ -45,10 +47,19 @@ SWITCH_MODULE_DEFINITION(mod_curl, mod_curl_load, mod_curl_shutdown, NULL); static char *SYNTAX = "curl url [headers|json|content-type ] [get|head|post [post_data]]"; +#define HTTP_SENDFILE_ACK_EVENT "curl_sendfile::ack" +#define HTTP_SENDFILE_RESPONSE_SIZE 32768 + static struct { switch_memory_pool_t *pool; } globals; +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; @@ -61,6 +72,29 @@ struct http_data_obj { }; 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; + 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 callback_obj { switch_memory_pool_t *pool; char *name; @@ -230,6 +264,426 @@ static char *print_json(switch_memory_pool_t *pool, http_data_t *http_data) 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) +{ + http_data->curl_handle = curl_easy_init(); + + if (!strncasecmp(http_data->url, "https", 5)) + { + 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(uint8_t 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) + 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 " [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: \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 && http_data->headers) + { + switch_curl_slist_free_all(http_data->headers); + } + + return; +} + +#define HTTP_SENDFILE_SYNTAX " [nopost|postparam1=foo&postparam2=bar... [event|stream|both|none [identifier ]]]" +SWITCH_STANDARD_API(http_sendfile_function) +{ + switch_status_t status; + 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; @@ -430,7 +884,10 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_curl_load) 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; }