/* iksemel (XML parser for Jabber)
** Copyright (C) 2000-2003 Gurer Ozen <madcat@e-kolay.net>
** This code is free software; you can redistribute it and/or
** modify it under the terms of GNU Lesser General Public License.
*/

#include "common.h"
#include "iksemel.h"

iksid *
iks_id_new (ikstack *s, const char *jid)
{
	iksid *id;
	char *src, *tmp;

/* FIXME: add jabber id validity checks to this function */
/* which characters are allowed in id parts? */

	if (!jid) return NULL;
	id = iks_stack_alloc (s, sizeof (iksid));
	if (!id) return NULL;
	memset (id, 0, sizeof (iksid));

	/* skip scheme */
	if (strncmp ("jabber:", jid, 7) == 0) jid += 7;

	id->full = iks_stack_strdup (s, jid, 0);
	src = id->full;

	/* split resource */
	tmp = strchr (src, '/');
	if (tmp) {
		id->partial = iks_stack_strdup (s, src, tmp - src);
		id->resource = tmp + 1;
		src = id->partial;
	} else {
		id->partial = src;
	}

	/* split user */
	tmp = strchr (src, '@');
	if (tmp) {
		id->user = iks_stack_strdup (s, src, tmp - src);
		src = ++tmp;
	}

	id->server = src;

	return id;
}

int
iks_id_cmp (iksid *a, iksid *b, int parts)
{
	int diff;

	if (!a || !b) return (IKS_ID_RESOURCE | IKS_ID_USER | IKS_ID_SERVER);
	diff = 0;
	if (parts & IKS_ID_RESOURCE && !(!a->resource && !b->resource) && iks_strcmp (a->resource, b->resource) != 0)
		diff += IKS_ID_RESOURCE;
	if (parts & IKS_ID_USER && !(!a->user && !b->user) && iks_strcasecmp (a->user, b->user) != 0)
		diff += IKS_ID_USER;
	if (parts & IKS_ID_SERVER && !(!a->server && !b->server) && iks_strcmp (a->server, b->server) != 0)
		diff += IKS_ID_SERVER;
	return diff;
}

