/*  Spruce
 *  Copyright (C) 1999-2000 Jeffrey Stedfast
 *
 *  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.
*/

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

#include "imap.h"

#define IMAP_LOGGING

/* this is used in the tag before each command */
static guint32 imap_commands = 0;

extern gint timeout;
extern GList *mime_parts;


gint imap_ok (gint tag, gchar *line)
{
   /* returns 1 if <tag> OK was found */
   gchar find[64];
   gint ret;

   g_snprintf(find, sizeof(find)-1, "A%.5d OK", tag);

   ret = find_string (line, find);
   
   if (ret < 0)
      return 0;
      
   return 1;
}

gint imap_login_cram_md5 (gint socket, gchar *username, gchar *password)
{
   /* Log in to server using CRAM-MD5 keyed hash. */ 
   gchar buffer[512];
   gchar *retstr;
   gint pos;

   if (username == NULL || password == NULL) 
      return ERROR;

   memset(buffer, 0, sizeof(buffer));
   if (recvline(socket, buffer, sizeof(buffer)-1) < 0)
      return ERROR; /* Fetch the OK line from the server */

   if (find_string(buffer, "OK") == -1)
      return ERROR;

   g_snprintf(buffer, sizeof(buffer)-1, "A%.5d AUTHENTICATE CRAM-MD5\r\n", imap_commands);

   if (send(socket, buffer, strlen(buffer), 0) < 0)
      return ERROR;

   memset(buffer, 0, sizeof(buffer));
   if (recvline(socket, buffer, sizeof(buffer)-1) < 0)
      return ERROR;

   pos = find_string(buffer, "\r\n");
   if (pos != -1)
      buffer[pos] = '\0';
   retstr = cram_md5(username, password, buffer);

   if (retstr[strlen(retstr)-1] == '\n')
      retstr[strlen(retstr)-1] = '\0';

   g_snprintf(buffer, sizeof(buffer)-1, "%s\r\n", retstr);
   g_free(retstr);

   if (send (socket, buffer, strlen(buffer), 0) < 0) 
      return ERROR;

   if (recvline(socket, buffer, sizeof(buffer)-1) < 0)
      return ERROR;

   if (!imap_ok(imap_commands, buffer))
      return ERROR;

   imap_commands++;

   return SUCCESS;
}
      
gint imap_login (gint socket, gchar *username, gchar *password)
{
   /* this logs us in to the server */
   gchar buffer[512];
   gchar temp[64];
   
   if (username == NULL || password == NULL)
      return ERROR;

   g_snprintf(buffer, sizeof(buffer)-1, "A%.5d LOGIN \"%s\" \"%s\"\r\n", imap_commands, username, password);
#ifdef IMAP_LOGGING
   fprintf(stderr, "%s", buffer);
#endif

   if (send (socket, buffer, strlen(buffer), 0) < 0)
   {
      return ERROR;
   }
      
   g_snprintf(temp, sizeof(temp)-1, "A%.5d", imap_commands);

   memset(buffer, 0, sizeof(buffer));
   recvline_timeo(socket, buffer, sizeof(buffer)-1, timeout);
   while (!strstr(buffer, temp))
   {
      memset(buffer, 0, sizeof(buffer));
      recvline_timeo(socket, buffer, sizeof(buffer)-1, timeout);
   }

   if (!imap_ok(imap_commands, buffer))
      return ERROR;

   imap_commands++;
   
   return SUCCESS;   
}

