preserve stringlengths when outputting stuff in the imap module
[citadel.git] / citadel / modules / imap / imap_tools.c
index 0ca98feb701d53fa4458615e825dbcf0372dd0d3..e32320d000beabe5286d46f08e4e256b7b8454a4 100644 (file)
@@ -1,43 +1,39 @@
 /*
- * $Id$
- *
  * Utility functions for the IMAP module.
  *
  * Copyright (c) 2001-2009 by the citadel.org team and others, except for
  * most of the UTF7 and UTF8 handling code which was lifted from Evolution.
  *
- *  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 3 of the License, or
- *  (at your option) any later version.
+ * This program is open source 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 3 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.
+ * 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
+ * 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
  */
 
+#define SHOW_ME_VAPPEND_PRINTF
 #include <stdlib.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <ctype.h>
 #include <string.h>
+#include <stdarg.h>
 #include <libcitadel.h>
 #include "citadel.h"
 #include "sysdep_decls.h"
-#include "room_ops.h"
 #include "internet_addressing.h"
+#include "serv_imap.h"
 #include "imap_tools.h"
 #include "ctdl_module.h"
 
-#ifndef HAVE_SNPRINTF
-#include "snprintf.h"
-#endif
-
 /* String handling helpers */
 
 /* This code uses some pretty nasty string manipulation. To make everything
@@ -84,7 +80,7 @@ static void string_append_sn(struct string* s, char* p, int len)
 
 static void string_append_c(struct string* s, int c)
 {
-       char buf[5];
+       char UmlChar[5];
        int len = 0;
 
        /* Don't do anything if there's no room. */
@@ -102,27 +98,27 @@ static void string_append_c(struct string* s, int c)
        }
        else if (c <= 0x7FF)
        {
-               buf[0] = 0xC0 | (c >> 6);
-               buf[1] = 0x80 | (c & 0x3F);
+               UmlChar[0] = 0xC0 | (c >> 6);
+               UmlChar[1] = 0x80 | (c & 0x3F);
                len = 2;
        }
        else if (c <= 0xFFFF)
        {
-               buf[0] = 0xE0 | (c >> 12);
-               buf[1] = 0x80 | ((c >> 6) & 0x3f);
-               buf[2] = 0x80 | (c & 0x3f);
+               UmlChar[0] = 0xE0 | (c >> 12);
+               UmlChar[1] = 0x80 | ((c >> 6) & 0x3f);
+               UmlChar[2] = 0x80 | (c & 0x3f);
                len = 3;
        }
        else
        {
-               buf[0] = 0xf0 | c >> 18;
-               buf[1] = 0x80 | ((c >> 12) & 0x3f);
-               buf[2] = 0x80 | ((c >> 6) & 0x3f);
-               buf[3] = 0x80 | (c & 0x3f);
+               UmlChar[0] = 0xf0 | c >> 18;
+               UmlChar[1] = 0x80 | ((c >> 12) & 0x3f);
+               UmlChar[2] = 0x80 | ((c >> 6) & 0x3f);
+               UmlChar[3] = 0x80 | (c & 0x3f);
                len = 4;
        }
 
-       string_append_sn(s, buf, len);
+       string_append_sn(s, UmlChar, len);
 }      
 
 /* Reads a UTF8 character from a char*, advancing the pointer. */
@@ -225,7 +221,7 @@ static char* toimap(char* destp, char* destend, char* src)
 
        *destp = 0;
        string_init(&dest, destp, destend-destp);
-       /* CtdlLogPrintf(CTDL_DEBUG, "toimap %s\r\n", src); */
+       /* IMAP_syslog(LOG_DEBUG, "toimap %s", src); */
 
        for (;;)
        {
@@ -291,17 +287,17 @@ static char* toimap(char* destp, char* destend, char* src)
 
        if (state == 1)
                utf7_closeb64(&dest, v, i);
-       /* CtdlLogPrintf(CTDL_DEBUG, "    -> %s\r\n", destp); */
+       /* IMAP_syslog(LOG_DEBUG, "    -> %s", destp); */
        return string_end(&dest);
 }
 
 /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
 
 static int cfrommap(int c);
