/* $Id$
 *
 * Lasso - A free implementation of the Liberty Alliance specifications.
 *
 * Copyright (C) 2004-2007 Entr'ouvert
 * http://lasso.entrouvert.org
 *
 * Authors: See AUTHORS file in top-level directory.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/**
 * SECTION:node
 * @short_description: Base class for all Lasso objects
 *
 * #LassoNode is the base class for Lasso objects; just a step over GObject as
 * defined in glib.
 *
 */

#include "private.h"
#include <ctype.h>


#include <xmlsec/base64.h>
#include <xmlsec/xmltree.h>
#include <xmlsec/xmldsig.h>
#include <xmlsec/templates.h>
#include <xmlsec/crypto.h>
#include <xmlsec/xmlenc.h>

#include "xml.h"
#include "xml_enc.h"
#include "saml_name_identifier.h"
#include "../utils.h"
#include "../registry.h"
#include "../debug.h"

static void lasso_node_build_xmlNode_from_snippets(LassoNode *node, xmlNode *xmlnode,
		struct XmlSnippet *snippets, gboolean lasso_dump);
static struct XmlSnippet* find_xml_snippet_by_name(LassoNode *node, char *name);
static gboolean set_value_at_path(LassoNode *node, char *path, char *query_value);
static char* get_value_by_path(LassoNode *node, char *path, struct XmlSnippet *xml_snippet);
static gboolean find_path(LassoNode *node, char *path, LassoNode **value_node,
		struct XmlSnippet **snippet);

static void lasso_node_add_signature_template(LassoNode *node, xmlNode *xmlnode,
		struct XmlSnippet *snippet_signature);
static void lasso_node_traversal(LassoNode *node, void (*do_to_node)(LassoNode *node, SnippetType type), SnippetType type);

static LassoNode* lasso_node_new_from_xmlNode_with_type(xmlNode *xmlnode, char *typename);
static void lasso_node_remove_original_xmlnode(LassoNode *node, SnippetType type);

GHashTable *dst_services_by_href = NULL; /* ID-WSF 1 extra DST services, indexed on href */
GHashTable *dst_services_by_prefix = NULL; /* ID-WSF 1 extra DST services, indexed on prefix */

GHashTable *idwsf2_dst_services_by_href = NULL; /* ID-WSF 2 DST services, indexed on href */
GHashTable *idwsf2_dst_services_by_prefix = NULL; /* ID-WSF 2 DST services, indexed on prefix */

/*****************************************************************************/
/* global methods                                                            */
/*****************************************************************************/


/**
 * lasso_register_dst_service:
 * @prefix: prefix of DST service
 * @href: href of DST service
 *
 * Registers prefix and href of a custom data service template service.
 **/
void
lasso_register_dst_service(const gchar *prefix, const gchar *href)
{
	if (dst_services_by_href == NULL) {
		dst_services_by_href = g_hash_table_new_full(
				g_str_hash, g_str_equal, g_free, g_free);
		dst_services_by_prefix = g_hash_table_new_full(
				g_str_hash, g_str_equal, g_free, g_free);
	}
	g_hash_table_insert(dst_services_by_prefix, g_strdup(prefix), g_strdup(href));
	g_hash_table_insert(dst_services_by_href, g_strdup(href), g_strdup(prefix));
}

void
lasso_register_idwsf2_dst_service(const gchar *prefix, const gchar *href)
{
	if (idwsf2_dst_services_by_href == NULL) {
		idwsf2_dst_services_by_href = g_hash_table_new_full(
				g_str_hash, g_str_equal, g_free, g_free);
		idwsf2_dst_services_by_prefix = g_hash_table_new_full(
				g_str_hash, g_str_equal, g_free, g_free);
	}
	g_hash_table_insert(idwsf2_dst_services_by_prefix, g_strdup(prefix), g_strdup(href));
	g_hash_table_insert(idwsf2_dst_services_by_href, g_strdup(href), g_strdup(prefix));
}

gchar*
lasso_get_prefix_for_dst_service_href(const gchar *href)
{
	if (strcmp(href, LASSO_PP_HREF) == 0)
		return g_strdup(LASSO_PP_PREFIX);
	if (strcmp(href, LASSO_EP_HREF) == 0)
		return g_strdup(LASSO_EP_PREFIX);

	if (dst_services_by_href == NULL)
		return NULL;

	return g_strdup(g_hash_table_lookup(dst_services_by_href, href));
}

gchar*
lasso_get_prefix_for_idwsf2_dst_service_href(const gchar *href)
{
	if (idwsf2_dst_services_by_href == NULL)
		return NULL;

	return g_strdup(g_hash_table_lookup(idwsf2_dst_services_by_href, href));
}


/*****************************************************************************/
/* virtual public methods                                                    */
/*****************************************************************************/

/**
 * lasso_node_dump:
 * @node: a #LassoNode
 *
 * Dumps @node.  All datas in object are dumped in an XML format.
 *
 * Return value: a full XML dump of @node.  The string must be freed by the
 *     caller.
 **/
char*
lasso_node_dump(LassoNode *node)
{
	xmlNode *xmlnode;
	char *ret;
	xmlOutputBuffer *buf;

	g_return_val_if_fail (LASSO_IS_NODE(node), NULL);

	buf = xmlAllocOutputBuffer(NULL);
	if (buf == NULL) {
		return NULL;
	}
	xmlnode = lasso_node_get_xmlNode(node, TRUE);
	xmlNodeDumpOutput(buf, NULL, xmlnode, 0, 1, NULL);
	xmlOutputBufferFlush(buf);
	if (buf->conv != NULL) {
		ret = g_strdup((char*)buf->conv->content);
	} else {
		ret = g_strdup((char*)buf->buffer->content);
	}
	xmlOutputBufferClose(buf);

	xmlFreeNode(xmlnode);

	return ret;
}

/**
 * lasso_node_destroy:
 * @node: a #LassoNode
 *
 * Destroys the #LassoNode.
 **/
void
lasso_node_destroy(LassoNode *node)
{
	if (node == NULL) {
		return;
	}
	if (LASSO_IS_NODE(node)) {
		LassoNodeClass *class = LASSO_NODE_GET_CLASS(node);
		class->destroy(node);
	}
}

/**
 * lasso_node_export_to_base64:
 * @node: a #LassoNode
 *
 * Exports @node to a base64-encoded message.
 *
 * Return value: a base64-encoded export of @node.  The string must be freed by
 *      the caller.
 **/
char*
lasso_node_export_to_base64(LassoNode *node)
{
	xmlNode *message;
	xmlOutputBufferPtr buf;
	xmlCharEncodingHandlerPtr handler = NULL;
	xmlChar *buffer;
	char *ret;

	g_return_val_if_fail(LASSO_IS_NODE(node), NULL);

	message = lasso_node_get_xmlNode(node, FALSE);

	handler = xmlFindCharEncodingHandler("utf-8");
	buf = xmlAllocOutputBuffer(handler);
	xmlNodeDumpOutput(buf, NULL, message, 0, 0, "utf-8");
	xmlOutputBufferFlush(buf);
	buffer = buf->conv ? buf->conv->content : buf->buffer->content;

	ret = (char*)xmlSecBase64Encode(buffer, strlen((char*)buffer), 0);
	xmlOutputBufferClose(buf);

	xmlFreeNode(message);

	return ret;
}

/**
 * lasso_node_export_to_ecp_soap_response:
 * @node: a #LassoNode
 *
 * Exports @node to a ECP SOAP message.
 *
 * Return value: a ECP SOAP export of @node.  The string must be freed by the
 *      caller.
 **/
char*
lasso_node_export_to_ecp_soap_response(LassoNode *node, const char *assertionConsumerURL)
{
	xmlNode *envelope, *body, *message, *header, *ecp_response;
	xmlNs *soap_env_ns, *ecp_ns;
	xmlOutputBuffer *buf;
	xmlCharEncodingHandler *handler;
	char *ret;

	g_return_val_if_fail(LASSO_IS_NODE(node), NULL);

	message = lasso_node_get_xmlNode(node, FALSE);

	envelope = xmlNewNode(NULL, (xmlChar*)"Envelope");
	soap_env_ns = xmlNewNs(envelope,
				(xmlChar*)LASSO_SOAP_ENV_HREF, (xmlChar*)LASSO_SOAP_ENV_PREFIX);
	xmlSetNs(envelope, soap_env_ns);

	header = xmlNewTextChild(envelope, NULL, (xmlChar*)"Header", NULL);

	/* ECP response header block */
	ecp_response = xmlNewNode(NULL, (xmlChar*)"Response");
	ecp_ns = xmlNewNs(ecp_response, (xmlChar*)LASSO_ECP_HREF, (xmlChar*)LASSO_ECP_PREFIX);
	xmlSetNs(ecp_response, ecp_ns);
	xmlSetNsProp(ecp_response, soap_env_ns, (xmlChar*)"mustUnderstand", (xmlChar*)"1");
	xmlSetNsProp(ecp_response, soap_env_ns,
			(xmlChar*)"actor", (xmlChar*)LASSO_SOAP_ENV_ACTOR);
	xmlSetProp(ecp_response, (xmlChar*)"AssertionConsumerServiceURL",
			(const xmlChar*)assertionConsumerURL);
	xmlAddChild(header, ecp_response);

	/* Body block */
	body = xmlNewTextChild(envelope, NULL, (xmlChar*)"Body", NULL);
	xmlAddChild(body, message);

	handler = xmlFindCharEncodingHandler("utf-8");
	buf = xmlAllocOutputBuffer(handler);
	xmlNodeDumpOutput(buf, NULL, envelope, 0, 0, "utf-8");
	xmlOutputBufferFlush(buf);
	ret = g_strdup( (char*)(buf->conv ? buf->conv->content : buf->buffer->content) );
	xmlOutputBufferClose(buf);

	xmlFreeNode(envelope);

	return ret;
}

/**
 * lasso_node_export_to_paos_request:
 * @node: a #LassoNode
 *
 * Exports @node to a PAOS message.
 *
 * Return value: a PAOS export of @node.  The string must be freed by the
 *      caller.
 **/
