diff --git a/build/modules.conf.in b/build/modules.conf.in
index 8682fb52aa..7b4d4ee04f 100644
--- a/build/modules.conf.in
+++ b/build/modules.conf.in
@@ -44,6 +44,7 @@ applications/mod_valet_parking
#applications/mod_abstraction
#applications/mod_esl
applications/mod_sms
+applications/mod_httapi
codecs/mod_g723_1
codecs/mod_amr
#codecs/mod_amrwb
diff --git a/conf/autoload_configs/httapi.conf.xml b/conf/autoload_configs/httapi.conf.xml
new file mode 100644
index 0000000000..22f23d1f8f
--- /dev/null
+++ b/conf/autoload_configs/httapi.conf.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/conf/autoload_configs/modules.conf.xml b/conf/autoload_configs/modules.conf.xml
index de4bc40897..e4b8422712 100644
--- a/conf/autoload_configs/modules.conf.xml
+++ b/conf/autoload_configs/modules.conf.xml
@@ -62,6 +62,7 @@
+
diff --git a/src/mod/applications/mod_httapi/examples/perl/dialer.cgi b/src/mod/applications/mod_httapi/examples/perl/dialer.cgi
new file mode 100755
index 0000000000..ce00289013
--- /dev/null
+++ b/src/mod/applications/mod_httapi/examples/perl/dialer.cgi
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+# Object initialization:
+use XML::Simple;
+use CGI;
+use Data::Dumper;
+use XML::Writer;
+
+my $q = CGI->new;
+my $exiting = $q->param("exiting");
+
+if ($exiting) {
+ print $q->header(-type => "text/plain");
+ print "OK";
+ exit();
+}
+
+print $q->header(-type => "text/xml");
+
+
+my $writer = new XML::Writer(OUTPUT => STDOUT, DATA_MODE => 1);
+
+$writer->startTag('document', type => 'xml/freeswitch-httapi');
+
+$writer->startTag('work');
+$writer->emptyTag('pause', milliseconds => "1500");
+$writer->startTag('playback',
+ name => digits,
+ file => "http://sidious.freeswitch.org/sounds/exten.wav",
+ 'error-file' => "http://sidious.freeswitch.org/sounds/invalid.wav",
+ 'input-timeout' => "5000",
+ action => "dial:default:XML");
+
+$writer->dataElement("bind", "~\\d+\#", strip => "#");
+$writer->endTag('playback');
+$writer->endTag('work');
+
+
+$writer->endTag('document');
+$writer->end();
+
diff --git a/src/mod/applications/mod_httapi/examples/perl/ext_pin.cgi b/src/mod/applications/mod_httapi/examples/perl/ext_pin.cgi
new file mode 100755
index 0000000000..0ffbc3fc43
--- /dev/null
+++ b/src/mod/applications/mod_httapi/examples/perl/ext_pin.cgi
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+# Object initialization:
+use XML::Simple;
+use CGI;
+use Data::Dumper;
+use XML::Writer;
+
+my $q = CGI->new;
+
+my $exten = $q->param("exten");
+my $pin = $q->param("pin");
+my $exiting = $q->param("exiting");
+
+if ($exiting) {
+ print $q->header(-type => "text/plain");
+ print "OK";
+ exit();
+ }
+
+print $q->header(-type => "text/xml");
+
+my $writer = new XML::Writer(OUTPUT => STDOUT, DATA_MODE => 1);
+
+$writer->startTag('document', type => 'xml/freeswitch-httapi');
+
+$writer->startTag('params');
+if ($exten) {
+ $writer->dataElement("exten", $exten);
+}
+if ($pin) {
+ $writer->dataElement("exten", $pin);
+}
+$writer->endTag('params');
+
+if ($exten && $pin) {
+ $writer->startTag('work');
+ $writer->dataElement("playback", "http://sidious.freeswitch.org/sounds/ext_num.wav");
+ $writer->dataElement("say", $exten, language => "en", type => "name_spelled", method => "pronounced");
+ $writer->emptyTag('pause', milliseconds => "1500");
+ $writer->dataElement("say", $pin, language => "en", type => "name_spelled", method => "pronounced");
+ $writer->emptyTag('hangup');
+ $writer->endTag('work');
+} elsif ($exten) {
+ $writer->startTag('work');
+ $writer->startTag('playback',
+ name => "pin",
+ file => "http://sidious.freeswitch.org/sounds/pin.wav",
+ 'error-file' => "http://sidious.freeswitch.org/sounds/bad-pin.wav",
+ 'input-timeout' => "5000");
+
+
+ $writer->dataElement("bind", "~\\d+\#", strip => "#");
+ $writer->endTag('playback');
+ $writer->endTag('work');
+} else {
+ $writer->startTag('work');
+ $writer->startTag('playback',
+ name => "exten",
+ file => "http://sidious.freeswitch.org/sounds/exten.wav",
+ 'error-file' => "http://sidious.freeswitch.org/sounds/invalid.wav",
+ 'input-timeout' => "5000");
+
+ $writer->dataElement("bind", "~\\d+\#", strip => "#");
+ $writer->endTag('playback');
+ $writer->endTag('work');
+}
+
+$writer->endTag('document');
+$writer->end();
+
diff --git a/src/mod/applications/mod_httapi/examples/perl/record_name.cgi b/src/mod/applications/mod_httapi/examples/perl/record_name.cgi
new file mode 100755
index 0000000000..ca5b7bac01
--- /dev/null
+++ b/src/mod/applications/mod_httapi/examples/perl/record_name.cgi
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+# Object initialization:
+use XML::Simple;
+use CGI;
+use Data::Dumper;
+use XML::Writer;
+
+my $q = CGI->new;
+my $exiting = $q->param("exiting");
+
+my $file = $q->upload("recorded_file");
+
+if ($file) {
+ open(O, ">/tmp/recording.wav");
+ while(<$file>) {
+ print O $_;
+ }
+ close O;
+
+ print $q->header(-type => "text/plain");
+ print "OK\n";
+ exit();
+}
+
+if ($exiting) {
+ print $q->header(-type => "text/plain");
+ print "OK";
+ exit();
+}
+
+
+print $q->header(-type => "text/xml");
+
+my $writer = new XML::Writer(OUTPUT => STDOUT, DATA_MODE => 1);
+
+$writer->startTag('document', type => 'xml/freeswitch-httapi');
+
+$writer->startTag('work');
+$writer->emptyTag('pause', milliseconds => "1500");
+$writer->emptyTag('playback', file => "http://sidious.freeswitch.org/eg/ivr-say_name.wav");
+$writer->startTag('record',
+ name => "recorded_file",
+ file => "recording.wav",
+ 'error-file' => "http://sidious.freeswitch.org/sounds/invalid.wav",
+ 'input-timeout' => "5000",
+ 'beep-file', => "tone_stream://%(1000,0,460)");
+
+
+$writer->dataElement("bind", "~\\d+\#", strip => "#");
+$writer->endTag('record');
+
+$writer->endTag('work');
+
+
+$writer->endTag('document');
+$writer->end();
+
diff --git a/src/mod/applications/mod_httapi/examples/perl/speech.cgi b/src/mod/applications/mod_httapi/examples/perl/speech.cgi
new file mode 100755
index 0000000000..9ede418dc1
--- /dev/null
+++ b/src/mod/applications/mod_httapi/examples/perl/speech.cgi
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+
+# Object initialization:
+use XML::Simple;
+use CGI;
+use Data::Dumper;
+use XML::Writer;
+
+my $q = CGI->new;
+
+my $result = $q->param("result");
+my $type = $q->param("input_type");
+my $exiting = $q->param("exiting");
+
+if ($exiting) {
+ print $q->header(-type => "text/plain");
+ print "OK";
+ exit();
+}
+
+print $q->header(-type => "text/xml");
+
+
+my $writer = new XML::Writer(OUTPUT => STDOUT, DATA_MODE => 1);
+
+$writer->startTag('document', type => 'xml/freeswitch-httapi');
+
+if ($result) {
+ $writer->startTag('work');
+
+ if ($type eq "dtmf") {
+ $writer->dataElement("say", $result, language => "en", type => "name_spelled", method => "pronounced");
+ }
+
+ $writer->dataElement("log", $result, level => "crit");
+
+ $writer->emptyTag('hangup');
+ $writer->endTag('work');
+} else {
+
+ $writer->startTag('work');
+ $writer->emptyTag('pause', milliseconds => "1500");
+ $writer->startTag('playback',
+ name => "result",
+ 'asr-engine' => "pocketsphinx",
+ 'asr-grammar' => "pizza_yesno",
+ file => "http://sidious.freeswitch.org/sounds/ConfirmDelivery.wav",
+ 'error-file' => "http://sidious.freeswitch.org/sounds/invalid.wav"
+ );
+
+ $writer->dataElement("bind", "~\\d+\#", strip => "#");
+ #$writer->dataElement("bind", "1");
+ #$writer->dataElement("bind", "2");
+ $writer->endTag('playback');
+
+ $writer->endTag('work');
+}
+
+$writer->endTag('document');
+$writer->end();
+
diff --git a/src/mod/applications/mod_httapi/httapi.conf.xml b/src/mod/applications/mod_httapi/httapi.conf.xml
new file mode 100644
index 0000000000..22f23d1f8f
--- /dev/null
+++ b/src/mod/applications/mod_httapi/httapi.conf.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mod/applications/mod_httapi/mod_httapi.2008.vcproj b/src/mod/applications/mod_httapi/mod_httapi.2008.vcproj
new file mode 100644
index 0000000000..775ce2b9cd
--- /dev/null
+++ b/src/mod/applications/mod_httapi/mod_httapi.2008.vcproj
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mod/applications/mod_httapi/mod_httapi.2010.vcxproj b/src/mod/applications/mod_httapi/mod_httapi.2010.vcxproj
new file mode 100644
index 0000000000..c1782544c7
--- /dev/null
+++ b/src/mod/applications/mod_httapi/mod_httapi.2010.vcxproj
@@ -0,0 +1,135 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ Win32
+
+
+ Release
+ x64
+
+
+
+ mod_httapi
+ {11C9BC3D-45E9-46E3-BE84-B8CEE4685E39}
+ mod_httapi
+ Win32Proj
+
+
+
+ DynamicLibrary
+ MultiByte
+
+
+ DynamicLibrary
+ MultiByte
+
+
+ DynamicLibrary
+ MultiByte
+
+
+ DynamicLibrary
+ MultiByte
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ProjectFileVersion>10.0.30319.1
+ $(Platform)\$(Configuration)\
+ $(Platform)\$(Configuration)\
+ $(Platform)\$(Configuration)\
+ $(Platform)\$(Configuration)\
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+ X64
+
+
+
+
+
+
+ false
+
+
+ MachineX64
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+ X64
+
+
+
+
+
+
+ false
+
+
+ MachineX64
+
+
+
+
+
+
+
+ {202d7a4e-760d-4d0e-afa1-d7459ced30ff}
+ false
+
+
+
+
+
+
diff --git a/src/mod/applications/mod_httapi/mod_httapi.c b/src/mod/applications/mod_httapi/mod_httapi.c
new file mode 100644
index 0000000000..989eb29832
--- /dev/null
+++ b/src/mod/applications/mod_httapi/mod_httapi.c
@@ -0,0 +1,2297 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2005-2011, Anthony Minessale II
+ *
+ * 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
+ * Portions created by the Initial Developer are Copyright (C)
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Anthony Minessale II
+ *
+ * mod_httapi.c -- HT-TAPI Hypertext Telephony API
+ *
+ */
+#include
+#include
+
+SWITCH_MODULE_LOAD_FUNCTION(mod_httapi_load);
+SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_httapi_shutdown);
+SWITCH_MODULE_DEFINITION(mod_httapi, mod_httapi_load, mod_httapi_shutdown, NULL);
+
+typedef struct profile_perms_s {
+ switch_byte_t set_params;
+ switch_byte_t set_vars;
+ switch_byte_t extended_data;
+ switch_byte_t execute_apps;
+ switch_byte_t expand_vars_in_tag_body;
+ struct {
+ switch_byte_t enabled;
+ switch_byte_t set_context;
+ switch_byte_t set_dp;
+ switch_byte_t set_cid_name;
+ switch_byte_t set_cid_number;
+ switch_byte_t full_originate;
+ } dial;
+ struct {
+ switch_byte_t enabled;
+ switch_byte_t set_profile;
+ } conference;
+} profile_perms_t;
+
+struct client_s;
+
+typedef struct action_binding_s {
+ char *realm;
+ char *input;
+ char *action;
+ char *error_file;
+ char *match_digits;
+ char *strip;
+ struct client_s *client;
+ struct action_binding_s *parent;
+} action_binding_t;
+
+typedef struct client_profile_s {
+ char *name;
+ char *method;
+ char *url;
+ char *cred;
+ char *bind_local;
+ int disable100continue;
+ uint32_t enable_cacert_check;
+ char *ssl_cert_file;
+ char *ssl_key_file;
+ char *ssl_key_password;
+ char *ssl_version;
+ char *ssl_cacert_file;
+ uint32_t enable_ssl_verifyhost;
+ char *cookie_file;
+ switch_hash_t *vars_map;
+ int auth_scheme;
+ int timeout;
+ profile_perms_t perms;
+
+ struct {
+ char *use_profile;
+ } conference_params;
+
+ struct {
+ char *context;
+ char *dp;
+ } dial_params;
+
+} client_profile_t;
+
+#define HTTAPI_MAX_API_BYTES 1024 * 1024
+#define HTTAPI_MAX_FILE_BYTES 1024 * 1024 * 100
+
+typedef struct client_s {
+ switch_memory_pool_t *pool;
+ int fd;
+ switch_buffer_t *buffer;
+ switch_size_t bytes;
+ switch_size_t max_bytes;
+ switch_event_t *headers;
+ switch_event_t *params;
+ switch_event_t *one_time_params;
+ client_profile_t *profile;
+ switch_core_session_t *session;
+ switch_channel_t *channel;
+ action_binding_t *matching_action_binding;
+ action_binding_t *no_matching_action_binding;
+ struct {
+ char *action;
+ char *name;
+ char *file;
+ } record;
+
+ int err;
+ long code;
+} client_t;
+
+typedef struct hash_node {
+ switch_hash_t *hash;
+ struct hash_node *next;
+} hash_node_t;
+
+static struct {
+ switch_memory_pool_t *pool;
+ hash_node_t *hash_root;
+ hash_node_t *hash_tail;
+ switch_hash_t *profile_hash;
+ switch_hash_t *parse_hash;
+ char cache_path[128];
+ int debug;
+ int not_found_expires;
+ int cache_ttl;
+} globals;
+
+
+/* for apr_pstrcat */
+#define DEFAULT_PREBUFFER_SIZE 1024 * 64
+
+struct http_file_source;
+
+struct http_file_context {
+ int samples;
+ switch_file_handle_t fh;
+ char *cache_file;
+ char *meta_file;
+ char *lock_file;
+ char *metadata;
+ time_t expires;
+ switch_file_t *lock_fd;
+ switch_memory_pool_t *pool;
+};
+
+typedef struct http_file_context http_file_context_t;
+typedef switch_status_t (*tag_parse_t)(const char *tag_name, client_t *client, switch_xml_t tag, const char *body);
+
+static void bind_parser(const char *tag_name, tag_parse_t handler)
+{
+ switch_core_hash_insert(globals.parse_hash, tag_name, (void *)(intptr_t)handler);
+}
+
+
+#define HTTAPI_SYNTAX "[debug_on|debug_off]"
+SWITCH_STANDARD_API(httapi_api_function)
+{
+ if (session) {
+ return SWITCH_STATUS_FALSE;
+ }
+
+ if (zstr(cmd)) {
+ goto usage;
+ }
+
+ if (!strcasecmp(cmd, "debug_on")) {
+ globals.debug = 1;
+ } else if (!strcasecmp(cmd, "debug_off")) {
+ globals.debug = 0;
+ } else {
+ goto usage;
+ }
+
+ stream->write_function(stream, "OK\n");
+ return SWITCH_STATUS_SUCCESS;
+
+ usage:
+ stream->write_function(stream, "USAGE: %s\n", HTTAPI_SYNTAX);
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t digit_action_callback(switch_ivr_dmachine_match_t *match)
+{
+ action_binding_t *action_binding = (action_binding_t *) match->user_data;
+
+ action_binding->client->matching_action_binding = action_binding;
+ action_binding->match_digits = switch_core_strdup(action_binding->client->pool, match->match_digits);
+
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t digit_nomatch_action_callback(switch_ivr_dmachine_match_t *match)
+{
+ action_binding_t *action_binding = (action_binding_t *) match->user_data;
+
+ action_binding->client->no_matching_action_binding = action_binding;
+
+ return SWITCH_STATUS_BREAK;
+}
+
+static switch_status_t parse_break(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ return SWITCH_STATUS_FALSE;
+}
+
+
+static void console_log(const char *level_str, const char *msg)
+{
+ switch_log_level_t level = SWITCH_LOG_DEBUG;
+ if (level_str) {
+ level = switch_log_str2level(level_str);
+ if (level == SWITCH_LOG_INVALID) {
+ level = SWITCH_LOG_DEBUG;
+ }
+ }
+ switch_log_printf(SWITCH_CHANNEL_LOG, level, "%s", switch_str_nil(msg));
+}
+
+static void console_clean_log(const char *level_str, const char *msg)
+{
+ switch_log_level_t level = SWITCH_LOG_DEBUG;
+ if (level_str) {
+ level = switch_log_str2level(level_str);
+ if (level == SWITCH_LOG_INVALID) {
+ level = SWITCH_LOG_DEBUG;
+ }
+ }
+ switch_log_printf(SWITCH_CHANNEL_LOG_CLEAN, level, "%s", switch_str_nil(msg));
+}
+
+static switch_status_t parse_log(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ const char *level = switch_xml_attr(tag, "level");
+ const char *clean = switch_xml_attr(tag, "clean");
+
+ if (switch_true(clean)) {
+ console_clean_log(level, body);
+ } else {
+ console_log(level, body);
+ }
+
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t parse_playback(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ const char *file = switch_xml_attr(tag, "file");
+ const char *name = switch_xml_attr(tag, "name");
+ const char *error_file = switch_xml_attr(tag, "error-file");
+ const char *action = switch_xml_attr(tag, "action");
+ const char *digit_timeout_ = switch_xml_attr(tag, "digit-timeout");
+ const char *input_timeout_ = switch_xml_attr(tag, "input-timeout");
+ const char *tts_engine = NULL;
+ const char *tts_voice = NULL;
+ char *loops_ = (char *) switch_xml_attr(tag, "loops");
+ int loops = 0;
+ switch_status_t status = SWITCH_STATUS_FALSE;
+ switch_ivr_dmachine_t *dmachine = NULL;
+ switch_input_args_t *args = NULL, myargs = { 0 }, nullargs = { 0 };
+ long digit_timeout = 1500;
+ long input_timeout = 5000;
+ long tmp;
+ switch_xml_t bind = NULL;
+ int submit = 0;
+ int input = 0;
+ int speak = 0, say = 0, pause = 0, play = 0, speech = 0;
+ char *sub_action = NULL;
+ action_binding_t *top_action_binding = NULL;
+ const char *say_lang = NULL;
+ const char *say_type = NULL;
+ const char *say_method = NULL;
+ const char *say_gender = NULL;
+ const char *sp_engine = NULL;
+ const char *sp_grammar = NULL;
+
+ if (!strcasecmp(tag_name, "say")) {
+ say_lang = switch_xml_attr(tag, "language");
+ say_type = switch_xml_attr(tag, "type");
+ say_method = switch_xml_attr(tag, "method");
+ say_gender = switch_xml_attr(tag, "gender");
+
+ if (zstr(say_lang) || zstr(say_type) || zstr(say_method) || zstr(body)) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "speak: missing required attributes or text! (language) (type) (method) \n");
+ return SWITCH_STATUS_FALSE;
+ }
+
+ say = 1;
+
+ } else if (!strcasecmp(tag_name, "speak")) {
+ tts_engine = switch_xml_attr(tag, "engine");
+ tts_voice = switch_xml_attr(tag, "voice");
+
+ if (zstr(tts_engine)) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "speak: missing engine attribute!\n");
+ return SWITCH_STATUS_FALSE;
+ }
+ speak = 1;
+ } else if (!strcasecmp(tag_name, "pause")) {
+ const char *ms_ = switch_xml_attr(tag, "milliseconds");
+ pause = atoi(ms_);
+ if (pause < 0) pause = 1000;
+ } else if (!strcasecmp(tag_name, "playback")) {
+ sp_engine = switch_xml_attr(tag, "asr-engine");
+ sp_grammar = switch_xml_attr(tag, "asr-grammar");
+
+ if (sp_grammar && sp_engine) {
+ speech = 1;
+ } else {
+ play = 1;
+ }
+ }
+
+
+ if (zstr(name)) name = "dialed_digits";
+
+ if (loops_) {
+ loops = atoi(loops_);
+
+ if (loops < 0) {
+ loops = -1;
+ }
+ }
+
+ if (digit_timeout_) {
+ tmp = atol(digit_timeout_);
+
+ if (tmp > 0) {
+ digit_timeout = tmp;
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid digit timeout [%s]\n", digit_timeout_);
+ }
+ }
+
+ if (input_timeout_) {
+ tmp = atol(input_timeout_);
+
+ if (tmp > 0) {
+ input_timeout = tmp;
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid input timeout [%s]\n", input_timeout_);
+ }
+ }
+
+ if ((bind = switch_xml_child(tag, "bind"))) {
+ action_binding_t *action_binding;
+ const char *realm = "default";
+
+
+ input++;
+
+ top_action_binding = switch_core_session_alloc(client->session, sizeof(*action_binding));
+ top_action_binding->client = client;
+ top_action_binding->action = (char *) action;
+ top_action_binding->error_file = (char *)error_file;
+
+ switch_ivr_dmachine_create(&dmachine, "HTTAPI", NULL, digit_timeout, 0,
+ NULL, digit_nomatch_action_callback, top_action_binding);
+
+ while(bind) {
+ action_binding = switch_core_session_alloc(client->session, sizeof(*action_binding));
+ action_binding->realm = (char *) realm;
+ action_binding->input = bind->txt;
+ action_binding->client = client;
+ action_binding->action = (char *) switch_xml_attr(bind, "action");
+ action_binding->strip = (char *) switch_xml_attr(bind, "strip");
+ action_binding->error_file = (char *) error_file;
+ action_binding->parent = top_action_binding;
+
+ switch_ivr_dmachine_bind(dmachine, action_binding->realm, action_binding->input, 0, digit_action_callback, action_binding);
+ bind = bind->next;
+ }
+
+ switch_ivr_dmachine_set_realm(dmachine, realm);
+ myargs.dmachine = dmachine;
+ args = &myargs;
+ }
+
+ if (zstr(file) && !input) {
+ file = body;
+ }
+
+ if (zstr(file) && !(speak || say || pause)) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "missing file attribute!\n");
+ switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
+ return SWITCH_STATUS_FALSE;
+ }
+
+ do {
+ if (speak) {
+ status = switch_ivr_speak_text(client->session, tts_engine, tts_voice, (char *)body, args);
+ } else if (say) {
+ status = switch_ivr_say(client->session, body, say_lang, say_type, say_method, say_gender, args);
+ } else if (play) {
+ status = switch_ivr_play_file(client->session, NULL, file, args);
+ } else if (speech) {
+ char *result = NULL;
+
+ status = switch_ivr_play_and_detect_speech(client->session, file, sp_engine, sp_grammar, &result, input_timeout, args);
+
+ if (!zstr(result)) {
+ switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, name, result);
+ switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "input_type", "detected_speech");
+ submit = 1;
+ break;
+ }
+
+ input_timeout = 0;
+ } else if (pause) {
+ input_timeout = pause;
+ status = SWITCH_STATUS_SUCCESS;
+ }
+
+ if (!switch_channel_ready(client->channel)) {
+ break;
+ }
+
+ if (!input && !pause) {
+ status = switch_channel_ready(client->channel) ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE;
+ submit = 1;
+ break;
+ }
+
+ if (input_timeout && status == SWITCH_STATUS_SUCCESS) {
+ if ((status = switch_ivr_sleep(client->session, input_timeout, SWITCH_TRUE, args)) == SWITCH_STATUS_SUCCESS) {
+ status = SWITCH_STATUS_BREAK;
+ }
+ }
+
+ if (status == SWITCH_STATUS_BREAK) {
+ if (error_file) {
+ switch_ivr_play_file(client->session, NULL, error_file, &nullargs);
+ status = SWITCH_STATUS_SUCCESS;
+ }
+ } else if (status == SWITCH_STATUS_FOUND) {
+ status = SWITCH_STATUS_SUCCESS;
+ submit = 1;
+ break;
+ } else if (status != SWITCH_STATUS_SUCCESS) {
+ break;
+ }
+ } while (loops-- > 0);
+
+
+ if (submit) {
+ if (client->matching_action_binding) {
+ if (client->matching_action_binding->match_digits) {
+
+ if (client->matching_action_binding->strip) {
+ char *pp, *p;
+
+ for(pp = client->matching_action_binding->strip; pp && *pp; pp++) {
+ if ((p = strchr(client->matching_action_binding->match_digits, *pp))) {
+ *p = '\0';
+ }
+ }
+ }
+ switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, name, client->matching_action_binding->match_digits);
+ switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "input_type", "dtmf");
+ }
+
+ if (client->matching_action_binding->action) {
+ sub_action = client->matching_action_binding->action;
+ } else if (client->matching_action_binding->parent && client->matching_action_binding->parent->action) {
+ sub_action = client->matching_action_binding->parent->action;
+ }
+ }
+
+ if (!sub_action && top_action_binding && top_action_binding->action) {
+ sub_action = top_action_binding->action;
+ }
+
+ if (sub_action && client->matching_action_binding && client->matching_action_binding->match_digits) {
+ if (!strncasecmp(sub_action, "dial:", 5)) {
+ if (client->profile->perms.dial.set_context) {
+ char *context = switch_core_session_strdup(client->session, sub_action + 5);
+ char *dp;
+
+ if ((dp = strchr(context, ':'))) {
+ *dp++ = '\0';
+ if (!client->profile->perms.dial.set_dp) {
+ dp = NULL;
+ }
+ }
+
+ switch_ivr_session_transfer(client->session, client->matching_action_binding->match_digits, dp, context);
+ status = SWITCH_STATUS_FALSE;
+ }
+ } else {
+ switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, "url", sub_action);
+ }
+ }
+ }
+
+ if (dmachine) {
+ switch_ivr_dmachine_destroy(&dmachine);
+ }
+
+ return status;
+}
+
+static switch_status_t parse_conference(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ char *str;
+ char *dup, *p;
+ const char *profile_name = switch_xml_attr(tag, "profile");
+
+ if (!client->profile->perms.conference.enabled) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Permission Denied!\n");
+ switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
+ return SWITCH_STATUS_FALSE;
+ }
+
+ dup = switch_core_session_strdup(client->session, body);
+
+ if ((p = strchr(dup, '@'))) {
+ *p = '\0';
+ }
+
+ if (zstr(profile_name) || !client->profile->perms.conference.set_profile) {
+ profile_name = client->profile->conference_params.use_profile;
+ }
+
+ str = switch_core_session_sprintf(client->session, "%s@%s", dup, profile_name);
+ switch_core_session_execute_application(client->session, "conference", str);
+
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t parse_dial(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ const char *context = NULL;
+ const char *dp = NULL;
+ const char *cid_name = NULL;
+ const char *cid_number = NULL;
+
+ if (!client->profile->perms.dial.enabled) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Permission Denied!\n");
+ switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
+ return SWITCH_STATUS_FALSE;
+ }
+
+ if (client->profile->perms.dial.set_context) {
+ context = switch_xml_attr(tag, "context");
+ }
+
+ if (client->profile->perms.dial.set_dp) {
+ dp = switch_xml_attr(tag, "dialplan");
+ }
+
+ if (client->profile->perms.dial.set_cid_name) {
+ cid_name = switch_xml_attr(tag, "caller-id-name");
+ }
+
+ if (client->profile->perms.dial.set_cid_number) {
+ cid_number = switch_xml_attr(tag, "caller-id-number");
+ }
+
+ if (client->profile->perms.dial.full_originate && strchr(body, '/')) {
+ char *str;
+
+ if (!cid_name) {
+ cid_name = switch_channel_get_variable(client->channel, "caller_id_name");
+ }
+ if (!cid_number) {
+ cid_number = switch_channel_get_variable(client->channel, "caller_id_number");
+ }
+
+ str = switch_core_session_sprintf(client->session,
+ "{origination_caller_id_name='%s',origination_caller_id_number='%s'}%s", cid_name, cid_number, body);
+
+ switch_core_session_execute_application(client->session, "bridge", str);
+ } else {
+ switch_ivr_session_transfer(client->session, body, dp, context);
+ }
+
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t parse_sms(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ switch_event_t *event;
+ const char *from_proto = "httapi";
+ const char *to_proto = "sip";
+ const char *to = switch_xml_attr(tag, "to");
+
+ if (to && switch_event_create(&event, SWITCH_EVENT_MESSAGE) == SWITCH_STATUS_SUCCESS) {
+ switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "proto", from_proto);
+ switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "to_proto", to_proto);
+
+ switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "from", switch_channel_get_variable(client->channel, "caller_id_number"));
+ switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "to", to);
+ switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "type", "text/plain");
+
+ if (body) {
+ switch_event_add_body(event, "%s", body);
+ }
+
+ switch_core_chat_send(to_proto, event);
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing 'to' Attribute!\n");
+ return SWITCH_STATUS_FALSE;
+ }
+
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t parse_execute(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ const char *app_name = switch_xml_attr(tag, "application");
+
+ if (!client->profile->perms.execute_apps) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Permission Denied!\n");
+ switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
+ return SWITCH_STATUS_FALSE;
+ }
+
+ if (zstr(app_name)) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid app\n");
+ switch_channel_hangup(client->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
+ return SWITCH_STATUS_FALSE;
+ } else {
+ switch_core_session_execute_application(client->session, app_name, body);
+ }
+
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t parse_hangup(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ const char *cause_str = switch_xml_attr(tag, "cause");
+ switch_call_cause_t cause = SWITCH_CAUSE_NORMAL_CLEARING;
+
+ if (zstr(cause_str)) {
+ cause_str = body;
+ }
+
+ if (!zstr(cause_str)) {
+ cause = switch_channel_str2cause(cause_str);
+ }
+
+ switch_channel_hangup(client->channel, cause);
+
+ return SWITCH_STATUS_FALSE;
+}
+
+static switch_status_t parse_record_call(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ const char *limit_ = switch_xml_attr(tag, "limit");
+ const char *name = switch_xml_attr(tag, "name");
+ const char *action = switch_xml_attr(tag, "action");
+ int limit = 0;
+
+ if (client->record.file) {
+ return SWITCH_STATUS_SUCCESS;
+ }
+
+ if (zstr(name)) name = "recorded_file";
+
+ client->record.action = (char *) action;
+ client->record.name = (char *)name;
+ client->record.file = switch_core_session_sprintf(client->session, "%s%s%s.wav",
+ SWITCH_GLOBAL_dirs.temp_dir, SWITCH_PATH_SEPARATOR, switch_core_session_get_uuid(client->session));
+
+ if (limit_) {
+ limit = atoi(limit_);
+ if (limit < 0) limit = 0;
+ }
+
+
+ switch_ivr_record_session(client->session, client->record.file, limit, NULL);
+
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t parse_record(const char *tag_name, client_t *client, switch_xml_t tag, const char *body)
+{
+ const char *file = switch_xml_attr(tag, "file");
+ const char *name = switch_xml_attr(tag, "name");
+ const char *error_file = switch_xml_attr(tag, "error-file");
+ const char *beep_file = switch_xml_attr(tag, "beep-file");
+ const char *action = switch_xml_attr(tag, "action");
+ const char *sub_action = NULL;
+ const char *digit_timeout_ = switch_xml_attr(tag, "digit-timeout");
+ char *loops_ = (char *) switch_xml_attr(tag, "loops");
+ int loops = 0;
+ switch_status_t status = SWITCH_STATUS_FALSE;
+ switch_ivr_dmachine_t *dmachine = NULL;
+ switch_input_args_t *args = NULL, myargs = { 0 };
+ long digit_timeout = 1500;
+ long tmp;
+ int thresh = 200;
+ int silence_hits = 2;
+ int record_limit = 0;
+ char *tmp_record_path = NULL;
+ const char *v;
+ int rtmp;
+ char uuid_str[SWITCH_UUID_FORMATTED_LENGTH + 1];
+ char *fname = NULL;
+ char *p, *ext = "wav";
+ switch_xml_t bind;
+ action_binding_t *top_action_binding = NULL;
+
+ switch_uuid_str(uuid_str, sizeof(uuid_str));
+
+ if (zstr(name)) name = "attached_file";
+
+ if (zstr(file)) {
+ return SWITCH_STATUS_FALSE;
+ }
+
+ fname = switch_core_strdup(client->pool, file);
+
+ for(p = fname; p && *p; p++) {
+ if (*p == '/' || *p == '\\' || *p == '`') {
+ *p = '_';
+ }
+ }
+
+ if ((p = strrchr(fname, '.'))) {
+ *p++ = '\0';
+ ext = p;
+ }
+
+ for(p = fname; p && *p; p++) {
+ if (*p == '.') {
+ *p = '_';
+ }
+ }
+
+ tmp_record_path = switch_core_sprintf(client->pool, "%s%s%s_%s.%s",
+ SWITCH_GLOBAL_dirs.temp_dir, SWITCH_PATH_SEPARATOR, uuid_str, fname, ext);
+
+ if ((v = switch_xml_attr(tag, "limit"))) {
+ if ((rtmp = atoi(v)) > -1) {
+ record_limit = rtmp;
+ }
+ }
+
+ if ((v = switch_xml_attr(tag, "silence-hits"))) {
+ if ((rtmp = atoi(v)) > -1) {
+ silence_hits = rtmp;
+ }
+ }
+
+ if ((v = switch_xml_attr(tag, "threshold"))) {
+ if ((rtmp = atoi(v)) > -1) {
+ thresh = rtmp;
+ }
+ }
+
+ if (loops_) {
+ loops = atoi(loops_);
+
+ if (loops < 0) {
+ loops = -1;
+ }
+ }
+
+ if (digit_timeout_) {
+ tmp = atol(digit_timeout_);
+
+ if (tmp > 0) {
+ digit_timeout = tmp;
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid digit timeout [%s]\n", digit_timeout_);
+ }
+ }
+
+ if ((bind = switch_xml_child(tag, "bind"))) {
+ action_binding_t *action_binding;
+ const char *realm = "default";
+
+ top_action_binding = switch_core_session_alloc(client->session, sizeof(*action_binding));
+ top_action_binding->client = client;
+ top_action_binding->action = (char *) action;
+ top_action_binding->error_file = (char *)error_file;
+
+ switch_ivr_dmachine_create(&dmachine, "HTTAPI", NULL, digit_timeout, 0,
+ NULL, digit_nomatch_action_callback, top_action_binding);
+
+ while(bind) {
+ action_binding = switch_core_session_alloc(client->session, sizeof(*action_binding));
+ action_binding->realm = (char *) realm;
+ action_binding->input = bind->txt;
+ action_binding->client = client;
+ action_binding->action = (char *) switch_xml_attr(bind, "action");
+ action_binding->error_file = (char *) error_file;
+ action_binding->parent = top_action_binding;
+
+ switch_ivr_dmachine_bind(dmachine, action_binding->realm, action_binding->input, 0, digit_action_callback, action_binding);
+ bind = bind->next;
+ }
+
+ switch_ivr_dmachine_set_realm(dmachine, realm);
+ myargs.dmachine = dmachine;
+ args = &myargs;
+ }
+
+ if (beep_file) {
+ status = switch_ivr_play_file(client->session, NULL, beep_file, args);
+ }
+
+ if (!switch_channel_ready(client->channel)) {
+ goto end;
+ }
+
+ if (status == SWITCH_STATUS_SUCCESS) {
+ switch_file_handle_t fh = { 0 };
+ fh.thresh = thresh;
+ fh.silence_hits = silence_hits;
+
+ status = switch_ivr_record_file(client->session, &fh, tmp_record_path, args, record_limit);
+ }
+
+ if (client->matching_action_binding) {
+ if (client->matching_action_binding->action) {
+ sub_action = client->matching_action_binding->action;
+ } else if (client->matching_action_binding->parent && client->matching_action_binding->parent->action) {
+ sub_action = client->matching_action_binding->parent->action;
+ }
+ }
+
+ if (!sub_action && top_action_binding && top_action_binding->action) {
+ sub_action = top_action_binding->action;
+ }
+
+ if (sub_action) {
+ switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, "url", sub_action);
+ }
+
+ if (!zstr(tmp_record_path) && switch_file_exists(tmp_record_path, client->pool) == SWITCH_STATUS_SUCCESS) {
+ char *key = switch_core_sprintf(client->pool, "attach_file:%s:%s.%s", name, fname, ext);
+ switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, key, tmp_record_path);
+ status = SWITCH_STATUS_TERM;
+ }
+
+ end:
+
+ if (dmachine) {
+ switch_ivr_dmachine_destroy(&dmachine);
+ }
+
+ return status;
+}
+
+
+static switch_status_t parse_xml(client_t *client)
+{
+ switch_status_t status = SWITCH_STATUS_FALSE;
+ const void *bdata;
+ switch_size_t len;
+
+ if ((len = switch_buffer_peek_zerocopy(client->buffer, &bdata)) && switch_buffer_len(client->buffer) > len) {
+ switch_xml_t xml, tag, category;
+
+ if (globals.debug) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Debugging Return Data:\n%s\n", (char *)bdata);
+ }
+
+ if ((xml = switch_xml_parse_str((char *)bdata, len))) {
+
+ if (client->profile->perms.set_params) {
+ if ((category = switch_xml_child(xml, "params"))) {
+ tag = category->child;
+
+ while(tag) {
+ if (!zstr(tag->name)) {
+ char *val = tag->txt;
+ if (zstr(val)) {
+ val = NULL;
+ }
+ switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, tag->name, val);
+ }
+ tag = tag->sibling;
+ }
+ }
+ }
+
+ if (client->profile->perms.set_vars) {
+ if ((category = switch_xml_child(xml, "variables"))) {
+ tag = category->child;
+
+ while(tag) {
+ if (!zstr(tag->name)) {
+ char *val = tag->txt;
+ if (zstr(val)) {
+ val = NULL;
+ }
+ switch_channel_set_variable(client->channel, tag->name, val);
+ }
+ tag = tag->sibling;
+ }
+ }
+ }
+
+ if ((category = switch_xml_child(xml, "work"))) {
+ tag = category->child;
+ status = SWITCH_STATUS_SUCCESS;
+
+ while(status == SWITCH_STATUS_SUCCESS && tag) {
+ if (!zstr(tag->name)) {
+ tag_parse_t handler;
+
+ if ((handler = (tag_parse_t)(intptr_t) switch_core_hash_find(globals.parse_hash, tag->name))) {
+ char *expanded = tag->txt;
+ switch_event_t *templ_data;
+
+ if (tag->txt && client->profile->perms.expand_vars_in_tag_body) {
+ switch_channel_get_variables(client->channel, &templ_data);
+ switch_event_merge(templ_data, client->params);
+ expanded = switch_event_expand_headers(templ_data, tag->txt);
+ switch_event_destroy(&templ_data);
+ }
+
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Process Tag: [%s]\n", tag->name);
+ handler(tag->name, client, tag, expanded);
+
+ if (expanded && expanded != tag->txt) {
+ free(expanded);
+ }
+
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unsupported Tag: [%s]\n", tag->name);
+ status = SWITCH_STATUS_FALSE;
+ }
+
+ }
+ tag = tag->ordered;
+ }
+ } else {
+ status = SWITCH_STATUS_FALSE;
+ }
+
+ switch_xml_free(xml);
+ }
+ }
+
+ return status;
+}
+
+
+static size_t get_header_callback(void *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ size_t realsize = (size * nmemb);
+ char *val, *header = NULL;
+ client_t *client = userdata;
+
+ /* validate length... Apache and IIS won't send a header larger than 16 KB */
+ if (realsize == 0 || realsize > 1024 * 16) {
+ return realsize;
+ }
+
+ /* get the header, adding NULL terminator if there isn't one */
+ switch_zmalloc(header, realsize + 1);
+ strncpy(header, (char *)ptr, realsize);
+
+ if ((val = strchr(header, ':'))) {
+ char *cr;
+ *val++ = '\0';
+ while(*val == ' ') val++;
+
+ if ((cr = strchr(val, '\r')) || (cr = strchr(val, '\r'))) {
+ *cr = '\0';
+ }
+
+ switch_event_add_header_string(client->headers, SWITCH_STACK_BOTTOM, header, val);
+ }
+
+ switch_safe_free(header);
+ return realsize;
+}
+
+
+static size_t file_callback(void *ptr, size_t size, size_t nmemb, void *data)
+{
+ register unsigned int realsize = (unsigned int) (size * nmemb);
+ client_t *client = data;
+
+ client->bytes += realsize;
+
+ if (client->bytes > client->max_bytes) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Oversized file detected [%d bytes]\n", (int) client->bytes);
+ client->err = 1;
+ return 0;
+ }
+
+ switch_buffer_write(client->buffer, ptr, realsize);
+
+ return realsize;
+}
+
+static void client_destroy(client_t **client)
+{
+ if (client && *client) {
+ switch_memory_pool_t *pool;
+
+ switch_event_destroy(&(*client)->headers);
+ switch_event_destroy(&(*client)->params);
+ switch_event_destroy(&(*client)->one_time_params);
+ switch_buffer_destroy(&(*client)->buffer);
+
+ pool = (*client)->pool;
+ switch_core_destroy_memory_pool(&pool);
+ }
+}
+
+static void client_reset(client_t *client)
+{
+ if (client->headers) {
+ switch_event_destroy(&client->headers);
+ }
+
+ switch_event_destroy(&client->one_time_params);
+ switch_event_create(&client->one_time_params, SWITCH_EVENT_CLONE);
+ client->one_time_params->flags |= EF_UNIQ_HEADERS;
+
+ switch_event_create(&client->headers, SWITCH_EVENT_CLONE);
+
+
+ switch_buffer_zero(client->buffer);
+
+ client->code = 0;
+ client->err = 0;
+ client->matching_action_binding = NULL;
+ client->no_matching_action_binding = NULL;
+}
+
+static client_t *client_create(switch_core_session_t *session, const char *profile_name, switch_event_t **params)
+{
+ client_t *client;
+ switch_memory_pool_t *pool;
+ client_profile_t *profile;
+
+ if (zstr(profile_name)) {
+ profile_name = "default";
+ }
+
+ profile = (client_profile_t *) switch_core_hash_find(globals.profile_hash, profile_name);
+
+ if (!profile) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot find profile [%s]\n", profile_name);
+ return NULL;
+ }
+
+ switch_core_new_memory_pool(&pool);
+ client = switch_core_alloc(pool, sizeof(*client));
+ client->pool = pool;
+
+ switch_event_create(&client->headers, SWITCH_EVENT_CLONE);
+
+ client->session = session;
+ client->channel = switch_core_session_get_channel(session);
+
+
+ client->profile = profile;
+
+ client->max_bytes = HTTAPI_MAX_API_BYTES;
+
+ switch_buffer_create_dynamic(&client->buffer, 1024, 1024, 0);
+
+ if (params && *params) {
+ client->params = *params;
+ *params = NULL;
+ } else {
+ switch_event_create(&client->params, SWITCH_EVENT_CLONE);
+ client->params->flags |= EF_UNIQ_HEADERS;
+ }
+
+ switch_event_create(&client->one_time_params, SWITCH_EVENT_CLONE);
+ client->one_time_params->flags |= EF_UNIQ_HEADERS;
+
+ switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, "hostname", switch_core_get_switchname());
+
+ return client;
+}
+
+
+static void cleanup_attachments(client_t *client)
+{
+ switch_event_header_t *hp;
+
+ for (hp = client->params->headers; hp; hp = hp->next) {
+ if (!strncasecmp(hp->name, "attach_file:", 12)) {
+ if (switch_file_exists(hp->value, client->pool)) {
+ unlink(hp->value);
+ }
+ }
+ }
+}
+
+static switch_status_t process_form_post_params(client_t *client, switch_CURL *curl_handle, struct curl_httppost **formpostp)
+{
+
+ struct curl_httppost *formpost=NULL;
+ struct curl_httppost *lastptr=NULL;
+ switch_event_header_t *hp;
+ int go = 0;
+
+ for (hp = client->params->headers; hp; hp = hp->next) {
+ if (!strncasecmp(hp->name, "attach_file:", 12)) {
+ go = 1;
+ break;
+ }
+ }
+
+ if (!go) {
+ return SWITCH_STATUS_FALSE;
+ }
+
+ for (hp = client->params->headers; hp; hp = hp->next) {
+
+ if (!strncasecmp(hp->name, "attach_file:", 12)) {
+ char *pname = switch_core_strdup(client->pool, hp->name + 12);
+ char *fname = strchr(pname, ':');
+
+ if (fname && pname) {
+ *fname++ = '\0';
+
+ switch_curl_formadd(&formpost,
+ &lastptr,
+ CURLFORM_COPYNAME, pname,
+ CURLFORM_FILENAME, fname,
+ CURLFORM_FILE, hp->value,
+ CURLFORM_END);
+ }
+
+ } else {
+ switch_curl_formadd(&formpost,
+ &lastptr,
+ CURLFORM_COPYNAME, hp->name,
+ CURLFORM_COPYCONTENTS, hp->value,
+ CURLFORM_END);
+
+ }
+ }
+
+ *formpostp = formpost;
+
+ return SWITCH_STATUS_SUCCESS;
+
+}
+
+static switch_status_t httapi_sync(client_t *client)
+
+{
+ switch_CURL *curl_handle = NULL;
+ char *data = NULL;
+ switch_curl_slist_t *headers = NULL;
+ char *url = NULL;
+ char *dynamic_url = NULL;
+ const char *session_id = NULL;
+ switch_status_t status = SWITCH_STATUS_FALSE;
+ int get_style_method = 0;
+ char *method = NULL;
+ struct curl_httppost *formpost=NULL;
+ switch_event_t *save_params = NULL;
+
+ if (client->one_time_params && client->one_time_params->headers) {
+ save_params = client->params;
+ switch_event_dup(&client->params, save_params);
+ switch_event_merge(client->params, client->one_time_params);
+ switch_event_destroy(&client->one_time_params);
+ switch_event_create(&client->one_time_params, SWITCH_EVENT_CLONE);
+ client->one_time_params->flags |= EF_UNIQ_HEADERS;
+ }
+
+ if (!(session_id = switch_event_get_header(client->params, "HTTAPI_SESSION_ID"))) {
+ if (!(session_id = switch_channel_get_variable(client->channel, "HTTAPI_SESSION_ID"))) {
+ session_id = switch_core_session_get_uuid(client->session);
+ }
+ }
+
+ if (client->code || client->err) {
+ client_reset(client);
+ }
+
+
+ if (!(method = switch_event_get_header(client->params, "method"))) {
+ method = client->profile->method;
+ }
+
+ if (!(url = switch_event_get_header(client->params, "url"))) {
+ url = client->profile->url;
+ switch_event_add_header_string(client->params, SWITCH_STACK_BOTTOM, "url", url);
+ }
+
+ get_style_method = method ? strcasecmp(method, "post") : 1;
+
+ switch_event_add_header_string(client->params, SWITCH_STACK_TOP, "session_id", session_id);
+
+ dynamic_url = switch_event_expand_headers(client->params, url);
+
+ process_form_post_params(client, curl_handle, &formpost);
+
+ if (formpost) {
+ get_style_method = 1;
+ } else {
+ data = switch_event_build_param_string(client->params, NULL, client->profile->vars_map);
+ switch_assert(data);
+
+ if (get_style_method) {
+ char *tmp = switch_mprintf("%s%c%s", dynamic_url, strchr(dynamic_url, '?') != NULL ? '&' : '?', data);
+
+ if (dynamic_url != url) {
+ free(dynamic_url);
+ }
+
+ dynamic_url = tmp;
+ }
+ }
+
+ curl_handle = switch_curl_easy_init();
+
+ if (session_id) {
+ char *hval = switch_mprintf("HTTAPI_SESSION_ID=%s", session_id);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_COOKIE, hval);
+ free(hval);
+ }
+
+ if (!strncasecmp(dynamic_url, "https", 5)) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
+ }
+
+
+ if (!zstr(client->profile->cred)) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPAUTH, client->profile->auth_scheme);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_USERPWD, client->profile->cred);
+ }
+
+ if (client->profile->disable100continue) {
+ headers = switch_curl_slist_append(headers, "Expect:");
+ }
+
+ if (client->profile->enable_cacert_check) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, TRUE);
+ }
+
+ switch_curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
+
+ if (method != NULL && strcasecmp(method, "get") && strcasecmp(method, "post")) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, method);
+ }
+
+ if (formpost) {
+ curl_easy_setopt(curl_handle, CURLOPT_HTTPPOST, formpost);
+ } else {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_POST, !get_style_method);
+ }
+
+ switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10);
+
+ if (!get_style_method && !formpost) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, data);
+ }
+
+ switch_curl_easy_setopt(curl_handle, CURLOPT_URL, dynamic_url);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, file_callback);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, get_header_callback);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) client);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, (void *) client);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "mod_httapi/1.0");
+
+ if (client->profile->timeout) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, client->profile->timeout);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
+ }
+
+ if (client->profile->ssl_cert_file) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSLCERT, client->profile->ssl_cert_file);
+ }
+
+ if (client->profile->ssl_key_file) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEY, client->profile->ssl_key_file);
+ }
+
+ if (client->profile->ssl_key_password) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSLKEYPASSWD, client->profile->ssl_key_password);
+ }
+
+ if (client->profile->ssl_version) {
+ if (!strcasecmp(client->profile->ssl_version, "SSLv3")) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
+ } else if (!strcasecmp(client->profile->ssl_version, "TLSv1")) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
+ }
+ }
+
+ if (client->profile->ssl_cacert_file) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_CAINFO, client->profile->ssl_cacert_file);
+ }
+
+ if (client->profile->enable_ssl_verifyhost) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2);
+ }
+
+ if (client->profile->cookie_file) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_COOKIEJAR, client->profile->cookie_file);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_COOKIEFILE, client->profile->cookie_file);
+ }
+
+ if (client->profile->bind_local) {
+ curl_easy_setopt(curl_handle, CURLOPT_INTERFACE, client->profile->bind_local);
+ }
+
+ switch_curl_easy_perform(curl_handle);
+ switch_curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &client->code);
+ switch_curl_easy_cleanup(curl_handle);
+ switch_curl_slist_free_all(headers);
+
+ if (formpost) {
+ curl_formfree(formpost);
+ }
+
+ if (client->err) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error encountered! [%s]\ndata: [%s]\n", client->profile->url, data);
+ status = SWITCH_STATUS_FALSE;
+ } else {
+ status = SWITCH_STATUS_SUCCESS;
+ }
+
+ switch_safe_free(data);
+
+ if (dynamic_url != url) {
+ switch_safe_free(dynamic_url);
+ }
+
+ cleanup_attachments(client);
+
+ if (save_params) {
+ switch_event_destroy(&client->params);
+ client->params = save_params;
+ save_params = NULL;
+ }
+
+
+ return status;
+}
+
+#define ENABLE_PARAM_VALUE "enabled"
+static switch_status_t do_config(void)
+{
+ char *cf = "httapi.conf";
+ switch_xml_t cfg, xml, profiles_tag, profile_tag, param, settings, tag;
+ client_profile_t *profile = NULL;
+ int x = 0;
+ int need_vars_map = 0;
+ switch_hash_t *vars_map = NULL;
+
+ if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", cf);
+ return SWITCH_STATUS_TERM;
+ }
+
+ if ((settings = switch_xml_child(cfg, "settings"))) {
+ for (param = switch_xml_child(settings, "param"); param; param = param->next) {
+ char *var = (char *) switch_xml_attr_soft(param, "name");
+ char *val = (char *) switch_xml_attr_soft(param, "value");
+
+ if (!strcasecmp(var, "debug")) {
+ globals.debug = switch_true(val);
+ } else if (!strcasecmp(var, "file-cache-ttl")) {
+ int tmp = atoi(val);
+
+ if (tmp > 0) {
+ globals.cache_ttl = tmp;
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid value [%s]for file-cache-ttl\n", val);
+ }
+
+ } else if (!strcasecmp(var, "file-not-found-expires")) {
+ globals.not_found_expires = atoi(val);
+
+ if (globals.not_found_expires < 0) {
+ globals.not_found_expires = -1;
+ }
+ }
+ }
+ }
+
+ if (!(profiles_tag = switch_xml_child(cfg, "profiles"))) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing tag!\n");
+ goto done;
+ }
+
+ for (profile_tag = switch_xml_child(profiles_tag, "profile"); profile_tag; profile_tag = profile_tag->next) {
+ char *bname = (char *) switch_xml_attr_soft(profile_tag, "name");
+ char *url = NULL;
+ char *bind_local = NULL;
+ char *bind_cred = NULL;
+ char *method = NULL;
+ int disable100continue = 1;
+ int timeout = 0;
+ uint32_t enable_cacert_check = 0;
+ char *ssl_cert_file = NULL;
+ char *ssl_key_file = NULL;
+ char *ssl_key_password = NULL;
+ char *ssl_version = NULL;
+ char *ssl_cacert_file = NULL;
+ uint32_t enable_ssl_verifyhost = 0;
+ char *cookie_file = NULL;
+ hash_node_t *hash_node;
+ int auth_scheme = CURLAUTH_BASIC;
+ need_vars_map = 0;
+ vars_map = NULL;
+
+ if (zstr(bname)) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "profile tag missing name field!\n");
+ continue;
+ }
+
+ if (switch_core_hash_find(globals.profile_hash, bname)) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile %s already exists\n", (char *)bname);
+ continue;
+ }
+
+ if ((tag = switch_xml_child(profile_tag, "params"))) {
+ for (param = switch_xml_child(tag, "param"); param; param = param->next) {
+ char *var = (char *) switch_xml_attr_soft(param, "name");
+ char *val = (char *) switch_xml_attr_soft(param, "value");
+
+ if (!strcasecmp(var, "gateway-url")) {
+ if (val) {
+ url = val;
+ }
+ } else if (!strcasecmp(var, "gateway-credentials")) {
+ bind_cred = val;
+ } else if (!strcasecmp(var, "auth-scheme")) {
+ if (*val == '=') {
+ auth_scheme = 0;
+ val++;
+ }
+
+ if (!strcasecmp(val, "basic")) {
+ auth_scheme |= CURLAUTH_BASIC;
+ } else if (!strcasecmp(val, "digest")) {
+ auth_scheme |= CURLAUTH_DIGEST;
+ } else if (!strcasecmp(val, "NTLM")) {
+ auth_scheme |= CURLAUTH_NTLM;
+ } else if (!strcasecmp(val, "GSS-NEGOTIATE")) {
+ auth_scheme |= CURLAUTH_GSSNEGOTIATE;
+ } else if (!strcasecmp(val, "any")) {
+ auth_scheme = CURLAUTH_ANY;
+ }
+ } else if (!strcasecmp(var, "disable-100-continue") && !switch_true(val)) {
+ disable100continue = 0;
+ } else if (!strcasecmp(var, "method")) {
+ method = val;
+ } else if (!strcasecmp(var, "timeout")) {
+ int tmp = atoi(val);
+ if (tmp >= 0) {
+ timeout = tmp;
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Can't set a negative timeout!\n");
+ }
+ } else if (!strcasecmp(var, "enable-cacert-check") && switch_true(val)) {
+ enable_cacert_check = 1;
+ } else if (!strcasecmp(var, "ssl-cert-path")) {
+ ssl_cert_file = val;
+ } else if (!strcasecmp(var, "ssl-key-path")) {
+ ssl_key_file = val;
+ } else if (!strcasecmp(var, "ssl-key-password")) {
+ ssl_key_password = val;
+ } else if (!strcasecmp(var, "ssl-version")) {
+ ssl_version = val;
+ } else if (!strcasecmp(var, "ssl-cacert-file")) {
+ ssl_cacert_file = val;
+ } else if (!strcasecmp(var, "enable-ssl-verifyhost") && switch_true(val)) {
+ enable_ssl_verifyhost = 1;
+ } else if (!strcasecmp(var, "cookie-file")) {
+ cookie_file = val;
+ } else if (!strcasecmp(var, "enable-post-var")) {
+ if (!vars_map && need_vars_map == 0) {
+ if (switch_core_hash_init(&vars_map, globals.pool) != SWITCH_STATUS_SUCCESS) {
+ need_vars_map = -1;
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Can't init params hash!\n");
+ continue;
+ }
+ need_vars_map = 1;
+ }
+
+ if (vars_map && val) {
+ if (switch_core_hash_insert(vars_map, val, ENABLE_PARAM_VALUE) != SWITCH_STATUS_SUCCESS) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Can't add %s to params hash!\n", val);
+ }
+ }
+ } else if (!strcasecmp(var, "bind-local")) {
+ bind_local = val;
+ }
+ }
+ }
+
+ if (!url) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile has no url!\n");
+ if (vars_map)
+ switch_core_hash_destroy(&vars_map);
+ continue;
+ }
+
+ if (!(profile = switch_core_alloc(globals.pool, sizeof(*profile)))) {
+ if (vars_map)
+ switch_core_hash_destroy(&vars_map);
+ goto done;
+ }
+ memset(profile, 0, sizeof(*profile));
+
+ /* Defaults */
+ profile->conference_params.use_profile = "default";
+ profile->perms.set_params = 1;
+ profile->perms.conference.enabled = 1;
+ profile->perms.dial.enabled = 1;
+
+ if ((tag = switch_xml_child(profile_tag, "conference"))) {
+ char *var = (char *) switch_xml_attr_soft(param, "name");
+ char *val = (char *) switch_xml_attr_soft(param, "value");
+
+ if (!strcasecmp(var, "default-profile")) {
+ profile->conference_params.use_profile = switch_core_strdup(globals.pool, val);
+ }
+ }
+
+ if ((tag = switch_xml_child(profile_tag, "dial"))) {
+ char *var = (char *) switch_xml_attr_soft(param, "name");
+ char *val = (char *) switch_xml_attr_soft(param, "value");
+
+ if (!strcasecmp(var, "context")) {
+ profile->dial_params.context = switch_core_strdup(globals.pool, val);
+ } else if (!strcasecmp(var, "dialplan")) {
+ profile->dial_params.dp = switch_core_strdup(globals.pool, val);;
+ }
+ }
+
+ if ((tag = switch_xml_child(profile_tag, "permissions"))) {
+ for (param = switch_xml_child(tag, "permission"); param; param = param->next) {
+ char *var = (char *) switch_xml_attr_soft(param, "name");
+ char *val = (char *) switch_xml_attr_soft(param, "value");
+
+ if (!strcasecmp(var, "all")) {
+ switch_byte_t all = switch_true(val);
+ memset(&profile->perms, all, sizeof(profile->perms));
+ } else if (!strcasecmp(var, "none")) {
+ switch_byte_t none = switch_true(val);
+ memset(&profile->perms, none, sizeof(profile->perms));
+ } else if (!strcasecmp(var, "set-params")) {
+ profile->perms.set_params = switch_true(val);
+ } else if (!strcasecmp(var, "set-vars")) {
+ profile->perms.set_vars = switch_true(val);
+ } else if (!strcasecmp(var, "extended-data")) {
+ profile->perms.extended_data = switch_true(val);
+ } else if (!strcasecmp(var, "execute-apps")) {
+ profile->perms.execute_apps = switch_true(val);
+ } else if (!strcasecmp(var, "expand-vars-in-tag-body")) {
+ profile->perms.expand_vars_in_tag_body = switch_true(val);
+ } else if (!strcasecmp(var, "dial")) {
+ profile->perms.dial.enabled = switch_true(val);
+ } else if (!strcasecmp(var, "dial-set-context")) {
+ profile->perms.dial.enabled = switch_true(val);
+ profile->perms.dial.set_context = switch_true(val);
+ } else if (!strcasecmp(var, "dial-set-dialplan")) {
+ profile->perms.dial.enabled = switch_true(val);
+ profile->perms.dial.set_dp = switch_true(val);
+ } else if (!strcasecmp(var, "dial-set-cid-name")) {
+ profile->perms.dial.enabled = switch_true(val);
+ profile->perms.dial.set_cid_name = switch_true(val);
+ } else if (!strcasecmp(var, "dial-set-cid-number")) {
+ profile->perms.dial.enabled = switch_true(val);
+ profile->perms.dial.set_cid_number = switch_true(val);
+ } else if (!strcasecmp(var, "dial-full-originate")) {
+ profile->perms.dial.enabled = switch_true(val);
+ profile->perms.dial.full_originate = switch_true(val);
+ } else if (!strcasecmp(var, "conference")) {
+ profile->perms.conference.enabled = switch_true(val);
+ } else if (!strcasecmp(var, "conference-set-profile")) {
+ profile->perms.conference.enabled = switch_true(val);
+ profile->perms.conference.set_profile = switch_true(val);
+ }
+
+ }
+ }
+
+
+
+ profile->auth_scheme = auth_scheme;
+ profile->timeout = timeout;
+ profile->url = strdup(url);
+ switch_assert(profile->url);
+
+ if (bind_local != NULL) {
+ profile->bind_local = strdup(bind_local);
+ }
+ if (method != NULL) {
+ profile->method = strdup(method);
+ } else {
+ profile->method = NULL;
+ }
+
+ if (bind_cred) {
+ profile->cred = strdup(bind_cred);
+ }
+
+ profile->disable100continue = disable100continue;
+ profile->enable_cacert_check = enable_cacert_check;
+
+ if (ssl_cert_file) {
+ profile->ssl_cert_file = strdup(ssl_cert_file);
+ }
+
+ if (ssl_key_file) {
+ profile->ssl_key_file = strdup(ssl_key_file);
+ }
+
+ if (ssl_key_password) {
+ profile->ssl_key_password = strdup(ssl_key_password);
+ }
+
+ if (ssl_version) {
+ profile->ssl_version = strdup(ssl_version);
+ }
+
+ if (ssl_cacert_file) {
+ profile->ssl_cacert_file = strdup(ssl_cacert_file);
+ }
+
+ profile->enable_ssl_verifyhost = enable_ssl_verifyhost;
+
+ if (cookie_file) {
+ profile->cookie_file = strdup(cookie_file);
+ }
+
+ profile->vars_map = vars_map;
+
+ if (vars_map) {
+ switch_zmalloc(hash_node, sizeof(hash_node_t));
+ hash_node->hash = vars_map;
+ hash_node->next = NULL;
+
+ if (!globals.hash_root) {
+ globals.hash_root = hash_node;
+ globals.hash_tail = globals.hash_root;
+ }
+
+ else {
+ globals.hash_tail->next = hash_node;
+ globals.hash_tail = globals.hash_tail->next;
+ }
+
+ }
+
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Profile [%s] JSON Function [%s]\n",
+ zstr(bname) ? "N/A" : bname, profile->url);
+
+ profile->name = strdup(bname);
+
+ switch_core_hash_insert(globals.profile_hash, bname, profile);
+
+ x++;
+ profile = NULL;
+ }
+
+ done:
+
+ switch_xml_free(xml);
+
+ return x ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE;
+}
+
+static switch_status_t my_on_reporting(switch_core_session_t *session)
+{
+ switch_channel_t *channel = switch_core_session_get_channel(session);
+ switch_status_t status = SWITCH_STATUS_SUCCESS;
+ client_t *client;
+ const char *var;
+
+ if (!(client = (client_t *) switch_channel_get_private(channel, "_HTTAPI_CLIENT_"))) {
+ return status;
+ }
+
+ switch_channel_set_private(channel, "_HTTAPI_CLIENT_", NULL);
+
+ if (client->profile->perms.extended_data) {
+ switch_channel_event_set_extended_data(channel, client->one_time_params);
+ }
+
+ switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "exiting", "true");
+
+ if (client->record.file) {
+ char *key = switch_core_sprintf(client->pool, "attach_file:%s:%s.wav", client->record.name, switch_core_session_get_uuid(session));
+ switch_ivr_stop_record_session(client->session, client->record.file);
+ switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, key, client->record.file);
+ }
+
+ var = switch_event_get_header(client->params, "url");
+
+ if (client->record.action) {
+ if (strcmp(var, client->record.action)) {
+ switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "url", client->record.action);
+ httapi_sync(client);
+ if (client->profile->perms.extended_data) {
+ switch_channel_event_set_extended_data(channel, client->one_time_params);
+ }
+ switch_event_add_header_string(client->one_time_params, SWITCH_STACK_BOTTOM, "exiting", "true");
+ }
+ }
+
+ httapi_sync(client);
+
+ client_destroy(&client);
+
+ return status;
+}
+
+static switch_state_handler_table_t state_handlers = {
+ /*.on_init */ NULL,
+ /*.on_routing */ NULL,
+ /*.on_execute */ NULL,
+ /*.on_hangup */ NULL,
+ /*.on_exchange_media */ NULL,
+ /*.on_soft_execute */ NULL,
+ /*.on_consume_media */ NULL,
+ /*.on_hibernate */ NULL,
+ /*.on_reset */ NULL,
+ /*.on_park */ NULL,
+ /*.on_reporting */ my_on_reporting,
+ /*.on_destroy */ NULL,
+ SSH_FLAG_STICKY
+};
+
+
+SWITCH_STANDARD_APP(httapi_function)
+{
+ switch_channel_t *channel = switch_core_session_get_channel(session);
+ char *parsed = NULL;
+ const char *profile_name = NULL;
+ client_t *client;
+ switch_event_t *params = NULL;
+ uint32_t loops = 0, all_extended = 0;
+
+ if (!zstr(data)) {
+ switch_event_create_brackets((char *)data, '{', '}', ',', ¶ms, &parsed, SWITCH_TRUE);
+ }
+
+ if ((client = (client_t *) switch_channel_get_private(channel, "_HTTAPI_CLIENT_"))) {
+ if (params) {
+ switch_event_merge(client->params, params);
+ switch_event_destroy(¶ms);
+ }
+ } else {
+ if (params) {
+ profile_name = switch_event_get_header(params, "httapi_profile");
+ }
+
+ if (zstr(profile_name) && !(profile_name = switch_channel_get_variable(channel, "httapi_profile"))) {
+ profile_name = "default";
+ }
+
+ if ((client = client_create(session, profile_name, ¶ms))) {
+ switch_channel_set_private(channel, "_HTTAPI_CLIENT_", client);
+ switch_channel_add_state_handler(channel, &state_handlers);
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot find suitable profile\n");
+ switch_event_destroy(¶ms);
+ return;
+ }
+ }
+
+ if (client->profile->perms.extended_data) {
+ all_extended = switch_true(switch_event_get_header(client->params, "full_channel_data_on_every_req"));
+ }
+
+ while(switch_channel_ready(channel)) {
+ switch_status_t status = SWITCH_STATUS_FALSE;
+
+ if (client->profile->perms.extended_data && (!loops++ || all_extended)) {
+ switch_channel_event_set_extended_data(channel, client->one_time_params);
+ }
+
+ if ((status = httapi_sync(client)) == SWITCH_STATUS_SUCCESS) {
+ if (client->code == 200) {
+ const char *ct = switch_event_get_header(client->headers, "content-type");
+
+ if (switch_stristr("text/xml", ct)) {
+ status = parse_xml(client);
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received unsupported content-type %s\n", ct);
+ break;
+ }
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Received HTTP response: %ld.\n", client->code);
+ break;
+ }
+ } else {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error %d!\n", client->err);
+ }
+
+ if (status == SWITCH_STATUS_TERM) {
+ httapi_sync(client);
+ }
+
+ if (status != SWITCH_STATUS_SUCCESS) {
+ break;
+ }
+ }
+
+
+ switch_safe_free(parsed);
+
+}
+
+
+/* HTTP FILE INTERFACE */
+
+static char *load_cache_data(http_file_context_t *context, const char *url)
+{
+ char *ext;
+ char digest[SWITCH_MD5_DIGEST_STRING_SIZE] = { 0 };
+ char meta_buffer[1024] = "";
+ int fd;
+ switch_ssize_t bytes;
+
+ switch_md5_string(digest, (void *) url, strlen(url));
+
+ if ((ext = strrchr(url, '.'))) {
+ ext++;
+ } else {
+ ext = "wav";
+ }
+
+ context->cache_file = switch_core_sprintf(context->pool, "%s%s%s.%s", globals.cache_path, SWITCH_PATH_SEPARATOR, digest, ext);
+ context->meta_file = switch_core_sprintf(context->pool, "%s.meta", context->cache_file);
+ context->lock_file = switch_core_sprintf(context->pool, "%s.lock", context->cache_file);
+
+ if (switch_file_exists(context->meta_file, context->pool) == SWITCH_STATUS_SUCCESS && ((fd = open(context->meta_file, O_RDONLY, 0)) > -1)) {
+ if ((bytes = read(fd, meta_buffer, sizeof(meta_buffer))) > 0) {
+ char *p;
+
+ if ((p = strchr(meta_buffer, ':'))) {
+ *p++ = '\0';
+ context->expires = (time_t) atol(meta_buffer);
+ context->metadata = switch_core_strdup(context->pool, p);
+ }
+ }
+ close(fd);
+ }
+
+ return context->cache_file;
+}
+
+static size_t save_file_callback(void *ptr, size_t size, size_t nmemb, void *data)
+{
+ register unsigned int realsize = (unsigned int) (size * nmemb);
+ client_t *client = data;
+ int x;
+
+ client->bytes += realsize;
+
+
+
+ if (client->bytes > client->max_bytes) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Oversized file detected [%d bytes]\n", (int) client->bytes);
+ client->err = 1;
+ return 0;
+ }
+
+ x = write(client->fd, ptr, realsize);
+
+ if (x != (int) realsize) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Short write! %d out of %d\n", x, realsize);
+ }
+ return x;
+}
+
+
+
+static switch_status_t fetch_cache_data(const char *url, switch_event_t **headers, const char *save_path)
+{
+ switch_CURL *curl_handle = NULL;
+ client_t client = { 0 };
+ long code;
+ switch_status_t status = SWITCH_STATUS_FALSE;
+
+ client.fd = -1;
+
+ if (save_path) {
+ if ((client.fd = open(save_path, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
+ return SWITCH_STATUS_FALSE;
+ }
+ }
+
+ curl_handle = switch_curl_easy_init();
+
+ switch_curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
+
+ if (!strncasecmp(url, "https", 5)) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
+ }
+
+ client.max_bytes = HTTAPI_MAX_FILE_BYTES;
+
+ switch_curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_MAXREDIRS, 10);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_URL, url);
+
+ if (save_path) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, save_file_callback);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &client);
+ } else {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_HEADER, 1);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1);
+ }
+
+ if (headers) {
+ switch_event_create(&client.headers, SWITCH_EVENT_CLONE);
+ if (save_path) {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, get_header_callback);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEHEADER, (void *) &client);
+ } else {
+ switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, get_header_callback);
+ switch_curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *) &client);
+ }
+ }
+
+ switch_curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "mod_httapi/1.0");
+ switch_curl_easy_perform(curl_handle);
+ switch_curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &code);
+ switch_curl_easy_cleanup(curl_handle);
+
+ if (client.fd > -1) {
+ close(client.fd);
+ }
+
+ if (headers && client.headers) {
+ switch_event_add_header(client.headers, SWITCH_STACK_BOTTOM, "http-response-code", "%ld", code);
+ *headers = client.headers;
+ }
+
+ switch(code) {
+ case 200:
+ if (save_path) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "caching: url:%s to %s (%ld bytes)\n", url, save_path, client.bytes);
+ }
+ status = SWITCH_STATUS_SUCCESS;
+ break;
+
+ case 404:
+ status = SWITCH_STATUS_NOTFOUND;
+ break;
+
+ default:
+ status = SWITCH_STATUS_FALSE;
+ break;
+ }
+
+
+ return status;
+}
+
+static switch_status_t write_meta_file(http_file_context_t *context, const char *data)
+{
+ int fd;
+ switch_status_t status = SWITCH_STATUS_SUCCESS;
+ char write_data[1024];
+
+ if ((fd = open(context->meta_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) < 0) {
+ return SWITCH_STATUS_FALSE;
+ }
+
+ if (!zstr(data)) {
+
+ switch_snprintf(write_data, sizeof(write_data),
+ "%" SWITCH_TIME_T_FMT ":%s",
+ switch_epoch_time_now(NULL) + globals.cache_ttl,
+ data);
+
+
+ status = write(fd, write_data, strlen(write_data) + 1) > 0 ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE;
+ }
+
+ close(fd);
+
+ return status;
+}
+
+
+static switch_status_t lock_file(http_file_context_t *context, switch_bool_t lock)
+{
+
+ switch_status_t status = SWITCH_STATUS_SUCCESS;
+
+
+ if (lock) {
+ if (switch_file_open(&context->lock_fd,
+ context->lock_file,
+ SWITCH_FOPEN_WRITE | SWITCH_FOPEN_CREATE | SWITCH_FOPEN_TRUNCATE,
+ SWITCH_FPROT_UREAD | SWITCH_FPROT_UWRITE, context->pool) != SWITCH_STATUS_SUCCESS) {
+ return SWITCH_STATUS_FALSE;
+ }
+
+
+ if (switch_file_lock(context->lock_fd, SWITCH_FLOCK_EXCLUSIVE) != SWITCH_STATUS_SUCCESS) {
+ return SWITCH_STATUS_FALSE;
+ }
+ } else {
+ if (context->lock_fd){
+ switch_file_close(context->lock_fd);
+ status = SWITCH_STATUS_SUCCESS;
+ }
+
+ unlink(context->lock_file);
+ }
+
+ return status;
+}
+
+
+static switch_status_t locate_url_file(http_file_context_t *context, const char *url)
+{
+ switch_event_t *headers = NULL;
+ int unreachable = 0;
+ switch_status_t status = SWITCH_STATUS_FALSE;
+ time_t now = switch_epoch_time_now(NULL);
+ char *metadata;
+
+ load_cache_data(context, url);
+
+ if (context->expires && now < context->expires) {
+ return SWITCH_STATUS_SUCCESS;
+ }
+
+ lock_file(context, SWITCH_TRUE);
+
+ if ((status = fetch_cache_data(url, &headers, NULL)) != SWITCH_STATUS_SUCCESS) {
+ if (status == SWITCH_STATUS_NOTFOUND) {
+ unreachable = 2;
+ if (now - context->expires < globals.not_found_expires) {
+ switch_goto_status(SWITCH_STATUS_SUCCESS, end);
+ }
+ } else {
+ unreachable = 1;
+ }
+ }
+
+ if (!unreachable && !zstr(context->metadata)) {
+ metadata = switch_core_sprintf(context->pool, "%s:%s:%s:%s",
+ url,
+ switch_event_get_header_nil(headers, "last-modified"),
+ switch_event_get_header_nil(headers, "etag"),
+ switch_event_get_header_nil(headers, "content-length")
+ );
+
+ if (!strcmp(metadata, context->metadata)) {
+ write_meta_file(context, metadata);
+ switch_goto_status(SWITCH_STATUS_SUCCESS, end);
+ }
+ }
+
+ switch_event_destroy(&headers);
+ fetch_cache_data(url, &headers, context->cache_file);
+ metadata = switch_core_sprintf(context->pool, "%s:%s:%s:%s",
+ url,
+ switch_event_get_header_nil(headers, "last-modified"),
+ switch_event_get_header_nil(headers, "etag"),
+ switch_event_get_header_nil(headers, "content-length")
+ );
+
+ write_meta_file(context, metadata);
+
+ if (switch_file_exists(context->cache_file, context->pool) == SWITCH_STATUS_SUCCESS) {
+ status = SWITCH_STATUS_SUCCESS;
+ }
+
+ end:
+
+ if (status != SWITCH_STATUS_SUCCESS) {
+ unlink(context->meta_file);
+ unlink(context->cache_file);
+ }
+
+ lock_file(context, SWITCH_FALSE);
+
+ switch_event_destroy(&headers);
+
+ return status;
+}
+
+
+static switch_status_t http_file_file_seek(switch_file_handle_t *handle, unsigned int *cur_sample, int64_t samples, int whence)
+{
+ http_file_context_t *context = handle->private_info;
+
+ if (!handle->seekable) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "File is not seekable\n");
+ return SWITCH_STATUS_NOTIMPL;
+ }
+
+ return switch_core_file_seek(&context->fh, cur_sample, samples, whence);
+}
+
+static switch_status_t http_file_file_open(switch_file_handle_t *handle, const char *path)
+{
+ http_file_context_t *context;
+ char *file_dup;
+ switch_status_t status;
+
+ if (switch_test_flag(handle, SWITCH_FILE_FLAG_WRITE)) {
+ switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "This format does not support writing!\n");
+ return SWITCH_STATUS_FALSE;
+ }
+
+ context = switch_core_alloc(handle->memory_pool, sizeof(*context));
+ context->pool = handle->memory_pool;
+
+ file_dup = switch_core_sprintf(handle->memory_pool, "http://%s", path);
+
+ if ((status = locate_url_file(context, file_dup)) != SWITCH_STATUS_SUCCESS) {
+ return status;
+ }
+
+ handle->private_info = context;
+
+ if ((status = switch_core_file_open(&context->fh,
+ context->cache_file,
+ handle->channels,
+ handle->samplerate,
+ SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL)) != SWITCH_STATUS_SUCCESS) {
+ return status;
+ }
+
+ handle->samples = context->fh.samples;
+ handle->format = context->fh.format;
+ handle->sections = context->fh.sections;
+ handle->seekable = context->fh.seekable;
+ handle->speed = context->fh.speed;
+ handle->interval = context->fh.interval;
+
+ if (switch_test_flag((&context->fh), SWITCH_FILE_NATIVE)) {
+ switch_set_flag(handle, SWITCH_FILE_NATIVE);
+ } else {
+ switch_clear_flag(handle, SWITCH_FILE_NATIVE);
+ }
+
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t http_file_file_close(switch_file_handle_t *handle)
+{
+ http_file_context_t *context = handle->private_info;
+
+ if (switch_test_flag((&context->fh), SWITCH_FILE_OPEN)) {
+ switch_core_file_close(&context->fh);
+ }
+
+ return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t http_file_file_read(switch_file_handle_t *handle, void *data, size_t *len)
+{
+ http_file_context_t *context = handle->private_info;
+ switch_status_t status;
+
+ if (context->samples > 0) {
+ if (*len > (size_t) context->samples) {
+ *len = context->samples;
+ }
+
+ context->samples -= *len;
+ memset(data, 255, *len *2);
+ status = SWITCH_STATUS_SUCCESS;
+ } else {
+ status = switch_core_file_read(&context->fh, data, len);
+ }
+
+ return status;
+}
+
+/* Registration */
+
+static char *http_file_supported_formats[SWITCH_MAX_CODECS] = { 0 };
+
+
+/* /HTTP FILE INTERFACE */
+
+SWITCH_MODULE_LOAD_FUNCTION(mod_httapi_load)
+{
+ switch_api_interface_t *httapi_api_interface;
+ switch_application_interface_t *app_interface;
+ switch_file_interface_t *file_interface;
+
+ /* connect my internal structure to the blank pointer passed to me */
+ *module_interface = switch_loadable_module_create_module_interface(pool, modname);
+
+ memset(&globals, 0, sizeof(globals));
+ globals.pool = pool;
+ globals.hash_root = NULL;
+ globals.hash_tail = NULL;
+ globals.cache_ttl = 300;
+ globals.not_found_expires = 300;
+
+
+ http_file_supported_formats[0] = "http";
+
+ file_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_FILE_INTERFACE);
+ file_interface->interface_name = modname;
+ file_interface->extens = http_file_supported_formats;
+ file_interface->file_open = http_file_file_open;
+ file_interface->file_close = http_file_file_close;
+ file_interface->file_read = http_file_file_read;
+ file_interface->file_seek = http_file_file_seek;
+
+ switch_snprintf(globals.cache_path, sizeof(globals.cache_path), "%s%shttp_file_cache", SWITCH_GLOBAL_dirs.storage_dir, SWITCH_PATH_SEPARATOR);
+ switch_dir_make_recursive(globals.cache_path, SWITCH_DEFAULT_DIR_PERMS, pool);
+
+
+ switch_core_hash_init(&globals.profile_hash, globals.pool);
+ switch_core_hash_init_case(&globals.parse_hash, globals.pool, SWITCH_FALSE);
+
+ bind_parser("execute", parse_execute);
+ bind_parser("sms", parse_sms);
+ bind_parser("dial", parse_dial);
+ bind_parser("pause", parse_playback);
+ bind_parser("hangup", parse_hangup);
+ bind_parser("record", parse_record);
+ bind_parser("recordCall", parse_record_call);
+ bind_parser("playback", parse_playback);
+ bind_parser("speak", parse_playback);
+ bind_parser("say", parse_playback);
+ bind_parser("conference", parse_conference);
+ bind_parser("break", parse_break);
+ bind_parser("log", parse_log);
+
+ if (do_config() != SWITCH_STATUS_SUCCESS) {
+ return SWITCH_STATUS_FALSE;
+ }
+
+ SWITCH_ADD_API(httapi_api_interface, "HT-TAPI Hypertext Telephony API", "HT-TAPI Hypertext Telephony API", httapi_api_function, HTTAPI_SYNTAX);
+
+ SWITCH_ADD_APP(app_interface, "httapi",
+ "HT-TAPI Hypertext Telephony API",
+ "HT-TAPI Hypertext Telephony API", httapi_function, "{=}", SAF_SUPPORT_NOMEDIA);
+
+
+
+ switch_console_set_complete("add httapi debug_on");
+ switch_console_set_complete("add httapi debug_off");
+
+ /* indicate that the module should continue to be loaded */
+ return SWITCH_STATUS_SUCCESS;
+}
+
+SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_httapi_shutdown)
+{
+ hash_node_t *ptr = NULL;
+
+ switch_core_hash_destroy(&globals.profile_hash);
+ switch_core_hash_destroy(&globals.parse_hash);
+
+ while (globals.hash_root) {
+ ptr = globals.hash_root;
+ switch_core_hash_destroy(&ptr->hash);
+ globals.hash_root = ptr->next;
+ switch_safe_free(ptr);
+ }
+
+ 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:
+ */
+
+
diff --git a/src/mod/applications/mod_httapi/mod_httapi_doc.txt b/src/mod/applications/mod_httapi/mod_httapi_doc.txt
new file mode 100644
index 0000000000..f87ceaf041
--- /dev/null
+++ b/src/mod/applications/mod_httapi/mod_httapi_doc.txt
@@ -0,0 +1,279 @@
+HT-TAPI Hyper-Text Telephony API and http file format plugin
+
+This module provides an HTTP based Telephony API using a standard FreeSWITCH application interface as well as a cached http file format interface.
+
+The file format plugin can be used like this:
+
+
+This syntax is valid anywhere a filname parameter is requested in FreeSWITCH.
+
+
+The application is called like this:
+
+
+
+The target url is expected to be a CGI returning text/xml using the documentation below.
+
+The format is roughly as described below (We could use a DTD maybe).
+
+
+
+
+
+
+
+
+
+ <...>
+
+
+
+
+The profile name must be chosen to bind to a preset series of settings and permissions to use as a basis for the client session.
+Its chosen from the specified params or from the channel variable using the keyname 'httapi_profile' and if not specified will default to 'default'
+Any params specified in the initial data of the application encased in brackets {} will be parsed into the initial params similar to originate.
+These params remain set until the call ends and are supplied as form elements on each hit to the HTTP server.
+If the permissions allow, a tag is parsed from the resulting document and set into this data set and will be mirrored back on subsequqent http posts.
+Also if the permissions allow a tag is parsed the same way setting channel variables.
+The tag is required and contains one or more of the supported command tags that generally manipulate the call behaviour.
+The application will continue to do the task in the work section until an error is encoutered or when an action from the work tag warrants it.
+If the session exits the httapi app without hanging up it can do other tasks and make another call to httapi and the session data will remain preserved.
+
+
+EXAMPLE:
+
+
+
+
+
+~\d+#
+
+
+
+
+BINDINGS
+Several of the work tags that indicate they support bindings can contain one of more bind tags that function with similar fashion to bind_digit_action
+
+*EXPR*
+
+ATTRS:
+action : a specific url to go to next if the binding is dialed
+strip : a character to strip from in the result such as #
+
+WORK TAGS:
+*ACTIONS*
+
+
+*EXPR*
+ : Plays a file and optionally collects input.
+
+ATTRS:
+file : The file
+name : Param name to save result.
+error-file : Error file to play on invalid input.
+action : Change the new target url
+digit-timeout : Timeout waiting for digits after file plays (when input bindings are present)
+input-timeout : Timeout waiting for more digits in a multi-digit input.
+loops : max times to play the file when input bindings are present.
+asr-engine : ASR engine to use
+asr-grammar : ASR grammar to use
+
+
+
+
+*EXPR*
+ : Records a file, optionally collects input and posts the file back to the target url
+
+ATTRS:
+file : The file
+name : Param name to save result.
+error-file : Error file to play on invalid input.
+action : Change the new target url
+digit-timeout : Timeout waiting for digits after file plays (when input bindings are present)
+input-timeout : Timeout waiting for more digits in a multi-digit input.
+
+
+
+
+
+*EXPR*
+ : Waits for input for a specific amount of time.
+
+ATTRS:
+milliseconds : Number of milliseconds to pause
+name : Param name to save result.
+error-file : Error file to play on invalid input.
+action : Change the new target url
+digit-timeout : Timeout waiting for digits after file plays (when input bindings are present)
+input-timeout : Timeout waiting for more digits in a multi-digit input.
+loops : max times to play the file when input bindings are present.
+
+
+
+
+
+*EXPR*
+ : Read Text to Speech with optional input.
+
+ATTRS:
+file : The file
+name : Param name to save result.
+error-file : Error file to play on invalid input.
+action : Change the new target url
+digit-timeout : Timeout waiting for digits after file plays (when input bindings are present)
+input-timeout : Timeout waiting for more digits in a multi-digit input.
+loops : max times to play the file when input bindings are present.
+engine : tts engine to use.
+voice : tts voice to use.
+
+
+
+
+
+*EXPR*
+ : Use the FS say engine to iterate sounds to similate a human speaker.
+
+ATTRS:
+file : The file
+name : Param name to save result.
+error-file : Error file to play on invalid input.
+action : Change the new target url
+digit-timeout : Timeout waiting for digits after file plays (when input bindings are present)
+input-timeout : Timeout waiting for more digits in a multi-digit input.
+loops : max times to play the file when input bindings are present.
+language : language
+type : type (fs param)
+method : method (fs param)
+gender : gender (fs param)
+
+
+
+
+
+*DATA*
+ : Execute a FreeSWITCH app.
+ATTRS:
+application : The app to run
+*DATA* : The app data
+
+DATA
+ : Send a SMS message.
+ATTRS:
+to : The dest number
+*DATA* : The message data
+
+
+
+
+
+*DATA*
+ : Place an outbound call or transfer.
+
+ATTRS:
+context : Dialplan context.
+dialplan : Dialplan dialplan.
+caller-id-name : Caller ID Name.
+caller-id-number : Caller ID Number.
+*DATA* : Number to dial or originate string
+
+
+
+
+
+
+ : begin recording the call. The file will be posted when the call ends.
+
+ATTRS:
+limit : Timeout in seconds.
+name : Name to use for input values.
+action : URL action to use.
+
+
+
+
+
+
+ : Start a conference call.
+
+ATTRS:
+profile : Conference profile to use.
+
+
+
+
+
+
+ : Hangup the call
+
+ATTRS:
+cause : Hangup cause
+
+
+
+
+
+
+ : Exit the httapi application and continue in the dialplan.
+
+
+
+
+
+
+ : Exit the httapi application and continue in the dialplan.
+ATTRS:
+level : The log level to use.
+clean : If true do not pring log prefix.
+
+
+
+
+CONFIGURATION:
+
+:
+
+debug : false Print debug data
+file-cache-ttl : 300 How long to wait before checking the server to see if audio file has changed.
+file-not-found-expires : 300 How long to still preserve cached audio files that are not found by the server.
+
+ : CREATE NEW PROFILE TO REFERENCE BY NAME
+gateway-url : "" Initial URL to connect to.
+gateway-credentials : "" HTTP credentials.
+auth-scheme : basic auth scheme to use. [basic|digest|NTLM|GSS-NEGOTIATE|any]
+disable-100-continue : true Disable the 100 continue feature.
+method : "" METHOD name to send.
+timeout : 0 Timeout waiting for response.
+enable-cacert-check : false Check CA/CERT.
+ssl-cert-path : "" path to file.
+ssl-key-path : "" path to file.
+ssl-key-password : "" password to use.
+ssl-version : "" ssl version
+ssl-cacert-file : "" CA/CERT file.
+enable-ssl-verifyhost : "" Verify ssl host.
+cookie-file : "" Path to file to use for cookie.
+enable-post-var : "" Specify specifc param names ok to send.
+bind-local : "" Interface to bind to.
+default-profile : default Profile to use when not specified.
+
+
+
+: * = default
+
+
+*set-params : tag can be parsed for session params.
+set-vars : tag can be parsed to set channel vars.
+extended-data : Extended data is sent like full channel event data.
+execute-apps : tag is enabled to execute apps.
+expand-vars-in-tag-body : body content of tags are run trough variable expansion.
+*dial : tag is enabled allowing outbound dialing.
+dial-set-context : context attribute is permitted.
+dial-set-dialplan : dialplan attribute is permitted.
+dial-set-cid-name : cid_name attribute is permitted.
+dial-set-cid-number : cid_number attribute is permitted.
+dial-full-originate : full originate syntax is permitted instead of just a number.
+*conference : tag is enabled allowing creation of conferences.
+conference-set-profile : attribure is permitted.
+all : all permissions are set
+none : no permissions are set
+
+