[asterisk-commits] trunk r16850 - in /trunk: ./ configs/ doc/ include/asterisk/ static-http/

asterisk-commits at lists.digium.com asterisk-commits at lists.digium.com
Sat Apr 1 01:49:56 MST 2006


Author: markster
Date: Sat Apr  1 02:49:54 2006
New Revision: 16850

URL: http://svn.digium.com/view/asterisk?rev=16850&view=rev
Log:
Flesh out the remainder of the manager + http changes and create a sample application to partially 
demonstrate the capability of manager over http.

Added:
    trunk/doc/ajam.txt   (with props)
    trunk/static-http/
    trunk/static-http/ajamdemo.html   (with props)
    trunk/static-http/astman.css   (with props)
    trunk/static-http/astman.js   (with props)
    trunk/static-http/prototype.js   (with props)
Modified:
    trunk/Makefile
    trunk/configs/http.conf.sample
    trunk/configs/manager.conf.sample
    trunk/http.c
    trunk/include/asterisk/http.h
    trunk/manager.c

Modified: trunk/Makefile
URL: http://svn.digium.com/view/asterisk/trunk/Makefile?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/Makefile (original)
+++ trunk/Makefile Sat Apr  1 02:49:54 2006
@@ -566,6 +566,13 @@
 
 datafiles: all
 	if [ x`$(ID) -un` = xroot ]; then sh build_tools/mkpkgconfig $(DESTDIR)/usr/lib/pkgconfig; fi
