/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 */

#include "evolution-ews-config.h"

#include <libedataserver/eds-version.h>

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <libsoup/soup.h>
#ifdef G_OS_WIN32
#include <io.h>
#endif

#include "e-soap-message.h"
#include "e-ews-debug.h"

#define E_SOAP_MESSAGE_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), E_TYPE_SOAP_MESSAGE, ESoapMessagePrivate))

struct _ESoapMessagePrivate {
	/* Serialization fields */
	xmlParserCtxtPtr ctxt;
	xmlDocPtr doc;
	xmlNodePtr last_node;
	xmlNsPtr soap_ns;
	xmlNsPtr xsi_ns;
	xmlChar *env_prefix;
	xmlChar *env_uri;
	gboolean body_started;
	gchar *action;

	/* Content stealing */
	gchar *steal_node;
	gchar *steal_dir;
	gboolean steal_base64;

	gint steal_b64_state;
	guint steal_b64_save;
	gint steal_fd;

	/* Progress callbacks */
	gsize response_size;
	gsize response_received;
	ESoapProgressFn progress_fn;
	gpointer progress_data;
};

G_DEFINE_TYPE (ESoapMessage, e_soap_message, SOUP_TYPE_MESSAGE)

static void
soap_message_finalize (GObject *object)
{
	ESoapMessagePrivate *priv;

	priv = E_SOAP_MESSAGE_GET_PRIVATE (object);

	if (priv->ctxt != NULL) {
		if (priv->ctxt->myDoc != NULL)
			xmlFreeDoc (priv->ctxt->myDoc);
		xmlFreeParserCtxt (priv->ctxt);
	}

	if (priv->doc != NULL)
		xmlFreeDoc (priv->doc);

	if (priv->action != NULL)
		g_free (priv->action);

	if (priv->env_uri != NULL)
		xmlFree (priv->env_uri);

	if (priv->env_prefix != NULL)
		xmlFree (priv->env_prefix);

	g_free (priv->steal_node);
	g_free (priv->steal_dir);

	if (priv->steal_fd != -1)
		close (priv->steal_fd);

	/* Chain up to parent's finalize() method. */
	G_OBJECT_CLASS (e_soap_message_parent_class)->finalize (object);
}