char*
lasso_node_export_to_paos_request(LassoNode *node, const char *issuer,
		const char *responseConsumerURL, const char *relay_state)
{
	xmlNode *envelope, *body, *header, *paos_request, *ecp_request, *ecp_relay_state, *message;
	xmlNs *soap_env_ns, *saml_ns, *ecp_ns;
	xmlOutputBuffer *buf;
	xmlCharEncodingHandler *handler;
	char *ret;

	g_return_val_if_fail(LASSO_IS_NODE(node), NULL);

	message = lasso_node_get_xmlNode(node, FALSE);

	envelope = xmlNewNode(NULL, (xmlChar*)"Envelope");
	soap_env_ns = xmlNewNs(envelope,
			(xmlChar*)LASSO_SOAP_ENV_HREF, (xmlChar*)LASSO_SOAP_ENV_PREFIX);
	xmlSetNs(envelope, soap_env_ns);

	header = xmlNewTextChild(envelope, NULL, (xmlChar*)"Header", NULL);

	/* PAOS request header block */
	paos_request = xmlNewNode(NULL, (xmlChar*)"Request");
	xmlSetNs(paos_request, xmlNewNs(paos_request,
				(xmlChar*)LASSO_PAOS_HREF, (xmlChar*)LASSO_PAOS_PREFIX));
	xmlSetProp(paos_request, (xmlChar*)"service", (xmlChar*)LASSO_ECP_HREF);
	xmlSetProp(paos_request, (xmlChar*)"responseConsumerURL",
			(const xmlChar*)responseConsumerURL);
	xmlSetNsProp(paos_request, soap_env_ns, (xmlChar*)"mustUnderstand", (xmlChar*)"1");
	xmlSetNsProp(paos_request, soap_env_ns, (xmlChar*)"actor", (xmlChar*)LASSO_SOAP_ENV_ACTOR);
	xmlAddChild(header, paos_request);

	/* ECP request header block */
	ecp_request = xmlNewNode(NULL, (xmlChar*)"Request");
	ecp_ns = xmlNewNs(ecp_request, (xmlChar*)LASSO_ECP_HREF, (xmlChar*)LASSO_ECP_PREFIX);
	xmlSetNs(ecp_request, ecp_ns);
	xmlSetProp(ecp_request, (xmlChar*)"responseConsumerURL",
			(const xmlChar*)responseConsumerURL);
	xmlSetNsProp(ecp_request, soap_env_ns, (xmlChar*)"mustUnderstand", (xmlChar*)"1");
	xmlSetNsProp(ecp_request, soap_env_ns, (xmlChar*)"actor", (xmlChar*)LASSO_SOAP_ENV_ACTOR);
	saml_ns = xmlNewNs(ecp_request,
			(xmlChar*)LASSO_SAML2_ASSERTION_HREF,
			(xmlChar*)LASSO_SAML2_ASSERTION_PREFIX);
	xmlNewTextChild(ecp_request, saml_ns, (xmlChar*)"Issuer", (const xmlChar*)issuer);
	xmlAddChild(header, ecp_request);

	/* ECP relay state block */
	if (relay_state) {
		ecp_relay_state = xmlNewNode(NULL, (xmlChar*)"RelayState");
		xmlNodeSetContent(ecp_relay_state, (const xmlChar*)relay_state);
		ecp_ns = xmlNewNs(ecp_relay_state, (xmlChar*)LASSO_ECP_HREF,
				(xmlChar*)LASSO_ECP_PREFIX);
		xmlSetNs(ecp_relay_state, ecp_ns);
		xmlSetNsProp(ecp_relay_state, soap_env_ns,
				(xmlChar*)"mustUnderstand", (xmlChar*)"1");
		xmlSetNsProp(ecp_relay_state, soap_env_ns,
				(xmlChar*)"actor", (xmlChar*)LASSO_SOAP_ENV_ACTOR);
		xmlAddChild(header, ecp_relay_state);
	}

	/* Body block */
	body = xmlNewTextChild(envelope, NULL, (xmlChar*)"Body", NULL);
	xmlAddChild(body, message);

	handler = xmlFindCharEncodingHandler("utf-8");
	buf = xmlAllocOutputBuffer(handler);
	xmlNodeDumpOutput(buf, NULL, envelope, 0, 0, "utf-8");
	xmlOutputBufferFlush(buf);
	ret = g_strdup( (char*)(buf->conv ? buf->conv->content : buf->buffer->content) );
	xmlOutputBufferClose(buf);

	xmlFreeNode(envelope);

	return ret;
}

/**
 * lasso_node_export_to_query:
 * @node: a #LassoNode
 * @sign_method: the Signature transform method
 * @private_key_file: the path to the private key (may be NULL)
 *
 * Exports @node to a HTTP query string.  If @private_key_file is NULL,
 * query won't be signed.
 *
 * Return value: a HTTP query export of @node.  The string must be freed by the
 *      caller.
 **/
char*
lasso_node_export_to_query(LassoNode *node,
		LassoSignatureMethod sign_method, const char *private_key_file)
{
	char *unsigned_query, *query = NULL;

	g_return_val_if_fail(LASSO_IS_NODE(node), NULL);

	unsigned_query = lasso_node_build_query(node);
	if (private_key_file) {
		query = lasso_query_sign(unsigned_query, sign_method, private_key_file);
	} else {
		lasso_transfer_string(query, unsigned_query);
	}
	lasso_release(unsigned_query);

	return query;
}

/**
 * lasso_node_export_to_xml:
 * @node: a #LassoNode
 *
 * Exports @node to an xml message.
 *
 * Return value: an xml export of @node.  The string must be freed by the
 *      caller.
 **/
gchar*
lasso_node_export_to_xml(LassoNode *node)
{
	xmlNode *message;
	xmlOutputBuffer *buf;
	xmlCharEncodingHandler *handler;
	gchar *ret;

	g_return_val_if_fail(LASSO_IS_NODE(node), NULL);

	message = lasso_node_get_xmlNode(node, FALSE);

	handler = xmlFindCharEncodingHandler("utf-8");
	buf = xmlAllocOutputBuffer(handler);
	xmlNodeDumpOutput(buf, NULL, message, 0, 0, "utf-8");
	xmlOutputBufferFlush(buf);
	ret = g_strdup((gchar*)(buf->conv ? buf->conv->content : buf->buffer->content));
	xmlOutputBufferClose(buf);

	return ret;
}

/**
 * lasso_node_export_to_soap:
 * @node: a #LassoNode
 *
 * Exports @node to a SOAP message.
 *
 * Return value: a SOAP export of @node.  The string must be freed by the
 *      caller.
 **/
char*
lasso_node_export_to_soap(LassoNode *node)
{
	xmlNode *envelope, *body, *message;
	xmlOutputBuffer *buf;
	xmlCharEncodingHandler *handler;
	char *ret;

	g_return_val_if_fail(LASSO_IS_NODE(node), NULL);

	message = lasso_node_get_xmlNode(node, FALSE);

	envelope = xmlNewNode(NULL, (xmlChar*)"Envelope");
	xmlSetNs(envelope, xmlNewNs(envelope,
				(xmlChar*)LASSO_SOAP_ENV_HREF, (xmlChar*)LASSO_SOAP_ENV_PREFIX));

	body = xmlNewTextChild(envelope, NULL, (xmlChar*)"Body", NULL);
	xmlAddChild(body, message);

	handler = xmlFindCharEncodingHandler("utf-8");
	buf = xmlAllocOutputBuffer(handler);
	xmlNodeDumpOutput(buf, NULL, envelope, 0, 0, "utf-8");
	xmlOutputBufferFlush(buf);
	ret = g_strdup( (char*)(buf->conv ? buf->conv->content : buf->buffer->content) );
	xmlOutputBufferClose(buf);

	xmlFreeNode(envelope);

	return ret;
}

/**
 * lasso_node_encrypt:
 * @lasso_node: a #LassoNode to encrypt
 * @encryption_public_key : RSA public key the node will be encrypted with
 *
 * Generate a DES key and encrypt it with the RSA key.
 * Then encrypt @lasso_node with the DES key.
 *
 * Return value: an xmlNode which is the @node in an encrypted fashion.
 * It must be freed by the caller.
 **/
LassoSaml2EncryptedElement*
lasso_node_encrypt(LassoNode *lasso_node, xmlSecKey *encryption_public_key,
		LassoEncryptionSymKeyType encryption_sym_key_type, const char *recipient)
{
	xmlDocPtr doc = NULL;
	xmlNodePtr orig_node = NULL;
	LassoSaml2EncryptedElement *encrypted_element = NULL, *ret = NULL;
	xmlSecKeysMngrPtr key_manager = NULL;
	xmlNodePtr key_info_node = NULL;
	xmlNodePtr encrypted_key_node = NULL;
	xmlNodePtr encrypted_data = NULL;
	xmlNodePtr key_info_node2 = NULL;
	xmlSecEncCtxPtr enc_ctx = NULL;
	xmlSecTransformId xmlsec_encryption_sym_key_type;

	if (encryption_public_key == NULL || !xmlSecKeyIsValid(encryption_public_key)) {
		message(G_LOG_LEVEL_WARNING, "Invalid encryption key");
		goto cleanup;
	}

	/* Create a document to contain the node to encrypt */
	doc = xmlNewDoc((xmlChar*)"1.0");
	orig_node = lasso_node_get_xmlNode(lasso_node, FALSE);
	xmlDocSetRootElement(doc, orig_node);

	/* Get the symetric key type */
	switch (encryption_sym_key_type) {
		case LASSO_ENCRYPTION_SYM_KEY_TYPE_AES_256:
			xmlsec_encryption_sym_key_type = xmlSecTransformAes256CbcId;
			break;
		case LASSO_ENCRYPTION_SYM_KEY_TYPE_3DES:
			xmlsec_encryption_sym_key_type = xmlSecTransformDes3CbcId;
			break;
		case LASSO_ENCRYPTION_SYM_KEY_TYPE_AES_128:
		default:
			xmlsec_encryption_sym_key_type = xmlSecTransformAes128CbcId;
			break;
	}

	/* Create encryption template for a specific symetric key type */
	/* saml-core 2.2.4 line 498:
	 * The Type attribute SHOULD be present and, if present, MUST contain a value of
	 * http://www.w3.org/2001/04/xmlenc#Element. */
	encrypted_data = xmlSecTmplEncDataCreate(doc,
			xmlsec_encryption_sym_key_type,	NULL, xmlSecTypeEncElement, NULL, NULL);

	if (encrypted_data == NULL) {
		message(G_LOG_LEVEL_WARNING, "Failed to create encryption template");
		goto cleanup;
	}

	if (xmlSecTmplEncDataEnsureCipherValue(encrypted_data) == NULL) {
		message(G_LOG_LEVEL_WARNING, "Failed to add CipherValue node");
		goto cleanup;
	}

	/* create and initialize keys manager, we use a simple list based
	 * keys manager, implement your own xmlSecKeysStore klass if you need
	 * something more sophisticated
	 */
	key_manager = xmlSecKeysMngrCreate();
	if (key_manager == NULL) {
		message(G_LOG_LEVEL_WARNING, "Failed to create keys manager");
		goto cleanup;
	}

	if (xmlSecCryptoAppDefaultKeysMngrInit(key_manager) < 0) {
		message(G_LOG_LEVEL_WARNING, "Failed to initialize keys manager");
		xmlSecKeysMngrDestroy(key_manager);
		goto cleanup;
	}

	/* add key to keys manager, from now on keys manager is responsible
	 * for destroying key
	 */
	if (xmlSecCryptoAppDefaultKeysMngrAdoptKey(key_manager, encryption_public_key) < 0) {
		xmlSecKeysMngrDestroy(key_manager);
		goto cleanup;
	}

	/* add <dsig:KeyInfo/> */
	key_info_node = xmlSecTmplEncDataEnsureKeyInfo(encrypted_data, NULL);
	if (key_info_node == NULL) {
		message(G_LOG_LEVEL_WARNING, "Failed to add key info");
		goto cleanup;
	}

	/* add <enc:EncryptedKey/> to store the encrypted session key */
	encrypted_key_node = xmlSecTmplKeyInfoAddEncryptedKey(key_info_node,
			xmlSecTransformRsaPkcs1Id, NULL, NULL, (xmlChar*)recipient);
	if (encrypted_key_node == NULL) {
		message(G_LOG_LEVEL_WARNING, "Failed to add encrypted key");
		goto cleanup;
	}

	/* we want to put encrypted key in the <enc:CipherValue/> node */
	if (xmlSecTmplEncDataEnsureCipherValue(encrypted_key_node) == NULL) {
		message(G_LOG_LEVEL_WARNING, "Failed to add CipherValue node");
		goto cleanup;
	}

	/* add <dsig:KeyInfo/> and <dsig:KeyName/> nodes to <enc:EncryptedKey/> */
	key_info_node2 = xmlSecTmplEncDataEnsureKeyInfo(encrypted_key_node, NULL);
	if (key_info_node2 == NULL) {
		message(G_LOG_LEVEL_WARNING, "Failed to add key info");
		goto cleanup;
	}

	/* create encryption context */
	enc_ctx = (xmlSecEncCtxPtr)xmlSecEncCtxCreate(key_manager);
	if (enc_ctx == NULL) {
		message(G_LOG_LEVEL_WARNING, "Failed to create encryption context");
		goto cleanup;
	}

	/* generate a symetric key */
	switch (encryption_sym_key_type) {
		case LASSO_ENCRYPTION_SYM_KEY_TYPE_AES_256:
			enc_ctx->encKey = xmlSecKeyGenerate(xmlSecKeyDataAesId, 256,
					xmlSecKeyDataTypeSession);
			break;
		case LASSO_ENCRYPTION_SYM_KEY_TYPE_3DES:
			enc_ctx->encKey = xmlSecKeyGenerate(xmlSecKeyDataDesId, 192,
					xmlSecKeyDataTypeSession);
			break;
		case LASSO_ENCRYPTION_SYM_KEY_TYPE_AES_128:
		default:
			enc_ctx->encKey = xmlSecKeyGenerate(xmlSecKeyDataAesId, 128,
					xmlSecKeyDataTypeSession);
			break;
	}

	if (enc_ctx->encKey == NULL) {
		message(G_LOG_LEVEL_WARNING, "Failed to generate session des key");
		goto cleanup;
	}


	/* encrypt the data */
	if (xmlSecEncCtxXmlEncrypt(enc_ctx, encrypted_data, orig_node) < 0) {
		message(G_LOG_LEVEL_WARNING, "Encryption failed");
		goto cleanup;
	}
	

	/* Create a new EncryptedElement */
	encrypted_element = LASSO_SAML2_ENCRYPTED_ELEMENT(lasso_saml2_encrypted_element_new());
	lasso_assign_gobject(encrypted_element->original_data, lasso_node);
	lasso_list_add_xml_node(encrypted_element->EncryptedKey, xmlCopyNode(encrypted_key_node, 1));
	lasso_assign_xml_node(encrypted_element->EncryptedData, xmlCopyNode(xmlDocGetRootElement(doc), 1));
	lasso_transfer_gobject(ret, encrypted_element);

cleanup:
	lasso_release_gobject(encrypted_element);
	lasso_release_encrypt_context(enc_ctx);
	lasso_release_doc(doc);

	return ret;
}


