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