static void
e_soap_message_class_init (ESoapMessageClass *class)
{
	GObjectClass *object_class;

	g_type_class_add_private (class, sizeof (ESoapMessagePrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->finalize = soap_message_finalize;
}

static void
e_soap_message_init (ESoapMessage *msg)
{
	msg->priv = E_SOAP_MESSAGE_GET_PRIVATE (msg);

	/* initialize XML structures */
	msg->priv->doc = xmlNewDoc ((const xmlChar *) "1.0");
	msg->priv->doc->standalone = FALSE;
	msg->priv->doc->encoding = xmlCharStrdup ("UTF-8");
	msg->priv->steal_fd = -1;

#ifdef HAVE_SOUP_MESSAGE_IDEMPOTENT_FLAG
	soup_message_set_flags (SOUP_MESSAGE (msg), SOUP_MESSAGE_IDEMPOTENT);
#endif
}

static xmlNsPtr
fetch_ns (ESoapMessage *msg,
          const gchar *prefix,
          const gchar *ns_uri)
{
	ESoapMessagePrivate *priv = E_SOAP_MESSAGE_GET_PRIVATE (msg);
	xmlNsPtr ns = NULL;

	if (prefix && ns_uri)
		ns = xmlNewNs (
			priv->last_node,
			(const xmlChar *) ns_uri,
			(const xmlChar *) prefix);
	else if (prefix && !ns_uri) {
		ns = xmlSearchNs (
			priv->doc, priv->last_node,
			(const xmlChar *) prefix);
		if (!ns)
			ns = xmlNewNs (
				priv->last_node,
				(const xmlChar *) "",
				(const xmlChar *) prefix);
	}

	return ns;
}

static void
soap_got_headers (SoupMessage *msg,
                  gpointer data)
{
	ESoapMessagePrivate *priv = E_SOAP_MESSAGE_GET_PRIVATE (msg);
	const gchar *size;

	size = soup_message_headers_get_one (
		msg->response_headers,
		"Content-Length");

	if (size)
		priv->response_size = strtol (size, NULL, 10);
}

static void
soap_restarted (SoupMessage *msg,
                gpointer data)
{
	ESoapMessagePrivate *priv = E_SOAP_MESSAGE_GET_PRIVATE (msg);

	priv->response_size = 0;
	priv->response_received = 0;

	/* Discard the existing context, if there is one, and start again */
	if (priv->ctxt) {
		if (priv->ctxt->myDoc)
			xmlFreeDoc (priv->ctxt->myDoc);
		xmlFreeParserCtxt (priv->ctxt);
		priv->ctxt = NULL;
	}
}

static void
soap_sax_startElementNs (gpointer _ctxt,
                         const xmlChar *localname,
                         const xmlChar *prefix,
                         const xmlChar *uri,
                         gint nb_namespaces,
                         const xmlChar **namespaces,
                         gint nb_attributes,
                         gint nb_defaulted,
                         const xmlChar **attributes)
{
	xmlParserCtxt *ctxt = _ctxt;
	ESoapMessagePrivate *priv = ctxt->_private;
	gchar *fname;

	xmlSAX2StartElementNs (
		ctxt, localname, prefix, uri, nb_namespaces,
		namespaces, nb_attributes, nb_defaulted,
		attributes);

	/* steal_node can contain multiple node name separated by " " */
	if (priv->steal_node && *priv->steal_node) {
		gchar **prop = g_strsplit (priv->steal_node, " ", 0);
		gint i = 0;
		gboolean isnode = FALSE;

		while (prop[i]) {
			if (strcmp ((const gchar *) localname, prop[i]) == 0) {
				isnode = TRUE;
				break;
			}
			i++;
		}
		g_strfreev (prop);

		if (!isnode) return;
	} else
		return;

	fname = g_build_filename (priv->steal_dir, "XXXXXX", NULL);
	priv->steal_fd = g_mkstemp (fname);
	if (priv->steal_fd != -1) {
		if (priv->steal_base64) {
			gchar *enc = g_base64_encode ((guchar *) fname, strlen (fname));
			xmlSAX2Characters (ctxt, (xmlChar *) enc, strlen (enc));
			g_free (enc);
		} else
			xmlSAX2Characters (ctxt, (xmlChar *) fname, strlen (fname));
	} else {
		gint err = errno;

		g_warning ("%s: Failed to create temp file '%s': %s\n", G_STRFUNC, fname, g_strerror (err));
	}
	g_free (fname);
}

static void
soap_sax_endElementNs (gpointer _ctxt,
                       const xmlChar *localname,
                       const xmlChar *prefix,
                       const xmlChar *uri)
{
	xmlParserCtxt *ctxt = _ctxt;
	ESoapMessagePrivate *priv = ctxt->_private;

	if (priv->steal_fd != -1) {
		close (priv->steal_fd);
		priv->steal_fd = -1;
	}
	xmlSAX2EndElementNs (ctxt, localname, prefix, uri);
}

static void
soap_sax_characters (gpointer _ctxt,
                     const xmlChar *ch,
                     gint len)
{
	xmlParserCtxt *ctxt = _ctxt;
	ESoapMessagePrivate *priv = ctxt->_private;

	if (priv->steal_fd == -1)
		xmlSAX2Characters (ctxt, ch, len);
	else if (!priv->steal_base64) {
		if (write (priv->steal_fd, (const gchar *) ch, len) != len) {
		write_err:
			/* Handle error better */
			g_warning ("Failed to write streaming data to file");
		}
	} else {
		guchar *bdata = g_malloc (len);
		gsize blen;

		blen = g_base64_decode_step (
			(const gchar *) ch, len,
			bdata, &priv->steal_b64_state,
			&priv->steal_b64_save);
		if (write (priv->steal_fd, (const gchar *) bdata, blen) != blen) {
			g_free (bdata);
			goto write_err;
		}
		g_free (bdata);
	}
}

static void
soap_got_chunk (SoupMessage *msg,
                SoupBuffer *chunk,
                gpointer data)
{
	ESoapMessagePrivate *priv = E_SOAP_MESSAGE_GET_PRIVATE (msg);

	priv->response_received += chunk->length;

	if (priv->response_size && priv->progress_fn) {
		gint pc = priv->response_received * 100 / priv->response_size;
		priv->progress_fn (priv->progress_data, pc);
	}

	if (!priv->ctxt) {
		priv->ctxt = xmlCreatePushParserCtxt (
			NULL, msg, chunk->data,
			chunk->length, NULL);
		priv->ctxt->_private = priv;
		priv->ctxt->sax->startElementNs = soap_sax_startElementNs;
		priv->ctxt->sax->endElementNs = soap_sax_endElementNs;
		priv->ctxt->sax->characters = soap_sax_characters;
	}
	else
		xmlParseChunk (priv->ctxt, chunk->data, chunk->length, 0);
}

/**
 * e_soap_message_new:
 * @method: the HTTP method for the created request.
 * @uri_string: the destination endpoint (as a string).
 * @standalone: ??? FIXME
 * @xml_encoding: ??? FIXME
 * @env_prefix: ??? FIXME
 * @env_uri: ??? FIXME
 *
 * Creates a new empty #ESoapMessage, which will connect to @uri_string.
 *
 * Returns: the new #ESoapMessage (or %NULL if @uri_string could not be
 * parsed).
 */
ESoapMessage *
e_soap_message_new (const gchar *method,
                    const gchar *uri_string,
                    gboolean standalone,
                    const gchar *xml_encoding,
                    const gchar *env_prefix,
                    const gchar *env_uri,
		    gboolean standard_handlers)
{
	ESoapMessage *msg;
	SoupURI *uri;

	uri = soup_uri_new (uri_string);
	if (!uri)
		return NULL;

	msg = e_soap_message_new_from_uri (
		method, uri, standalone,
		xml_encoding, env_prefix, env_uri);

	soup_uri_free (uri);

	/*
	 * Don't use streaming-based messages when we are loggin the traffic
	 * to generate trace files for tests
	 */
	if (e_ews_debug_get_log_level () <= 2) {
		/* Don't accumulate body data into a huge buffer.
		 * Instead, parse it as it arrives. */
		soup_message_body_set_accumulate (
				SOUP_MESSAGE (msg)->response_body,
				FALSE);
	}

	if (standard_handlers) {
		g_signal_connect (msg, "got-headers", G_CALLBACK (soap_got_headers), NULL);
		g_signal_connect (msg, "got-chunk", G_CALLBACK (soap_got_chunk), NULL);
		g_signal_connect (msg, "restarted", G_CALLBACK (soap_restarted), NULL);
	}

	return msg;
}

/**
 * e_soap_message_new_from_uri:
 * @method: the HTTP method for the created request.
 * @uri: the destination endpoint (as a #SoupURI).
 * @standalone: ??? FIXME
 * @xml_encoding: ??? FIXME
 * @env_prefix: ??? FIXME
 * @env_uri: ??? FIXME
 *
 * Creates a new empty #ESoapMessage, which will connect to @uri
 *
 * Returns: the new #ESoapMessage
 */
ESoapMessage *
e_soap_message_new_from_uri (const gchar *method,
                             SoupURI *uri,
                             gboolean standalone,
                             const gchar *xml_encoding,
                             const gchar *env_prefix,
                             const gchar *env_uri)
{
	ESoapMessage *msg;

	msg = g_object_new (
		E_TYPE_SOAP_MESSAGE,
		SOUP_MESSAGE_METHOD, method,
		SOUP_MESSAGE_URI, uri, NULL);

	msg->priv->doc->standalone = standalone;

	if (xml_encoding) {
		xmlFree ((xmlChar *) msg->priv->doc->encoding);
		msg->priv->doc->encoding = xmlCharStrdup (xml_encoding);
	}

	if (env_prefix != NULL)
		msg->priv->env_prefix = xmlCharStrdup (env_prefix);
	if (env_uri != NULL)
		msg->priv->env_uri = xmlCharStrdup (env_uri);

	return msg;
}

/**
 * e_soap_message_store_node_data:
 * @msg: the %ESoapMessage.
 * @nodename: the name of the XML node from which to store data
 * @directory: cache directory in which to create data files
 * @base64: flag to request base64 decoding of node content
 *
 * This requests that character data for certain XML nodes should
 * be streamed directly to a disk file as it arrives, rather than
 * being stored in memory in the soup response buffer.
 */

void
e_soap_message_store_node_data (ESoapMessage *msg,
                                const gchar *nodename,
                                const gchar *directory,
                                gboolean base64)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	msg->priv->steal_node = g_strdup (nodename);
	msg->priv->steal_dir = g_strdup (directory);
	msg->priv->steal_base64 = base64;
}

/**
 * e_soap_message_set_progress_fn:
 * @msg: the %ESoapMessage.
 * @fn: callback function to be given progress updates
 * @object: first argument to callback function
 *
 * Starts the top level SOAP Envelope element.
 */
void
e_soap_message_set_progress_fn (ESoapMessage *msg,
                                ESoapProgressFn fn,
                                gpointer object)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	msg->priv->progress_fn = fn;
	msg->priv->progress_data = object;
}

