/*
 * Copyright (c) 2004-2009, Luiz Otavio O Souza <loos.br@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

static const char rcsid[] = "$Id: xml.c 112 2009-03-15 17:30:28Z loos-br $";

#include <string.h>

#include "msn-proxy.h"
#include "contacts.h"
#include "return.h"
#include "ctl.h"
#include "xml.h"
#include "io.h"

#define SIZE(s)		(sizeof(s)/sizeof(*s))

struct cmd_ {
    char	*name;
    int		(*func)(struct ctl_ *ctl, struct xml_tags *xml_tag_head);
} cmds[] = {
    { "loaduser",	loaduser }
};

void
printt(int t) {
    while(t-- > 0) io_printf(1, "\t");
}

void
xml_print(struct xml_tags *head, int depth) {
 struct xml_tag_opt_    *tag_opt;
 struct xml_tag_        *tag;

    SLIST_FOREACH(tag, head, xml_tag__) {
	printt(depth);
	io_printf(1, "name: [%s]\n", &tag->name);
	if (tag->data.len > 0) {
	    printt(depth);
	    io_printf(1, "data: [%s]\n", &tag->data);
	}
	SLIST_FOREACH(tag_opt, &tag->xml_tag_opt_head, xml_tag_opt__) {
	    printt(depth);
	    io_printf(1, "opt name: [%s = %s]\n", &tag_opt->name,
		      &tag_opt->value);
	}
	xml_print(&tag->xml_tag_head, depth + 1);
    }
}

void
xml_free(struct xml_tags *xml_tag_head) {
 struct xml_tag_opt_	*tag_opt = NULL;
 struct xml_tag_opt_	*opttmp = NULL;
 struct xml_tag_	*tag = NULL;
 struct xml_tag_	*tmp = NULL;

    for (tag = SLIST_FIRST(xml_tag_head); tag;) {

	tmp = SLIST_NEXT(tag, xml_tag__);

	str_free(&tag->name);
	str_free(&tag->data);

	for (tag_opt = SLIST_FIRST(&tag->xml_tag_opt_head); tag_opt; ) {

	    opttmp = SLIST_NEXT(tag_opt, xml_tag_opt__);

	    str_free(&tag_opt->name);
	    str_free(&tag_opt->value);
	    SLIST_REMOVE(&tag->xml_tag_opt_head, tag_opt, xml_tag_opt_, xml_tag_opt__);
	    free(tag_opt);
	    tag_opt = opttmp;
	}

	xml_free((struct xml_tags *)&tag->xml_tag_head);

	SLIST_REMOVE(xml_tag_head, tag, xml_tag_, xml_tag__);
	free(tag);
	tag = tmp;
    }
}

struct xml_tag_ *
xml_alloc_tag(void) {
 struct xml_tag_	*tag;

    tag = (struct xml_tag_ *)malloc(sizeof(struct xml_tag_));
    if (tag == NULL) exit(51);
    memset(tag, 0, sizeof(struct xml_tag_));
    SLIST_INIT(&tag->xml_tag_opt_head);
    SLIST_INIT(&tag->xml_tag_head);
    return(tag);
}

void
xml_trim(string *s) {
    if (s == NULL || s->len == 0 || s->s == NULL) return;
    while (s->len > 0) {
	switch (*s->s) {
	    case '\r':
	    case '\n':
 	    case '\t':
	    case ' ':
		s->len--; s->s++; continue;
	}
	break;
    }
}

int
xml_parse_tag_opt(struct xml_tag_ *tag, string *in) {
 struct xml_tag_opt_	*tag_opt = NULL;
 int			quotes	 = 0;
 int			name	 = 1;

    while (in->len > 0) {

	if (in->len > 1 && *in->s == '/' && *(in->s + 1) == '>') {
	    in->len -= 2; in->s += 2; return(RCLOSE);
	}
	if (*in->s == '>') { in->len--; in->s++; return(REND); }
	if ((*in->s == '"' && quotes == 1) ||
	    (*in->s == ' ' && quotes == 0)) {
	    in->len--; in->s++;
	    while (in->len > 0) {
		if (*in->s != ' ' && *in->s != '\t') break;
		in->len--; in->s++;
	    }
	    break;
	}
	if (*in->s == '=') {
	    in->len--; in->s++; name = 0;
	    if (in->len > 0 && *in->s == '"') {
		in->len--; in->s++; quotes = 1;
	    }
	    continue;
	}
	if (tag_opt == NULL) {
	    tag_opt = (struct xml_tag_opt_ *)malloc(sizeof(struct xml_tag_opt_));
	    memset(tag_opt, 0, sizeof(struct xml_tag_opt_));
	}
	if (tag_opt == NULL) exit(51);

	if (name == 1) {
	    if (str_cat(&tag_opt->name, in->s, 1) == 0) exit(51);
	} else {
	    if (str_cat(&tag_opt->value, in->s, 1) == 0) exit(51);
	}
	in->len--; in->s++;
    }

    SLIST_INSERT_HEAD(&tag->xml_tag_opt_head, tag_opt, xml_tag_opt__);
    return(ROK);
}

int
xml_parse_tag(struct xml_tags *head, struct xml_tag_ *up, string *in) {
 struct xml_tag_	*tag;
 string			vrfy;
 int			close = 0;
 int			end = 0;
 int			err;

    if (in->len > 0 && *in->s == '?') {
	while (in->len > 0) {
	    if (*in->s == '>') {
		in->len--; in->s++; break;
	    }
	    in->len--; in->s++;
	}
	return(ROK);
    }

    /* alloc new tag */
    tag = xml_alloc_tag();

    /* read tag name */
    while (in->len > 0) {

	/* end - read data */
	if (*in->s == '>') { in->len--; in->s++; break; }
	if (in->len > 1 && *in->s == '/' && *(in->s + 1) == '>') {
	    in->len -= 2; in->s += 2; end = 2; break;
	}

	/* read options */
	if (*in->s == ' ') {
	    in->len--; in->s++;
	    for (;;) {
		err = xml_parse_tag_opt(tag, in);
		if (err == RCLOSE) { end = 2; break; }
		if (err == RFAIL) return(RFAIL);
		if (err == REND) break;
	    }
	    break;
	}

	/* save tag name */
	if (str_cat(&tag->name, in->s, 1) == 0) exit(51);
	in->len--;
	in->s++;
    }

    xml_trim(in);

    /* data */
    while (end == 0 && in->len > 0) {

	/* end of data */
	if (*in->s == '<') {
	    in->len--; in->s++;
	    if (in->len > 0 && *in->s == '/') {
		in->len--; in->s++; end = 1;
	    }
	    break;
	}

	if (str_cat(&tag->data, in->s, 1) == 0) exit(51);
	in->len--; in->s++;
    }

    for (;;) {

	if (end == 2) break;
	if (in->len > 0 && end == 0) {
	    err = xml_parse_tag((struct xml_tags *)&tag->xml_tag_head, tag, in);
	    if (err == RFAIL) return(RFAIL);
	    if (err == RCLOSE) { end = 2; break; }
	    xml_trim(in);
	}
	if (in->len > 0 && *in->s == '<') {
	    in->len--; in->s++;
	    if (in->len > 0 && *in->s == '/') {
		in->len--; in->s++; end = 1;
	    }
	} else
	    break;
    }

    str_zero(&vrfy);
    while (end == 1 && in->len > 0) {

	/* end of tag */
	if (*in->s == '>') { in->len--; in->s++; break; }
	if (str_cat(&vrfy, in->s, 1) == 0) exit(51);
	in->len--; in->s++;
    }

    if (end == 1 && up && up->name.len > 0 && vrfy.len > 0 &&
		strcmp((char *)up->name.s, (char *)vrfy.s) == 0) {
	close = 1;
    } else if (end == 1 && (tag->name.len == 0 || vrfy.len == 0 ||
		strcmp((char *)tag->name.s, (char *)vrfy.s) != 0)) {
	io_printf(1, "tag: [%s] vrfy: [%s]\n", &tag->name, &vrfy);
	io_printf(1, "fail to close tag\n");
	return(RFAIL);
    }

    xml_trim(in);

    SLIST_INSERT_HEAD(head, tag, xml_tag__);

    str_free(&vrfy);

    if (close == 1)
	return(RCLOSE);
    return(ROK);
}

