2 * Utility functions for the IMAP module.
4 * Copyright (c) 2001-2009 by the citadel.org team and others, except for
5 * most of the UTF7 and UTF8 handling code which was lifted from Evolution.
7 * This program is open source software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #define SHOW_ME_VAPPEND_PRINTF
29 #include <libcitadel.h>
31 #include "sysdep_decls.h"
32 #include "internet_addressing.h"
33 #include "serv_imap.h"
34 #include "imap_tools.h"
35 #include "ctdl_module.h"
37 /* String handling helpers */
39 /* This code uses some pretty nasty string manipulation. To make everything
40 * manageable, we use this semi-high-level string manipulation API. Strings are
41 * always \0-terminated, despite the fact that we keep track of the size.
49 static void string_init(struct string* s, char* buf, int bufsize)
52 s->maxsize = bufsize-1;
53 s->size = strlen(buf);
56 static char* string_end(struct string* s)
58 return s->buffer + s->size;
61 /* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
63 static void string_append_sn(struct string* s, char* p, int len)
67 if ((s->size+len) > s->maxsize)
68 len = s->maxsize - s->size;
69 memcpy(s->buffer + s->size, p, len);
71 s->buffer[s->size] = '\0';
74 /* As above, always autocalculate. */
76 #define string_append_s(s, p) string_append_sn((s), (p), -1)
78 /* Appends a UTF8 character --- which may make the size change by more than 1!
79 * If the string overflows, the last character may become invalid. */
81 static void string_append_c(struct string* s, int c)
86 /* Don't do anything if there's no room. */
88 if (s->size == s->maxsize)
93 /* This is the most common case, so we optimise it. */
95 s->buffer[s->size++] = c;
96 s->buffer[s->size] = 0;
101 UmlChar[0] = 0xC0 | (c >> 6);
102 UmlChar[1] = 0x80 | (c & 0x3F);
105 else if (c <= 0xFFFF)
107 UmlChar[0] = 0xE0 | (c >> 12);
108 UmlChar[1] = 0x80 | ((c >> 6) & 0x3f);
109 UmlChar[2] = 0x80 | (c & 0x3f);
114 UmlChar[0] = 0xf0 | c >> 18;
115 UmlChar[1] = 0x80 | ((c >> 12) & 0x3f);
116 UmlChar[2] = 0x80 | ((c >> 6) & 0x3f);
117 UmlChar[3] = 0x80 | (c & 0x3f);
121 string_append_sn(s, UmlChar, len);
124 /* Reads a UTF8 character from a char*, advancing the pointer. */
126 int utf8_getc(char** ptr)
128 unsigned char* p = (unsigned char*) *ptr;
144 /* valid start char? (max 4 octets) */
146 m = 0x7f80; /* used to mask out the length bits */
149 if ((c & 0xc0) != 0x80)
154 v = (v<<6) | (c & 0x3f);
169 /* IMAP name safety */
171 /* IMAP has certain special requirements in its character set, which means we
172 * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
173 * strings. The next two routines (and their data tables) do that.
176 static char *utf7_alphabet =
177 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
179 static unsigned char utf7_rank[256] = {
180 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
181 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
182 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF,
183 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
184 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
185 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
186 0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
187 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
188 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
189 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
190 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
191 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
192 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
193 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
194 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
195 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
198 /* Base64 helpers. */
200 static void utf7_closeb64(struct string* out, int v, int i)
206 x = (v << (6-i)) & 0x3F;
207 string_append_c(out, utf7_alphabet[x]);
209 string_append_c(out, '-');
212 /* Convert from a Citadel name to an IMAP-safe name. Returns the end
213 * of the destination.
215 static char* toimap(char* destp, char* destend, char* src)
223 string_init(&dest, destp, destend-destp);
224 /* IMAP_syslog(LOG_DEBUG, "toimap %s", src); */
228 int c = utf8_getc(&src);
232 if (c >= 0x20 && c <= 0x7e)
236 utf7_closeb64(&dest, v, i);
244 string_append_sn(&dest, "&-", 2);
248 /* Citadel extension: / becomes |, because /
249 * isn't valid as part of an IMAP name. */
255 /* Citadel extension: backslashes mark folder
256 * seperators in the IMAP subfolder emulation
257 * hack. We turn them into / characters,
258 * *except* if it's the last character in the
267 string_append_c(&dest, c);
274 string_append_c(&dest, '&');
281 int x = (v >> (i-6)) & 0x3f;
282 string_append_c(&dest, utf7_alphabet[x]);
289 utf7_closeb64(&dest, v, i);
290 /* IMAP_syslog(LOG_DEBUG, " -> %s", destp); */
291 return string_end(&dest);
294 /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
296 static int cfrommap(int c);
297 static char* fromimap(char* destp, char* destend, const char* src)
300 unsigned const char *p = (unsigned const char*) src;
307 string_init(&dest, destp, destend-destp);
308 /* IMAP_syslog(LOG_DEBUG, "fromimap %s", src); */
315 /* US-ASCII characters. */
320 string_append_c(&dest, cfrommap(c));
326 string_append_c(&dest, '&');
329 else if (utf7_rank[c] != 0xff)
338 string_append_sn(&dest, "&-", 2);
346 else if (utf7_rank[c] != 0xFF)
348 v = (v<<6) | utf7_rank[c];
352 int x = (v >> (i-16)) & 0xFFFF;
353 string_append_c(&dest, cfrommap(x));
359 string_append_c(&dest, cfrommap(c));
366 /* IMAP_syslog(LOG_DEBUG, " -> %s", destp); */
367 return string_end(&dest);
370 /* Undoes the special character conversion. */
372 static int cfrommap(int c)
376 case '|': return '/';
377 case '/': return '\\';
385 /* Break a command down into tokens, unquoting any escaped characters. */
387 void MakeStringOf(StrBuf *Buf, int skip)
390 citimap_command *Cmd = &IMAP->Cmd;
392 for (i=skip; i<Cmd->num_parms; ++i) {
393 StrBufAppendBufPlain(Buf, Cmd->Params[i].Key, Cmd->Params[i].len, 0);
394 if (i < (Cmd->num_parms-1)) StrBufAppendBufPlain(Buf, HKEY(" "), 0);
399 void TokenCutRight(citimap_command *Cmd,
405 if (CutMe->len < n) {
410 CutAt = CutMe->Key + CutMe->len - n;
413 StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0');
416 void TokenCutLeft(citimap_command *Cmd,
420 if (CutMe->len < n) {
421 CutMe->Key += CutMe->len;
432 int CmdAdjust(citimap_command *Cmd,
437 if (nArgs > Cmd->avail_parms) {
438 Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs);
442 sizeof(ConstStr) * Cmd->avail_parms);
445 sizeof(ConstStr) * Cmd->avail_parms,
447 sizeof(ConstStr) * nArgs -
448 sizeof(ConstStr) * Cmd->avail_parms
454 sizeof(ConstStr) * nArgs);
456 Cmd->avail_parms = nArgs;
457 if (Cmd->Params != NULL)
459 Cmd->Params = Params;
465 sizeof(ConstStr) * Cmd->avail_parms);
469 return Cmd->avail_parms;
472 int imap_parameterize(citimap_command *Cmd)
475 const char *In, *End;
477 In = ChrPtr(Cmd->CmdBuf);
478 End = In + StrLength(Cmd->CmdBuf);
480 /* we start with 10 chars per arg, maybe we need to realloc later. */
481 nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
482 nArgs = CmdAdjust(Cmd, nArgs, 0);
485 /* Skip whitespace. */
491 /* Found the start of a token. */
493 Cmd->Params[Cmd->num_parms].Key = In;
495 /* Read in the token. */
504 /* Found a quoted section. */
506 Cmd->Params[Cmd->num_parms].Key++;
512 StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
515 else if (*In == '\\')
519 Cmd->Params[Cmd->num_parms].len =
520 In - Cmd->Params[Cmd->num_parms].Key;
522 return Cmd->num_parms;
527 else if (*In == '\\')
533 Cmd->Params[Cmd->num_parms].len =
534 In - Cmd->Params[Cmd->num_parms].Key;
536 return Cmd->num_parms;
540 StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
541 Cmd->Params[Cmd->num_parms].len =
542 In - Cmd->Params[Cmd->num_parms].Key;
543 if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
544 nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
549 return Cmd->num_parms;
553 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
555 long imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
557 char* bufend = buf+bufsize;
562 /* For mailboxes, just do it straight.
563 * Do the Cyrus-compatible thing: all private folders are
564 * subfolders of INBOX. */
566 if (qrbuf->QRflags & QR_MAILBOX)
568 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
570 pend = toimap(p, bufend, "INBOX");
575 p = toimap(p, bufend, "INBOX");
578 pend = toimap(p, bufend, qrbuf->QRname+11);
584 /* Otherwise, prefix the floor name as a "public folders" moniker. */
586 fl = CtdlGetCachedFloor(qrbuf->QRfloor);
587 p = toimap(p, bufend, fl->f_name);
590 pend = toimap(p, bufend, qrbuf->QRname);
596 * Convert an inputted folder name to our best guess as to what an equivalent
597 * room name should be.
599 * If an error occurs, it returns -1. Otherwise...
601 * The lower eight bits of the return value are the floor number on which the
602 * room most likely resides. The upper eight bits may contain flags,
603 * including IR_MAILBOX if we're dealing with a personal room.
607 int imap_roomname(char *rbuf, int bufsize, const char *foldername)
609 struct CitContext *CCC = CC;
611 char floorname[ROOMNAMELEN*2];
612 char roomname[ROOMNAMELEN];
617 if (foldername == NULL)
620 /* Unmunge the entire string into the output buffer. */
622 fromimap(rbuf, rbuf+bufsize, foldername);
624 /* Is this an IMAP inbox? */
626 if (strncasecmp(rbuf, "INBOX", 5) == 0)
630 /* It's the system inbox. */
632 safestrncpy(rbuf, MAILROOM, bufsize);
633 ret = (0 | IR_MAILBOX);
636 else if (rbuf[5] == FDELIM)
638 /* It's another personal mail folder. */
640 safestrncpy(rbuf, rbuf+6, bufsize);
641 ret = (0 | IR_MAILBOX);
645 /* If we get here, the folder just happens to start with INBOX
646 * --- fall through. */
649 /* Is this a multi-level room name? */
651 levels = num_tokens(rbuf, FDELIM);
655 /* Extract the main room name. */
657 len = extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
658 if (len < 0) len = 0;
659 safestrncpy(roomname, &rbuf[len + 1], sizeof(roomname));
661 /* Try and find it on any floor. */
663 for (i = 0; i < MAXFLOORS; ++i)
665 fl = CtdlGetCachedFloor(i);
666 if (fl->f_flags & F_INUSE)
668 if (strcasecmp(floorname, fl->f_name) == 0)
672 safestrncpy(rbuf, roomname, bufsize);
680 /* Meh. It's either not a multi-level room name, or else we
683 ret = (0 | IR_MAILBOX);
686 IMAP_syslog(LOG_DEBUG, "(That translates to \"%s\")", rbuf);
692 * Determine whether the supplied string is a valid message set.
693 * If the string contains only numbers, colons, commas, and asterisks,
694 * return 1 for a valid message set. If any other character is found,
697 int imap_is_message_set(const char *buf)
702 return (0); /* stupidity checks */
706 if (!strcasecmp(buf, "ALL"))
707 return (1); /* macro? why? */
709 for (i = 0; buf[i]; ++i) { /* now start the scan */
719 return (1); /* looks like we're good */
724 * imap_match.c, based on wildmat.c from INN
725 * hacked for Citadel/IMAP by Daniel Malament
728 /* note: not all return statements use these; don't change them */
729 #define WILDMAT_TRUE 1
730 #define WILDMAT_FALSE 0
731 #define WILDMAT_ABORT -1
732 #define WILDMAT_DELIM '/'
735 * Match text and p, return TRUE, FALSE, or ABORT.
737 static int do_imap_match(const char *supplied_text, const char *supplied_p)
740 char lcase_text[SIZ], lcase_p[SIZ];
744 /* Copy both strings and lowercase them, in order to
745 * make this entire operation case-insensitive.
748 ((supplied_text[i] != '\0') &&
749 (i < sizeof(lcase_text)));
751 lcase_text[i] = tolower(supplied_text[i]);
752 lcase_text[i] = '\0';
755 ((supplied_p[i] != '\0') &&
756 (i < sizeof(lcase_p)));
758 lcase_p[i] = tolower(supplied_p[i]);
762 for (p = lcase_p, text = lcase_text;
763 !IsEmptyStr(p) && !IsEmptyStr(text);
765 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
766 return WILDMAT_ABORT;
771 return WILDMAT_FALSE;
776 while (++p, ((*p == '*') || (*p == '%'))) {
777 /* Consecutive stars or %'s act
778 * just like one star.
783 /* Trailing star matches everything. */
787 if ((matched = do_imap_match(text++, p))
792 return WILDMAT_ABORT;
794 while (++p, (!IsEmptyStr(p) && ((*p == '*') || (*p == '%'))))
796 /* Consecutive %'s act just like one, but even
797 * a single star makes the sequence act like
807 * Trailing % matches everything
808 * without a delimiter.
810 while (!IsEmptyStr(text)) {
811 if (*text == WILDMAT_DELIM) {
812 return WILDMAT_FALSE;
818 while (!IsEmptyStr(text) &&
819 /* make shure texst - 1 isn't before lcase_p */
820 ((text == lcase_text) || (*(text - 1) != WILDMAT_DELIM)))
822 if ((matched = do_imap_match(text++, p))
827 return WILDMAT_ABORT;
831 if ((*text == '\0') && (*p == '\0')) return WILDMAT_TRUE;
832 else return WILDMAT_FALSE;
838 * Support function for mailbox pattern name matching in LIST and LSUB
839 * Returns nonzero if the supplied mailbox name matches the supplied pattern.
841 int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname)
843 /* handle just-star case quickly */
844 if ((pattern[0] == '*') && (pattern[1] == '\0')) {
847 return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
853 * Compare an IMAP date string (date only, no time) to the date found in
856 int imap_datecmp(const char *datestr, time_t msgtime) {
861 int day, month, year;
862 int msgday, msgmonth, msgyear;
865 char *imap_datecmp_ascmonths[12] = {
866 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
869 if (datestr == NULL) return(0);
871 /* Expecting a date in the form dd-Mmm-yyyy */
872 extract_token(daystr, datestr, 0, '-', sizeof daystr);
873 extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
874 extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
877 year = atoi(yearstr);
879 for (i=0; i<12; ++i) {
880 if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
885 /* Extract day/month/year from message timestamp */
886 localtime_r(&msgtime, &msgtm);
887 msgday = msgtm.tm_mday;
888 msgmonth = msgtm.tm_mon;
889 msgyear = msgtm.tm_year + 1900;
891 /* Now start comparing */
893 if (year < msgyear) return(+1);
894 if (year > msgyear) return(-1);
896 if (month < msgmonth) return(+1);
897 if (month > msgmonth) return(-1);
899 if (day < msgday) return(+1);
900 if (day > msgday) return(-1);
909 void IAPrintf(const char *Format, ...)
913 va_start(arg_ptr, Format);
914 StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
918 void iaputs(const char *Str, long Len)
920 StrBufAppendBufPlain(IMAP->Reply, Str, Len, 0);
923 void ireply(const char *Msg, long len)
925 citimap *Imap = IMAP;
927 StrBufAppendBufPlain(Imap->Reply,
928 CKEY(Imap->Cmd.Params[0]), 0);
929 StrBufAppendBufPlain(Imap->Reply,
931 StrBufAppendBufPlain(Imap->Reply,
934 StrBufAppendBufPlain(Imap->Reply,
939 void IReplyPrintf(const char *Format, ...)
941 citimap *Imap = IMAP;
945 StrBufAppendBufPlain(Imap->Reply,
946 CKEY(Imap->Cmd.Params[0]), 0);
948 StrBufAppendBufPlain(Imap->Reply,
951 va_start(arg_ptr, Format);
952 StrBufVAppendPrintf(IMAP->Reply, Format, arg_ptr);
955 StrBufAppendBufPlain(Imap->Reply,
961 /* Output a string to the IMAP client, either as a literal or quoted.
962 * (We do a literal if it has any double-quotes or backslashes.) */
965 void IPutStr(const char *Msg, long Len)
969 citimap *Imap = IMAP;
972 if ((Msg == NULL) || (Len == 0))
973 { /* yeah, we handle this */
974 StrBufAppendBufPlain(Imap->Reply, HKEY("NIL"), 0);
978 for (i = 0; i < Len; ++i) {
979 if ((Msg[i] == '\"') || (Msg[i] == '\\'))
984 StrBufAppendPrintf(Imap->Reply, "{%ld}\r\n", Len);
985 StrBufAppendBufPlain(Imap->Reply, Msg, Len, 0);
987 StrBufAppendBufPlain(Imap->Reply,
989 StrBufAppendBufPlain(Imap->Reply,
991 StrBufAppendBufPlain(Imap->Reply,
996 void IUnbuffer (void)
998 citimap *Imap = IMAP;
1000 cputbuf(Imap->Reply);
1001 FlushStrBuf(Imap->Reply);