ikspak *
iks_packet (iks *x)
{
	ikspak *pak;
	ikstack *s;
	char *tmp;

	s = iks_stack (x);
	pak = iks_stack_alloc (s, sizeof (ikspak));
	if (!pak) return NULL;
	memset (pak, 0, sizeof (ikspak));
	pak->x = x;
	tmp = iks_find_attrib (x, "from");
	if (tmp) pak->from = iks_id_new (s, tmp);
	pak->id = iks_find_attrib (x, "id");

	tmp = iks_find_attrib (x, "type");
	if (strcmp (iks_name (x), "message") == 0) {
		pak->type = IKS_PAK_MESSAGE;
		if (tmp) {
			if (strcmp (tmp, "chat") == 0)
				pak->subtype = IKS_TYPE_CHAT;
			else if (strcmp (tmp, "groupchat") == 0)
				pak->subtype = IKS_TYPE_GROUPCHAT;
			else if (strcmp (tmp, "headline") == 0)
				pak->subtype = IKS_TYPE_HEADLINE;
			else if (strcmp (tmp, "error") == 0)
				pak->subtype = IKS_TYPE_ERROR;
		}
	} else if (strcmp (iks_name (x), "presence") == 0) {
		pak->type = IKS_PAK_S10N;
		if (tmp) {
			if (strcmp (tmp, "unavailable") == 0) {
				pak->type = IKS_PAK_PRESENCE;
				pak->subtype = IKS_TYPE_UNAVAILABLE;
				pak->show = IKS_SHOW_UNAVAILABLE;
			} else if (strcmp (tmp, "probe") == 0) {
				pak->type = IKS_PAK_PRESENCE;
				pak->subtype = IKS_TYPE_PROBE;
			} else if(strcmp(tmp, "subscribe") == 0)
				pak->subtype = IKS_TYPE_SUBSCRIBE;
			else if(strcmp(tmp, "subscribed") == 0)
				pak->subtype = IKS_TYPE_SUBSCRIBED;
			else if(strcmp(tmp, "unsubscribe") == 0)
				pak->subtype = IKS_TYPE_UNSUBSCRIBE;
			else if(strcmp(tmp, "unsubscribed") == 0)
				pak->subtype = IKS_TYPE_UNSUBSCRIBED;
			else if(strcmp(tmp, "error") == 0)
				pak->subtype = IKS_TYPE_ERROR;
		} else {
			pak->type = IKS_PAK_PRESENCE;
			pak->subtype = IKS_TYPE_AVAILABLE;
			tmp = iks_find_cdata (x, "show");
			pak->show = IKS_SHOW_AVAILABLE;
			if (tmp) {
				if (strcmp (tmp, "chat") == 0)
					pak->show = IKS_SHOW_CHAT;
				else if (strcmp (tmp, "away") == 0)
					pak->show = IKS_SHOW_AWAY;
				else if (strcmp (tmp, "xa") == 0)
					pak->show = IKS_SHOW_XA;
				else if (strcmp (tmp, "dnd") == 0)
					pak->show = IKS_SHOW_DND;
			}
		}
	} else if (strcmp (iks_name (x), "iq") == 0) {
		iks *q;
		pak->type = IKS_PAK_IQ;
		if (tmp) {
			if (strcmp (tmp, "get") == 0)
				pak->subtype = IKS_TYPE_GET;
			else if (strcmp (tmp, "set") == 0)
				pak->subtype = IKS_TYPE_SET;
			else if (strcmp (tmp, "result") == 0)
				pak->subtype = IKS_TYPE_RESULT;
			else if (strcmp (tmp, "error") == 0)
				pak->subtype = IKS_TYPE_ERROR;
		}
		for (q = iks_child (x); q; q = iks_next (q)) {
			if (IKS_TAG == iks_type (q)) {
				char *ns;
				ns = iks_find_attrib (q, "xmlns");
				if (ns) {
					pak->query = q;
					pak->ns = ns;
					break;
				}
			}
		}
	}
	return pak;
}

iks *
iks_make_auth (iksid *id, const char *pass, const char *sid)
{
	iks *x, *y;

	x = iks_new ("iq");
	iks_insert_attrib (x, "type", "set");
	y = iks_insert (x, "query");
	iks_insert_attrib (y, "xmlns", IKS_NS_AUTH);
	iks_insert_cdata (iks_insert (y, "username"), id->user, 0);
	iks_insert_cdata (iks_insert (y, "resource"), id->resource, 0);
	if(sid) {
		char buf[41];
		iksha *sha;
		sha = iks_sha_new ();
		iks_sha_hash (sha, (const unsigned char*)sid, strlen (sid), 0);
		iks_sha_hash (sha, (const unsigned char*)pass, strlen (pass), 1);
		iks_sha_print (sha, buf);
		iks_sha_delete (sha);
		iks_insert_cdata (iks_insert (y, "digest"), buf, 40);
	} else {
		iks_insert_cdata (iks_insert (y, "password"), pass, 0);
	}
	return x;
}

iks *
iks_make_msg (enum iksubtype type, const char *to, const char *body)
{
	iks *x;
	char *t = NULL;

	x = iks_new ("message");
	switch (type) {
	case IKS_TYPE_CHAT: t = "chat"; break;
	case IKS_TYPE_GROUPCHAT: t = "groupchat"; break;
	case IKS_TYPE_HEADLINE: t = "headline"; break;
	default: break;
	}
	if (t) iks_insert_attrib (x, "type", t);
	if (to) iks_insert_attrib (x, "to", to);
	if (body) iks_insert_cdata (iks_insert (x, "body"), body, 0);
	return x;
}

