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