/**
 * lasso_node_init_from_query:
 * @node: a #LassoNode (or derived class)
 * @query: the query string
 *
 * Initialiazes @node fields with data from @query string.
 *
 * Return value: %TRUE if success
 **/
gboolean
lasso_node_init_from_query(LassoNode *node, const char *query)
{
	LassoNodeClass *class;
	char **query_fields;
	int i;
	gboolean rc;

	g_return_val_if_fail(LASSO_IS_NODE(node), FALSE);
	class = LASSO_NODE_GET_CLASS(node);

	query_fields = urlencoded_to_strings(query);
	rc = class->init_from_query(node, query_fields);
	for (i = 0; query_fields[i]; i++) {
		xmlFree(query_fields[i]);
		query_fields[i] = NULL;
	}
	g_free(query_fields);
	return rc;
}


/**
 * lasso_node_init_from_xml:
 * @node: a #LassoNode (or derived class)
 * @xmlnode: the libxml2 node
 *
 * Initialiazes @node fields with data from @xmlnode XML node.
 *
 * Return value: 0 on success; or a negative value otherwise.
 **/
int
lasso_node_init_from_xml(LassoNode *node, xmlNode *xmlnode)
{
	LassoNodeClass *class;

	g_return_val_if_fail(LASSO_IS_NODE(node), LASSO_XML_ERROR_OBJECT_CONSTRUCTION_FAILED);
	class = LASSO_NODE_GET_CLASS(node);

	return class->init_from_xml(node, xmlnode);
}

/**
 * lasso_node_build_query(LassoNode *node)
 * @node: a #LassoNode
 *
 * Build an HTTP query from the given LassoNode, this is a pure virtual
 * function, you must overload it in subclass.
 *
 * Return value: a newly allocated string containing the query if it succeed,
 * or NULL otherwise.
 */
char*
lasso_node_build_query(LassoNode *node)
{
	LassoNodeClass *class;
	g_return_val_if_fail (LASSO_IS_NODE(node), NULL);

	class = LASSO_NODE_GET_CLASS(node);
	return class->build_query(node);
}


/**
 * lasso_node_get_xmlNode:
 * @node: a #LassoNode
 * @lasso_dump: whether to include lasso-specific nodes
 *
 * Builds an XML representation of @node.
 *
 * Return value: a new xmlNode.  It must be freed by the caller.
 **/
xmlNode*
lasso_node_get_xmlNode(LassoNode *node, gboolean lasso_dump)
{
	LassoNodeClass *class;
	g_return_val_if_fail (LASSO_IS_NODE(node), NULL);
	class = LASSO_NODE_GET_CLASS(node);
	return class->get_xmlNode(node, lasso_dump);
}

/**
 * lasso_node_cleanup_original_xmlnodes:
 *
 * @node: a #LassoNode
 *
 * Traverse the #LassoNode tree starting at Node and remove keeped xmlNode if one is found.
 *
 * Return value: None
 */
void
lasso_node_cleanup_original_xmlnodes(LassoNode *node)
{
	lasso_node_traversal(node, lasso_node_remove_original_xmlnode, 0);
}

static GQuark original_xmlnode_quark;
static GQuark custom_element_quark;

/**
 * lasso_node_get_original_xmlnode:
 * @node: a #LassoNode
 *
 * Retrieve the original xmlNode eventually associated to this #LassoNode.
 *
 * Return value: an #xmlNodePtr or NULL.
 */
xmlNodePtr
lasso_node_get_original_xmlnode(LassoNode *node)
{
	return g_object_get_qdata(G_OBJECT(node), original_xmlnode_quark);
}

static void original_xmlnode_free(void *node) {
	xmlNode *xnode = (xmlNode*)node;


	if (node) {
		if (lasso_flag_memory_debug) {
			fprintf(stderr, "freeing original xmlnode %s (at %p)\n", xnode->name, xnode);
		}
		xmlFreeNode(xnode);
	}
}

/**
 * lasso_node_set_original_xmlnode:
 * @node: the #LassoNode object
 * @xmlnode: an #xmlNode
 *
 * Set the underlying XML representation of the object.
 *
 */
void
lasso_node_set_original_xmlnode(LassoNode *node, xmlNode* xmlnode)
{
	if (xmlnode) {
		xmlNode *copy = NULL;

		copy = xmlCopyNode(xmlnode, 1);
		if (lasso_flag_memory_debug) {
			fprintf(stderr, "setting original xmlnode (at %p) on node %s:%p\n", copy, G_OBJECT_TYPE_NAME (node), node);
		}
		g_object_set_qdata_full(G_OBJECT(node), original_xmlnode_quark, copy, (GDestroyNotify)original_xmlnode_free);
	} else {
		if (lasso_flag_memory_debug) {
			fprintf(stderr, "clearing original xmlnode on node %p\n", node);
		}
		g_object_set_qdata_full(G_OBJECT(node), original_xmlnode_quark, NULL, (GDestroyNotify)original_xmlnode_free);
	}
}

struct _CustomElement {
	char *prefix;
	char *href;
	char *nodename;
};

static struct _CustomElement *
_lasso_node_new_custom_element()
{
	struct _CustomElement *ret = g_new0(struct _CustomElement, 1);
	return ret;
}

static void
_lasso_node_free_custom_element(struct _CustomElement *custom_element)
{
	lasso_release_string(custom_element->prefix);
	lasso_release_string(custom_element->href);
	lasso_release(custom_element);
}

/**
 * _lasso_node_get_custom_element:
 * @node: a #LassoNode object
 *
 * Return the eventually attached custom namespace object
 *
 * Return value: NULL or an #_CustomElement structure.
 */
static struct _CustomElement*
_lasso_node_get_custom_element(LassoNode *node)
{
	if (! LASSO_NODE(node))
		return NULL;
	return g_object_get_qdata((GObject*)node, custom_element_quark);
}

static struct _CustomElement*
_lasso_node_get_custom_element_or_create(LassoNode *node)
{
	struct _CustomElement *custom_element;

	if (! LASSO_IS_NODE(node))
		return NULL;

	custom_element = _lasso_node_get_custom_element(node);
	if (! custom_element) {
		custom_element = _lasso_node_new_custom_element();
		g_object_set_qdata_full((GObject*)node, custom_element_quark,
				custom_element,
				(GDestroyNotify)_lasso_node_free_custom_element);
	}
	return custom_element;
}


/**
 * lasso_node_set_custom_namespace:
 * @node: a #LassoNode object
 * @prefix: the prefix to use for the definition
 * @href: the URI of the namespace
 *
 * Set a custom namespace for an object instance, use it with object existing a lot of revision of
 * the nearly same namespace.
 */
void
lasso_node_set_custom_namespace(LassoNode *node, char *prefix, char *href)
{
	struct _CustomElement *custom_element;

	custom_element = _lasso_node_get_custom_element_or_create(node);
	g_return_if_fail (custom_element != NULL);

	lasso_assign_string(custom_element->prefix, prefix);
	lasso_assign_string(custom_element->href, href);
}

/**
 * lasso_node_set_custom_nodename:
 * @node: a #LassoNode object
 * @nodename: the name to use for the node
 *
 * Set a custom nodename for an object instance, use it with object implement a schema type and not
 * a real element.
 */
void
lasso_node_set_custom_nodename(LassoNode *node, char *nodename)
{
	struct _CustomElement *custom_element;

	custom_element = _lasso_node_get_custom_element_or_create(node);
	g_return_if_fail (custom_element != NULL);

	lasso_assign_string(custom_element->nodename, nodename);
}

/*****************************************************************************/
/* implementation methods                                                    */
/*****************************************************************************/

static void
lasso_node_remove_original_xmlnode(LassoNode *node, SnippetType type) {
	LassoNodeClass *class;
	class = LASSO_NODE_GET_CLASS(node);

	if (class->node_data->keep_xmlnode || type & SNIPPET_KEEP_XMLNODE) {
		lasso_node_set_original_xmlnode(node, NULL);
	}
}

static void
lasso_node_traversal(LassoNode *node, void (*do_to_node)(LassoNode *node, SnippetType type), SnippetType type) {
	LassoNodeClass *class;
	struct XmlSnippet *snippet;

	if (node == NULL) {
		return;
	}
	class = LASSO_NODE_GET_CLASS(node);
	if (class == NULL || class->node_data == NULL || do_to_node == NULL) {
		return;
	}
	do_to_node(node, type);

	snippet = class->node_data->snippets;
	while (snippet->name != NULL) {
		SnippetType type;
		void **value = G_STRUCT_MEMBER_P(node, snippet->offset);

		type = snippet->type & 0xff;
		switch (type) {
			case SNIPPET_NODE:
			case SNIPPET_NAME_IDENTIFIER:
			case SNIPPET_NODE_IN_CHILD:
				lasso_node_traversal(*value, do_to_node, snippet->type);
				break;
			case SNIPPET_LIST_NODES:
				{
					GList *list = *value;
					while (list != NULL) {
						if (list->data) {
							lasso_node_traversal(LASSO_NODE(list->data), do_to_node, snippet->type);
						}
						list = g_list_next(list);
					}
				}
				break;
			default:
				break;
		}
		snippet++;
	}
}

static void
lasso_node_impl_destroy(LassoNode *node)
{
	g_object_unref(G_OBJECT(node));
}