+	# Should static HTTP be installed during make samples or even with its own target ala
+	# webvoicemail?  There are portions here that *could* be customized but might also be
+	# improved a lot.  I'll put it here for now.
+	mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/static-http
+	for x in static-http/*; do \
+		install -m 644 $$x $(DESTDIR)$(ASTVARLIBDIR)/static-http ; \
+	done
 	mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/sounds/digits
 	mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/sounds/priv-callerintros
 	for x in sounds/digits/*.gsm; do \

Modified: trunk/configs/http.conf.sample
URL: http://svn.digium.com/view/asterisk/trunk/configs/http.conf.sample?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/configs/http.conf.sample (original)
+++ trunk/configs/http.conf.sample Sat Apr  1 02:49:54 2006
@@ -4,15 +4,20 @@
 ;
 [general]
 ;
-; Whether HTTP interface is enabled or not.
+; Whether HTTP interface is enabled or not.  Default is no.
 ;
-enabled=no
+;enabled=yes
 ;
-; Address to bind to
+; Whether Asterisk should serve static content from http-static
+; Default is no.
+;
+;enablestatic=yes
+;
+; Address to bind to.  Default is 0.0.0.0
 ;
 bindaddr=127.0.0.1
 ;
-; Port to bind to
+; Port to bind to (default is 8088)
 ;
 bindport=8088
 ;

Modified: trunk/configs/manager.conf.sample
URL: http://svn.digium.com/view/asterisk/trunk/configs/manager.conf.sample?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/configs/manager.conf.sample (original)
+++ trunk/configs/manager.conf.sample Sat Apr  1 02:49:54 2006
@@ -13,11 +13,18 @@
 ; ---------------------------- SECURITY NOTE -------------------------------
 ; Note that you should not enable the AMI on a public IP address. If needed,
 ; block this TCP port with iptables (or another FW software) and reach it
-; with IPsec, SSH, or SSL vpn tunnel
+; with IPsec, SSH, or SSL vpn tunnel.  You can also make the manager 
+; interface available over http if Asterisk's http server is enabled in
+; http.conf and if both "enabled" and "webenabled" are set to yes in
+; this file.  Both default to no.  httptimeout provides the maximum 
+; timeout in seconds before a web based session is discarded.  The 
+; default is 60 seconds.
 ;
 [general]
 enabled = no
+;webenabled = yes
 port = 5038
+;httptimeout = 60
 bindaddr = 0.0.0.0
 ;displayconnects = yes
 ;

Added: trunk/doc/ajam.txt
URL: http://svn.digium.com/view/asterisk/trunk/doc/ajam.txt?rev=16850&view=auto
==============================================================================
--- trunk/doc/ajam.txt (added)
+++ trunk/doc/ajam.txt Sat Apr  1 02:49:54 2006
@@ -1,0 +1,91 @@
+Asynchronous Javascript Asterisk Manger (AJAM)
+==============================================
+
+AJAM is a new technology which allows web browsers or other HTTP enabled 
+applications and web pages to directly access the Asterisk Manger 
+Interface (AMI) via HTTP.  Setting up your server to process AJAM 
+involves a few steps:
+
+Setup the Asterisk HTTP server
+------------------------------
+
+1) Uncomment the line "enabled=yes" in /etc/asterisk/http.conf to enable
+   Asterisk's builtin micro HTTP server.
+
+2) If you want Asterisk to actually deliver simple HTML pages, CSS, 
+   javascript, etc. you should uncomment "enablestatic=yes"
+
+3) Adjust your "bindaddr" and "bindport" settings as appropriate for 
+   your desired accessibility
+
+4) Adjust your "prefix" if appropriate, which must be the beginning of
+   any URI on the server to match.  The default is "asterisk" and the 
+   rest of these instructions assume that value.
+
+Allow Manager Access via HTTP
+-----------------------------
+
+1) Make sure you have both "enabled = yes" and "webenabled = yes" setup 
+   in /etc/asterisk/manager.conf
+
+2) You may also use "httptimeout" to set a default timeout for HTTP 
+   connections.
+
+3) Make sure you have a manager username/secret
+
+Once those configurations are complete you can reload or restart 
+Asterisk and you should be able to point your web browser to specific 
+URI's which will allow you to access various web functions.  A complete 
+list can be found by typing "show http" at the Asterisk CLI.
+
+examples:
+
+http://localhost:8088/asterisk/manager?action=login&username=foo&secret=bar
+
+This logs you into the manager interface's "HTML" view.  Once you're 
+logged in, Asterisk stores a cookie on your browser (valid for the 
+length of httptimeout) which is used to connect to the same session.  
+
+http://localhost:8088/asterisk/rawman?action=status
+
+Assuming you've already logged into manager, this URI will give you a 
+"raw" manager output for the "status" command.
+
+http://localhost:8088/asterisk/mxml?action=status
+
+This will give you the same status view but represented as AJAX data, 
+theoretically compatible with RICO (http://www.openrico.org).
+
+http://localhost:8088/asterisk/static/ajamdemo.html
+
+If you have enabled static content support and have done a make install, 
+Asterisk will serve up a demo page which presents a live, but very 
+basic, "astman" like interface.  You can login with your username/secret 
+for manager and have a basic view of channels as well as transfer and 
+hangup calls.  It's only tested in Firefox, but could probably be made
+to run in other browsers as well.
+
+A sample library (astman.js) is included to help ease the creation of 
+manager HTML interfaces.
+
+Note that for the demo, there is no need for *any* external web server.
+
+Integration with other web servers 
+---------------------------------- 
+
+Asterisk's micro HTTP server is *not* designed to replace a general 
+purpose web server and it is intentionally created to provide only the 
+minimal interfaces required.  Even without the addition of an external 
+web server, one can use Asterisk's interfaces to implement screen pops 
+and similar tools pulling data from other web servers using iframes, 
+div's etc.  If you want to integrate CGI's, databases, PHP, etc.  you 
+will likely need to use a more traditional web server like Apache and 
+link in your Asterisk micro HTTP server with something like this:
+
+ProxyPass /asterisk http://localhost:8088/asterisk
+
+This is a fairly new technology so I'd love to hear if it's useful for 
+you!
+
+Mark
+

Propchange: trunk/doc/ajam.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: trunk/doc/ajam.txt
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision

Propchange: trunk/doc/ajam.txt
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: trunk/http.c
URL: http://svn.digium.com/view/asterisk/trunk/http.c?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/http.c (original)
+++ trunk/http.c Sat Apr  1 02:49:54 2006
@@ -33,16 +33,20 @@
 #include <netinet/in.h>
 #include <sys/time.h>
 #include <sys/socket.h>
+#include <sys/stat.h>
 #include <sys/signal.h>
 #include <arpa/inet.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <pthread.h>
 
+#include "asterisk.h"
 #include "asterisk/cli.h"
 #include "asterisk/http.h"
 #include "asterisk/utils.h"
 #include "asterisk/strings.h"
+#include "asterisk/options.h"
+#include "asterisk/config.h"
 
 #define MAX_PREFIX 80
 #define DEFAULT_PREFIX "asterisk"
@@ -61,6 +65,100 @@
 static char prefix[MAX_PREFIX];
 static int prefix_len = 0;
 static struct sockaddr_in oldsin;
+static int enablestatic=0;
+
+/* Limit the kinds of files we're willing to serve up */
+static struct {
+	char *ext;
+	char *mtype;
+} mimetypes[] = {
+	{ "png", "image/png" },
+	{ "jpg", "image/jpeg" },
+	{ "js", "application/x-javascript" },
+	{ "wav", "audio/x-wav" },
+	{ "mp3", "audio/mpeg" },
+};
+
+static char *ftype2mtype(const char *ftype, char *wkspace, int wkspacelen)
+{
+	int x;
+	if (ftype) {
+		for (x=0;x<sizeof(mimetypes) / sizeof(mimetypes[0]); x++) {
+			if (!strcasecmp(ftype, mimetypes[x].ext))
+				return mimetypes[x].mtype;
+		}
+	}
+	snprintf(wkspace, wkspacelen, "text/%s", ftype ? ftype : "plain");
+	return wkspace;
+}
+
+static char *static_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
+{
+	char result[4096];
+	char *c=result;
+	char *path;
+	char *ftype, *mtype;
+	char wkspace[80];
+	struct stat st;
+	int len;
+	int fd;
+	void *blob;
+
+	/* Yuck.  I'm not really sold on this, but if you don't deliver static content it makes your configuration 
+	   substantially more challenging, but this seems like a rather irritating feature creep on Asterisk. */
+	if (!enablestatic || ast_strlen_zero(uri))
+		goto out403;
+	/* Disallow any funny filenames at all */
+	if ((uri[0] < 33) || strchr("./|~@#$%^&*() \t", uri[0]))
+		goto out403;
+	if (strstr(uri, "/.."))
+		goto out403;
+		
+	if ((ftype = strrchr(uri, '.')))
+		ftype++;
+	mtype=ftype2mtype(ftype, wkspace, sizeof(wkspace));
+	
+	/* Cap maximum length */
+	len = strlen(uri) + strlen(ast_config_AST_VAR_DIR) + strlen("/static-http/") + 5;
+	if (len > 1024)
+		goto out403;
+		
+	path = alloca(len);
+	sprintf(path, "%s/static-http/%s", ast_config_AST_VAR_DIR, uri);
+	if (stat(path, &st))
+		goto out404;
+	if (S_ISDIR(st.st_mode))
+		goto out404;
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		goto out403;
+	
+	len = st.st_size + strlen(mtype) + 40;
+	
+	blob = malloc(len);
+	if (blob) {
+		c = blob;
+		sprintf(c, "Content-type: %s\r\n\r\n", mtype);
+		c += strlen(c);
+		*contentlength = read(fd, c, st.st_size);
+		if (*contentlength < 0) {
+			close(fd);
+			free(blob);
+			goto out403;
+		}
+	}
+	return blob;
+
+out404:
+	*status = 404;
+	*title = strdup("Not Found");
+	return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
+
+out403:
+	*status = 403;
+	*title = strdup("Access Denied");
+	return ast_http_error(403, "Access Denied", NULL, "Sorry, I cannot let you do that, Dave.");
+}
 
 
 static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
