/*
 Copyright (C) 1999-2004 IC & S  dbmail@ic-s.nl

 Modified by Mikhail Ramendik mr@ramendik.ru (MR)

 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/* $Id: imaputil.c 1949 2005-12-30 09:41:56Z paul $
 * 
 * imaputil.c
 *
 * IMAP-server utility functions implementations
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include "imaputil.h"
#include "imap4.h"
#include "debug.h"
#include "db.h"
#include "memblock.h"
#include "dbsearch.h"
#include "rfcmsg.h"

#define BUFLEN 2048
#define SEND_BUF_SIZE 1024
#define MAX_ARGS 512

/* cache */
extern cache_t cached_msg;

extern const char AcceptedChars[];
extern const char AcceptedTagChars[];
extern const char AcceptedMailboxnameChars[];
extern const char *month_desc[];

static const char *search_cost[] = { "b","b","c","c","c","d","d","d","d","c","e","e","b","b","j","j","j" };

char base64encodestring[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/* returned by date_sql2imap() */
#define IMAP_STANDARD_DATE "Sat, 03-Nov-1979 00:00:00 +0000"
char _imapdate[IMAP_INTERNALDATE_LEN] = IMAP_STANDARD_DATE;

/* returned by date_imap2sql() */
#define SQL_STANDARD_DATE "1979-11-03 00:00:00"
char _sqldate[SQL_INTERNALDATE_LEN + 1] = SQL_STANDARD_DATE;


const int month_len[] = {
	31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

const char *item_desc[] = {
	"TEXT", "HEADER", "MIME", "HEADER.FIELDS", "HEADER.FIELDS.NOT"
};

const char *envelope_items[] = {
	"from", "sender", "reply-to", "to", "cc", "bcc", NULL
};

/* 
 * retrieve_structure()
 *
 * retrieves the MIME-IMB structure of a message. The msg should be in the format
 * as build by db_fetch_headers().
 *
 * shows extension data if show_extension_data != 0
 *
 * returns -1 on error, 0 on success
 */
int retrieve_structure(FILE * outstream, mime_message_t * msg,
		       int show_extension_data)
{
	struct mime_record *mr;
	struct element *curr;
	struct list *header_to_use;
	mime_message_t rfcmsg;
	char *subtype, *extension, *newline;
	int is_mime_multipart = 0, is_rfc_multipart = 0;
	int rfc822 = 0;

	fprintf(outstream, "(");

	mime_findfield("content-type", &msg->mimeheader, &mr);
	is_mime_multipart = (mr
			     && strncasecmp(mr->value, "multipart",
					    strlen("multipart")) == 0
			     && !msg->message_has_errors);

	mime_findfield("content-type", &msg->rfcheader, &mr);
	is_rfc_multipart = (mr
			    && strncasecmp(mr->value, "multipart",
					   strlen("multipart")) == 0
			    && !msg->message_has_errors);

	/* eddy */
	if (mr
	    && strncasecmp(mr->value, "message/rfc822",
			   strlen("message/rfc822")) == 0) {
		rfc822 = 1;
	}


	if (rfc822 || (!is_rfc_multipart && !is_mime_multipart)) {
		/* show basic fields:
		 * content-type, content-subtype, (parameter list), 
		 * content-id, content-description, content-transfer-encoding,
		 * size
		 */

		if (msg->mimeheader.start == NULL)
			header_to_use = &msg->rfcheader;	/* we're dealing with a single-part RFC msg here */
		else
			header_to_use = &msg->mimeheader;	/* we're dealing with a pure-MIME header here */

		mime_findfield("content-type", header_to_use, &mr);
		if (mr && strlen(mr->value) > 0)
			show_mime_parameter_list(outstream, mr, 1, 0);
		else
			fprintf(outstream, "\"TEXT\" \"PLAIN\" (\"CHARSET\" \"US-ASCII\")");	/* default */

		mime_findfield("content-id", header_to_use, &mr);
		if (mr && strlen(mr->value) > 0) {
			fprintf(outstream, " ");
			quoted_string_out(outstream, mr->value);
		} else
			fprintf(outstream, " NIL");

		mime_findfield("content-description", header_to_use, &mr);
		if (mr && strlen(mr->value) > 0) {
			fprintf(outstream, " ");
			quoted_string_out(outstream, mr->value);
		} else
			fprintf(outstream, " NIL");

		mime_findfield("content-transfer-encoding", header_to_use,
			       &mr);
		if (mr && strlen(mr->value) > 0) {
			fprintf(outstream, " ");
			quoted_string_out(outstream, mr->value);
		} else
			fprintf(outstream, " \"7BIT\"");

		/* now output size */
		/* add msg->bodylines because \n is dumped as \r\n */
		if (msg->mimeheader.start && msg->rfcheader.start)
			fprintf(outstream, " %llu",
				msg->bodysize + msg->mimerfclines +
				msg->rfcheadersize - msg->rfcheaderlines);
		else
			fprintf(outstream, " %llu",
				msg->bodysize + msg->bodylines);


		/* now check special cases, first case: message/rfc822 */
		mime_findfield("content-type", header_to_use, &mr);
		if (mr
		    && strncasecmp(mr->value, "message/rfc822",
				   strlen("message/rfc822")) == 0
		    && header_to_use != &msg->rfcheader) {
			/* msg/rfc822 found; extra items to be displayed:
			 * (a) body envelope of rfc822 msg
			 * (b) body structure of rfc822 msg
			 * (c) msg size (lines)
			 */

			if (retrieve_envelope(outstream, &msg->rfcheader)
			    == -1)
				return -1;

			fprintf(outstream, " ");

			memmove(&rfcmsg, msg, sizeof(rfcmsg));
			rfcmsg.mimeheader.start = NULL;	/* forget MIME-part */
			
			if (retrieve_structure
			    (outstream, &rfcmsg,
			     show_extension_data) == -1)
				return -1;

			/* output # of lines */
			fprintf(outstream, " %llu", msg->bodylines);
		}
		/* now check second special case: text 
		 * NOTE: if 'content-type' is absent, TEXT is assumed 
		 */
		if ((mr
		     && strncasecmp(mr->value, "text",
				    strlen("text")) == 0) || !mr) {
			/* output # of lines */
			if (msg->mimeheader.start && msg->rfcheader.start)
				fprintf(outstream, " %llu",
					msg->mimerfclines);
			else
				fprintf(outstream, " %llu", msg->bodylines);
		}

		if (show_extension_data) {
			mime_findfield("content-md5", header_to_use, &mr);
			if (mr && strlen(mr->value) > 0) {
				fprintf(outstream, " ");
				quoted_string_out(outstream, mr->value);
			} else
				fprintf(outstream, " NIL");

			mime_findfield("content-disposition",
				       header_to_use, &mr);
			if (mr && strlen(mr->value) > 0) {
				fprintf(outstream, " (");
				show_mime_parameter_list(outstream, mr, 0,
							 0);
				fprintf(outstream, ")");
			} else
				fprintf(outstream, " NIL");

			mime_findfield("content-language", header_to_use,
				       &mr);
			if (mr && strlen(mr->value) > 0) {
				fprintf(outstream, " ");
				quoted_string_out(outstream, mr->value);
			} else
				fprintf(outstream, " NIL");
		}
	} else {
		/* check for a multipart message */
		if (is_rfc_multipart || is_mime_multipart) {
			curr = list_getstart(&msg->children);
			while (curr) {
				if (retrieve_structure
				    (outstream,
				     (mime_message_t *) curr->data,
				     show_extension_data) == -1)
					return -1;

				curr = curr->nextnode;
			}

			/* show multipart subtype */
			if (is_mime_multipart)
				mime_findfield("content-type",
					       &msg->mimeheader, &mr);
			else
				mime_findfield("content-type",
					       &msg->rfcheader, &mr);

			subtype = strchr(mr->value, '/');
			extension = strchr(subtype, ';');

			if (!subtype)
				fprintf(outstream, " NIL");
			else {
				if (!extension) {
					newline = strchr(subtype, '\n');
					if (!newline)
						return -1;

					*newline = 0;
					fprintf(outstream, " ");
					quoted_string_out(outstream,
							  subtype + 1);
					*newline = '\n';
				} else {
					*extension = 0;
					fprintf(outstream, " ");
					quoted_string_out(outstream,
							  subtype + 1);
					*extension = ';';
				}
			}

			/* show extension data (after subtype) */
			if (extension && show_extension_data) {
				show_mime_parameter_list(outstream, mr, 0,
							 1);

				/* FIXME: should give body-disposition & body-language here */
				fprintf(outstream, " NIL NIL");
			}
		} else {
			/* ??? */
		}
	}
	fprintf(outstream, ")");

	return 0;
}


/*
 * retrieve_envelope()
 *
 * retrieves the body envelope of an RFC-822 msg
 *
 * returns -1 on error, 0 on success
 */
int retrieve_envelope(FILE * outstream, struct list *rfcheader)
{
	struct mime_record *mr;
	int idx;

	fprintf(outstream, "(");

	mime_findfield("date", rfcheader, &mr);
	if (mr && strlen(mr->value) > 0) {
		quoted_string_out(outstream, mr->value);
		fprintf(outstream, " ");
	} else
		fprintf(outstream, "NIL ");

	mime_findfield("subject", rfcheader, &mr);
	if (mr && strlen(mr->value) > 0) {
		quoted_string_out(outstream, mr->value);
		fprintf(outstream, " ");
	} else
		fprintf(outstream, "NIL ");

	/* now from, sender, reply-to, to, cc, bcc, in-reply-to fields;
	 * note that multiple mailaddresses are separated by ','
	 */

	for (idx = 0; envelope_items[idx]; idx++) {
		mime_findfield(envelope_items[idx], rfcheader, &mr);
		if (mr && strlen(mr->value) > 0) {
			show_address_list(outstream, mr);
		} else if (strcasecmp(envelope_items[idx], "reply-to") ==
			   0) {
			/* default this field */
			mime_findfield("from", rfcheader, &mr);
			if (mr && strlen(mr->value) > 0)
				show_address_list(outstream, mr);
			else	/* no from field ??? */
				fprintf(outstream,
					"((NIL NIL \"nobody\" \"nowhere.nirgendwo\"))");
		} else if (strcasecmp(envelope_items[idx], "sender") == 0) {
			/* default this field */
			mime_findfield("from", rfcheader, &mr);
			if (mr && strlen(mr->value) > 0)
				show_address_list(outstream, mr);
			else	/* no from field ??? */
				fprintf(outstream,
					"((NIL NIL \"nobody\" \"nowhere.nirgendwo\"))");
		} else
			fprintf(outstream, "NIL");

		fprintf(outstream, " ");
	}

	mime_findfield("in-reply-to", rfcheader, &mr);
	if (mr && strlen(mr->value) > 0) {
		quoted_string_out(outstream, mr->value);
		fprintf(outstream, " ");
	} else
		fprintf(outstream, "NIL ");

	mime_findfield("message-id", rfcheader, &mr);
	if (mr && strlen(mr->value) > 0)
		quoted_string_out(outstream, mr->value);
	else
		fprintf(outstream, "NIL");

	fprintf(outstream, ")");

	return 0;
}


/*
 * show_address_list()
 *
 * gives an address list, output to outstream
 */
int show_address_list(FILE * outstream, struct mime_record *mr)
{
	int delimiter, i, inquote, start, has_split;
	char savechar;

	fprintf(outstream, "(");

	/* find ',' to split up multiple addresses */
	delimiter = 0;

	do {
		fprintf(outstream, "(");

		start = delimiter;

		for (inquote = 0;
		     mr->value[delimiter] && !(mr->value[delimiter] == ','
					       && !inquote); delimiter++)
			if (mr->value[delimiter] == '\"')
				inquote ^= 1;

		if (mr->value[delimiter])
			mr->value[delimiter] = 0;	/* replace ',' by NULL-termination */
		else
			delimiter = -1;	/* this will be the last one */

		/* the address currently being processed is now contained within
		 * &mr->value[start] 'till first '\0'
		 */

		/* possibilities for the mail address:
		 * (1) name <user@domain>
		 * (2) <user@domain>
		 * (3) user@domain
		 * scan for '<' to determine which case we should be dealing with;
		 */

		for (i = start, inquote = 0;
		     mr->value[i] && !(mr->value[i] == '<' && !inquote);
		     i++)
			if (mr->value[i] == '\"')
				inquote ^= 1;

		if (mr->value[i]) {
			if (i > start + 2) {
				/* name is contained in &mr->value[start] untill &mr->value[i-2] */
				/* name might contain quotes */
				savechar = mr->value[i - 1];
				mr->value[i - 1] = '\0';	/* terminate string */

				quoted_string_out(outstream,
						  &mr->value[start]);

				mr->value[i - 1] = savechar;

			} else
				fprintf(outstream, "NIL");

			start = i + 1;	/* skip to after '<' */
		} else
			fprintf(outstream, "NIL");

		fprintf(outstream, " NIL ");	/* source route ?? smtp at-domain-list ?? */

		/* now display user domainname; &mr->value[start] is starting point */
		fprintf(outstream, "\"");

		/*
		 * added a check for whitespace within the address (not good)
		 */
		for (i = start, has_split = 0;
		     mr->value[i] && mr->value[i] != '>'
		     && !isspace(mr->value[i]); i++) {
			if (mr->value[i] == '@') {
				fprintf(outstream, "\" \"");
				has_split = 1;
			} else {
				if (mr->value[i] == '"')
					fprintf(outstream, "\\");
				fprintf(outstream, "%c", mr->value[i]);
			}
		}

		if (!has_split)
			fprintf(outstream, "\" \"\"");	/* '@' did not occur */
		else
			fprintf(outstream, "\"");

		if (delimiter > 0) {
			mr->value[delimiter++] = ',';	/* restore & prepare for next iteration */
			while (isspace(mr->value[delimiter]))
				delimiter++;
		}

		fprintf(outstream, ")");

	} while (delimiter > 0);

	fprintf(outstream, ")");

	return 0;
}



/*
 * show_mime_parameter_list()
 *
 * shows mime name/value pairs, output to outstream
 * 
 * if force_subtype != 0 'NIL' will be outputted if no subtype is specified
 * if only_extension != 0 only extension data (after first ';') will be shown
 */
int show_mime_parameter_list(FILE * outstream, struct mime_record *mr,
			     int force_subtype, int only_extension)
{
	int idx, delimiter, start, end;
	char *fieldvalue = (char *)dm_malloc(sizeof(char *)*255);
	char *tmpstring = (char *)dm_malloc(sizeof(char *)*255);
	
	/* find first delimiter */
	for (delimiter = 0;
	     mr->value[delimiter] && mr->value[delimiter] != ';';
	     delimiter++);

	/* are there non-whitespace chars after the delimiter?                    */
	/* looking for the case where the mime type ends with a ";"               */
	/* if it is of type "text" it must have a default character set generated */
	end = strlen(mr->value);
	for (start = delimiter + 1;
	     (isspace(mr->value[start]) == 0 && start <= end); start++);
	end = start - delimiter - 1;
	start = 0;
	if (end && strstr(mr->value, "text"))
		start++;

	if (mr->value[delimiter])
		mr->value[delimiter] = 0;
	else
		delimiter = -1;

	if (!only_extension) {
		/* find main type in value */
		for (idx = 0; mr->value[idx] && mr->value[idx] != '/';
		     idx++);

		if (mr->value[idx] && (idx < delimiter || delimiter == -1)) {
			mr->value[idx] = 0;

			quoted_string_out(outstream, mr->value);
			fprintf(outstream, " ");
			quoted_string_out(outstream, &mr->value[idx + 1]);

			mr->value[idx] = '/';
		} else {
			quoted_string_out(outstream, mr->value);
			fprintf(outstream, " %s",
				force_subtype ? "NIL" : "");
		}
	}

	if (delimiter >= 0) {
		/* extra parameters specified */
		mr->value[delimiter] = ';';
		idx = delimiter;

		fprintf(outstream, " (");

		if (start)
			fprintf(outstream, "\"CHARSET\" \"US-ASCII\"");
		/* extra params: <name>=<val> [; <name>=<val> [; ...etc...]]
		 * note that both name and val may or may not be enclosed by 
		 * either single or double quotation marks
		 */

		do {
			/* skip whitespace */
			for (idx++; isspace(mr->value[idx]); idx++);

			if (!mr->value[idx])
				break;	/* ?? */

			/* check if quotation marks are specified */
			if (mr->value[idx] == '\"' || mr->value[idx] == '\'') {
				start = ++idx;
				while (mr->value[idx] && mr->value[idx] != mr->value[start - 1])
					idx++;

				if (!mr->value[idx] || mr->value[idx + 1] != '=')	/* ?? no end quote */
					break;

				end = idx;
				idx += 2;	/* skip to after '=' */
			} else {
				start = idx;
				while (mr->value[idx] && mr->value[idx] != '=')
					idx++;

				if (!mr->value[idx])	/* ?? no value specified */
					break;

				end = idx;
				idx++;	/* skip to after '=' */
			}
			
			fprintf(outstream, "\"%.*s\" ", (end - start), &mr->value[start]);

			/* now process the value; practically same procedure */

			if (mr->value[idx] == '\"' || mr->value[idx] == '\'') {
				start = ++idx;
				while (mr->value[idx] && mr->value[idx] != mr->value[start - 1])
					idx++;

				if (!mr->value[idx])	/* ?? no end quote */
					break;

				end = idx;
				idx++;
			} else {
				start = idx;

				while (mr->value[idx] && !isspace(mr->value[idx]) && mr->value[idx] != ';')
					idx++;

				end = idx;
			}

			
			/* Thunderbird doesn't like mime-values that are wrapped, so
			 * we unwrap mime-values 
			 *
			 * this code assumes mime values are no longer than 255 chars.
			 *
			 * PJS
			 */
			
			
			snprintf(tmpstring,255,"\"%.*s\"", (end - start), &mr->value[start]);
			mime_unwrap(fieldvalue,tmpstring);
			fprintf(outstream,fieldvalue);
			//fprintf(outstream, "\"%.*s\"", (end - start), &mr->value[start]);
			
			/* check for more name/val pairs */
			while (mr->value[idx] && mr->value[idx] != ';')
				idx++;

			if (mr->value[idx])
				fprintf(outstream, " ");

		} while (mr->value[idx]);

		fprintf(outstream, ")");

	} else {
		fprintf(outstream, " NIL");
	}
	dm_free(fieldvalue);
	dm_free(tmpstring);
			
	return 0;
}


/* 
 * get_part_by_num()
 *
 * retrieves a msg part by it's numeric specifier
 * 'part' is assumed to be valid! (i.e '1.2.3.44')
 * returns NULL if there is no such part 
 */
mime_message_t *get_part_by_num(mime_message_t * msg, const char *part)
{
	int nextpart, j;
	char *endptr;
	struct element *curr;

	if (part == NULL || strlen(part) == 0 || msg == NULL)
		return msg;

	nextpart = strtoul(part, &endptr, 10);	/* strtoul() stops at '.' */

	for (j = 1, curr = list_getstart(&msg->children);
	     j < nextpart && curr; j++, curr = curr->nextnode);

	if (!curr)
		return NULL;

	if (*endptr)
		return get_part_by_num((mime_message_t *) curr->data, &endptr[1]);	/* skip dot in part */

	return (mime_message_t *) curr->data;
}


/*
 * rfcheader_dump()
 * 
 * dumps rfc-header fields belonging to rfcheader
 * the fields to be dumped are specified in fieldnames, an array containing nfields items
 *
 * if equal_type == 0 the field match criterium is inverted and non-matching fieldnames
 * will be selected
 *
 * to select every headerfield it suffices to set nfields and equal_type to 0
 *
 * returns number of bytes written to outmem
 */
u64_t rfcheader_dump(MEM * outmem, struct list * rfcheader,
		     char **fieldnames, int nfields, int equal_type)
{
	struct mime_record *mr;
	struct element *curr;
	u64_t size = 0;

	curr = list_getstart(rfcheader);
	if (rfcheader == NULL || curr == NULL) {
		/*size += fprintf(outstream, "NIL\r\n"); */
		return 0;
	}

	curr = list_getstart(rfcheader);
	while (curr) {
		mr = (struct mime_record *) curr->data;

		if (haystack_find(nfields, fieldnames, mr->field) ==
		    equal_type) {
			/* ok output this field */
			size +=
			    mwrite(mr->field, strlen(mr->field), outmem);
			size += mwrite(": ", 2, outmem);
			size +=
			    mwrite(mr->value, strlen(mr->value), outmem);
			size += mwrite("\r\n", 2, outmem);
		}

		curr = curr->nextnode;
	}
	size += mwrite("\r\n", 2, outmem);

	return size;
}


/*
 * mimeheader_dump()
 * 
 * dumps mime-header fields belonging to mimeheader
 *
 */
u64_t mimeheader_dump(MEM * outmem, struct list * mimeheader)
{
	struct mime_record *mr;
	struct element *curr;
	u64_t size = 0;

	curr = list_getstart(mimeheader);
	if (mimeheader == NULL || curr == NULL) {
		/*size = fprintf(outstream, "NIL\r\n"); */
		return 0;
	}

	while (curr) {
		mr = (struct mime_record *) curr->data;
		size += mwrite(mr->field, strlen(mr->field), outmem);
		size += mwrite(": ", 2, outmem);
		size += mwrite(mr->value, strlen(mr->value), outmem);
		size += mwrite("\r\n", 2, outmem);
		curr = curr->nextnode;
	}
	size += mwrite("\r\n", 2, outmem);

	return size;
}


/* 
 * find a string in an array of strings
 */
int haystack_find(int haystacklen, char **haystack, const char *needle)
{
	int i;

	for (i = 0; i < haystacklen; i++)
		if (strcasecmp(haystack[i], needle) == 0)
			return 1;

	return 0;
}


/*
 * next_fetch_item()
 *
 * retrieves next item to be fetched from an argument list starting at the given
 * index. The update index is returned being -1 on 'no-more' and -2 on error.
 * arglist is supposed to be formatted according to build_args_array()
 *
 */
int next_fetch_item(char **args, int idx, fetch_items_t * fi)
{
	int invalidargs, indigit, ispeek, shouldclose, delimpos;
	unsigned int j = 0;

	memset(fi, 0, sizeof(fetch_items_t));	/* init */
	fi->bodyfetch.itemtype = -1;	/* expect no body fetches (a priori) */
	invalidargs = 0;

	if (!args[idx])
		return -1;	/* no more */

	if (args[idx][0] == '(')
		idx++;

	if (!args[idx])
		return -2;	/* error */

	if (strcasecmp(args[idx], "flags") == 0)
		fi->getFlags = 1;
	else if (strcasecmp(args[idx], "internaldate") == 0)
		fi->getInternalDate = 1;
	else if (strcasecmp(args[idx], "uid") == 0)
		fi->getUID = 1;
	else if (strcasecmp(args[idx], "rfc822") == 0) {
		fi->getRFC822 = 1;
		fi->msgparse_needed = 1;
	} else if (strcasecmp(args[idx], "rfc822.peek") == 0) {
		fi->getRFC822Peek = 1;
		fi->msgparse_needed = 1;
	} else if (strcasecmp(args[idx], "rfc822.header") == 0) {
		fi->getRFC822Header = 1;
		fi->msgparse_needed = 1;
	} else if (strcasecmp(args[idx], "rfc822.size") == 0) {
		fi->getSize = 1;
/*      fi->msgparse_needed = 1;*//* after first calc, it will be in the dbase */
	} else if (strcasecmp(args[idx], "rfc822.text") == 0) {
		fi->getRFC822Text = 1;
		fi->msgparse_needed = 1;
	} else if (strcasecmp(args[idx], "body") == 0
		   || strcasecmp(args[idx], "body.peek") == 0) {
		fi->msgparse_needed = 1;

		if (!args[idx + 1] || strcmp(args[idx + 1], "[") != 0) {
			if (strcasecmp(args[idx], "body.peek") == 0)
				return -2;	/* error DONE */
			else
				fi->getMIME_IMB_noextension = 1;	/* just BODY specified */
		} else {
			/* determine wheter or not to set the seen flag */
			ispeek = (strcasecmp(args[idx], "body.peek") == 0);

			/* now read the argument list to body */
			idx++;	/* now pointing at '[' (not the last arg, parentheses are matched) */
			idx++;	/* now pointing at what should be the item type */

			if (strcmp(args[idx], "]") == 0) {
				/* specified body[] or body.peek[] */
				if (ispeek)
					fi->getBodyTotalPeek = 1;
				else
					fi->getBodyTotal = 1;

				/* check if octet start/cnt is specified */
				if (args[idx + 1]
				    && args[idx + 1][0] == '<') {
					idx++;	/* advance */

					/* check argument */
					if (args[idx]
					    [strlen(args[idx]) - 1] != '>')
						return -2;	/* error DONE */

					delimpos = -1;
					for (j = 1;
					     j < strlen(args[idx]) - 1;
					     j++) {
						if (args[idx][j] == '.') {
							if (delimpos != -1) {
								invalidargs
								    = 1;
								break;
							}
							delimpos = j;
						} else
						    if (!isdigit
							(args[idx][j])) {
							invalidargs = 1;
							break;
						}
					}

					if (invalidargs || delimpos == -1
					    || delimpos == 1
					    || delimpos ==
					    (int) (strlen(args[idx]) - 2))
						return -2;	/* no delimiter found or at first/last pos OR invalid args DONE */

					/* read the numbers */
					args[idx][strlen(args[idx]) - 1] =
					    '\0';
					args[idx][delimpos] = '\0';
					fi->bodyfetch.octetstart =
					    strtoll(&args[idx][1], NULL,
						    10);
					fi->bodyfetch.octetcnt =
					    strtoll(&args[idx]
						    [delimpos + 1], NULL,
						    10);

					/* restore argument */
					args[idx][delimpos] = '.';
					args[idx][strlen(args[idx]) - 1] =
					    '>';
				} else {
					fi->bodyfetch.octetstart = -1;
					fi->bodyfetch.octetcnt = -1;
				}

				return idx + 1;	/* DONE */
			}

			if (ispeek)
				fi->bodyfetch.noseen = 1;

			/* check for a partspecifier */
			/* first check if there is a partspecifier (numbers & dots) */
			indigit = 0;

			for (j = 0; args[idx][j]; j++) {
				if (isdigit(args[idx][j])) {
					indigit = 1;
					continue;
				} else if (args[idx][j] == '.') {
					if (!indigit) {
						/* error, single dot specified */
						invalidargs = 1;
						break;
					}

					indigit = 0;
					continue;
				} else
					break;	/* other char found */
			}

			if (invalidargs)
				return -2;	/* error DONE */

			if (j > 0) {
				if (indigit && args[idx][j])
					return -2;	/* error DONE */

				/* partspecifier present, save it */
				if (j >= IMAP_MAX_PARTSPEC_LEN)
					return -2;	/* error DONE */

				strncpy(fi->bodyfetch.partspec, args[idx],
					j);
			}

			fi->bodyfetch.partspec[j] = '\0';


			shouldclose = 0;
			if (strcasecmp(&args[idx][j], "text") == 0) {
				fi->bodyfetch.itemtype = BFIT_TEXT;
				shouldclose = 1;
			} else if (strcasecmp(&args[idx][j], "header") ==
				   0) {
				fi->bodyfetch.itemtype = BFIT_HEADER;
				shouldclose = 1;
			} else if (strcasecmp(&args[idx][j], "mime") == 0) {
				if (j == 0)
					return -2;	/* error DONE */

				fi->bodyfetch.itemtype = BFIT_MIME;
				shouldclose = 1;
			} else
			    if (strcasecmp(&args[idx][j], "header.fields")
				== 0)
				fi->bodyfetch.itemtype =
				    BFIT_HEADER_FIELDS;
			else if (strcasecmp
				 (&args[idx][j], "header.fields.not") == 0)
				fi->bodyfetch.itemtype =
				    BFIT_HEADER_FIELDS_NOT;
			else if (args[idx][j] == '\0') {
				fi->bodyfetch.itemtype = BFIT_TEXT_SILENT;
				shouldclose = 1;
			} else
				return -2;	/* error DONE */

			if (shouldclose) {
				if (strcmp(args[idx + 1], "]") != 0)
					return -2;	/* error DONE */
			} else {
				idx++;	/* should be at '(' now */
				if (strcmp(args[idx], "(") != 0)
					return -2;	/* error DONE */

				idx++;	/* at first item of field list now, remember idx */
				fi->bodyfetch.argstart = idx;

				/* walk on untill list terminates (and it does 'cause parentheses are matched) */
				while (strcmp(args[idx], ")") != 0)
					idx++;

				fi->bodyfetch.argcnt =
				    idx - fi->bodyfetch.argstart;

				if (fi->bodyfetch.argcnt == 0
				    || strcmp(args[idx + 1], "]") != 0)
					return -2;	/* error DONE */
			}

			idx++;	/* points to ']' now */

			/* check if octet start/cnt is specified */
			if (args[idx + 1] && args[idx + 1][0] == '<') {
				idx++;	/* advance */

				/* check argument */
				if (args[idx][strlen(args[idx]) - 1] !=
				    '>')
					return -2;	/* error DONE */

				delimpos = -1;
				for (j = 1; j < strlen(args[idx]) - 1; j++) {
					if (args[idx][j] == '.') {
						if (delimpos != -1) {
							invalidargs = 1;
							break;
						}
						delimpos = j;
					} else if (!isdigit(args[idx][j])) {
						invalidargs = 1;
						break;
					}
				}

				if (invalidargs || delimpos == -1 ||
				    delimpos == 1
				    || delimpos ==
				    (int) (strlen(args[idx]) - 2))
					return -2;	/* no delimiter found or at first/last pos OR invalid args DONE */

				/* read the numbers */
				args[idx][strlen(args[idx]) - 1] = '\0';
				args[idx][delimpos] = '\0';
				fi->bodyfetch.octetstart =
				    strtoll(&args[idx][1], NULL, 10);
				fi->bodyfetch.octetcnt =
				    strtoll(&args[idx][delimpos + 1], NULL,
					    10);

				/* restore argument */
				args[idx][delimpos] = '.';
				args[idx][strlen(args[idx]) - 1] = '>';
			} else {
				fi->bodyfetch.octetstart = -1;
				fi->bodyfetch.octetcnt = -1;
			}
			/* ok all done for body item */
		}
	} else if (strcasecmp(args[idx], "all") == 0) {
		fi->getFlags = 1;
		fi->getInternalDate = 1;
		fi->getSize = 1;
		fi->getEnvelope = 1;
		fi->msgparse_needed = 1;
	} else if (strcasecmp(args[idx], "fast") == 0) {
		fi->getFlags = 1;
		fi->getInternalDate = 1;
		fi->getSize = 1;
/*      fi->msgparse_needed = 1; *//* size will be in dbase after first calc */
	} else if (strcasecmp(args[idx], "full") == 0) {
		fi->getFlags = 1;
		fi->getInternalDate = 1;
		fi->getSize = 1;
		fi->getEnvelope = 1;
		fi->getMIME_IMB = 1;
		fi->msgparse_needed = 1;
	} else if (strcasecmp(args[idx], "bodystructure") == 0) {
		fi->getMIME_IMB = 1;
		fi->msgparse_needed = 1;
	} else if (strcasecmp(args[idx], "envelope") == 0) {
		fi->getEnvelope = 1;
		fi->msgparse_needed = 1;
	} else if (strcmp(args[idx], ")") == 0) {
		/* only allowed if last arg here */
		if (args[idx + 1])
			return -2;	/* DONE */
		else
			return -1;
	} else
		return -2;	/* DONE */

	trace(TRACE_DEBUG,
	      "next_fetch_item(): args[idx = %d] = %s (returning %d)\n",
	      idx, args[idx], idx + 1);
	return idx + 1;
}


/*
 * give_chunks()
 *
 * splits up a string delimited by a given character into a NULL-terminated array of strings
 * does not perform any quotation-mark checks
 */
char **give_chunks(const char *str, char delimiter)
{
	int cnt, i;
	char **array, *cpy, *tmp;

	cpy = (char *) dm_malloc(sizeof(char) * (strlen(str) + 1));
	if (!cpy) {
		trace(TRACE_ERROR, "give_chunks(): out of memory\n");
		return NULL;
	}

	strcpy(cpy, str);
	tmp = cpy;		/* save start of cpy */

	for (i = 0, cnt = 0; str[i]; i++)
		if (str[i] == delimiter) {
			cnt++;
			cpy[i] = '\0';
		}

	cnt++;			/* add last part */

	/* alloc mem */
	cnt++;			/* for NULL termination */
	array = (char **) dm_malloc(sizeof(char *) * cnt);

	if (!array) {
		trace(TRACE_ERROR, "give_chunks(): out of memory\n");
		dm_free(cpy);
		return NULL;
	}

	for (i = 0, cnt = 0; str[i]; i++) {
		if (str[i] == delimiter) {
			array[cnt++] = cpy;	/* save this address */
			cpy = &tmp[i + 1];	/* let cpy point to next string */
		}
	}

	/* copy last part */
	array[cnt++] = cpy;

	array[cnt] = NULL;
	return array;
}


/* 
 * free_chunks()
 *
 * frees memory allocated using give_chunks()
 */
void free_chunks(char **chunks)
{
	if (!chunks)
		return;

	if (chunks[0])
		dm_free(chunks[0]);	/* the entire array will be freed now */

	dm_free(chunks);	/* free ptrs to strings */
}



/*
 * check_state_and_args()
 *
 * checks if the user is in the right state & the numbers of arguments;
 * a state of -1 specifies any state
 * arguments can be grouped by means of parentheses
 *
 * returns 1 on succes, 0 on failure
 */
int check_state_and_args(const char *command, const char *tag, char **args,
			 int nargs, int state, ClientInfo * ci)
{
	int i;
	imap_userdata_t *ud = (imap_userdata_t *) ci->userData;

	/* check state */
	if (state != -1) {
		if (ud->state != state) {
			if (!
			    (state == IMAPCS_AUTHENTICATED
			     && ud->state == IMAPCS_SELECTED)) {
				fprintf(ci->tx,
					"%s BAD %s command received in invalid state\r\n",
					tag, command);
				return 0;
			}
		}
	}

	/* check args */
	for (i = 0; i < nargs; i++) {
		if (!args[i]) {
			/* error: need more args */
			fprintf(ci->tx,
				"%s BAD missing argument%s to %s\r\n", tag,
				(nargs == 1) ? "" : "(s)", command);
			return 0;
		}
	}

	for (i = 0; args[i]; i++);

	if (i > nargs) {
		/* error: too many args */
		fprintf(ci->tx, "%s BAD too many arguments to %s\r\n", tag,
			command);
		return 0;
	}

	/* succes */
	return 1;
}



/*
 * build_args_array()
 *
 * builds an dimensional array of strings containing arguments based upon 
 * a series of arguments passed as a single string.
 * normal/square parentheses have special meaning:
 * '(body [all header])' will result in the following array:
 * [0] = '('
 * [1] = 'body'
 * [2] = '['
 * [3] = 'all'
 * [4] = 'header'
 * [5] = ']'
 * [6] = ')'
 *
 * quoted strings are those enclosed by double quotation marks and returned as a single argument
 * WITHOUT the enclosing quotation marks
 *
 * parentheses loose their special meaning if inside (double)quotation marks;
 * data should be 'clarified' (see clarify_data() function below)
 *
 * The returned array will be NULL-terminated.
 * Will return NULL upon errors.
 */

/* local defines */
#define NORMPAR 1
#define SQUAREPAR 2
#define NOPAR 0

char *the_args[MAX_ARGS];

char **build_args_array(const char *s)
{
	int nargs = 0, inquote = 0, i, quotestart = 0;
	int nnorm = 0, nsquare = 0, paridx = 0, slen = 0, argstart = 0;
	char parlist[IMAP_MAX_LINESIZE];
 	unsigned int argsize = 0;

	if (!s)
		return NULL;

	/* check for empty string */
	if (!(*s)) {
		the_args[0] = NULL;
		return the_args;
	}

	/* find the arguments */
	paridx = 0;
	parlist[paridx] = NOPAR;

	inquote = 0;
	slen = strlen(s);

	for (i = 0, nargs = 0; i < slen && nargs < MAX_ARGS - 1; i++) {
		/* check quotes */
		if (s[i] == '"' && ((i > 0 && s[i - 1] != '\\') || i == 0)) {
			if (inquote) {
				argsize = i - quotestart -1; // avoid copying "
				
				/* quotation end, treat quoted string as argument */
				
				if (!  (the_args[nargs] = (char *) dm_malloc(sizeof(char) * argsize + 1))) {
					/* out of mem */
					while (--nargs >= 0) {
						dm_free(the_args[nargs]);
						the_args[nargs] = NULL;
					}

					trace(TRACE_ERROR, "IMAPD: Not enough memory while building up argument array.");
					return NULL;
				}
				memset((void *)the_args[nargs],'\0', argsize + 1);
				memcpy((void *) the_args[nargs], (void *) &s[quotestart+1], argsize);

				nargs++;
				inquote = 0;
			} else {
				inquote = 1;
				quotestart = i;
			}

			continue;
		}

		if (inquote)
			continue;

		/* check for (, ), [ or ] in string */
		if (s[i] == '(' || s[i] == ')' || s[i] == '[' || s[i] == ']') {
			/* check parenthese structure */
			if (s[i] == ')') {
				if (paridx < 0 || parlist[paridx] != NORMPAR)
					paridx = -1;
				else {
					nnorm--;
					paridx--;
				}
			} else if (s[i] == ']') {
				if (paridx < 0 || parlist[paridx] != SQUAREPAR)
					paridx = -1;
				else {
					paridx--;
					nsquare--;
				}
			} else if (s[i] == '(') {
				parlist[++paridx] = NORMPAR;
				nnorm++;
			} else {	/* s[i] == '[' */

				parlist[++paridx] = SQUAREPAR;
				nsquare++;
			}

			if (paridx < 0) {
				/* error in parenthesis structure */
				while (--nargs >= 0) {
					dm_free(the_args[nargs]);
					the_args[nargs] = NULL;
				}
				return NULL;
			}

			/* add this parenthesis to the arg list and continue */
			if (!  (the_args[nargs] = (char *) dm_malloc(sizeof(" ") + 1))) {
				/* out of mem */
				while (--nargs >= 0) {
					dm_free(the_args[nargs]);
					the_args[nargs] = NULL;
				}
				trace(TRACE_ERROR, "IMAPD: Not enough memory while building up argument array.");
				return NULL;
			}
			the_args[nargs][0] = s[i];
			the_args[nargs][1] = '\0';

			nargs++;
			continue;
		}

		if (s[i] == ' ')
			continue;

		/* at an argument start now, walk on until next delimiter
		 * and save argument 
		 */

		for (argstart = i; i < slen && !strchr(" []()", s[i]); i++)
			if (s[i] == '"') {
				if (s[i - 1] == '\\')
					continue;
				else
					break;
			}
		argsize = i - argstart; // no +1 here to avoid copying delimiter
		if (!  (the_args[nargs] = (char *) dm_malloc(sizeof(char) * argsize + 1))) {
			/* out of mem */
			while (--nargs >= 0) {
				dm_free(the_args[nargs]);
				the_args[nargs] = NULL;
			}

			trace(TRACE_ERROR, "IMAPD: Not enough memory while building up argument array.");
			return NULL;
		}
		memset((void *) the_args[nargs], '\0', argsize + 1);
		memcpy((void *) the_args[nargs], (void *) &s[argstart], argsize);

		nargs++;
		i--;		/* walked one too far */
	}

	if (paridx != 0) {
		/* error in parenthesis structure */
		while (--nargs >= 0) {
			dm_free(the_args[nargs]);
			the_args[nargs] = NULL;
		}
		return NULL;
	}

	the_args[nargs] = NULL;	/* terminate */

	/* dump args (debug) */
	for (i = 0; the_args[i]; i++) {
		trace(TRACE_DEBUG, "arg[%d]: '%s'\n", i, the_args[i]);
	}

	return the_args;
}

/*
 * as build_args_array(), but reads strings on cmd line specified by {##}\0
 * (\r\n had been removed from string)
 */
char **build_args_array_ext(const char *originalString, clientinfo_t * ci)
{
	int nargs = 0, inquote = 0, quotestart = 0;
	int nnorm = 0, nsquare = 0, paridx = 0, argstart = 0;
	unsigned int i;
	char parlist[IMAP_MAX_LINESIZE];
	char s[IMAP_MAX_LINESIZE];
	char *tmp, *lastchar;
	int quotedSize, cnt, dataidx;
	int argsize=0;

	/* this is done for the possible extra lines to be read from the client:
	 * the line is read into currline; s will always point to the line currently
	 * being processed
	 */
	strncpy(s, originalString, IMAP_MAX_LINESIZE);

	if (!s)
		return NULL;

	/* check for empty string */
	if (!(*s)) {
		the_args[0] = NULL;
		return the_args;
	}

	/* find the arguments */
	paridx = 0;
	parlist[paridx] = NOPAR;

	inquote = 0;

	for (i = 0, nargs = 0; s[i] && nargs < MAX_ARGS - 1; i++) {
		/* check quotes */
		if (s[i] == '"' && ((i > 0 && s[i - 1] != '\\') || i == 0)) {
			if (inquote) {
				argsize = i - quotestart - 1; // avoid copying "
				/* quotation end, treat quoted string as argument */
				if (!  (the_args[nargs] = (char *) dm_malloc(sizeof(char) * argsize + 1))) {
					/* out of mem */
					while (--nargs >= 0) {
						dm_free(the_args[nargs]);
						the_args[nargs] = NULL;
					}

					trace(TRACE_ERROR, "IMAPD: Not enough memory while building up argument array.");
					return NULL;
				}
				memset((void *) the_args[nargs], '\0', argsize + 1);
				memcpy((void *) the_args[nargs], (void *) &s[quotestart + 1], argsize);

				nargs++;
				inquote = 0;
			} else {
				inquote = 1;
				quotestart = i;
			}

			continue;
		}

		if (inquote)
			continue;

		/* check for (, ), [ or ] in string */
		if (s[i] == '(' || s[i] == ')' || s[i] == '[' || s[i] == ']') {
			/* check parenthese structure */
			if (s[i] == ')') {
				if (paridx < 0 || parlist[paridx] != NORMPAR)
					paridx = -1;
				else {
					nnorm--;
					paridx--;
				}
			} else if (s[i] == ']') {
				if (paridx < 0 || parlist[paridx] != SQUAREPAR)
					paridx = -1;
				else {
					paridx--;
					nsquare--;
				}
			} else if (s[i] == '(') {
				parlist[++paridx] = NORMPAR;
				nnorm++;
			} else {	/* s[i] == '[' */

				parlist[++paridx] = SQUAREPAR;
				nsquare++;
			}

			if (paridx < 0) {
				/* error in parenthesis structure */
				while (--nargs >= 0) {
					dm_free(the_args[nargs]);
					the_args[nargs] = NULL;
				}
				return NULL;
			}

			/* add this parenthesis to the arg list and continue */
			if (!  (the_args[nargs] = (char *) dm_malloc(sizeof(" ") + 1))) {
				/* out of mem */
				while (--nargs >= 0) {
					dm_free(the_args[nargs]);
					the_args[nargs] = NULL;
				}

				trace(TRACE_ERROR,
				      "IMAPD: Not enough memory while building up argument array.");
				return NULL;
			}
			the_args[nargs][0] = s[i];
			the_args[nargs][1] = '\0';

			nargs++;
			continue;
		}

		if (s[i] == ' ')
			continue;

		/* check for {number}\0 */
		if (s[i] == '{') {
			quotedSize = strtoul(&s[i + 1], &lastchar, 10);

			/* only continue if the number is followed by '}\0' */
			trace(TRACE_DEBUG, "%s,%s: last char = %c", __FILE__, __func__, *lastchar);
			if ((*lastchar == '+' && *(lastchar + 1) == '}' && 
			     *(lastchar + 2) == '\0') || 
			    (*lastchar == '}' && *(lastchar + 1) == '\0')) {
				argsize = quotedSize + 1;
				/* allocate space for this argument (could be a message when used with APPEND) */
				if (!  (the_args[nargs] = (char *) dm_malloc(sizeof(char) * argsize + 1))) {
					/* out of mem */
					while (--nargs >= 0) {
						dm_free(the_args[nargs]);
						the_args[nargs] = NULL;
					}

					trace(TRACE_ERROR,
					      "build_args_array_ext(): out of memory allocating [%u] bytes for extra string",
					      argsize);
					return NULL;
				}
				memset((void *)the_args[nargs],'\0', argsize + 1);
				
				fprintf(ci->tx, "+ OK gimme that string\r\n");
				alarm(ci->timeout);	/* dont wait forever */
				for (cnt = 0, dataidx = 0; cnt < quotedSize; cnt++) {
					the_args[nargs][dataidx] = fgetc(ci->rx);

					if (the_args[nargs][dataidx] != '\r')
						dataidx++;	/* only store if it is not \r */
				}

				alarm(0);
				nargs++;

				if (!ci->rx || !ci->tx || ferror(ci->rx) || ferror(ci->tx)) {
					/* timeout occurred or connection has gone away */
					while (--nargs >= 0) {
						dm_free(the_args[nargs]);
						the_args[nargs] = NULL;
					}

					trace(TRACE_ERROR, "build_args_array_ext(): timeout occurred");
					return NULL;
				}

				/* now read the rest of this line */
				alarm(ci->timeout);
				fgets(s, IMAP_MAX_LINESIZE, ci->rx);
				alarm(0);

				if (!ci->rx || !ci->tx || ferror(ci->rx) || ferror(ci->tx)) {
					/* timeout occurred */
					while (--nargs >= 0) {
						dm_free(the_args[nargs]);
						the_args[nargs] = NULL;
					}

					trace(TRACE_ERROR, "build_args_array_ext(): timeout occurred");
					return NULL;
				}

				/* remove trailing \r\n */
				tmp = &s[strlen(s)];
				tmp--;	/* go before trailing \0; watch this with empty strings! */
				while (tmp >= s && (*tmp == '\r' || *tmp == '\n')) {
					*tmp = '\0';
					tmp--;
				}

				trace(TRACE_DEBUG, "build_args_array_ext(): got extra line [%s]", s);

				/* start over! */
				i = 0;
				continue;
			}
		}

		/* at an argument start now, walk on until next delimiter
		 * and save argument 
		 */

		for (argstart = i; i < strlen(s) && !strchr(" []()", s[i]);
		     i++)
			if (s[i] == '"') {
				if (s[i - 1] == '\\')
					continue;
				else
					break;
			}
		argsize = i - argstart; // no +1 here to avoid copying delimiter.
		if (!  (the_args[nargs] = (char *) dm_malloc(sizeof(char) * argsize + 1))) {
			/* out of mem */
			while (--nargs >= 0) {
				dm_free(the_args[nargs]);
				the_args[nargs] = NULL;
			}

			trace(TRACE_ERROR,
			      "IMAPD: Not enough memory while building up argument array.");
			return NULL;
		}
		memset((void *) the_args[nargs], '\0', argsize + 1);
		memcpy((void *) the_args[nargs], (void *) &s[argstart], argsize);

		nargs++;
		i--;		/* walked one too far */
	}

	if (paridx != 0) {
		/* error in parenthesis structure */
		while (--nargs >= 0) {
			dm_free(the_args[nargs]);
			the_args[nargs] = NULL;
		}
		return NULL;
	}

	the_args[nargs] = NULL;	/* terminate */

	/* dump args (debug) */
	for (i = 0; the_args[i]; i++) {
		trace(TRACE_DEBUG, "arg[%d]: '%s'\n", i, the_args[i]);
	}

	return the_args;
}

#undef NOPAR
#undef NORMPAR
#undef RIGHTPAR


/*
 * clarify_data()
 *
 * replaces all multiple spaces by a single one except for quoted spaces;
 * removes leading and trailing spaces and a single trailing newline (if present)
 */
void clarify_data(char *str)
{
	int startidx, inquote, endidx;
	unsigned int i;


	/* remove leading spaces */
	for (i = 0; str[i] == ' '; i++);
	memmove(str, &str[i], sizeof(char) * (strlen(&str[i]) + 1));	/* add one for \0 */

	/* remove CR/LF */
	endidx = strlen(str) - 1;
	if (endidx >= 0 && (str[endidx] == '\n' || str[endidx] == '\r'))
		endidx--;

	if (endidx >= 0 && (str[endidx] == '\n' || str[endidx] == '\r'))
		endidx--;


	if (endidx == 0) {
		/* only 1 char left and it is not a space */
		str[1] = '\0';
		return;
	}

	/* remove trailing spaces */
	for (i = endidx; i > 0 && str[i] == ' '; i--);
	if (i == 0) {
		/* empty string remains */
		*str = '\0';
		return;
	}

	str[i + 1] = '\0';

	/* scan for multiple spaces */
	inquote = 0;
	for (i = 0; i < strlen(str); i++) {
		if (str[i] == '"') {
			if ((i > 0 && str[i - 1] != '\\') || i == 0) {
				/* toggle in-quote flag */
				inquote ^= 1;
			}
		}

		if (str[i] == ' ' && !inquote) {
			for (startidx = i; str[i] == ' '; i++);

			if (i - startidx > 1) {
				/* multiple non-quoted spaces found --> remove 'm */
				memmove(&str[startidx + 1], &str[i],
					sizeof(char) * (strlen(&str[i]) +
							1));
				/* update i */
				i = startidx + 1;
			}
		}
	}
}


/*
 * is_textplain()
 *
 * checks if content-type is text/plain
 */
int is_textplain(struct list *hdr)
{
	struct mime_record *mr;
	int i, len;

	if (!hdr)
		return 0;

	mime_findfield("content-type", hdr, &mr);

	if (!mr)
		return 0;

	len = strlen(mr->value);
	for (i = 0; len - i >= (int) sizeof("text/plain"); i++)
		if (strncasecmp
		    (&mr->value[i], "text/plain",
		     sizeof("text/plain") - 1) == 0)
			return 1;

	return 0;
}


/*
 * convert a mySQL date (yyyy-mm-dd hh:mm:ss) to a valid IMAP internal date:
 * [Mon, 30 May 2005 10:01:55 +0100] with mon characters (i.e. 'Apr' for april)
 * return value is valid until next function call.
 * NOTE: if date is not valid, IMAP_STANDARD_DATE is returned
 */
char *date_sql2imap(const char *sqldate)
{
        struct tm tm_sql_date;
	struct tm *tm_imap_date;
	
	time_t ltime;
        char *last;

	memset(&tm_sql_date, 0, sizeof(struct tm));
	
        last = strptime(sqldate,"%Y-%m-%d %T", &tm_sql_date);
        if ( (last == NULL) || (*last != '\0') ) {
                strcpy(_imapdate, IMAP_STANDARD_DATE);
                return _imapdate;
        }

	ltime = mktime (&tm_sql_date);
	tm_imap_date = localtime(&ltime);

        strftime(_imapdate, sizeof(_imapdate), "%a, %d %b %Y %H:%M:%S %z", tm_imap_date);
        return _imapdate;
}

/*
 * convert TO a mySQL date (yyyy-mm-dd) FROM a valid IMAP internal date:
 * dd-mon-yyyy with mon characters (i.e. 'Apr' for april)
 * OR
 * d-mon-yyyy
 * return value is valid until next function call.
 */
char *date_imap2sql(const char *imapdate)
{
	struct tm tm;
	char *last_char;

	memset(&tm, 0, sizeof(struct tm));

	last_char = strptime(imapdate, "%d-%b-%Y", &tm);
	if (last_char == NULL || *last_char != '\0') {
		trace(TRACE_DEBUG, "%s,%s: error parsing IMAP date %s",
		      __FILE__, __func__, imapdate);
		return NULL;
	}
	(void) strftime(_sqldate, SQL_INTERNALDATE_LEN,
			"%Y-%m-%d 00:00:00", &tm);

	return _sqldate;
}

/*
 *
 */
size_t stridx(const char *s, char ch)
{
	size_t i;

	for (i = 0; s[i] && s[i] != ch; i++);

	return i;
}


/*
 * checkchars()
 *
 * performs a check to see if the read data is valid
 * returns 0 if invalid, 1 otherwise
 */
int checkchars(const char *s)
{
	int i;

	for (i = 0; s[i]; i++) {
		if (!strchr(AcceptedChars, s[i])) {
			/* wrong char found */
			return 0;
		}
	}
	return 1;
}


/*
 * checktag()
 *
 * performs a check to see if the read data is valid
 * returns 0 if invalid, 1 otherwise
 */
int checktag(const char *s)
{
	int i;

	for (i = 0; s[i]; i++) {
		if (!strchr(AcceptedTagChars, s[i])) {
			/* wrong char found */
			return 0;
		}
	}
	return 1;
}


/*
 * checkmailboxname()
 *
 * performs a check to see if the mailboxname is valid
 * returns 0 if invalid, 1 otherwise
 */
int checkmailboxname(const char *s)
{
	int i;

	if (strlen(s) == 0)
		return 0;	/* empty name is not valid */

	if (strlen(s) >= IMAP_MAX_MAILBOX_NAMELEN)
		return 0;	/* a too large string is not valid */

	/* check for invalid characters */
	for (i = 0; s[i]; i++) {
		if (!strchr(AcceptedMailboxnameChars, s[i])) {
			/* dirty hack to allow namespaces to function */
			if (i == 0 && s[0] == '#')
				continue;
			/* wrong char found */
			return 0;
		}
	}

	/* check for double '/' */
	for (i = 1; s[i]; i++) {
		if (s[i] == '/' && s[i - 1] == '/')
			return 0;
	}

	/* check if the name consists of a single '/' */
	if (strlen(s) == 1 && s[0] == '/')
		return 0;

	return 1;
}


/*
 * check_date()
 *
 * checks a date for IMAP-date validity:
 * dd-MMM-yyyy
 * 01234567890
 * month three-letter specifier
 */
int check_date(const char *date)
{
	char sub[4];
	int days, i, j;

	if (strlen(date) != strlen("01-Jan-1970")
	    && strlen(date) != strlen("1-Jan-1970"))
		return 0;

	j = (strlen(date) == strlen("1-Jan-1970")) ? 1 : 0;

	if (date[2 - j] != '-' || date[6 - j] != '-')
		return 0;

	days = strtoul(date, NULL, 10);
	strncpy(sub, &date[3 - j], 3);
	sub[3] = 0;

	for (i = 0; i < 12; i++) {
		if (strcasecmp(month_desc[i], sub) == 0)
			break;
	}

	if (i >= 12 || days > month_len[i])
		return 0;

	for (i = 7; i < 11; i++)
		if (!isdigit(date[i - j]))
			return 0;

	return 1;
}



/*
 * check_msg_set()
 *
 * checks if s represents a valid message set 
 */
int check_msg_set(const char *s)
{
	int i, indigit;

	if (!s || !isdigit(s[0]))
		return 0;

	for (i = 1, indigit = 1; s[i]; i++) {
		if (isdigit(s[i]))
			indigit = 1;
		else if (s[i] == ',') {
			if (!indigit && s[i - 1] != '*')
				return 0;

			indigit = 0;
		} else if (s[i] == ':') {
			if (!indigit)
				return 0;

			indigit = 0;
		} else if (s[i] == '*') {
			if (s[i - 1] != ':')
				return 0;
		}
	}

	return 1;
}


/*
 * base64encode()
 *
 * encodes a string using base64 encoding
 */
void base64encode(char *in, char *out)
{
	for (; strlen(in) >= 3; in += 3) {
		*out++ = base64encodestring[(in[0] & 0xFC) >> 2U];
		*out++ =
		    base64encodestring[((in[0] & 0x03) << 4U) |
				       ((in[1] & 0xF0) >> 4U)];
		*out++ =
		    base64encodestring[((in[1] & 0x0F) << 2U) |
				       ((in[2] & 0xC0) >> 6U)];
		*out++ = base64encodestring[(in[2] & 0x3F)];
	}

	if (strlen(in) == 2) {
		/* 16 bits left to encode */
		*out++ = base64encodestring[(in[0] & 0xFC) >> 2U];
		*out++ =
		    base64encodestring[((in[0] & 0x03) << 4U) |
				       ((in[1] & 0xF0) >> 4U)];
		*out++ = base64encodestring[((in[1] & 0x0F) << 2U)];
		*out++ = '=';

		return;
	}

	if (strlen(in) == 1) {
		/* 8 bits left to encode */
		*out++ = base64encodestring[(in[0] & 0xFC) >> 2U];
		*out++ = base64encodestring[((in[0] & 0x03) << 4U)];
		*out++ = '=';
		*out++ = '=';

		return;
	}
}


/*
 * base64decode()
 *
 * decodes a base64 encoded string
 */
void base64decode(char *in, char *out)
{
	for (; strlen(in) >= 4; in += 4) {
		*out++ = (stridx(base64encodestring, in[0]) << 2U)
		    | ((stridx(base64encodestring, in[1]) & 0x30) >> 4U);

		*out++ = ((stridx(base64encodestring, in[1]) & 0x0F) << 4U)
		    | ((stridx(base64encodestring, in[2]) & 0x3C) >> 2U);

		*out++ = ((stridx(base64encodestring, in[2]) & 0x03) << 6U)
		    | (stridx(base64encodestring, in[3]) & 0x3F);
	}

	*out = 0;
}


/*
 * binary_search()
 *
 * performs a binary search on array to find key
 * array should be ascending in values
 *
 * returns -1 if not found. key_idx will hold key if found
 */
int binary_search(const u64_t * array, unsigned arraysize, u64_t key,
		  unsigned int *key_idx)
{
	unsigned low, high, mid = 1;

	assert(key_idx != NULL);
	*key_idx = 0;
	if (arraysize == 0)
		return -1;

	low = 0;
	high = arraysize - 1;

	while (low <= high) {
		mid = (high + low) / (unsigned) 2;
		if (array[mid] < key)
			low = mid + 1;
		else if (array[mid] > key) {
			if (mid > 0)
				high = mid - 1;
			else
				break;
		} else {
			*key_idx = mid;
			return 1;
		}
	}

	return -1;		/* not found */
}

/* 
 * sends a string to outstream, escaping the following characters:
 * "  --> \"
 * \  --> \\
 *
 * double quotes are placed at the beginning and end of the string.
 *
 * returns the number of bytes outputted.
 */
int quoted_string_out(FILE * outstream, const char *s)
{
	int i, cnt;

	char *t, *l = NULL;
        char first, last, penult = '\\';

        l = strdup(s);
        t = l;
        /* strip off dquote */
        first = s[0];
        last = s[strlen(s)-1];
        if (strlen(s) > 2)
                penult = s[strlen(s)-2];
        if ((first == '"') && (last == '"') && (penult != '\\')) {
                l[strlen(l)-1] = '\0';
                l++;
        }
								
	// check wheter we must use literal string
	for (i = 0; l[i]; i++) {
		if (!(l[i] & 0xe0) || (l[i] & 0x80) || (l[i] == '"') || (l[i] == '\\')) {
			cnt = fprintf(outstream, "{");
			cnt += fprintf(outstream, "%lu", (unsigned long) strlen(l));
			cnt += fprintf(outstream, "}\r\n");
			cnt += fprintf(outstream, "%s", l);
			return cnt;
		}
	}

	cnt = fprintf(outstream, "\"");
	cnt += fprintf(outstream, "%s", l);
	cnt += fprintf(outstream, "\"");

	dm_free(t);

	return cnt;
}


/*
 * send_data()
 *
 * sends cnt bytes from a MEM structure to a FILE stream
 * uses a simple buffering system
 */
void send_data(FILE * to, MEM * from, int cnt)
{
	char buf[SEND_BUF_SIZE];

	for (cnt -= SEND_BUF_SIZE; cnt >= 0; cnt -= SEND_BUF_SIZE) {
		mread(buf, SEND_BUF_SIZE, from);
		fwrite(buf, SEND_BUF_SIZE, 1, to);
	}

	if (cnt < 0) {
		mread(buf, cnt + SEND_BUF_SIZE, from);
		fwrite(buf, cnt + SEND_BUF_SIZE, 1, to);
	}

	fflush(to);
}



/*
 * build_imap_search()
 *
 * builds a linked list of search items from a set of IMAP search keys
 * sl should be initialized; new search items are simply added to the list
 *
 * returns -1 on syntax error, -2 on memory error; 0 on success, 1 if ')' has been encountered
 */
int build_imap_search(char **search_keys, struct list *sl, int *idx, int sorted)
{
	search_key_t key;
	int result;

	if (!search_keys || !search_keys[*idx])
		return 0;

	memset(&key, 0, sizeof(key));
	/* coming from _ic_sort */
	if(sorted && (strcasecmp(search_keys[*idx], "arrival") == 0)) {
		key.type = IST_SORT;
		strncpy(key.search, "order by pms.internal_date", MAX_SEARCH_LEN);
		(*idx)++;
	} else if(sorted && (strcasecmp(search_keys[*idx], "from") == 0)) {
		key.type = IST_SORTHDR;
		strncpy(key.hdrfld, "from", MIME_FIELD_MAX);
		(*idx)++;
	} else if(sorted && (strcasecmp(search_keys[*idx], "subject") == 0)) {
		key.type = IST_SORTHDR;
		strncpy(key.hdrfld, "subject", MIME_FIELD_MAX);
		(*idx)++;
	} else if(sorted && (strcasecmp(search_keys[*idx], "cc") == 0)) {
		key.type = IST_SORTHDR;
		strncpy(key.hdrfld, "cc", MIME_FIELD_MAX);
		(*idx)++;
	} else if(sorted && (strcasecmp(search_keys[*idx], "to") == 0)) {
		key.type = IST_SORTHDR;
		strncpy(key.hdrfld, "to", MIME_FIELD_MAX);
		(*idx)++;
	} else if(sorted && (strcasecmp(search_keys[*idx], "reverse") == 0)) {
		/* TODO */ 
		(*idx)++;
	} else if(sorted && (strcasecmp(search_keys[*idx], "size") == 0)) {
		/* TODO */ 
		(*idx)++;
	} else if(sorted && (strcasecmp(search_keys[*idx], "iso-8859-1") == 0)) {
		/* TODO */ 
		(*idx)++;
//	} else if(sorted && (strcasecmp(search_keys[*idx], "us-ascii") == 0)) {
//		/* TODO */ 
//		(*idx)++;
	} else if(sorted && (strcasecmp(search_keys[*idx], "date") == 0)) {
		/* TODO */ 
		(*idx)++;
	} else if(sorted && (strcasecmp(search_keys[*idx], "all") == 0)) {
		/* TODO */ 
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "all") == 0) {
		key.type = IST_SET;
		strcpy(key.search, "1:*");
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "uid") == 0) {
		key.type = IST_SET_UID;
		if (!search_keys[*idx + 1])
			return -1;

		(*idx)++;

		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		if (!check_msg_set(key.search))
			return -1;

		(*idx)++;
	}

	/*
	 * FLAG search keys
	 */

	else if (strcasecmp(search_keys[*idx], "answered") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "answered_flag=1", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "deleted") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "deleted_flag=1", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "flagged") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "flagged_flag=1", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "recent") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "recent_flag=1", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "seen") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "seen_flag=1", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "keyword") == 0) {
		/* no results from this one */
		if (!search_keys[(*idx) + 1])	/* there should follow an argument */
			return -1;

		(*idx)++;

		key.type = IST_SET;
		strcpy(key.search, "0");
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "draft") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "draft_flag=1", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "new") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "(seen_flag=0 AND recent_flag=1)",
			MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "old") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "recent_flag=0", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "unanswered") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "answered_flag=0", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "undeleted") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "deleted_flag=0", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "unflagged") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "flagged_flag=0", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "unseen") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "seen_flag=0", MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "unkeyword") == 0) {
		/* matches every msg */
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;

		key.type = IST_SET;
		strcpy(key.search, "1:*");
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "undraft") == 0) {
		key.type = IST_FLAG;
		strncpy(key.search, "draft_flag=0", MAX_SEARCH_LEN);
		(*idx)++;
	}

	/*
	 * HEADER search keys
	 */

	else if (strcasecmp(search_keys[*idx], "bcc") == 0) {
		key.type = IST_HDR;
		strncpy(key.hdrfld, "bcc", MIME_FIELD_MAX);
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "cc") == 0) {
		key.type = IST_HDR;
		strncpy(key.hdrfld, "cc", MIME_FIELD_MAX);
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "from") == 0) {
		key.type = IST_HDR;
		strncpy(key.hdrfld, "from", MIME_FIELD_MAX);
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "to") == 0) {
		key.type = IST_HDR;
		strncpy(key.hdrfld, "to", MIME_FIELD_MAX);
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "subject") == 0) {
		key.type = IST_HDR;
		strncpy(key.hdrfld, "subject", MIME_FIELD_MAX);
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "header") == 0) {
		key.type = IST_HDR;
		if (!search_keys[(*idx) + 1] || !search_keys[(*idx) + 2])
			return -1;

		strncpy(key.hdrfld, search_keys[(*idx) + 1],
			MIME_FIELD_MAX);
		strncpy(key.search, search_keys[(*idx) + 2],
			MAX_SEARCH_LEN);

		(*idx) += 3;
	} else if (strcasecmp(search_keys[*idx], "sentbefore") == 0) {
		key.type = IST_HDRDATE_BEFORE;
		strncpy(key.hdrfld, "date", MIME_FIELD_MAX);
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "senton") == 0) {
		key.type = IST_HDRDATE_ON;
		strncpy(key.hdrfld, "date", MIME_FIELD_MAX);
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "sentsince") == 0) {
		key.type = IST_HDRDATE_SINCE;
		strncpy(key.hdrfld, "date", MIME_FIELD_MAX);
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	}

	/*
	 * INTERNALDATE keys
	 */

	else if (strcasecmp(search_keys[*idx], "before") == 0) {
		key.type = IST_IDATE;
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		if (!check_date(search_keys[*idx]))
			return -1;

		strncpy(key.search, "internal_date<'", MAX_SEARCH_LEN);
		strncat(key.search, date_imap2sql(search_keys[*idx]),
			MAX_SEARCH_LEN - sizeof("internal_date<''"));
		strcat(key.search, "'");

		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "on") == 0) {
		key.type = IST_IDATE;
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		if (!check_date(search_keys[*idx]))
			return -1;

		strncpy(key.search, "internal_date LIKE '",
			MAX_SEARCH_LEN);
		strncat(key.search, date_imap2sql(search_keys[*idx]),
			MAX_SEARCH_LEN - sizeof("internal_date LIKE 'x'"));
		strcat(key.search, "%'");

		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "since") == 0) {
		key.type = IST_IDATE;
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		if (!check_date(search_keys[*idx]))
			return -1;

		strncpy(key.search, "internal_date>'", MAX_SEARCH_LEN);
		strncat(key.search, date_imap2sql(search_keys[*idx]),
			MAX_SEARCH_LEN - sizeof("internal_date>''"));
		strcat(key.search, "'");

		(*idx)++;
	}

	/*
	 * DATA-keys
	 */

	else if (strcasecmp(search_keys[*idx], "body") == 0) {
		key.type = IST_DATA_BODY;
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "text") == 0) {
		key.type = IST_DATA_TEXT;
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		strncpy(key.search, search_keys[(*idx)], MAX_SEARCH_LEN);
		(*idx)++;
	}

	/*
	 * SIZE keys
	 */

	else if (strcasecmp(search_keys[*idx], "larger") == 0) {
		key.type = IST_SIZE_LARGER;
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		key.size = strtoull(search_keys[(*idx)], NULL, 10);
		(*idx)++;
	} else if (strcasecmp(search_keys[*idx], "smaller") == 0) {
		key.type = IST_SIZE_SMALLER;
		if (!search_keys[(*idx) + 1])
			return -1;

		(*idx)++;
		key.size = strtoull(search_keys[(*idx)], NULL, 10);
		(*idx)++;
	}

	/*
	 * NOT, OR, ()
	 */
	else if (strcasecmp(search_keys[*idx], "not") == 0) {
		key.type = IST_SUBSEARCH_NOT;

		(*idx)++;
		if ((result =
		     build_imap_search(search_keys, &key.sub_search,
				       idx, sorted )) < 0) {
			list_freelist(&key.sub_search.start);
			return result;
		}

		/* a NOT should be unary */
		if (key.sub_search.total_nodes != 1) {
			free_searchlist(&key.sub_search);
			return -1;
		}
	} else if (strcasecmp(search_keys[*idx], "or") == 0) {
		key.type = IST_SUBSEARCH_OR;

		(*idx)++;
		if ((result = build_imap_search(search_keys, &key.sub_search,
				       idx, sorted)) < 0) {
			list_freelist(&key.sub_search.start);
			return result;
		}

		if ((result = build_imap_search(search_keys, &key.sub_search,
				       idx, sorted )) < 0) {
			list_freelist(&key.sub_search.start);
			return result;
		}

		/* an OR should be binary */
		if (key.sub_search.total_nodes != 2) {
			free_searchlist(&key.sub_search);
			return -1;
		}

	} else if (strcasecmp(search_keys[*idx], "(") == 0) {
		key.type = IST_SUBSEARCH_AND;

		(*idx)++;
		while ((result = build_imap_search(search_keys, &key.sub_search,
					  idx, sorted)) == 0 && search_keys[*idx]);

		if (result < 0) {
			/* error */
			list_freelist(&key.sub_search.start);
			return result;
		}

		if (result == 0) {
			/* no ')' encountered (should not happen, parentheses are matched at the command line) */
			free_searchlist(&key.sub_search);
			return -1;
		}
	} else if (strcasecmp(search_keys[*idx], ")") == 0) {
		(*idx)++;
		return 1;
	} else if (check_msg_set(search_keys[*idx])) {
		key.type = IST_SET;
		strncpy(key.search, search_keys[*idx], MAX_SEARCH_LEN);
		(*idx)++;
	} else {
		trace(TRACE_DEBUG,"%s,%s: unknown search key [%s]", __FILE__, __func__, search_keys[*idx]);
		return -1;
	}

	if (!list_nodeadd(sl, &key, sizeof(key)))
		return -2;

	return 0;
}