GList *imap_list (gint socket, gchar *namespace)
{
   /* this gets the names of all the mailboxes */
   gchar buffer[512];
   gchar flags[256];
   gchar temp[64], *ptr = NULL, *flagptr = NULL;
   gchar slashdot = '\0';
   GList *list = NULL;
   gint ret, size = 0, flaglen = 0;

   if (namespace && *namespace)
   {
      if (*namespace && namespace[strlen(namespace)-1] != '/' && namespace[strlen(namespace)-1] != '.')
         slashdot = '/';
      g_snprintf(buffer, sizeof(buffer)-1, "A%.5d LIST \"\" %s%c*\r\n", imap_commands, namespace, slashdot);
   }
   else
      g_snprintf(buffer, sizeof(buffer)-1, "A%.5d LIST \"\" INBOX.*\r\n", imap_commands);
#ifdef IMAP_LOGGING
   fprintf(stderr, "%s", buffer);
#endif

   if (send(socket, buffer, strlen(buffer), 0) < 0)
   {
      return NULL;
   }

   do
   {
      memset(buffer, 0, sizeof(buffer));
      ret = recvline(socket, buffer, sizeof(buffer)-1);
      if (ret > 0)
      {
#ifdef IMAP_LOGGING
         fprintf(stderr, "received: %s", buffer);
#endif
         if (buffer[0] == '*')
         {
            strip(buffer, '\r');
            strip(buffer, '\n');

            /* skip ahead to the flag section */
            ptr = strstr(buffer, "(");

            /* find the end of the flags section */
            flagptr = ptr + 1;
            ptr = strstr(ptr, ")") + 1;

            /* eventually we will need to parse this */
            memset(flags, 0, sizeof(flags));
            flaglen = (gint)(ptr - flagptr) - 1;
            size = sizeof(flags);
            strncpy(flags, flagptr, flaglen > size ? size : flaglen);
            if (!strstrcase(flags, "\\NoSelect")) /* is this a selectable mailbox? */
            {
               /* skip the reference name */
               ptr += imap_get_string (ptr, temp, sizeof(temp)-1, "");

               /* the rest of the return string is fair play... */
               g_strstrip(ptr);       /* trim off any extra white space */
               unquote(ptr);           /* unquote the mailbox name if it is quoted */
               if (slashdot)
                  strcut(ptr, 0, strlen(namespace)+1); /* cut out the namespace and the '/' */
               else
                  strcut(ptr, 0, strlen(namespace));   /* cut out the namespace */
					

               list = g_list_append (list, g_strdup(ptr));
            }
         }
         else 
	         break;
      }
   } while (ret > 0);

   imap_commands++;

   return list;
}

gint imap_select_mailbox (gint socket, gchar *mailbox, gchar *namespace)
{
   /* selects a mailbox, returns the number of messages in that mailbox
    * or -1 on error */
   gchar *cmdbuf, buffer[512], temp[64], *index, mesgs[16];
   gchar slashdot = '\0';
   gint ret, i;

   if (mailbox == NULL)
      return ERROR;

   if (namespace && strcmp(mailbox, "INBOX"))
   {
      if (*namespace && namespace[strlen(namespace)-1] != '/' && namespace[strlen(namespace)-1] != '.')
         slashdot = '/';

      cmdbuf = g_strdup_printf("A%.5d SELECT %s%c%s\r\n", imap_commands, namespace, slashdot, mailbox);
   }
   else
      cmdbuf = g_strdup_printf("A%.5d SELECT %s\r\n", imap_commands, mailbox);
#ifdef IMAP_LOGGING
   fprintf(stderr, "%s", cmdbuf);
#endif

   if (send(socket, cmdbuf, strlen(cmdbuf), 0) < 0)
   {
      g_free(cmdbuf);
      return -1;
   }
   g_free(cmdbuf);
   
   g_snprintf(temp, sizeof(temp)-1, "A%.5d", imap_commands);

   memset(buffer, 0, sizeof(buffer));
   ret = recvline(socket, buffer, sizeof(buffer)-1);
   while (ret > 0)
   {
#ifdef IMAP_LOGGING
      fprintf(stderr, "received: %s", buffer);
#endif
      if (strstr(buffer, temp))
         break;
      if (buffer[0] == '*')
      {
         if (strstr(buffer, "EXISTS"))
         {
            index = buffer;
            while (*index != ' ')
               index++;
            index++;
            
            i = 0;
            memset(mesgs, 0, sizeof(mesgs));
            while (*index != ' ' && i < sizeof(mesgs)-1)
            {
               mesgs[i] = *index;
               index++;
               i++;
            }
         }
      }
      memset(buffer, 0, sizeof(buffer));
      ret = recvline(socket, buffer, sizeof(buffer)-1);   
   }
   
   if (!imap_ok(imap_commands, buffer))
      return -1;
   
   imap_commands++;

   return atoi(mesgs);
}

gint imap_logout (gint socket)
{
   /* logs out */
   gchar buffer[256];

   g_snprintf(buffer, sizeof(buffer)-1, "A%.5d LOGOUT\r\n", imap_commands);
#ifdef IMAP_LOGGING
   fprintf(stderr, "%s", buffer);
#endif

   if (send(socket, buffer, strlen(buffer), 0) < 0)
   {
      return ERROR;
   }

   return SUCCESS;
}

