/*
 * libEtPan! -- a mail stuff library
 *
 * Copyright (C) 2001, 2005 - DINH Viet Hoa
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the libEtPan! project nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * $Id: maildriver_tools.c,v 1.36 2008/02/20 22:15:51 hoa Exp $
 */

#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "maildriver_tools.h"

#include "libetpan-config.h"

#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#ifdef WIN32
#	include "win_etpan.h"
#else
#	include <dirent.h>
#endif
#ifdef HAVE_UNISTD_H
#	include <unistd.h>
#endif
#include "maildriver.h"
#include "mailmessage.h"
#include "mailstream.h"
#include "mailmime.h"
#include "mail_cache_db.h"

/* ********************************************************************* */
/* tools */


int
maildriver_generic_get_envelopes_list(mailsession * session,
    struct mailmessage_list * env_list)
{
  int r;
  unsigned i;

  for(i = 0 ; i < carray_count(env_list->msg_tab) ; i ++) {
    mailmessage * msg;
    
    msg = carray_get(env_list->msg_tab, i);
    
    if (msg->msg_fields == NULL) {
      struct mailimf_fields * fields;

      r = mailmessage_fetch_envelope(msg, &fields);
      if (r != MAIL_NO_ERROR) {
	/* do nothing */
      }
      else {
	msg->msg_fields = fields;
      }
      mailmessage_flush(msg);
    }
  }

  return MAIL_NO_ERROR;
}


#if 0
static int is_search_header_only(struct mail_search_key * key)
{
  clistiter * cur;
  int result;

  switch (key->type) {
  case MAIL_SEARCH_KEY_ANSWERED:
  case MAIL_SEARCH_KEY_BCC:
  case MAIL_SEARCH_KEY_BEFORE:
  case MAIL_SEARCH_KEY_CC:
  case MAIL_SEARCH_KEY_DELETED:
  case MAIL_SEARCH_KEY_FLAGGED:
  case MAIL_SEARCH_KEY_FROM:
  case MAIL_SEARCH_KEY_NEW:
  case MAIL_SEARCH_KEY_OLD:
  case MAIL_SEARCH_KEY_ON:
  case MAIL_SEARCH_KEY_RECENT:
  case MAIL_SEARCH_KEY_SEEN:
  case MAIL_SEARCH_KEY_SINCE:
  case MAIL_SEARCH_KEY_SUBJECT:
  case MAIL_SEARCH_KEY_TO:
  case MAIL_SEARCH_KEY_UNANSWERED:
  case MAIL_SEARCH_KEY_UNDELETED:
  case MAIL_SEARCH_KEY_UNFLAGGED:
  case MAIL_SEARCH_KEY_UNSEEN:
  case MAIL_SEARCH_KEY_HEADER:
  case MAIL_SEARCH_KEY_LARGER:
  case MAIL_SEARCH_KEY_NOT:
  case MAIL_SEARCH_KEY_SMALLER:
  case MAIL_SEARCH_KEY_ALL:
    return TRUE;

  case MAIL_SEARCH_KEY_BODY:
  case MAIL_SEARCH_KEY_TEXT:
    return FALSE;

  case MAIL_SEARCH_KEY_OR:
    return (is_search_header_only(key->or1) &&
	    is_search_header_only(key->or2));

  case MAIL_SEARCH_KEY_MULTIPLE:
    result = TRUE;
    for (cur = clist_begin(key->multiple) ; cur != NULL ;
         cur = clist_next(cur))
      result = result && is_search_header_only(clist_content(cur));
    return result;

  default:
    return TRUE;
  }
}

static int match_header(struct mailimf_fields * fields,
    char * name, char * value)
{
  clistiter * cur;

  for(cur = clist_begin(fields->list) ; cur != NULL ;
      cur = clist_content(cur)) {
    struct mailimf_field * field;
    struct mailimf_optional_field * opt_field;

    field = clist_content(cur);
    opt_field = field->optional_field;
    if ((char) toupper((unsigned char) opt_field->name[0]) ==
        (char) toupper((unsigned char) name[0])) {
      if (strcasecmp(opt_field->name, name) == 0)
	if (strstr(opt_field->value, value) != NULL)
	  return TRUE;
    }
  }
  return FALSE;
}

