/*
 * ImapProxy - a caching IMAP proxy daemon
 * Copyright (C) 2002 Steven Van Acker
 * 
 * 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.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#define _GNU_SOURCE
#include <string.h>
#include <parsed_message.h>
#include <misc.h>
#include <output.h>

int init_parsed_message(Parsed_message *pm)
{
    memset(pm,0,sizeof(Parsed_message));
    return 0;
}

#define PARSE_MESSAGE_MODE_NONE		0
#define PARSE_MESSAGE_MODE_NORMAL	1
#define PARSE_MESSAGE_MODE_QUOTED	2
#define PARSE_MESSAGE_MODE_LITERAL	3

int make_parsed_message(struct message_t *m,Parsed_message *pm)
{
    int mode = 0;
    char *p,*q,*s, buffer[1024];
    int end;
    int tmpi;
    
    int currindex = 0;
    
    if(m->count < 1)
	/* empty message ? */
	return -1;
   
    p = m->lines[currindex];
    q = p;
    
    mode = PARSE_MESSAGE_MODE_NONE;
   
    while(p && *p)
    {
	switch(mode)
	{
	    case PARSE_MESSAGE_MODE_NONE:
		/* if we don't know what to do, start cleaning up the spaces */
		while(*p == ' ')
		    p++;
		
		/* the new string will start here */
		q = p;
		
		switch(*p)
		{
		    case '\r':
		    case '\n':
			p++;
		    case '\0':
			/* reached end of string ! end loop */
			break;
		    case '"':
			mode = PARSE_MESSAGE_MODE_QUOTED;
			break;
		    case '{':
			mode = PARSE_MESSAGE_MODE_LITERAL;
			break;
		    default:
			mode = PARSE_MESSAGE_MODE_NORMAL;
			break;
		}
		
		break;
	    case PARSE_MESSAGE_MODE_NORMAL:
		/* in normal mode, we look for spaces. */
		if(!(p = strchr(p,' ')))
		{
		    /* let p point to end of string otherwise */
		    p = q + strlen(q);

		    /* string should end with \r\n, but lets not take risks */
		    if(strlen(q) > 2 && *(p-1) == '\n' && *(p-2) == '\r') p-=2;
		}

		/* add this argument to the list and mark it as normal ! */
	
		if(parsed_message_add_arg(pm,q,p,PARSED_MESSAGE_NORMAL_STRING) < 0)
		{
		    debug("make_parsed_message(): Adding argument failed.\n");
		    return -1;
		}
		
		mode = PARSE_MESSAGE_MODE_NONE;
		break;
		
	    case PARSE_MESSAGE_MODE_QUOTED:
		end = 0;
		
		/* skip the first " */
		p++;
		
		while(*p && !end)
		{
		    switch(*p)
		    {
			case '\\':
			    /* skip all quoted stuff */
			    p+=2;
			    break;
			case '"':
			    /* aha, the end of our misery */
			    p++;
			    end = 1;
			    break;
			default:
			    p++;
			    break;
		    }
		}
	
		if(!end) /* abnormal end */
		{
		    debug("make_parsed_message(): received a quoted string that was not terminated !\n");
		    return -1;
		}
	
		/* unquote the argument ! */

		if(!(s = my_strndup(q,(int)(p-q))))
		{
		    debug("make_parsed_message(): Out of memory ?\n");
		    return -1;
		}
	
		if(string_unquote(s,buffer,sizeof(buffer)))
		{
		    debug("make_parsed_message(): Unquoting failed\n");
		    return -1;
		}
		
		/* add this argument to the list and mark it as quoted. */

		if(parsed_message_add_arg(pm,buffer,buffer + strlen(buffer),PARSED_MESSAGE_QUOTED_STRING) < 0)
		{
		    debug("make_parsed_message(): Adding argument failed.\n");
		    return -1;
		}

		/* and clean up */
		free(s);
		
		mode = PARSE_MESSAGE_MODE_NONE;
		break;
		
	    case PARSE_MESSAGE_MODE_LITERAL:
		if((tmpi = check_for_literal_count(p)) < 0)
		{
		    debug("make_parsed_message(): a sneaky attempt at a literal. handling it as being a regular string !\n");
		    mode = PARSE_MESSAGE_MODE_NORMAL;
		}
		else
		{
		    /* read tmpi chars from the next line, if there are so many
		     * add them to the list as a literal.
		     */

		    currindex++;
		    
		    if(currindex >= m->count)
		    {
			debug("make_parsed_message(): a literal was found, but there is no more data !!\n");
			return -1;
		    }
		   
		    q = m->lines[currindex];
		    p = q + tmpi;
		    
		    if(parsed_message_add_arg(pm,q,p,PARSED_MESSAGE_LITERAL_STRING) < 0)
		    {
			debug("make_parsed_message(): Adding argument failed.\n");
			return -1;
		    }
		    
		    mode = PARSE_MESSAGE_MODE_NONE;
		}

		break;
	    default:
		debug("huh ?\n");
		break;
	}
    }
    
    return -1;
}