/*
 * perform_imap_search()
 *
 * returns 0 on succes, -1 on dbase error, -2 on memory error, 1 if result set is too small
 * (new mail has been added to mailbox while searching, mailbox data out of sync)
 */
int perform_imap_search(unsigned int *rset, int setlen, search_key_t * sk,
			mailbox_t * mb, int sorted, int condition)
{
	search_key_t *subsk;
	struct element *el;
	int result, i;
	unsigned int *newset = NULL;
	int subtype = IST_SUBSEARCH_OR;

	if (!rset) {
		trace(TRACE_ERROR, "%s,%s: can't perform search on empty set", __FILE__, __func__);
		return -2;	/* stupidity */
	}

	if (!sk) {
		trace(TRACE_ERROR, "%s,%s: can't perform search for empty searchkey", __FILE__, __func__);
		return -2;	/* no search */
	}

	newset = (unsigned int *) dm_malloc(sizeof(unsigned int) * setlen);
	
	if (!newset)
		return -2;

	memset(newset,'\0', sizeof(newset));

	trace(TRACE_DEBUG, "%s,%s: search key type [%d], condition [%d]", 
			__FILE__, __func__, sk->type, condition);
	
	switch (sk->type) {
	case IST_SET:
		build_set(rset, setlen, sk->search);
		break;

	case IST_SET_UID:
		build_uid_set(rset, setlen, sk->search, mb);
		break;

	case IST_SORT:
		result = db_search(rset, setlen, sk->search, mb, sk->type);
		dm_free(newset);
		return 0;
		break;

	case IST_SORTHDR:
		result = db_sort_parsed(rset, setlen, sk, mb, condition);
		dm_free(newset);
		return 0;
		break;

 
	case IST_FLAG:
		result = db_search(rset, setlen, sk->search, mb, sk->type);
		if (result != 0) {
			dm_free(newset);
			return result;
		}
		break;

	case IST_HDR:
	case IST_HDRDATE_BEFORE:
	case IST_HDRDATE_ON:
	case IST_HDRDATE_SINCE:
	case IST_DATA_BODY:
	case IST_DATA_TEXT:
	case IST_SIZE_LARGER:
	case IST_SIZE_SMALLER:
		/* these all have in common that a message should be parsed before 
		   matching is possible
		 */
		result = db_search_parsed(rset, setlen, sk, mb, condition);
		break;

	case IST_IDATE:
		result = db_search(rset, setlen, sk->search, mb, sk->type);
		if (result != 0) {
			dm_free(newset);
			return result;
		}
		break;

	case IST_SUBSEARCH_NOT:
	case IST_SUBSEARCH_AND:
		subtype = IST_SUBSEARCH_AND;

	case IST_SUBSEARCH_OR:
		el = list_getstart(&sk->sub_search);
		while (el) {
			subsk = (search_key_t *) el->data;

			if (subsk->type == IST_SUBSEARCH_OR)
				memset(newset, 0, sizeof(int) * setlen);
			else
				for (i = 0; i < setlen; i++)
					newset[i] = rset[i];

			if ((result = perform_imap_search(newset, setlen, subsk, mb, sorted, sk->type))) {
				dm_free(newset);
				return result;
			}
			
			if (! sorted)
				combine_sets(rset, newset, setlen, subtype);
			else {
				for (i=0; i<setlen; i++)
					rset[i] = newset[i];
			}
 
			el = el->nextnode;
		}

		if (sk->type == IST_SUBSEARCH_NOT)
			invert_set(rset, setlen);

		break;

	default:
		dm_free(newset);
		return -2;	/* ??? */
	}

	dm_free(newset);
	return 0;
}


