597e50a6c4c1658b9ad4cede15fbc11fa50ae8c5
[citadel.git] / citadel / imap_tools.c
1 /*
2  * $Id$
3  *
4  * Utility functions for the IMAP module.
5  *
6  */
7
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <ctype.h>
12 #include <string.h>
13 #include "citadel.h"
14 #include "sysdep_decls.h"
15 #include "tools.h"
16 #include "room_ops.h"
17 #include "internet_addressing.h"
18 #include "imap_tools.h"
19
20
21 #ifndef HAVE_SNPRINTF
22 #include "snprintf.h"
23 #endif
24
25 /*
26  * Output a string to the IMAP client, either as a literal or quoted.
27  * (We do a literal if it has any double-quotes or backslashes.)
28  */
29 void imap_strout(char *buf)
30 {
31         int i;
32         int is_literal = 0;
33
34         if (buf == NULL) {      /* yeah, we handle this */
35                 cprintf("NIL");
36                 return;
37         }
38         for (i = 0; i < strlen(buf); ++i) {
39                 if ((buf[i] == '\"') || (buf[i] == '\\'))
40                         is_literal = 1;
41         }
42
43         if (is_literal) {
44                 cprintf("{%ld}\r\n%s", (long)strlen(buf), buf);
45         } else {
46                 cprintf("\"%s\"", buf);
47         }
48 }
49
50
51
52
53
54 /*
55  * Break a command down into tokens, taking into consideration the
56  * possibility of escaping spaces using quoted tokens
57  */
58 int imap_parameterize(char **args, char *buf)
59 {
60         int num = 0;
61         int start = 0;
62         int i;
63         int in_quote = 0;
64         int original_len;
65
66         strcat(buf, " ");
67
68         original_len = strlen(buf);
69
70         for (i = 0; i < original_len; ++i) {
71
72                 if ((isspace(buf[i])) && (!in_quote)) {
73                         buf[i] = 0;
74                         args[num] = &buf[start];
75                         start = i + 1;
76                         if (args[num][0] == '\"') {
77                                 ++args[num];
78                                 args[num][strlen(args[num]) - 1] = 0;
79                         }
80                         ++num;
81                 } else if ((buf[i] == '\"') && (!in_quote)) {
82                         in_quote = 1;
83                 } else if ((buf[i] == '\"') && (in_quote)) {
84                         in_quote = 0;
85                 }
86         }
87
88         return (num);
89 }
90
91 /*
92  * Convert a struct ctdlroom to an IMAP-compatible mailbox name.
93  */
94 void imap_mailboxname(char *buf, int bufsize, struct ctdlroom *qrbuf)
95 {
96         struct floor *fl;
97         int i;
98         char buf2[SIZ];
99         int sfstart = 0;
100
101         /*
102          * For mailboxes, just do it straight.
103          * Do the Cyrus-compatible thing: all private folders are
104          * subfolders of INBOX.
105          */
106         if (qrbuf->QRflags & QR_MAILBOX) {
107                 safestrncpy(buf, qrbuf->QRname, bufsize);
108                 strcpy(buf, &buf[11]);
109                 if (!strcasecmp(buf, MAILROOM)) {
110                         strcpy(buf, "INBOX");
111                         sfstart = 5;
112                 }
113                 else {
114                         sprintf(buf2, "INBOX/%s", buf);
115                         strcpy(buf, buf2);
116                         sfstart = 6;
117                 }
118         }
119         /*
120          * Otherwise, prefix the floor name as a "public folders" moniker
121          */
122         else {
123                 fl = cgetfloor(qrbuf->QRfloor);
124                 snprintf(buf, bufsize, "%s/%s",
125                          fl->f_name,
126                          qrbuf->QRname);
127                 sfstart = strlen(fl->f_name) + 1;
128         }
129
130         /*
131          * Replace delimiter characters with "/" for pseudo-folder-delimiting
132          * and replace actual slashes with "|" because they're illegal here.
133          */
134         for (i=sfstart; i<strlen(buf); ++i) {
135                 if (buf[i] == FDELIM) buf[i] = '/';
136                 else if (buf[i] == '/') buf[i] = '|';
137         }
138 }
139
140
141 /*
142  * Convert an inputted folder name to our best guess as to what an equivalent
143  * room name should be.
144  *
145  * If an error occurs, it returns -1.  Otherwise...
146  *
147  * The lower eight bits of the return value are the floor number on which the
148  * room most likely resides.   The upper eight bits may contain flags,
149  * including IR_MAILBOX if we're dealing with a personal room.
150  *
151  */
152 int imap_roomname(char *rbuf, int bufsize, char *foldername)
153 {
154         int levels;
155         char floorname[SIZ];
156         char roomname[SIZ];
157         int i;
158         struct floor *fl;
159         int ret = (-1);
160
161         if (foldername == NULL) return(-1);
162         levels = num_tokens(foldername, '/');
163
164         /*
165          * Convert the crispy idiot's reserved names to our reserved names.
166          * Also handle Cyrus-compatible folder names.
167          */
168         if (!strcasecmp(foldername, "INBOX")) {
169                 safestrncpy(rbuf, MAILROOM, bufsize);
170                 ret = (0 | IR_MAILBOX);
171         }
172         else if (!strncasecmp(foldername, "INBOX/", 6)) {
173                 safestrncpy(rbuf, &foldername[6], bufsize);
174                 ret = (0 | IR_MAILBOX);
175         }
176         else if (levels > 1) {
177                 extract_token(floorname, foldername, 0, '/');
178                 strcpy(roomname, &foldername[strlen(floorname)+1]);
179                 for (i = 0; i < MAXFLOORS; ++i) {
180                         fl = cgetfloor(i);
181                         if (fl->f_flags & F_INUSE) {
182                                 if (!strcasecmp(floorname, fl->f_name)) {
183                                         strcpy(rbuf, roomname);
184                                         ret = i;
185                                 }
186                         }
187                 }
188
189                 if (ret < 0) {
190                         /* No subfolderificationalisticism on this one... */
191                         safestrncpy(rbuf, foldername, bufsize);
192                         ret = (0 | IR_MAILBOX);
193                 }
194
195         }
196         else {
197                 safestrncpy(rbuf, foldername, bufsize);
198                 ret = (0 | IR_MAILBOX);
199         }
200
201         /* Undelimiterizationalisticize the room name (change '/' and '|') */
202         for (i=0; i<strlen(rbuf); ++i) {
203                 if (rbuf[i] == '/') rbuf[i] = FDELIM;
204                 else if (rbuf[i] == '|') rbuf[i] = '/';
205         }
206
207         lprintf(CTDL_DEBUG, "(That translates to \"%s\")\n", rbuf);
208         return(ret);
209 }
210
211
212
213
214
215 /*
216  * Output a struct internet_address_list in the form an IMAP client wants
217  */
218 void imap_ial_out(struct internet_address_list *ialist)
219 {
220         struct internet_address_list *iptr;
221
222         if (ialist == NULL) {
223                 cprintf("NIL");
224                 return;
225         }
226         cprintf("(");
227
228         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
229                 cprintf("(");
230                 imap_strout(iptr->ial_name);
231                 cprintf(" NIL ");
232                 imap_strout(iptr->ial_user);
233                 cprintf(" ");
234                 imap_strout(iptr->ial_node);
235                 cprintf(")");
236         }
237
238         cprintf(")");
239 }
240
241
242
243 /*
244  * Determine whether the supplied string is a valid message set.
245  * If the string contains only numbers, colons, commas, and asterisks,
246  * return 1 for a valid message set.  If any other character is found, 
247  * return 0.
248  */
249 int imap_is_message_set(char *buf)
250 {
251         int i;
252
253         if (buf == NULL)
254                 return (0);     /* stupidity checks */
255         if (strlen(buf) == 0)
256                 return (0);
257
258         if (!strcasecmp(buf, "ALL"))
259                 return (1);     /* macro?  why?  */
260
261         for (i = 0; i < strlen(buf); ++i) {     /* now start the scan */
262                 if (
263                            (!isdigit(buf[i]))
264                            && (buf[i] != ':')
265                            && (buf[i] != ',')
266                            && (buf[i] != '*')
267                     )
268                         return (0);
269         }
270
271         return (1);             /* looks like we're good */
272 }
273
274
275 /*
276  * imap_match.c, based on wildmat.c from INN
277  * hacked for Citadel/IMAP by Daniel Malament
278  */
279
280 /* note: not all return statements use these; don't change them */
281 #define WILDMAT_TRUE    1
282 #define WILDMAT_FALSE   0
283 #define WILDMAT_ABORT   -1
284 #define WILDMAT_DELIM   '/'
285
286 /*
287  * Match text and p, return TRUE, FALSE, or ABORT.
288  */
289 static int do_imap_match(const char *supplied_text, const char *supplied_p)
290 {
291         int matched, i;
292         char lcase_text[SIZ], lcase_p[SIZ];
293         char *text = lcase_text;
294         char *p = lcase_p;
295
296         /* Copy both strings and lowercase them, in order to
297          * make this entire operation case-insensitive.
298          */
299         for (i=0; i<=strlen(supplied_text); ++i)
300                 lcase_text[i] = tolower(supplied_text[i]);
301         for (i=0; i<=strlen(supplied_p); ++i)
302                 p[i] = tolower(supplied_p[i]);
303
304         /* Start matching */
305         for (; *p; text++, p++) {
306                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
307                         return WILDMAT_ABORT;
308                 }
309                 switch (*p) {
310                 default:
311                         if (*text != *p) {
312                                 return WILDMAT_FALSE;
313                         }
314                         continue;
315                 case '*':
316 star:
317                         while (++p, ((*p == '*') || (*p == '%'))) {
318                                 /* Consecutive stars or %'s act
319                                  * just like one star.
320                                  */
321                                 continue;
322                         }
323                         if (*p == '\0') {
324                                 /* Trailing star matches everything. */
325                                 return WILDMAT_TRUE;
326                         }
327                         while (*text) {
328                                 if ((matched = do_imap_match(text++, p))
329                                    != WILDMAT_FALSE) {
330                                         return matched;
331                                 }
332                         }
333                         return WILDMAT_ABORT;
334                 case '%':
335                         while (++p, ((*p == '*') || (*p == '%'))) {
336                                 /* Consecutive %'s act just like one, but even
337                                  * a single star makes the sequence act like
338                                  * one star, instead.
339                                  */
340                                 if (*p == '*') {
341                                         goto star;
342                                 }
343                                 continue;
344                         }
345                         if (*p == '\0') {
346                                 /*
347                                  * Trailing % matches everything
348                                  * without a delimiter.
349                                  */
350                                 while (*text) {
351                                         if (*text == WILDMAT_DELIM) {
352                                                 return WILDMAT_FALSE;
353                                         }
354                                         text++;
355                                 }
356                                 return WILDMAT_TRUE;
357                         }
358                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
359                                 if ((matched = do_imap_match(text++, p))
360                                    != WILDMAT_FALSE) {
361                                         return matched;
362                                 }
363                         }
364                         return WILDMAT_ABORT;
365                 }
366         }
367
368         return (*text == '\0');
369 }
370
371
372
373 /*
374  * Support function for mailbox pattern name matching in LIST and LSUB
375  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
376  */
377 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
378 {
379         /* handle just-star case quickly */
380         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
381                 return WILDMAT_TRUE;
382         }
383         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
384 }
385
386
387
388 /*
389  * Compare an IMAP date string (date only, no time) to the date found in
390  * a Unix timestamp.
391  */
392 int imap_datecmp(char *datestr, time_t msgtime) {
393         char daystr[SIZ];
394         char monthstr[SIZ];
395         char yearstr[SIZ];
396         int i;
397         int day, month, year;
398         int msgday, msgmonth, msgyear;
399         struct tm msgtm;
400
401         if (datestr == NULL) return(0);
402
403         /* Expecting a date in the form dd-Mmm-yyyy */
404         extract_token(daystr, datestr, 0, '-');
405         extract_token(monthstr, datestr, 1, '-');
406         extract_token(yearstr, datestr, 2, '-');
407
408         day = atoi(daystr);
409         year = atoi(yearstr);
410         month = 0;
411         for (i=0; i<12; ++i) {
412                 if (!strcasecmp(monthstr, ascmonths[i])) {
413                         month = i;
414                 }
415         }
416
417         /* Extract day/month/year from message timestamp */
418         memcpy(&msgtm, localtime(&msgtime), sizeof(struct tm));
419         msgday = msgtm.tm_mday;
420         msgmonth = msgtm.tm_mon;
421         msgyear = msgtm.tm_year + 1900;
422
423         /* Now start comparing */
424
425         if (year < msgyear) return(+1);
426         if (year > msgyear) return(-1);
427
428         if (month < msgmonth) return(+1);
429         if (month > msgmonth) return(-1);
430
431         if (day < msgday) return(+1);
432         if (day > msgday) return(-1);
433
434         return(0);
435 }