Esempio n. 1
0
/**
 * @return whether node has content (children).
 */
bool
xnode_has_content(const xnode_t *xn)
{
	xnode_check(xn);

	return xnode_first_child(xn) != NULL;
}
Esempio n. 2
0
/* XML helper functions */
static xnode_t * 
find_element_by_name(xnode_t *p, const char *name)
{
   xnode_t *xn;

    for (xn = xnode_first_child(p); xn != NULL; xn = xnode_next_sibling(xn)) {
        if (xnode_is_element(xn) && 0 == strcmp(xnode_element_name(xn), name))
			return xn;
    }
    return NULL;
}
Esempio n. 3
0
/**
 * @return node's first child text, NULL if the first child is not a
 * text node, or an empty string if the node has no children.
 */
const char *
xnode_first_text(const xnode_t *xn)
{
	const xnode_t *child;

	xnode_check(xn);

	child = xnode_first_child(xn);
	if (NULL == child)
		return "";

	return xnode_text(child);
}
Esempio n. 4
0
/**
 * @return whether node is empty (has either no children, or a single one
 * which is an empty text node).
 */
bool
xnode_is_empty(const xnode_t *xn)
{
	const xnode_t *xc;

	xnode_check(xn);

	xc = xnode_first_child(xn);

	if (NULL == xc)
		return TRUE;

	if (xnode_next_sibling(xc) != NULL)
		return FALSE;

	return XNODE_T_TEXT == xc->type && '\0' == *xc->u.t.text;
}
Esempio n. 5
0
/**
 * 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);
}
Esempio n. 6
0
/**
 * 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;
}
Esempio n. 7
0
/**
 * 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;
}