/**
 * e_soap_message_start_envelope:
 * @msg: the %ESoapMessage.
 *
 * Starts the top level SOAP Envelope element.
 */
void
e_soap_message_start_envelope (ESoapMessage *msg)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	msg->priv->doc->xmlRootNode = xmlNewDocNode (
		msg->priv->doc, NULL,
		(const xmlChar *) "Envelope",
		(const xmlChar *) NULL);
	msg->priv->last_node = msg->priv->doc->xmlRootNode;

	msg->priv->soap_ns = xmlNewNs (
		msg->priv->doc->xmlRootNode,
		msg->priv->env_uri ? msg->priv->env_uri :
		(const xmlChar *) "http://schemas.xmlsoap.org/soap/envelope/",
		msg->priv->env_prefix ? msg->priv->env_prefix :
		(const xmlChar *) "SOAP-ENV");

	if (msg->priv->env_uri != NULL) {
		xmlFree (msg->priv->env_uri);
		msg->priv->env_uri = NULL;
	}

	if (msg->priv->env_prefix) {
		xmlFree (msg->priv->env_prefix);
		msg->priv->env_prefix = NULL;
	}

	xmlSetNs (
		msg->priv->doc->xmlRootNode,
		msg->priv->soap_ns);
	xmlNewNs (
		msg->priv->doc->xmlRootNode,
		(const xmlChar *) "http://schemas.xmlsoap.org/soap/encoding/",
		(const xmlChar *) "SOAP-ENC");
	xmlNewNs (
		msg->priv->doc->xmlRootNode,
		(const xmlChar *) "http://www.w3.org/2001/XMLSchema",
		(const xmlChar *) "xsd");
	xmlNewNs (
		msg->priv->doc->xmlRootNode,
		(const xmlChar *) "http://schemas.xmlsoap.org/soap/envelope/",
		(const xmlChar *) "SOAP-ENV");
	msg->priv->xsi_ns = xmlNewNs (
		msg->priv->doc->xmlRootNode,
		(const xmlChar *) "http://www.w3.org/2001/XMLSchema-instance",
		(const xmlChar *) "xsi");
}