@@ -86,7 +184,15 @@
 	ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
 	v = vars;
 	while(v) {
-		ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
+		if (strncasecmp(v->name, "cookie_", 7))
+			ast_build_string(&c, &reslen, "<tr><td><i>Submitted Variable '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
+		v = v->next;
+	}
+	ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+	v = vars;
+	while(v) {
+		if (!strncasecmp(v->name, "cookie_", 7))
+			ast_build_string(&c, &reslen, "<tr><td><i>Cookie '%s'</i></td><td>%s</td></tr>\r\n", v->name, v->value);
 		v = v->next;
 	}
 	ast_build_string(&c, &reslen, "</table><center><font size=\"-1\"><i>Asterisk and Digium are registered trademarks of Digium, Inc.</i></font></center></body>\r\n");
@@ -98,6 +204,13 @@
 	.description = "Asterisk HTTP General Status",
 	.uri = "httpstatus",
 	.has_subtree = 0,
+};
+	
+static struct ast_http_uri staticuri = {
+	.callback = static_callback,
+	.description = "Asterisk HTTP Static Delivery",
+	.uri = "static",
+	.has_subtree = 1,
 };
 	
 char *ast_http_error(int status, const char *title, const char *extra_header, const char *text)
@@ -153,7 +266,7 @@
 	}
 }
 
-static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength)
+static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **title, int *contentlength, struct ast_variable **cookies)
 {
 	char *c;
 	char *turi;
@@ -176,9 +289,9 @@
 			if (val) {
 				*val = '\0';
 				val++;
+				ast_uri_decode(val);
 			} else 
 				val = "";
-			ast_uri_decode(val);
 			ast_uri_decode(var);
 			if ((v = ast_variable_new(var, val))) {
 				if (vars)
@@ -189,6 +302,11 @@
 			}
 		}
 	}
+	if (prev)
+		prev->next = *cookies;
+	else
+		vars = *cookies;
+	*cookies = NULL;
 	ast_uri_decode(uri);
 	if (!strncasecmp(uri, prefix, prefix_len)) {
 		uri += prefix_len;
@@ -227,9 +345,12 @@
 static void *ast_httpd_helper_thread(void *data)
 {
 	char buf[4096];
+	char cookie[4096];
 	char timebuf[256];
 	struct ast_http_server_instance *ser = data;
+	struct ast_variable *var, *prev=NULL, *vars=NULL;
 	char *uri, *c, *title=NULL;
+	char *vname, *vval;
 	int status = 200, contentlength = 0;
 	time_t t;
 
@@ -252,25 +373,68 @@
 				*c = '\0';
 			}
 		}
+
+		while (fgets(cookie, sizeof(cookie), ser->f)) {
+			/* Trim trailing characters */
+			while(!ast_strlen_zero(cookie) && (cookie[strlen(cookie) - 1] < 33)) {
+				cookie[strlen(cookie) - 1] = '\0';
+			}
+			if (ast_strlen_zero(cookie))
+				break;
+			if (!strncasecmp(cookie, "Cookie: ", 8)) {
+				vname = cookie + 8;
+				vval = strchr(vname, '=');
+				if (vval) {
+					/* Ditch the = and the quotes */
+					*vval = '\0';
+					vval++;
+					if (*vval)
+						vval++;
+					if (strlen(vval))
+						vval[strlen(vval) - 1] = '\0';
+					var = ast_variable_new(vname, vval);
+					if (var) {
+						if (prev)
+							prev->next = var;
+						else
+							vars = var;
+						prev = var;
+					}
+				}
+			}
+		}
+
 		if (*uri) {
 			if (!strcasecmp(buf, "get")) 
-				c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength);
+				c = handle_uri(&ser->requestor, uri, &status, &title, &contentlength, &vars);
 			else 
 				c = ast_http_error(501, "Not Implemented", NULL, "Attempt to use unimplemented / unsupported method");\
 		} else 
 			c = ast_http_error(400, "Bad Request", NULL, "Invalid Request");
+
+		/* If they aren't mopped up already, clean up the cookies */
+		if (vars)
+			ast_variables_destroy(vars);
+
 		if (!c)
 			c = ast_http_error(500, "Internal Error", NULL, "Internal Server Error");
 		if (c) {
 			time(&t);
 			strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&t));
-			ast_cli(ser->fd, "HTTP/1.1 GET %d %s\r\n", status, title ? title : "OK");
+			ast_cli(ser->fd, "HTTP/1.1 %d %s\r\n", status, title ? title : "OK");
 			ast_cli(ser->fd, "Server: Asterisk\r\n");
 			ast_cli(ser->fd, "Date: %s\r\n", timebuf);
-			if (contentlength)
-				ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
 			ast_cli(ser->fd, "Connection: close\r\n");
-			ast_cli(ser->fd, "%s", c);
+			if (contentlength) {
+				char *tmp;
+				tmp = strstr(c, "\r\n\r\n");
+				if (tmp) {
+					ast_cli(ser->fd, "Content-length: %d\r\n", contentlength);
+					write(ser->fd, c, (tmp + 4 - c));
+					write(ser->fd, tmp + 4, contentlength);
+				}
+			} else
+				ast_cli(ser->fd, "%s", c);
 			free(c);
 		}
 		if (title)
@@ -297,25 +461,40 @@
 				ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
 			continue;
 		}