iks *
iks_make_s10n (enum iksubtype type, const char *to, const char *msg)
{
	iks *x;
	char *t;

	x = iks_new ("presence");
	switch (type) {
		case IKS_TYPE_SUBSCRIBE: t = "subscribe"; break;
		case IKS_TYPE_SUBSCRIBED: t = "subscribed"; break;
		case IKS_TYPE_UNSUBSCRIBE: t = "unsubscribe"; break;
		case IKS_TYPE_UNSUBSCRIBED: t = "unsubscribed"; break;
		case IKS_TYPE_PROBE: t = "probe"; break;
		default: t = NULL; break;
	}
	if (t) iks_insert_attrib (x, "type", t);
	if (to) iks_insert_attrib (x, "to", to);
	if (msg) iks_insert_cdata(iks_insert (x, "status"), msg, 0);
	return x;
}

iks *
iks_make_pres (enum ikshowtype show, const char *status)
{
	iks *x;
	char *t;

	x = iks_new ("presence");
	switch (show) {
		case IKS_SHOW_CHAT: t = "chat"; break;
		case IKS_SHOW_AWAY: t = "away"; break;
		case IKS_SHOW_XA: t = "xa"; break;
		case IKS_SHOW_DND: t = "dnd"; break;
		case IKS_SHOW_UNAVAILABLE:
			t = NULL;
			iks_insert_attrib (x, "type", "unavailable");
			break;
		default: t = NULL; break;
	}
	if (t) iks_insert_cdata (iks_insert (x, "show"), t, 0);
	if (status) iks_insert_cdata(iks_insert (x, "status"), status, 0);
	return x;
}

iks *
iks_make_iq (enum iksubtype type, const char *xmlns)
{
	iks *x;
	char *t = NULL;

	x = iks_new ("iq");
	switch (type) {
	case IKS_TYPE_GET: t = "get"; break;
	case IKS_TYPE_SET: t = "set"; break;
	case IKS_TYPE_RESULT: t = "result"; break;
	case IKS_TYPE_ERROR: t = "error"; break;
	default: break;
	}
	if (t) iks_insert_attrib (x, "type", t);
	iks_insert_attrib (iks_insert (x, "query"), "xmlns", xmlns);

	return x;
}

iks *
iks_make_resource_bind (iksid *id)
{
	iks *x, *y, *z;

	x = iks_new("iq");
	iks_insert_attrib(x, "type", "set");
	y = iks_insert(x, "bind");
	iks_insert_attrib(y, "xmlns", IKS_NS_XMPP_BIND);
	if (id->resource && iks_strcmp(id->resource, "")) {
		z = iks_insert(y, "resource");
		iks_insert_cdata(z, id->resource, 0);
	}
	return x;
}

iks *
iks_make_session (void)
{
	iks *x, *y;

	x = iks_new ("iq");
	iks_insert_attrib (x, "type", "set");
	y = iks_insert (x, "session");
	iks_insert_attrib (y, "xmlns", IKS_NS_XMPP_SESSION);
	return x;
}

static int
iks_sasl_mechanisms (iks *x)
{
	int sasl_mech = 0;

	while (x) {
		if (!iks_strcmp(iks_cdata(iks_child(x)), "DIGEST-MD5"))
			sasl_mech |= IKS_STREAM_SASL_MD5;
		else if (!iks_strcmp(iks_cdata(iks_child(x)), "PLAIN"))
			sasl_mech |= IKS_STREAM_SASL_PLAIN;
		x = iks_next_tag(x);
	}
	return sasl_mech;
}

int
iks_stream_features (iks *x)
{
	int features = 0;

	if (iks_strcmp(iks_name(x), "stream:features"))
		return 0;
	for (x = iks_child(x); x; x = iks_next_tag(x))
		if (!iks_strcmp(iks_name(x), "starttls"))
			features |= IKS_STREAM_STARTTLS;
		else if (!iks_strcmp(iks_name(x), "bind"))
			features |= IKS_STREAM_BIND;
		else if (!iks_strcmp(iks_name(x), "session"))
			features |= IKS_STREAM_SESSION;
		else if (!iks_strcmp(iks_name(x), "mechanisms"))
			features |= iks_sasl_mechanisms(iks_child(x));
	return features;
}
