/** * @return whether node has content (children). */ bool xnode_has_content(const xnode_t *xn) { xnode_check(xn); return xnode_first_child(xn) != NULL; }
/* XML helper functions */ static xnode_t * find_element_by_name(xnode_t *p, const char *name) { xnode_t *xn; for (xn = xnode_first_child(p); xn != NULL; xn = xnode_next_sibling(xn)) { if (xnode_is_element(xn) && 0 == strcmp(xnode_element_name(xn), name)) return xn; } return NULL; }
/** * @return node's first child text, NULL if the first child is not a * text node, or an empty string if the node has no children. */ const char * xnode_first_text(const xnode_t *xn) { const xnode_t *child; xnode_check(xn); child = xnode_first_child(xn); if (NULL == child) return ""; return xnode_text(child); }
/** * @return whether node is empty (has either no children, or a single one * which is an empty text node). */ bool xnode_is_empty(const xnode_t *xn) { const xnode_t *xc; xnode_check(xn); xc = xnode_first_child(xn); if (NULL == xc) return TRUE; if (xnode_next_sibling(xc) != NULL) return FALSE; return XNODE_T_TEXT == xc->type && '\0' == *xc->u.t.text; }
/** * Successful SOAP RPC reply callback. * * @param sr the SOAP RPC request * @param root XML tree of SOAP reply * @param arg the UPnP control request */ static void upnp_ctrl_soap_reply(const soap_rpc_t *sr, xnode_t *root, void *arg) { upnp_ctrl_t *ucd = arg; xnode_t *xn; nv_table_t *nvt; void *reply; size_t reply_len; host_addr_t local_addr; int code; upnp_ctrl_check(ucd); if (GNET_PROPERTY(upnp_debug) > 1) { g_debug("UPNP got SOAP reply for %s", ucd->action); if (GNET_PROPERTY(upnp_debug) > 2) xfmt_tree_dump(root, stderr); } ucd->sr = NULL; /* Done with SOAP request */ if (soap_rpc_local_addr(sr, &local_addr)) upnp_set_local_addr(local_addr); /* * Decompile the returned values. * * <u:actionResponse xmlns:u="urn:schemas-upnp-org:service:serviceType:v"> * <arg1>out value1</arg1> * <arg2>out value2</arg2> * : : : : * <argn>out valuen</argn> * </u:actionResponse> * * Values are inserted in name / value pairs: "arg1" -> "out value 1" and * given to the launch callback for extracting and decompiling the values. */ nvt = nv_table_make(TRUE); for (xn = xnode_first_child(root); xn; xn = xnode_next_sibling(xn)) { nv_pair_t *nv; xnode_t *xt; if (!xnode_is_element(xn)) { if (GNET_PROPERTY(upnp_debug)) { g_warning("UPNP \"%s\" skipping XML node %s", ucd->action, xnode_to_string(xn)); } continue; } xt = xnode_first_child(xn); if (NULL == xt || !xnode_is_text(xt)) { if (GNET_PROPERTY(upnp_debug)) { g_warning("UPNP \"%s\" bad child node %s in %s", ucd->action, xnode_to_string(xt), xnode_to_string2(xn)); } } else { /* * Name/value strings point in the tree, which is going to be * alive for the duration of the processing, so we can use the * strings without copying them. */ nv = nv_pair_make_static_str( xnode_element_name(xn), xnode_text(xt)); nv_table_insert_pair(nvt, nv); if (xnode_next_sibling(xt) != NULL) { if (GNET_PROPERTY(upnp_debug)) { g_warning("UPNP \"%s\" content of %s is not pure text", ucd->action, xnode_to_string(xt)); } } } } /* * Attempt to decompile the replied values, if any are expected. * * Allocated data is done via walloc(), and the returned structure is flat. * It will be freed after invoking the user callback. */ if (ucd->lcb != NULL) { reply = (*ucd->lcb)(nvt, &reply_len); code = NULL == reply ? UPNP_ERR_OK : UPNP_ERR_BAD_REPLY; } else { code = UPNP_ERR_OK; reply = NULL; reply_len = 0; } /* * Let UPnP control invoker know about the result of the query. */ (*ucd->cb)(code, reply, reply_len, ucd->cb_arg); /* * Done, final cleanup. */ WFREE_NULL(reply, reply_len); nv_table_free(nvt); upnp_ctrl_free(ucd); }
/** * Extract information from SOAP fault tree. * * @param fault the XML <Fault> tree * @param code where UPnP error code is written, if non-NULL * @param error where address of UPnP error string is written, if non-NULL * * @attention * The error string is pointing in the XML tree and will become invalid as * soon as the tree is freed so it needs to be duplicated if it must persist. * * @return TRUE if OK, FALSE on error. */ static gboolean upnp_ctrl_extract_fault(xnode_t *fault, int *code, const char **error) { xnode_t *fcode, *fstring, *detail, *uerror; const char *parse_error = NULL; g_assert(fault != NULL); /* * The SOAP specification for the <faultcode> element are very bad. * Indeed, the content is a string bearing the *prefix* of the SOAP * namespace, which is completely arbitrary and not accessible at this * level since all nodes are normalized with their namespace, the prefix * string being irrelevant once parsing is done. And namespace have no * meaning in element *content*. * * Sure, we know we force the "s" prefix for SOAP, and most UPnP stacks * are going to use that prefix as well, but matching the <faultcode> * content to look for "s:Client" or "s:MustUnderstand" is just plain * wrong, and a blatant encapsulation violation. * * So instead we look backwards in the string to find the first ':' and * consider the tail part of the string, totally ignoring the prefix. * That's a lousy parsing, but in practice it's going to work and should * be safe since there's little choice anyway according to the SOAP * specifications (meaning they could have just as well ignored the * prefix in this string and just mandate "Client" or "MustUnderstand"). * * Also note that <faultcode>, <faultstring> and <detail> elements are * architected without any SOAP namespace. That's surprising. */ fcode = xnode_tree_find_depth(fault, 1, node_named_as, deconstify_gchar(SOAP_FAULT_CODE)); if (NULL == fcode) { parse_error = "cannot find <faultcode>"; goto error; } else { const char *value; const char *name; value = xnode_first_text(fcode); if (NULL == value) { parse_error = "<faultcode> does not contain text"; goto error; } /* * We're only handling "Client" errors. */ name = strrchr(value, ':'); if (NULL == name) { parse_error = "no ':' in fault code name"; goto error; } name++; if (0 != strcmp(name, SOAP_CLIENT_FAULT)) { parse_error = "not a Client fault"; goto error; } } /* * Here is a sample fault tree from the UPnP 1.0 architecture: * * <s:Fault> * <faultcode>s:Client</faultcode> * <faultstring>UPnPError</faultstring> * <detail> * <UPnpError xmlns="urn:schemas-upnp-org:control-1-0"> * <errorCode>error code</errorCode> * <errorDescription>error string</errorDescription> * </UPnPError> * </detail> * <s:Fault> * * Note that the UPnP tags are in the "urn:schemas-upnp-org:control-1-0" * namespace. */ fstring = xnode_tree_find_depth(fault, 1, node_named_as, deconstify_gchar(SOAP_FAULT_STRING)); if (NULL == fstring) { parse_error = "no <faultstring> found"; goto error; } else { const char *value; value = xnode_first_text(fstring); if (NULL == value) { parse_error = "<faultstring> does not contain text"; goto error; } if (0 != strcmp(value, SOAP_UPNP_ERROR)) { parse_error = "<faultstring> is not an UPnP error"; goto error; } } detail = xnode_tree_find_depth(fault, 1, node_named_as, deconstify_gchar(SOAP_FAULT_DETAIL)); if (NULL == detail) { parse_error = "no <detail> found"; goto error; } /* * First child must be a <UPnpError> tag. */ uerror = xnode_first_child(detail); if (xnode_is_element_named(uerror, UPNP_NS_ERROR, SOAP_UPNP_ERROR)) { xnode_t *xn; if (code != NULL) { const char *value; int err; xn = xnode_tree_find_depth(uerror, 1, node_named_as, deconstify_gchar(UPNP_ERROR_CODE)); if (NULL == xn) { parse_error = "no <errorCode> found"; goto error; } value = xnode_first_text(xn); if (NULL == value) { parse_error = "<errorCode> doest not contain text"; goto error; } *code = parse_uint32(value, NULL, 10, &err); if (err) { parse_error = "cannot parse <errorCode> value"; goto error; } } if (error != NULL) { xn = xnode_tree_find_depth(uerror, 1, node_named_as, deconstify_gchar(UPNP_ERROR_DESC)); *error = (NULL == xn) ? NULL : xnode_first_text(xn); } } else { parse_error = "no <UPnPError> found"; goto error; } return TRUE; error: if (GNET_PROPERTY(upnp_debug)) g_warning("UPNP fault parsing error: %s", EMPTY_STRING(parse_error)); return FALSE; }
/** * Process the SOAP reply from the server. */ static void soap_process_reply(soap_rpc_t *sr) { const char *buf; vxml_parser_t *vp; vxml_error_t e; xnode_t *root = NULL; xnode_t *xn = NULL; const char *charset; soap_rpc_check(sr); if (sr->reply_len != 0 && (GNET_PROPERTY(soap_trace) & SOCK_TRACE_IN)) { g_debug("----Got SOAP HTTP reply data from %s:", sr->url); if (log_printable(LOG_STDERR)) { fwrite(sr->reply_data, sr->reply_len, 1, stderr); fputs("----End SOAP HTTP reply\n", stderr); } } if (GNET_PROPERTY(soap_debug) > 2) { g_debug("SOAP \"%s\" at \"%s\": processing reply (%lu byte%s) HTTP %d", sr->action, sr->url, (unsigned long) sr->reply_len, 1 == sr->reply_len ? "" : "s", sr->http_code); } /* * If we got a 2xx reply, we need to parse up to the <Body> element * and then pass up the remaining to the user for parsing specific * elemnts accordingly. * * Other reply codes indicate an error. On 4xx replies we may not * have any XML to parse. On 5xx replies, we should usually have * a <Fault> indication under the <Body>. * * The strategy used here is to parse the XML reply into a tree and then * analyse the tree, ignoring the HTTP status code which is redundant. */ buf = header_get(sr->header, "Content-Type"); if (NULL == buf) goto no_xml; /* * MIME type and subtypes are case-insensitive (see RFC 2616, section 3.7). */ if ( !http_field_starts_with(buf, SOAP_TEXT_REPLY, FALSE) && !http_field_starts_with(buf, SOAP_APPLICATION_REPLY, FALSE) ) { if (GNET_PROPERTY(soap_debug)) { g_debug("SOAP \"%s\" at \"%s\": got unexpected Content-Type: %s", sr->action, sr->url, buf); } goto no_xml; } /* * Extract charset if given. */ charset = http_parameter_get(buf, "charset"); /* * Parse the SOAP envelope. */ vp = vxml_parser_make(sr->action, VXML_O_STRIP_BLANKS); vxml_parser_add_data(vp, sr->reply_data, sr->reply_len); if (!vxml_parser_set_charset(vp, charset)) { g_warning("SOAP \"%s\" at \"%s\": ignoring unknown charset \"%s\"", sr->action, sr->url, charset); } e = vxml_parse_tree(vp, &root); vxml_parser_free(vp); if (e != VXML_E_OK) { if (GNET_PROPERTY(soap_debug)) { g_debug("SOAP \"%s\" at \"%s\": cannot parse XML reply: %s", sr->action, sr->url, vxml_strerror(e)); } goto bad_xml; } g_assert(root != NULL); /* * Make sure we got a SOAP reply. */ if (!xnode_is_element_named(root, SOAP_NAMESPACE, SOAP_X_ENVELOPE)) goto not_soap; /* * Look for the <SOAP:Body> element. */ for (xn = xnode_first_child(root); TRUE; xn = xnode_next_sibling(xn)) { if (NULL == xn || !xnode_within_namespace(xn, SOAP_NAMESPACE)) goto bad_soap; if (0 == strcmp(SOAP_X_BODY, xnode_element_name(xn))) break; } /* * Inspect the first child of the <SOAP:Body> element. * * If it's a <SOAP:Fault>, go process it and return an error. * If it's another SOAP tag, we have an unknown structure. * Otherwise it's the reply, for user code to handle. */ xn = xnode_first_child(xn); if (NULL == xn) goto bad_soap; if (xnode_is_element_named(xn, SOAP_NAMESPACE, SOAP_X_FAULT)) { xnode_detach(xn); soap_fault(sr, xn); } else if (xnode_within_namespace(xn, SOAP_NAMESPACE)) { goto bad_soap; } else { xnode_detach(xn); soap_reply(sr, xn); } xnode_tree_free(root); return; not_soap: if (GNET_PROPERTY(soap_debug)) { g_debug("SOAP \"%s\" at \"%s\": unexpected root XML " "element <%s:%s>", sr->action, sr->url, EMPTY_STRING(xnode_element_ns(root)), xnode_element_name(root)); } xnode_tree_free(root); /* FALL THROUGH */ no_xml: soap_error(sr, SOAP_E_PROTOCOL); return; bad_soap: if (GNET_PROPERTY(soap_debug)) { g_debug("SOAP \"%s\" at \"%s\": unexpected XML structure", sr->action, sr->url); } if (GNET_PROPERTY(soap_debug) > 1) { g_debug("SOAP current node is %s", xnode_to_string(xn)); } if (GNET_PROPERTY(soap_debug) > 2) xfmt_tree_dump(root, stderr); xnode_tree_free(root); /* FALL THROUGH */ bad_xml: soap_error(sr, SOAP_E_PROCESSING); return; }