-		if (!(ser = ast_calloc(1, sizeof(*ser)))) {
-			close(fd);
-			continue;
-		}
-		ser->fd = fd;
-		if ((ser->f = fdopen(ser->fd, "w+"))) {
-			if (ast_pthread_create(&launched, NULL, ast_httpd_helper_thread, ser)) {
-				ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
-				fclose(ser->f);
+		ser = ast_calloc(1, sizeof(*ser));
+		if (ser) {
+			ser->fd = fd;
+			memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
+			if ((ser->f = fdopen(ser->fd, "w+"))) {
+				if (ast_pthread_create(&launched, NULL, ast_httpd_helper_thread, ser)) {
+					ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
+					fclose(ser->f);
+					free(ser);
+				}
+			} else {
+				ast_log(LOG_WARNING, "fdopen failed!\n");
+				close(ser->fd);
 				free(ser);
 			}
 		} else {
-			ast_log(LOG_WARNING, "fdopen failed!\n");
 			close(ser->fd);
 			free(ser);
 		}
 	}
 	return NULL;
 }
+
+char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, int buflen)
+{
+	char *c;
+	c = buf;
+	ast_build_string(&c, &buflen, "Set-Cookie: %s=\"%s\"; Version=\"1\"", var, val);
+	if (expires)
+		ast_build_string(&c, &buflen, "; Max-Age=%d", expires);
+	ast_build_string(&c, &buflen, "\r\n");
+	return buf;
+}
+
 
 static void http_server_start(struct sockaddr_in *sin)
 {
@@ -383,6 +562,7 @@
 	struct ast_config *cfg;
 	struct ast_variable *v;
 	int enabled=0;
+	int newenablestatic=0;
 	struct sockaddr_in sin;
 	struct hostent *hp;
 	struct ast_hostent ahp;
@@ -396,6 +576,8 @@
 		while(v) {
 			if (!strcasecmp(v->name, "enabled"))
 				enabled = ast_true(v->value);
+			else if (!strcasecmp(v->name, "enablestatic"))
+				newenablestatic = ast_true(v->value);
 			else if (!strcasecmp(v->name, "bindport"))
 				sin.sin_port = ntohs(atoi(v->value));
 			else if (!strcasecmp(v->name, "bindaddr")) {
@@ -416,6 +598,7 @@
 		ast_copy_string(prefix, newprefix, sizeof(prefix));
 		prefix_len = strlen(prefix);
 	}
+	enablestatic = newenablestatic;
 	http_server_start(&sin);
 	return 0;
 }
@@ -462,6 +645,7 @@
 int ast_http_init(void)
 {
 	ast_http_uri_link(&statusuri);
+	ast_http_uri_link(&staticuri);
 	ast_cli_register_multiple(http_cli, sizeof(http_cli) / sizeof(http_cli[0]));
 	return __ast_http_load(0);
 }

Modified: trunk/include/asterisk/http.h
URL: http://svn.digium.com/view/asterisk/trunk/include/asterisk/http.h?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/include/asterisk/http.h (original)
+++ trunk/include/asterisk/http.h Sat Apr  1 02:49:54 2006
@@ -58,6 +58,8 @@
 /* Destroy an HTTP server */
 void ast_http_uri_unlink(struct ast_http_uri *urihandler);
 
+char *ast_http_setcookie(const char *var, const char *val, int expires, char *buf, int buflen);
+
 int ast_http_init(void);
 int ast_http_reload(void);
 

Modified: trunk/manager.c
URL: http://svn.digium.com/view/asterisk/trunk/manager.c?rev=16850&r1=16849&r2=16850&view=diff
==============================================================================
--- trunk/manager.c (original)
+++ trunk/manager.c Sat Apr  1 02:49:54 2006
@@ -35,6 +35,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <ctype.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <netdb.h>
@@ -64,6 +65,7 @@
 #include "asterisk/md5.h"
 #include "asterisk/acl.h"
 #include "asterisk/utils.h"
+#include "asterisk/http.h"
 
 struct fast_originate_helper {
 	char tech[AST_MAX_MANHEADER_LEN];
@@ -86,6 +88,7 @@
 static int asock = -1;
 static int displayconnects = 1;
 static int timestampevents = 0;
+static int httptimeout = 60;
 
 static pthread_t t;
 AST_MUTEX_DEFINE_STATIC(sessionlock);
@@ -119,6 +122,18 @@
 	int busy;
 	/*! Whether or not we're "dead" */
 	int dead;
+	/*! Whether an HTTP manager is in use */
+	int inuse;
+	/*! Whether an HTTP session should be destroyed */
+	int needdestroy;
+	/*! Whether an HTTP session has someone waiting on events */
+	pthread_t waiting_thread;
+	/*! Unique manager identifer */
+	unsigned long managerid;
+	/*! Session timeout if HTTP */
+	time_t sessiontimeout;
+	/*! Output from manager interface */
+	char *outputstr;
 	/*! Logged in username */
 	char username[80];
 	/*! Authentication challenge */
@@ -212,11 +227,168 @@
 	return ret;
 }
 
+static void xml_copy_escape(char **dst, int *maxlen, const char *src, int lower)
+{
+	while (*src && (*maxlen > 6)) {
+		switch(*src) {
+		case '<':
+			strcpy(*dst, "&lt;");
+			(*dst) += 4;
+			*maxlen -= 4;
+			break;
+		case '>':
+			strcpy(*dst, "&gt;");
+			(*dst) += 4;
+			*maxlen -= 4;
+			break;
+		case '\"':
+			strcpy(*dst, "&quot;");
+			(*dst) += 6;
+			*maxlen -= 6;
+			break;
+		case '\'':
+			strcpy(*dst, "&apos;");
+			(*dst) += 6;
+			*maxlen -= 6;
+			break;
+		case '&':
+			strcpy(*dst, "&amp;");
+			(*dst) += 4;
+			*maxlen -= 4;
+			break;		
+		default:
+			*(*dst)++ = lower ? tolower(*src) : *src;
+			(*maxlen)--;
+		}
+		src++;
+	}
+}
+static char *xml_translate(char *in, struct ast_variable *vars)
+{
+	struct ast_variable *v;
+	char *dest=NULL;
+	char *out, *tmp, *var, *val;
+	char *objtype=NULL;
+	int colons = 0;
+	int breaks = 0;
+	int len;
+	int count = 1;
+	int escaped = 0;
+	int inobj = 0;
+	int x;
+	v = vars;
+	while(v) {
+		if (!dest && !strcasecmp(v->name, "ajaxdest"))
+			dest = v->value;
+		else if (!objtype && !strcasecmp(v->name, "ajaxobjtype")) 
+			objtype = v->value;
+		v = v->next;
+	}
+	if (!dest)
+		dest = "unknown";
+	if (!objtype)
+		objtype = "generic";
+	for (x=0;in[x];x++) {
+		if (in[x] == ':')
+			colons++;
+		else if (in[x] == '\n')
+			breaks++;
+		else if (strchr("&\"<>", in[x]))
+			escaped++;
+	}
+	len = strlen(in) + colons * 5 + breaks * (40 + strlen(dest) + strlen(objtype)) + escaped * 10; /* foo="bar", "<response type=\"object\" id=\"dest\"", "&amp;" */
+	out = malloc(len);
+	if (!out)
+		return 0;
+	tmp = out;
+	while(*in) {
+		var = in;
+		while (*in && (*in >= 32)) in++;
+		if (*in) {
+			if ((count > 3) && inobj) {
+				ast_build_string(&tmp, &len, " /></response>\n");
+				inobj = 0;
+			}
+			count = 0;
+			while (*in && (*in < 32)) {
+				*in = '\0';
+				in++;
+				count++;
+			}
+			val = strchr(var, ':');
+			if (val) {
+				*val = '\0';
+				val++;
+				if (*val == ' ')
+					val++;
+				if (!inobj) {
+					ast_build_string(&tmp, &len, "<response type='object' id='%s'><%s", dest, objtype);
+					inobj = 1;
+				}
+				ast_build_string(&tmp, &len, " ");				
+				xml_copy_escape(&tmp, &len, var, 1);
+				ast_build_string(&tmp, &len, "='");
+				xml_copy_escape(&tmp, &len, val, 0);
+				ast_build_string(&tmp, &len, "'");
+			}
+		}
+	}
+	if (inobj)
+		ast_build_string(&tmp, &len, " /></response>\n");
+	return out;
+}
+
+static char *html_translate(char *in)
+{
+	int x;
+	int colons = 0;
+	int breaks = 0;
+	int len;
+	int count=1;
+	char *tmp, *var, *val, *out;
+	for (x=0;in[x];x++) {
+		if (in[x] == ':')
+			colons++;
+		if (in[x] == '\n')
+			breaks++;
+	}
+	len = strlen(in) + colons * 40 + breaks * 40; /* <tr><td></td><td></td></tr>, "<tr><td colspan=\"2\"><hr></td></tr> */
+	out = malloc(len);
+	if (!out)
+		return 0;
+	tmp = out;
+	while(*in) {
+		var = in;
+		while (*in && (*in >= 32)) in++;
+		if (*in) {
+			if ((count % 4) == 0){
+				ast_build_string(&tmp, &len, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+			}
+			count = 0;
+			while (*in && (*in < 32)) {
+				*in = '\0';
+				in++;
+				count++;
+			}
+			val = strchr(var, ':');
+			if (val) {
+				*val = '\0';
+				val++;
+				if (*val == ' ')
+					val++;
+				ast_build_string(&tmp, &len, "<tr><td>%s</td><td>%s</td></tr>\r\n", var, val);
+			}
+		}
+	}
+	return out;
+}
+
 void astman_append(struct mansession *s, const char *fmt, ...)
 {
 	char *stuff;
 	int res;
 	va_list ap;
+	char *tmp;
 
 	va_start(ap, fmt);
 	res = vasprintf(&stuff, fmt, ap);
@@ -224,7 +396,17 @@
 	if (res == -1) {
 		ast_log(LOG_ERROR, "Memory allocation failure\n");
 	} else {
-		ast_carefulwrite(s->fd, stuff, strlen(stuff), 100);
+		if (s->fd > -1)
+			ast_carefulwrite(s->fd, stuff, strlen(stuff), 100);
+		else {
+			tmp = realloc(s->outputstr, (s->outputstr ? strlen(s->outputstr) : 0) + strlen(stuff) + 1);
+			if (tmp) {
+				if (!s->outputstr)
+					tmp[0] = '\0';
+				s->outputstr = tmp;
+				strcat(s->outputstr, stuff);
+			}
+		}
 		free(stuff);
 	}
 }
@@ -320,6 +502,8 @@
 	struct eventqent *eqe;
 	if (s->fd > -1)
 		close(s->fd);
+	if (s->outputstr)
+		free(s->outputstr);
 	ast_mutex_destroy(&s->__lock);
 	while(s->eventq) {
 		eqe = s->eventq;
@@ -606,7 +790,7 @@
 							return -1;
 						}
 					}
-				} else if (password && !strcasecmp(password, pass)) {
+				} else if (password && !strcmp(password, pass)) {
 					break;
 				} else {
 					ast_log(LOG_NOTICE, "%s failed to authenticate as '%s'\n", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr), user);