/*
 * frees the search-list sl
 *
 */
void free_searchlist(struct list *sl)
{
	search_key_t *sk;
	struct element *el;

	if (!sl)
		return;

	el = list_getstart(sl);

	while (el) {
		sk = (search_key_t *) el->data;

		free_searchlist(&sk->sub_search);
		list_freelist(&sk->sub_search.start);

		el = el->nextnode;
	}

	list_freelist(&sl->start);
	return;
}


void invert_set(unsigned int *set, int setlen)
{
	int i;

	if (!set)
		return;

	for (i = 0; i < setlen; i++)
		set[i] = !set[i];
}


void combine_sets(unsigned int *dest, unsigned int *sec, int setlen, int type)
{
	int i;

	if (!dest || !sec)
		return;

	if (type == IST_SUBSEARCH_AND) {
		for (i = 0; i < setlen; i++)
			dest[i] = (sec[i] && dest[i]);
	} else if (type == IST_SUBSEARCH_OR) {
		for (i = 0; i < setlen; i++)
			dest[i] = (sec[i] || dest[i]);
	}
}


/* 
 * build_set()
 *
 * builds a msn-set from a IMAP message set spec. the IMAP set is supposed to be correct,
 * no checks are performed.
 */
void build_set(unsigned int *set, unsigned int setlen, char *cset)
{
	unsigned int i;
	u64_t num, num2;
	char *sep = NULL;

	if (!set)
		return;

	memset(set, 0, setlen * sizeof(int));

	if (!cset)
		return;

	do {
		num = strtoull(cset, &sep, 10);

		if (num <= setlen && num > 0) {
			if (!*sep)
				set[num - 1] = 1;
			else if (*sep == ',') {
				set[num - 1] = 1;
				cset = sep + 1;
			} else {
				/* sep == ':' here */
				sep++;
				if (*sep == '*') {
					for (i = num - 1; i < setlen; i++)
						set[i] = 1;

					cset = sep + 1;
				} else {
					cset = sep;
					num2 = strtoull(cset, &sep, 10);

					if (num2 > setlen)
						num2 = setlen;
					if (num2 > 0) {
						/* NOTE: here: num2 > 0, num > 0 */
						if (num2 < num) {
							/* swap! */
							i = num;
							num = num2;
							num2 = i;
						}

						for (i = num - 1; i < num2;
						     i++)
							set[i] = 1;
					}
					if (*sep)
						cset = sep + 1;
				}
			}
		} else {
			/* invalid num, skip it */
			if (*sep) {
				cset = sep + 1;
				sep++;
			}
		}
	} while (sep && *sep && cset && *cset);
}