static int comp_date(struct mailimf_fields * fields,
    struct mailimf_date_time * ref_date)
{
  clistiter * cur;
  struct mailimf_date_time * date;
  int r;

  date = NULL;
  for(cur = clist_begin(fields->list) ; cur != NULL ;
      cur = clist_content(cur)) {
    struct mailimf_field * field;
    struct mailimf_optional_field * opt_field;

    field = clist_content(cur);
    opt_field = field->optional_field;
    if ((char) toupper((unsigned char) opt_field->name[0]) == 'D') {
      if (strcasecmp(opt_field->name, "Date") == 0) {
	size_t cur_token;

	cur_token = 0;
	r = mailimf_date_time_parse(opt_field->value, strlen(opt_field->value),
				    &cur_token, &date);
	if (r == MAILIMF_NO_ERROR)
	  break;
	else if (r == MAILIMF_ERROR_PARSE) {
	  /* do nothing */
	}
	else
	  break;
      }
    }
  }

  if (date == NULL)
    return 0;

  return mailimf_date_time_comp(date, ref_date);
}

static int match_messages(char * message,
    size_t size,
    struct mailimf_fields * fields,
    int32_t flags,
    char * charset,
    struct mail_search_key * key)
{
  clistiter * cur;
  size_t length;
  size_t cur_token;
  int r;

  switch (key->type) {

    /* flags */
  case MAIL_SEARCH_KEY_ANSWERED:
    return ((flags & MAIL_FLAG_ANSWERED) != 0);

  case MAIL_SEARCH_KEY_FLAGGED:
    return ((flags & MAIL_FLAG_FLAGGED) != 0);

  case MAIL_SEARCH_KEY_DELETED:
    return ((flags & MAIL_FLAG_DELETED) != 0);

  case MAIL_SEARCH_KEY_RECENT:
    return ((flags & MAIL_FLAG_NEW) != 0) &&
      ((flags & MAIL_FLAG_SEEN) == 0);

  case MAIL_SEARCH_KEY_SEEN:
    return ((flags & MAIL_FLAG_SEEN) != 0);

  case MAIL_SEARCH_KEY_NEW:
    return ((flags & MAIL_FLAG_NEW) != 0);

  case MAIL_SEARCH_KEY_OLD:
    return ((flags & MAIL_FLAG_NEW) == 0);

  case MAIL_SEARCH_KEY_UNANSWERED:
    return ((flags & MAIL_FLAG_ANSWERED) == 0);

  case MAIL_SEARCH_KEY_UNDELETED:
    return ((flags & MAIL_FLAG_DELETED) == 0);

  case MAIL_SEARCH_KEY_UNFLAGGED:
    return ((flags & MAIL_FLAG_FLAGGED) == 0);

  case MAIL_SEARCH_KEY_UNSEEN:
    return ((flags & MAIL_FLAG_SEEN) == 0);

    /* headers */
  case MAIL_SEARCH_KEY_BCC:
    return match_header(fields, "Bcc", key->bcc);

  case MAIL_SEARCH_KEY_CC:
    return match_header(fields, "Cc", key->cc);

  case MAIL_SEARCH_KEY_FROM:
    return match_header(fields, "From", key->from);

  case MAIL_SEARCH_KEY_SUBJECT:
    return match_header(fields, "Subject", key->subject);

  case MAIL_SEARCH_KEY_TO:
    return match_header(fields, "To", key->to);

  case MAIL_SEARCH_KEY_HEADER:
    return match_header(fields, key->header_name, key->header_value);

    /* date */
  case MAIL_SEARCH_KEY_BEFORE:
    return (comp_date(fields, key->before) <= 0);

  case MAIL_SEARCH_KEY_ON:
    return (comp_date(fields, key->before) == 0);

  case MAIL_SEARCH_KEY_SINCE:
    return (comp_date(fields, key->before) >= 0);
    
    /* boolean */
  case MAIL_SEARCH_KEY_NOT:
    return (!match_messages(message, size, fields, flags, charset, key->not));
  case MAIL_SEARCH_KEY_OR:
    return (match_messages(message, size, fields, flags, charset, key->or1) ||
	    match_messages(message, size, fields, flags, charset, key->or2));

  case MAIL_SEARCH_KEY_MULTIPLE:
    for(cur = clist_begin(key->multiple) ; cur != NULL ;
        cur = clist_next(cur)) {
      if (!match_messages(message, size, fields, flags, charset,
              clist_content(cur)))
	return FALSE;
    }

    return TRUE;

    /* size */
  case MAIL_SEARCH_KEY_SMALLER:
    return (size <= key->smaller);

  case MAIL_SEARCH_KEY_LARGER:
    return (size >= key->larger);

  case MAIL_SEARCH_KEY_BODY:
    length = strlen(message);

    cur_token = 0;
    while (1) {
      r = mailimf_ignore_field_parse(message, length, &cur_token);
      if (r == MAILIMF_NO_ERROR) {
	/* do nothing */
      }
      else
	break;
    }

    return (strstr(message + cur_token, key->body) != NULL);

  case MAIL_SEARCH_KEY_TEXT:
    return (strstr(message, key->body) != NULL);
    
  case MAIL_SEARCH_KEY_ALL:
  default:
    return TRUE;
  }
}