-static char* fromimap(char* destp, char* destend, char* src)
+static char* fromimap(char* destp, char* destend, const char* src)
 {
        struct string dest;
-       unsigned char *p = (unsigned char*) src;
+       unsigned const char *p = (unsigned const char*) src;
        int v = 0;
        int i = 0;
        int state = 0;
@@ -309,7 +305,7 @@ static char* fromimap(char* destp, char* destend, char* src)
 
        *destp = 0;
        string_init(&dest, destp, destend-destp);
-       /* CtdlLogPrintf(CTDL_DEBUG, "fromimap %s\r\n", src); */
+       /* IMAP_syslog(LOG_DEBUG, "fromimap %s", src); */
 
        do {
                c = *p++;
@@ -367,7 +363,7 @@ static char* fromimap(char* destp, char* destend, char* src)
                        }
        } while (c != '\0');
 
-       /* CtdlLogPrintf(CTDL_DEBUG, "      -> %s\r\n", destp); */
+       /* IMAP_syslog(LOG_DEBUG, "      -> %s", destp); */
        return string_end(&dest);
 }
 
@@ -383,102 +379,185 @@ static int cfrommap(int c)
        return c;               
 }
 
-/* Output a string to the IMAP client, either as a literal or quoted.
- * (We do a literal if it has any double-quotes or backslashes.) */
 