int
xml_parse(struct xml_tags *xml_tag_head, string *in) {
 string         buf;

    if (in == NULL || in->len == 0 || in->s == NULL)
        return(RFAIL);

    /* reset tags tree */
    SLIST_INIT(xml_tag_head);

    str_zero(&buf);
    buf.len = in->len;
    buf.s   = in->s;
    while (buf.len > 0) {
	if (*buf.s == '<') {
	    buf.len--;
	    buf.s++;
	    if (xml_parse_tag(xml_tag_head, NULL, &buf) == RFAIL)
		return(RFAIL);
	    continue;
	}
	buf.len--;
	buf.s++;
    }
    return(ROK);
}

int
xml_encode(unsigned char *in, string *out) {
 unsigned char		byte;

    str_zero(out);
    while (*in) {
	if (*in == '&') {
	    if (str_cats(out, (const unsigned char *)"&amp;") == 0) exit(51);
	} else if (*in == '<') {
	    if (str_cats(out, (const unsigned char *)"&lt;") == 0) exit(51);
	} else if (*in == '>') {
	    if (str_cats(out, (const unsigned char *)"&gt;") == 0) exit(51);
	} else if (*in >= 32 && *in <= 126) {
	    if (str_cat(out, in, 1) == 0) exit(51);
	} else {
	    /* utf8 encode */

	    /* first utf8 byte */
	    byte = 0xc0;
	    byte |= (*in >> 6) & 0x03;
	    if (str_cat(out, &byte, 1) == 0) exit(51);

	    /* second and last - yes this is a bogus utf8 encode - utf8 byte */
	    byte = 0x80;
	    byte |= *in & 0x3f;
	    if (str_cat(out, &byte, 1) == 0) exit(51);
	}
	++in;
    }
    return(ROK);
}

