/** * 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; }