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