gint imap_mailbox_create (gint socket, gchar *mailbox)
{
   /* creates a new mailbox */
   gchar buffer[256];

   if (mailbox == NULL)
      return ERROR;

   g_snprintf(buffer, sizeof(buffer)-1, "A%.5d CREATE %s\r\n", imap_commands, mailbox);
#ifdef IMAP_LOGGING
   fprintf(stderr, "%s", buffer);
#endif

   if (send(socket, buffer, strlen(buffer), 0) < 0)
   {
      return ERROR;
   }

   memset(buffer, 0, sizeof(buffer));
   if (recvline(socket, buffer, sizeof(buffer)-1) < 0 || !imap_ok(imap_commands, buffer))
   {
      return ERROR;
   }

   imap_commands++;

   return SUCCESS;
}

gint imap_mailbox_delete (gint socket, gchar *mailbox)
{
   /* deletes a mailbox */
   gchar buffer[256];

   if (mailbox == NULL)
      return ERROR;

   g_snprintf(buffer, sizeof(buffer)-1, "A%.5d DELETE %s\r\n", imap_commands, mailbox);
#ifdef IMAP_LOGGING
   fprintf(stderr, "%s", buffer);
#endif

   if (send(socket, buffer, strlen(buffer), 0) < 0)
   {
      return ERROR;
   }

   memset(buffer, 0, sizeof(buffer));
   if (recvline(socket, buffer, sizeof(buffer)-1) < 0 ||
      !imap_ok(imap_commands, buffer))
   {
      return ERROR;
   }

   imap_commands++;

   return SUCCESS;
}

/* fetches the specified part of a message, which can be alot of 
 * if you use peek the \Seen flag is not set */
gchar *imap_fetch (gint socket, gint mesgnum, gchar *part, gint *seen)
{
   /* fetches the specified part of the mesg. */
   gchar *mesg = NULL;
   gchar buffer[512], *index;
   gchar flags[128], size[16], temp[64];
   gint i, n, msgsize = 1000;

   if (mesgnum < 0)
      return (gchar *)NULL;

   g_snprintf(buffer, sizeof(buffer)-1, "A%.5d FETCH %d (FLAGS %s)\r\n", imap_commands, mesgnum, part);
#ifdef IMAP_LOGGING
   fprintf(stderr, "%s", buffer);
#endif

   if (send(socket, buffer, strlen(buffer), 0) < 0)
   {
      return (gchar *)NULL;
   }   


   memset(buffer, 0, sizeof(buffer));
   n = recvline(socket, buffer, sizeof(buffer)-1);

   if (buffer[0] != '*' && imap_ok(imap_commands, buffer))
   {
       memset(buffer, 0, sizeof(buffer));
       n = recvline(socket, buffer, sizeof(buffer)-1);
   }
     
   if (buffer[0] == '*')
   /*if (imap_ok(imap_commands, buffer))*/
   {
      index = strstrcase(buffer, "FLAGS");
      if (index == NULL) /* hmm */
      {
         fprintf(stderr, _("IMAP server replied using unknown tokens.\n"));
         return (gchar *)NULL;
      }
      else
      {
#ifdef IMAP_LOGGING
         fprintf(stderr, "received: %s", buffer);
#endif
         /* skip to the FLAGS token */
         for ( ; *index && *index != '('; index++);
         index++;

         i = 0;
         memset(flags, 0, sizeof(flags));
         while (*index != ')' && i < sizeof(flags)-1)
         {
            flags[i] = *index;
            index++;
            i++;
         }
         flags[i] = '\0';

         /* skip to the next significant token */
         for (index++; *index && *index != '{'; index++);
         index++;
         
         i = 0;
         memset(size, 0, sizeof(size));
         while (*index != '}' && i < sizeof(size)-1)
         {
            size[i] = *index;
            index++;
            i++;
         }
         size[i] = '\0';
         msgsize = atoi(size);
      }
   }
   else
   {
      g_snprintf(temp, sizeof(temp)-1, "A%.5d", imap_commands);
      if (strstr(buffer, temp)) /* this means there's no such message */
      {
         fprintf(stderr, _("IMAP responded with \"no such message\".\n"));
         return (gchar *)NULL;
      }
   }
   

   mesg = g_malloc0(msgsize + 50); /* just to be safe */
   n = recvline(socket, buffer, sizeof(buffer)-1);
   
   while (!(n <= 0) && !imap_ok(imap_commands, buffer))
   {
      strip(buffer, '\r');  /* strip all the \r's */
      strcat(mesg, buffer);
      memset(buffer, 0, sizeof(buffer));
      n = recvline(socket, buffer, sizeof(buffer)-1);
   }

   if (mesg)
      mesg[strlen(mesg)-3] = '\0';   /* strip the ending ) */

   if (seen != NULL)
   {
      if (strstrcase(flags, "\\Seen"))
         *seen = 1;
      else
         *seen = 0;
   }
   
   imap_commands++;

   return (gchar*)mesg;
}