int
xml_decode(string *in, string *out) {
 unsigned char		byte;
 string			buf;

    str_zero(out);
    str_zero(&buf);
    buf.s = in->s;
    buf.len = in->len;
    while (buf.len > 0) {
	if (*buf.s == '&') {
	    if (buf.len >= 5 && strncasecmp((char *)buf.s, "&amp;", 5) == 0) {
		if (str_cats(out, (const unsigned char *)"&") == 0) exit(51);
		buf.len -= 5; buf.s += 5; continue;
	    } else if (buf.len >= 4 && strncasecmp((char *)buf.s, "&lt;", 4) == 0) {
		if (str_cats(out, (const unsigned char *)"<") == 0) exit(51);
		buf.len -= 4; buf.s += 4; continue;
	    } else if (buf.len >= 4 && strncasecmp((char *)buf.s, "&gt;", 4) == 0) {
		if (str_cats(out, (const unsigned char *)">") == 0) exit(51);
		buf.len -= 4; buf.s += 4; continue;
	    }
	    if (str_cats(out, (const unsigned char *)"&") == 0) exit(51);
	    --buf.len; ++buf.s;

	} else if ((*buf.s & 0xe0) == 0xc0 && buf.len >= 2) {

	    /* UTF8 encoded char */
	    byte = (*buf.s & 0x1f) << 6;
	    --buf.len; ++buf.s;

	    byte |= (*buf.s & 0x3f);
	    --buf.len; ++buf.s;

	    if (str_cat(out, &byte, 1) == 0) exit(51);

	} else {
	    if (str_cat(out, buf.s, 1) == 0) exit(51);
	    --buf.len; ++buf.s;
	}
    }
    return(ROK);
}

int
xml_run(struct xml_tags *xml_tag_head, struct ctl_ *ctl) {
 struct xml_tag_opt_	*tag_opt;
 struct xml_tag_	*msn_proxy;
 struct xml_tag_	*cmd;
 int			cmdtype;
 int			i;

    /* check for msn-proxy class */
    msn_proxy = SLIST_FIRST(xml_tag_head);
    if (!msn_proxy ||
	msn_proxy->name.len == 0 ||
	strcmp((char *)msn_proxy->name.s, "msn-proxy") != 0) {

	return(RFAIL);
    }

    /* check for command */
    SLIST_FOREACH(cmd, &msn_proxy->xml_tag_head, xml_tag__) {

	if (cmd->name.len == 0)
	    continue;

	if (strcmp((char *)cmd->name.s, "command") != 0)
	    continue;

	/* check for command type */
	cmdtype = UNKNOWN;
	SLIST_FOREACH(tag_opt, &cmd->xml_tag_opt_head, xml_tag_opt__) {

	    if (tag_opt->name.len == 0 || tag_opt->value.len == 0)
		continue;

	    if (strcasecmp((char *)tag_opt->name.s, "type") != 0)
		continue;

	    for (i = 0; i < SIZE(cmds); i++) {
		if (strcmp(cmds[i].name, (char *)tag_opt->value.s) == 0)
		    return (cmds[i].func(ctl, &cmd->xml_tag_head));
	    }
	}
    }
    return(ROK);
}