/**
 * e_soap_message_end_envelope:
 * @msg: the %ESoapMessage.
 *
 * Closes the top level SOAP Envelope element.
 */
void
e_soap_message_end_envelope (ESoapMessage *msg)
{
	e_soap_message_end_element (msg);
}

/**
 * e_soap_message_start_body:
 * @msg: the %ESoapMessage.
 *
 * Starts the SOAP Body element.
 */
void
e_soap_message_start_body (ESoapMessage *msg)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	if (msg->priv->body_started)
		return;

	msg->priv->last_node = xmlNewChild (
		msg->priv->last_node,
		msg->priv->soap_ns,
		(const xmlChar *) "Body", NULL);

	msg->priv->body_started = TRUE;
}

/**
 * e_soap_message_end_body:
 * @msg: the %ESoapMessage.
 *
 * Closes the SOAP Body element.
 */
void
e_soap_message_end_body (ESoapMessage *msg)
{
	e_soap_message_end_element (msg);
}

/**
 * e_soap_message_start_element:
 * @msg: the #ESoapMessage.
 * @name: the element name.
 * @prefix: the namespace prefix
 * @ns_uri: the namespace URI
 *
 * Starts a new arbitrary message element, with @name as the element
 * name, @prefix as the XML Namespace prefix, and @ns_uri as the XML
 * Namespace uri for * the created element.
 *
 * Passing @prefix with no @ns_uri will cause a recursive search for
 * an existing namespace with the same prefix. Failing that a new ns
 * will be created with an empty uri.
 *
 * Passing both @prefix and @ns_uri always causes new namespace
 * attribute creation.
 *
 * Passing NULL for both @prefix and @ns_uri causes no prefix to be
 * used, and the element will be in the default namespace.
 */
