* rework imap tokenizer, we no longer copy the stuff around, we keep a reference...
[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 "serv_imap.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, const char* src)
302 {
303         struct string dest;
304         unsigned const char *p = (unsigned const 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 plain_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 /* Output a string to the IMAP client, either as a literal or quoted.
414  * (We do a literal if it has any double-quotes or backslashes.) */
415
416 void imap_strout(ConstStr *args)
417 {
418         int i;
419         int is_literal = 0;
420         
421         if ((args == NULL) || (args->len == 0))
422         {       /* yeah, we handle this */
423                 cprintf("NIL");
424                 return;
425         }
426
427         for (i = 0; i < args->len; ++i) {
428                 if ((args->Key[i] == '\"') || (args->Key[i] == '\\'))
429                         is_literal = 1;
430         }
431
432         if (is_literal) {
433                 cprintf("{%ld}\r\n%s", args->len, args->Key);
434         } else {
435                 cprintf("\"%s\"", args->Key);
436         }
437 }
438
439 /* Break a command down into tokens, unquoting any escaped characters. */
440
441
442 void TokenCutRight(citimap_command *Cmd, 
443                    ConstStr *CutMe,
444                    int n)
445 {
446         const char *CutAt;
447
448         if (CutMe->len < n) {
449                 CutAt = CutMe->Key;
450                 CutMe->len = 0;
451         }
452         else {
453                 CutAt = CutMe->Key + CutMe->len - n;
454                 CutMe->len -= n;
455         }
456         StrBufPeek(Cmd->CmdBuf, CutAt, -1, '\0');
457 }
458
459 void TokenCutLeft(citimap_command *Cmd, 
460                   ConstStr *CutMe,
461                   int n)
462 {
463         if (CutMe->len < n) {
464                 CutMe->Key += CutMe->len;
465                 CutMe->len = 0;
466         }
467         else {
468                 CutMe->Key += n;
469                 CutMe->len -= n;
470         }
471 }
472
473
474
475 int CmdAdjust(citimap_command *Cmd, 
476               int nArgs,
477               int Realloc)
478 {
479         ConstStr *Params;
480         if (nArgs > Cmd->avail_parms) {
481                 Params = (ConstStr*) malloc(sizeof(ConstStr) * nArgs);
482                 if (Realloc) {
483                         memcpy(Params, 
484                                Cmd->Params, 
485                                sizeof(ConstStr) * Cmd->avail_parms);
486
487                         memset(Cmd->Params + 
488                                sizeof(ConstStr) * Cmd->avail_parms,
489                                0, 
490                                sizeof(ConstStr) * nArgs - 
491                                sizeof(ConstStr) * Cmd->avail_parms 
492                                 );
493                 }
494                 else 
495                         Cmd->num_parms = 0;
496                 Cmd->avail_parms = nArgs;
497                 if (Cmd->Params != NULL)
498                         free (Cmd->Params);
499                 Cmd->Params = Params;
500         }
501         else {
502                 if (!Realloc) {
503                         memset(Cmd->Params, 
504                                0,
505                                sizeof(ConstStr) * Cmd->avail_parms);
506                         Cmd->num_parms = 0;
507                 }
508         }
509         return Cmd->num_parms;
510 }
511
512 int imap_parameterize(citimap_command *Cmd)
513 {
514         int nArgs;
515         const char *In, *End;
516
517         In = ChrPtr(Cmd->CmdBuf);
518         End = In + StrLength(Cmd->CmdBuf);
519
520         /* we start with 10 chars per arg, maybe we need to realloc later. */
521         nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
522         nArgs = CmdAdjust(Cmd, nArgs, 0);
523         while (In < End)
524         {
525                 /* Skip whitespace. */
526                 while (isspace(*In))
527                         In++;
528                 if (*In == '\0')
529                         break;
530
531                 /* Found the start of a token. */
532                 
533                 Cmd->Params[Cmd->num_parms].Key = In;
534
535                 /* Read in the token. */
536
537                 for (;;)
538                 {
539                         if (isspace(*In))
540                                 break;
541                         
542                         if (*In == '\"')
543                         {
544                                 /* Found a quoted section. */
545
546                                 Cmd->Params[Cmd->num_parms].Key++; 
547                                 //In++;
548                                 for (;;)
549                                 {
550                                         In++;
551                                         if (*In == '\"') {
552                                                 StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
553                                                 break;
554                                         }
555                                         else if (*In == '\\')
556                                                 In++;
557
558                                         if (*In == '\0') {
559                                                 Cmd->Params[Cmd->num_parms].len = 
560                                                         In - Cmd->Params[Cmd->num_parms].Key;
561                                                 Cmd->num_parms++;
562                                                 return Cmd->num_parms;
563                                         }
564                                 }
565                                 break;
566                         }
567                         else if (*In == '\\')
568                         {
569                                 In++;
570                         }
571
572                         if (*In == '\0') {
573                                 Cmd->Params[Cmd->num_parms].len = 
574                                         In - Cmd->Params[Cmd->num_parms].Key;
575                                 Cmd->num_parms++;
576                                 return Cmd->num_parms;
577                         }
578                         In++;
579                 }
580                 StrBufPeek(Cmd->CmdBuf, In, -1, '\0');
581                 Cmd->Params[Cmd->num_parms].len = 
582                         In - Cmd->Params[Cmd->num_parms].Key;
583                 Cmd->num_parms ++;
584                 if (Cmd->num_parms >= Cmd->avail_parms) {
585                         nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
586                 }
587                 In++;
588         }
589         return Cmd->num_parms;
590 }
591
592 int old_imap_parameterize(char** args, char *in)
593 {
594         char* out = in;
595         int num = 0;
596
597         for (;;)
598         {
599                 /* Skip whitespace. */
600
601                 while (isspace(*in))
602                         in++;
603                 if (*in == 0)
604                         break;
605
606                 /* Found the start of a token. */
607                 
608                 args[num++] = out;
609
610                 /* Read in the token. */
611
612                 for (;;)
613                 {
614                         int c = *in++;
615                         if (isspace(c))
616                                 break;
617                         
618                         if (c == '\"')
619                         {
620                                 /* Found a quoted section. */
621
622                                 for (;;)
623                                 {
624                                         c = *in++;
625                                         if (c == '\"')
626                                                 break;
627                                         else if (c == '\\')
628                                                 c = *in++;
629
630                                         *out++ = c;
631                                         if (c == 0)
632                                                 return num;
633                                 }
634                         }
635                         else if (c == '\\')
636                         {
637                                 c = *in++;
638                                 *out++ = c;
639                         }
640                         else
641                                 *out++ = c;
642
643                         if (c == 0)
644                                 return num;
645                 }
646                 *out++ = '\0';
647         }
648
649         return num;
650 }
651
652 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
653
654 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
655 {
656         char* bufend = buf+bufsize;
657         struct floor *fl;
658         char* p = buf;
659
660         /* For mailboxes, just do it straight.
661          * Do the Cyrus-compatible thing: all private folders are
662          * subfolders of INBOX. */
663
664         if (qrbuf->QRflags & QR_MAILBOX)
665         {
666                 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
667                         p = toimap(p, bufend, "INBOX");
668                 else
669                 {
670                         p = toimap(p, bufend, "INBOX");
671                         if (p < bufend)
672                                 *p++ = '/';
673                         p = toimap(p, bufend, qrbuf->QRname+11);
674                 }
675         }
676         else
677         {
678                 /* Otherwise, prefix the floor name as a "public folders" moniker. */
679
680                 fl = CtdlGetCachedFloor(qrbuf->QRfloor);
681                 p = toimap(p, bufend, fl->f_name);
682                 if (p < bufend)
683                         *p++ = '/';
684                 p = toimap(p, bufend, qrbuf->QRname);
685         }
686 }
687
688 /*
689  * Convert an inputted folder name to our best guess as to what an equivalent
690  * room name should be.
691  *
692  * If an error occurs, it returns -1.  Otherwise...
693  *
694  * The lower eight bits of the return value are the floor number on which the
695  * room most likely resides.   The upper eight bits may contain flags,
696  * including IR_MAILBOX if we're dealing with a personal room.
697  *
698  */
699
700 int imap_roomname(char *rbuf, int bufsize, const char *foldername)
701 {
702         int levels;
703         char floorname[256];
704         char roomname[ROOMNAMELEN];
705         int i;
706         struct floor *fl;
707         int ret = (-1);
708
709         if (foldername == NULL)
710                 return(-1);
711
712         /* Unmunge the entire string into the output buffer. */
713
714         fromimap(rbuf, rbuf+bufsize, foldername);
715
716         /* Is this an IMAP inbox? */
717
718         if (strncasecmp(rbuf, "INBOX", 5) == 0)
719         {
720                 if (rbuf[5] == 0)
721                 {
722                         /* It's the system inbox. */
723
724                         safestrncpy(rbuf, MAILROOM, bufsize);
725                         ret = (0 | IR_MAILBOX);
726                         goto exit;
727                 }
728                 else if (rbuf[5] == FDELIM)
729                 {
730                         /* It's another personal mail folder. */
731
732                         safestrncpy(rbuf, rbuf+6, bufsize);
733                         ret = (0 | IR_MAILBOX);
734                         goto exit;
735                 }
736
737                 /* If we get here, the folder just happens to start with INBOX
738                  * --- fall through. */
739         }
740
741         /* Is this a multi-level room name? */
742
743         levels = num_tokens(rbuf, FDELIM);
744         if (levels > 1)
745         {
746                 /* Extract the main room name. */
747                 
748                 extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
749                 strcpy(roomname, &rbuf[strlen(floorname)+1]);
750
751                 /* Try and find it on any floor. */
752                 
753                 for (i = 0; i < MAXFLOORS; ++i)
754                 {
755                         fl = CtdlGetCachedFloor(i);
756                         if (fl->f_flags & F_INUSE)
757                         {
758                                 if (strcasecmp(floorname, fl->f_name) == 0)
759                                 {
760                                         /* Got it! */
761
762                                         safestrncpy(rbuf, roomname, bufsize);
763                                         ret = i;
764                                         goto exit;
765                                 }
766                         }
767                 }
768         }
769
770         /* Meh. It's either not a multi-level room name, or else we
771          * couldn't find it.
772          */
773         ret = (0 | IR_MAILBOX);
774
775 exit:
776         CtdlLogPrintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
777         return(ret);
778 }
779
780 /*
781  * Output a struct internet_address_list in the form an IMAP client wants
782  */
783 void imap_ial_out(struct internet_address_list *ialist)
784 {
785         struct internet_address_list *iptr;
786
787         if (ialist == NULL) {
788                 cprintf("NIL");
789                 return;
790         }
791         cprintf("(");
792
793         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
794                 cprintf("(");
795                 plain_imap_strout(iptr->ial_name);
796                 cprintf(" NIL ");
797                 plain_imap_strout(iptr->ial_user);
798                 cprintf(" ");
799                 plain_imap_strout(iptr->ial_node);
800                 cprintf(")");
801         }
802
803         cprintf(")");
804 }
805
806
807
808 /*
809  * Determine whether the supplied string is a valid message set.
810  * If the string contains only numbers, colons, commas, and asterisks,
811  * return 1 for a valid message set.  If any other character is found, 
812  * return 0.
813  */
814 int imap_is_message_set(const char *buf)
815 {
816         int i;
817
818         if (buf == NULL)
819                 return (0);     /* stupidity checks */
820         if (IsEmptyStr(buf))
821                 return (0);
822
823         if (!strcasecmp(buf, "ALL"))
824                 return (1);     /* macro?  why?  */
825
826         for (i = 0; buf[i]; ++i) {      /* now start the scan */
827                 if (
828                            (!isdigit(buf[i]))
829                            && (buf[i] != ':')
830                            && (buf[i] != ',')
831                            && (buf[i] != '*')
832                     )
833                         return (0);
834         }
835
836         return (1);             /* looks like we're good */
837 }
838
839
840 /*
841  * imap_match.c, based on wildmat.c from INN
842  * hacked for Citadel/IMAP by Daniel Malament
843  */
844
845 /* note: not all return statements use these; don't change them */
846 #define WILDMAT_TRUE    1
847 #define WILDMAT_FALSE   0
848 #define WILDMAT_ABORT   -1
849 #define WILDMAT_DELIM   '/'
850
851 /*
852  * Match text and p, return TRUE, FALSE, or ABORT.
853  */
854 static int do_imap_match(const char *supplied_text, const char *supplied_p)
855 {
856         int matched, i;
857         char lcase_text[SIZ], lcase_p[SIZ];
858         char *text = lcase_text;
859         char *p = lcase_p;
860         long len;
861
862         /* Copy both strings and lowercase them, in order to
863          * make this entire operation case-insensitive.
864          */
865         len = strlen(supplied_text);
866         for (i=0; i<=len; ++i)
867                 lcase_text[i] = tolower(supplied_text[i]);
868         len = strlen(supplied_p);
869         for (i=0; i<=len; ++i)
870                 p[i] = tolower(supplied_p[i]);
871
872         /* Start matching */
873         for (; *p; text++, p++) {
874                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
875                         return WILDMAT_ABORT;
876                 }
877                 switch (*p) {
878                 default:
879                         if (*text != *p) {
880                                 return WILDMAT_FALSE;
881                         }
882                         continue;
883                 case '*':
884 star:
885                         while (++p, ((*p == '*') || (*p == '%'))) {
886                                 /* Consecutive stars or %'s act
887                                  * just like one star.
888                                  */
889                                 continue;
890                         }
891                         if (*p == '\0') {
892                                 /* Trailing star matches everything. */
893                                 return WILDMAT_TRUE;
894                         }
895                         while (*text) {
896                                 if ((matched = do_imap_match(text++, p))
897                                    != WILDMAT_FALSE) {
898                                         return matched;
899                                 }
900                         }
901                         return WILDMAT_ABORT;
902                 case '%':
903                         while (++p, ((*p == '*') || (*p == '%'))) {
904                                 /* Consecutive %'s act just like one, but even
905                                  * a single star makes the sequence act like
906                                  * one star, instead.
907                                  */
908                                 if (*p == '*') {
909                                         goto star;
910                                 }
911                                 continue;
912                         }
913                         if (*p == '\0') {
914                                 /*
915                                  * Trailing % matches everything
916                                  * without a delimiter.
917                                  */
918                                 while (*text) {
919                                         if (*text == WILDMAT_DELIM) {
920                                                 return WILDMAT_FALSE;
921                                         }
922                                         text++;
923                                 }
924                                 return WILDMAT_TRUE;
925                         }
926                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
927                                 if ((matched = do_imap_match(text++, p))
928                                    != WILDMAT_FALSE) {
929                                         return matched;
930                                 }
931                         }
932                         return WILDMAT_ABORT;
933                 }
934         }
935
936         return (*text == '\0');
937 }
938
939
940
941 /*
942  * Support function for mailbox pattern name matching in LIST and LSUB
943  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
944  */
945 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
946 {
947         /* handle just-star case quickly */
948         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
949                 return WILDMAT_TRUE;
950         }
951         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
952 }
953
954
955
956 /*
957  * Compare an IMAP date string (date only, no time) to the date found in
958  * a Unix timestamp.
959  */
960 int imap_datecmp(const char *datestr, time_t msgtime) {
961         char daystr[256];
962         char monthstr[256];
963         char yearstr[256];
964         int i;
965         int day, month, year;
966         int msgday, msgmonth, msgyear;
967         struct tm msgtm;
968
969         char *imap_datecmp_ascmonths[12] = {
970                 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
971         };
972
973         if (datestr == NULL) return(0);
974
975         /* Expecting a date in the form dd-Mmm-yyyy */
976         extract_token(daystr, datestr, 0, '-', sizeof daystr);
977         extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
978         extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
979
980         day = atoi(daystr);
981         year = atoi(yearstr);
982         month = 0;
983         for (i=0; i<12; ++i) {
984                 if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
985                         month = i;
986                 }
987         }
988
989         /* Extract day/month/year from message timestamp */
990         localtime_r(&msgtime, &msgtm);
991         msgday = msgtm.tm_mday;
992         msgmonth = msgtm.tm_mon;
993         msgyear = msgtm.tm_year + 1900;
994
995         /* Now start comparing */
996
997         if (year < msgyear) return(+1);
998         if (year > msgyear) return(-1);
999
1000         if (month < msgmonth) return(+1);
1001         if (month > msgmonth) return(-1);
1002
1003         if (day < msgday) return(+1);
1004         if (day > msgday) return(-1);
1005
1006         return(0);
1007 }
1008