/** * Signal an error to user and destroy SOAP request. */ static void soap_fault(soap_rpc_t *sr, xnode_t *xn) { soap_rpc_check(sr); if (GNET_PROPERTY(soap_debug)) { g_warning("SOAP \"%s\" at \"%s\": got a SOAP fault:", sr->action, sr->url); xfmt_tree_dump(xn, stderr); } if (sr->error_cb != NULL) (*sr->error_cb)(sr, SOAP_E_FAULT, xn, sr->arg); xnode_tree_free(xn); soap_rpc_free(sr); }
/** * Callback invoked on SOAP error (or cancel). * * @param sr the SOAP request * @param err the SOAP error code * @paran fault the XML tree root of fault description (may be NULL) * @param arg user-supplied callback */ static void upnp_ctrl_soap_error(const soap_rpc_t *sr, soap_error_t err, xnode_t *fault, void *arg) { upnp_ctrl_t *ucd = arg; host_addr_t local_addr; int code = UPNP_ERR_SOAP; upnp_ctrl_check(ucd); if (GNET_PROPERTY(upnp_debug)) { g_message("UPNP \"%s\" failed: %s", ucd->action, soap_strerror(err)); } ucd->sr = NULL; /* Done with SOAP request */ if (soap_rpc_local_addr(sr, &local_addr)) upnp_set_local_addr(local_addr); if (fault != NULL) { const char *error; if (upnp_ctrl_extract_fault(fault, &code, &error)) { if (GNET_PROPERTY(upnp_debug)) { g_message("UPNP \"%s\" fault: %s (%d => \"%s\")", ucd->action, error, code, upnp_strerror(code)); } } else { if (GNET_PROPERTY(upnp_debug)) { g_warning("UPNP \"%s\" un-parseable SOAP fault:", ucd->action); xfmt_tree_dump(fault, stderr); } code = UPNP_ERR_UNPARSEABLE; } } if (ucd->cb != NULL) (*ucd->cb)(code, NULL, 0, ucd->cb_arg); upnp_ctrl_free(ucd); }
/** * 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); }
/** * Initiate a SOAP remote procedure call. * * Call will be launched asynchronously, not immediately upon return so that * callbacks are never called on the same stack frame and to allow further * options to be set on the handle before the call begins. * * Initially the request is sent as a regular POST. It is possible to force * the usage of the HTTP Extension Framework by using the SOAP_RPC_O_MAN_FORCE * option, in which case an M-POST will be sent with the proper SOAP Man: * header. Finally, automatic retry of the request can be requested via the * SOAP_RPC_O_MAN_RETRY option: it will start with POST and switch to M-POST * on 405 or 510 errors. * * @param url the HTTP URL to contact for the RPC * @param action the SOAP action to perform * @param maxlen maximum length of data we accept to receive * @param options user-supplied options * @param xn SOAP RPC data payload (XML tree root, will be freed) * @param soap_ns requested SOAP namespace prefix, NULL to use default * @param reply_cb callback to invoke when we get a reply * @param error_cb callback to invoke on error * @param arg additional user-defined callback parameter * * @return a SOAP RPC handle, NULL if the request cannot be initiated (XML * payload too large). In any case, the XML tree is freed. */ soap_rpc_t * soap_rpc(const char *url, const char *action, size_t maxlen, guint32 options, xnode_t *xn, const char *soap_ns, soap_reply_cb_t reply_cb, soap_error_cb_t error_cb, void *arg) { soap_rpc_t *sr; xnode_t *root, *body; pmsg_t *mb; ostream_t *os; gboolean failed = FALSE; g_assert(url != NULL); g_assert(action != NULL); /* * Create the SOAP XML request. */ root = xnode_new_element(NULL, SOAP_NAMESPACE, SOAP_X_ENVELOPE); xnode_add_namespace(root, soap_ns ? soap_ns : "SOAP", SOAP_NAMESPACE); xnode_prop_ns_set(root, SOAP_NAMESPACE, SOAP_X_ENC_STYLE, SOAP_ENCODING); body = xnode_new_element(root, SOAP_NAMESPACE, SOAP_X_BODY); xnode_add_child(body, xn); /* * Serialize the XML tree to a PDU message buffer. */ mb = pmsg_new(PMSG_P_DATA, NULL, SOAP_MAX_PAYLOAD); os = ostream_open_pmsg(mb); xfmt_tree(root, os, XFMT_O_NO_INDENT); if (!ostream_close(os)) { failed = TRUE; g_warning("SOAP unable to serialize payload within %d bytes", SOAP_MAX_PAYLOAD); if (GNET_PROPERTY(soap_debug) > 1) xfmt_tree_dump(root, stderr); } /* * Free the XML tree, including the supplied user nodes. */ xnode_tree_free(root); if (failed) { pmsg_free(mb); return NULL; } /* * Serialization of the XML payload was successful, prepare the * asynchronous SOAP request. */ sr = soap_rpc_alloc(); sr->url = atom_str_get(url); sr->action = atom_str_get(action); sr->maxlen = maxlen; sr->content_len = maxlen; /* Until we see a Content-Length */ sr->options = options; sr->mb = mb; sr->reply_cb = reply_cb; sr->error_cb = error_cb; sr->arg = arg; /* * Make sure the error callback is not called synchronously, and give * them time to supply other options after creating the request before * it starts. */ sr->delay_ev = cq_main_insert(1, soap_rpc_launch, sr); return sr; }
/** * 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; }