int maildriver_generic_search_messages(mailsession * session, char * charset,
    struct mail_search_key * key,
    struct mail_search_result ** result)
{
  int header;
  clist * list;
  struct mail_search_result * search_result;
  int r;
  struct mailmessage_list * env_list;
  int res;
  unsigned int i;

  header = is_search_header_only(key);

  r = mailsession_get_messages_list(session, &env_list);
  if (r != MAIL_NO_ERROR)
    return r;

  list = NULL;
  for(i = 0 ; i < carray_count(env_list->tab) ; i ++) {
    char * message;
    size_t length;
    struct mail_info * info;
    uint32_t flags;
    struct mailimf_fields * fields;
    size_t cur_token;

    info = carray_get(env_list->tab, i);

    if (!header) {
      r = mailsession_fetch_message(session, info->indx, &message, &length);
      if (r != MAIL_NO_ERROR) {
	res = r;
	goto free_list;
      }

      cur_token = 0;
      r = mailimf_optional_fields_parse(message, length,
					&cur_token, &fields);
      if (r != MAILIMF_NO_ERROR) {
	res = MAIL_ERROR_PARSE;
	goto free_list;
      }
    }
    else {
      char * msg_header;
      int r;
      size_t cur_token;
      size_t header_len;

      r = mailsession_fetch_message_header(session, info->indx, &msg_header,
					   &header_len);
      if (r != MAIL_NO_ERROR) {
	res = r;
	goto free_list;
      }

      message = NULL;
      cur_token = 0;
      r = mailimf_optional_fields_parse(msg_header, header_len,
					&cur_token, &fields);
      if (r != MAILIMF_NO_ERROR) {
	res = MAIL_ERROR_PARSE;
	goto free_list;
      }

      mailsession_fetch_result_free(session, msg_header);
    }

    r = mailsession_get_message_flags(session, info->indx, &flags);
    if (r != MAIL_NO_ERROR) {
      res = r;
      goto free_list;
    }

    if (match_messages(message, info->size, fields, flags,
		       charset, key)) {
      uint32_t * pnum;

      pnum = malloc(sizeof(* pnum));
      if (pnum == NULL) {
	if (message != NULL)
	  mailsession_fetch_result_free(session, message);
	res = MAIL_ERROR_MEMORY;
	goto free_list;
      }

      * pnum = info->indx;

      r = clist_append(list, pnum);
      if (r < 0) {
	free(pnum);
	if (message != NULL)
	  mailsession_fetch_result_free(session, message);
	res = MAIL_ERROR_MEMORY;
	goto free_list;
      }
    }

    if (message != NULL)
      mailsession_fetch_result_free(session, message);
  }

  search_result =  mail_search_result_new(list);
  if (search_result == NULL) {
    res = MAIL_ERROR_MEMORY;
    goto free_list;
  }

  * result = search_result;

  return MAIL_NO_ERROR;

 free_list:
  clist_foreach(list, (clist_func) free, NULL);
  clist_free(list);
  mailmessage_list_free(env_list);
  return res;
}
#endif

#if 0
int maildriver_generic_search_messages(mailsession * session, char * charset,
    struct mail_search_key * key,
    struct mail_search_result ** result)
{
  return MAIL_ERROR_NOT_IMPLEMENTED;
}
#endif