void
e_soap_message_start_element (ESoapMessage *msg,
                              const gchar *name,
                              const gchar *prefix,
                              const gchar *ns_uri)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	msg->priv->last_node = xmlNewChild (
		msg->priv->last_node, NULL,
		(const xmlChar *) name, NULL);

	xmlSetNs (msg->priv->last_node, fetch_ns (msg, prefix, ns_uri));

	if (ns_uri == NULL)
		ns_uri = "";

	if (msg->priv->body_started && msg->priv->action == NULL)
		msg->priv->action = g_strconcat (ns_uri, "#", name, NULL);
}

/**
 * e_soap_message_end_element:
 * @msg: the #ESoapMessage.
 *
 * Closes the current message element.
 */
void
e_soap_message_end_element (ESoapMessage *msg)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	msg->priv->last_node = msg->priv->last_node->parent;
}

/**
 * e_soap_message_start_fault:
 * @msg: the #ESoapMessage.
 * @faultcode: faultcode element value
 * @faultstring: faultstring element value
 * @faultfactor: faultfactor element value
 *
 * Starts a new SOAP Fault element, creating faultcode, faultstring,
 * and faultfactor child elements.
 *
 * If you wish to add the faultdetail element, use
 * e_soap_message_start_fault_detail(), and then
 * e_soap_message_start_element() to add arbitrary sub-elements.
 */
void
e_soap_message_start_fault (ESoapMessage *msg,
                            const gchar *faultcode,
                            const gchar *faultstring,
                            const gchar *faultfactor)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	msg->priv->last_node = xmlNewChild (
		msg->priv->last_node,
		msg->priv->soap_ns,
		(const xmlChar *) "Fault", NULL);
	xmlNewChild (
		msg->priv->last_node,
		msg->priv->soap_ns,
		(const xmlChar *) "faultcode",
		(const xmlChar *) faultcode);
	xmlNewChild (
		msg->priv->last_node,
		msg->priv->soap_ns,
		(const xmlChar *) "faultstring",
		(const xmlChar *) faultstring);

	msg->priv->last_node = xmlNewChild (
		msg->priv->last_node,
		msg->priv->soap_ns,
		(const xmlChar *) "faultfactor",
		(const xmlChar *) faultfactor);
	if (faultfactor == NULL)
		e_soap_message_set_null (msg);

	e_soap_message_end_element (msg);
}

/**
 * e_soap_message_end_fault:
 * @msg: the #ESoapMessage.
 *
 * Closes the current SOAP Fault element.
 */
void
e_soap_message_end_fault (ESoapMessage *msg)
{
	e_soap_message_end_element (msg);
}

/**
 * e_soap_message_start_fault_detail:
 * @msg: the #ESoapMessage.
 *
 * Start the faultdetail child element of the current SOAP Fault
 * element. The faultdetail element allows arbitrary data to be sent
 * in a returned fault.
 **/
void
e_soap_message_start_fault_detail (ESoapMessage *msg)
{

	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	msg->priv->last_node = xmlNewChild (
		msg->priv->last_node,
		msg->priv->soap_ns,
		(const xmlChar *) "detail",
		(const xmlChar *) NULL);
}

/**
 * e_soap_message_end_fault_detail:
 * @msg: the #ESoapMessage.
 *
 * Closes the current SOAP faultdetail element.
 */