/* 
 * build_uid_set()
 *
 * as build_set() but takes uid's instead of MSN's
 */
void build_uid_set(unsigned int *set, unsigned int setlen, char *cset,
		   mailbox_t * mb)
{
	unsigned int i, msn, msn2;
	int result;
	int num2found = 0;
	u64_t num, num2;
	char *sep = NULL;

	if (!set)
		return;

	memset(set, 0, setlen * sizeof(int));

	if (!cset || setlen == 0)
		return;

	do {
		num = strtoull(cset, &sep, 10);
		result =
		    binary_search(mb->seq_list, mb->exists, num, &msn);

		if (result < 0 && num < mb->seq_list[mb->exists - 1]) {
			/* ok this num is not a UID, but if a range is specified (i.e. 1:*) 
			 * it is valid -> check *sep
			 */
			if (*sep == ':') {
                                result = 1; /*MR as we have a valid number here, let's tell the code below that it should run*/
				for (msn = 0; mb->seq_list[msn] < num;
				     msn++);
				if (msn >= mb->exists)
					msn = mb->exists - 1;
			}
		}

		if (result >= 0) {
			if (!*sep)
				set[msn] = 1;
			else if (*sep == ',') {
				set[msn] = 1;
				cset = sep + 1;
			} else {
				/* sep == ':' here */
				sep++;
				if (*sep == '*') {
					for (i = msn; i < setlen; i++)
						set[i] = 1;

					cset = sep + 1;
				} else {
					/* fetch second number */
					cset = sep;
					num2 = strtoull(cset, &sep, 10);
					result =
					    binary_search(mb->seq_list,
							  mb->exists, num2,
							  &msn2);

					if (result < 0) {
						/* in a range: (like 1:1000) so this number doesnt need to exist;
						 * find the closest match below this UID value
						 */
						if (mb->exists == 0)
							num2found = 0;
						else {
							for (msn2 =
							     mb->exists -
							     1;; msn2--) {
								if (msn2 ==
								    0
								    && mb->
								    seq_list
								    [msn2]
								    > num2) {
									num2found
									    =
									    0;
									break;
								} else
								    if
								    (mb->
								     seq_list
								     [msn2]
								     <=
								     num2)
								{
									/* found! */
									num2found
									    =
									    1;
									break;
								}
							}
						}

					} else
						num2found = 1;

					if (num2found == 1) {
						if (msn2 < msn) {
							/* swap! */
							i = msn;
							msn = msn2;
							msn2 = i;
						}

						for (i = msn; i <= msn2;
						     i++)
							set[i] = 1;
					}

					if (*sep)
						cset = sep + 1;
				}
			}
		} else {
			/* invalid num, skip it */
			if (*sep) {
				cset = sep + 1;
				sep++;
			}
		}
	} while (sep && *sep && cset && *cset);
}