gboolean imap_delete(const ImapAccount_t *imap, GList *sorted)
{
   GList *p = sorted;
   gchar buffer[256];
   gchar temp[16];
   gint ret;

   do
   {
      gint id = GPOINTER_TO_INT(p->data);
      g_snprintf(buffer, sizeof(buffer)-1, "A%.5d STORE %d +FLAGS (\\Deleted)\r\n", imap_commands, id);
#ifdef IMAP_LOGGING
      fprintf(stderr, "%s", buffer);
#endif
      if (send(imap->socket, buffer, strlen(buffer), 0) < 0)
      {
         return FALSE;
      }
      g_snprintf(temp, sizeof(temp)-1, "A%.5d", imap_commands);

      memset(buffer, 0, sizeof(buffer));
      ret = recvline(imap->socket, buffer, sizeof(buffer)-1);
      while (ret > 0)
      {
         if (find_string(buffer, temp) >= 0)
            break;

         memset(buffer, 0, sizeof(buffer));
         ret = recvline(imap->socket, buffer, sizeof(buffer)-1);
      }

      if (!imap_ok(imap_commands, buffer))
      {
         return FALSE;
      }
      imap_commands++;
   } while ((p = g_list_next(p)));

   g_snprintf(buffer, 255, "A%.5d EXPUNGE\r\n", imap_commands);
#ifdef IMAP_LOGGING
   fprintf(stderr, "%s", buffer);
#endif
   if (send(imap->socket, buffer, strlen(buffer), 0) < 0)
   {
      return FALSE;
   }   

   g_snprintf (temp, 15, "A%.5d", imap_commands);

   memset(buffer, 0, sizeof(buffer));
   ret = recvline(imap->socket, buffer, sizeof(buffer)-1);
   while (ret > 0)
   {
      if (find_string(buffer, temp) >= 0)
        break;

     memset(buffer, 0, sizeof(buffer));
     ret = recvline(imap->socket, buffer, sizeof(buffer)-1);
   }
    
   if (!imap_ok(imap_commands, buffer))
   {
      return FALSE;
   }

   imap_commands++;

   return TRUE;
}

gint imap_connect (Server *server)
{
   /* connects to the server and returns the socket or -1 on error */
   gchar buffer[512];
   gint sock;

   if (!Resolve(server))
      return -1;

   sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   if (sock < 0)
      return -1;

   server->sin.sin_family = AF_INET;
   server->sin.sin_port = htons(server->port);

#ifdef IMAP_LOGGING
   fprintf(stderr, _("Connecting to IMAP server (%s)..."), server->ip);
#endif
   if (connect_timeo(sock, (struct sockaddr*)&server->sin, sizeof(server->sin), timeout) < 0)
   {
      fprintf(stderr, _("failed.\n"));
      close(sock);
      return -1;
   }
   fprintf(stderr, _("success.\n"));

   {
      /* read the connect responce */
      memset(buffer, 0, sizeof(buffer));
      recvline_timeo(sock, buffer, sizeof(buffer)-1, timeout);
   }

   return sock;
}

gint imap_add_part(gchar *c)
{
   gchar name[64], value[64];
   gchar temp[64];
   gchar *start = c;
   struct mime_part *part;

   part = g_malloc0(sizeof(struct mime_part));

   c += imap_get_string (c, part->type, sizeof(part->type)-1, "text");
   c += imap_get_string (c, part->subtype, sizeof(part->subtype)-1, "plain");

   /* seek to the beginning of the parameter... */
   for ( ; *c && *c == ' '; c++);

   if (*c)
   {
      gchar *p = part->parameter;
      if (*c == '(')
      {
         c++;
         while (*c && *c != ')')
         {
            c += imap_get_string (c, name, sizeof(name)-1, "");
            c += imap_get_string (c, value, sizeof(value)-1, "");
            /* don't buffer overrun */
            g_snprintf(p, sizeof(part->parameter)-1, "%s=\"%s\"; ", name, value);
            p += strlen(p);
   
            while (*c && *c == ' ') /* skip any spaces */
               c++;
         }
      }
      else
      {
         c += imap_get_string (c, name, sizeof(name)-1, "");
         strcpy(value, name);
         *p++ = '\0';
      }

      c++; /* skip over the ')' belonging to the parameter values */
      if (*c)
      {
         /* ignore id and description */
         c += imap_get_string (c, temp, sizeof(temp)-1, "");	   
         c += imap_get_string (c, temp, sizeof(temp)-1, "");

         /* encoding */
         c += imap_get_string (c, part->encoding, sizeof(part->encoding)-1, "");

         /* size */
         c += imap_get_number (c, &part->len);

         /* skip the optional info */
         c += imap_skip_section(c);

         part->pos = 0; /* isn't useful in imap */
#ifdef IMAP_LOGGING
         fprintf(stderr, "type = %s/%s\n", part->type, part->subtype);
			fprintf(stderr, "encoding = %s\n", part->encoding);
			fprintf(stderr, "param = %s\n", part->parameter);
#endif
         mime_parts = g_list_append (mime_parts, part);

         return (c - start);
      }
   }
   return -1;
}