/** FIXME: return a real error code */
static int
lasso_node_impl_init_from_xml(LassoNode *node, xmlNode *xmlnode)
{
	struct XmlSnippet *snippet;
	xmlNode *t;
	LassoNodeClass *class;
	void *value;
	SnippetType type;
	struct XmlSnippet *snippet_any = NULL;
	struct XmlSnippet *snippet_any_attribute = NULL;
	GSList *unknown_nodes = NULL;
	GSList *known_attributes = NULL;

	class = LASSO_NODE_GET_CLASS(node);

	/* What do you want me to initialize ? */
	if (! xmlnode)
		return 1;

	/* No node_data no initialization possible */
	if (! class->node_data) {
		message(G_LOG_LEVEL_WARNING, "Class %s has no node_data so no initialization is possible", G_OBJECT_CLASS_NAME(class));
		return 0;
	}

	if (class->node_data->keep_xmlnode) {
		lasso_node_set_original_xmlnode(node, xmlnode);
	}
	if (lasso_flag_memory_debug == TRUE) {
		fprintf(stderr, "Initializing %s (at %p)\n", G_OBJECT_TYPE_NAME(node), node);
	}

	while (class && LASSO_IS_NODE_CLASS(class) && class->node_data) {
		if (lasso_flag_memory_debug == TRUE) {
			fprintf(stderr, " initializing %s\n", G_OBJECT_CLASS_NAME(class));
		}

		for (t = xmlnode->children; t; t = t->next) {
			if (t->type == XML_TEXT_NODE) {
				for (snippet = class->node_data->snippets;
						snippet && snippet->name; snippet++) {
					GList **location = NULL;

					type = snippet->type & 0xff;
					value = G_STRUCT_MEMBER_P(node, snippet->offset);

					if (type == SNIPPET_LIST_XMLNODES) {
						location = value;
						*location = g_list_append(
								*location, xmlCopyNode(t, 1));
					} else if (type == SNIPPET_LIST_NODES &&
							snippet->type & SNIPPET_ALLOW_TEXT) {
						LassoNode *text_node;
						text_node = lasso_node_new_from_xmlNode_with_type(t,
								"LassoMiscTextNode");
						location = value;
						*location = g_list_append(*location, text_node);
					}
					continue;
				}
				continue;
			}

			if (t->type != XML_ELEMENT_NODE)
				continue;

			for (snippet = class->node_data->snippets;
					snippet && snippet->name; snippet++) {
				void *tmp = NULL;
				type = snippet->type & 0xff;
				value = G_STRUCT_MEMBER_P(node, snippet->offset);

				if ((snippet->type & SNIPPET_ANY) && type != SNIPPET_ATTRIBUTE) {
					snippet_any = snippet;
				}

				if (strcmp((char*)t->name, snippet->name) != 0 && snippet->name[0])
					continue;

				if (type == SNIPPET_NODE) {
					tmp = lasso_node_new_from_xmlNode_with_type(t,
							snippet->class_name);
				} else if (type == SNIPPET_NODE_IN_CHILD) {
					xmlNode *t2 = t->children;
					while (t2 && t2->type != XML_ELEMENT_NODE)
						t2 = t2->next;
					if (t2)
						tmp = lasso_node_new_from_xmlNode_with_type(t2,
								snippet->class_name);
				} else if (type == SNIPPET_CONTENT) {
					tmp = xmlNodeGetContent(t);
				} else if (type == SNIPPET_NAME_IDENTIFIER) {
					tmp = lasso_saml_name_identifier_new_from_xmlNode(t);
				} else if (type == SNIPPET_LIST_NODES) {
					GList **location = value;
					LassoNode *n;
					n = lasso_node_new_from_xmlNode_with_type(t,
							snippet->class_name);
					if (n == NULL && snippet_any == snippet &&
							t->properties == NULL && t->children &&
							t->children->type == XML_TEXT_NODE &&
							t->children->next == NULL) {
						/* unknown, but no attributes, and content
						 * is text ? -> use generic object */
						n = lasso_node_new_from_xmlNode_with_type(t,
								"LassoMiscTextNode");
					}
					if (n && snippet->type & SNIPPET_KEEP_XMLNODE &&
							! LASSO_NODE_GET_CLASS(n)->node_data->keep_xmlnode) {
						lasso_node_set_original_xmlnode(n, t);
					}

					if (n) {
						*location = g_list_append(*location, n);
					} else {
						/* failed to do sth with */
						message(G_LOG_LEVEL_WARNING,
								"Failed to do sth with %s",
								t->name);
					}
				} else if (type == SNIPPET_LIST_CONTENT) {
					GList **location = value;
					xmlChar *s = xmlNodeGetContent(t);
					lasso_list_add_string(*location, (char*)s);
					lasso_release_xml_string(s);
				} else if (type == SNIPPET_EXTENSION ||
						type == SNIPPET_LIST_XMLNODES) {
					GList **location = value;
					*location = g_list_append(*location, xmlCopyNode(t, 1));
				} else if (type == SNIPPET_XMLNODE)
					tmp = xmlCopyNode(t, 1);

				if (tmp == NULL)
					break;

				if (type == SNIPPET_XMLNODE || type == SNIPPET_NODE || type == SNIPPET_NODE_IN_CHILD || type ==
						SNIPPET_NAME_IDENTIFIER) {
					if (snippet->type & SNIPPET_KEEP_XMLNODE && !
							LASSO_NODE_GET_CLASS(tmp)->node_data->keep_xmlnode)
					{
						lasso_node_set_original_xmlnode(tmp, t);
					}
					*(void**)value = tmp;
					tmp = NULL;
				} else if (snippet->type & SNIPPET_INTEGER) {
					int val = atoi(tmp);
					(*(int*)value) = val;
					xmlFree(tmp);
					tmp = NULL;
				} else if (snippet->type & SNIPPET_BOOLEAN) {
					int val = 0;
					if (strcmp((char*)tmp, "true") == 0) {
						val = 1;
					} else if (strcmp((char*)tmp, "1") == 0) {
						val = 1;
					}
					(*(int*)value) = val;
					xmlFree(tmp);
					tmp = NULL;
				} else {
					lasso_release_string(*(char**)value);
					*(char**)value = g_strdup(tmp);
					if (lasso_flag_memory_debug == TRUE) {
						fprintf(stderr, "   setting field %s/%s to value %p: %s\n", G_OBJECT_TYPE_NAME(node), snippet->name, *(void**)value, (char*)tmp);
					}
					lasso_release_xml_string((*(xmlChar**)&tmp));
					tmp = NULL;
				}

				break;
			}
			if ((snippet == NULL || snippet->name == NULL) && snippet_any) {
				if (g_slist_find(unknown_nodes, t) == NULL)
					unknown_nodes = g_slist_append(unknown_nodes, t);
			} else {
				unknown_nodes = g_slist_remove(unknown_nodes, t);
			}
		}

		for (snippet = class->node_data->snippets; snippet && snippet->name; snippet++) {
			void *tmp = NULL;
			type = snippet->type & 0xff;

			value = G_STRUCT_MEMBER_P(node, snippet->offset);
			if (type == SNIPPET_ATTRIBUTE) {
				if (snippet->type & SNIPPET_ANY) {
					snippet_any_attribute = snippet;
					continue;
				}
				tmp = xmlGetProp(xmlnode, (xmlChar*)snippet->name);
				known_attributes = g_slist_append(known_attributes, snippet->name);
			}
			if (type == SNIPPET_TEXT_CHILD)
				tmp = xmlNodeGetContent(xmlnode);
			if (tmp == NULL)
				continue;

			if (snippet->type & SNIPPET_INTEGER) {
				int val = atoi(tmp);
				(*(int*)value) = val;
			} else if (snippet->type & SNIPPET_BOOLEAN) {
				int val = 0;
				if (strcmp((char*)tmp, "true") == 0) {
					val = 1;
				} else if (strcmp((char*)tmp, "1") == 0) {
					val = 1;
				}
				(*(int*)value) = val;
			} else {
				lasso_assign_string((*(char**)value), tmp);
				if (lasso_flag_memory_debug == TRUE) {
					fprintf(stderr, "   setting prop %s/%s to value %p: %s\n",
							G_OBJECT_TYPE_NAME(node), snippet->name, *(void**)value, (char*)tmp);
				}
			}
			xmlFree(tmp);
		}

		class = g_type_class_peek_parent(class);
	}

	if (unknown_nodes && snippet_any) {
		xmlNode *t = unknown_nodes->data;
		void *tmp;
		value = G_STRUCT_MEMBER_P(node, snippet_any->offset);
		tmp = lasso_node_new_from_xmlNode_with_type(t, snippet_any->class_name);
		(*(char**)value) = tmp;
	}

	if (snippet_any_attribute) {
		GHashTable **any_attribute;
		GSList *tmp_attr;
		xmlAttr *node_attr;

		any_attribute = G_STRUCT_MEMBER_P(node, snippet_any_attribute->offset);
		if (*any_attribute == NULL) {
			*any_attribute = g_hash_table_new_full(
					g_str_hash, g_str_equal, g_free, g_free);
		}

		for (node_attr = xmlnode->properties; node_attr; node_attr = node_attr->next) {
			xmlChar *attr_name = (xmlChar*)node_attr->name;
			gboolean known_attr = FALSE;
			for (tmp_attr = known_attributes; tmp_attr;
					tmp_attr = g_slist_next(tmp_attr)) {
				if (strcmp(tmp_attr->data, (char*)attr_name) == 0) {
					known_attr = TRUE;
					break;
				}
			}
			if (known_attr == FALSE) {
				xmlChar *tmp = xmlGetProp(xmlnode, attr_name);
				g_hash_table_insert(*any_attribute,
						g_strdup((char*)attr_name), g_strdup((char*)tmp));
				xmlFree(tmp);
			}
		}

	}

	if (unknown_nodes) {
		g_slist_free(unknown_nodes);
	}

	if (known_attributes) {
		g_slist_free(known_attributes);
	}

	return 0;
}

/**
 * lasso_node_remove_signature:
 * @node: a #LassoNode object
 *
 * Remove any signature setup on this node.
 */
void
lasso_node_remove_signature(LassoNode *node) {
       LassoNodeClass *klass;

       if (! LASSO_IS_NODE(node))
               return;
       klass = LASSO_NODE_GET_CLASS(node);
       /* follow the class parenting chain */
       while (klass && LASSO_IS_NODE_CLASS(klass)) {
               if (klass && klass->node_data && klass->node_data->sign_type_offset != 0) {
                       G_STRUCT_MEMBER(LassoSignatureType, node, klass->node_data->sign_type_offset) =
                               LASSO_SIGNATURE_TYPE_NONE;
               }
               klass = g_type_class_peek_parent(klass);
       }
}

/*****************************************************************************/
/* private methods                                                           */
/*****************************************************************************/

static char*
lasso_node_impl_build_query(LassoNode *node)
{
	return lasso_node_build_query_from_snippets(node);
}

static xmlNode*
lasso_node_impl_get_xmlNode(LassoNode *node, gboolean lasso_dump)
{
	LassoNodeClass *class = LASSO_NODE_GET_CLASS(node);
	xmlNode *xmlnode;
	xmlNs *ns;
	GList *list_ns = NULL, *list_classes = NULL, *t;
	LassoNode *value_node;
	struct XmlSnippet *version_snippet;
	struct _CustomElement *custom_element;

	if (class->node_data == NULL)
		return NULL;

	xmlnode = xmlNewNode(NULL, (xmlChar*)class->node_data->node_name);
	custom_element = _lasso_node_get_custom_element(node);
	/* collect namespaces in the order of ancestor classes, nearer first */
	while (class && LASSO_IS_NODE_CLASS(class) && class->node_data) {
		if (class->node_data->ns && (! custom_element || ! custom_element->href || class != LASSO_NODE_GET_CLASS(node)))
			list_ns = g_list_append(list_ns, class->node_data->ns);
		list_classes = g_list_append(list_classes, class);
		class = g_type_class_peek_parent(class);
	}

	/* create the namespaces */
	t = g_list_first(list_ns);
	while (t) {
		ns = t->data;
		xmlNewNs(xmlnode, ns->href, ns->prefix);
		t = g_list_next(t);
	}
	g_list_free(list_ns);
	/* first NS defined is the namespace of the element */
	xmlSetNs(xmlnode, xmlnode->nsDef);

	/* set a custom namespace if one is found */
	if (custom_element != NULL) {
		if (custom_element->href) {
			xmlChar *prefix = BAD_CAST (custom_element->prefix);
			xmlNs *ns = NULL, *oldns = NULL;

			oldns = xmlSearchNs(NULL, xmlnode, prefix);
			if (prefix && oldns) {
				prefix = NULL;
			}
			// remove existing default namespace
			if (prefix == NULL) {
				xmlNs *cur = xmlnode->nsDef, *last = NULL;
				while (cur) {
					if (cur->prefix == NULL) {
						if (last) {
							last->next = cur->next;
						} else {
							xmlnode->nsDef = cur->next;
						}
						xmlFreeNs(cur);
					}
					last = cur;
					cur = cur->next;
				}
			}
			ns = xmlNewNs(xmlnode, (xmlChar*)custom_element->href,
					(xmlChar*)custom_element->prefix);
			/* skip the base class namespace, it is replaced by the custom one */
			xmlSetNs(xmlnode, ns);
		}
		if (custom_element->nodename) {
			xmlNodeSetName(xmlnode, BAD_CAST (custom_element->nodename));
		}
	}


	t = g_list_last(list_classes);
	while (t) {
		class = t->data;
		lasso_node_build_xmlNode_from_snippets(node, xmlnode,
				class->node_data->snippets, lasso_dump);
		t = g_list_previous(t);
	}
	g_list_free(list_classes);

	xmlCleanNs(xmlnode);

	/* backward compatibility with Liberty ID-FF 1.1; */
	if (find_path(node, "MajorVersion", &value_node, &version_snippet) == TRUE) {
		int *value;
		int major_version, minor_version;

		value = G_STRUCT_MEMBER_P(value_node, version_snippet->offset);
		major_version = *value;

		find_path(node, "MinorVersion", &value_node, &version_snippet);
		value = G_STRUCT_MEMBER_P(value_node, version_snippet->offset);
		minor_version = *value;

		if (strcmp((char*)xmlnode->ns->href, LASSO_LIB_HREF) == 0) {
			if (major_version == 1 && minor_version == 0) {
				xmlFree((xmlChar*)xmlnode->ns->href); /* warning: discard const */
				xmlnode->ns->href = xmlStrdup((xmlChar*)
						"http://projectliberty.org/schemas/core/2002/12");
			}
		}
	}


	return xmlnode;
}

