Пример #1
0
/**
 * 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;
}
Пример #2
0
/**
 * 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;
}