/*
 * 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: msg.c 102 2009-02-05 18:37:40Z loos-br $";

#include <string.h>

#include "msg.h"
#include "sql.h"
#include "return.h"
#include "protocol.h"
#include "msn-proxy.h"
#include "array_cmd.h"

void
msg_zero(msg_ *msg) {
    str_zero(&msg->body);
    msg->header_len  = 0;
    msg->header_last = (header_ *)0;
    msg->header      = (header_ *)0;
}

void
msg_add_header(msg_ *msg, header_ *header) {
    msg->header_len++;
    if (msg->header_last)
	msg->header_last->next = header;
    else
	msg->header = header;
    msg->header_last = header;
}

int
msg_header_find(msg_ *msg, const char *header, string *value) {
 header_	*tmp;

    for (tmp = msg->header; tmp; tmp = tmp->next) {
	if (tmp->header.len > 0 &&
	    strcasecmp((char *)tmp->header.s, header) == 0) {
	    if (str_copy(value, tmp->value.s, tmp->value.len) == 0)
		die_nomem();
	    return(ROK);
	}
    }
    return(ROK);
}

int
msn_msg_type(msg_ *msg) {
 string		content_type;
 int		type = UNKNOWN;

    str_zero(&content_type);

    if (msg_header_find(msg, "content-type", &content_type) == RFAIL)
	return(RFAIL);

    if (content_type.len == 0)
	return(ROK);

    if (strcasecmp((char *)content_type.s, "text/x-msmsgscontrol") == 0)
	type = TYPE_MSGSCONTROL;
    else if (strcasecmp((char *)content_type.s, "application/x-msnmsgrp2p") == 0)
	type = TYPE_P2P;
    else if (strcasecmp((char *)content_type.s, "text/plain; charset=UTF-8") == 0)
	type = TYPE_TEXTUTF8;
    else if (strcasecmp((char *)content_type.s, "text/x-clientcaps") == 0)
	type = TYPE_CLIENTCAPS;
    else if (strcasecmp((char *)content_type.s, "text/x-msnmsgr-datacast") == 0)
	type = TYPE_DATACAST;
    else if (strcasecmp((char *)content_type.s, "text/x-mms-animemoticon") == 0)
	type = TYPE_EMOTICON;
    else if (strcasecmp((char *)content_type.s, "text/x-mms-emoticon") == 0)
	type = TYPE_EMOTICON;
    else if (strcasecmp((char *)content_type.s, "text/x-msmsgsinvite; charset=UTF-8") == 0)
	type = TYPE_FILETRANSF;
    else if (strcasecmp((char *)content_type.s, "text/x-keepalive") == 0)
	type = TYPE_KEEPALIVE;

    str_free(&content_type);
    return(type);
}

void
msg_free(msg_ *msg) {
 header_	*tmp;

    while (msg->header_len > 0) {

	/* save pointer */
	tmp = msg->header;

	/* skip root pointer */
	msg->header = msg->header->next;

	/* update last */
	if (msg->header == (header_ *)0)
	    msg->header_last = msg->header;

	/* release memory */
	str_free(&tmp->header);
	str_free(&tmp->value);
	free(tmp);

	msg->header_len--;
    }
}

header_ *
header_alloc(void) {
 header_		*header;

    header = (header_ *)malloc(sizeof(header_));
    if (!header)
	die_nomem();

    header->next = (header_ *)0;
    str_zero(&header->header);
    str_zero(&header->value);
    return(header);
}

int
msn_msg_header_decode(msg_ *msg, string *payload) {
 header_	*header = (header_ *)0;
 int		state;

    state = HEADER;
    msg->body.len = payload->len;
    msg->body.s   = payload->s;
    while (msg->body.len > 0) {

        /* ignore '\r' */
        if (*msg->body.s == '\r') {
            msg->body.len -= 1;
            msg->body.s   += 1;
            continue;
        }

	/* start of value */
	if (*msg->body.s == ':' && state == HEADER) {
	    state = START_VALUE;
	    msg->body.len -= 1;
	    msg->body.s   += 1;
	    continue;
	}

	if (*msg->body.s == '\n' && state == HEADER) {
	    msg->body.len--;
	    msg->body.s++;
	    state = BODY;
	    break;
	}

	if (*msg->body.s == '\n' && state == VALUE) {

	    /* check for header */
	    if (header == (header_ *)0)
		return(RFAIL);

	    /* save header */
	    msg_add_header(msg, header);
	    header = (header_ *)0;
	    state = HEADER;

	    msg->body.len -= 1;
	    msg->body.s   += 1;
	    continue;
	}

	if (state == START_VALUE) {
	    if (*msg->body.s == ' ' || *msg->body.s == '\t') {
		msg->body.len -= 1;
		msg->body.s   += 1;
		continue;
	    } else
		state = VALUE;
	}

	/* alloc new header */
	if (header == (header_ *)0)
	    header = header_alloc();

	if (state == HEADER) {
	   if (str_cat(&header->header, msg->body.s, 1) == 0)
		die_nomem();
	} else if (state == VALUE) {
	   if (str_cat(&header->value, msg->body.s, 1) == 0)
		die_nomem();
	}
	msg->body.len -= 1;
	msg->body.s   += 1;
    }

    if (state != BODY)
	return(RFAIL);

    return(ROK);
}

