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