4 * Utility functions for the IMAP module.
6 * Copyright (c) 2001-2009 by the citadel.org team and others, except for
7 * most of the UTF7 and UTF8 handling code which was lifted from Evolution.
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include <libcitadel.h>
31 #include "sysdep_decls.h"
32 #include "internet_addressing.h"
33 #include "imap_tools.h"
34 #include "serv_imap.h"
35 #include "ctdl_module.h"
41 /* String handling helpers */
43 /* This code uses some pretty nasty string manipulation. To make everything
44 * manageable, we use this semi-high-level string manipulation API. Strings are
45 * always \0-terminated, despite the fact that we keep track of the size.
53 static void string_init(struct string* s, char* buf, int bufsize)
56 s->maxsize = bufsize-1;
57 s->size = strlen(buf);
60 static char* string_end(struct string* s)
62 return s->buffer + s->size;
65 /* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
67 static void string_append_sn(struct string* s, char* p, int len)
71 if ((s->size+len) > s->maxsize)
72 len = s->maxsize - s->size;
73 memcpy(s->buffer + s->size, p, len);
75 s->buffer[s->size] = '\0';
78 /* As above, always autocalculate. */
80 #define string_append_s(s, p) string_append_sn((s), (p), -1)
82 /* Appends a UTF8 character --- which may make the size change by more than 1!
83 * If the string overflows, the last character may become invalid. */
85 static void string_append_c(struct string* s, int c)
90 /* Don't do anything if there's no room. */
92 if (s->size == s->maxsize)
97 /* This is the most common case, so we optimise it. */
99 s->buffer[s->size++] = c;
100 s->buffer[s->size] = 0;
105 buf[0] = 0xC0 | (c >> 6);
106 buf[1] = 0x80 | (c & 0x3F);
109 else if (c <= 0xFFFF)
111 buf[0] = 0xE0 | (c >> 12);
112 buf[1] = 0x80 | ((c >> 6) & 0x3f);
113 buf[2] = 0x80 | (c & 0x3f);
118 buf[0] = 0xf0 | c >> 18;
119 buf[1] = 0x80 | ((c >> 12) & 0x3f);
120 buf[2] = 0x80 | ((c >> 6) & 0x3f);
121 buf[3] = 0x80 | (c & 0x3f);
125 string_append_sn(s, buf, len);
128 /* Reads a UTF8 character from a char*, advancing the pointer. */
130 int utf8_getc(char** ptr)
132 unsigned char* p = (unsigned char*) *ptr;
148 /* valid start char? (max 4 octets) */
150 m = 0x7f80; /* used to mask out the length bits */
153 if ((c & 0xc0) != 0x80)
158 v = (v<<6) | (c & 0x3f);
173 /* IMAP name safety */
175 /* IMAP has certain special requirements in its character set, which means we
176 * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
177 * strings. The next two routines (and their data tables) do that.
180 static char *utf7_alphabet =
181 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
183 static unsigned char utf7_rank[256] = {
184 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
185 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
186 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF,
187 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
188 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
189 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
190 0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
191 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,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,
196 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
197 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
198 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
199 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
202 /* Base64 helpers. */
204 static void utf7_closeb64(struct string* out, int v, int i)
210 x = (v << (6-i)) & 0x3F;
211 string_append_c(out, utf7_alphabet[x]);
213 string_append_c(out, '-');
216 /* Convert from a Citadel name to an IMAP-safe name. Returns the end
217 * of the destination.
219 static char* toimap(char* destp, char* destend, char* src)
227 string_init(&dest, destp, destend-destp);
228 /* CtdlLogPrintf(CTDL_DEBUG, "toimap %s\r\n", src); */
232 int c = utf8_getc(&src);
236 if (c >= 0x20 && c <= 0x7e)
240 utf7_closeb64(&dest, v, i);
248 string_append_sn(&dest, "&-", 2);
252 /* Citadel extension: / becomes |, because /
253 * isn't valid as part of an IMAP name. */
259 /* Citadel extension: backslashes mark folder
260 * seperators in the IMAP subfolder emulation
261 * hack. We turn them into / characters,
262 * *except* if it's the last character in the
271 string_append_c(&dest, c);
278 string_append_c(&dest, '&');
285 int x = (v >> (i-6)) & 0x3f;
286 string_append_c(&dest, utf7_alphabet[x]);
293 utf7_closeb64(&dest, v, i);
294 /* CtdlLogPrintf(CTDL_DEBUG, " -> %s\r\n", destp); */
295 return string_end(&dest);
298 /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
300 static int cfrommap(int c);
301 static char* fromimap(char* destp, char* destend, const char* src)
304 unsigned const char *p = (unsigned const char*) src;
311 string_init(&dest, destp, destend-destp);
312 /* CtdlLogPrintf(CTDL_DEBUG, "fromimap %s\r\n", src); */
319 /* US-ASCII characters. */
324 string_append_c(&dest, cfrommap(c));
330 string_append_c(&dest, '&');
333 else if (utf7_rank[c] != 0xff)
342 string_append_sn(&dest, "&-", 2);
350 else if (utf7_rank[c] != 0xFF)
352 v = (v<<6) | utf7_rank[c];
356 int x = (v >> (i-16)) & 0xFFFF;
357 string_append_c(&dest, cfrommap(x));
363 string_append_c(&dest, cfrommap(c));
370 /* CtdlLogPrintf(CTDL_DEBUG, " -> %s\r\n", destp); */
371 return string_end(&dest);
374 /* Undoes the special character conversion. */
376 static int cfrommap(int c)
380 case '|': return '/';
381 case '/': return '\\';
386 /* Output a string to the IMAP client, either as a literal or quoted.
387 * (We do a literal if it has any double-quotes or backslashes.) */
389 void plain_imap_strout(char *buf)
395 if (buf == NULL) { /* yeah, we handle this */
401 for (i = 0; i < len; ++i) {
402 if ((buf[i] == '\"') || (buf[i] == '\\'))
407 cprintf("{%ld}\r\n%s", len, buf);
409 cprintf("\"%s\"", buf);
413 /* Output a string to the IMAP client, either as a literal or quoted.
414 * (We do a literal if it has any double-quotes or backslashes.) */
416 void imap_strout(ConstStr *args)
421 if ((args == NULL) || (args->len == 0))
422 { /* yeah, we handle this */
427 for (i = 0; i < args->len; ++i) {
428 if ((args->Key[i] == '\"') || (args->Key[i] == '\\'))
433 cprintf("{%ld}\r\n%s", args->len, args->Key);
435 cprintf("\"%s\"", args->Key);
439 /* Break a command down into tokens, unquoting any escaped characters. */
442 void TokenCutRight(citimap_command *Cmd,
448 if (CutMe->len < n) {
453 CutAt = CutMe->Key + CutMe->len - n;
456 StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0');
459 void TokenCutLeft(citimap_command *Cmd,
463 if (CutMe->len < n) {
464 CutMe->Key += CutMe->len;
475 int CmdAdjust(citimap_command *Cmd,
480 if (nArgs > Cmd->avail_parms) {
481 Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs);
485 sizeof(ConstStr) * Cmd->avail_parms);
488 sizeof(ConstStr) * Cmd->avail_parms,
490 sizeof(ConstStr) * nArgs -
491 sizeof(ConstStr) * Cmd->avail_parms
496 Cmd->avail_parms = nArgs;
497 if (Cmd->Params != NULL)
499 Cmd->Params = Params;
505 sizeof(ConstStr) * Cmd->avail_parms);
509 return Cmd->num_parms;
512 int imap_parameterize(citimap_command *Cmd)
515 const char *In, *End;
517 In = ChrPtr(Cmd->CmdBuf);
518 End = In + StrLength(Cmd->CmdBuf);
520 /* we start with 10 chars per arg, maybe we need to realloc later. */
521 nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
522 nArgs = CmdAdjust(Cmd, nArgs, 0);
525 /* Skip whitespace. */
531 /* Found the start of a token. */
533 Cmd->Params[Cmd->num_parms].Key = In;
535 /* Read in the token. */
544 /* Found a quoted section. */
546 Cmd->Params[Cmd->num_parms].Key++;
552 StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
555 else if (*In == '\\')
559 Cmd->Params[Cmd->num_parms].len =
560 In - Cmd->Params[Cmd->num_parms].Key;
562 return Cmd->num_parms;
567 else if (*In == '\\')
573 Cmd->Params[Cmd->num_parms].len =
574 In - Cmd->Params[Cmd->num_parms].Key;
576 return Cmd->num_parms;
580 StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
581 Cmd->Params[Cmd->num_parms].len =
582 In - Cmd->Params[Cmd->num_parms].Key;
584 if (Cmd->num_parms >= Cmd->avail_parms) {
585 nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
589 return Cmd->num_parms;
592 int old_imap_parameterize(char** args, char *in)
599 /* Skip whitespace. */
606 /* Found the start of a token. */
610 /* Read in the token. */
620 /* Found a quoted section. */
652 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
654 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
656 char* bufend = buf+bufsize;
660 /* For mailboxes, just do it straight.
661 * Do the Cyrus-compatible thing: all private folders are
662 * subfolders of INBOX. */
664 if (qrbuf->QRflags & QR_MAILBOX)
666 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
667 p = toimap(p, bufend, "INBOX");
670 p = toimap(p, bufend, "INBOX");
673 p = toimap(p, bufend, qrbuf->QRname+11);
678 /* Otherwise, prefix the floor name as a "public folders" moniker. */
680 fl = CtdlGetCachedFloor(qrbuf->QRfloor);
681 p = toimap(p, bufend, fl->f_name);
684 p = toimap(p, bufend, qrbuf->QRname);
689 * Convert an inputted folder name to our best guess as to what an equivalent
690 * room name should be.
692 * If an error occurs, it returns -1. Otherwise...
694 * The lower eight bits of the return value are the floor number on which the
695 * room most likely resides. The upper eight bits may contain flags,
696 * including IR_MAILBOX if we're dealing with a personal room.
700 int imap_roomname(char *rbuf, int bufsize, const char *foldername)
704 char roomname[ROOMNAMELEN];
709 if (foldername == NULL)
712 /* Unmunge the entire string into the output buffer. */
714 fromimap(rbuf, rbuf+bufsize, foldername);
716 /* Is this an IMAP inbox? */
718 if (strncasecmp(rbuf, "INBOX", 5) == 0)
722 /* It's the system inbox. */
724 safestrncpy(rbuf, MAILROOM, bufsize);
725 ret = (0 | IR_MAILBOX);
728 else if (rbuf[5] == FDELIM)
730 /* It's another personal mail folder. */
732 safestrncpy(rbuf, rbuf+6, bufsize);
733 ret = (0 | IR_MAILBOX);
737 /* If we get here, the folder just happens to start with INBOX
738 * --- fall through. */
741 /* Is this a multi-level room name? */
743 levels = num_tokens(rbuf, FDELIM);
746 /* Extract the main room name. */
748 extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
749 strcpy(roomname, &rbuf[strlen(floorname)+1]);
751 /* Try and find it on any floor. */
753 for (i = 0; i < MAXFLOORS; ++i)
755 fl = CtdlGetCachedFloor(i);
756 if (fl->f_flags & F_INUSE)
758 if (strcasecmp(floorname, fl->f_name) == 0)
762 safestrncpy(rbuf, roomname, bufsize);
770 /* Meh. It's either not a multi-level room name, or else we
773 ret = (0 | IR_MAILBOX);
776 CtdlLogPrintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
781 * Output a struct internet_address_list in the form an IMAP client wants
783 void imap_ial_out(struct internet_address_list *ialist)
785 struct internet_address_list *iptr;
787 if (ialist == NULL) {
793 for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
795 plain_imap_strout(iptr->ial_name);
797 plain_imap_strout(iptr->ial_user);
799 plain_imap_strout(iptr->ial_node);
809 * Determine whether the supplied string is a valid message set.
810 * If the string contains only numbers, colons, commas, and asterisks,
811 * return 1 for a valid message set. If any other character is found,
814 int imap_is_message_set(const char *buf)
819 return (0); /* stupidity checks */
823 if (!strcasecmp(buf, "ALL"))
824 return (1); /* macro? why? */
826 for (i = 0; buf[i]; ++i) { /* now start the scan */
836 return (1); /* looks like we're good */
841 * imap_match.c, based on wildmat.c from INN
842 * hacked for Citadel/IMAP by Daniel Malament
845 /* note: not all return statements use these; don't change them */
846 #define WILDMAT_TRUE 1
847 #define WILDMAT_FALSE 0
848 #define WILDMAT_ABORT -1
849 #define WILDMAT_DELIM '/'
852 * Match text and p, return TRUE, FALSE, or ABORT.
854 static int do_imap_match(const char *supplied_text, const char *supplied_p)
857 char lcase_text[SIZ], lcase_p[SIZ];
858 char *text = lcase_text;
862 /* Copy both strings and lowercase them, in order to
863 * make this entire operation case-insensitive.
865 len = strlen(supplied_text);
866 for (i=0; i<=len; ++i)
867 lcase_text[i] = tolower(supplied_text[i]);
868 len = strlen(supplied_p);
869 for (i=0; i<=len; ++i)
870 p[i] = tolower(supplied_p[i]);
873 for (; *p; text++, p++) {
874 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
875 return WILDMAT_ABORT;
880 return WILDMAT_FALSE;
885 while (++p, ((*p == '*') || (*p == '%'))) {
886 /* Consecutive stars or %'s act
887 * just like one star.
892 /* Trailing star matches everything. */
896 if ((matched = do_imap_match(text++, p))
901 return WILDMAT_ABORT;
903 while (++p, ((*p == '*') || (*p == '%'))) {
904 /* Consecutive %'s act just like one, but even
905 * a single star makes the sequence act like
915 * Trailing % matches everything
916 * without a delimiter.
919 if (*text == WILDMAT_DELIM) {
920 return WILDMAT_FALSE;
926 while (*text && (*(text - 1) != WILDMAT_DELIM)) {
927 if ((matched = do_imap_match(text++, p))
932 return WILDMAT_ABORT;
936 return (*text == '\0');
942 * Support function for mailbox pattern name matching in LIST and LSUB
943 * Returns nonzero if the supplied mailbox name matches the supplied pattern.
945 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
947 /* handle just-star case quickly */
948 if ((pattern[0] == '*') && (pattern[1] == '\0')) {
951 return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
957 * Compare an IMAP date string (date only, no time) to the date found in
960 int imap_datecmp(const char *datestr, time_t msgtime) {
965 int day, month, year;
966 int msgday, msgmonth, msgyear;
969 char *imap_datecmp_ascmonths[12] = {
970 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
973 if (datestr == NULL) return(0);
975 /* Expecting a date in the form dd-Mmm-yyyy */
976 extract_token(daystr, datestr, 0, '-', sizeof daystr);
977 extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
978 extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
981 year = atoi(yearstr);
983 for (i=0; i<12; ++i) {
984 if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
989 /* Extract day/month/year from message timestamp */
990 localtime_r(&msgtime, &msgtm);
991 msgday = msgtm.tm_mday;
992 msgmonth = msgtm.tm_mon;
993 msgyear = msgtm.tm_year + 1900;
995 /* Now start comparing */
997 if (year < msgyear) return(+1);
998 if (year > msgyear) return(-1);
1000 if (month < msgmonth) return(+1);
1001 if (month > msgmonth) return(-1);
1003 if (day < msgday) return(+1);
1004 if (day > msgday) return(-1);