Citadel API clean up.
[citadel.git] / citadel / modules / imap / imap_tools.c
1 /*
2  * $Id$
3  *
4  * Utility functions for the IMAP module.
5  *
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.
8  *
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.
13  *
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.
18  *
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
22  */
23
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <stdio.h>
27 #include <ctype.h>
28 #include <string.h>
29 #include <libcitadel.h>
30 #include "citadel.h"
31 #include "sysdep_decls.h"
32 #include "internet_addressing.h"
33 #include "imap_tools.h"
34 #include "ctdl_module.h"
35
36 #ifndef HAVE_SNPRINTF
37 #include "snprintf.h"
38 #endif
39
40 /* String handling helpers */
41
42 /* This code uses some pretty nasty string manipulation. To make everything
43  * manageable, we use this semi-high-level string manipulation API. Strings are
44  * always \0-terminated, despite the fact that we keep track of the size.
45  */
46 struct string {
47         char* buffer;
48         int maxsize;
49         int size;
50 };
51
52 static void string_init(struct string* s, char* buf, int bufsize)
53 {
54         s->buffer = buf;
55         s->maxsize = bufsize-1;
56         s->size = strlen(buf);
57 }
58
59 static char* string_end(struct string* s)
60 {
61         return s->buffer + s->size;
62 }
63
64 /* Append a UTF8 string of a particular length (in bytes). -1 to autocalculate. */
65
66 static void string_append_sn(struct string* s, char* p, int len)
67 {
68         if (len == -1)
69                 len = strlen(p);
70         if ((s->size+len) > s->maxsize)
71                 len = s->maxsize - s->size;
72         memcpy(s->buffer + s->size, p, len);
73         s->size += len;
74         s->buffer[s->size] = '\0';
75 }
76
77 /* As above, always autocalculate. */
78
79 #define string_append_s(s, p) string_append_sn((s), (p), -1)
80
81 /* Appends a UTF8 character --- which may make the size change by more than 1!
82  * If the string overflows, the last character may become invalid. */
83
84 static void string_append_c(struct string* s, int c)
85 {
86         char buf[5];
87         int len = 0;
88
89         /* Don't do anything if there's no room. */
90
91         if (s->size == s->maxsize)
92                 return;
93
94         if (c <= 0x7F)
95         {
96                 /* This is the most common case, so we optimise it. */
97
98                 s->buffer[s->size++] = c;
99                 s->buffer[s->size] = 0;
100                 return;
101         }
102         else if (c <= 0x7FF)
103         {
104                 buf[0] = 0xC0 | (c >> 6);
105                 buf[1] = 0x80 | (c & 0x3F);
106                 len = 2;
107         }
108         else if (c <= 0xFFFF)
109         {
110                 buf[0] = 0xE0 | (c >> 12);
111                 buf[1] = 0x80 | ((c >> 6) & 0x3f);
112                 buf[2] = 0x80 | (c & 0x3f);
113                 len = 3;
114         }
115         else
116         {
117                 buf[0] = 0xf0 | c >> 18;
118                 buf[1] = 0x80 | ((c >> 12) & 0x3f);
119                 buf[2] = 0x80 | ((c >> 6) & 0x3f);
120                 buf[3] = 0x80 | (c & 0x3f);
121                 len = 4;
122         }
123
124         string_append_sn(s, buf, len);
125 }       
126
127 /* Reads a UTF8 character from a char*, advancing the pointer. */
128
129 int utf8_getc(char** ptr)
130 {
131         unsigned char* p = (unsigned char*) *ptr;
132         unsigned char c, r;
133         int v, m;
134
135         for (;;)
136         {
137                 r = *p++;
138         loop:
139                 if (r < 0x80)
140                 {
141                         *ptr = (char*) p;
142                         v = r;
143                         break;
144                 }
145                 else if (r < 0xf8)
146                 {
147                         /* valid start char? (max 4 octets) */
148                         v = r;
149                         m = 0x7f80;     /* used to mask out the length bits */
150                         do {
151                                 c = *p++;
152                                 if ((c & 0xc0) != 0x80)
153                                 {
154                                         r = c;
155                                         goto loop;
156                                 }
157                                 v = (v<<6) | (c & 0x3f);
158                                 r<<=1;
159                                 m<<=5;
160                         } while (r & 0x40);
161                         
162                         *ptr = (char*)p;
163
164                         v &= ~m;
165                         break;
166                 }
167         }
168
169         return v;
170 }
171
172 /* IMAP name safety */
173
174 /* IMAP has certain special requirements in its character set, which means we
175  * have to do a fair bit of work to convert Citadel's UTF8 strings to IMAP
176  * strings. The next two routines (and their data tables) do that.
177  */
178
179 static char *utf7_alphabet =
180         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
181
182 static unsigned char utf7_rank[256] = {
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,0x3E,0x3F,0xFF,0xFF,0xFF,
186         0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
187         0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
188         0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
189         0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
190         0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,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,
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 };
200
201 /* Base64 helpers. */
202
203 static void utf7_closeb64(struct string* out, int v, int i)
204 {
205         int x;
206
207         if (i > 0)
208         {
209                 x = (v << (6-i)) & 0x3F;
210                 string_append_c(out, utf7_alphabet[x]);
211         }
212         string_append_c(out, '-');
213 }
214
215 /* Convert from a Citadel name to an IMAP-safe name. Returns the end
216  * of the destination.
217  */
218 static char* toimap(char* destp, char* destend, char* src)
219 {
220         struct string dest;
221         int state = 0;
222         int v = 0;
223         int i = 0;
224
225         *destp = 0;
226         string_init(&dest, destp, destend-destp);
227         /* CtdlLogPrintf(CTDL_DEBUG, "toimap %s\r\n", src); */
228
229         for (;;)
230         {
231                 int c = utf8_getc(&src);
232                 if (c == '\0')
233                         break;
234
235                 if (c >= 0x20 && c <= 0x7e)
236                 {
237                         if (state == 1)
238                         {
239                                 utf7_closeb64(&dest, v, i);
240                                 state = 0;
241                                 i = 0;
242                         }
243
244                         switch (c)
245                         {
246                                 case '&':
247                                         string_append_sn(&dest, "&-", 2);
248                                         break;
249
250                                 case '/':
251                                         /* Citadel extension: / becomes |, because /
252                                          * isn't valid as part of an IMAP name. */
253
254                                         c = '|';
255                                         goto defaultcase;
256
257                                 case '\\':
258                                         /* Citadel extension: backslashes mark folder
259                                          * seperators in the IMAP subfolder emulation
260                                          * hack. We turn them into / characters,
261                                          * *except* if it's the last character in the
262                                          * string. */
263
264                                         if (*src != '\0')
265                                                 c = '/';
266                                         /* fall through */
267
268                                 default:
269                                 defaultcase:
270                                         string_append_c(&dest, c);
271                         }
272                 }
273                 else
274                 {
275                         if (state == 0)
276                         {
277                                 string_append_c(&dest, '&');
278                                 state = 1;
279                         }
280                         v = (v << 16) | c;
281                         i += 16;
282                         while (i >= 6)
283                         {
284                                 int x = (v >> (i-6)) & 0x3f;
285                                 string_append_c(&dest, utf7_alphabet[x]);
286                                 i -= 6;
287                         }
288                 }
289         }
290
291         if (state == 1)
292                 utf7_closeb64(&dest, v, i);
293         /* CtdlLogPrintf(CTDL_DEBUG, "    -> %s\r\n", destp); */
294         return string_end(&dest);
295 }
296
297 /* Convert from an IMAP-safe name back into a Citadel name. Returns the end of the destination. */
298
299 static int cfrommap(int c);
300 static char* fromimap(char* destp, char* destend, char* src)
301 {
302         struct string dest;
303         unsigned char *p = (unsigned char*) src;
304         int v = 0;
305         int i = 0;
306         int state = 0;
307         int c;
308
309         *destp = 0;
310         string_init(&dest, destp, destend-destp);
311         /* CtdlLogPrintf(CTDL_DEBUG, "fromimap %s\r\n", src); */
312
313         do {
314                 c = *p++;
315                 switch (state)
316                 {
317                         case 0:
318                                 /* US-ASCII characters. */
319                                 
320                                 if (c == '&')
321                                         state = 1;
322                                 else
323                                         string_append_c(&dest, cfrommap(c));
324                                 break;
325
326                         case 1:
327                                 if (c == '-')
328                                 {
329                                         string_append_c(&dest, '&');
330                                         state = 0;
331                                 }
332                                 else if (utf7_rank[c] != 0xff)
333                                 {
334                                         v = utf7_rank[c];
335                                         i = 6;
336                                         state = 2;
337                                 }
338                                 else
339                                 {
340                                         /* invalid char */
341                                         string_append_sn(&dest, "&-", 2);
342                                         state = 0;
343                                 }
344                                 break;
345                                 
346                         case 2:
347                                 if (c == '-')
348                                         state = 0;
349                                 else if (utf7_rank[c] != 0xFF)
350                                 {
351                                         v = (v<<6) | utf7_rank[c];
352                                         i += 6;
353                                         if (i >= 16)
354                                         {
355                                                 int x = (v >> (i-16)) & 0xFFFF;
356                                                 string_append_c(&dest, cfrommap(x));
357                                                 i -= 16;
358                                         }
359                                 }
360                                 else
361                                 {
362                                         string_append_c(&dest, cfrommap(c));
363                                         state = 0;
364                                 }
365                                 break;
366                         }
367         } while (c != '\0');
368
369         /* CtdlLogPrintf(CTDL_DEBUG, "      -> %s\r\n", destp); */
370         return string_end(&dest);
371 }
372
373 /* Undoes the special character conversion. */
374
375 static int cfrommap(int c)
376 {
377         switch (c)
378         {
379                 case '|':       return '/';
380                 case '/':       return '\\';
381         }
382         return c;               
383 }
384
385 /* Output a string to the IMAP client, either as a literal or quoted.
386  * (We do a literal if it has any double-quotes or backslashes.) */
387
388 void imap_strout(char *buf)
389 {
390         int i;
391         int is_literal = 0;
392         long len;
393
394         if (buf == NULL) {      /* yeah, we handle this */
395                 cprintf("NIL");
396                 return;
397         }
398
399         len = strlen(buf);
400         for (i = 0; i < len; ++i) {
401                 if ((buf[i] == '\"') || (buf[i] == '\\'))
402                         is_literal = 1;
403         }
404
405         if (is_literal) {
406                 cprintf("{%ld}\r\n%s", len, buf);
407         } else {
408                 cprintf("\"%s\"", buf);
409         }
410 }
411
412 /* Break a command down into tokens, unquoting any escaped characters. */
413
414 int imap_parameterize(char** args, char* in)
415 {
416         char* out = in;
417         int num = 0;
418
419         for (;;)
420         {
421                 /* Skip whitespace. */
422
423                 while (isspace(*in))
424                         in++;
425                 if (*in == 0)
426                         break;
427
428                 /* Found the start of a token. */
429                 
430                 args[num++] = out;
431
432                 /* Read in the token. */
433
434                 for (;;)
435                 {
436                         int c = *in++;
437                         if (isspace(c))
438                                 break;
439                         
440                         if (c == '\"')
441                         {
442                                 /* Found a quoted section. */
443
444                                 for (;;)
445                                 {
446                                         c = *in++;
447                                         if (c == '\"')
448                                                 break;
449                                         else if (c == '\\')
450                                                 c = *in++;
451
452                                         *out++ = c;
453                                         if (c == 0)
454                                                 return num;
455                                 }
456                         }
457                         else if (c == '\\')
458                         {
459                                 c = *in++;
460                                 *out++ = c;
461                         }
462                         else
463                                 *out++ = c;
464
465                         if (c == 0)
466                                 return num;
467                 }
468                 *out++ = '\0';
469         }
470
471         return num;
472 }
473
474 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
475
476 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
477 {
478         char* bufend = buf+bufsize;
479         struct floor *fl;
480         char* p = buf;
481
482         /* For mailboxes, just do it straight.
483          * Do the Cyrus-compatible thing: all private folders are
484          * subfolders of INBOX. */
485
486         if (qrbuf->QRflags & QR_MAILBOX)
487         {
488                 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
489                         p = toimap(p, bufend, "INBOX");
490                 else
491                 {
492                         p = toimap(p, bufend, "INBOX");
493                         if (p < bufend)
494                                 *p++ = '/';
495                         p = toimap(p, bufend, qrbuf->QRname+11);
496                 }
497         }
498         else
499         {
500                 /* Otherwise, prefix the floor name as a "public folders" moniker. */
501
502                 fl = CtdlGetCachedFloor(qrbuf->QRfloor);
503                 p = toimap(p, bufend, fl->f_name);
504                 if (p < bufend)
505                         *p++ = '/';
506                 p = toimap(p, bufend, qrbuf->QRname);
507         }
508 }
509
510 /*
511  * Convert an inputted folder name to our best guess as to what an equivalent
512  * room name should be.
513  *
514  * If an error occurs, it returns -1.  Otherwise...
515  *
516  * The lower eight bits of the return value are the floor number on which the
517  * room most likely resides.   The upper eight bits may contain flags,
518  * including IR_MAILBOX if we're dealing with a personal room.
519  *
520  */
521
522 int imap_roomname(char *rbuf, int bufsize, char *foldername)
523 {
524         int levels;
525         char floorname[256];
526         char roomname[ROOMNAMELEN];
527         int i;
528         struct floor *fl;
529         int ret = (-1);
530
531         if (foldername == NULL)
532                 return(-1);
533
534         /* Unmunge the entire string into the output buffer. */
535
536         fromimap(rbuf, rbuf+bufsize, foldername);
537
538         /* Is this an IMAP inbox? */
539
540         if (strncasecmp(rbuf, "INBOX", 5) == 0)
541         {
542                 if (rbuf[5] == 0)
543                 {
544                         /* It's the system inbox. */
545
546                         safestrncpy(rbuf, MAILROOM, bufsize);
547                         ret = (0 | IR_MAILBOX);
548                         goto exit;
549                 }
550                 else if (rbuf[5] == FDELIM)
551                 {
552                         /* It's another personal mail folder. */
553
554                         safestrncpy(rbuf, rbuf+6, bufsize);
555                         ret = (0 | IR_MAILBOX);
556                         goto exit;
557                 }
558
559                 /* If we get here, the folder just happens to start with INBOX
560                  * --- fall through. */
561         }
562
563         /* Is this a multi-level room name? */
564
565         levels = num_tokens(rbuf, FDELIM);
566         if (levels > 1)
567         {
568                 /* Extract the main room name. */
569                 
570                 extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
571                 strcpy(roomname, &rbuf[strlen(floorname)+1]);
572
573                 /* Try and find it on any floor. */
574                 
575                 for (i = 0; i < MAXFLOORS; ++i)
576                 {
577                         fl = CtdlGetCachedFloor(i);
578                         if (fl->f_flags & F_INUSE)
579                         {
580                                 if (strcasecmp(floorname, fl->f_name) == 0)
581                                 {
582                                         /* Got it! */
583
584                                         safestrncpy(rbuf, roomname, bufsize);
585                                         ret = i;
586                                         goto exit;
587                                 }
588                         }
589                 }
590         }
591
592         /* Meh. It's either not a multi-level room name, or else we
593          * couldn't find it.
594          */
595         ret = (0 | IR_MAILBOX);
596
597 exit:
598         CtdlLogPrintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
599         return(ret);
600 }
601
602 /*
603  * Output a struct internet_address_list in the form an IMAP client wants
604  */
605 void imap_ial_out(struct internet_address_list *ialist)
606 {
607         struct internet_address_list *iptr;
608
609         if (ialist == NULL) {
610                 cprintf("NIL");
611                 return;
612         }
613         cprintf("(");
614
615         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
616                 cprintf("(");
617                 imap_strout(iptr->ial_name);
618                 cprintf(" NIL ");
619                 imap_strout(iptr->ial_user);
620                 cprintf(" ");
621                 imap_strout(iptr->ial_node);
622                 cprintf(")");
623         }
624
625         cprintf(")");
626 }
627
628
629
630 /*
631  * Determine whether the supplied string is a valid message set.
632  * If the string contains only numbers, colons, commas, and asterisks,
633  * return 1 for a valid message set.  If any other character is found, 
634  * return 0.
635  */
636 int imap_is_message_set(char *buf)
637 {
638         int i;
639
640         if (buf == NULL)
641                 return (0);     /* stupidity checks */
642         if (IsEmptyStr(buf))
643                 return (0);
644
645         if (!strcasecmp(buf, "ALL"))
646                 return (1);     /* macro?  why?  */
647
648         for (i = 0; buf[i]; ++i) {      /* now start the scan */
649                 if (
650                            (!isdigit(buf[i]))
651                            && (buf[i] != ':')
652                            && (buf[i] != ',')
653                            && (buf[i] != '*')
654                     )
655                         return (0);
656         }
657
658         return (1);             /* looks like we're good */
659 }
660
661
662 /*
663  * imap_match.c, based on wildmat.c from INN
664  * hacked for Citadel/IMAP by Daniel Malament
665  */
666
667 /* note: not all return statements use these; don't change them */
668 #define WILDMAT_TRUE    1
669 #define WILDMAT_FALSE   0
670 #define WILDMAT_ABORT   -1
671 #define WILDMAT_DELIM   '/'
672
673 /*
674  * Match text and p, return TRUE, FALSE, or ABORT.
675  */
676 static int do_imap_match(const char *supplied_text, const char *supplied_p)
677 {
678         int matched, i;
679         char lcase_text[SIZ], lcase_p[SIZ];
680         char *text = lcase_text;
681         char *p = lcase_p;
682
683         /* Copy both strings and lowercase them, in order to
684          * make this entire operation case-insensitive.
685          */
686         for (i=0; i<=strlen(supplied_text); ++i)
687                 lcase_text[i] = tolower(supplied_text[i]);
688         for (i=0; i<=strlen(supplied_p); ++i)
689                 p[i] = tolower(supplied_p[i]);
690
691         /* Start matching */
692         for (; *p; text++, p++) {
693                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
694                         return WILDMAT_ABORT;
695                 }
696                 switch (*p) {
697                 default:
698                         if (*text != *p) {
699                                 return WILDMAT_FALSE;
700                         }
701                         continue;
702                 case '*':
703 star:
704                         while (++p, ((*p == '*') || (*p == '%'))) {
705                                 /* Consecutive stars or %'s act
706                                  * just like one star.
707                                  */
708                                 continue;
709                         }
710                         if (*p == '\0') {
711                                 /* Trailing star matches everything. */
712                                 return WILDMAT_TRUE;
713                         }
714                         while (*text) {
715                                 if ((matched = do_imap_match(text++, p))
716                                    != WILDMAT_FALSE) {
717                                         return matched;
718                                 }
719                         }
720                         return WILDMAT_ABORT;
721                 case '%':
722                         while (++p, ((*p == '*') || (*p == '%'))) {
723                                 /* Consecutive %'s act just like one, but even
724                                  * a single star makes the sequence act like
725                                  * one star, instead.
726                                  */
727                                 if (*p == '*') {
728                                         goto star;
729                                 }
730                                 continue;
731                         }
732                         if (*p == '\0') {
733                                 /*
734                                  * Trailing % matches everything
735                                  * without a delimiter.
736                                  */
737                                 while (*text) {
738                                         if (*text == WILDMAT_DELIM) {
739                                                 return WILDMAT_FALSE;
740                                         }
741                                         text++;
742                                 }
743                                 return WILDMAT_TRUE;
744                         }
745                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
746                                 if ((matched = do_imap_match(text++, p))
747                                    != WILDMAT_FALSE) {
748                                         return matched;
749                                 }
750                         }
751                         return WILDMAT_ABORT;
752                 }
753         }
754
755         return (*text == '\0');
756 }
757
758
759
760 /*
761  * Support function for mailbox pattern name matching in LIST and LSUB
762  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
763  */
764 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
765 {
766         /* handle just-star case quickly */
767         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
768                 return WILDMAT_TRUE;
769         }
770         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
771 }
772
773
774
775 /*
776  * Compare an IMAP date string (date only, no time) to the date found in
777  * a Unix timestamp.
778  */
779 int imap_datecmp(char *datestr, time_t msgtime) {
780         char daystr[256];
781         char monthstr[256];
782         char yearstr[256];
783         int i;
784         int day, month, year;
785         int msgday, msgmonth, msgyear;
786         struct tm msgtm;
787
788         char *imap_datecmp_ascmonths[12] = {
789                 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
790         };
791
792         if (datestr == NULL) return(0);
793
794         /* Expecting a date in the form dd-Mmm-yyyy */
795         extract_token(daystr, datestr, 0, '-', sizeof daystr);
796         extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
797         extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
798
799         day = atoi(daystr);
800         year = atoi(yearstr);
801         month = 0;
802         for (i=0; i<12; ++i) {
803                 if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
804                         month = i;
805                 }
806         }
807
808         /* Extract day/month/year from message timestamp */
809         localtime_r(&msgtime, &msgtm);
810         msgday = msgtm.tm_mday;
811         msgmonth = msgtm.tm_mon;
812         msgyear = msgtm.tm_year + 1900;
813
814         /* Now start comparing */
815
816         if (year < msgyear) return(+1);
817         if (year > msgyear) return(-1);
818
819         if (month < msgmonth) return(+1);
820         if (month > msgmonth) return(-1);
821
822         if (day < msgday) return(+1);
823         if (day > msgday) return(-1);
824
825         return(0);
826 }
827