Index: main/manager.c
===================================================================
--- main/manager.c	(revision 198185)
+++ main/manager.c	(working copy)
@@ -4127,12 +4127,14 @@
 	FORMAT_RAW,
 	FORMAT_HTML,
 	FORMAT_XML,
+	FORMAT_JSON,
 };
 
 static const char * const contenttype[] = {
-	[FORMAT_RAW] = "plain",
-	[FORMAT_HTML] = "html",
-	[FORMAT_XML] =  "xml",
+	[FORMAT_RAW] = "text/plain",
+	[FORMAT_HTML] = "text/html",
+	[FORMAT_XML] =  "text/xml",
+	[FORMAT_JSON] = "application/json",
 };
 
 /*!
@@ -4384,12 +4386,15 @@
 	const char *objtype = NULL;
 	int in_data = 0;	/* parsing data */
 	int inobj = 0;
+	int is_first = 1;
+	int html = (format == FORMAT_HTML);
 	int xml = (format == FORMAT_XML);
+	int json = (format == FORMAT_JSON);
 	struct variable_count *vc = NULL;
 	struct ao2_container *vco = NULL;
 
-	if (xml) {
-		/* dest and objtype need only for XML format */
+	if (xml || json) {
+		/* dest and objtype are needed for XML and JSON formats */
 		for (v = get_vars; v; v = v->next) {
 			if (!strcasecmp(v->name, "ajaxdest")) {
 				dest = v->value;
@@ -4417,14 +4422,23 @@
 			/* empty line */
 			if (in_data) {
 				/* close data in Opaque mode */
-				ast_str_append(out, 0, xml ? "'" : "</td></tr>\n");
+				if (html)
+					ast_str_append(out, 0, "</td></tr>\n");
+				else if (xml)
+					ast_str_append(out, 0, "'");
+				else if (json)
+					ast_str_append(out, 0, "\"");
 				in_data = 0;
 			}
 
 			if (inobj) {
 				/* close block */
-				ast_str_append(out, 0, xml ? " /></response>\n" :
-					"<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+				if (html)
+					ast_str_append(out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+				else if (xml)
+					ast_str_append(out, 0, " /></response>\n");
+				else if (json)
+					ast_str_append(out, 0, "}");
 				inobj = 0;
 				ao2_ref(vco, -1);
 				vco = NULL;
@@ -4434,17 +4448,30 @@
 
 		if (!inobj) {
 			/* start new block */
-			if (xml) {
+			if (xml)
 				ast_str_append(out, 0, "<response type='object' id='%s'><%s", dest, objtype);
+			else if (json) {
+				if (!is_first)
+					ast_str_append(out, 0, ",");
+				ast_str_append(out, 0, "{\"id\":\"%s\",\"type\":\"%s\"", dest, objtype);
 			}
 			vco = ao2_container_alloc(37, variable_count_hash_fn, variable_count_cmp_fn);
 			inobj = 1;
+			is_first = 0;
 		}
 
 		if (in_data) {
 			/* Process data field in Opaque mode */
-			xml_copy_escape(out, val, 0);   /* data field */
-			ast_str_append(out, 0, xml ? "\n" : "<br>\n");
+			if (json)
+				ast_str_append(out, 0, val);
+			else
+				xml_copy_escape(out, val, 0);
+
+			if (html)
+				ast_str_append(out, 0, "<br>\n");
+			else if (xml)
+				ast_str_append(out, 0, "\n");
+
 			continue;
 		}
 
@@ -4461,8 +4488,13 @@
 			in_data = 1;
 		}
 
+		if (html)
+			ast_str_append(out, 0, "<tr><td>");
+		else if (xml)
+			ast_str_append(out, 0, " ");
+		else if (json)
+			ast_str_append(out, 0, ",\"");
 
-		ast_str_append(out, 0, xml ? " " : "<tr><td>");
 		if ((vc = ao2_find(vco, var, 0))) {
 			vc->count++;
 		} else {
@@ -4473,19 +4505,37 @@
 			ao2_link(vco, vc);
 		}
 
-		xml_copy_escape(out, var, xml ? 1 | 2 : 0); /* data name */
+		xml_copy_escape(out, var, xml || json ? 1 | 2 : 0); /* data name */
 		if (vc->count > 1) {
 			ast_str_append(out, 0, "-%d", vc->count);
 		}
 		ao2_ref(vc, -1);
-		ast_str_append(out, 0, xml ? "='" : "</td><td>");
-		xml_copy_escape(out, val, 0);	/* data field */
-		ast_str_append(out, 0, xml ? "'" : "</td></tr>\n");
+		if (html)
+			ast_str_append(out, 0, "</td><td>");
+		else if (xml)
+			ast_str_append(out, 0, "='");
+		else if (json)
+			ast_str_append(out, 0, "\":\"");
+		/* data field */
+		if (json)
+			ast_str_append(out, 0, val);
+		else
+			xml_copy_escape(out, val, 0);
+		if (html)
+			ast_str_append(out, 0, "</td></tr>\n");
+		else if (xml)
+			ast_str_append(out, 0, "'");
+		else if (json)
+			ast_str_append(out, 0, "\"");
 	}
 
 	if (inobj) {
-		ast_str_append(out, 0, xml ? " /></response>\n" :
-			"<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+		if (html)
+			ast_str_append(out, 0, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+		else if (xml)
+			ast_str_append(out, 0, " /></response>\n");
+		else if (json)
+			ast_str_append(out, 0, "}");
 		ao2_ref(vco, -1);
 	}
 }
@@ -4602,15 +4652,17 @@
 	}
 
 	ast_str_append(&http_header, 0,
-		"Content-type: text/%s\r\n"
+		"Content-type: %s\r\n"
 		"Cache-Control: no-cache;\r\n"
-		"Set-Cookie: mansession_id=\"%08x\"; Version=\"1\"; Max-Age=%d"
+		"Set-Cookie: mansession_id=\"%08x\"; Max-Age=%d; Version=\"1\"\r\n"
 		"Pragma: SuppressEvents\r\n",
 		contenttype[format],
 		session->managerid, httptimeout);
 
 	if (format == FORMAT_XML) {
 		ast_str_append(&out, 0, "<ajax-response>\n");
+	} else if (format == FORMAT_JSON) {
+		ast_str_append(&out, 0, "[");
 	} else if (format == FORMAT_HTML) {
 		/*
 		 * When handling AMI-over-HTTP in HTML format, we provide a simple form for
@@ -4647,14 +4699,14 @@
 			if (MAP_FAILED == (buf = mmap(NULL, l, PROT_READ | PROT_WRITE, MAP_PRIVATE, s.fd, 0))) {
 				ast_log(LOG_WARNING, "mmap failed.  Manager output was not processed\n");
 			} else {
-				if (format == FORMAT_XML || format == FORMAT_HTML) {
+				if (format == FORMAT_XML || format == FORMAT_HTML || format == FORMAT_JSON) {
 					xml_translate(&out, buf, params, format);
 				} else {
 					ast_str_append(&out, 0, "%s", buf);
 				}
 				munmap(buf, l);
 			}
-		} else if (format == FORMAT_XML || format == FORMAT_HTML) {
+		} else if (format == FORMAT_XML || format == FORMAT_HTML || format == FORMAT_JSON) {
 			xml_translate(&out, "", params, format);
 		}
 		fclose(s.f);
@@ -4663,7 +4715,9 @@
 	}
 
 	if (format == FORMAT_XML) {
-		ast_str_append(&out, 0, "</ajax-response>\n");
+		ast_str_append(&out, 0, "</ajax-response>\r\n");
+	} else if (format == FORMAT_JSON) {
+		ast_str_append(&out, 0, "]\r\n");
 	} else if (format == FORMAT_HTML) {
 		ast_str_append(&out, 0, "</table></body>\r\n");
 	}
@@ -4976,14 +5030,14 @@
 
 		if (l) {
 			if ((buf = mmap(NULL, l, PROT_READ | PROT_WRITE, MAP_SHARED, s.fd, 0))) {
-				if (format == FORMAT_XML || format == FORMAT_HTML) {
+				if (format == FORMAT_XML || format == FORMAT_HTML || format == FORMAT_JSON) {
 					xml_translate(&out, buf, params, format);
 				} else {
 					ast_str_append(&out, 0, "%s", buf);
 				}
 				munmap(buf, l);
 			}
-		} else if (format == FORMAT_XML || format == FORMAT_HTML) {
+		} else if (format == FORMAT_XML || format == FORMAT_HTML || format == FORMAT_JSON) {
 			xml_translate(&out, "", params, format);
 		}
 		fclose(s.f);
@@ -4992,7 +5046,9 @@
 	}
 
 	if (format == FORMAT_XML) {
-		ast_str_append(&out, 0, "</ajax-response>\n");
+		ast_str_append(&out, 0, "</ajax-response>\r\n");
+	} else if (format == FORMAT_JSON) {
+		ast_str_append(&out, 0, "]\r\n");
 	} else if (format == FORMAT_HTML) {
 		ast_str_append(&out, 0, "</table></form></body></html>\r\n");
 	}
@@ -5046,6 +5102,11 @@
 	return generic_http_callback(ser, method, FORMAT_XML, &ser->remote_address, uri, get_params, headers);
 }
 
+static int mjson_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers)
+{
+	return generic_http_callback(ser, method, FORMAT_JSON, &ser->remote_address, uri, get_params, headers);
+}
+
 static int rawman_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers)
 {
 	return generic_http_callback(ser, method, FORMAT_RAW, &ser->remote_address, uri, get_params, headers);
@@ -5075,6 +5136,13 @@
 	.key = __FILE__,
 };
 
+struct ast_http_uri managerjsonuri = {
+    .description = "JSON Manager Event Interface",
+    .uri = "mjson",
+    .callback = mjson_http_callback,
+    .data = NULL,
+    .key = __FILE__,
+};
 
 /* Callback with Digest authentication */
 static int auth_manager_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params,  struct ast_variable *headers)
@@ -5087,6 +5155,11 @@
 	return auth_http_callback(ser, method, FORMAT_XML, &ser->remote_address, uri, get_params, headers);
 }
 
+static int auth_mjson_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers)
+{
+    return auth_http_callback(ser, method, FORMAT_JSON, &ser->remote_address, uri, get_params, headers);
+}
+
 static int auth_rawman_http_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers)
 {
 	return auth_http_callback(ser, method, FORMAT_RAW, &ser->remote_address, uri, get_params, headers);
@@ -5119,6 +5192,15 @@
 	.key = __FILE__,
 };
 
+struct ast_http_uri amanagerjsonuri = {
+    .description = "JSON Manager Event Interface w/Digest authentication",
+    .uri = "amjson",
+    .has_subtree = 0,
+    .callback = auth_mjson_http_callback,
+    .data = NULL,
+    .key = __FILE__,
+};
+
 static int registered = 0;
 static int webregged = 0;
 
@@ -5478,10 +5560,12 @@
 			ast_http_uri_link(&rawmanuri);
 			ast_http_uri_link(&manageruri);
 			ast_http_uri_link(&managerxmluri);
+			ast_http_uri_link(&managerjsonuri);
 
 			ast_http_uri_link(&arawmanuri);
 			ast_http_uri_link(&amanageruri);
 			ast_http_uri_link(&amanagerxmluri);
+			ast_http_uri_link(&amanagerjsonuri);
 			webregged = 1;
 		}
 	} else {
@@ -5489,10 +5573,12 @@
 			ast_http_uri_unlink(&rawmanuri);
 			ast_http_uri_unlink(&manageruri);
 			ast_http_uri_unlink(&managerxmluri);
+			ast_http_uri_unlink(&managerjsonuri);
 
 			ast_http_uri_unlink(&arawmanuri);
 			ast_http_uri_unlink(&amanageruri);
 			ast_http_uri_unlink(&amanagerxmluri);
+			ast_http_uri_unlink(&amanagerjsonuri);
 			webregged = 0;
 		}
 	}