-void imap_strout(char *buf)
+
+
+/* Break a command down into tokens, unquoting any escaped characters. */
+
+void MakeStringOf(StrBuf *Buf, int skip)
 {
        int i;
-       int is_literal = 0;
-       long len;
+       citimap_command *Cmd = &IMAP->Cmd;
 
-       if (buf == NULL) {      /* yeah, we handle this */
-               cprintf("NIL");
-               return;
+       for (i=skip; i<Cmd->num_parms; ++i) {
+               StrBufAppendBufPlain(Buf, Cmd->Params[i].Key, Cmd->Params[i].len, 0);
+               if (i < (Cmd->num_parms-1)) StrBufAppendBufPlain(Buf, HKEY(" "), 0);
        }
+}
 
-       len = strlen(buf);
-       for (i = 0; i < len; ++i) {
-               if ((buf[i] == '\"') || (buf[i] == '\\'))
-                       is_literal = 1;
+
+void TokenCutRight(citimap_command *Cmd, 
+                  ConstStr *CutMe,
+                  int n)
+{
+       const char *CutAt;
+
+       if (CutMe->len < n) {
+               CutAt = CutMe->Key;
+               CutMe->len = 0;
+       }
+       else {
+               CutAt = CutMe->Key + CutMe->len - n;
+               CutMe->len -= n;
        }
+       StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0');
+}
 
-       if (is_literal) {
-               cprintf("{%ld}\r\n%s", len, buf);
-       } else {
-               cprintf("\"%s\"", buf);
+void TokenCutLeft(citimap_command *Cmd, 
+                 ConstStr *CutMe,
+                 int n)
+{
+       if (CutMe->len < n) {
+               CutMe->Key += CutMe->len;
+               CutMe->len = 0;
+       }
+       else {
+               CutMe->Key += n;
+               CutMe->len -= n;
        }
 }
 
-/* Break a command down into tokens, unquoting any escaped characters. */
 
-int imap_parameterize(char** args, char* in)
+
+int CmdAdjust(citimap_command *Cmd, 
+             int nArgs,
+             int Realloc)
+{
+       ConstStr *Params;
+       if (nArgs > Cmd->avail_parms) {
+               Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs);
+               if (Realloc) {
+                       memcpy(Params, 
+                              Cmd->Params, 
+                              sizeof(ConstStr) * Cmd->avail_parms);
+
+                       memset(Cmd->Params + 
+                              sizeof(ConstStr) * Cmd->avail_parms,
+                              0, 
+                              sizeof(ConstStr) * nArgs - 
+                              sizeof(ConstStr) * Cmd->avail_parms 
+                               );
+               }
+               else {
+                       Cmd->num_parms = 0;
+                       memset(Params, 0, 
+                              sizeof(ConstStr) * nArgs);
+               }
+               Cmd->avail_parms = nArgs;
+               if (Cmd->Params != NULL)
+                       free (Cmd->Params);
+               Cmd->Params = Params;
+       }
+       else {
+               if (!Realloc) {
+                       memset(Cmd->Params, 
+                              0,
+                              sizeof(ConstStr) * Cmd->avail_parms);
+                       Cmd->num_parms = 0;
+               }
+       }
+       return Cmd->avail_parms;
+}
+
+int imap_parameterize(citimap_command *Cmd)
 {
-       char* out = in;
-       int num = 0;
+       int nArgs;
+       const char *In, *End;
 
-       for (;;)
+       In = ChrPtr(Cmd->CmdBuf);
+       End = In + StrLength(Cmd->CmdBuf);
+
+       /* we start with 10 chars per arg, maybe we need to realloc later. */
+       nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
+       nArgs = CmdAdjust(Cmd, nArgs, 0);
+       while (In < End)
        {
                /* Skip whitespace. */
-
-               while (isspace(*in))
-                       in++;
-               if (*in == 0)
+               while (isspace(*In))
+                       In++;
+               if (*In == '\0')
                        break;
 
                /* Found the start of a token. */
                
-               args[num++] = out;
+               Cmd->Params[Cmd->num_parms].Key = In;
 
                /* Read in the token. */
 
                for (;;)
                {
-                       int c = *in++;
-                       if (isspace(c))
+                       if (isspace(*In))
                                break;
                        
-                       if (c == '\"')
+                       if (*In == '\"')
                        {
                                /* Found a quoted section. */
 
+                               Cmd->Params[Cmd->num_parms].Key++; 
+                               //In++;
                                for (;;)
                                {
-                                       c = *in++;
-                                       if (c == '\"')
+                                       In++;
+                                       if (*In == '\"') {
+                                               StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
                                                break;
-                                       else if (c == '\\')
-                                               c = *in++;
-
-                                       *out++ = c;
-                                       if (c == 0)
-                                               return num;
+                                       }
+                                       else if (*In == '\\')
+                                               In++;
+
+                                       if (*In == '\0') {
+                                               Cmd->Params[Cmd->num_parms].len = 
+                                                       In - Cmd->Params[Cmd->num_parms].Key;
+                                               Cmd->num_parms++;
+                                               return Cmd->num_parms;
+                                       }
                                }
+                               break;
                        }
-                       else if (c == '\\')
+                       else if (*In == '\\')
                        {
-                               c = *in++;
-                               *out++ = c;
+                               In++;
                        }
-                       else
-                               *out++ = c;
 
-                       if (c == 0)
-                               return num;
+                       if (*In == '\0') {
+                               Cmd->Params[Cmd->num_parms].len = 
+                                       In - Cmd->Params[Cmd->num_parms].Key;
+                               Cmd->num_parms++;
+                               return Cmd->num_parms;
+                       }
+                       In++;
+               }
+               StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
+               Cmd->Params[Cmd->num_parms].len = 
+                       In - Cmd->Params[Cmd->num_parms].Key;
+               if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
+                       nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
                }
-               *out++ = '\0';
+               Cmd->num_parms ++;
+               In++;
        }
-
-       return num;
+       return Cmd->num_parms;
 }
 
+
 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
 
-void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
+long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
 {
        char* bufend = buf+bufsize;
        struct floor *fl;
        char* p = buf;
+       const char *pend;
 
        /* For mailboxes, just do it straight.
         * Do the Cyrus-compatible thing: all private folders are
@@ -487,24 +566,29 @@ void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
        if (qrbuf->QRflags & QR_MAILBOX)
        {
                if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
-                       p = toimap(p, bufend, "INBOX");
+               {
+                       pend = toimap(p, bufend, "INBOX");
+                       return pend - p;
+               }
                else
                {
                        p = toimap(p, bufend, "INBOX");
                        if (p < bufend)
                                *p++ = '/';
-                       p = toimap(p, bufend, qrbuf->QRname+11);
+                       pend = toimap(p, bufend, qrbuf->QRname+11);
+                       return pend - p;
                }
        }
        else
        {
                /* Otherwise, prefix the floor name as a "public folders" moniker. */
 
-               fl = cgetfloor(qrbuf->QRfloor);
+               fl = CtdlGetCachedFloor(qrbuf->QRfloor);
                p = toimap(p, bufend, fl->f_name);
                if (p < bufend)
                        *p++ = '/';
-               p = toimap(p, bufend, qrbuf->QRname);
+               pend = toimap(p, bufend, qrbuf->QRname);
+               return pend - p;
        }
 }
 
@@ -520,10 +604,11 @@ void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
  *
  */
 