int
msn_msg_decode(struct sb_ *sb, command *cmd, int type) {
 struct user_	*user = sb->user;
 log_		*log  = &config.log;
 msg_		msg;

    /* zero message info */
    msg_zero(&msg);

    if (msn_msg_header_decode(&msg, &cmd->payload) == RFAIL)
	goto error;

    /* check message type */
    switch (msn_msg_type(&msg)) {

	case TYPE_TEXTUTF8:

	    if ((user->commands & CHAT)) {
		msg_free(&msg);
		return(ROK);
	    }

	    if (user->commands & SAVE_MSG) {
		msg_free(&msg);
		return(ROK);
	    }

	    if ((user->commands & LOGWARNING) && sb->warned == NO) {
		msn_warning_msg_client(sb);
		sb->warned = YES;
	    }

	    if (msg_decode_utf8(sb, cmd, &msg, type) == RFAIL)
		goto error;

	    msg_free(&msg);
	    return(ROK);

	case TYPE_CLIENTCAPS:

	    msg_free(&msg);
	    return(ROK);

	case TYPE_P2P:

//	    msg_read_p2p_header(sb, &msg);
	    /*
		p2p = p2p_msg(sb, &msg);
		switch(p2p->type) {
		    case INVITE_BUDY:
		    case INVITE:
		    case ACCEPT:
		    case DECLINE:
		    case ACK:
	    */
	    msg_free(&msg);
	    if ((user->commands & P2P))
		return(RFAIL);
	    return(ROK);

	case TYPE_MSGSCONTROL:
	    msg_free(&msg);
	    return(ROK);

	case TYPE_DATACAST:
	    msg_free(&msg);
	    if ((user->commands & DATACAST))
		return(RFAIL);
	    return(ROK);

        case TYPE_EMOTICON:
	    msg_free(&msg);
	    if ((user->commands & EMOTICON))
		return(RFAIL);
	    return(ROK);

	case TYPE_FILETRANSF:
	    msg_free(&msg);
	    if ((user->commands & FILETRANSF))
		return(RFAIL);
	    return(ROK);

	case RFAIL:
	    goto error;

	case UNKNOWN:
	    log->debug("debug: unknown message type\n");
	    /* fallthrough */
	default:
	    msg_free(&msg);
	    return(ROK);
    }

error:
    msg_free(&msg);
    return(RFAIL);
}

int
msg_decode_utf8(struct sb_ *sb, command *cmd, msg_ *msg, int type) {
 struct contact_	*contact, *save, f;
 struct sb_user_	*sb_user;
 struct user_		*user = sb->user;
 string			dn;
 string			to;
 string			tmp;
 string			*dn_;
 string			*email;
 int			to_start;

    to_start = 0;
    str_zero(&tmp);
    str_zero(&dn);
    str_zero(&to);

    if (cmd->args_len < 1)
	return(ROK);
    if (msg->body.len == 0)
	return(ROK);

    if (msn_decode(&msg->body, &tmp) == RFAIL)
	return(RFAIL);

    email = cmd->args[0];
    SLIST_FOREACH(sb_user, &sb->sb_users, sb_user__) {

	if (sb_user->email.len == 0) continue;

	if (type == SERVER && email && email->len > 0 &&
	    strcmp((char *)sb_user->email.s, (char *)email->s) == 0) {
	    if (to_start && str_cats(&to, (unsigned char *)", ") == 0)
		die_nomem();
	    if (user->dn.len > 0 &&
		str_cat(&to, user->dn.s, user->dn.len) == 0)
		die_nomem();
	    if (str_cats(&to, (unsigned char *)" (") == 0)
		die_nomem();
	    if (user->email.len > 0 &&
		str_cat(&to, user->email.s, user->email.len) == 0)
		die_nomem();
	    if (str_cats(&to, (unsigned char *)")") == 0)
		die_nomem();

	    if (!to_start) to_start = 1;

	} else {

	    memset(&f, 0, sizeof(f));
	    if (str_copy(&f.c, sb_user->email.s, sb_user->email.len) == 0)
		die_nomem();

	    contact = RB_FIND(contacts, &user->contacts, &f);
	    contact_free(&f);

	    if (contact && contact->chat != YES) {

		save = contact_update(user, &contact->c);
		if (save) {
		    save->chat = YES;
		    (void)sql_contact_save(user, save);
		}
	    }

	    if (to_start && str_cats(&to, (unsigned char *)", ") == 0)
		die_nomem();
	    if (contact && contact->dn.len > 0 &&
		str_cat(&to, contact->dn.s, contact->dn.len) == 0)
		die_nomem();
	    if (str_cats(&to, (unsigned char *)" (") == 0)
		die_nomem();
	    if (sb_user->email.len > 0 &&
		str_cat(&to, sb_user->email.s, sb_user->email.len) == 0)
		die_nomem();
	    if (str_cats(&to, (unsigned char *)")") == 0)
		die_nomem();

	    if (!to_start) to_start = 1;
	}
    }

    switch (type) {

	case SERVER:

	    dn_   = cmd->args[1];
	    if (cmd->args_len < 2) {
		str_free(&to);
		return(RFAIL);
	    }

	    if (msn_decode(dn_, &dn) == RFAIL) {
		str_free(&tmp);
		str_free(&to);
		return(RFAIL);
	    }

	    if (sql_log_msg(sb->id, email, &dn, &to, &tmp) == RFAIL) {
		str_free(&tmp);
		str_free(&dn);
		str_free(&to);
		return(RFAIL);
	    }
	    break;

	case CLIENT:

	    if (sql_log_msg(sb->id, &user->email, &user->dn, &to, &tmp) == RFAIL) {
		str_free(&tmp);
		str_free(&to);
		return(RFAIL);
	    }
	    break;
    }

    str_free(&tmp);
    str_free(&dn);
    str_free(&to);

    return(ROK);
}

int
msn_sb_server_msg(struct sb_ *sb, command *cmd, int args) {
    return(msn_msg_decode(sb, cmd, SERVER));
}

int
msn_sb_client_msg(struct sb_ *sb, command *cmd, int args) {
    return(msn_msg_decode(sb, cmd, CLIENT));
}