void
e_soap_message_end_fault_detail (ESoapMessage *msg)
{
	e_soap_message_end_element (msg);
}

/**
 * e_soap_message_start_header:
 * @msg: the #ESoapMessage.
 *
 * Creates a new SOAP Header element. You can call
 * e_soap_message_start_header_element() after this to add a new
 * header child element. SOAP Header elements allow out-of-band data
 * to be transferred while not interfering with the message body.
 *
 * This should be called after e_soap_message_start_envelope() and
 * before e_soap_message_start_body().
 */
void
e_soap_message_start_header (ESoapMessage *msg)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	msg->priv->last_node = xmlNewChild (
		msg->priv->last_node,
		msg->priv->soap_ns,
		(const xmlChar *) "Header",
		(const xmlChar *) NULL);
}

/**
 * e_soap_message_end_header:
 * @msg: the #ESoapMessage.
 *
 * Closes the current SOAP Header element.
 */
void
e_soap_message_end_header (ESoapMessage *msg)
{
	e_soap_message_end_element (msg);
}

/**
 * e_soap_message_start_header_element:
 * @msg: the #ESoapMessage.
 * @name: name of the header element
 * @must_understand: whether the recipient must understand the header in order
 * to proceed with processing the message
 * @actor_uri: the URI which represents the destination actor for this header.
 * @prefix: the namespace prefix
 * @ns_uri: the namespace URI
 *
 * Starts a new SOAP arbitrary header element.
 */
void
e_soap_message_start_header_element (ESoapMessage *msg,
                                     const gchar *name,
                                     gboolean must_understand,
                                     const gchar *actor_uri,
                                     const gchar *prefix,
                                     const gchar *ns_uri)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	e_soap_message_start_element (msg, name, prefix, ns_uri);
	if (actor_uri != NULL)
		xmlNewNsProp (
			msg->priv->last_node,
			msg->priv->soap_ns,
			(const xmlChar *) "actorUri",
			(const xmlChar *) actor_uri);
	if (must_understand)
		xmlNewNsProp (
			msg->priv->last_node,
			msg->priv->soap_ns,
			(const xmlChar *) "mustUnderstand",
			(const xmlChar *) "1");
}

/**
 * e_soap_message_end_header_element:
 * @msg: the #ESoapMessage.
 *
 * Closes the current SOAP header element.
 */
void
e_soap_message_end_header_element (ESoapMessage *msg)
{
	e_soap_message_end_element (msg);
}

/**
 * e_soap_message_write_int:
 * @msg: the #ESoapMessage.
 * @i: the integer value to write.
 *
 * Writes the stringified value of @i as the current element's content.
 */
void
e_soap_message_write_int (ESoapMessage *msg,
                          glong i)
{
	gchar *string;

	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	string = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) i);
	e_soap_message_write_string (msg, string);
	g_free (string);
}

/**
 * e_soap_message_write_double:
 * @msg: the #ESoapMessage.
 * @d: the double value to write.
 *
 * Writes the stringified value of @d as the current element's content.
 */
void
e_soap_message_write_double (ESoapMessage *msg,
                             gdouble d)
{
	gchar buffer[G_ASCII_DTOSTR_BUF_SIZE + 1];

	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	g_ascii_dtostr (buffer, sizeof (buffer), d);
	e_soap_message_write_string (msg, buffer);
}

/**
 * e_soap_message_write_base64:
 * @msg: the #ESoapMessage
 * @string: the binary data buffer to encode
 * @len: the length of data to encode
 *
 * Writes the Base-64 encoded value of @string as the current
 * element's content.
 **/
void
e_soap_message_write_base64 (ESoapMessage *msg,
                             const gchar *string,
                             gint len)
{
	gchar *encoded;

	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	encoded = g_base64_encode ((const guchar *) string, len);
	e_soap_message_write_string (msg, encoded);
	g_free (encoded);
}

/**
 * e_soap_message_write_time:
 * @msg: the #ESoapMessage.
 * @timeval: the time_t to encode
 *
 * Writes the iso8601 value of @timeval as the current element's
 * content.
 **/
