/** * Pass 1 handler on each tree node entry. */ static bool xfmt_handle_pass1_enter(const void *node, void *data) { const xnode_t *xn = node; struct xfmt_pass1 *xp1 = data; xp1->depth++; if (xnode_is_element(xn)) { const char *uri = xnode_element_ns(xn); xp1->node = xn; if (uri != NULL) xfmt_uri_declare(uri, xp1); xnode_prop_foreach(xn, xfmt_handle_pass1_attr, xp1); xnode_ns_foreach(xn, xfmt_handle_pass1_ns, xp1); } return TRUE; }
/** * Pass 2 handler on each tree node leave. */ static void xfmt_handle_pass2_leave(void *node, void *data) { xnode_t *xn = node; struct xfmt_pass2 *xp2 = data; if (xnode_is_element(xn)) { const char *uri = xnode_element_ns(xn); xfmt_indent(xp2); /* * We don't emit the URI if it is that of the default namespace. */ if ( uri != NULL && xp2->default_ns != NULL && 0 == strcmp(uri, xp2->default_ns) ) { uri = NULL; } if (uri != NULL) { const char *pre = xfmt_uri_to_prefix(xp2, uri); ostream_printf(xp2->os, "</%s:%s>", pre, xnode_element_name(xn)); } else { ostream_printf(xp2->os, "</%s>", xnode_element_name(xn)); } if (!(xp2->options & XFMT_O_SINGLE_LINE)) { ostream_putc(xp2->os, '\n'); } /* Reset for next element */ xp2->had_text = FALSE; xp2->last_was_nl = TRUE; } xfmt_pass2_leaving(xp2); }
/** * Launch UPnP control request. * * The argv[] vector (with argc entries) contains the arguments and their * values to send to the remote UPnP device. * * If a structured reply is expected (and not just a returned status code), * a launch_cb callback must be provided to process the arguments returned * by the control request and populate a structure that will be passed to the * user callback to propagate the result of the control request. * * @param usd the service to contact * @param action the action to request * @param argv the argument list for the request * @param argc amount of arguments in argv[] * @param cb user-callback when action is completed * @param cb_arg additional callback argument * @param launch_cb internal launch callback invoked on SOAP reply * * @return UPnP request handle if the SOAP RPC was initiated, NULL otherwise * (in which case callbacks will never be called). */ static upnp_ctrl_t * upnp_ctrl_launch(const upnp_service_t *usd, const char *action, nv_pair_t **argv, size_t argc, upnp_ctrl_cb_t cb, void *cb_arg, upnp_ctrl_launch_cb_t launch_cb) { upnp_ctrl_t *ucd; xnode_t *root; size_t i; soap_rpc_t *sr; g_assert(usd != NULL); g_assert(action != NULL); g_assert(0 == argc || argv != NULL); WALLOC0(ucd); ucd->magic = UPNP_CTRL_MAGIC; ucd->lcb = launch_cb; ucd->cb = cb; ucd->cb_arg = cb_arg; /* * The root element of the UPnP request. * * Its serialized form looks like this: * * <u:action xmlns:u="urn:schemas-upnp-org:service:serviceType:v"> * <arg1>in value1</arg1> * <arg2>in value2</arg2> * : : : : * <argn>in valuen</argn> * </u:action> * * The "u" prefix is arbitrary but it is the one used in all examples * presented in the UPnP architecture, and naive implementations within * devices could choke on anything else. */ { char ns[256]; gm_snprintf(ns, sizeof ns, "%s%s:%u", UPNP_NS_BASE, upnp_service_type_to_string(upnp_service_type(usd)), upnp_service_version(usd)); root = xnode_new_element(NULL, ns, action); xnode_add_namespace(root, UPNP_PREFIX, ns); } /* * Attach each argument to the root. */ for (i = 0; i < argc; i++) { nv_pair_t *nv = argv[i]; xnode_t *xargs; xargs = xnode_new_element(root, NULL, nv_pair_name(nv)); xnode_new_text(xargs, nv_pair_value_str(nv), FALSE); } /* * Launch the SOAP RPC. * * We force "s" as the SOAP prefix. It shouldn't matter at all, but * since the UPnP architecture documents its examples with "s", naive * implementations in devices could choke on anything else. * * Likewise, since the UPnP architecture document uses all-caps HTTP header * names, we can expect that some implementations within devices will * not properly understand headers spelt with traditional mixed-cased, * although it mentions that headers are case-insensitive names. Hence, * force all-caps header names. * * If the SOAP RPC cannot be launched (payload too large), the XML tree * built above was freed anyway. */ { char action_uri[256]; guint32 options = SOAP_RPC_O_MAN_RETRY | SOAP_RPC_O_ALL_CAPS; /* * Grab our local IP address if it is unknown so far. */ if (host_addr_net(upnp_get_local_addr()) == NET_TYPE_NONE) options |= SOAP_RPC_O_LOCAL_ADDR; gm_snprintf(action_uri, sizeof action_uri, "%s#%s", xnode_element_ns(root), action); ucd->action = atom_str_get(action_uri); sr = soap_rpc(upnp_service_control_url(usd), action_uri, UPNP_REPLY_MAXSIZE, options, root, UPNP_SOAP_PREFIX, upnp_ctrl_soap_reply, upnp_ctrl_soap_error, ucd); } /* * We no longer need the arguments. */ for (i = 0; i < argc; i++) { nv_pair_free_null(&argv[i]); } /* * Cleanup if we were not able to launch the request because the * serialized XML payload is too large. */ if (NULL == sr) { if (GNET_PROPERTY(upnp_debug)) { g_warning("UPNP SOAP RPC \"%s\" to \"%s\" not launched: " "payload is too large", action, upnp_service_control_url(usd)); } upnp_ctrl_free(ucd); return NULL; } ucd->sr = sr; /* So that we may cancel it if needed */ return ucd; }
/** * 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; }
/** * Pass 2 handler on each tree node entry. */ static bool xfmt_handle_pass2_enter(const void *node, void *data) { const xnode_t *xn = node; struct xfmt_pass2 *xp2 = data; xp2->depth++; if (xnode_is_element(xn)) { GSList *ns = xfmt_ns_declarations(xp2, xn); const char *nsuri = xnode_element_ns(xn); if (!xp2->had_text && !xp2->last_was_nl) { if (!(xp2->options & XFMT_O_SINGLE_LINE)) ostream_putc(xp2->os, '\n'); xp2->last_was_nl = TRUE; } xfmt_indent(xp2); /* * Look for the namespace matching the default namespace, in which * case we don't have to emit it. */ if ( nsuri != NULL && xp2->default_ns != NULL && 0 == strcmp(nsuri, xp2->default_ns) ) { nsuri = NULL; } if (nsuri != NULL) { const char *prefix = xfmt_uri_to_prefix(xp2, nsuri); ostream_printf(xp2->os, "<%s:%s", prefix, xnode_element_name(xn)); } else { ostream_printf(xp2->os, "<%s", xnode_element_name(xn)); } /* * Install default namespace on the root element, if any. */ if (1 == xp2->depth && xp2->default_ns != NULL) { int c = xfmt_quoting_char(xp2->default_ns); g_assert(c != '\0'); ostream_printf(xp2->os, " xmlns=%c%s%c", c, xp2->default_ns, c); } /* * Declare namespaces for the element's scope. */ xfmt_pass2_declare_ns(xp2, ns); g_slist_free(ns); /* * Emit attributes. */ xnode_prop_foreach(xn, xfmt_handle_pass2_attr, xp2); /* * Handle content-less elements specially: we don't let the * "leave" callback run. * * We consider an element with a single empty text child as * content-less, so we test with xnode_is_empty() instead of * !xnode_has_content(). */ xp2->had_text = FALSE; if (xnode_is_empty(xn)) { ostream_write(xp2->os, XFMT_EMPTY, CONST_STRLEN(XFMT_EMPTY)); if (!(xp2->options & XFMT_O_SINGLE_LINE)) ostream_putc(xp2->os, '\n'); xp2->last_was_nl = TRUE; xfmt_pass2_leaving(xp2); /* No children, no "leave" callback */ return FALSE; } ostream_write(xp2->os, XFMT_GT, CONST_STRLEN(XFMT_GT)); xp2->last_was_nl = FALSE; } else if (xnode_is_text(xn)) { const char *text = xnode_text(xn); size_t len; size_t overhead; bool amp; if (xp2->options & XFMT_O_SKIP_BLANKS) { const char *start; size_t tlen; start = xfmt_strip_blanks(text, &tlen); if (0 == tlen) goto ignore; /* FIXME: handle blank collapsing */ (void) start; } /* * If text is known to have entities, we must not escape the '&'. * This means the generated XML must define that entity in the DTD * part of the tree. * * Computes the required overhead to fully escape the text (0 meaning * that no escaping is required). If the overhead is larger than * a leading "<![CDATA[" and a closing ""]]>", we can emit a CDATA * section instead, provided the text does not contain "]]>". */ amp = !xnode_text_has_entities(xn); overhead = xfmt_text_escape_overhead(text, amp, FALSE, &len); if (0 == overhead) { ostream_write(xp2->os, text, len); } else if ( overhead >= XFMT_CDATA_OVERHEAD && NULL == strstr(text, XFMT_CDATA_END) ) { ostream_write(xp2->os, XFMT_CDATA_START, CONST_STRLEN(XFMT_CDATA_START)); ostream_write(xp2->os, text, len); ostream_write(xp2->os, XFMT_CDATA_END, CONST_STRLEN(XFMT_CDATA_END)); } else { char *escaped = xfmt_text_escape(text, amp, FALSE, len + overhead); ostream_write(xp2->os, escaped, len + overhead); hfree(escaped); } xp2->last_was_nl = FALSE; xp2->had_text = TRUE; } ignore: return TRUE; }