/*****************************************************************************/
/* overridden parent class methods                                           */
/*****************************************************************************/

static GObjectClass *parent_class = NULL;

static void
lasso_node_dispose(GObject *object)
{
	LassoNodeClass *class;
	struct XmlSnippet *snippet;
	SnippetType type;
	GList *elem;

	if (lasso_flag_memory_debug == TRUE) {
		fprintf(stderr, "dispose of %s (at %p)\n", G_OBJECT_TYPE_NAME(object), object);
	}

	class = LASSO_NODE_GET_CLASS(object);

	while (class && LASSO_IS_NODE_CLASS(class) && class->node_data) {
		for (snippet = class->node_data->snippets; snippet && snippet->name; snippet++) {
			void **value = G_STRUCT_MEMBER_P(object, snippet->offset);
			type = snippet->type & 0xff;

			if (snippet->type & SNIPPET_BOOLEAN)
				continue;
			if (snippet->type & SNIPPET_INTEGER)
				continue;

			if (*value == NULL)
				continue;

			if (lasso_flag_memory_debug == TRUE) {
				fprintf(stderr, "  freeing %s/%s (at %p)\n",
						G_OBJECT_TYPE_NAME(object), snippet->name, *value);
			}
			switch (type) {
				case SNIPPET_NODE:
				case SNIPPET_NAME_IDENTIFIER:
				case SNIPPET_NODE_IN_CHILD:
					lasso_release_gobject(*value);
					break;
				case SNIPPET_XMLNODE:
					xmlFreeNode(*value);
					break;
				case SNIPPET_LIST_NODES:
					lasso_release_list_of_gobjects((*(GList**)value));
					break;
				case SNIPPET_EXTENSION:
				case SNIPPET_LIST_CONTENT:
				case SNIPPET_LIST_XMLNODES:
					elem = (GList*)(*value);
					while (elem) {
						if (type == SNIPPET_LIST_XMLNODES && elem->data)
							xmlFreeNode(elem->data);
						if (type == SNIPPET_EXTENSION && elem->data)
							xmlFreeNode(elem->data);
						if (type == SNIPPET_LIST_NODES && elem->data)
							lasso_node_destroy(elem->data);
						if (type == SNIPPET_LIST_CONTENT && elem->data)
							g_free(elem->data);
						elem = g_list_next(elem);
					}
					g_list_free(*value);
					break;
				case SNIPPET_CONTENT:
				case SNIPPET_TEXT_CHILD:
				case SNIPPET_ATTRIBUTE: {
								if (snippet->type & SNIPPET_ANY) {
									if (*value) {
										g_hash_table_destroy(*value);
									}
								} else {
									lasso_release_string(*(char**)value);
								}
							} break;
				case SNIPPET_SIGNATURE:
							break; /* no real element here */
				default:
							fprintf(stderr, "%d\n", type);
							g_assert_not_reached();
			}

			if (type != SNIPPET_SIGNATURE) {
				/* Signature snippet is not something to free,
				 * so don't set the value to NULL */
				*value = NULL;
			}
		}
		class = g_type_class_peek_parent(class);
	}

	parent_class->dispose(object);
}

static void
lasso_node_finalize(GObject *object)
{
	parent_class->finalize(object);
}

/*****************************************************************************/
/* instance and class init functions                                         */
/*****************************************************************************/

static gboolean
init_from_query(LassoNode *node, char **query_fields)
{
	return lasso_node_init_from_query_fields(node, query_fields);
}

static void
class_init(LassoNodeClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);

	parent_class = g_type_class_peek_parent(class);
	/* virtual public methods */
	class->destroy = lasso_node_impl_destroy;
	class->init_from_query = init_from_query;
	class->init_from_xml = lasso_node_impl_init_from_xml;

	/* virtual private methods */
	class->build_query = lasso_node_impl_build_query;
	class->get_xmlNode = lasso_node_impl_get_xmlNode;

	/* override */
	gobject_class->dispose = lasso_node_dispose;
	gobject_class->finalize = lasso_node_finalize;

	original_xmlnode_quark = g_quark_from_static_string("lasso_original_xmlnode");
	custom_element_quark = g_quark_from_static_string("lasso_custom_element");
	class->node_data = NULL;
}

static void
base_class_finalize(LassoNodeClass *class)
{
	if (class->node_data) {
		LassoNodeClassData *data = class->node_data;

		if (data->ns) {
			xmlFreeNs(data->ns);
		}
		if (data->node_name) {
			g_free(data->node_name);
		}
		g_free(class->node_data);
		class->node_data = NULL;
	}
}

GType
lasso_node_get_type()
{
	static GType this_type = 0;

	if (!this_type) {
		static const GTypeInfo this_info = {
			sizeof (LassoNodeClass),
			NULL,
			(GBaseFinalizeFunc) base_class_finalize,
			(GClassInitFunc) class_init,
			NULL,
			NULL,
			sizeof(LassoNode),
			0,
			NULL,
			NULL,
		};

		this_type = g_type_register_static(G_TYPE_OBJECT , "LassoNode", &this_info, 0);
	}
	return this_type;
}

/**
 * lasso_node_new:
 *
 * Creates a new #LassoNode.
 *
 * Return value: a newly created #LassoNode object
 **/
LassoNode*
lasso_node_new()
{
	return g_object_new(LASSO_TYPE_NODE, NULL);
}

/**
 * lasso_node_new_from_dump:
 * @dump: XML object dump
 *
 * Restores the @dump to a new #LassoNode subclass.
 *
 * Return value: a newly created object; or NULL if an error occured.
 **/
LassoNode*
lasso_node_new_from_dump(const char *dump)
{
	LassoNode *node;
	xmlDoc *doc;

	if (dump == NULL)
		return NULL;

	doc = lasso_xml_parse_memory(dump, strlen(dump));
	if (doc == NULL)
		return NULL;

	node = lasso_node_new_from_xmlNode(xmlDocGetRootElement(doc));

	lasso_release_doc(doc);
	return node;
}


/**
 * lasso_node_new_from_soap:
 * @soap: the SOAP message
 *
 * Parses SOAP message and creates a new Lasso object with the right class.
 *
 * Return value: node if success; NULL otherwise
 **/
LassoNode*
lasso_node_new_from_soap(const char *soap)
{
	xmlDoc *doc;
	xmlNode *xmlnode;
	LassoNode *node = NULL;

	doc = lasso_xml_parse_memory(soap, strlen(soap));
	if (doc == NULL) {
		return NULL;
	}
	xmlnode = lasso_xml_get_soap_content(xmlDocGetRootElement(doc));
	if (xmlnode == NULL) {
		return NULL;
	}
	node = lasso_node_new_from_xmlNode(xmlnode);

	lasso_release_doc(doc);

	return node;
}

/* How finding a typename from an xmlNode works ?
 *
 * There is three way to get to a typename:
 * 1. by an xsi:type QName attribute, that we resolve
 * 2. by constructing a QName from the namespace of the xsi:type and the name of the node
 * 3. by resolving the QName of the node
 *
 * To resolve a typename you must map the QName using the default registry object, or use
 * prefix_from_href_and_nodename() to mat the QName to a prefix used to build the typename with this
 * template: typename = "Lasso" + prefix + name_part(QName).
 *
 * The resolving algorithm is in the function _type_name_from_href_and_nodename().
 *
 * The prefix extraction in prefix_from_href_and_nodename().
 *
 */
static const char *
prefix_from_href_and_nodename(const xmlChar *href, const xmlChar *nodename) {
	char *prefix = NULL;
	char *tmp = NULL;

	if (strcmp((char*)href, LASSO_LASSO_HREF) == 0)
		prefix = "";
	else if (strcmp((char*)href, LASSO_SAML_ASSERTION_HREF) == 0)
		prefix = "Saml";
	else if (strcmp((char*)href, LASSO_SAML_PROTOCOL_HREF) == 0)
		prefix = "Samlp";
	else if (strcmp((char*)href, LASSO_LIB_HREF) == 0)
		prefix = "Lib";
	else if (strcmp((char*)href, LASSO_SAML2_ASSERTION_HREF) == 0)
		prefix = "Saml2";
	else if (strcmp((char*)href, LASSO_SAML2_PROTOCOL_HREF) == 0)
		prefix = "Samlp2";
	else if (strcmp((char*)href, LASSO_SOAP_ENV_HREF) == 0)
		prefix = "Soap";
	else if (strcmp((char*)href, LASSO_SOAP_BINDING_HREF) == 0)
		prefix = "SoapBinding";
	else if (strcmp((char*)href, LASSO_SOAP_BINDING_EXT_HREF) == 0)
		prefix = "SoapBindingExt";
	else if (strcmp((char*)href, LASSO_DISCO_HREF) == 0)
		prefix = "Disco";
	else if (strcmp((char*)href, LASSO_DS_HREF) == 0)
		prefix = "Ds";
	else if (strcmp((char*)href, LASSO_IS_HREF) == 0)
		prefix = "Is";
	else if (strcmp((char*)href, LASSO_SA_HREF) == 0)
		prefix = "Sa";
	else if (strcmp((char*)href, LASSO_WSSE_HREF) == 0)
		prefix = "WsSec1";
	else if (strcmp((char*)href, LASSO_WSSE1_HREF) == 0)
		prefix = "WsSec1";
	else if (strcmp((char*)href, LASSO_IDWSF2_DISCO_HREF) == 0)
		prefix = "IdWsf2Disco";
	else if (strcmp((char*)href, LASSO_IDWSF2_SBF_HREF) == 0)
		prefix = "IdWsf2Sbf";
	else if (strcmp((char*)href, LASSO_IDWSF2_SB2_HREF) == 0)
		prefix = "IdWsf2Sb2";
	else if (strcmp((char*)href, LASSO_IDWSF2_UTIL_HREF) == 0)
		prefix = "IdWsf2Util";
	else if (strcmp((char*)href, LASSO_IDWSF2_SEC_HREF) == 0)
		prefix = "IdWsf2Sec";
	else if (strcmp((char*)href, LASSO_IDWSF2_IMS_HREF) == 0)
		prefix = "IdWsf2Ims";
	else if (strcmp((char*)href, LASSO_IDWSF2_IS_HREF) == 0)
		prefix = "IdWsf2Is";
	else if (strcmp((char*)href, LASSO_IDWSF2_PS_HREF) == 0)
		prefix = "IdWsf2Ps";
	else if (strcmp((char*)href, LASSO_IDWSF2_SUBS_HREF) == 0)
		prefix = "IdWsf2Subs";
	else if (strcmp((char*)href, LASSO_IDWSF2_SUBSREF_HREF) == 0)
		prefix = "IdWsf2SubsRef";
	else if (strcmp((char*)href, LASSO_WSA_HREF) == 0)
		prefix = "WsAddr";
#if 0 /* Desactivate DGME lib special casing */
	else if (strcmp((char*)href, "urn:dgme:msp:ed:2007-01") == 0)
		/* FIXME: new namespaces should be possible to add from another library than lasso */
		prefix = "DgmeMspEd";
#endif
	else if ((tmp = lasso_get_prefix_for_idwsf2_dst_service_href((char*)href))
			!= NULL) {
		/* ID-WSF 2 Profile */
		prefix = "IdWsf2DstRef";
		lasso_release_string(tmp);
	} else if ((tmp = lasso_get_prefix_for_dst_service_href((char*)href))
			!= NULL) {
		/* ID-WSF 1 Profile */
		prefix = "Dst";
		lasso_release_string(tmp);
	}

	if (prefix != NULL && strcmp(prefix, "Dst") == 0 && strcmp((char*)nodename, "Status") == 0)
		prefix = "Utility";
	else if (prefix != NULL && strcmp(prefix, "Disco") == 0 && strcmp((char*)nodename, "Status") == 0)
		prefix = "Utility";
	else if (prefix != NULL && strcmp(prefix, "Sa") == 0 && strcmp((char*)nodename, "Status") == 0)
		prefix = "Utility";

	return prefix;
}

