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