int delete_parsed_message(Parsed_message *pm)
{
    int i;

    for(i = 0;i < pm->numargs; i++)
    {
	free(pm->args[i]);
    }

    if(pm->numargs) 
	free(pm->args);
    if(pm->argsdesc) 
	free(pm->argsdesc);

    init_parsed_message(pm);
    return 0;
}

int parsed_message_count_args(Parsed_message *pm)
{
    return pm->numargs;
}

char *parsed_message_get_idtag(Parsed_message *pm)
{
    if(pm->numargs > 0)
	return pm->args[0];
    else 
	return NULL;
}

char *parsed_message_get_command(Parsed_message *pm)
{
    if(pm->numargs > 1)
	return pm->args[1];
    else
	return NULL;
}

char *parsed_message_get_arg(Parsed_message *pm,int i)
{
    if(i < 2 || i > pm->numargs)
	return NULL;

    return pm->args[i];
}

/* this function quotes a string
 * out_len represents the length of the output buffer
 */
int string_quote(char *in, char *out, int out_len)
{
    int i = 0;
    int o = 0;
    int in_len = strlen(in) + 1;

    out[o++] = '"';
    while(i < in_len && o < out_len - 2)
    {
	switch(in[i])
	{
	    case '\\':
		out[o++] = '\\';
		out[o++] = '\\';
		i++;
		break;
	    case '"':
		out[o++] = '\\';
		out[o++] = '"';
		i++;
		break;
	    default:
		out[o++] = in[i++];
		break;
	}
    }

    if(i != in_len)
    {
	/* didn't get to end of the input string, which means the output buffer is too small */
	return -1;
    }

    out[o++] = '"';
    out[o] = '\0';

    return 0;
}

/* this function unquotes a quoted string.
 * out_len represents the length of the output buffer
 */
int string_unquote(char *in, char *out, int out_len)
{
    /* start after the first " */
    int i = 1;
    int o = 0;

    /* we don't want the last " */
    int in_len = strlen(in) - 1;

    if(in[0] != '"' || in[strlen(in) - 1] != '"')
    {
	debug("string_unquote(): This does not look like a quoted string !\n");
    }
    else
    {
	
    }
    
    while(i < in_len && o < out_len - 1)
    {
        switch(in[i])
        {
            case '\\':
		i++;
            default:
                out[o++] = in[i++];
                break;
        }
    }

    if(i != in_len)
    {
        /* didn't get to end of the input string, which means the output buffer is too small */
        return -1;
    }

    out[o] = '\0';

    return 0;
}
   
/* end needs to point to char after the char you want last */
int parsed_message_add_arg(Parsed_message *pm, char *start, char *end,int type)
{
    int n = 0;
    char *p = NULL;
    
    pm->numargs++;
    
    /* allocating space for the pointer to the argument */
    if(!(pm->args = realloc(pm->args,pm->numargs * sizeof(char *))))
    {
	debug("parsed_message_add_arg(): Out of memory !\n");
	return -1;
    }
    
    /* space for the type */
    if(!(pm->argsdesc = realloc(pm->argsdesc,pm->numargs * sizeof(int))))
    {
	debug("parsed_message_add_arg(): Out of memory (%d)!\n",pm->numargs * sizeof(int));
	return -1;
    }

    n = (int)(end - start) + 1;

    /* space for the argument */
    if(!(p = (char *)malloc(n)))
    {
	debug("parsed_message_add_arg(): Out of memory (%d)!\n",n);
	return -1;
    }

    /* copying the argument */
    strncpy(p,start,n - 1);
    p[n - 1] = 0;

    pm->args[pm->numargs - 1] = p;
    pm->argsdesc[pm->numargs - 1] = type;

    return 0;
}

void print_parsed_message(Parsed_message *pm)
{
    int i;
    char *desc;
    
    for(i = 0;i < pm->numargs;i++)
    {
	switch(pm->argsdesc[i])
	{
	    case PARSED_MESSAGE_NORMAL_STRING:
		desc = "PARSED_MESSAGE_NORMAL_STRING";
		break;
	    case PARSED_MESSAGE_QUOTED_STRING:
		desc = "PARSED_MESSAGE_QUOTED_STRING";
		break;
	    case PARSED_MESSAGE_LITERAL_STRING:
		desc = "PARSED_MESSAGE_LITERAL_STRING";
		break;
	    default:
		desc = "UNKNOWN_TYPE";
		break;
	}

	debug("Parsed message argument[%d] (%s) = \"%s\"\n",i,desc,pm->args[i]);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1