4 * Utility functions for the IMAP module.
6 * Note: most of the UTF7 and UTF8 handling in here was lifted from Evolution.
15 #include <libcitadel.h>
17 #include "sysdep_decls.h"
19 #include "internet_addressing.h"
20 #include "imap_tools.h"
21 #include "ctdl_module.h"
27 /* String handling helpers */
29 /* This code uses some pretty nasty 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.
166 static char *utf7_alphabet
=
167 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
169 static unsigned char utf7_rank
[256] = {
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,0xFF,0xFF,0xFF,0xFF,0xFF,
172 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0x3F,0xFF,0xFF,0xFF,
173 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
174 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
175 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
176 0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
177 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,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,
185 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
188 /* Base64 helpers. */
190 static void utf7_closeb64(struct string
* out
, int v
, int i
)
196 x
= (v
<< (6-i
)) & 0x3F;
197 string_append_c(out
, utf7_alphabet
[x
]);
199 string_append_c(out
, '-');
202 /* Convert from a Citadel name to an IMAP-safe name. Returns the end
203 * of the destination.
205 static char* toimap(char* destp
, char* destend
, char* src
)
213 string_init(&dest
, destp
, destend
-destp
);
214 /* CtdlLogPrintf(CTDL_DEBUG, "toimap %s\r\n", src); */
218 int c
= utf8_getc(&src
);
222 if (c
>= 0x20 && c
<= 0x7e)
226 utf7_closeb64(&dest
, v
, i
);
234 string_append_sn(&dest
, "&-", 2);
238 /* Citadel extension: / becomes |, because /
239 * isn't valid as part of an IMAP name. */
245 /* Citadel extension: backslashes mark folder
246 * seperators in the IMAP subfolder emulation
247 * hack. We turn them into / characters,
248 * *except* if it's the last character in the
257 string_append_c(&dest
, c
);
264 string_append_c(&dest
, '&');
271 int x
= (v
>> (i
-6)) & 0x3f;
272 string_append_c(&dest
, utf7_alphabet
[x
]);
279 utf7_closeb64(&dest
, v
, i
);
280 /* CtdlLogPrintf(CTDL_DEBUG, " -> %s\r\n", destp); */
281 return string_end(&dest
);
284 /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
286 static int cfrommap(int c
);
287 static char* fromimap(char* destp
, char* destend
, char* src
)
290 unsigned char *p
= (unsigned char*) src
;
297 string_init(&dest
, destp
, destend
-destp
);
298 /* CtdlLogPrintf(CTDL_DEBUG, "fromimap %s\r\n", src); */
305 /* US-ASCII characters. */
310 string_append_c(&dest
, cfrommap(c
));
316 string_append_c(&dest
, '&');
319 else if (utf7_rank
[c
] != 0xff)
328 string_append_sn(&dest
, "&-", 2);
336 else if (utf7_rank
[c
] != 0xFF)
338 v
= (v
<<6) | utf7_rank
[c
];
342 int x
= (v
>> (i
-16)) & 0xFFFF;
343 string_append_c(&dest
, cfrommap(x
));
349 string_append_c(&dest
, cfrommap(c
));
356 /* CtdlLogPrintf(CTDL_DEBUG, " -> %s\r\n", destp); */
357 return string_end(&dest
);
360 /* Undoes the special character conversion. */
362 static int cfrommap(int c
)
366 case '|': return '/';
367 case '/': return '\\';
372 /* Output a string to the IMAP client, either as a literal or quoted.
373 * (We do a literal if it has any double-quotes or backslashes.) */
375 void imap_strout(char *buf
)
381 if (buf
== NULL
) { /* yeah, we handle this */
387 for (i
= 0; i
< len
; ++i
) {
388 if ((buf
[i
] == '\"') || (buf
[i
] == '\\'))
393 cprintf("{%ld}\r\n%s", len
, buf
);
395 cprintf("\"%s\"", buf
);
399 /* Break a command down into tokens, unquoting any escaped characters. */
401 int imap_parameterize(char** args
, char* in
)
408 /* Skip whitespace. */
415 /* Found the start of a token. */
419 /* Read in the token. */
429 /* Found a quoted section. */
461 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
463 void imap_mailboxname(char *buf
, int bufsize
, struct ctdlroom
*qrbuf
)
465 char* bufend
= buf
+bufsize
;
469 /* For mailboxes, just do it straight.
470 * Do the Cyrus-compatible thing: all private folders are
471 * subfolders of INBOX. */
473 if (qrbuf
->QRflags
& QR_MAILBOX
)
475 if (strcasecmp(qrbuf
->QRname
+11, MAILROOM
) == 0)
476 p
= toimap(p
, bufend
, "INBOX");
479 p
= toimap(p
, bufend
, "INBOX");
482 p
= toimap(p
, bufend
, qrbuf
->QRname
+11);
487 /* Otherwise, prefix the floor name as a "public folders" moniker. */
489 fl
= cgetfloor(qrbuf
->QRfloor
);
490 p
= toimap(p
, bufend
, fl
->f_name
);
493 p
= toimap(p
, bufend
, qrbuf
->QRname
);
498 * Convert an inputted folder name to our best guess as to what an equivalent
499 * room name should be.
501 * If an error occurs, it returns -1. Otherwise...
503 * The lower eight bits of the return value are the floor number on which the
504 * room most likely resides. The upper eight bits may contain flags,
505 * including IR_MAILBOX if we're dealing with a personal room.
509 int imap_roomname(char *rbuf
, int bufsize
, char *foldername
)
513 char roomname
[ROOMNAMELEN
];
518 if (foldername
== NULL
)
521 /* Unmunge the entire string into the output buffer. */
523 fromimap(rbuf
, rbuf
+bufsize
, foldername
);
525 /* Is this an IMAP inbox? */
527 if (strncasecmp(rbuf
, "INBOX", 5) == 0)
531 /* It's the system inbox. */
533 safestrncpy(rbuf
, MAILROOM
, bufsize
);
534 ret
= (0 | IR_MAILBOX
);
537 else if (rbuf
[5] == FDELIM
)
539 /* It's another personal mail folder. */
541 safestrncpy(rbuf
, rbuf
+6, bufsize
);
542 ret
= (0 | IR_MAILBOX
);
546 /* If we get here, the folder just happens to start with INBOX
547 * --- fall through. */
550 /* Is this a multi-level room name? */
552 levels
= num_tokens(rbuf
, FDELIM
);
555 /* Extract the main room name. */
557 extract_token(floorname
, rbuf
, 0, FDELIM
, sizeof floorname
);
558 strcpy(roomname
, &rbuf
[strlen(floorname
)+1]);
560 /* Try and find it on any floor. */
562 for (i
= 0; i
< MAXFLOORS
; ++i
)
565 if (fl
->f_flags
& F_INUSE
)
567 if (strcasecmp(floorname
, fl
->f_name
) == 0)
571 safestrncpy(rbuf
, roomname
, bufsize
);
579 /* Meh. It's either not a multi-level room name, or else we
582 ret
= (0 | IR_MAILBOX
);
585 CtdlLogPrintf(CTDL_DEBUG
, "(That translates to \"%s\")\n", rbuf
);
590 * Output a struct internet_address_list in the form an IMAP client wants
592 void imap_ial_out(struct internet_address_list
*ialist
)
594 struct internet_address_list
*iptr
;
596 if (ialist
== NULL
) {
602 for (iptr
= ialist
; iptr
!= NULL
; iptr
= iptr
->next
) {
604 imap_strout(iptr
->ial_name
);
606 imap_strout(iptr
->ial_user
);
608 imap_strout(iptr
->ial_node
);
618 * Determine whether the supplied string is a valid message set.
619 * If the string contains only numbers, colons, commas, and asterisks,
620 * return 1 for a valid message set. If any other character is found,
623 int imap_is_message_set(char *buf
)
628 return (0); /* stupidity checks */
632 if (!strcasecmp(buf
, "ALL"))
633 return (1); /* macro? why? */
635 for (i
= 0; buf
[i
]; ++i
) { /* now start the scan */
645 return (1); /* looks like we're good */
650 * imap_match.c, based on wildmat.c from INN
651 * hacked for Citadel/IMAP by Daniel Malament
654 /* note: not all return statements use these; don't change them */
655 #define WILDMAT_TRUE 1
656 #define WILDMAT_FALSE 0
657 #define WILDMAT_ABORT -1
658 #define WILDMAT_DELIM '/'
661 * Match text and p, return TRUE, FALSE, or ABORT.
663 static int do_imap_match(const char *supplied_text
, const char *supplied_p
)
666 char lcase_text
[SIZ
], lcase_p
[SIZ
];
667 char *text
= lcase_text
;
670 /* Copy both strings and lowercase them, in order to
671 * make this entire operation case-insensitive.
673 for (i
=0; i
<=strlen(supplied_text
); ++i
)
674 lcase_text
[i
] = tolower(supplied_text
[i
]);
675 for (i
=0; i
<=strlen(supplied_p
); ++i
)
676 p
[i
] = tolower(supplied_p
[i
]);
679 for (; *p
; text
++, p
++) {
680 if ((*text
== '\0') && (*p
!= '*') && (*p
!= '%')) {
681 return WILDMAT_ABORT
;
686 return WILDMAT_FALSE
;
691 while (++p
, ((*p
== '*') || (*p
== '%'))) {
692 /* Consecutive stars or %'s act
693 * just like one star.
698 /* Trailing star matches everything. */
702 if ((matched
= do_imap_match(text
++, p
))
707 return WILDMAT_ABORT
;
709 while (++p
, ((*p
== '*') || (*p
== '%'))) {
710 /* Consecutive %'s act just like one, but even
711 * a single star makes the sequence act like
721 * Trailing % matches everything
722 * without a delimiter.
725 if (*text
== WILDMAT_DELIM
) {
726 return WILDMAT_FALSE
;
732 while (*text
&& (*(text
- 1) != WILDMAT_DELIM
)) {
733 if ((matched
= do_imap_match(text
++, p
))
738 return WILDMAT_ABORT
;
742 return (*text
== '\0');
748 * Support function for mailbox pattern name matching in LIST and LSUB
749 * Returns nonzero if the supplied mailbox name matches the supplied pattern.
751 int imap_mailbox_matches_pattern(char *pattern
, char *mailboxname
)
753 /* handle just-star case quickly */
754 if ((pattern
[0] == '*') && (pattern
[1] == '\0')) {
757 return (do_imap_match(mailboxname
, pattern
) == WILDMAT_TRUE
);
763 * Compare an IMAP date string (date only, no time) to the date found in
766 int imap_datecmp(char *datestr
, time_t msgtime
) {
771 int day
, month
, year
;
772 int msgday
, msgmonth
, msgyear
;
775 char *imap_datecmp_ascmonths
[12] = {
776 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
779 if (datestr
== NULL
) return(0);
781 /* Expecting a date in the form dd-Mmm-yyyy */
782 extract_token(daystr
, datestr
, 0, '-', sizeof daystr
);
783 extract_token(monthstr
, datestr
, 1, '-', sizeof monthstr
);
784 extract_token(yearstr
, datestr
, 2, '-', sizeof yearstr
);
787 year
= atoi(yearstr
);
789 for (i
=0; i
<12; ++i
) {
790 if (!strcasecmp(monthstr
, imap_datecmp_ascmonths
[i
])) {
795 /* Extract day/month/year from message timestamp */
796 localtime_r(&msgtime
, &msgtm
);
797 msgday
= msgtm
.tm_mday
;
798 msgmonth
= msgtm
.tm_mon
;
799 msgyear
= msgtm
.tm_year
+ 1900;
801 /* Now start comparing */
803 if (year
< msgyear
) return(+1);
804 if (year
> msgyear
) return(-1);
806 if (month
< msgmonth
) return(+1);
807 if (month
> msgmonth
) return(-1);
809 if (day
< msgday
) return(+1);
810 if (day
> msgday
) return(-1);