int
maildriver_env_list_to_msg_list(struct mailmessage_list * env_list,
    clist ** result)
{
  clist * msg_list;
  int r;
  int res;
  unsigned int i;

  msg_list = clist_new();
  if (msg_list == NULL) {
    res = MAIL_ERROR_MEMORY;
    goto err;
  }

  for(i = 0 ; i < carray_count(env_list->msg_tab) ; i ++) {
    mailmessage * msg;

    msg = carray_get(env_list->msg_tab, i);

    if (msg->msg_fields == NULL) {
      uint32_t * pindex;

      pindex = malloc(sizeof(* pindex));
      if (pindex == NULL) {
	res = MAIL_ERROR_MEMORY;
	goto free_msg_list;
      }

      * pindex = msg->msg_index;

      r = clist_append(msg_list, pindex);
      if (r < 0) {
	free(pindex);
	res = MAIL_ERROR_MEMORY;
	goto free_msg_list;
      }

    }
  }

  * result = msg_list;

  return MAIL_NO_ERROR;

 free_msg_list:
  clist_foreach(msg_list, (clist_func) free, NULL);
  clist_free(msg_list);
 err:
  return res;
}


int
maildriver_env_list_to_msg_list_no_flags(struct mailmessage_list * env_list,
    clist ** result)
{
  clist * msg_list;
  int r;
  int res;
  unsigned int i;

  msg_list = clist_new();
  if (msg_list == NULL) {
    res = MAIL_ERROR_MEMORY;
    goto err;
  }

  for(i = 0 ; i < carray_count(env_list->msg_tab) ; i ++) {
    mailmessage * msg;

    msg = carray_get(env_list->msg_tab, i);

    if (msg->msg_flags == NULL) {
      uint32_t * pindex;

      pindex = malloc(sizeof(* pindex));
      if (pindex == NULL) {
	res = MAIL_ERROR_MEMORY;
	goto free_msg_list;
      }

      * pindex = msg->msg_index;

      r = clist_append(msg_list, pindex);
      if (r < 0) {
	free(pindex);
	res = MAIL_ERROR_MEMORY;
	goto free_msg_list;
      }

    }
  }

  * result = msg_list;

  return MAIL_NO_ERROR;

 free_msg_list:
  clist_foreach(msg_list, (clist_func) free, NULL);
  clist_free(msg_list);
 err:
  return res;
}



int maildriver_imf_error_to_mail_error(int error)
{
  switch (error) {
  case MAILIMF_NO_ERROR:
    return MAIL_NO_ERROR;

  case MAILIMF_ERROR_PARSE:
    return MAIL_ERROR_PARSE;

  case MAILIMF_ERROR_MEMORY:
    return MAIL_ERROR_MEMORY;

  case MAILIMF_ERROR_INVAL:
    return MAIL_ERROR_INVAL;

  case MAILIMF_ERROR_FILE:
    return MAIL_ERROR_FILE;
    
  default:
    return MAIL_ERROR_INVAL;
  }
}

char * maildriver_quote_mailbox(const char * mb)
{
  MMAPString * gstr;
  char * str;

  gstr = mmap_string_new("");
  if (gstr == NULL)
    return NULL;
  
  while (* mb != 0) {
    char hex[3];

    if (((* mb >= 'a') && (* mb <= 'z')) ||
	((* mb >= 'A') && (* mb <= 'Z')) ||
	((* mb >= '0') && (* mb <= '9')))
      mmap_string_append_c(gstr, * mb);
    else {
      if (mmap_string_append_c(gstr, '%') == NULL)
	goto free;
      snprintf(hex, 3, "%02x", (unsigned char) (* mb));
      if (mmap_string_append(gstr, hex) == NULL)
	goto free;
    }
    mb ++;
  }

  str = strdup(gstr->str);
  if (str == NULL)
    goto free;

  mmap_string_free(gstr);
  
  return str;

 free:
  mmap_string_free(gstr);
  return NULL;
}


int maildriver_cache_clean_up(struct mail_cache_db * cache_db_env,
    struct mail_cache_db * cache_db_flags,
    struct mailmessage_list * env_list)
{
  chash * hash_exist;
  int res;
  int r;
  char keyname[PATH_MAX];
  unsigned int i;
  