/**
 * _type_name_from_href_and_nodename:
 * @href: the href part of a QName
 * @nodename: the name part of a QName
 *
 * Return value: a typename string if one if found that exists, NULL otherwise.
 */
static char*
_type_name_from_href_and_nodename(char *href, char *nodename) {
	const char *prefix = prefix_from_href_and_nodename(BAD_CAST (href), BAD_CAST (nodename));
	char *typename = NULL;

	if (!href || !nodename)
		return NULL;

	/* FIXME: hardcoded mappings */
	if (strcmp(nodename, "SvcMD") == 0) {
		typename = g_strdup("LassoIdWsf2DiscoSvcMetadata");
	} else if (prefix != NULL && strcmp(prefix, "IdWsf2DstRef") == 0 && strcmp(nodename, "Status") == 0) {
		typename = g_strdup("LassoIdWsf2UtilStatus");
	} else if (prefix != NULL && strcmp(prefix, "WsSec1") == 0 && strcmp(nodename, "Security") == 0) {
		typename = g_strdup("LassoWsSec1SecurityHeader");
	} else if (prefix != NULL && strcmp(prefix, "Soap") == 0 && strcmp(nodename, "detail") == 0) {
		typename = g_strdup("LassoSoapDetail");
	} else {
		/* first try with registered mappings */
		const char *ctypename = lasso_registry_default_get_mapping(href, nodename, LASSO_LASSO_HREF);
		if (ctypename) {
			typename = g_strdup(ctypename);
		}
		/* finally try the default behaviour */
		if (prefix != NULL && typename == NULL) {
			typename = g_strdup_printf("Lasso%s%s", prefix, nodename);
		}
	}

	/* Does it really exist ? */
	if (typename && g_type_from_name (typename) == 0) {
		lasso_release_string(typename);
	}

	return typename;
}

/**
 * lasso_node_new_from_xmlNode:
 * @node: an xmlNode
 *
 * Builds a new #LassoNode from an xmlNode.
 *
 * Return value: a new node
 **/
LassoNode*
lasso_node_new_from_xmlNode(xmlNode *xmlnode)
{
	char *typename = NULL;
	xmlChar *xsitype = NULL;
	LassoNode *node = NULL;
	gboolean fromXsi = FALSE;

	if (xmlnode == NULL || xmlnode->ns == NULL) {
		message(G_LOG_LEVEL_CRITICAL, "Unable to build a LassoNode from a xmlNode");
		return NULL;
	}

	xsitype = xmlGetNsProp(xmlnode, (xmlChar*)"type", (xmlChar*)LASSO_XSI_HREF);
	if (xsitype) {
		xmlChar *xmlPrefix, *separator;
		xmlNsPtr xsiNs = NULL;
		char *xsiNodeName = NULL;

		/** Honor xsi:type  */
		xmlPrefix = (xmlChar*)xsitype;
		separator = (xmlChar*)strchr((char*)xsitype, ':');
		if (separator != NULL) {
			xmlPrefix = (xmlChar*)g_strndup((char*)xmlPrefix, (size_t)(separator - xmlPrefix));
			xsiNs = xmlSearchNs(NULL, xmlnode, xmlPrefix);
			if (xsiNs != NULL) {
				xsiNodeName = g_strdup((char*)(separator+1));
				if (strcmp((char*)xsiNs->href, LASSO_LASSO_HREF) == 0) {
					typename = g_strdup(xsiNodeName);
				}
			}
			lasso_release(xmlPrefix);
		}
		if (! typename && xsiNs && xsiNodeName) {
			typename = _type_name_from_href_and_nodename ((char*)xsiNs->href, xsiNodeName);
		}
		if (! typename && xsiNs) {
			typename = _type_name_from_href_and_nodename ((char*)xsiNs->href, (char*)xmlnode->name);
		}
		lasso_release_xml_string(xsitype);
		if (xsiNodeName)
			lasso_release_string(xsiNodeName);
		if (typename)
			fromXsi = TRUE;
	}

	if (typename == NULL) {
		typename = _type_name_from_href_and_nodename ((char*)xmlnode->ns->href, (char*)xmlnode->name);
	}

	if (typename) {
		node = lasso_node_new_from_xmlNode_with_type(xmlnode, typename);
	}
	if (! fromXsi) {
		/* if the typename was not obtained via xsi:type but through mapping of the element
		 * name then keep the element name */
		if (LASSO_NODE_GET_CLASS(node)->node_data && LASSO_NODE_GET_CLASS(node)->node_data->node_name && g_strcmp0((char*)xmlnode->name,
					LASSO_NODE_GET_CLASS(node)->node_data->node_name) != 0) {
			lasso_node_set_custom_nodename(node, (char*)xmlnode->name);
		}

		if (xmlnode->ns && (LASSO_NODE_GET_CLASS(node)->node_data == NULL ||
					LASSO_NODE_GET_CLASS(node)->node_data->ns == NULL ||
					g_strcmp0((char*)xmlnode->ns->href,
						(char*)LASSO_NODE_GET_CLASS(node)->node_data->ns->href)
					!= 0)) {
			lasso_node_set_custom_namespace(node, (char*)xmlnode->ns->prefix,
					(char*)xmlnode->ns->href);
		}


	}
	lasso_release(typename);

	return node;
}


static LassoNode*
lasso_node_new_from_xmlNode_with_type(xmlNode *xmlnode, char *typename)
{
	GType gtype;
	LassoNode *node;
	int rc;

	if (typename == NULL)
		return lasso_node_new_from_xmlNode(xmlnode); /* will auto-detect */

	gtype = g_type_from_name(typename);
	if (gtype == 0)
		return NULL;

	node = g_object_new(gtype, NULL);
	rc = lasso_node_init_from_xml(node, xmlnode);
	if (rc) {
		lasso_node_destroy(node);
		return NULL;
	}

	return node;
}

static gboolean
is_base64(const char *message)
{
	const char *c;

	c = message;
	while (*c != 0 && (isalnum((int)*c) || *c == '+' || *c == '/' || *c == '\n' || *c == '\r')) c++;
	while (*c == '=' || *c == '\n' || *c == '\r') c++; /* trailing = */

	if (*c == 0)
		return TRUE;

	return FALSE;
}


/**
 * lasso_node_init_from_message_with_format:
 * @node: a #LassoNode (or derived class)
 * @message: a Liberty message
 * @constraint: LASSO_MESSAGE_FORMAT_UNKNOWN or the format the message must be in
 * @doc_out: a pointer to store the resulting #xmlDoc structure
 * @node_out: a pointer to store the resulting content #xmlNode
 *
 * Parses @message and initialiazes @node fields with data from it.  Message type may be base64,
 * SOAP, XML or query string, correct type is found automatically if contraint is
 * LASSO_MESSAGE_FORMAT_UNKNOWN or is limited to the value given.
 * If the format is one of LASSO_MESSAGE_FORMAT_XML or LASSO_MESSAGE_FORMAT_XML or
 * LASSO_MESSAGE_FORMAT_BASE64 the resulting #xmlDoc and #xmlNode of the message can be retrieved.
 *
 * Return value: a #LassoMessageFormat value.
 **/
LassoMessageFormat
lasso_node_init_from_message_with_format(LassoNode *node, const char *message, LassoMessageFormat constraint, xmlDoc **doc_out, xmlNode **root_out)
{
	char *msg = NULL;
	gboolean b64 = FALSE;
	LassoMessageFormat rc = LASSO_MESSAGE_FORMAT_ERROR;
	xmlDoc *doc = NULL;
	xmlNode *root = NULL;
	gboolean any = constraint == LASSO_MESSAGE_FORMAT_UNKNOWN;

	msg = (char*)message;

	/* BASE64 case */
	if (any || constraint == LASSO_MESSAGE_FORMAT_BASE64) {
		if (message[0] != 0 && is_base64(message)) {
			int rc;

			msg = g_malloc(strlen(message));
			rc = xmlSecBase64Decode((xmlChar*)message, (xmlChar*)msg, strlen(message));
			if (rc >= 0) {
				b64 = TRUE;
			} else {
				g_free(msg);
				msg = (char*)message;
			}
		}
	}

	/* XML case */
	if (any || constraint == LASSO_MESSAGE_FORMAT_XML ||
		constraint == LASSO_MESSAGE_FORMAT_BASE64 ||
		constraint == LASSO_MESSAGE_FORMAT_SOAP) {
		if (strchr(msg, '<')) {
			doc = lasso_xml_parse_memory(msg, strlen(msg));
			if (doc == NULL) {
				rc = LASSO_MESSAGE_FORMAT_UNKNOWN;
				goto cleanup;
			}
			root = xmlDocGetRootElement(doc);

			if (any || constraint == LASSO_MESSAGE_FORMAT_SOAP) {
				gboolean is_soap = FALSE;

				is_soap = lasso_xml_is_soap(root);
				if (is_soap) {
					root = lasso_xml_get_soap_content(root);
				}
				rc = lasso_node_init_from_xml(node, root);
				if (rc != 0) {
					rc = LASSO_MESSAGE_FORMAT_XSCHEMA_ERROR;
					goto cleanup;

				}
				if (is_soap) {
					rc = LASSO_MESSAGE_FORMAT_SOAP;
					goto cleanup;
				}
				if (b64) {
					g_free(msg);
					rc = LASSO_MESSAGE_FORMAT_BASE64;
					goto cleanup;
				}
				rc = LASSO_MESSAGE_FORMAT_XML;
				goto cleanup;
			}
		}
	}

	/* HTTP query CASE */
	if (any || constraint == LASSO_MESSAGE_FORMAT_QUERY) {
		if (strchr(msg, '&') || strchr(msg, '=')) {
			/* XXX: detect SAML artifact messages to return a different status code ? */
			if (lasso_node_init_from_query(node, msg) == FALSE) {
				goto cleanup;
			}
			rc = LASSO_MESSAGE_FORMAT_QUERY;
			goto cleanup;
		}
	}

cleanup:
	if (doc_out) {
		*doc_out = doc;
		if (root_out) {
			*root_out = root;
		}
	} else {
		lasso_release_doc(doc);
	}
	return rc;
}

/**
 * lasso_node_init_from_message:
 * @node: a #LassoNode (or derived class)
 * @message: a Liberty message
 *
 * Parses @message and initialiazes @node fields with data from it.  Message
 * type may be base64, SOAP, XML or query string, correct type is found
 * automatically.
 *
 * Return value: a #LassoMessageFormat value.
 **/
LassoMessageFormat
lasso_node_init_from_message(LassoNode *node, const char *message)
{
	return lasso_node_init_from_message_with_format(node, message, LASSO_MESSAGE_FORMAT_UNKNOWN, NULL, NULL);
}

/**
 * lasso_node_class_add_snippets:
 * @klass: object class
 * @snippets: array of XmlSnippet (NULL terminated)
 **/
void
lasso_node_class_add_snippets(LassoNodeClass *klass, struct XmlSnippet *snippets)
{
	klass->node_data->snippets = snippets;
}


/**
 * lasso_node_class_add_query_snippets:
 * @klass: object class
 * @snippets: array of QuerySnippet (NULL terminated)
 **/
void
lasso_node_class_add_query_snippets(LassoNodeClass *klass, struct QuerySnippet *snippets)
{
	klass->node_data->query_snippets = snippets;
}

/**
 * lasso_node_class_set_nodename:
 * @klass: object class
 * @name: name for element node
 **/
void
lasso_node_class_set_nodename(LassoNodeClass *klass, char *name)
{
	if (klass->node_data->node_name)
		g_free(klass->node_data->node_name);
	klass->node_data->node_name = g_strdup(name);
}


/**
 * lasso_node_class_set_ns:
 * @klass: object class
 * @href: namespace uri
 * @prefix: namespace prefix
 **/
void
lasso_node_class_set_ns(LassoNodeClass *klass, char *href, char *prefix)
{
	if (klass->node_data->ns)
		xmlFreeNs(klass->node_data->ns);
	klass->node_data->ns = xmlNewNs(NULL, (xmlChar*)href, (xmlChar*)prefix);
}


static void
snippet_dump_any(gchar *key, gchar *value, xmlNode *xmlnode)
{
	xmlSetProp(xmlnode, (xmlChar*)key, (xmlChar*)value);
}


