4 * Utility functions for the IMAP module.
6 * Note: most of the UTF7 and UTF8 handling in here was stolen from Evolution.
16 #include "sysdep_decls.h"
19 #include "internet_addressing.h"
20 #include "imap_tools.h"
27 /* String handling helpers */
29 /* This code uses some pretty narsty string manipulation. To make everything
30 * manageable, we use this semi-high-level string manipulation API. Strings are
31 * always \0-terminated, despite the fact that we keep track of the size. */
39 static void string_init(struct string* s, char* buf, int bufsize)
42 s->maxsize = bufsize-1;
43 s->size = strlen(buf);
46 static int string_overflow(struct string* s)
48 return (s->size == s->maxsize);
51 static int string_length(struct string* s)
56 static char* string_ptr(struct string* s)
61 static char* string_end(struct string* s)
63 return s->buffer + s->size;
66 /* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
68 static void string_append_sn(struct string* s, char* p, int len)
72 if ((s->size+len) > s->maxsize)
73 len = s->maxsize - s->size;
74 memcpy(s->buffer + s->size, p, len);
76 s->buffer[s->size] = '\0';
79 /* As above, always autocalculate. */
81 #define string_append_s(s, p) string_append_sn((s), (p), -1)
83 /* Appends a UTF8 character --- which may make the size change by more than 1!
84 * If the string overflows, the last character may become invalid. */
86 static void string_append_c(struct string* s, int c)
91 /* Don't do anything if there's no room. */
93 if (s->size == s->maxsize)
98 /* This is the most common case, so we optimise it. */
100 s->buffer[s->size++] = c;
101 s->buffer[s->size] = 0;
106 buf[0] = 0xC0 | (c >> 6);
107 buf[1] = 0x80 | (c & 0x3F);
110 else if (c <= 0xFFFF)
112 buf[0] = 0xE0 | (c >> 12);
113 buf[1] = 0x80 | ((c >> 6) & 0x3f);
114 buf[2] = 0x80 | (c & 0x3f);
119 buf[0] = 0xf0 | c >> 18;
120 buf[1] = 0x80 | ((c >> 12) & 0x3f);
121 buf[2] = 0x80 | ((c >> 6) & 0x3f);
122 buf[3] = 0x80 | (c & 0x3f);
126 string_append_sn(s, buf, len);
129 /* Append another string structure. */
131 static void string_append(struct string* dest, struct string* src)
133 string_append_sn(dest, src->buffer, src->size);
136 /* Reads a UTF8 character from a char*, advancing the pointer. */
138 int utf8_getc(char** ptr)
140 unsigned char* p = (unsigned char*) *ptr;
156 /* valid start char? (max 4 octets) */
158 m = 0x7f80; /* used to mask out the length bits */
161 if ((c & 0xc0) != 0x80)
166 v = (v<<6) | (c & 0x3f);
181 /* IMAP name safety */
183 /* IMAP has certain special requirements in its character set, which means we
184 * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
185 * strings. The next two routines (and their data tables) do that. */
187 static char *utf7_alphabet =
188 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
190 static unsigned char utf7_rank[256] = {
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,0x3E,0x3F,0xFF,0xFF,0xFF,
194 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
195 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
196 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
197 0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
198 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
199 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
200 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
201 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
202 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
203 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
204 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
205 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
206 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
209 /* Base64 helpers. */
211 static void utf7_closeb64(struct string* out, int v, int i)
217 x = (v << (6-i)) & 0x3F;
218 string_append_c(out, utf7_alphabet[x]);
220 string_append_c(out, '-');
223 /* Convert from a Citadel name to an IMAP-safe name. Returns the end of the destination. */
225 static char* toimap(char* destp, char* destend, char* src)
233 string_init(&dest, destp, destend-destp);
234 lprintf(CTDL_DEBUG, "toimap %s\r\n", src);
238 int c = utf8_getc(&src);
242 if (c >= 0x20 && c <= 0x7e)
246 utf7_closeb64(&dest, v, i);
254 string_append_sn(&dest, "&-", 2);
258 /* Citadel extension: / becomes |, because /
259 * isn't valid as part of an IMAP name. */
265 /* Citadel extension: backslashes mark folder
266 * seperators in the IMAP subfolder emulation
267 * hack. We turn them into / characters,
268 * *except* if it's the last character in the
277 string_append_c(&dest, c);
284 string_append_c(&dest, '&');
291 int x = (v >> (i-6)) & 0x3f;
292 string_append_c(&dest, utf7_alphabet[x]);
299 utf7_closeb64(&dest, v, i);
300 lprintf(CTDL_DEBUG, " -> %s\r\n", destp);
301 return string_end(&dest);
304 /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
306 static int cfrommap(int c);
307 static char* fromimap(char* destp, char* destend, char* src)
310 unsigned char *p = (unsigned char*) src;
317 string_init(&dest, destp, destend-destp);
318 lprintf(CTDL_DEBUG, "fromimap %s\r\n", src);
325 /* US-ASCII characters. */
330 string_append_c(&dest, cfrommap(c));
336 string_append_c(&dest, '&');
339 else if (utf7_rank[c] != 0xff)
348 string_append_sn(&dest, "&-", 2);
356 else if (utf7_rank[c] != 0xFF)
358 v = (v<<6) | utf7_rank[c];
362 int x = (v >> (i-16)) & 0xFFFF;
363 string_append_c(&dest, cfrommap(x));
369 string_append_c(&dest, cfrommap(c));
376 lprintf(CTDL_DEBUG, " -> %s\r\n", destp);
377 return string_end(&dest);
380 /* Undoes the special character conversion. */
382 static int cfrommap(int c)
386 case '|': return '/';
387 case '/': return '\\';
392 /* Output a string to the IMAP client, either as a literal or quoted.
393 * (We do a literal if it has any double-quotes or backslashes.) */
395 void imap_strout(char *buf)
400 if (buf == NULL) { /* yeah, we handle this */
404 for (i = 0; i < strlen(buf); ++i) {
405 if ((buf[i] == '\"') || (buf[i] == '\\'))
410 cprintf("{%ld}\r\n%s", (long)strlen(buf), buf);
412 cprintf("\"%s\"", buf);
416 /* Break a command down into tokens, unquoting any escaped characters. */
418 int imap_parameterize(char** args, char* in)
425 /* Skip whitespace. */
432 /* Found the start of a token. */
436 /* Read in the token. */
446 /* Found a quoted section. */
478 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
480 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
482 char* bufend = buf+bufsize;
486 /* For mailboxes, just do it straight.
487 * Do the Cyrus-compatible thing: all private folders are
488 * subfolders of INBOX. */
490 if (qrbuf->QRflags & QR_MAILBOX)
492 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
493 p = toimap(p, bufend, "INBOX");
496 p = toimap(p, bufend, "INBOX");
499 p = toimap(p, bufend, qrbuf->QRname+11);
504 /* Otherwise, prefix the floor name as a "public folders" moniker. */
506 fl = cgetfloor(qrbuf->QRfloor);
507 p = toimap(p, bufend, fl->f_name);
510 p = toimap(p, bufend, qrbuf->QRname);
515 * Convert an inputted folder name to our best guess as to what an equivalent
516 * room name should be.
518 * If an error occurs, it returns -1. Otherwise...
520 * The lower eight bits of the return value are the floor number on which the
521 * room most likely resides. The upper eight bits may contain flags,
522 * including IR_MAILBOX if we're dealing with a personal room.
526 int imap_roomname(char *rbuf, int bufsize, char *foldername)
535 if (foldername == NULL)
538 /* Unmunge the entire string into the output buffer. */
540 fromimap(rbuf, rbuf+bufsize, foldername);
542 /* Is this an IMAP inbox? */
544 if (strncasecmp(rbuf, "INBOX", 5) == 0)
548 /* It's the system inbox. */
550 safestrncpy(rbuf, MAILROOM, bufsize);
551 ret = (0 | IR_MAILBOX);
554 else if (rbuf[5] == FDELIM)
556 /* It's another personal mail folder. */
558 safestrncpy(rbuf, rbuf+6, bufsize);
559 ret = (0 | IR_MAILBOX);
563 /* If we get here, the folder just happens to start with INBOX
564 * --- fall through. */
567 /* Is this a multi-level room name? */
569 levels = num_tokens(rbuf, FDELIM);
572 /* Extract the main room name. */
574 extract_token(floorname, rbuf, 0, FDELIM);
575 strcpy(roomname, &rbuf[strlen(floorname)+1]);
577 /* Try and find it on any floor. */
579 for (i = 0; i < MAXFLOORS; ++i)
582 if (fl->f_flags & F_INUSE)
584 if (strcasecmp(floorname, fl->f_name) == 0)
588 strcpy(rbuf, roomname);
596 /* Meh. It's either not a multi-level room name, or else we couldn't find it. */
598 ret = (0 | IR_MAILBOX);
601 lprintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
606 * Output a struct internet_address_list in the form an IMAP client wants
608 void imap_ial_out(struct internet_address_list *ialist)
610 struct internet_address_list *iptr;
612 if (ialist == NULL) {
618 for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
620 imap_strout(iptr->ial_name);
622 imap_strout(iptr->ial_user);
624 imap_strout(iptr->ial_node);
634 * Determine whether the supplied string is a valid message set.
635 * If the string contains only numbers, colons, commas, and asterisks,
636 * return 1 for a valid message set. If any other character is found,
639 int imap_is_message_set(char *buf)
644 return (0); /* stupidity checks */
645 if (strlen(buf) == 0)
648 if (!strcasecmp(buf, "ALL"))
649 return (1); /* macro? why? */
651 for (i = 0; i < strlen(buf); ++i) { /* now start the scan */
661 return (1); /* looks like we're good */
666 * imap_match.c, based on wildmat.c from INN
667 * hacked for Citadel/IMAP by Daniel Malament
670 /* note: not all return statements use these; don't change them */
671 #define WILDMAT_TRUE 1
672 #define WILDMAT_FALSE 0
673 #define WILDMAT_ABORT -1
674 #define WILDMAT_DELIM '/'
677 * Match text and p, return TRUE, FALSE, or ABORT.
679 static int do_imap_match(const char *supplied_text, const char *supplied_p)
682 char lcase_text[SIZ], lcase_p[SIZ];
683 char *text = lcase_text;
686 /* Copy both strings and lowercase them, in order to
687 * make this entire operation case-insensitive.
689 for (i=0; i<=strlen(supplied_text); ++i)
690 lcase_text[i] = tolower(supplied_text[i]);
691 for (i=0; i<=strlen(supplied_p); ++i)
692 p[i] = tolower(supplied_p[i]);
695 for (; *p; text++, p++) {
696 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
697 return WILDMAT_ABORT;
702 return WILDMAT_FALSE;
707 while (++p, ((*p == '*') || (*p == '%'))) {
708 /* Consecutive stars or %'s act
709 * just like one star.
714 /* Trailing star matches everything. */
718 if ((matched = do_imap_match(text++, p))
723 return WILDMAT_ABORT;
725 while (++p, ((*p == '*') || (*p == '%'))) {
726 /* Consecutive %'s act just like one, but even
727 * a single star makes the sequence act like
737 * Trailing % matches everything
738 * without a delimiter.
741 if (*text == WILDMAT_DELIM) {
742 return WILDMAT_FALSE;
748 while (*text && (*(text - 1) != WILDMAT_DELIM)) {
749 if ((matched = do_imap_match(text++, p))
754 return WILDMAT_ABORT;
758 return (*text == '\0');
764 * Support function for mailbox pattern name matching in LIST and LSUB
765 * Returns nonzero if the supplied mailbox name matches the supplied pattern.
767 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
769 /* handle just-star case quickly */
770 if ((pattern[0] == '*') && (pattern[1] == '\0')) {
773 return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
779 * Compare an IMAP date string (date only, no time) to the date found in
782 int imap_datecmp(char *datestr, time_t msgtime) {
787 int day, month, year;
788 int msgday, msgmonth, msgyear;
791 if (datestr == NULL) return(0);
793 /* Expecting a date in the form dd-Mmm-yyyy */
794 extract_token(daystr, datestr, 0, '-');
795 extract_token(monthstr, datestr, 1, '-');
796 extract_token(yearstr, datestr, 2, '-');
799 year = atoi(yearstr);
801 for (i=0; i<12; ++i) {
802 if (!strcasecmp(monthstr, ascmonths[i])) {
807 /* Extract day/month/year from message timestamp */
808 localtime_r(&msgtime, &msgtm);
809 msgday = msgtm.tm_mday;
810 msgmonth = msgtm.tm_mon;
811 msgyear = msgtm.tm_year + 1900;
813 /* Now start comparing */
815 if (year < msgyear) return(+1);
816 if (year > msgyear) return(-1);
818 if (month < msgmonth) return(+1);
819 if (month > msgmonth) return(-1);
821 if (day < msgday) return(+1);
822 if (day > msgday) return(-1);