-int imap_roomname(char *rbuf, int bufsize, char *foldername)
+int imap_roomname(char *rbuf, int bufsize, const char *foldername)
 {
+       struct CitContext *CCC = CC;
        int levels;
-       char floorname[256];
+       char floorname[ROOMNAMELEN*2];
        char roomname[ROOMNAMELEN];
        int i;
        struct floor *fl;
@@ -566,16 +651,18 @@ int imap_roomname(char *rbuf, int bufsize, char *foldername)
        levels = num_tokens(rbuf, FDELIM);
        if (levels > 1)
        {
+               long len;
                /* Extract the main room name. */
                
-               extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
-               strcpy(roomname, &rbuf[strlen(floorname)+1]);
+               len = extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
+               if (len < 0) len = 0;
+               safestrncpy(roomname, &rbuf[len  + 1], sizeof(roomname));
 
                /* Try and find it on any floor. */
                
                for (i = 0; i < MAXFLOORS; ++i)
                {
-                       fl = cgetfloor(i);
+                       fl = CtdlGetCachedFloor(i);
                        if (fl->f_flags & F_INUSE)
                        {
                                if (strcasecmp(floorname, fl->f_name) == 0)
@@ -596,37 +683,10 @@ int imap_roomname(char *rbuf, int bufsize, char *foldername)
        ret = (0 | IR_MAILBOX);
 
 exit:
-       CtdlLogPrintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
+       IMAP_syslog(LOG_DEBUG, "(That translates to \"%s\")", rbuf);
        return(ret);
 }
 
-/*
- * Output a struct internet_address_list in the form an IMAP client wants
- */
-void imap_ial_out(struct internet_address_list *ialist)
-{
-       struct internet_address_list *iptr;
-
-       if (ialist == NULL) {
-               cprintf("NIL");
-               return;
-       }
-       cprintf("(");
-
-       for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
-               cprintf("(");
-               imap_strout(iptr->ial_name);
-               cprintf(" NIL ");
-               imap_strout(iptr->ial_user);
-               cprintf(" ");
-               imap_strout(iptr->ial_node);
-               cprintf(")");
-       }
-
-       cprintf(")");
-}
-
-
 
 /*
  * Determine whether the supplied string is a valid message set.
@@ -634,7 +694,7 @@ void imap_ial_out(struct internet_address_list *ialist)
  * return 1 for a valid message set.  If any other character is found, 
  * return 0.
  */
-int imap_is_message_set(char *buf)
+int imap_is_message_set(const char *buf)
 {
        int i;
 
@@ -678,19 +738,30 @@ static int do_imap_match(const char *supplied_text, const char *supplied_p)
 {
        int matched, i;
        char lcase_text[SIZ], lcase_p[SIZ];
-       char *text = lcase_text;
-       char *p = lcase_p;
-
+       char *text;
+       char *p;
+       
        /* Copy both strings and lowercase them, in order to
         * make this entire operation case-insensitive.
         */
-       for (i=0; i<=strlen(supplied_text); ++i)
+       for (i=0; 
+            ((supplied_text[i] != '\0') && 
+             (i < sizeof(lcase_text)));
+            ++i)
                lcase_text[i] = tolower(supplied_text[i]);
-       for (i=0; i<=strlen(supplied_p); ++i)
-               p[i] = tolower(supplied_p[i]);
+       lcase_text[i] = '\0';
+
+       for (i=0; 
+            ((supplied_p[i] != '\0') && 
+             (i < sizeof(lcase_p))); 
+            ++i)
+               lcase_p[i] = tolower(supplied_p[i]);
+       lcase_p[i] = '\0';
 
        /* Start matching */
-       for (; *p; text++, p++) {
+       for (p = lcase_p, text = lcase_text; 
+            !IsEmptyStr(p) && !IsEmptyStr(text); 
+            text++, p++) {
                if ((*text == '\0') && (*p != '*') && (*p != '%')) {
                        return WILDMAT_ABORT;
                }
@@ -720,7 +791,8 @@ star:
                        }
                        return WILDMAT_ABORT;
                case '%':
-                       while (++p, ((*p == '*') || (*p == '%'))) {
+                       while (++p, (!IsEmptyStr(p) && ((*p == '*') || (*p == '%'))))
+                       {
                                /* Consecutive %'s act just like one, but even
                                 * a single star makes the sequence act like
                                 * one star, instead.
@@ -735,7 +807,7 @@ star:
                                 * Trailing % matches everything
                                 * without a delimiter.
                                 */
-                               while (*text) {
+                               while (!IsEmptyStr(text)) {
                                        if (*text == WILDMAT_DELIM) {
                                                return WILDMAT_FALSE;
                                        }
@@ -743,7 +815,10 @@ star:
                                }
                                return WILDMAT_TRUE;
                        }
-                       while (*text && (*(text - 1) != WILDMAT_DELIM)) {
+                       while (!IsEmptyStr(text) &&
+                              /* make shure texst - 1 isn't before lcase_p */
+                              ((text == lcase_text) || (*(text - 1) != WILDMAT_DELIM)))
+                       {
                                if ((matched = do_imap_match(text++, p))
                                   != WILDMAT_FALSE) {
                                        return matched;
@@ -753,7 +828,8 @@ star:
                }
        }
 
-       return (*text == '\0');
+       if ((*text == '\0') && (*p == '\0')) return WILDMAT_TRUE;
+       else return WILDMAT_FALSE;
 }
 
 
@@ -762,7 +838,7 @@ star:
  * Support function for mailbox pattern name matching in LIST and LSUB
  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
  */
-int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
+int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname)
 {
        /* handle just-star case quickly */
        if ((pattern[0] == '*') && (pattern[1] == '\0')) {
@@ -777,7 +853,7 @@ int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
  * Compare an IMAP date string (date only, no time) to the date found in
  * a Unix timestamp.
  */
-int imap_datecmp(char *datestr, time_t msgtime) {
+int imap_datecmp(const char *datestr, time_t msgtime) {
        char daystr[256];
        char monthstr[256];
        char yearstr[256];
@@ -826,3 +902,101 @@ int imap_datecmp(char *datestr, time_t msgtime) {
        return(0);
 }
 
+
+
+
+
+void IAPrintf(const char *Format, ...)
+{
+       va_list arg_ptr;
+       
+       va_start(arg_ptr, Format);
+       StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
+       va_end(arg_ptr);
+}
+
+void iaputs(const char *Str, long Len)
+{
+       StrBufAppendBufPlain(IMAP->Reply, Str, Len, 0);
+}
+
+void ireply(const char *Msg, long len)
+{
+       citimap *Imap = IMAP;
+
+       StrBufAppendBufPlain(Imap->Reply, 
+                            CKEY(Imap->Cmd.Params[0]), 0);
+       StrBufAppendBufPlain(Imap->Reply, 
+                            HKEY(" "), 0);
+       StrBufAppendBufPlain(Imap->Reply, 
+                            Msg, len, 0);
+       
+       StrBufAppendBufPlain(Imap->Reply, 
+                            HKEY("\r\n"), 0);
+       
+}
+
+void IReplyPrintf(const char *Format, ...)
+{
+       citimap *Imap = IMAP;
+       va_list arg_ptr;
+       
+
+       StrBufAppendBufPlain(Imap->Reply, 
+                            CKEY(Imap->Cmd.Params[0]), 0);
+
+       StrBufAppendBufPlain(Imap->Reply, 
+                            HKEY(" "), 0);
+
+       va_start(arg_ptr, Format);
+       StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
+       va_end(arg_ptr);
+       
+       StrBufAppendBufPlain(Imap->Reply, 
+                            HKEY("\r\n"), 0);
+       
+}
+
+
+/* Output a string to the IMAP client, either as a literal or quoted.
+ * (We do a literal if it has any double-quotes or backslashes.) */
+
+
+void IPutStr(const char *Msg, long Len)
+{
+       int i;
+       int is_literal = 0;
+       citimap *Imap = IMAP;
+
+       
+       if ((Msg == NULL) || (Len == 0))
+       {       /* yeah, we handle this */
+               StrBufAppendBufPlain(Imap->Reply, HKEY("NIL"), 0);
+               return;
+       }
+
+       for (i = 0; i < Len; ++i) {
+               if ((Msg[i] == '\"') || (Msg[i] == '\\'))
+                       is_literal = 1;
+       }
+
+       if (is_literal) {
+               StrBufAppendPrintf(Imap->Reply, "{%ld}\r\n", Len);
+               StrBufAppendBufPlain(Imap->Reply, Msg, Len, 0);
+       } else {
+               StrBufAppendBufPlain(Imap->Reply, 
+                                    HKEY("\""), 0);
+               StrBufAppendBufPlain(Imap->Reply, 
+                                    Msg, Len, 0);
+               StrBufAppendBufPlain(Imap->Reply, 
+                                    HKEY("\""), 0);
+       }
+}
+
+void IUnbuffer (void)
+{
+       citimap *Imap = IMAP;
+
+       cputbuf(Imap->Reply);
+       FlushStrBuf(Imap->Reply);
+}