void dumpsearch(search_key_t * sk, int level)
{
	char *spaces = (char *) dm_malloc(level * 3 + 1);
	struct element *el;
	search_key_t *subsk;

	if (!spaces)
		return;

	memset(spaces, ' ', level * 3);
	spaces[level * 3] = 0;

	if (!sk) {
		trace(TRACE_DEBUG, "%s(null)\n", spaces);
		dm_free(spaces);
		return;
	}

	switch (sk->type) {
	case IST_SUBSEARCH_NOT:
		trace(TRACE_DEBUG, "%sNOT\n", spaces);

		el = list_getstart(&sk->sub_search);
		if (el)
			subsk = (search_key_t *) el->data;
		else
			subsk = NULL;

		dumpsearch(subsk, level + 1);
		break;

	case IST_SUBSEARCH_AND:
		trace(TRACE_DEBUG, "%sAND\n", spaces);
		el = list_getstart(&sk->sub_search);

		while (el) {
			subsk = (search_key_t *) el->data;
			dumpsearch(subsk, level + 1);

			el = el->nextnode;
		}
		break;

	case IST_SUBSEARCH_OR:
		trace(TRACE_DEBUG, "%sOR\n", spaces);
		el = list_getstart(&sk->sub_search);

		while (el) {
			subsk = (search_key_t *) el->data;
			dumpsearch(subsk, level + 1);

			el = el->nextnode;
		}
		break;

	default:
		trace(TRACE_DEBUG, "%s[type %d] \"%s\"\n", spaces,
		      sk->type, sk->search);
	}

	dm_free(spaces);
	return;
}


