[asterisk-commits] rizzo: trunk r45582 - /trunk/main/manager.c
asterisk-commits at lists.digium.com
asterisk-commits at lists.digium.com
Wed Oct 18 10:45:52 MST 2006
Author: rizzo
Date: Wed Oct 18 12:45:50 2006
New Revision: 45582
URL: http://svn.digium.com/view/asterisk?rev=45582&view=rev
Log:
despite the large changes, this commit only moves functions
around so that functions belonging to the same group are
close to each other.
At the beginning of each group i have added a bit of documentation
to explain what the group does and what is the typical flow - basically,
all i have learned by code inspection over the past few days should
be documented for you to read.
I have not put many doxygen annotations just because i am not
sure what are the proper ones. Hopefully some doxygen experts will jump in.
Next on the plate: try to figure out how "struct eventqent"
are supposed to work.
Modified:
trunk/main/manager.c
Modified: trunk/main/manager.c
URL: http://svn.digium.com/view/asterisk/trunk/main/manager.c?rev=45582&r1=45581&r2=45582&view=diff
==============================================================================
--- trunk/main/manager.c (original)
+++ trunk/main/manager.c Wed Oct 18 12:45:50 2006
@@ -22,8 +22,15 @@
*
* \author Mark Spencer <markster at digium.com>
*
- * Channel Management and more
- *
+ * At the moment this file contains a number of functions, namely:
+ *
+ * - data structures storing AMI state
+ * - AMI-related API functions, used by internal asterisk components
+ * - handlers for AMI-related CLI functions
+ * - handlers for AMI functions (available through the AMI socket)
+ * - the code for the main AMI listener thread and individual session threads
+ * - the http handlers invoked for AMI-over-HTTP by the threads in main/http.c
+ *
* \ref amiconf
*/
@@ -69,22 +76,6 @@
#include "asterisk/threadstorage.h"
#include "asterisk/linkedlists.h"
-struct fast_originate_helper {
- char tech[AST_MAX_MANHEADER_LEN];
- char data[AST_MAX_MANHEADER_LEN];
- int timeout;
- char app[AST_MAX_APP];
- char appdata[AST_MAX_MANHEADER_LEN];
- char cid_name[AST_MAX_MANHEADER_LEN];
- char cid_num[AST_MAX_MANHEADER_LEN];
- char context[AST_MAX_CONTEXT];
- char exten[AST_MAX_EXTENSION];
- char idtext[AST_MAX_MANHEADER_LEN];
- char account[AST_MAX_ACCOUNT_CODE];
- int priority;
- struct ast_variable *vars;
-};
-
struct eventqent {
int usecount;
int category;
@@ -127,22 +118,6 @@
AST_THREADSTORAGE(astman_append_buf, astman_append_buf_init);
#define ASTMAN_APPEND_BUF_INITSIZE 256
-
-static struct permalias {
- int num;
- char *label;
-} perms[] = {
- { EVENT_FLAG_SYSTEM, "system" },
- { EVENT_FLAG_CALL, "call" },
- { EVENT_FLAG_LOG, "log" },
- { EVENT_FLAG_VERBOSE, "verbose" },
- { EVENT_FLAG_COMMAND, "command" },
- { EVENT_FLAG_AGENT, "agent" },
- { EVENT_FLAG_USER, "user" },
- { EVENT_FLAG_CONFIG, "config" },
- { -1, "all" },
- { 0, "none" },
-};
struct mansession {
/*! Execution thread */
@@ -206,6 +181,26 @@
static struct manager_action *first_action = NULL;
AST_MUTEX_DEFINE_STATIC(actionlock);
+/*
+ * helper functions to convert back and forth between
+ * string and numeric representation of set of flags
+ */
+static struct permalias {
+ int num;
+ char *label;
+} perms[] = {
+ { EVENT_FLAG_SYSTEM, "system" },
+ { EVENT_FLAG_CALL, "call" },
+ { EVENT_FLAG_LOG, "log" },
+ { EVENT_FLAG_VERBOSE, "verbose" },
+ { EVENT_FLAG_COMMAND, "command" },
+ { EVENT_FLAG_AGENT, "agent" },
+ { EVENT_FLAG_USER, "user" },
+ { EVENT_FLAG_CONFIG, "config" },
+ { -1, "all" },
+ { 0, "none" },
+};
+
/*! \brief Convert authority code to a list of options */
static char *authority_to_str(int authority, char *res, int reslen)
{
@@ -227,6 +222,70 @@
return res;
}
+/*! Tells you if smallstr exists inside bigstr
+ which is delim by delim and uses no buf or stringsep
+ ast_instring("this|that|more","this",'|') == 1;
+
+ feel free to move this to app.c -anthm */
+static int ast_instring(const char *bigstr, const char *smallstr, const char delim)
+{
+ const char *val = bigstr, *next;
+
+ do {
+ if ((next = strchr(val, delim))) {
+ if (!strncmp(val, smallstr, (next - val)))
+ return 1;
+ else
+ continue;
+ } else
+ return !strcmp(smallstr, val);
+
+ } while (*(val = (next + 1)));
+
+ return 0;
+}
+
+static int get_perm(const char *instr)
+{
+ int x = 0, ret = 0;
+
+ if (!instr)
+ return 0;
+
+ for (x = 0; x < (sizeof(perms) / sizeof(perms[0])); x++) {
+ if (ast_instring(instr, perms[x].label, ','))
+ ret |= perms[x].num;
+ }
+
+ return ret;
+}
+
+/*
+ * A number returns itself, false returns 0, true returns all flags,
+ * other strings return the flags that are set.
+ */
+static int ast_strings_to_mask(const char *string)
+{
+ const char *p;
+
+ if (ast_strlen_zero(string))
+ return -1;
+
+ for (p = string; *p; p++)
+ if (*p < '0' || *p > '9')
+ break;
+ if (!p) /* all digits */
+ return atoi(string);
+ if (ast_false(string))
+ return 0;
+ if (ast_true(string)) { /* all permissions */
+ int x, ret = 0;
+ for (x=0; x<sizeof(perms) / sizeof(perms[0]); x++)
+ ret |= perms[x].num;
+ return ret;
+ }
+ return get_perm(string);
+}
static char *complete_show_mancmd(const char *line, const char *word, int pos, int state)
{
struct manager_action *cur;
@@ -245,177 +304,6 @@
return ret;
}
-/*
- * convert to xml with various conversion:
- * mode & 1 -> lowercase;
- * mode & 2 -> replace non-alphanumeric chars with underscore
- */
-static void xml_copy_escape(char **dst, size_t *maxlen, const char *src, int mode)
-{
- for ( ; *src && *maxlen > 6; src++) {
- if ( (mode & 2) && !isalnum(*src)) {
- *(*dst)++ = '_';
- (*maxlen)--;
- continue;
- }
- switch (*src) {
- case '<':
- strcpy(*dst, "<");
- (*dst) += 4;
- *maxlen -= 4;
- break;
- case '>':
- strcpy(*dst, ">");
- (*dst) += 4;
- *maxlen -= 4;
- break;
- case '\"':
- strcpy(*dst, """);
- (*dst) += 6;
- *maxlen -= 6;
- break;
- case '\'':
- strcpy(*dst, "'");
- (*dst) += 6;
- *maxlen -= 6;
- break;
- case '&':
- strcpy(*dst, "&");
- (*dst) += 5;
- *maxlen -= 5;
- break;
-
- default:
- *(*dst)++ = mode ? tolower(*src) : *src;
- (*maxlen)--;
- }
- }
-}
-
-/*! \brief Convert the input into XML or HTML.
- * The input is supposed to be a sequence of lines of the form
- * Name: value
- * optionally followed by a blob of unformatted text.
- * A blank line is a section separator. Basically, this is a
- * mixture of the format of Manager Interface and CLI commands.
- * The unformatted text is considered as a single value of a field
- * named 'Opaque-data'.
- *
- * At the moment the output format is the following (but it may
- * change depending on future requirements so don't count too
- * much on it when writing applications):
- *
- * General: the unformatted text is used as a value of
- * XML output: to be completed
- * Each section is within <response type="object" id="xxx">
- * where xxx is taken from ajaxdest variable or defaults to unknown
- * Each row is reported as an attribute Name="value" of an XML
- * entity named from the variable ajaxobjtype, default to "generic"
- *
- * HTML output:
- * each Name-value pair is output as a single row of a two-column table.
- * Sections (blank lines in the input) are separated by a <HR>
- *
- */
-static char *xml_translate(char *in, struct ast_variable *vars, enum output_format format)
-{
- struct ast_variable *v;
- char *dest = NULL;
- char *out, *tmp, *var, *val;
- char *objtype = NULL;
- int colons = 0;
- int breaks = 0;
- size_t len;
- int in_data = 0; /* parsing data */
- int escaped = 0;
- int inobj = 0;
- int x;
- int xml = (format == FORMAT_XML);
-
- for (v = vars; v; v = v->next) {
- if (!dest && !strcasecmp(v->name, "ajaxdest"))
- dest = v->value;
- else if (!objtype && !strcasecmp(v->name, "ajaxobjtype"))
- objtype = v->value;
- }
- if (!dest)
- dest = "unknown";
- if (!objtype)
- objtype = "generic";
-
- /* determine how large is the response.
- * This is a heuristic - counting colons (for headers),
- * newlines (for extra arguments), and escaped chars.
- * XXX needs to be checked carefully for overflows.
- * Even better, use some code that allows extensible strings.
- */
- for (x = 0; in[x]; x++) {
- if (in[x] == ':')
- colons++;
- else if (in[x] == '\n')
- breaks++;
- else if (strchr("&\"<>", in[x]))
- escaped++;
- }
- len = (size_t) (strlen(in) + colons * 5 + breaks * (40 + strlen(dest) + strlen(objtype)) + escaped * 10); /* foo="bar", "<response type=\"object\" id=\"dest\"", "&" */
- out = ast_malloc(len);
- if (!out)
- return NULL;
- tmp = out;
- /* we want to stop when we find an empty line */
- while (in && *in) {
- in = ast_skip_blanks(in); /* trailing \n from before */
- val = strsep(&in, "\r\n"); /* mark start and end of line */
- ast_trim_blanks(val);
- ast_verbose("inobj %d in_data %d line <%s>\n", inobj, in_data, val);
- if (ast_strlen_zero(val)) {
- if (in_data) { /* close data */
- ast_build_string(&tmp, &len, xml ? "'" : "</td></tr>\n");
- in_data = 0;
- }
- ast_build_string(&tmp, &len, xml ? " /></response>\n" :
- "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
- inobj = 0;
- continue;
- }
- /* we expect Name: value lines */
- if (in_data) {
- var = NULL;
- } else {
- var = strsep(&val, ":");
- if (val) { /* found the field name */
- val = ast_skip_blanks(val);
- ast_trim_blanks(var);
- } else { /* field name not found, move to opaque mode */
- val = var;
- var = "Opaque-data";
- }
- }
- if (!inobj) {
- if (xml)
- ast_build_string(&tmp, &len, "<response type='object' id='%s'><%s", dest, objtype);
- else
- ast_build_string(&tmp, &len, "<body>\n");
- inobj = 1;
- }
- if (!in_data) { /* build appropriate line start */
- ast_build_string(&tmp, &len, xml ? " " : "<tr><td>");
- xml_copy_escape(&tmp, &len, var, xml ? 1 | 2 : 0);
- ast_build_string(&tmp, &len, xml ? "='" : "</td><td>");
- if (!strcmp(var, "Opaque-data"))
- in_data = 1;
- }
- xml_copy_escape(&tmp, &len, val, 0); /* data field */
- if (!in_data)
- ast_build_string(&tmp, &len, xml ? "'" : "</td></tr>\n");
- else
- ast_build_string(&tmp, &len, xml ? "\n" : "<br>\n");
- }
- if (inobj)
- ast_build_string(&tmp, &len, xml ? " /></response>\n" :
- "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
- return out;
-}
static struct ast_manager_user *ast_get_manager_by_name_locked(const char *name)
{
@@ -427,27 +315,6 @@
return user;
}
-void astman_append(struct mansession *s, const char *fmt, ...)
-{
- va_list ap;
- struct ast_dynamic_str *buf;
-
- if (!(buf = ast_dynamic_str_thread_get(&astman_append_buf, ASTMAN_APPEND_BUF_INITSIZE)))
- return;
-
- va_start(ap, fmt);
- ast_dynamic_str_thread_set_va(&buf, 0, &astman_append_buf, fmt, ap);
- va_end(ap);
-
- if (s->fd > -1)
- ast_carefulwrite(s->fd, buf->str, strlen(buf->str), s->writetimeout);
- else {
- if (!s->outputstr && !(s->outputstr = ast_calloc(1, sizeof(*s->outputstr))))
- return;
-
- ast_dynamic_str_append(&s->outputstr, 0, "%s", buf->str);
- }
-}
static int handle_showmancmd(int fd, int argc, char *argv[])
{
@@ -730,6 +597,31 @@
return head;
}
+/*
+ * utility functions for creating AMI replies
+ */
+void astman_append(struct mansession *s, const char *fmt, ...)
+{
+ va_list ap;
+ struct ast_dynamic_str *buf;
+
+ if (!(buf = ast_dynamic_str_thread_get(&astman_append_buf, ASTMAN_APPEND_BUF_INITSIZE)))
+ return;
+
+ va_start(ap, fmt);
+ ast_dynamic_str_thread_set_va(&buf, 0, &astman_append_buf, fmt, ap);
+ va_end(ap);
+
+ if (s->fd > -1)
+ ast_carefulwrite(s->fd, buf->str, strlen(buf->str), s->writetimeout);
+ else {
+ if (!s->outputstr && !(s->outputstr = ast_calloc(1, sizeof(*s->outputstr))))
+ return;
+
+ ast_dynamic_str_append(&s->outputstr, 0, "%s", buf->str);
+ }
+}
+
/*! \note NOTE: XXX this comment is unclear and possibly wrong.
Callers of astman_send_error(), astman_send_response() or astman_send_ack() must EITHER
hold the session lock _or_ be running in an action callback (in which case s->busy will
@@ -777,70 +669,7 @@
astman_send_response(s, m, "Success", MSG_MOREDATA);
}
-/*! Tells you if smallstr exists inside bigstr
- which is delim by delim and uses no buf or stringsep
- ast_instring("this|that|more","this",'|') == 1;
-
- feel free to move this to app.c -anthm */
-static int ast_instring(const char *bigstr, const char *smallstr, const char delim)
-{
- const char *val = bigstr, *next;
-
- do {
- if ((next = strchr(val, delim))) {
- if (!strncmp(val, smallstr, (next - val)))
- return 1;
- else
- continue;
- } else
- return !strcmp(smallstr, val);
-
- } while (*(val = (next + 1)));
-
- return 0;
-}
-
-static int get_perm(const char *instr)
-{
- int x = 0, ret = 0;
-
- if (!instr)
- return 0;
-
- for (x = 0; x < (sizeof(perms) / sizeof(perms[0])); x++) {
- if (ast_instring(instr, perms[x].label, ','))
- ret |= perms[x].num;
- }
-
- return ret;
-}
-
-/*
- * A number returns itself, false returns 0, true returns all flags,
- * other strings return the flags that are set.
- */
-static int ast_strings_to_mask(const char *string)
-{
- const char *p;
-
- if (ast_strlen_zero(string))
- return -1;
-
- for (p = string; *p; p++)
- if (*p < '0' || *p > '9')
- break;
- if (!p) /* all digits */
- return atoi(string);
- if (ast_false(string))
- return 0;
- if (ast_true(string)) { /* all permissions */
- int x, ret = 0;
- for (x=0; x<sizeof(perms) / sizeof(perms[0]); x++)
- ret |= perms[x].num;
- return ret;
- }
- return get_perm(string);
-}
+
/*! \brief
Rather than braindead on,off this now can also accept a specific int mask value
@@ -858,6 +687,13 @@
return maskint;
}
+/*
+ * Here we start with action_ handlers for AMI actions,
+ * and the internal functions used by them.
+ * Generally, the handlers are called action_foo()
+ */
+
+/* helper function for action_login() */
static int authenticate(struct mansession *s, struct message *m)
{
char *user = astman_get_header(m, "Username");
@@ -1006,7 +842,7 @@
return 0;
}
-
+/* helper function for action_updateconfig */
static void handle_updates(struct mansession *s, struct message *m, struct ast_config *cfg)
{
int x;
@@ -1570,6 +1406,22 @@
}
/* helper function for originate */
+struct fast_originate_helper {
+ char tech[AST_MAX_MANHEADER_LEN];
+ char data[AST_MAX_MANHEADER_LEN];
+ int timeout;
+ char app[AST_MAX_APP];
+ char appdata[AST_MAX_MANHEADER_LEN];
+ char cid_name[AST_MAX_MANHEADER_LEN];
+ char cid_num[AST_MAX_MANHEADER_LEN];
+ char context[AST_MAX_CONTEXT];
+ char exten[AST_MAX_EXTENSION];
+ char idtext[AST_MAX_MANHEADER_LEN];
+ char account[AST_MAX_ACCOUNT_CODE];
+ int priority;
+ struct ast_variable *vars;
+};
+
static void *fast_originate(void *data)
{
struct fast_originate_helper *in = data;
@@ -1911,6 +1763,15 @@
}
/*
+ * Done with the action handlers here, we start with the code in charge
+ * of accepting connections and serving them.
+ * accept_thread() forks a new thread for each connection, session_do(),
+ * which in turn calls get_input() repeatedly until a full message has
+ * been accumulated, and then invokes process_message() to pass it to
+ * the appropriate handler.
+ */
+
+/*
* Process an AMI message, performing desired action.
* Return 0 on success, -1 on error that require the session to be destroyed.
*/
@@ -2164,6 +2025,10 @@
return NULL;
}
+/*
+ * events are appended to a queue from where they
+ * can be dispatched to clients.
+ */
static int append_event(const char *str, int category)
{
struct eventqent *tmp, *prev = NULL;
@@ -2237,6 +2102,9 @@
return 0;
}
+/*
+ * support functions to register/unregister AMI action handlers,
+ */
int ast_manager_unregister(char *action)
{
struct manager_action *cur = first_action, *prev = first_action;
@@ -2317,6 +2185,18 @@
/*! @}
END Doxygen group */
+/*
+ * The following are support functions for AMI-over-http.
+ * The common entry point is generic_http_callback(),
+ * which extracts HTTP header and URI fields and reformats
+ * them into AMI messages, locates a proper session
+ * (using the mansession_id Cookie or GET variable),
+ * and calls process_message() as for regular AMI clients.
+ * When done, the output (which goes to a temporary file)
+ * is read back into a buffer and reformatted as desired,
+ * then fed back to the client over the original socket.
+ */
+
static struct mansession *find_session(unsigned long ident)
{
struct mansession *s;
@@ -2335,7 +2215,6 @@
return s;
}
-
static void vars2msg(struct message *m, struct ast_variable *vars)
{
int x;
@@ -2347,6 +2226,177 @@
}
}
+/*
+ * convert to xml with various conversion:
+ * mode & 1 -> lowercase;
+ * mode & 2 -> replace non-alphanumeric chars with underscore
+ */
+static void xml_copy_escape(char **dst, size_t *maxlen, const char *src, int mode)
+{
+ for ( ; *src && *maxlen > 6; src++) {
+ if ( (mode & 2) && !isalnum(*src)) {
+ *(*dst)++ = '_';
+ (*maxlen)--;
+ continue;
+ }
+ switch (*src) {
+ case '<':
+ strcpy(*dst, "<");
+ (*dst) += 4;
+ *maxlen -= 4;
+ break;
+ case '>':
+ strcpy(*dst, ">");
+ (*dst) += 4;
+ *maxlen -= 4;
+ break;
+ case '\"':
+ strcpy(*dst, """);
+ (*dst) += 6;
+ *maxlen -= 6;
+ break;
+ case '\'':
+ strcpy(*dst, "'");
+ (*dst) += 6;
+ *maxlen -= 6;
+ break;
+ case '&':
+ strcpy(*dst, "&");
+ (*dst) += 5;
+ *maxlen -= 5;
+ break;
+
+ default:
+ *(*dst)++ = mode ? tolower(*src) : *src;
+ (*maxlen)--;
+ }
+ }
+}
+
+/*! \brief Convert the input into XML or HTML.
+ * The input is supposed to be a sequence of lines of the form
+ * Name: value
+ * optionally followed by a blob of unformatted text.
+ * A blank line is a section separator. Basically, this is a
+ * mixture of the format of Manager Interface and CLI commands.
+ * The unformatted text is considered as a single value of a field
+ * named 'Opaque-data'.
+ *
+ * At the moment the output format is the following (but it may
+ * change depending on future requirements so don't count too
+ * much on it when writing applications):
+ *
+ * General: the unformatted text is used as a value of
+ * XML output: to be completed
+ * Each section is within <response type="object" id="xxx">
+ * where xxx is taken from ajaxdest variable or defaults to unknown
+ * Each row is reported as an attribute Name="value" of an XML
+ * entity named from the variable ajaxobjtype, default to "generic"
+ *
+ * HTML output:
+ * each Name-value pair is output as a single row of a two-column table.
+ * Sections (blank lines in the input) are separated by a <HR>
+ *
+ */
+static char *xml_translate(char *in, struct ast_variable *vars, enum output_format format)
+{
+ struct ast_variable *v;
+ char *dest = NULL;
+ char *out, *tmp, *var, *val;
+ char *objtype = NULL;
+ int colons = 0;
+ int breaks = 0;
+ size_t len;
+ int in_data = 0; /* parsing data */
+ int escaped = 0;
+ int inobj = 0;
+ int x;
+ int xml = (format == FORMAT_XML);
+
+ for (v = vars; v; v = v->next) {
+ if (!dest && !strcasecmp(v->name, "ajaxdest"))
+ dest = v->value;
+ else if (!objtype && !strcasecmp(v->name, "ajaxobjtype"))
+ objtype = v->value;
+ }
+ if (!dest)
+ dest = "unknown";
+ if (!objtype)
+ objtype = "generic";
+
+ /* determine how large is the response.
+ * This is a heuristic - counting colons (for headers),
+ * newlines (for extra arguments), and escaped chars.
+ * XXX needs to be checked carefully for overflows.
+ * Even better, use some code that allows extensible strings.
+ */
+ for (x = 0; in[x]; x++) {
+ if (in[x] == ':')
+ colons++;
+ else if (in[x] == '\n')
+ breaks++;
+ else if (strchr("&\"<>", in[x]))
+ escaped++;
+ }
+ len = (size_t) (strlen(in) + colons * 5 + breaks * (40 + strlen(dest) + strlen(objtype)) + escaped * 10); /* foo="bar", "<response type=\"object\" id=\"dest\"", "&" */
+ out = ast_malloc(len);
+ if (!out)
+ return NULL;
+ tmp = out;
+ /* we want to stop when we find an empty line */
+ while (in && *in) {
+ in = ast_skip_blanks(in); /* trailing \n from before */
+ val = strsep(&in, "\r\n"); /* mark start and end of line */
+ ast_trim_blanks(val);
+ ast_verbose("inobj %d in_data %d line <%s>\n", inobj, in_data, val);
+ if (ast_strlen_zero(val)) {
+ if (in_data) { /* close data */
+ ast_build_string(&tmp, &len, xml ? "'" : "</td></tr>\n");
+ in_data = 0;
+ }
+ ast_build_string(&tmp, &len, xml ? " /></response>\n" :
+ "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+ inobj = 0;
+ continue;
+ }
+ /* we expect Name: value lines */
+ if (in_data) {
+ var = NULL;
+ } else {
+ var = strsep(&val, ":");
+ if (val) { /* found the field name */
+ val = ast_skip_blanks(val);
+ ast_trim_blanks(var);
+ } else { /* field name not found, move to opaque mode */
+ val = var;
+ var = "Opaque-data";
+ }
+ }
+ if (!inobj) {
+ if (xml)
+ ast_build_string(&tmp, &len, "<response type='object' id='%s'><%s", dest, objtype);
+ else
+ ast_build_string(&tmp, &len, "<body>\n");
+ inobj = 1;
+ }
+ if (!in_data) { /* build appropriate line start */
+ ast_build_string(&tmp, &len, xml ? " " : "<tr><td>");
+ xml_copy_escape(&tmp, &len, var, xml ? 1 | 2 : 0);
+ ast_build_string(&tmp, &len, xml ? "='" : "</td><td>");
+ if (!strcmp(var, "Opaque-data"))
+ in_data = 1;
+ }
+ xml_copy_escape(&tmp, &len, val, 0); /* data field */
+ if (!in_data)
+ ast_build_string(&tmp, &len, xml ? "'" : "</td></tr>\n");
+ else
+ ast_build_string(&tmp, &len, xml ? "\n" : "<br>\n");
+ }
+ if (inobj)
+ ast_build_string(&tmp, &len, xml ? " /></response>\n" :
+ "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+ return out;
+}
static char *generic_http_callback(enum output_format format,
struct sockaddr_in *requestor, const char *uri,
More information about the asterisk-commits
mailing list