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