diff --git a/main/manager.c b/main/manager.c
index f0650457d9c5c25c2d37689dbb037c2906f76502..332d2c591d4f1166b24a6cd0abb09296d52ebae6 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -69,6 +69,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/threadstorage.h"
 #include "asterisk/linkedlists.h"
 #include "asterisk/term.h"
+#include "asterisk/astobj2.h"
 
 /*!
  * Linked list of events.
@@ -2935,6 +2936,47 @@ static void xml_copy_escape(struct ast_str **out, const char *src, int mode)
 	}
 }
 
+struct variable_count {
+	char *varname;
+	int count;
+};
+
+static int compress_char(char c)
+{
+	c &= 0x7f;
+	if (c < 32)
+		return 0;
+	else if (c >= 'a' && c <= 'z')
+		return c - 64;
+	else if (c > 'z')
+		return '_';
+	else
+		return c - 32;
+}
+
+static int variable_count_hash_fn(const void *vvc, const int flags)
+{
+	const struct variable_count *vc = vvc;
+	int res = 0, i;
+	for (i = 0; i < 5; i++) {
+		if (vc->varname[i] == '\0')
+			break;
+		res += compress_char(vc->varname[i]) << (i * 6);
+	}
+	return res;
+}
+
+static int variable_count_cmp_fn(void *obj, void *vstr, int flags)
+{
+	/* Due to the simplicity of struct variable_count, it makes no difference
+	 * if you pass in objects or strings, the same operation applies. This is
+	 * due to the fact that the hash occurs on the first element, which means
+	 * the address of both the struct and the string are exactly the same. */
+	struct variable_count *vc = obj;
+	char *str = vstr;
+	return !strcmp(vc->varname, str) ? CMP_MATCH : 0;
+}
+
 /*! \brief Convert the input into XML or HTML.
  * The input is supposed to be a sequence of lines of the form
  *	Name: value
@@ -2972,6 +3014,8 @@ static void xml_translate(struct ast_str **out, char *in, struct ast_variable *v
 	int in_data = 0;	/* parsing data */
 	int inobj = 0;
 	int xml = (format == FORMAT_XML);
+	struct variable_count *vc = NULL;
+	struct ao2_container *vco = NULL;
 
 	for (v = vars; v; v = v->next) {
 		if (!dest && !strcasecmp(v->name, "ajaxdest"))
@@ -2983,36 +3027,14 @@ static void xml_translate(struct ast_str **out, char *in, struct ast_variable *v
 		dest = "unknown";
 	if (!objtype)
 		objtype = "generic";
-#if 0
-	/* 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) (1 + strlen(in) + colons * 5 + breaks * (40 + strlen(dest) + strlen(objtype)) + escaped * 10); /* foo="bar", "<response type=\"object\" id=\"dest\"", "&amp;" */
-	out = ast_malloc(len);
-	if (!out)
-		return NULL;
-	tmp = out;
-	*tmp = '\0';
-#endif
+
 	/* we want to stop when we find an empty line */
 	while (in && *in) {
 		val = strsep(&in, "\r\n");	/* mark start and end of line */
 		if (in && *in == '\n')		/* remove trailing \n if any */
 			in++;
 		ast_trim_blanks(val);
-		if (0)
-			ast_verbose("inobj %d in_data %d line <%s>\n", inobj, in_data, val);
+		ast_debug(5, "inobj %d in_data %d line <%s>\n", inobj, in_data, val);
 		if (ast_strlen_zero(val)) {
 			if (in_data) { /* close data */
 				ast_str_append(out, 0, xml ? "'" : "</td></tr>\n");
@@ -3021,8 +3043,11 @@ static void xml_translate(struct ast_str **out, char *in, struct ast_variable *v
 			ast_str_append(out, 0, xml ? " /></response>\n" :
 				"<tr><td colspan=\"2\"><hr></td></tr>\r\n");
 			inobj = 0;
+			ao2_ref(vco, -1);
+			vco = NULL;
 			continue;
 		}
+
 		/* we expect Name: value lines */
 		if (in_data) {
 			var = NULL;
@@ -3036,16 +3061,33 @@ static void xml_translate(struct ast_str **out, char *in, struct ast_variable *v
 				var = "Opaque-data";
 			}
 		}
+
 		if (!inobj) {
 			if (xml)
 				ast_str_append(out, 0, "<response type='object' id='%s'><%s", dest, objtype);
 			else
 				ast_str_append(out, 0, "<body>\n");
+			vco = ao2_container_alloc(37, variable_count_hash_fn, variable_count_cmp_fn);
 			inobj = 1;
 		}
+
 		if (!in_data) {	/* build appropriate line start */
 			ast_str_append(out, 0, xml ? " " : "<tr><td>");
+			if ((vc = ao2_find(vco, var, 0)))
+				vc->count++;
+			else {
+				/* Create a new entry for this one */
+				vc = ao2_alloc(sizeof(*vc), NULL);
+				vc->varname = var;
+				vc->count = 1;
+				/* Increment refcount, because we're going to deref once later */
+				ao2_ref(vc, 1);
+				ao2_link(vco, vc);
+			}
 			xml_copy_escape(out, var, xml ? 1 | 2 : 0);
+			if (vc->count > 1)
+				ast_str_append(out, 0, "-%d", vc->count);
+			ao2_ref(vc, -1);
 			ast_str_append(out, 0, xml ? "='" : "</td><td>");
 			if (!strcmp(var, "Opaque-data"))
 				in_data = 1;
@@ -3056,9 +3098,11 @@ static void xml_translate(struct ast_str **out, char *in, struct ast_variable *v
 		else
 			ast_str_append(out, 0, xml ? "\n" : "<br>\n");
 	}
-	if (inobj)
+	if (inobj) {
 		ast_str_append(out, 0, xml ? " /></response>\n" :
 			"<tr><td colspan=\"2\"><hr></td></tr>\r\n");
+		ao2_ref(vco, -1);
+	}
 }
 
 static struct ast_str *generic_http_callback(enum output_format format,