/* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * Copyright (C) 2005/2006, Anthony Minessale II * * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application * * The Initial Developer of the Original Code is * Anthony Minessale II * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Anthony Minessale II * * * switch_xml.c -- XML PARSER * * Derived from EZXML http://ezxml.sourceforge.net * Original Copyright * * Copyright 2004, 2005 Aaron Voisine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #ifdef HAVE_MMAP #include #endif #define SWITCH_XML_WS "\t\r\n " // whitespace #define SWITCH_XML_ERRL 128 // maximum error string length typedef struct switch_xml_root *switch_xml_root_t; struct switch_xml_root { // additional data for the root tag struct switch_xml xml; // is a super-struct built on top of switch_xml struct switch_xml_t cur; // current xml tree insertion point char *m; // original xml string switch_size_t len; // length of allocated memory for mmap uint8_t dynamic; char *u; // UTF-8 conversion of string if original was UTF-16 char *s; // start of work area char *e; // end of work area char **ent; // general entities (ampersand sequences) char ***attr; // default attributes char ***pi; // processing instructions short standalone; // non-zero if char err[SWITCH_XML_ERRL]; // error string }; char *SWITCH_XML_NIL[] = { NULL }; // empty, null terminated array of strings struct switch_xml_binding { switch_xml_search_function_t function; switch_xml_section_t sections; struct switch_xml_binding *next; }; typedef struct switch_xml_binding switch_xml_binding_t; static switch_xml_binding_t *BINDINGS = NULL; static switch_xml_t MAIN_XML_ROOT = NULL; static switch_memory_pool_t *XML_MEMORY_POOL; static switch_mutex_t *XML_LOCK; static switch_thread_rwlock_t *RWLOCK; static uint32_t lock_count = 0; struct xml_section_t { const char *name; switch_xml_section_t section; }; static struct xml_section_t SECTIONS[] = { { "result", SWITCH_XML_SECTION_RESULT}, { "config", SWITCH_XML_SECTION_CONFIG}, { "directory", SWITCH_XML_SECTION_DIRECTORY}, { "dialplan", SWITCH_XML_SECTION_DIALPLAN}, { NULL, 0} }; SWITCH_DECLARE(switch_xml_section_t) switch_xml_parse_section_string(char *str) { size_t x; char buf[1024] = ""; switch_xml_section_t sections = SWITCH_XML_SECTION_RESULT; if (str) { for(x = 0; x < strlen(str); x++) { buf[x] = (char)tolower((int)str[x]); } for(x = 0;;x++) { if (!SECTIONS[x].name) { break; } if (strstr(buf, SECTIONS[x].name)) { sections |= SECTIONS[x].section; } } } return sections; } SWITCH_DECLARE(switch_status_t) switch_xml_bind_search_function(switch_xml_search_function_t function, switch_xml_section_t sections) { switch_xml_binding_t *binding = NULL, *ptr = NULL; assert(function != NULL); if (!(binding = switch_core_alloc(XML_MEMORY_POOL, sizeof(*binding)))) { return SWITCH_STATUS_MEMERR; } binding->function = function; binding->sections = sections; switch_mutex_lock(XML_LOCK); for (ptr = BINDINGS; ptr && ptr->next; ptr = ptr->next); if (ptr) { ptr->next = binding; } else { BINDINGS = binding; } switch_mutex_unlock(XML_LOCK); return SWITCH_STATUS_SUCCESS; } SWITCH_DECLARE(switch_xml_t) switch_xml_find_child(switch_xml_t node, char *childname, char *attrname, char *value) { switch_xml_t p = NULL; if (!(childname && attrname && value)) { return node; } for (p = switch_xml_child(node, childname); p; p = p->next) { const char *aname = switch_xml_attr(p, attrname); if (aname && value && !strcasecmp(aname, value)) { break; } } return p; } // returns the first child tag with the given name or NULL if not found SWITCH_DECLARE(switch_xml_t) switch_xml_child(switch_xml_t xml, const char *name) { xml = (xml) ? xml->child : NULL; while (xml && strcmp(name, xml->name)) xml = xml->sibling; return xml; } // returns the Nth tag with the same name in the same subsection or NULL if not // found switch_xml_t switch_xml_idx(switch_xml_t xml, int idx) { for (; xml && idx; idx--) xml = xml->next; return xml; } // returns the value of the requested tag attribute or "" if not found SWITCH_DECLARE(const char *) switch_xml_attr_soft(switch_xml_t xml, const char *attr) { const char *ret = switch_xml_attr(xml, attr); return ret ? ret : ""; } // returns the value of the requested tag attribute or NULL if not found SWITCH_DECLARE(const char *) switch_xml_attr(switch_xml_t xml, const char *attr) { int i = 0, j = 1; switch_xml_root_t root = (switch_xml_root_t)xml; if (! xml || ! xml->attr) return NULL; while (xml->attr[i] && strcmp(attr, xml->attr[i])) i += 2; if (xml->attr[i]) return xml->attr[i + 1]; // found attribute while (root->xml.parent) root = (switch_xml_root_t)root->xml.parent; // root tag for (i = 0; root->attr[i] && strcmp(xml->name, root->attr[i][0]); i++); if (! root->attr[i]) return NULL; // no matching default attributes while (root->attr[i][j] && strcmp(attr, root->attr[i][j])) j += 3; return (root->attr[i][j]) ? root->attr[i][j + 1] : NULL; // found default } // same as switch_xml_get but takes an already initialized va_list static switch_xml_t switch_xml_vget(switch_xml_t xml, va_list ap) { char *name = va_arg(ap, char *); int idx = -1; if (name && *name) { idx = va_arg(ap, int); xml = switch_xml_child(xml, name); } return (idx < 0) ? xml : switch_xml_vget(switch_xml_idx(xml, idx), ap); } // Traverses the xml tree to retrieve a specific subtag. Takes a variable // length list of tag names and indexes. The argument list must be terminated // by either an index of -1 or an empty string tag name. Example: // title = switch_xml_get(library, "shelf", 0, "book", 2, "title", -1); // This retrieves the title of the 3rd book on the 1st shelf of library. // Returns NULL if not found. SWITCH_DECLARE(switch_xml_t) switch_xml_get(switch_xml_t xml, ...) { va_list ap; switch_xml_t r; va_start(ap, xml); r = switch_xml_vget(xml, ap); va_end(ap); return r; } // returns a null terminated array of processing instructions for the given // target SWITCH_DECLARE(const char **) switch_xml_pi(switch_xml_t xml, const char *target) { switch_xml_root_t root = (switch_xml_root_t)xml; int i = 0; if (! root) return (const char **)SWITCH_XML_NIL; while (root->xml.parent) root = (switch_xml_root_t)root->xml.parent; // root tag while (root->pi[i] && strcmp(target, root->pi[i][0])) i++; // find target return (const char **)((root->pi[i]) ? root->pi[i] + 1 : SWITCH_XML_NIL); } // set an error string and return root static switch_xml_t switch_xml_err(switch_xml_root_t root, char *s, const char *err, ...) { va_list ap; int line = 1; char *t, fmt[SWITCH_XML_ERRL]; for (t = root->s; t && t < s; t++) if (*t == '\n') line++; snprintf(fmt, SWITCH_XML_ERRL, "[error near line %d]: %s", line, err); va_start(ap, err); vsnprintf(root->err, SWITCH_XML_ERRL, fmt, ap); va_end(ap); return &root->xml; } // Recursively decodes entity and character references and normalizes new lines // ent is a null terminated array of alternating entity names and values. set t // to '&' for general entity decoding, '%' for parameter entity decoding, 'c' // for cdata sections, ' ' for attribute normalization, or '*' for non-cdata // attribute normalization. Returns s, or if the decoded string is longer than // s, returns a malloced string that must be freed. static char *switch_xml_decode(char *s, char **ent, char t) { char *e, *r = s, *m = s; long b, c, d, l; for (; *s; s++) { // normalize line endings while (*s == '\r') { *(s++) = '\n'; if (*s == '\n') memmove(s, (s + 1), strlen(s)); } } for (s = r; ; ) { while (*s && *s != '&' && (*s != '%' || t != '%') && !isspace((int)(*s))) s++; if (! *s) break; else if (t != 'c' && ! strncmp(s, "&#", 2)) { // character reference if (s[2] == 'x') c = strtol(s + 3, &e, 16); // base 16 else c = strtol(s + 2, &e, 10); // base 10 if (! c || *e != ';') { s++; continue; } // not a character ref if (c < 0x80) *(s++) = (char)c; // US-ASCII subset else { // multi-byte UTF-8 sequence for (b = 0, d = c; d; d /= 2) b++; // number of bits in c b = (b - 2) / 5; // number of bytes in payload *(s++) = (char)((0xFF << (7 - b)) | (c >> (6 * b))); // head while (b) *(s++) = (char)(0x80 | ((c >> (6 * --b)) & 0x3F)); // payload } memmove(s, strchr(s, ';') + 1, strlen(strchr(s, ';'))); } else if ((*s == '&' && (t == '&' || t == ' ' || t == '*')) || (*s == '%' && t == '%')) { // entity reference for (b = 0; ent[b] && strncmp(s + 1, ent[b], strlen(ent[b])); b += 2); // find entity in entity list if (ent[b++]) { // found a match if ((c = (long)strlen(ent[b])) - 1 > (e = strchr(s, ';')) - s) { l = (d = (long)(s - r)) + c + (long)strlen(e); // new length r = (r == m) ? strcpy(malloc(l), r) : realloc(r, l); e = strchr((s = r + d), ';'); // fix up pointers } memmove(s + c, e + 1, strlen(e)); // shift rest of string strncpy(s, ent[b], c); // copy in replacement text } else s++; // not a known entity } else if ((t == ' ' || t == '*') && isspace((int)(*s))) *(s++) = ' '; else s++; // no decoding needed } if (t == '*') { // normalize spaces for non-cdata attributes for (s = r; *s; s++) { if ((l = (long)strspn(s, " "))) memmove(s, s + l, strlen(s + l) + 1); while (*s && *s != ' ') s++; } if (--s >= r && *s == ' ') *s = '\0'; // trim any trailing space } return r; } // called when parser finds start of new tag static void switch_xml_open_tag(switch_xml_root_t root, char *name, char **attr) { switch_xml_t xml = root->cur; if (xml->name) xml = switch_xml_add_child(xml, name, strlen(xml->txt)); else xml->name = name; // first open tag xml->attr = attr; root->cur = xml; // update tag insertion point } // called when parser finds character content between open and closing tag static void switch_xml_char_content(switch_xml_root_t root, char *s, switch_size_t len, char t) { switch_xml_t xml = root->cur; char *m = s; switch_size_t l; if (! xml || ! xml->name || ! len) return; // sanity check s[len] = '\0'; // null terminate text (calling functions anticipate this) len = strlen(s = switch_xml_decode(s, root->ent, t)) + 1; if (! *(xml->txt)) xml->txt = s; // initial character content else { // allocate our own memory and make a copy xml->txt = (xml->flags & SWITCH_XML_TXTM) // allocate some space ? realloc(xml->txt, (l = strlen(xml->txt)) + len) : strcpy(malloc((l = strlen(xml->txt)) + len), xml->txt); strcpy(xml->txt + l, s); // add new char content if (s != m) free(s); // free s if it was malloced by switch_xml_decode() } if (xml->txt != m) switch_xml_set_flag(xml, SWITCH_XML_TXTM); } // called when parser finds closing tag static switch_xml_t switch_xml_close_tag(switch_xml_root_t root, char *name, char *s) { if (! root->cur || ! root->cur->name || strcmp(name, root->cur->name)) return switch_xml_err(root, s, "unexpected closing tag ", name); root->cur = root->cur->parent; return NULL; } // checks for circular entity references, returns non-zero if no circular // references are found, zero otherwise static int switch_xml_ent_ok(char *name, char *s, char **ent) { int i; for (; ; s++) { while (*s && *s != '&') s++; // find next entity reference if (! *s) return 1; if (! strncmp(s + 1, name, strlen(name))) return 0; // circular ref. for (i = 0; ent[i] && strncmp(ent[i], s + 1, strlen(ent[i])); i += 2); if (ent[i] && ! switch_xml_ent_ok(name, ent[i + 1], ent)) return 0; } } // called when the parser finds a processing instruction static void switch_xml_proc_inst(switch_xml_root_t root, char *s, switch_size_t len) { int i = 0, j = 1; char *target = s; s[len] = '\0'; // null terminate instruction if (*(s += strcspn(s, SWITCH_XML_WS))) { *s = '\0'; // null terminate target s += strspn(s + 1, SWITCH_XML_WS) + 1; // skip whitespace after target } if (! strcmp(target, "xml")) { // if ((s = strstr(s, "standalone")) && ! strncmp(s + strspn(s + 10, SWITCH_XML_WS "='\"") + 10, "yes", 3)) root->standalone = 1; return; } if (! root->pi[0]) *(root->pi = malloc(sizeof(char **))) = NULL; //first pi while (root->pi[i] && strcmp(target, root->pi[i][0])) i++; // find target if (! root->pi[i]) { // new target root->pi = realloc(root->pi, sizeof(char **) * (i + 2)); root->pi[i] = malloc(sizeof(char *) * 3); root->pi[i][0] = target; root->pi[i][1] = (char *)(root->pi[i + 1] = NULL); // terminate pi list root->pi[i][2] = strdup(""); // empty document position list } while (root->pi[i][j]) j++; // find end of instruction list for this target root->pi[i] = realloc(root->pi[i], sizeof(char *) * (j + 3)); root->pi[i][j + 2] = realloc(root->pi[i][j + 1], j + 1); strcpy(root->pi[i][j + 2] + j - 1, (root->xml.name) ? ">" : "<"); root->pi[i][j + 1] = NULL; // null terminate pi list for this target root->pi[i][j] = s; // set instruction } // called when the parser finds an internal doctype subset static short switch_xml_internal_dtd(switch_xml_root_t root, char *s, switch_size_t len) { char q, *c, *t, *n = NULL, *v, **ent, **pe; int i, j; pe = memcpy(malloc(sizeof(SWITCH_XML_NIL)), SWITCH_XML_NIL, sizeof(SWITCH_XML_NIL)); for (s[len] = '\0'; s; ) { while (*s && *s != '<' && *s != '%') s++; // find next declaration if (! *s) break; else if (! strncmp(s, "'); continue; } for (i = 0, ent = (*c == '%') ? pe : root->ent; ent[i]; i++); ent = realloc(ent, (i + 3) * sizeof(char *)); // space for next ent if (*c == '%') pe = ent; else root->ent = ent; *(++s) = '\0'; // null terminate name if ((s = strchr(v, q))) *(s++) = '\0'; // null terminate value ent[i + 1] = switch_xml_decode(v, pe, '%'); // set value ent[i + 2] = NULL; // null terminate entity list if (! switch_xml_ent_ok(n, ent[i + 1], ent)) { // circular reference if (ent[i + 1] != v) free(ent[i + 1]); switch_xml_err(root, v, "circular entity declaration &%s", n); break; } else ent[i] = n; // set entity name } else if (! strncmp(s, "")) == '>') continue; else *s = '\0'; // null terminate tag name for (i = 0; root->attr[i] && strcmp(n, root->attr[i][0]); i++); while (*(n = ++s + strspn(s, SWITCH_XML_WS)) && *n != '>') { if (*(s = n + strcspn(n, SWITCH_XML_WS))) *s = '\0'; // attr name else { switch_xml_err(root, t, "malformed ") - 1; if (*c == ' ') continue; // cdata is default, nothing to do v = NULL; } else if ((*s == '"' || *s == '\'') && // default value (s = strchr(v = s + 1, *s))) *s = '\0'; else { switch_xml_err(root, t, "malformed attr[i]) { // new tag name root->attr = (! i) ? malloc(2 * sizeof(char **)) : realloc(root->attr, (i + 2) * sizeof(char **)); root->attr[i] = malloc(2 * sizeof(char *)); root->attr[i][0] = t; // set tag name root->attr[i][1] = (char *)(root->attr[i + 1] = NULL); } for (j = 1; root->attr[i][j]; j += 3); // find end of list root->attr[i] = realloc(root->attr[i], (j + 4) * sizeof(char *)); root->attr[i][j + 3] = NULL; // null terminate list root->attr[i][j + 2] = c; // is it cdata? root->attr[i][j + 1] = (v) ? switch_xml_decode(v, root->ent, *c) : NULL; root->attr[i][j] = n; // attribute name } } else if (! strncmp(s, ""); // comments else if (! strncmp(s, ""))) switch_xml_proc_inst(root, c, s++ - c); } else if (*s == '<') s = strchr(s, '>'); // skip other declarations else if (*(s++) == '%' && ! root->standalone) break; } free(pe); return ! *root->err; } // Converts a UTF-16 string to UTF-8. Returns a new string that must be freed // or NULL if no conversion was needed. static char *switch_xml_str2utf8(char **s, switch_size_t *len) { char *u; switch_size_t l = 0, sl, max = *len; long c, d; int b, be = (**s == '\xFE') ? 1 : (**s == '\xFF') ? 0 : -1; if (be == -1) return NULL; // not UTF-16 u = malloc(max); for (sl = 2; sl < *len - 1; sl += 2) { c = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF) //UTF-16BE : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); //UTF-16LE if (c >= 0xD800 && c <= 0xDFFF && (sl += 2) < *len - 1) { // high-half d = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF) : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); c = (((c & 0x3FF) << 10) | (d & 0x3FF)) + 0x10000; } while (l + 6 > max) u = realloc(u, max += SWITCH_XML_BUFSIZE); if (c < 0x80) u[l++] = (char)c; // US-ASCII subset else { // multi-byte UTF-8 sequence for (b = 0, d = c; d; d /= 2) b++; // bits in c b = (b - 2) / 5; // bytes in payload u[l++] = (char)((0xFF << (7 - b)) | (c >> (6 * b))); // head while (b) u[l++] = (char)(0x80 | ((c >> (6 * --b)) & 0x3F)); // payload } } return *s = realloc(u, *len = l); } // frees a tag attribute list static void switch_xml_free_attr(char **attr) { int i = 0; char *m; if (! attr || attr == SWITCH_XML_NIL) return; // nothing to free while (attr[i]) i += 2; // find end of attribute list m = attr[i + 1]; // list of which names and values are malloced for (i = 0; m[i]; i++) { if (m[i] & SWITCH_XML_NAMEM) free(attr[i * 2]); if (m[i] & SWITCH_XML_TXTM) free(attr[(i * 2) + 1]); } free(m); free(attr); } // parse the given xml string and return an switch_xml structure SWITCH_DECLARE(switch_xml_t) switch_xml_parse_str(char *s, switch_size_t len) { switch_xml_root_t root = (switch_xml_root_t)switch_xml_new(NULL); char q, e, *d, **attr, **a = NULL; // initialize a to avoid compile warning int l, i, j; root->m = s; if (! len) return switch_xml_err(root, s, "root tag missing"); root->u = switch_xml_str2utf8(&s, &len); // convert utf-16 to utf-8 root->e = (root->s = s) + len; // record start and end of work area e = s[len - 1]; // save end char s[len - 1] = '\0'; // turn end char into null terminator while (*s && *s != '<') s++; // find first tag if (! *s) return switch_xml_err(root, s, "root tag missing"); for (; ; ) { attr = (char **)SWITCH_XML_NIL; d = ++s; if (isalpha((int)(*s)) || *s == '_' || *s == ':' || (int8_t) *s < '\0') { // new tag if (! root->cur) return switch_xml_err(root, d, "markup outside of root element"); s += strcspn(s, SWITCH_XML_WS "/>"); while (isspace((int)(*s))) *(s++) = '\0'; // null terminate tag name if (*s && *s != '/' && *s != '>') // find tag in default attr list for (i = 0; (a = root->attr[i]) && strcmp(a[0], d); i++); for (l = 0; *s && *s != '/' && *s != '>'; l += 2) { // new attrib attr = (l) ? realloc(attr, (l + 4) * sizeof(char *)) : malloc(4 * sizeof(char *)); // allocate space attr[l + 3] = (l) ? realloc(attr[l + 1], (l / 2) + 2) : malloc(2); // mem for list of maloced vals strcpy(attr[l + 3] + (l / 2), " "); // value is not malloced attr[l + 2] = NULL; // null terminate list attr[l + 1] = ""; // temporary attribute value attr[l] = s; // set attribute name s += strcspn(s, SWITCH_XML_WS "=/>"); if (*s == '=' || isspace((int)(*s))) { *(s++) = '\0'; // null terminate tag attribute name q = *(s += strspn(s, SWITCH_XML_WS "=")); if (q == '"' || q == '\'') { // attribute value attr[l + 1] = ++s; while (*s && *s != q) s++; if (*s) *(s++) = '\0'; // null terminate attribute val else { switch_xml_free_attr(attr); return switch_xml_err(root, d, "missing %c", q); } for (j = 1; a && a[j] && strcmp(a[j], attr[l]); j +=3); attr[l + 1] = switch_xml_decode(attr[l + 1], root->ent, (a && a[j]) ? *a[j + 2] : ' '); if (attr[l + 1] < d || attr[l + 1] > s) attr[l + 3][l / 2] = SWITCH_XML_TXTM; // value malloced } } while (isspace((int)(*s))) s++; } if (*s == '/') { // self closing tag *(s++) = '\0'; if ((*s && *s != '>') || (! *s && e != '>')) { if (l) switch_xml_free_attr(attr); return switch_xml_err(root, d, "missing >"); } switch_xml_open_tag(root, d, attr); switch_xml_close_tag(root, d, s); } else if ((q = *s) == '>' || (! *s && e == '>')) { // open tag *s = '\0'; // temporarily null terminate tag name switch_xml_open_tag(root, d, attr); *s = q; } else { if (l) switch_xml_free_attr(attr); return switch_xml_err(root, d, "missing >"); } } else if (*s == '/') { // close tag s += strcspn(d = s + 1, SWITCH_XML_WS ">") + 1; if (! (q = *s) && e != '>') return switch_xml_err(root, d, "missing >"); *s = '\0'; // temporarily null terminate tag name if (switch_xml_close_tag(root, d, s)) return &root->xml; if (isspace((int)(*s = q))) s += strspn(s, SWITCH_XML_WS); } else if (! strncmp(s, "!--", 3)) { // comment if (! (s = strstr(s + 3, "--")) || (*(s += 2) != '>' && *s) || (! *s && e != '>')) return switch_xml_err(root, d, "unclosed