gint imap_parts (gint socket, gint mesg_num)
{
   GList *tmp;
   gchar *buffer = NULL, *c;
   gint res = 1, cnt;

   tmp = mime_parts;
   while (tmp != NULL)
   {
      g_free(tmp->data);
      tmp = tmp->next;
   }

   if (mime_parts != NULL)
   {
      g_list_free(mime_parts);
      mime_parts = NULL;
   }
  
   buffer = g_malloc0(sizeof(gchar)*2048);   
  
   g_snprintf(buffer, 2047, "A%.5d FETCH %d (BODYSTRUCTURE)\r\n", imap_commands, mesg_num);
#ifdef IMAP_LOGGING
   fprintf(stderr, "%s", buffer);
#endif

   if (send(socket, buffer, strlen(buffer), 0) < 0)
   {
      g_free(buffer);
      return 0;
   }
  
   /* get the structure of the body */
   memset (buffer, 0, sizeof(gchar)*2048);
   recvline (socket, buffer, sizeof(gchar)*2048);
#ifdef IMAP_LOGGING
   fprintf(stderr, "received: %s", buffer);
#endif

   c = buffer;
   /* skip to the BODYSTRUCTURE */
   c = strstr(c, "BODYSTRUCTURE");
   if (c == NULL)
      return 0;

   c += strlen("BODYSTRUCTURE");
   if (*c)
   {
      /* looks good so far, skip to the parts */
      for ( ; *c && *c != '('; c++);

      if (*c && *(c+1) == '(')
      {
         c++;
#ifdef IMAP_LOGGING
         fprintf(stderr, "message is multipart\n");
#endif
         /* multipart */
         while (*c == '(')
         {
            cnt = imap_skip_section(c);
            if (cnt > 1)
            {
               c[cnt-1] = '\0';
               cnt = imap_add_part(c);
               if (cnt == -1)
               {
                  res = 0;
                  break;
               }
               c += cnt;
            }
            else
            {
               res = 0;
               break;
            }
            /* skip to the next mime part */
            for ( ; *c && *c == ' '; c++);
         }
      }
      else
         if (*c)
         {
            /* one part */
            cnt = imap_add_part(c);
            res = res != -1;
         }
         /* just forget the rest, who cares?? */
   }
  
   g_free(buffer);

   return res;
}

gint imap_get_string (gchar *index, gchar *dest, gint destlen, gchar *def)
{
   /* gets a string ("data" or NIL) , if NIL it copies def instead */
   gint i;
   gchar *start = index;

   while (*index && *index == ' ') /* skip white space */
     index++;

   if (strncmp(index, "NIL", 3))
   {
      /* progress to the first quote (we should already be there but just in case) */
      while (*index && *index != '"')
         index++;

      index++;
   
      i = 0;
      while (*index && *index != '"')
      {
         if (i < destlen-1)
         {
            dest[i] = *index;
            i++;
         }
         index++;
      }
      dest[i] = '\0';
   }
   else
   {
      /* if there were no data we just copy def */
      index += 3;
      strncpy (dest, def, destlen);
   }

   return index - start + 1;
}

gint imap_get_number (gchar *index, gint *dest)
{
   /* gets a number */
   gchar number[32];
   gchar *start = index;
   gint i;

   /* skip white space **/
   while (*index == ' ')
     index++;

   i = 0;
   while (*index != ' ' && i < sizeof(number)-1)
   {
      number[i] = *index;
      index++;
      i++;
   }
   number[i] = '\0';

   *dest = atoi(number);

   return index - start;
}

gint imap_skip_section(gchar *index)
{
   gint depth = 1;
   gchar *start = index;

   while (depth != 0 && *index)
   {
      if (*index == '(')
         depth++;
      else if ( *index == ')' )
         depth--;
      index++;
   }

   return index - start;
}