More removal of $Id$ tags
[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 int old_imap_parameterize(char** args, char *in)
608 {
609         char* out = in;
610         int num = 0;
611
612         for (;;)
613         {
614                 /* Skip whitespace. */
615
616                 while (isspace(*in))
617                         in++;
618                 if (*in == 0)
619                         break;
620
621                 /* Found the start of a token. */
622                 
623                 args[num++] = out;
624
625                 /* Read in the token. */
626
627                 for (;;)
628                 {
629                         int c = *in++;
630                         if (isspace(c))
631                                 break;
632                         
633                         if (c == '\"')
634                         {
635                                 /* Found a quoted section. */
636
637                                 for (;;)
638                                 {
639                                         c = *in++;
640                                         if (c == '\"')
641                                                 break;
642                                         else if (c == '\\')
643                                                 c = *in++;
644
645                                         *out++ = c;
646                                         if (c == 0)
647                                                 return num;
648                                 }
649                         }
650                         else if (c == '\\')
651                         {
652                                 c = *in++;
653                                 *out++ = c;
654                         }
655                         else
656                                 *out++ = c;
657
658                         if (c == 0)
659                                 return num;
660                 }
661                 *out++ = '\0';
662         }
663
664         return num;
665 }
666
667 /* Convert a struct ctdlroom to an IMAP-compatible mailbox name. */
668
669 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
670 {
671         char* bufend = buf+bufsize;
672         struct floor *fl;
673         char* p = buf;
674
675         /* For mailboxes, just do it straight.
676          * Do the Cyrus-compatible thing: all private folders are
677          * subfolders of INBOX. */
678
679         if (qrbuf->QRflags & QR_MAILBOX)
680         {
681                 if (strcasecmp(qrbuf->QRname+11, MAILROOM) == 0)
682                         p = toimap(p, bufend, "INBOX");
683                 else
684                 {
685                         p = toimap(p, bufend, "INBOX");
686                         if (p < bufend)
687                                 *p++ = '/';
688                         p = toimap(p, bufend, qrbuf->QRname+11);
689                 }
690         }
691         else
692         {
693                 /* Otherwise, prefix the floor name as a "public folders" moniker. */
694
695                 fl = CtdlGetCachedFloor(qrbuf->QRfloor);
696                 p = toimap(p, bufend, fl->f_name);
697                 if (p < bufend)
698                         *p++ = '/';
699                 p = toimap(p, bufend, qrbuf->QRname);
700         }
701 }
702
703 /*
704  * Convert an inputted folder name to our best guess as to what an equivalent
705  * room name should be.
706  *
707  * If an error occurs, it returns -1.  Otherwise...
708  *
709  * The lower eight bits of the return value are the floor number on which the
710  * room most likely resides.   The upper eight bits may contain flags,
711  * including IR_MAILBOX if we're dealing with a personal room.
712  *
713  */
714
715 int imap_roomname(char *rbuf, int bufsize, const char *foldername)
716 {
717         int levels;
718         char floorname[ROOMNAMELEN*2];
719         char roomname[ROOMNAMELEN];
720         int i;
721         struct floor *fl;
722         int ret = (-1);
723
724         if (foldername == NULL)
725                 return(-1);
726
727         /* Unmunge the entire string into the output buffer. */
728
729         fromimap(rbuf, rbuf+bufsize, foldername);
730
731         /* Is this an IMAP inbox? */
732
733         if (strncasecmp(rbuf, "INBOX", 5) == 0)
734         {
735                 if (rbuf[5] == 0)
736                 {
737                         /* It's the system inbox. */
738
739                         safestrncpy(rbuf, MAILROOM, bufsize);
740                         ret = (0 | IR_MAILBOX);
741                         goto exit;
742                 }
743                 else if (rbuf[5] == FDELIM)
744                 {
745                         /* It's another personal mail folder. */
746
747                         safestrncpy(rbuf, rbuf+6, bufsize);
748                         ret = (0 | IR_MAILBOX);
749                         goto exit;
750                 }
751
752                 /* If we get here, the folder just happens to start with INBOX
753                  * --- fall through. */
754         }
755
756         /* Is this a multi-level room name? */
757
758         levels = num_tokens(rbuf, FDELIM);
759         if (levels > 1)
760         {
761                 long len;
762                 /* Extract the main room name. */
763                 
764                 len = extract_token(floorname, rbuf, 0, FDELIM, sizeof floorname);
765                 if (len < 0) len = 0;
766                 safestrncpy(roomname, &rbuf[len  + 1], sizeof(roomname));
767
768                 /* Try and find it on any floor. */
769                 
770                 for (i = 0; i < MAXFLOORS; ++i)
771                 {
772                         fl = CtdlGetCachedFloor(i);
773                         if (fl->f_flags & F_INUSE)
774                         {
775                                 if (strcasecmp(floorname, fl->f_name) == 0)
776                                 {
777                                         /* Got it! */
778
779                                         safestrncpy(rbuf, roomname, bufsize);
780                                         ret = i;
781                                         goto exit;
782                                 }
783                         }
784                 }
785         }
786
787         /* Meh. It's either not a multi-level room name, or else we
788          * couldn't find it.
789          */
790         ret = (0 | IR_MAILBOX);
791
792 exit:
793         CtdlLogPrintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
794         return(ret);
795 }
796
797 /*
798  * Output a struct internet_address_list in the form an IMAP client wants
799  */
800 void imap_ial_out(struct internet_address_list *ialist)
801 {
802         struct internet_address_list *iptr;
803
804         if (ialist == NULL) {
805                 cprintf("NIL");
806                 return;
807         }
808         cprintf("(");
809
810         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
811                 cprintf("(");
812                 plain_imap_strout(iptr->ial_name);
813                 cprintf(" NIL ");
814                 plain_imap_strout(iptr->ial_user);
815                 cprintf(" ");
816                 plain_imap_strout(iptr->ial_node);
817                 cprintf(")");
818         }
819
820         cprintf(")");
821 }
822
823
824
825 /*
826  * Determine whether the supplied string is a valid message set.
827  * If the string contains only numbers, colons, commas, and asterisks,
828  * return 1 for a valid message set.  If any other character is found, 
829  * return 0.
830  */
831 int imap_is_message_set(const char *buf)
832 {
833         int i;
834
835         if (buf == NULL)
836                 return (0);     /* stupidity checks */
837         if (IsEmptyStr(buf))
838                 return (0);
839
840         if (!strcasecmp(buf, "ALL"))
841                 return (1);     /* macro?  why?  */
842
843         for (i = 0; buf[i]; ++i) {      /* now start the scan */
844                 if (
845                            (!isdigit(buf[i]))
846                            && (buf[i] != ':')
847                            && (buf[i] != ',')
848                            && (buf[i] != '*')
849                     )
850                         return (0);
851         }
852
853         return (1);             /* looks like we're good */
854 }
855
856
857 /*
858  * imap_match.c, based on wildmat.c from INN
859  * hacked for Citadel/IMAP by Daniel Malament
860  */
861
862 /* note: not all return statements use these; don't change them */
863 #define WILDMAT_TRUE    1
864 #define WILDMAT_FALSE   0
865 #define WILDMAT_ABORT   -1
866 #define WILDMAT_DELIM   '/'
867
868 /*
869  * Match text and p, return TRUE, FALSE, or ABORT.
870  */
871 static int do_imap_match(const char *supplied_text, const char *supplied_p)
872 {
873         int matched, i;
874         char lcase_text[SIZ], lcase_p[SIZ];
875         char *text;
876         char *p;
877         
878         /* Copy both strings and lowercase them, in order to
879          * make this entire operation case-insensitive.
880          */
881         for (i=0; 
882              ((supplied_text[i] != '\0') && 
883               (i < sizeof(lcase_text)));
884              ++i)
885                 lcase_text[i] = tolower(supplied_text[i]);
886         lcase_text[i] = '\0';
887
888         for (i=0; 
889              ((supplied_p[i] != '\0') && 
890               (i < sizeof(lcase_p))); 
891              ++i)
892                 lcase_p[i] = tolower(supplied_p[i]);
893         lcase_p[i] = '\0';
894
895         /* Start matching */
896         for (p = lcase_p, text = lcase_text; 
897              !IsEmptyStr(p) && !IsEmptyStr(text); 
898              text++, p++) {
899                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
900                         return WILDMAT_ABORT;
901                 }
902                 switch (*p) {
903                 default:
904                         if (*text != *p) {
905                                 return WILDMAT_FALSE;
906                         }
907                         continue;
908                 case '*':
909 star:
910                         while (++p, ((*p == '*') || (*p == '%'))) {
911                                 /* Consecutive stars or %'s act
912                                  * just like one star.
913                                  */
914                                 continue;
915                         }
916                         if (*p == '\0') {
917                                 /* Trailing star matches everything. */
918                                 return WILDMAT_TRUE;
919                         }
920                         while (*text) {
921                                 if ((matched = do_imap_match(text++, p))
922                                    != WILDMAT_FALSE) {
923                                         return matched;
924                                 }
925                         }
926                         return WILDMAT_ABORT;
927                 case '%':
928                         while (++p, (!IsEmptyStr(p) && ((*p == '*') || (*p == '%'))))
929                         {
930                                 /* Consecutive %'s act just like one, but even
931                                  * a single star makes the sequence act like
932                                  * one star, instead.
933                                  */
934                                 if (*p == '*') {
935                                         goto star;
936                                 }
937                                 continue;
938                         }
939                         if (*p == '\0') {
940                                 /*
941                                  * Trailing % matches everything
942                                  * without a delimiter.
943                                  */
944                                 while (!IsEmptyStr(text)) {
945                                         if (*text == WILDMAT_DELIM) {
946                                                 return WILDMAT_FALSE;
947                                         }
948                                         text++;
949                                 }
950                                 return WILDMAT_TRUE;
951                         }
952                         while (!IsEmptyStr(text) && (*(text - 1) != WILDMAT_DELIM)) {
953                                 if ((matched = do_imap_match(text++, p))
954                                    != WILDMAT_FALSE) {
955                                         return matched;
956                                 }
957                         }
958                         return WILDMAT_ABORT;
959                 }
960         }
961
962         if ((*text == '\0') && (*p == '\0')) return WILDMAT_TRUE;
963         else return WILDMAT_FALSE;
964 }
965
966
967
968 /*
969  * Support function for mailbox pattern name matching in LIST and LSUB
970  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
971  */
972 int imap_mailbox_matches_pattern(const char *pattern, char *mailboxname)
973 {
974         /* handle just-star case quickly */
975         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
976                 return WILDMAT_TRUE;
977         }
978         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
979 }
980
981
982
983 /*
984  * Compare an IMAP date string (date only, no time) to the date found in
985  * a Unix timestamp.
986  */
987 int imap_datecmp(const char *datestr, time_t msgtime) {
988         char daystr[256];
989         char monthstr[256];
990         char yearstr[256];
991         int i;
992         int day, month, year;
993         int msgday, msgmonth, msgyear;
994         struct tm msgtm;
995
996         char *imap_datecmp_ascmonths[12] = {
997                 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
998         };
999
1000         if (datestr == NULL) return(0);
1001
1002         /* Expecting a date in the form dd-Mmm-yyyy */
1003         extract_token(daystr, datestr, 0, '-', sizeof daystr);
1004         extract_token(monthstr, datestr, 1, '-', sizeof monthstr);
1005         extract_token(yearstr, datestr, 2, '-', sizeof yearstr);
1006
1007         day = atoi(daystr);
1008         year = atoi(yearstr);
1009         month = 0;
1010         for (i=0; i<12; ++i) {
1011                 if (!strcasecmp(monthstr, imap_datecmp_ascmonths[i])) {
1012                         month = i;
1013                 }
1014         }
1015
1016         /* Extract day/month/year from message timestamp */
1017         localtime_r(&msgtime, &msgtm);
1018         msgday = msgtm.tm_mday;
1019         msgmonth = msgtm.tm_mon;
1020         msgyear = msgtm.tm_year + 1900;
1021
1022         /* Now start comparing */
1023
1024         if (year < msgyear) return(+1);
1025         if (year > msgyear) return(-1);
1026
1027         if (month < msgmonth) return(+1);
1028         if (month > msgmonth) return(-1);
1029
1030         if (day < msgday) return(+1);
1031         if (day > msgday) return(-1);
1032
1033         return(0);
1034 }
1035