Example #1
0
/**
 * 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;
}
Example #2
0
/**
 * 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);
}
Example #3
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;
}
Example #4
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;
}
Example #5
0
/**
 * 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;
}