void
e_soap_message_write_time (ESoapMessage *msg,
                           time_t timeval)
{
	GTimeVal tv;
	gchar *iso_time;

	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	tv.tv_usec = 0;
	tv.tv_sec = timeval;

	iso_time = g_time_val_to_iso8601 (&tv);
	e_soap_message_write_string (msg, iso_time);
	g_free (iso_time);
}

/**
 * e_soap_message_write_string:
 * @msg: the #ESoapMessage.
 * @string: string to write.
 *
 * Writes the @string as the current element's content.
 */
void
e_soap_message_write_string (ESoapMessage *msg,
                             const gchar *string)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	xmlNodeAddContent (
		msg->priv->last_node,
		(const xmlChar *) string);
}

/**
 * e_soap_message_write_buffer:
 * @msg: the #ESoapMessage.
 * @buffer: the string data buffer to write.
 * @len: length of @buffer.
 *
 * Writes the string buffer pointed to by @buffer as the current
 * element's content.
 */
void
e_soap_message_write_buffer (ESoapMessage *msg,
                             const gchar *buffer,
                             gint len)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	xmlNodeAddContentLen (
		msg->priv->last_node,
		(const xmlChar *) buffer, len);
}

/**
 * e_soap_message_set_element_type:
 * @msg: the #ESoapMessage.
 * @xsi_type: the type name for the element.
 *
 * Sets the current element's XML schema xsi:type attribute, which
 * specifies the element's type name.
 */
void
e_soap_message_set_element_type (ESoapMessage *msg,
                                 const gchar *xsi_type)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	xmlNewNsProp (
		msg->priv->last_node,
		msg->priv->xsi_ns,
		(const xmlChar *) "type",
		(const xmlChar *) xsi_type);
}

/**
 * e_soap_message_set_null:
 * @msg: the #ESoapMessage.
 *
 * Sets the current element's XML Schema xsi:null attribute.
 */
void
e_soap_message_set_null (ESoapMessage *msg)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	xmlNewNsProp (
		msg->priv->last_node,
		msg->priv->xsi_ns,
		(const xmlChar *) "null",
		(const xmlChar *) "1");
}

/**
 * e_soap_message_add_attribute:
 * @msg: the #ESoapMessage.
 * @name: name of the attribute
 * @value: value of the attribute
 * @prefix: the namespace prefix
 * @ns_uri: the namespace URI
 *
 * Adds an XML attribute to the current element.
 */
void
e_soap_message_add_attribute (ESoapMessage *msg,
                              const gchar *name,
                              const gchar *value,
                              const gchar *prefix,
                              const gchar *ns_uri)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	xmlNewNsProp (
		msg->priv->last_node,
		fetch_ns (msg, prefix, ns_uri),
		(const xmlChar *) name,
		(const xmlChar *) value);
}

/**
 * e_soap_message_add_namespace:
 * @msg: the #ESoapMessage.
 * @prefix: the namespace prefix
 * @ns_uri: the namespace URI, or NULL for empty namespace
 *
 * Adds a new XML namespace to the current element.
 */
void
e_soap_message_add_namespace (ESoapMessage *msg,
                              const gchar *prefix,
                              const gchar *ns_uri)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	if (ns_uri == NULL)
		ns_uri = "";

	xmlNewNs (
		msg->priv->last_node,
		(const xmlChar *) ns_uri,
		(const xmlChar *) prefix);
}

/**
 * e_soap_message_set_default_namespace:
 * @msg: the #ESoapMessage.
 * @ns_uri: the namespace URI.
 *
 * Sets the default namespace to the URI specified in @ns_uri. The
 * default namespace becomes the namespace all non-explicitly
 * namespaced child elements fall into.
 */
void
e_soap_message_set_default_namespace (ESoapMessage *msg,
                                      const gchar *ns_uri)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	e_soap_message_add_namespace (msg, NULL, ns_uri);
}

/**
 * e_soap_message_set_encoding_style:
 * @msg: the #ESoapMessage.
 * @enc_style: the new encodingStyle value
 *
 * Sets the encodingStyle attribute on the current element to the
 * value of @enc_style.
 */
