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