58aabd897da843ac79c3b2e8fa8c070419c79e8e
[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 /*
22  * Output a string to the IMAP client, either as a literal or quoted.
23  * (We do a literal if it has any double-quotes or backslashes.)
24  */
25 void imap_strout(char *buf)
26 {
27         int i;
28         int is_literal = 0;
29
30         if (buf == NULL) {      /* yeah, we handle this */
31                 cprintf("NIL");
32                 return;
33         }
34         for (i = 0; i < strlen(buf); ++i) {
35                 if ((buf[i] == '\"') || (buf[i] == '\\'))
36                         is_literal = 1;
37         }
38
39         if (is_literal) {
40                 cprintf("{%d}\r\n%s", strlen(buf), buf);
41         } else {
42                 cprintf("\"%s\"", buf);
43         }
44 }
45
46
47
48
49
50 /*
51  * Break a command down into tokens, taking into consideration the
52  * possibility of escaping spaces using quoted tokens
53  */
54 int imap_parameterize(char **args, char *buf)
55 {
56         int num = 0;
57         int start = 0;
58         int i;
59         int in_quote = 0;
60         int original_len;
61
62         strcat(buf, " ");
63
64         original_len = strlen(buf);
65
66         for (i = 0; i < original_len; ++i) {
67
68                 if ((isspace(buf[i])) && (!in_quote)) {
69                         buf[i] = 0;
70                         args[num] = &buf[start];
71                         start = i + 1;
72                         if (args[num][0] == '\"') {
73                                 ++args[num];
74                                 args[num][strlen(args[num]) - 1] = 0;
75                         }
76                         ++num;
77                 } else if ((buf[i] == '\"') && (!in_quote)) {
78                         in_quote = 1;
79                 } else if ((buf[i] == '\"') && (in_quote)) {
80                         in_quote = 0;
81                 }
82         }
83
84         return (num);
85 }
86
87 /*
88  * Convert a struct quickroom to an IMAP-compatible mailbox name.
89  */
90 void imap_mailboxname(char *buf, int bufsize, struct quickroom *qrbuf)
91 {
92         struct floor *fl;
93
94         /*
95          * For mailboxes, just do it straight...
96          */
97         if (qrbuf->QRflags & QR_MAILBOX) {
98                 safestrncpy(buf, qrbuf->QRname, bufsize);
99                 strcpy(buf, &buf[11]);
100                 if (!strcasecmp(buf, MAILROOM))
101                         strcpy(buf, "INBOX");
102         }
103         /*
104          * Otherwise, prefix the floor name as a "public folders" moniker
105          */
106         else {
107                 fl = cgetfloor(qrbuf->QRfloor);
108                 snprintf(buf, bufsize, "%s|%s",
109                          fl->f_name,
110                          qrbuf->QRname);
111         }
112 }
113
114
115 /*
116  * Convert an inputted folder name to our best guess as to what an equivalent
117  * room name should be.
118  *
119  * If an error occurs, it returns -1.  Otherwise...
120  *
121  * The lower eight bits of the return value are the floor number on which the
122  * room most likely resides.   The upper eight bits may contain flags,
123  * including IR_MAILBOX if we're dealing with a personal room.
124  *
125  */
126 int imap_roomname(char *rbuf, int bufsize, char *foldername)
127 {
128         int levels;
129         char buf[SIZ];
130         int i;
131         struct floor *fl;
132
133         if (foldername == NULL)
134                 return (-1);
135         levels = num_parms(foldername);
136
137         /* When we can support hierarchial mailboxes, take this out. */
138         if (levels > 2)
139                 return (-1);
140
141         /*
142          * Convert the crispy idiot's reserved names to our reserved names.
143          */
144         if (!strcasecmp(foldername, "INBOX")) {
145                 safestrncpy(rbuf, MAILROOM, bufsize);
146                 return (0 | IR_MAILBOX);
147         }
148         if (levels > 1) {
149                 extract(buf, foldername, 0);
150                 for (i = 0; i < MAXFLOORS; ++i) {
151                         fl = cgetfloor(i);
152                         if (fl->f_flags & F_INUSE) {
153                                 if (!strcasecmp(buf, fl->f_name)) {
154                                         extract(rbuf, foldername, 1);
155                                         return (i);
156                                 }
157                         }
158                 }
159
160                 /* since we don't allow multi-level yet, fail.
161                    extract(rbuf, buf, 1);
162                    return(0);
163                  */
164                 return (-1);
165         }
166         safestrncpy(rbuf, foldername, bufsize);
167         return (0 | IR_MAILBOX);
168 }
169
170
171
172
173
174 /*
175  * Output a struct internet_address_list in the form an IMAP client wants
176  */
177 void imap_ial_out(struct internet_address_list *ialist)
178 {
179         struct internet_address_list *iptr;
180
181         if (ialist == NULL) {
182                 cprintf("NIL");
183                 return;
184         }
185         cprintf("(");
186
187         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
188                 cprintf("(");
189                 imap_strout(iptr->ial_name);
190                 cprintf(" NIL ");
191                 imap_strout(iptr->ial_user);
192                 cprintf(" ");
193                 imap_strout(iptr->ial_node);
194                 cprintf(")");
195         }
196
197         cprintf(")");
198 }
199
200
201
202 /*
203  * Determine whether the supplied string is a valid message set.
204  * If the string contains only numbers, colons, commas, and asterisks,
205  * return 1 for a valid message set.  If any other character is found, 
206  * return 0.
207  */
208 int imap_is_message_set(char *buf)
209 {
210         int i;
211
212         if (buf == NULL)
213                 return (0);     /* stupidity checks */
214         if (strlen(buf) == 0)
215                 return (0);
216
217         if (!strcasecmp(buf, "ALL"))
218                 return (1);     /* macro?  why?  */
219
220         for (i = 0; i < strlen(buf); ++i) {     /* now start the scan */
221                 if (
222                            (!isdigit(buf[i]))
223                            && (buf[i] != ':')
224                            && (buf[i] != ',')
225                            && (buf[i] != '*')
226                     )
227                         return (0);
228         }
229
230         return (1);             /* looks like we're good */
231 }
232
233
234 /*
235  * imap_match.c, based on wildmat.c from INN
236  * hacked for Citadel/IMAP by Daniel Malament
237  */
238
239 /* note: not all return statements use these; don't change them */
240 #define WILDMAT_TRUE    1
241 #define WILDMAT_FALSE   0
242 #define WILDMAT_ABORT   -1
243 #define WILDMAT_DELIM   '|'
244
245 /*
246  * Match text and p, return TRUE, FALSE, or ABORT.
247  */
248 static int do_imap_match(const char *supplied_text, const char *supplied_p)
249 {
250         int matched, i;
251         char lcase_text[SIZ], lcase_p[SIZ];
252         char *text = lcase_text;
253         char *p = lcase_p;
254
255         /* Copy both strings and lowercase them, in order to
256          * make this entire operation case-insensitive.
257          */
258         for (i=0; i<=strlen(supplied_text); ++i)
259                 lcase_text[i] = tolower(supplied_text[i]);
260         for (i=0; i<=strlen(supplied_p); ++i)
261                 p[i] = tolower(supplied_p[i]);
262
263         /* Start matching */
264         for (; *p; text++, p++) {
265                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
266                         return WILDMAT_ABORT;
267                 }
268                 switch (*p) {
269                 default:
270                         if (*text != *p) {
271                                 return WILDMAT_FALSE;
272                         }
273                         continue;
274                 case '*':
275 star:
276                         while (++p, ((*p == '*') || (*p == '%'))) {
277                                 /* Consecutive stars or %'s act
278                                  * just like one star.
279                                  */
280                                 continue;
281                         }
282                         if (*p == '\0') {
283                                 /* Trailing star matches everything. */
284                                 return WILDMAT_TRUE;
285                         }
286                         while (*text) {
287                                 if ((matched = do_imap_match(text++, p))
288                                    != WILDMAT_FALSE) {
289                                         return matched;
290                                 }
291                         }
292                         return WILDMAT_ABORT;
293                 case '%':
294                         while (++p, ((*p == '*') || (*p == '%'))) {
295                                 /* Consecutive %'s act just like one, but even
296                                  * a single star makes the sequence act like
297                                  * one star, instead.
298                                  */
299                                 if (*p == '*') {
300                                         goto star;
301                                 }
302                                 continue;
303                         }
304                         if (*p == '\0') {
305                                 /*
306                                  * Trailing % matches everything
307                                  * without a delimiter.
308                                  */
309                                 while (*text) {
310                                         if (*text == WILDMAT_DELIM) {
311                                                 return WILDMAT_FALSE;
312                                         }
313                                         text++;
314                                 }
315                                 return WILDMAT_TRUE;
316                         }
317                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
318                                 if ((matched = do_imap_match(text++, p))
319                                    != WILDMAT_FALSE) {
320                                         return matched;
321                                 }
322                         }
323                         return WILDMAT_ABORT;
324                 }
325         }
326
327         return (*text == '\0');
328 }
329
330
331
332 /*
333  * Support function for mailbox pattern name matching in LIST and LSUB
334  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
335  */
336 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
337 {
338         /* handle just-star case quickly */
339         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
340                 return WILDMAT_TRUE;
341         }
342         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
343 }