summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid M. Lee <dlee@digium.com>2013-01-12 06:43:37 +0000
committerDavid M. Lee <dlee@digium.com>2013-01-12 06:43:37 +0000
commitaecd2429bd6ab0ae37c90cceb926fdb22cda84d0 (patch)
tree95606ce0b5e82599a04078ad0cde47af4a23b591
parentc5ec471766200966b567d9cc4709d03b9551361c (diff)
Fix XML encoding of 'identity display' in NOTIFY messages.
XML encoding in chan_sip is accomplished by naively building the XML directly from strings. While this usually works, it fails to take into account escaping the reserved characters in XML. This patch adds an 'ast_xml_escape' function, which works similarly to 'ast_uri_encode'. This is used to properly escape the local_display attribute in XML formatted NOTIFY messages. Several things to note: * The Right Thing(TM) to do would probably be to replace the ast_build_string stuff with building an ast_xml_doc. That's a much bigger change, and out of scope for the original ticket, so I refrained myself. * It is with great sadness that I wrote my own ast_xml_escape function. There's one in libxml2, but it's knee-deep in libxml2-ness, and not easily used to one-off escape a string. * I only escaped the string we know is causing problems (local_display). At least some of the other strings are URI-encoded, which should be XML safe. Rather than figuring out what's safe and escaping what's not, it would be much cleaner to simply build an ast_xml_doc for the messages and let the XML library do the XML escaping. Like I said, that's out of scope. (closes issue ABE-2902) Reported by: Guenther Kelleter Tested by: Guenther Kelleter Review: http://reviewboard.digium.internal/r/365/ ........ Merged revision 378919 from https://origsvn.digium.com/svn/asterisk/be/branches/C.3-bier ........ Merged revisions 378933 from http://svn.asterisk.org/svn/asterisk/branches/1.8 ........ Merged revisions 378934 from http://svn.asterisk.org/svn/asterisk/branches/11 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@378935 65c4cc65-6c06-0410-ace0-fbb531ad65f3
-rw-r--r--channels/chan_sip.c10
-rw-r--r--include/asterisk/utils.h14
-rw-r--r--main/utils.c63
-rw-r--r--tests/test_xml_escape.c118
4 files changed, 202 insertions, 3 deletions
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 528403d8a..c102b84c0 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -14543,7 +14543,8 @@ static void state_notify_build_xml(struct state_notify_data *data, int full, con
ast_str_append(tmp, 0, "<?xml version=\"1.0\"?>\n");
ast_str_append(tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%u\" state=\"%s\" entity=\"%s\">\n", p->dialogver, full ? "full" : "partial", mto);
if (data->state > 0 && (data->state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) {
- const char *local_display = exten;
+ /* Twice the extension length should be enough for XML encoding */
+ char local_display[AST_MAX_EXTENSION * 2];
char *local_target = ast_strdupa(mto);
const char *remote_display = exten;
/* It may seem odd to base the remote_target on the To header here,
@@ -14556,6 +14557,8 @@ static void state_notify_build_xml(struct state_notify_data *data, int full, con
*/
char *remote_target = ast_strdupa(mto);
+ ast_xml_escape(exten, local_display, sizeof(local_display));
+
/* There are some limitations to how this works. The primary one is that the
callee must be dialing the same extension that is being monitored. Simply dialing
the hint'd device is not sufficient. */
@@ -14575,8 +14578,9 @@ static void state_notify_build_xml(struct state_notify_data *data, int full, con
local_target = ast_alloca(need);
snprintf(local_target, need, "sip:%s@%s", cid_num, p->fromdomain);
- local_display = ast_strdupa(S_COR(ast_channel_caller(callee)->id.name.valid,
- ast_channel_caller(callee)->id.name.str, ""));
+ ast_xml_escape(S_COR(ast_channel_caller(callee)->id.name.valid,
+ ast_channel_caller(callee)->id.name.str, ""),
+ local_display, sizeof(local_display));
connected_num = S_COR(ast_channel_connected(callee)->id.number.valid,
ast_channel_connected(callee)->id.number.str, "");
diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h
index 7830b85fc..f93f563ac 100644
--- a/include/asterisk/utils.h
+++ b/include/asterisk/utils.h
@@ -290,6 +290,20 @@ char *ast_uri_encode(const char *string, char *outbuf, int buflen, struct ast_fl
*/
void ast_uri_decode(char *s, struct ast_flags spec);
+/*! ast_xml_escape
+ \brief Escape reserved characters for use in XML.
+
+ If \a outbuf is too short, the output string will be truncated.
+ Regardless, the output will always be null terminated.
+
+ \param string String to be converted
+ \param outbuf Resulting encoded string
+ \param buflen Size of output buffer
+ \return 0 for success
+ \return -1 if buflen is too short.
+ */
+int ast_xml_escape(const char *string, char *outbuf, size_t buflen);
+
/*!
* \brief Escape characters found in a quoted string.
*
diff --git a/main/utils.c b/main/utils.c
index cdb9b1a44..cc9dee38e 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -483,6 +483,69 @@ char *ast_escape_quoted(const char *string, char *outbuf, int buflen)
return outbuf;
}
+int ast_xml_escape(const char *string, char * const outbuf, const size_t buflen)
+{
+ char *dst = outbuf;
+ char *end = outbuf + buflen - 1; /* save one for the null terminator */
+
+ /* Handle the case for the empty output buffer */
+ if (buflen == 0) {
+ return -1;
+ }
+
+ /* Escaping rules from http://www.w3.org/TR/REC-xml/#syntax */
+ /* This also prevents partial entities at the end of a string */
+ while (*string && dst < end) {
+ const char *entity = NULL;
+ int len = 0;
+
+ switch (*string) {
+ case '<':
+ entity = "&lt;";
+ len = 4;
+ break;
+ case '&':
+ entity = "&amp;";
+ len = 5;
+ break;
+ case '>':
+ /* necessary if ]]> is in the string; easier to escape them all */
+ entity = "&gt;";
+ len = 4;
+ break;
+ case '\'':
+ /* necessary in single-quoted strings; easier to escape them all */
+ entity = "&apos;";
+ len = 6;
+ break;
+ case '"':
+ /* necessary in double-quoted strings; easier to escape them all */
+ entity = "&quot;";
+ len = 6;
+ break;
+ default:
+ *dst++ = *string++;
+ break;
+ }
+
+ if (entity) {
+ ast_assert(len == strlen(entity));
+ if (end - dst < len) {
+ /* no room for the entity; stop */
+ break;
+ }
+ /* just checked for length; strcpy is fine */
+ strcpy(dst, entity);
+ dst += len;
+ ++string;
+ }
+ }
+ /* Write null terminator */
+ *dst = '\0';
+ /* If any chars are left in string, return failure */
+ return *string == '\0' ? 0 : -1;
+}
+
/*! \brief ast_inet_ntoa: Recursive thread safe replacement of inet_ntoa */
const char *ast_inet_ntoa(struct in_addr ia)
{
diff --git a/tests/test_xml_escape.c b/tests/test_xml_escape.c
new file mode 100644
index 000000000..e4c36de91
--- /dev/null
+++ b/tests/test_xml_escape.c
@@ -0,0 +1,118 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Test ast_xml_escape
+ *
+ * \author\verbatim David M. Lee, II <dlee@digium.com> \endverbatim
+ *
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+ <depend>TEST_FRAMEWORK</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+
+static enum ast_test_result_state test_res = AST_TEST_PASS;
+
+static void test_xml(struct ast_test *test, const char *input, const char *expected, int max_len, int expected_res)
+{
+ char actual[256] = "";
+ int res;
+
+ if (max_len == -1) {
+ max_len = sizeof(actual);
+ }
+
+ res = ast_xml_escape(input, actual, max_len);
+ if (res != expected_res) {
+ ast_test_status_update(test, "Expected result '%d', got '%d'\n", expected_res, res);
+ test_res = AST_TEST_FAIL;
+ }
+
+ if (strcmp(expected, actual) != 0) {
+ ast_test_status_update(test, "Expected output '%s', got '%s'\n", expected, actual);
+ test_res = AST_TEST_FAIL;
+ }
+}
+
+AST_TEST_DEFINE(xml_escape_test)
+{
+ char *input;
+ char *expected;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "xml_escape_test";
+ info->category = "/main/xml_escape/";
+ info->summary = "Test XML escaping";
+ info->description =
+ "Test XML escaping";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ test_res = AST_TEST_PASS;
+
+ /* happy path */
+ input = "encode me: <&>'\"";
+ expected = "encode me: &lt;&amp;&gt;&apos;&quot;";
+ test_xml(test, input, expected, -1, 0);
+
+ /* size 0 should fail without changing anything */
+ input = "foo";
+ expected = "";
+ test_xml(test, input, expected, 0, -1);
+
+ /* truncate chars */
+ input = "<truncated>";
+ expected = "&lt;trunc";
+ test_xml(test, input, expected, 10, -1);
+
+ /* truncate entity */
+ input = "trunc<";
+ expected = "trunc";
+ test_xml(test, input, expected, 9, -1);
+
+ return test_res;
+}
+
+static int unload_module(void)
+{
+ AST_TEST_UNREGISTER(xml_escape_test);
+ return 0;
+}
+
+static int load_module(void)
+{
+ AST_TEST_REGISTER(xml_escape_test);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Skeleton (sample) Test");