/*
 * closes the msg cache
 */
void close_cache()
{
	if (cached_msg.msg_parsed)
		db_free_msg(&cached_msg.msg);

	cached_msg.num = -1;
	cached_msg.msg_parsed = 0;
	memset(&cached_msg.msg, 0, sizeof(cached_msg.msg));

	mclose(&cached_msg.memdump);
	mclose(&cached_msg.tmpdump);
}

/* 
 * init cache 
 */
int init_cache()
{
	cached_msg.num = -1;
	cached_msg.msg_parsed = 0;
	memset(&cached_msg.msg, 0, sizeof(cached_msg.msg));

	cached_msg.memdump = mopen();
	if (!cached_msg.memdump)
		return -1;

	cached_msg.tmpdump = mopen();
	if (!cached_msg.tmpdump) {
		mclose(&cached_msg.memdump);
		return -1;
	}

	cached_msg.file_dumped = 0;
	cached_msg.dumpsize = 0;
	return 0;
}

/* unwrap strings */
int mime_unwrap(char *to, const char *from) 
{
	while (*from) {
		if (((*from == '\n') || (*from == '\r')) && isspace(*(from+1))) {
			from+=2;
			continue;
		}
		*to++=*from++;
	}
	*to='\0';
	return 0;
}
/* 
 * sort_search()
 * 
 * reorder searchlist by using search cost as the sort key
 * 
 */ 

int sort_search(struct list *searchlist) 
{
	struct element *el;
	search_key_t *left = NULL, *right = NULL, *tmp=NULL;
	
	if (!searchlist) 
		return 0;

	el = list_getstart(searchlist);
	while (el != NULL) {
		if (! el->nextnode)
			break;
		left = el->data;
		right = (el->nextnode)->data;

		/* recurse into sub_search if necessary */
		switch(left->type) {
			case IST_SUBSEARCH_AND:
			case IST_SUBSEARCH_NOT:
			case IST_SUBSEARCH_OR:
				sort_search(&left->sub_search);
				break;
				;;
		}
		/* switch elements to sorted order */
		if (strcmp((char *)search_cost[left->type],(char *)search_cost[right->type]) > 0) {
			tmp = el->data;
			el->data = el->nextnode->data;
			el->nextnode->data = tmp;
			/* when in doubt, use brute force: starting over */
			el = list_getstart(searchlist);
			continue;
		}
		
		el = el->nextnode;
	}
	return 0;
}




syntax highlighted by Code2HTML, v. 0.9.1