  /* flush cache */
  
  hash_exist = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYALL);
  if (hash_exist == NULL) {
    res = MAIL_ERROR_MEMORY;
    goto err;
  }

  for(i = 0 ; i < carray_count(env_list->msg_tab) ; i ++) {
    mailmessage * msg;
    chashdatum key;
    chashdatum value;

    msg = carray_get(env_list->msg_tab, i);
    
    value.data = NULL;
    value.len = 0;
    
    if (cache_db_env != NULL) {
      snprintf(keyname, PATH_MAX, "%s-envelope", msg->msg_uid);
      
      key.data = keyname;
      key.len = (unsigned int) strlen(keyname);
      r = chash_set(hash_exist, &key, &value, NULL);
      if (r < 0) {
        res = MAIL_ERROR_MEMORY;
        goto free;
      }
    }
        
    if (cache_db_flags != NULL) {
      snprintf(keyname, PATH_MAX, "%s-flags", msg->msg_uid);
      
      key.data = keyname;
      key.len = (unsigned int) strlen(keyname);
      r = chash_set(hash_exist, &key, &value, NULL);
      if (r < 0) {
        res = MAIL_ERROR_MEMORY;
        goto free;
      }
    }
  }
  
  /* clean up */
  if (cache_db_env != NULL)
    mail_cache_db_clean_up(cache_db_env, hash_exist);
  if (cache_db_flags != NULL)
    mail_cache_db_clean_up(cache_db_flags, hash_exist);
  
  chash_free(hash_exist);
  
  return MAIL_NO_ERROR;

 free:
  chash_free(hash_exist);
 err:
  return res;
}

/*
  maildriver_message_cache_clean_up()
  
  remove files in cache_dir that does not correspond to a message.
  
  get_uid_from_filename() modifies the given filename so that it
  is a uid when returning from the function. If get_uid_from_filename()
  clears the content of file (set to empty string), this means that
  this file should not be deleted.
*/

int maildriver_message_cache_clean_up(char * cache_dir,
    struct mailmessage_list * env_list,
    void (* get_uid_from_filename)(char *))
{
  chash * hash_exist;
  DIR * d;
  char cached_filename[PATH_MAX];
  struct dirent * ent;
  char keyname[PATH_MAX];
  unsigned int i;
  int res;
  int r;
  
  /* remove files */
    
  hash_exist = chash_new(CHASH_DEFAULTSIZE, CHASH_COPYALL);
  if (hash_exist == NULL) {
    res = MAIL_ERROR_MEMORY;
    goto err;
  }
  
  for(i = 0 ; i < carray_count(env_list->msg_tab) ; i ++) {
    mailmessage * msg;
    chashdatum key;
    chashdatum value;
    
    msg = carray_get(env_list->msg_tab, i);
      
    key.data = msg->msg_uid;
    key.len = (unsigned int) strlen(msg->msg_uid);
    value.data = NULL;
    value.len = 0;
    r = chash_set(hash_exist, &key, &value, NULL);
    if (r < 0) {
      res = MAIL_ERROR_MEMORY;
      goto free;
    }
  }
  
  d = opendir(cache_dir);
  while ((ent = readdir(d)) != NULL) {
    chashdatum key;
    chashdatum value;
    
    if (strcmp(ent->d_name, ".") == 0)
      continue;
    
    if (strcmp(ent->d_name, "..") == 0)
      continue;
      
    if (strstr(ent->d_name, ".db") != NULL)
      continue;
    
    strncpy(keyname, ent->d_name, sizeof(keyname));
    keyname[sizeof(keyname) - 1] = '\0';
    
    get_uid_from_filename(keyname);
    
    if (* keyname == '\0')
      continue;
    
    key.data = keyname;
    key.len = (unsigned int) strlen(keyname);
    
    r = chash_get(hash_exist, &key, &value);
    if (r < 0) {
      snprintf(cached_filename, sizeof(cached_filename),
          "%s/%s", cache_dir, ent->d_name);
      unlink(cached_filename);
    }
  }
  closedir(d);
    
  chash_free(hash_exist);
  
  return MAIL_NO_ERROR;

 free:
  chash_free(hash_exist);
 err:
  return res;
}