@@ -633,13 +817,101 @@
 
 /*! \brief PING: Manager PING */
 static char mandescr_ping[] = 
-"Description: A 'Ping' action will ellicit a 'Pong' response.  Used to keep the "
+"Description: A 'Ping' action will ellicit a 'Pong' response.  Used to keep the\n"
 "  manager connection open.\n"
 "Variables: NONE\n";
 
 static int action_ping(struct mansession *s, struct message *m)
 {
 	astman_send_response(s, m, "Pong", NULL);
+	return 0;
+}
+
+/*! \brief WAITEVENT: Manager WAITEVENT */
+static char mandescr_waitevent[] = 
+"Description: A 'WaitEvent' action will ellicit a 'Success' response.  Whenever\n"
+"a manager event is queued.  Once WaitEvent has been called on an HTTP manager\n"
+"session, events will be generated and queued.\n"
+"Variables: \n"
+"   Timeout: Maximum time to wait for events\n";
+
+static int action_waitevent(struct mansession *s, struct message *m)
+{
+	char *timeouts = astman_get_header(m, "Timeout");
+	int timeout = -1, max;
+	int x;
+	int needexit = 0;
+	time_t now;
+	struct eventqent *eqe;
+	char *id = astman_get_header(m,"ActionID");
+	char idText[256]="";
+
+	if (!ast_strlen_zero(id))
+		snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
+
+	if (!ast_strlen_zero(timeouts)) {
+		sscanf(timeouts, "%i", &timeout);
+	}
+	
+	ast_mutex_lock(&s->__lock);
+	if (s->waiting_thread != AST_PTHREADT_NULL) {
+		pthread_kill(s->waiting_thread, SIGURG);
+	}
+	if (s->sessiontimeout) {
+		time(&now);
+		max = s->sessiontimeout - now - 10;
+		if (max < 0)
+			max = 0;
+		if ((timeout < 0) || (timeout > max))
+			timeout = max;
+		if (!s->send_events)
+			s->send_events = -1;
+		/* Once waitevent is called, always queue events from now on */
+		if (s->busy == 1)
+			s->busy = 2;
+	}
+	ast_mutex_unlock(&s->__lock);
+	s->waiting_thread = pthread_self();
+
+	ast_log(LOG_DEBUG, "Starting waiting for an event!\n");
+	for (x=0;((x<timeout) || (timeout < 0)); x++) {
+		ast_mutex_lock(&s->__lock);
+		if (s->eventq)
+			needexit = 1;
+		if (s->waiting_thread != pthread_self())
+			needexit = 1;
+		if (s->needdestroy)
+			needexit = 1;
+		ast_mutex_unlock(&s->__lock);
+		if (needexit)
+			break;
+		if (s->fd > 0) {
+			if (ast_wait_for_input(s->fd, 1000))
+				break;
+		} else {
+			sleep(1);
+		}
+	}
+	ast_log(LOG_DEBUG, "Finished waiting for an event!\n");
+	ast_mutex_lock(&s->__lock);
+	if (s->waiting_thread == pthread_self()) {
+		astman_send_response(s, m, "Success", "Waiting for Event...");
+		/* Only show events if we're the most recent waiter */
+		while(s->eventq) {
+			astman_append(s, "%s", s->eventq->eventdata);
+			eqe = s->eventq;
+			s->eventq = s->eventq->next;
+			free(eqe);
+		}
+		astman_append(s,
+			"Event: WaitEventComplete\r\n"
+			"%s"
+			"\r\n",idText);
+		s->waiting_thread = AST_PTHREADT_NULL;
+	} else {
+		ast_log(LOG_DEBUG, "Abandoning event request!\n");
+	}
+	ast_mutex_unlock(&s->__lock);
 	return 0;
 }
 