void
e_soap_message_set_encoding_style (ESoapMessage *msg,
                                   const gchar *enc_style)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	xmlNewNsProp (
		msg->priv->last_node,
		msg->priv->soap_ns,
		(const xmlChar *) "encodingStyle",
		(const xmlChar *) enc_style);
}

/**
 * e_soap_message_reset:
 * @msg: the #ESoapMessage.
 *
 * Resets the internal XML representation of the SOAP message.
 */
void
e_soap_message_reset (ESoapMessage *msg)
{
	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	xmlFreeDoc (msg->priv->doc);
	msg->priv->doc = xmlNewDoc ((const xmlChar *) "1.0");
	msg->priv->last_node = NULL;

	g_free (msg->priv->action);
	msg->priv->action = NULL;
	msg->priv->body_started = FALSE;

	if (msg->priv->env_uri != NULL) {
		xmlFree (msg->priv->env_uri);
		msg->priv->env_uri = NULL;
	}

	if (msg->priv->env_prefix != NULL) {
		xmlFree (msg->priv->env_prefix);
		msg->priv->env_prefix = NULL;
	}
}

/**
 * e_soap_message_persist:
 * @msg: the #ESoapMessage.
 *
 * Writes the serialized XML tree to the #SoupMessage's buffer.
 */
void
e_soap_message_persist (ESoapMessage *msg)
{
	xmlChar *body;
	gint len;

	g_return_if_fail (E_IS_SOAP_MESSAGE (msg));

	xmlDocDumpMemory (msg->priv->doc, &body, &len);

	/* serialize to SoupMessage class */
	soup_message_set_request (
		SOUP_MESSAGE (msg),
		"text/xml; charset=utf-8",
		SOUP_MEMORY_COPY, (gchar *) body, len);

	xmlFree (body);
}

/**
 * e_soap_message_get_namespace_prefix:
 * @msg: the #ESoapMessage.
 * @ns_uri: the namespace URI.
 *
 * Returns the namespace prefix for @ns_uri (or an empty string if
 * @ns_uri is set to the default namespace)
 *
 * Returns: The namespace prefix, or %NULL if no namespace exists
 * for the URI given.
 */
const gchar *
e_soap_message_get_namespace_prefix (ESoapMessage *msg,
                                     const gchar *ns_uri)
{
	xmlNsPtr ns = NULL;

	g_return_val_if_fail (E_IS_SOAP_MESSAGE (msg), NULL);
	g_return_val_if_fail (ns_uri != NULL, NULL);

	ns = xmlSearchNsByHref (
		msg->priv->doc,
		msg->priv->last_node,
		(const xmlChar *) ns_uri);
	if (ns != NULL) {
		if (ns->prefix != NULL)
			return (const gchar *) ns->prefix;
		else
			return "";
	}

	return NULL;
}

/**
 * e_soap_message_get_xml_doc:
 * @msg: the #ESoapMessage.
 *
 * Returns the internal XML representation tree of the
 * #ESoapMessage pointed to by @msg.
 *
 * Returns: the #xmlDocPtr representing the SOAP message.
 */
xmlDocPtr
e_soap_message_get_xml_doc (ESoapMessage *msg)
{
	g_return_val_if_fail (E_IS_SOAP_MESSAGE (msg), NULL);

	return msg->priv->doc;
}

/**
 * e_soap_message_parse_response:
 * @msg: the #ESoapMessage.
 *
 * Parses the response returned by the server.
 *
 * Returns: a #ESoapResponse representing the response from
 * the server, or %NULL if there was an error.
 */
ESoapResponse *
e_soap_message_parse_response (ESoapMessage *msg)
{
	xmlDocPtr xmldoc;

	g_return_val_if_fail (E_IS_SOAP_MESSAGE (msg), NULL);

	if (msg->priv->ctxt == NULL)
		return NULL;

	xmlParseChunk (msg->priv->ctxt, 0, 0, 1);

	xmldoc = msg->priv->ctxt->myDoc;

	xmlFreeParserCtxt (msg->priv->ctxt);
	msg->priv->ctxt = NULL;

	if (xmldoc == NULL)
		return NULL;

	return e_soap_response_new_from_xmldoc (xmldoc);
}

