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 char* string_end(struct string* s)
48 return s->buffer + s->size;
51 /* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
53 static void string_append_sn(struct string* s, char* p, int len)
57 if ((s->size+len) > s->maxsize)
58 len = s->maxsize - s->size;
59 memcpy(s->buffer + s->size, p, len);
61 s->buffer[s->size] = '\0';
64 /* As above, always autocalculate. */
66 #define string_append_s(s, p) string_append_sn((s), (p), -1)
68 /* Appends a UTF8 character --- which may make the size change by more than 1!
69 * If the string overflows, the last character may become invalid. */
71 static void string_append_c(struct string* s, int c)
76 /* Don't do anything if there's no room. */
78 if (s->size == s->maxsize)
83 /* This is the most common case, so we optimise it. */
85 s->buffer[s->size++] = c;
86 s->buffer[s->size] = 0;
91 buf[0] = 0xC0 | (c >> 6);
92 buf[1] = 0x80 | (c & 0x3F);
97 buf[0] = 0xE0 | (c >> 12);
98 buf[1] = 0x80 | ((c >> 6) & 0x3f);
99 buf[2] = 0x80 | (c & 0x3f);
104 buf[0] = 0xf0 | c >> 18;
105 buf[1] = 0x80 | ((c >> 12) & 0x3f);
106 buf[2] = 0x80 | ((c >> 6) & 0x3f);
107 buf[3] = 0x80 | (c & 0x3f);
111 string_append_sn(s, buf, len);
114 /* Reads a UTF8 character from a char*, advancing the pointer. */
116 int utf8_getc(char** ptr)
118 unsigned char* p = (unsigned char*) *ptr;
134 /* valid start char? (max 4 octets) */
136 m = 0x7f80; /* used to mask out the length bits */
139 if ((c & 0xc0) != 0x80)
144 v = (v<<6) | (c & 0x3f);
159 /* IMAP name safety */
161 /* IMAP has certain special requirements in its character set, which means we
162 * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
163 * strings. The next two routines (and their data tables) do that. */
165 static char *utf7_alphabet =
166 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
168 static unsigned char utf7_rank[256] = {
169 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
170 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
171 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF,
172 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
173 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
174 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
175 0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
176 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
177 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
178 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
179 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
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,0xFF,0xFF,0xFF,0xFF,0xFF,
183 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
184 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
187 /* Base64 helpers. */
189 static void utf7_closeb64(struct string* out, int v, int i)
195 x = (v << (6-i)) & 0x3F;
196 string_append_c(out, utf7_alphabet[x]);
198 string_append_c(out, '-');
201 /* Convert from a Citadel name to an IMAP-safe name. Returns the end of the destination. */
203 static char* toimap(char* destp, char* destend, char* src)
211 string_init(&dest, destp, destend-destp);
212 lprintf(CTDL_DEBUG, "toimap %s\r\n", src);
216 int c = utf8_getc(&src);
220 if (c >= 0x20 && c <= 0x7e)
224 utf7_closeb64(&dest, v, i);
232 string_append_sn(&dest, "&-", 2);
236 /* Citadel extension: / becomes |, because /
237 * isn't valid as part of an IMAP name. */
243 /* Citadel extension: backslashes mark folder
244 * seperators in the IMAP subfolder emulation
245 * hack. We turn them into / characters,
246 * *except* if it's the last character in the
255 string_append_c(&dest, c);
262 string_append_c(&dest, '&');
269 int x = (v >> (i-6)) & 0x3f;
270 string_append_c(&dest, utf7_alphabet[x]);
277 utf7_closeb64(&dest, v, i);
278 lprintf(CTDL_DEBUG, " -> %s\r\n", destp);
279 return string_end(&dest);
282 /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
284 static int cfrommap(int c);
285 static char* fromimap(char* destp, char* destend, char* src)
288 unsigned char *p = (unsigned char*) src;
295 string_init(&dest, destp, destend-destp);
296 lprintf(CTDL_DEBUG, "fromimap %s\r\n", src);
303 /* US-ASCII characters. */
308 string_append_c(&dest, cfrommap(c));
314 string_append_c(&dest, '&');
317 else if (utf7_rank[c] != 0xff)
326 string_append_sn(&dest, "&-", 2);
334 else if (utf7_rank[c] != 0xFF)
336 v = (v<<6) | utf7_rank[c];
340 int x = (v >> (i-16)) & 0xFFFF;
341 string_append_c(&dest, cfrommap(x));
347 string_append_c(&dest, cfrommap(c));
354 lprintf(CTDL_DEBUG, " -> %s\r\n", destp);
355 return string_end(&dest);
358 /* Undoes the special character conversion. */
360 static int cfrommap(int c)
364 case '|': return '/';
365 case '/': return '\\';
370 /* Output a string to the IMAP client, either as a literal or quoted.
371 * (We do a literal if it has any double-quotes or backslashes.) */
373 void imap_strout(char *buf)
378 if (buf == NULL) { /* yeah, we handle this */
382 for (i = 0; i < strlen(buf); ++i) {
383 if ((buf[i] == '\"') || (buf[i] == '\\'))
388 cprintf("{%ld}\r\n%s", (long)strlen(buf), buf);
390 cprintf("\"%s\"", buf);
394 /* Break a command down into tokens, unquoting any escaped characters. */
396 int imap_parameterize(char** args, char* in)
403 /* Skip whitespace. */
410 /* Found the start of a token. */
414 /* Read in the token. */
424 /* Found a quoted section. */
456 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
458 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
460 char* bufend = buf+bufsize;
464 /* For mailboxes, just do it straight.
465 * Do the Cyrus-compatible thing: all private folders are
466 * subfolders of INBOX. */
468 if (qrbuf->QRflags & QR_MAILBOX)
470 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
471 p = toimap(p, bufend, "INBOX");
474 p = toimap(p, bufend, "INBOX");
477 p = toimap(p, bufend, qrbuf->QRname+11);
482 /* Otherwise, prefix the floor name as a "public folders" moniker. */
484 fl = cgetfloor(qrbuf->QRfloor);
485 p = toimap(p, bufend, fl->f_name);
488 p = toimap(p, bufend, qrbuf->QRname);
493 * Convert an inputted folder name to our best guess as to what an equivalent
494 * room name should be.
496 * If an error occurs, it returns -1. Otherwise...
498 * The lower eight bits of the return value are the floor number on which the
499 * room most likely resides. The upper eight bits may contain flags,
500 * including IR_MAILBOX if we're dealing with a personal room.
504 int imap_roomname(char *rbuf, int bufsize, char *foldername)
513 if (foldername == NULL)
516 /* Unmunge the entire string into the output buffer. */
518 fromimap(rbuf, rbuf+bufsize, foldername);
520 /* Is this an IMAP inbox? */
522 if (strncasecmp(rbuf, "INBOX", 5) == 0)
526 /* It's the system inbox. */
528 safestrncpy(rbuf, MAILROOM, bufsize);
529 ret = (0 | IR_MAILBOX);
532 else if (rbuf[5] == FDELIM)
534 /* It's another personal mail folder. */
536 safestrncpy(rbuf, rbuf+6, bufsize);
537 ret = (0 | IR_MAILBOX);
541 /* If we get here, the folder just happens to start with INBOX
542 * --- fall through. */
545 /* Is this a multi-level room name? */
547 levels = num_tokens(rbuf, FDELIM);
550 /* Extract the main room name. */
552 extract_token(floorname, rbuf, 0, FDELIM);
553 strcpy(roomname, &rbuf[strlen(floorname)+1]);
555 /* Try and find it on any floor. */
557 for (i = 0; i < MAXFLOORS; ++i)
560 if (fl->f_flags & F_INUSE)
562 if (strcasecmp(floorname, fl->f_name) == 0)
566 strcpy(rbuf, roomname);
574 /* Meh. It's either not a multi-level room name, or else we couldn't find it. */
576 ret = (0 | IR_MAILBOX);
579 lprintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
584 * Output a struct internet_address_list in the form an IMAP client wants
586 void imap_ial_out(struct internet_address_list *ialist)
588 struct internet_address_list *iptr;
590 if (ialist == NULL) {
596 for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
598 imap_strout(iptr->ial_name);
600 imap_strout(iptr->ial_user);
602 imap_strout(iptr->ial_node);
612 * Determine whether the supplied string is a valid message set.
613 * If the string contains only numbers, colons, commas, and asterisks,
614 * return 1 for a valid message set. If any other character is found,
617 int imap_is_message_set(char *buf)
622 return (0); /* stupidity checks */
623 if (strlen(buf) == 0)
626 if (!strcasecmp(buf, "ALL"))
627 return (1); /* macro? why? */
629 for (i = 0; i < strlen(buf); ++i) { /* now start the scan */
639 return (1); /* looks like we're good */
644 * imap_match.c, based on wildmat.c from INN
645 * hacked for Citadel/IMAP by Daniel Malament
648 /* note: not all return statements use these; don't change them */
649 #define WILDMAT_TRUE 1
650 #define WILDMAT_FALSE 0
651 #define WILDMAT_ABORT -1
652 #define WILDMAT_DELIM '/'
655 * Match text and p, return TRUE, FALSE, or ABORT.
657 static int do_imap_match(const char *supplied_text, const char *supplied_p)
660 char lcase_text[SIZ], lcase_p[SIZ];
661 char *text = lcase_text;
664 /* Copy both strings and lowercase them, in order to
665 * make this entire operation case-insensitive.
667 for (i=0; i<=strlen(supplied_text); ++i)
668 lcase_text[i] = tolower(supplied_text[i]);
669 for (i=0; i<=strlen(supplied_p); ++i)
670 p[i] = tolower(supplied_p[i]);
673 for (; *p; text++, p++) {
674 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
675 return WILDMAT_ABORT;
680 return WILDMAT_FALSE;
685 while (++p, ((*p == '*') || (*p == '%'))) {
686 /* Consecutive stars or %'s act
687 * just like one star.
692 /* Trailing star matches everything. */
696 if ((matched = do_imap_match(text++, p))
701 return WILDMAT_ABORT;
703 while (++p, ((*p == '*') || (*p == '%'))) {
704 /* Consecutive %'s act just like one, but even
705 * a single star makes the sequence act like
715 * Trailing % matches everything
716 * without a delimiter.
719 if (*text == WILDMAT_DELIM) {
720 return WILDMAT_FALSE;
726 while (*text && (*(text - 1) != WILDMAT_DELIM)) {
727 if ((matched = do_imap_match(text++, p))
732 return WILDMAT_ABORT;
736 return (*text == '\0');
742 * Support function for mailbox pattern name matching in LIST and LSUB
743 * Returns nonzero if the supplied mailbox name matches the supplied pattern.
745 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
747 /* handle just-star case quickly */
748 if ((pattern[0] == '*') && (pattern[1] == '\0')) {
751 return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
757 * Compare an IMAP date string (date only, no time) to the date found in
760 int imap_datecmp(char *datestr, time_t msgtime) {
765 int day, month, year;
766 int msgday, msgmonth, msgyear;
769 if (datestr == NULL) return(0);
771 /* Expecting a date in the form dd-Mmm-yyyy */
772 extract_token(daystr, datestr, 0, '-');
773 extract_token(monthstr, datestr, 1, '-');
774 extract_token(yearstr, datestr, 2, '-');
777 year = atoi(yearstr);
779 for (i=0; i<12; ++i) {
780 if (!strcasecmp(monthstr, ascmonths[i])) {
785 /* Extract day/month/year from message timestamp */
786 localtime_r(&msgtime, &msgtm);
787 msgday = msgtm.tm_mday;
788 msgmonth = msgtm.tm_mon;
789 msgyear = msgtm.tm_year + 1900;
791 /* Now start comparing */
793 if (year < msgyear) return(+1);
794 if (year > msgyear) return(-1);
796 if (month < msgmonth) return(+1);
797 if (month > msgmonth) return(-1);
799 if (day < msgday) return(+1);
800 if (day > msgday) return(-1);