static void
lasso_node_build_xmlNode_from_snippets(LassoNode *node, xmlNode *xmlnode,
		struct XmlSnippet *snippets, gboolean lasso_dump)
{
	struct XmlSnippet *snippet;
	SnippetType type;
	xmlNode *t;
	xmlNs *xmlns;
	GList *elem;
	struct XmlSnippet *snippet_any_attribute = NULL;

	for (snippet = snippets; snippet && snippet->name; snippet++) {
		void *value = G_STRUCT_MEMBER(void*, node, snippet->offset);
		char *str = value;
		type = snippet->type & 0xff;

		if (lasso_dump == FALSE && snippet->type & SNIPPET_LASSO_DUMP)
			continue;

		if (type == SNIPPET_ATTRIBUTE && snippet->type & SNIPPET_ANY) {
			snippet_any_attribute = snippet;
			continue;
		}
		if (value == NULL && (!(snippet->type & SNIPPET_BOOLEAN ||
					snippet->type & SNIPPET_INTEGER) ||
					snippet->type & SNIPPET_OPTIONAL))
			continue;

		if (snippet->type & SNIPPET_OPTIONAL_NEG && GPOINTER_TO_INT(value) == -1)
			continue;

		/* XXX: not sure it is 64-bits clean */
		if (snippet->type & SNIPPET_BOOLEAN)
			str = GPOINTER_TO_INT(value) ? "true" : "false";
		if (snippet->type & SNIPPET_INTEGER)
			str = g_strdup_printf("%d", GPOINTER_TO_INT(value));

		switch (type) {
			case SNIPPET_ATTRIBUTE:
				if (snippet->ns_name) {
					xmlNsPtr ns;

					ns = xmlNewNs(xmlnode, (xmlChar*)snippet->ns_uri, (xmlChar*)snippet->ns_name);
					xmlSetNsProp(xmlnode, ns, (xmlChar*)snippet->name, (xmlChar*)str);
				} else {
					xmlSetProp(xmlnode, (xmlChar*)snippet->name, (xmlChar*)str);
				}
				break;
			case SNIPPET_TEXT_CHILD:
				xmlAddChild(xmlnode, xmlNewText((xmlChar*)str));
				break;
			case SNIPPET_NODE:
				{
					xmlNode *t2;
					t2 = lasso_node_get_xmlNode(LASSO_NODE(value), lasso_dump);
					if (snippet->class_name)
						xmlNodeSetName(t2, (xmlChar*)snippet->name);
					xmlAddChild(xmlnode, t2);
				} break;
			case SNIPPET_CONTENT:
				xmlNewTextChild(xmlnode, NULL,
						(xmlChar*)snippet->name, (xmlChar*)str);
				break;
			case SNIPPET_NAME_IDENTIFIER:
				xmlns = xmlNewNs(NULL, (xmlChar*)LASSO_LIB_HREF,
						(xmlChar*)LASSO_LIB_PREFIX);
				t = xmlAddChild(xmlnode, lasso_node_get_xmlNode(
							LASSO_NODE(value), lasso_dump));
				xmlNodeSetName(t, (xmlChar*)snippet->name);
				xmlSetNs(t, xmlns);
				break;
			case SNIPPET_NODE_IN_CHILD:
				t = xmlNewTextChild(xmlnode, NULL, (xmlChar*)snippet->name, NULL);
				xmlAddChild(t, lasso_node_get_xmlNode(
							LASSO_NODE(value), lasso_dump));
				break;
			case SNIPPET_LIST_NODES:
				elem = (GList *)value;
				while (elem) {
					xmlNode *subnode = lasso_node_get_xmlNode(
							LASSO_NODE(elem->data), lasso_dump);
					if (subnode) {
						if (snippet->name && snippet->name[0]) {
							xmlNodeSetName(subnode,
									(xmlChar*)snippet->name);
						}
						xmlAddChild(xmlnode, subnode);
					}
					elem = g_list_next(elem);
				}
				break;
			case SNIPPET_LIST_CONTENT:
				/* sequence of simple elements (no children,
				 * no attrs, just content) */
				elem = (GList *)value;
				while (elem) {
					xmlNs *content_ns = NULL;
					if (snippet->ns_name) {
						content_ns = xmlNewNs(xmlnode,
								(const xmlChar*)snippet->ns_uri,
								(const xmlChar*)snippet->ns_name);
					}
					xmlNewTextChild(xmlnode, content_ns,
							(xmlChar*)snippet->name,
							(xmlChar*)(elem->data));
					elem = g_list_next(elem);
				}
				break;
			case SNIPPET_LIST_XMLNODES:
			case SNIPPET_EXTENSION:
				elem = (GList *)value;
				while (elem) {
					xmlAddChild(xmlnode, xmlCopyNode(elem->data, 1));
					elem = g_list_next(elem);
				}
				break;
			case SNIPPET_XMLNODE:
				xmlAddChild(xmlnode, xmlCopyNode((xmlNode *)value, 1));
				break;
			case SNIPPET_SIGNATURE:
				lasso_node_add_signature_template(node, xmlnode, snippet);
				break;
			case SNIPPET_INTEGER:
			case SNIPPET_BOOLEAN:
			case SNIPPET_LASSO_DUMP:
			case SNIPPET_OPTIONAL:
			case SNIPPET_OPTIONAL_NEG:
			case SNIPPET_ALLOW_TEXT:
			case SNIPPET_ANY:
			case SNIPPET_KEEP_XMLNODE:
				g_assert_not_reached();
		}
		if (snippet->type & SNIPPET_INTEGER)
			g_free(str);
	}

	if (snippet_any_attribute) {
		GHashTable *value = G_STRUCT_MEMBER(GHashTable*, node,
				snippet_any_attribute->offset);
		if (value) {
			g_hash_table_foreach(value, (GHFunc)snippet_dump_any, xmlnode);
		}
	}
}

static
void lasso_node_add_signature_template(LassoNode *node, xmlNode *xmlnode,
		struct XmlSnippet *snippet_signature)
{
	LassoNodeClass *klass = LASSO_NODE_GET_CLASS(node);
	LassoSignatureType sign_type;
	LassoSignatureMethod sign_method;
	xmlNode *signature = NULL, *reference, *key_info, *t;
	char *uri;
	char *id;

	while (klass && LASSO_IS_NODE_CLASS(klass) && klass->node_data) {
		if (klass->node_data->sign_type_offset)
			break;
		klass = g_type_class_peek_parent(klass);
	}

	if (klass->node_data->sign_type_offset == 0)
		return;

	sign_type = G_STRUCT_MEMBER(
			LassoSignatureType, node,
			klass->node_data->sign_type_offset);
	sign_method = G_STRUCT_MEMBER(
			LassoSignatureMethod, node,
			klass->node_data->sign_method_offset);

	if (sign_type == LASSO_SIGNATURE_TYPE_NONE)
		return;

	if (sign_method == LASSO_SIGNATURE_METHOD_RSA_SHA1) {
		signature = xmlSecTmplSignatureCreate(NULL,
				xmlSecTransformExclC14NId,
				xmlSecTransformRsaSha1Id, NULL);
	} else {
		signature = xmlSecTmplSignatureCreate(NULL,
				xmlSecTransformExclC14NId,
				xmlSecTransformDsaSha1Id, NULL);
	}
	/* XXX: get out if signature == NULL ? */
	xmlAddChild(xmlnode, signature);

	id = G_STRUCT_MEMBER(char*, node, snippet_signature->offset);
	uri = g_strdup_printf("#%s", id);
	reference = xmlSecTmplSignatureAddReference(signature,
			xmlSecTransformSha1Id, NULL, (xmlChar*)uri, NULL);
	g_free(uri);

	/* add enveloped transform */
	xmlSecTmplReferenceAddTransform(reference, xmlSecTransformEnvelopedId);
	/* add exclusive C14N transform */
	xmlSecTmplReferenceAddTransform(reference, xmlSecTransformExclC14NId);

	if (sign_type == LASSO_SIGNATURE_TYPE_WITHX509) {
		/* add <dsig:KeyInfo/> */
		key_info = xmlSecTmplSignatureEnsureKeyInfo(signature, NULL);
		t = xmlSecTmplKeyInfoAddX509Data(key_info);
	}
}

static struct XmlSnippet*
find_xml_snippet_by_name(LassoNode *node, char *name)
{
	LassoNodeClass *class;
	struct XmlSnippet *snippet;

	class = LASSO_NODE_GET_CLASS(node);
	while (class && LASSO_IS_NODE_CLASS(class) && class->node_data) {
		for (snippet = class->node_data->snippets;
				snippet && snippet->name && strcmp(snippet->name, name) != 0;
				snippet++) ;
		if (snippet && snippet->name)
			return snippet;
		class = g_type_class_peek_parent(class);
	}

	return NULL;
}

static gboolean
find_path(LassoNode *node, char *path, LassoNode **value_node, struct XmlSnippet **snippet)
{
	char *s, *t;
	struct XmlSnippet *tsnippet = NULL;
	LassoNode *tnode = node;

	s = path;
	while (s-1) {
		t = strchr(s, '/');
		if (t) *t = 0;
		tsnippet = find_xml_snippet_by_name(tnode, s);
		if (t) {
			tnode = G_STRUCT_MEMBER(LassoNode*, tnode, tsnippet->offset);
			if (tnode == NULL)
				return FALSE;
		}
		s = t+1;
	}

	if (tsnippet == NULL)
		return FALSE;

	*snippet = tsnippet;
	*value_node = tnode;
	return TRUE;
}


static char*
get_value_by_path(LassoNode *node, char *path, struct XmlSnippet *xml_snippet)
{
	struct XmlSnippet *snippet;
	LassoNode *value_node;

	if (find_path(node, path, &value_node, &snippet) != TRUE)
		return NULL;

	*xml_snippet = *snippet;

	if (snippet->type & SNIPPET_BOOLEAN) {
		gboolean v = G_STRUCT_MEMBER(gboolean, value_node, snippet->offset);
		return v ? g_strdup("true") : g_strdup("false");
	} else if (snippet->type & SNIPPET_INTEGER) {
		int v = G_STRUCT_MEMBER(int, value_node, snippet->offset);
		return g_strdup_printf("%d", v);
	} else if (snippet->type == SNIPPET_NODE) {
		LassoNode *value = G_STRUCT_MEMBER(LassoNode*, value_node, snippet->offset);
		return lasso_node_build_query(value);
	} else if (snippet->type == SNIPPET_EXTENSION) {
		/* convert all of the <lib:Extension> into a string, already
		 * escaped for URI usage */
		GList *value = G_STRUCT_MEMBER(GList*, value_node, snippet->offset);
		xmlChar *s, *s2;
		GString *result = g_string_new("");
		while (value) {
			xmlNode *t = value->data;
			xmlNode *c;

			/* attributes */
#if 0
			xmlAttr *a;
			for (a = t->properties; a; a = a->next) {
				if (result->len)
					g_string_append(result, "&");
				s = xmlGetProp(t, a->name);
				g_string_append(result, a->name);
				g_string_append(result, "=");
				s2 = xmlURIEscapeStr(s, NULL);
				g_string_append(result, s2);
				xmlFree(s2);
				xmlFree(s);
			}
#endif

			/* children (only simple ones and 1-level deep) */
			for (c = t->children; c; c = c->next) {
				if (c->type != XML_ELEMENT_NODE)
					continue;
				if (c->children->type != XML_TEXT_NODE)
					continue;
				if (c->properties != NULL)
					continue;
				if (result->len)
					g_string_append(result, "&");
				g_string_append(result, (char*)c->name);
				g_string_append(result, "=");
				s = xmlNodeGetContent(c);
				s2 = xmlURIEscapeStr(s, NULL);
				g_string_append(result, (char*)s2);
				xmlFree(s2);
				xmlFree(s);
			}

			value = g_list_next(value);
		}
		if (result->len == 0) {
			g_string_free(result, TRUE);
			return NULL;
		}
		return g_string_free(result, FALSE);
	} else if (snippet->type == SNIPPET_LIST_CONTENT) {
		/* not clear in spec; concat values with spaces */
		GList *value = G_STRUCT_MEMBER(GList*, value_node, snippet->offset);
		GString *result = g_string_new("");
		while (value) {
			result = g_string_append(result, (char*)value->data);
			if (value->next)
				result = g_string_append(result, " ");
			value = value->next;
		}
		if (result->len == 0) {
			g_string_free(result, TRUE);
			return NULL;
		}
		return g_string_free(result, FALSE);
	} else {
		char *value = G_STRUCT_MEMBER(char*, value_node, snippet->offset);
		if (value == NULL) return NULL;
		return g_strdup(value);
	}
	return NULL;
}