@@ -1338,10 +1610,10 @@
 				s->authenticated = 1;
 				if (option_verbose > 1) {
 					if ( displayconnects ) {
-						ast_verbose(VERBOSE_PREFIX_2 "Manager '%s' logged on from %s\n", s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+						ast_verbose(VERBOSE_PREFIX_2 "%sManager '%s' logged on from %s\n", (s->sessiontimeout ? "HTTP " : ""), s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
 					}
 				}
-				ast_log(LOG_EVENT, "Manager '%s' logged on from %s\n", s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+				ast_log(LOG_EVENT, "%sManager '%s' logged on from %s\n", (s->sessiontimeout ? "HTTP " : ""), s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
 				astman_send_ack(s, m, "Authentication accepted");
 			}
 		} else if (!strcasecmp(action, "Logoff")) {
@@ -1353,7 +1625,7 @@
 		int ret=0;
 		struct eventqent *eqe;
 		ast_mutex_lock(&s->__lock);
-		s->busy = 1;
+		s->busy++;
 		ast_mutex_unlock(&s->__lock);
 		while( tmp ) { 		
 			if (!strcasecmp(action, tmp->action)) {
@@ -1370,15 +1642,17 @@
 		if (!tmp)
 			astman_send_error(s, m, "Invalid/unknown command");
 		ast_mutex_lock(&s->__lock);
-		s->busy = 0;
-		while(s->eventq) {
-			if (ast_carefulwrite(s->fd, s->eventq->eventdata, strlen(s->eventq->eventdata), s->writetimeout) < 0) {
-				ret = -1;
-				break;
+		if (s->fd > -1) {
+			s->busy--;
+			while(s->eventq) {
+				if (ast_carefulwrite(s->fd, s->eventq->eventdata, strlen(s->eventq->eventdata), s->writetimeout) < 0) {
+					ret = -1;
+					break;
+				}
+				eqe = s->eventq;
+				s->eventq = s->eventq->next;
+				free(eqe);
 			}
-			eqe = s->eventq;
-			s->eventq = s->eventq->next;
-			free(eqe);
 		}
 		ast_mutex_unlock(&s->__lock);
 		return ret;
@@ -1484,17 +1758,48 @@
 	int as;
 	struct sockaddr_in sin;
 	socklen_t sinlen;
-	struct mansession *s;
+	struct mansession *s, *prev=NULL, *next;
 	struct protoent *p;
 	int arg = 1;
 	int flags;
 	pthread_attr_t attr;
+	time_t now;
+	struct pollfd pfds[1];
+	char iabuf[INET_ADDRSTRLEN];
 
 	pthread_attr_init(&attr);
 	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 
 	for (;;) {
+		time(&now);
+		ast_mutex_lock(&sessionlock);
+		prev = NULL;
+		s = sessions;
+		while(s) {
+			next = s->next;
+			if (s->sessiontimeout && (now > s->sessiontimeout) && !s->inuse) {
+				if (prev)
+					prev->next = next;
+				else
+					sessions = next;
+				if (s->authenticated && (option_verbose > 1) && displayconnects) {
+					ast_verbose(VERBOSE_PREFIX_2 "HTTP Manager '%s' timed out from %s\n",
+						s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+				}
+				free_session(s);
+			} else
+				prev = s;
+			s = next;
+		}
+		ast_mutex_unlock(&sessionlock);
+
 		sinlen = sizeof(sin);
+		pfds[0].fd = asock;
+		pfds[0].events = POLLIN;
+		/* Wait for something to happen, but timeout every few seconds so
+		   we can ditch any old manager sessions */
+		if (poll(pfds, 1, 5000) < 1)
+			continue;
 		as = accept(asock, (struct sockaddr *)&sin, &sinlen);
 		if (as < 0) {
 			ast_log(LOG_NOTICE, "Accept returned -1: %s\n", strerror(errno));
@@ -1514,6 +1819,7 @@
 		memset(s, 0, sizeof(struct mansession));
 		memcpy(&s->sin, &sin, sizeof(sin));
 		s->writetimeout = 100;
+		s->waiting_thread = AST_PTHREADT_NULL;
 
 		if(! block_sockets) {
 			/* For safety, make sure socket is non-blocking */
@@ -1593,7 +1899,9 @@
 		ast_mutex_lock(&s->__lock);
 		if (s->busy) {
 			append_event(s, tmp);
-		} else if (!s->dead) {
+			if (s->waiting_thread != AST_PTHREADT_NULL)
+				pthread_kill(s->waiting_thread, SIGURG);
+		} else if (!s->dead && !s->sessiontimeout) {
 			if (ast_carefulwrite(s->fd, tmp, tmp_next - tmp, s->writetimeout) < 0) {
 				ast_log(LOG_WARNING, "Disconnecting slow (or gone) manager session!\n");
 				s->dead = 1;
@@ -1701,7 +2009,211 @@
 /*! @}
  END Doxygen group */
 
+static struct mansession *find_session(unsigned long ident)
+{
+	struct mansession *s;
+	ast_mutex_lock(&sessionlock);
+	s = sessions;
+	while(s) {
+		ast_mutex_lock(&s->__lock);
+		if (s->sessiontimeout && (s->managerid == ident) && !s->needdestroy) {
+			s->inuse++;
+			break;
+		}
+		ast_mutex_unlock(&s->__lock);
+		s = s->next;
+	}
+	ast_mutex_unlock(&sessionlock);
+	return s;
+}
+
+
+static void vars2msg(struct message *m, struct ast_variable *vars)
+{
+	int x;
+	for (x=0;vars && (x<AST_MAX_MANHEADERS);x++,vars = vars->next) {
+		if (!vars)
+			break;
+		m->hdrcount = x + 1;
+		snprintf(m->headers[x], sizeof(m->headers[x]), "%s: %s", vars->name, vars->value);
+	}
+}
+
+#define FORMAT_RAW	0
+#define FORMAT_HTML	1
+#define FORMAT_XML	2
+
+static char *contenttype[] = { "plain", "html", "xml" };
+
+static char *generic_http_callback(int format, struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
+{
+	struct mansession *s=NULL;
+	unsigned long ident=0;
+	char workspace[256];
+	char cookie[128];
+	char iabuf[INET_ADDRSTRLEN];
+	int len = sizeof(workspace);
+	int blastaway = 0;
+	char *c = workspace;
+	char *retval=NULL;
+	struct message m;
+	struct ast_variable *v;
+	
+	v = params;
+	while(v) {
+		if (!strcasecmp(v->name, "mansession_id")) {
+			sscanf(v->value, "%lx", &ident);
+			break;
+		}
+		v = v->next;
+	}
+	s = find_session(ident);
+
+	if (!s) {
+		/* Create new session */
+		s = calloc(1, sizeof(struct mansession));
+		memcpy(&s->sin, requestor, sizeof(s->sin));
+		s->fd = -1;
+		s->waiting_thread = AST_PTHREADT_NULL;
+		s->send_events = 0;
+		ast_mutex_init(&s->__lock);
+		ast_mutex_lock(&s->__lock);
+		ast_mutex_lock(&sessionlock);
+		s->inuse = 1;
+		s->managerid = rand() | (unsigned long)s;
+		s->next = sessions;
+		sessions = s;
+		ast_mutex_unlock(&sessionlock);
+	}
+
+	/* Reset HTTP timeout */
+	time(&s->sessiontimeout);
+	s->sessiontimeout += httptimeout;
+	ast_mutex_unlock(&s->__lock);
+	
+	memset(&m, 0, sizeof(m));
+	if (s) {
+		char tmp[80];
+		ast_build_string(&c, &len, "Content-type: text/%s\n", contenttype[format]);
+		sprintf(tmp, "%08lx", s->managerid);
+		ast_build_string(&c, &len, "%s\r\n", ast_http_setcookie("mansession_id", tmp, httptimeout, cookie, sizeof(cookie)));
+		if (format == FORMAT_HTML)
+			ast_build_string(&c, &len, "<title>Asterisk&trade; Manager Test Interface</title>");
+		vars2msg(&m, params);
+		if (format == FORMAT_XML) {
+			ast_build_string(&c, &len, "<ajax-response>\n");
+		} else if (format == FORMAT_HTML) {
+			ast_build_string(&c, &len, "<body bgcolor=\"#ffffff\"><table align=center bgcolor=\"#f1f1f1\" width=\"500\">\r\n");
+			ast_build_string(&c, &len, "<tr><td colspan=\"2\" bgcolor=\"#f1f1ff\"><h1>&nbsp;&nbsp;Manager Tester</h1></td></tr>\r\n");
+		}
+		if (process_message(s, &m)) {
+			if (s->authenticated) {
+				if (option_verbose > 1) {
+					if (displayconnects) 
+						ast_verbose(VERBOSE_PREFIX_2 "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));    
+				}
+				ast_log(LOG_EVENT, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+			} else {
+				if (option_verbose > 1) {
+					if (displayconnects)
+						ast_verbose(VERBOSE_PREFIX_2 "HTTP Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+				}
+				ast_log(LOG_EVENT, "HTTP Failed attempt from %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), s->sin.sin_addr));
+			}
+			s->needdestroy = 1;
+		}
+		if (s->outputstr) {
+			char *tmp;
+			if (format == FORMAT_XML)
+				tmp = xml_translate(s->outputstr, params);
+			else if (format == FORMAT_HTML)
+				tmp = html_translate(s->outputstr);
+			else
+				tmp = s->outputstr;
+			if (tmp) {
+				retval = malloc(strlen(workspace) + strlen(tmp) + 128);
+				if (retval) {
+					strcpy(retval, workspace);
+					strcpy(retval + strlen(retval), tmp);
+					c = retval + strlen(retval);
+					len = 120;
+				}
+				free(tmp);
+			}
+			if (tmp != s->outputstr)
+				free(s->outputstr);
+			s->outputstr = NULL;
+		}
+		/* Still okay because c would safely be pointing to workspace even
+		   if retval failed to allocate above */
+		if (format == FORMAT_XML) {
+			ast_build_string(&c, &len, "</ajax-response>\n");
+		} else if (format == FORMAT_HTML)
+			ast_build_string(&c, &len, "</table></body>\r\n");
+	} else {
+		*status = 500;
+		*title = strdup("Server Error");
+	}
+	ast_mutex_lock(&s->__lock);
+	if (s->needdestroy) {
+		if (s->inuse == 1) {
+			ast_log(LOG_DEBUG, "Need destroy, doing it now!\n");
+			blastaway = 1;
+		} else {
+			ast_log(LOG_DEBUG, "Need destroy, but can't do it yet!\n");
+			if (s->waiting_thread != AST_PTHREADT_NULL)
+				pthread_kill(s->waiting_thread, SIGURG);
+			s->inuse--;
+		}
+	} else
+		s->inuse--;
+	ast_mutex_unlock(&s->__lock);
+	
+	if (blastaway)
+		destroy_session(s);
+	if (*status != 200)
+		return ast_http_error(500, "Server Error", NULL, "Internal Server Error (out of memory)\n"); 
+	return retval;
+}
+
+static char *manager_http_callback(struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
+{
+	return generic_http_callback(FORMAT_HTML, requestor, uri, params, status, title, contentlength);
+}
+
+static char *mxml_http_callback(struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
+{
+	return generic_http_callback(FORMAT_XML, requestor, uri, params, status, title, contentlength);
+}
+
+static char *rawman_http_callback(struct sockaddr_in *requestor, const char *uri, struct ast_variable *params, int *status, char **title, int *contentlength)
+{
+	return generic_http_callback(FORMAT_RAW, requestor, uri, params, status, title, contentlength);
+}
+
+struct ast_http_uri rawmanuri = {
+	.description = "Raw HTTP Manager Event Interface",
+	.uri = "rawman",
+	.has_subtree = 0,
+	.callback = rawman_http_callback,
+};
+
+struct ast_http_uri manageruri = {
+	.description = "HTML Manager Event Interface",
+	.uri = "manager",
+	.has_subtree = 0,
+	.callback = manager_http_callback,
+};
+
+struct ast_http_uri managerxmluri = {
+	.description = "XML Manager Event Interface",
+	.uri = "mxml",
+	.has_subtree = 0,
+	.callback = mxml_http_callback,
+};
+
 static int registered = 0;
+static int webregged = 0;
 
 int init_manager(void)
 {
@@ -1710,6 +2222,9 @@
 	int oldportno = portno;
 	static struct sockaddr_in ba;
 	int x = 1;
+	int flags;
+	int webenabled=0;
+	int newhttptimeout = 60;
 	if (!registered) {
 		/* Register default actions */
 		ast_manager_register2("Ping", 0, action_ping, "Keepalive command", mandescr_ping);
@@ -1727,6 +2242,7 @@
 		ast_manager_register2("MailboxStatus", EVENT_FLAG_CALL, action_mailboxstatus, "Check Mailbox", mandescr_mailboxstatus );
 		ast_manager_register2("MailboxCount", EVENT_FLAG_CALL, action_mailboxcount, "Check Mailbox Message Count", mandescr_mailboxcount );
 		ast_manager_register2("ListCommands", 0, action_listcommands, "List available manager commands", mandescr_listcommands);
+		ast_manager_register2("WaitEvent", 0, action_waitevent, "Wait for an event to occur", mandescr_waitevent);
 
 		ast_cli_register(&show_mancmd_cli);
 		ast_cli_register(&show_mancmds_cli);
@@ -1750,6 +2266,10 @@
 	if(val)
 		block_sockets = ast_true(val);
 
+	val = ast_variable_retrieve(cfg, "general", "webenabled");
+	if (val)
+		webenabled = ast_true(val);
+
 	if ((val = ast_variable_retrieve(cfg, "general", "port"))) {
 		if (sscanf(val, "%d", &portno) != 1) {
 			ast_log(LOG_WARNING, "Invalid port number '%s'\n", val);
@@ -1762,6 +2282,9 @@
 
 	if ((val = ast_variable_retrieve(cfg, "general", "timestampevents")))
 		timestampevents = ast_true(val);
+
+	if ((val = ast_variable_retrieve(cfg, "general", "httptimeout")))
+		newhttptimeout = atoi(val);
 	
 	ba.sin_family = AF_INET;
 	ba.sin_port = htons(portno);
@@ -1785,6 +2308,25 @@
 	}
 	ast_config_destroy(cfg);
 	
+	if (webenabled && enabled) {
+		if (!webregged) {
+			ast_http_uri_link(&rawmanuri);
+			ast_http_uri_link(&manageruri);

[... 2390 lines stripped ...]


More information about the asterisk-commits mailing list