static gboolean
set_value_at_path(LassoNode *node, char *path, char *query_value)
{
	struct XmlSnippet *snippet;
	LassoNode *value_node;
	void *value;

	if (find_path(node, path, &value_node, &snippet) != TRUE)
		return FALSE;

	value = G_STRUCT_MEMBER_P(value_node, snippet->offset);

	if (snippet->type & SNIPPET_INTEGER) {
		int val = atoi(query_value);
		(*(int*)value) = val;
	} else if (snippet->type & SNIPPET_BOOLEAN) {
		int val = (strcmp(query_value, "true") == 0);
		(*(int*)value) = val;
	} else if (snippet->type == SNIPPET_NODE) {
		LassoNode *v = *(LassoNode**)value;
		if (v == NULL) {
			message(G_LOG_LEVEL_CRITICAL, "building node from query; unknown subnode");
			g_assert_not_reached();
		}
		LASSO_NODE_GET_CLASS(v)->init_from_query(v, &query_value);
	} else if (snippet->type == SNIPPET_LIST_CONTENT) {
		char **elems = g_strsplit(query_value, " ", 0);
		int i;
		GList *l = NULL;
		for (i = 0; elems[i]; i++) {
			l = g_list_append(l, g_strdup(elems[i]));
		}
		g_strfreev(elems);
		(*(GList**)value) = l;
	} else {
		(*(char**)value) = g_strdup(query_value);
	}

	return TRUE;
}


gchar*
lasso_node_build_query_from_snippets(LassoNode *node)
{
	int i;
	char path[100];
	char *v;
	GString *s;
	xmlChar *t;
	LassoNodeClass *class = LASSO_NODE_GET_CLASS(node);
	struct QuerySnippet *query_snippets = NULL;
	struct XmlSnippet xml_snippet;

	while (class && LASSO_IS_NODE_CLASS(class) && class->node_data) {
		if (class->node_data && class->node_data->query_snippets) {
			query_snippets = class->node_data->query_snippets;
			break;
		}
		class = g_type_class_peek_parent(class);
	}
	if (query_snippets == NULL)
		return NULL;

	s = g_string_sized_new(2000);

	for (i=0; query_snippets[i].path; i++) {
		g_strlcpy(path, query_snippets[i].path, 100);
		v = get_value_by_path(node, path, &xml_snippet);
		if (v && xml_snippet.type == SNIPPET_EXTENSION) {
			if (s->len)
				g_string_append(s, "&");
			g_string_append(s, v);
			g_free(v);
			continue;
		}
		if (v) {
			char *field_name = query_snippets[i].field_name;
			if (field_name == NULL)
				field_name = query_snippets[i].path;
			if (s->len)
				g_string_append(s, "&");
			g_string_append(s, field_name);
			g_string_append(s, "=");
			t = xmlURIEscapeStr((xmlChar*)v, NULL);
			g_string_append(s, (char*)t);
			xmlFree(t);
		}
		if (v)
			g_free(v);
	}

	return g_string_free(s, FALSE);
}


gboolean
lasso_node_init_from_query_fields(LassoNode *node, char **query_fields)
{
	int i, j;
	char *field, *t;
	LassoNodeClass *class = LASSO_NODE_GET_CLASS(node);
	struct QuerySnippet *query_snippets = NULL;
	gboolean has_extension = FALSE;

	while (class && LASSO_IS_NODE_CLASS(class) && class->node_data) {
		if (class->node_data && class->node_data->query_snippets) {
			query_snippets = class->node_data->query_snippets;
			break;
		}
		class = g_type_class_peek_parent(class);
	}
	if (query_snippets == NULL)
		return FALSE;

	for (i = 0; (field = query_fields[i]); i++) {
		t = strchr(field, '=');
		if (t == NULL)
			continue;
		*t = 0;

		for (j=0; query_snippets[j].path; j++) {
			char *field_name = query_snippets[j].field_name;
			char path[100];

			g_strlcpy(path, query_snippets[j].path, 100);

			if (field_name == NULL)
				field_name = query_snippets[j].path;
			if (strcmp(field_name, "Extension") == 0) {
				has_extension = TRUE;
				continue;
			}
			if (strcmp(field, field_name) != 0)
				continue;
			set_value_at_path(node, path, t+1);
			break;
		}
		if (query_snippets[j].path == NULL && has_extension &&
				strcmp(field, "SigAlg") != 0 && strcmp(field, "Signature") != 0) {
			/* got to the end without finding anything; and has
			 * Extension; build it */
			struct XmlSnippet *extension_snippet;
			LassoNode *value_node;
			GList **value;
			xmlNode *xmlnode, *xmlchild;
			if (find_path(node, "Extension", &value_node, &extension_snippet) == TRUE) {
				value = G_STRUCT_MEMBER_P(value_node, extension_snippet->offset);
				if (*value) {
					xmlnode = (*value)->data;
				} else {
					xmlnode = xmlNewNode(xmlNewNs(NULL,
								(xmlChar*)LASSO_LIB_HREF,
								(xmlChar*)LASSO_LIB_PREFIX),
							(xmlChar*)"Extension");
				}
				xmlchild = xmlNewNode(NULL, (xmlChar*)field);
				xmlAddChild(xmlchild, xmlNewText((xmlChar*)t+1));
				xmlAddChild(xmlnode, xmlchild);
				if (! *value)
					*value = g_list_append(*value, xmlnode);
			}
		}
		*t = '=';
	}

	return TRUE;
}

gboolean
lasso_node_init_from_saml2_query_fields(LassoNode *node, char **query_fields, G_GNUC_UNUSED char **relay_state)
{
	int i;
	char *field, *t;
	char *req = NULL;
	char *enc = NULL;
	gboolean rc;

	for (i=0; (field=query_fields[i]); i++) {
		t = strchr(field, '=');
		if (t == NULL)
			continue;
		*t = 0;
		if (strcmp(field, "SAMLEncoding") == 0) {
			enc = t+1;
			continue;
		}
		if (strcmp(field, "SAMLRequest") == 0 || strcmp(field, "SAMLResponse") == 0) {
			req = t+1;
			continue;
		}
	}

	if (enc && strcmp(enc, LASSO_SAML2_DEFLATE_ENCODING) != 0) {
		/* unknown encoding */
		message(G_LOG_LEVEL_CRITICAL, "Unknown URL encoding: %s", enc);
		return FALSE;
	}

	if (req == NULL) {
		return FALSE;
	}

	rc = lasso_node_init_from_deflated_query_part(node, req);
	if (rc == FALSE) {
		return rc;
	}

	return TRUE;
}

static void
xmlDeclareNs(xmlNode *root_node, xmlNode *node)
{
	xmlNs *ns, *ns2;
	xmlNode *t;

	if (strcmp((char*)node->name, "Signature") == 0)
		return;

	for (ns = node->nsDef; ns; ns = ns->next) {
		if (ns->prefix && strcmp((char*)ns->prefix, "xsi") != 0) {
			ns2 = xmlNewNs(root_node, ns->href, ns->prefix);
		}
	}
	for (t = node->children; t; t = t->next) {
		if (t->type == XML_ELEMENT_NODE) {
			xmlDeclareNs(root_node, t);
		}
	}
}

static inline int
sameNs(xmlNs *ns1, xmlNs *ns2)
{
	/* this checks ns->prefix instead of ns->href so it is possible to
	 * merge down to an earlier version of liberty namespace
	 */
	return (ns1 == NULL && ns2 == NULL) || (
			ns1 && ns2 && ns1->prefix && ns2->prefix &&
			strcmp((char*)ns1->prefix, (char*)ns2->prefix) == 0 &&
			strcmp((char*)ns1->href, (char*)ns2->href) == 0);
}

static void
xmlPropUseNsDef(xmlNs *ns, xmlNode *node)
{
	xmlAttr *attr;

	for (attr = node->properties; attr; attr = attr->next) {
		if (sameNs(ns, attr->ns)) {
			attr->ns = ns;
		}
	}
}

static void
xmlUseNsDef(xmlNs *ns, xmlNode *node)
{
	xmlNode *t;
	xmlNs *ns2;
	xmlNs *ns3 = NULL;

	xmlPropUseNsDef(ns, node);
	if (sameNs(ns, node->ns)) {
		node->ns = ns;
	}

	for (t = node->children; t; t = t->next) {
		if (t->type == XML_ELEMENT_NODE)
			xmlUseNsDef(ns, t);
	}

	if (sameNs(node->nsDef, ns)) {
		ns3 = node->nsDef;
		node->nsDef = node->nsDef->next;
		xmlFreeNs(ns3);
	} else if (node->nsDef) {
		for (ns2 = node->nsDef; ns2->next; ns2 = ns2->next) {
			if (sameNs(ns2->next, ns)) {
				ns3 = ns2->next;
				ns2->next = ns2->next->next;
				xmlFreeNs(ns3);
				if (ns2->next == NULL)
					break;
			}
		}
	}
}

/**
 * xmlCleanNs
 * @root_node:
 *
 * xmlCleanNs removes duplicate xml namespace declarations and merge them on
 * the @root_node.
 **/
void
xmlCleanNs(xmlNode *root_node)
{
	xmlNs *ns;
	xmlNode *t;

	for (t = root_node->children; t; t = t->next)
		if (t->type == XML_ELEMENT_NODE)
			xmlDeclareNs(root_node, t);

	for (ns = root_node->nsDef; ns; ns = ns->next) {
		for (t = root_node->children; t; t = t->next)
			if (t->type == XML_ELEMENT_NODE)
				xmlUseNsDef(ns, t);
	}
}

void
xml_insure_namespace(xmlNode *xmlnode, xmlNs *ns, gboolean force, gchar *ns_href, gchar *ns_prefix)
{
	xmlNode *t = xmlnode->children;

	if (ns == NULL) {
		for (ns = xmlnode->nsDef; ns; ns = ns->next) {
			if (strcmp((gchar*)ns->href, ns_href) == 0) {
				break;
			}
		}
		if (ns == NULL) {
			ns = xmlNewNs(xmlnode, (xmlChar*)ns_href, (xmlChar*)ns_prefix);
		}
	}

	xmlSetNs(xmlnode, ns);
	while (t) {
		if (t->type == XML_ELEMENT_NODE && (force == TRUE || t->ns == NULL)) {
			xml_insure_namespace(t, ns, force, NULL, NULL);
		}
		t = t->next;
	}
}

/**
 * lasso_node_get_xmlnode_for_any_type:
 * @node: a #LassoNode.
 * @xmlnode: the #xmlNode returned.
 *
 * Return value: a xmlNode completed with the content of the produced by the get_xmlNode virtual
 * method of the parent class.
 */
xmlNode*
lasso_node_get_xmlnode_for_any_type(LassoNode *node, xmlNode *cur)
{

	xmlNode *original_xmlnode;

	original_xmlnode = lasso_node_get_original_xmlnode(node);
	if (cur) {
		if (original_xmlnode) {
			xmlNode *children = xmlCopyNodeList(original_xmlnode->children);
			xmlCopyPropList(cur, original_xmlnode->properties);
			xmlAddChildList(cur, children);
			return cur;
		} else {
			return cur;
		}
	} else {
		if (original_xmlnode) {
			return xmlCopyNode(original_xmlnode, 1);
		} else {
			return cur;
		}
	}
}

/**
 * lasso_node_get_name:
 * @node: a #LassoNode
 *
 * Return the XML element name for this object, the one that would be used in the XML dump of this
 * object.
 *
 * Return value: the name of the object, the value must not be stored.
 */
const char*
lasso_node_get_name(LassoNode *node)
{
	struct _CustomElement *custom_element;
	LassoNodeClass *klass;
	g_return_val_if_fail(LASSO_IS_NODE(node), NULL);

	custom_element = _lasso_node_get_custom_element(node);
	if (custom_element && custom_element->nodename) {
		return custom_element->nodename;
	}
	klass = LASSO_NODE_GET_CLASS(node);
	return klass->node_data->node_name;
}
