]> code.citadel.org Git - citadel.git/blob - citadel/imap_tools.c
* IMAP LIST/LSUB: made it case insensitive. Also minor IMAP code cleanup.
[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                         lprintf(9, "floor %d: %s\n", i, fl->f_name);    /* FIXME take out */
153                         if (fl->f_flags & F_INUSE) {
154                                 if (!strcasecmp(buf, fl->f_name)) {
155                                         extract(rbuf, foldername, 1);
156                                         return (i);
157                                 }
158                         }
159                 }
160
161                 /* since we don't allow multi-level yet, fail.
162                    extract(rbuf, buf, 1);
163                    return(0);
164                  */
165                 return (-1);
166         }
167         safestrncpy(rbuf, foldername, bufsize);
168         return (0 | IR_MAILBOX);
169 }
170
171
172
173
174
175 /*
176  * Output a struct internet_address_list in the form an IMAP client wants
177  */
178 void imap_ial_out(struct internet_address_list *ialist)
179 {
180         struct internet_address_list *iptr;
181
182         if (ialist == NULL) {
183                 cprintf("NIL");
184                 return;
185         }
186         cprintf("(");
187
188         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
189                 cprintf("(");
190                 imap_strout(iptr->ial_name);
191                 cprintf(" NIL ");
192                 imap_strout(iptr->ial_user);
193                 cprintf(" ");
194                 imap_strout(iptr->ial_node);
195                 cprintf(")");
196         }
197
198         cprintf(")");
199 }
200
201
202
203 /*
204  * Determine whether the supplied string is a valid message set.
205  * If the string contains only numbers, colons, commas, and asterisks,
206  * return 1 for a valid message set.  If any other character is found, 
207  * return 0.
208  */
209 int imap_is_message_set(char *buf)
210 {
211         int i;
212
213         if (buf == NULL)
214                 return (0);     /* stupidity checks */
215         if (strlen(buf) == 0)
216                 return (0);
217
218         if (!strcasecmp(buf, "ALL"))
219                 return (1);     /* macro?  why?  */
220
221         for (i = 0; i < strlen(buf); ++i) {     /* now start the scan */
222                 if (
223                            (!isdigit(buf[i]))
224                            && (buf[i] != ':')
225                            && (buf[i] != ',')
226                            && (buf[i] != '*')
227                     )
228                         return (0);
229         }
230
231         return (1);             /* looks like we're good */
232 }
233
234
235 /*
236  * imap_match.c, based on wildmat.c from INN
237  * hacked for Citadel/IMAP by Daniel Malament
238  */
239
240 /* note: not all return statements use these; don't change them */
241 #define WILDMAT_TRUE    1
242 #define WILDMAT_FALSE   0
243 #define WILDMAT_ABORT   -1
244 #define WILDMAT_DELIM   '|'
245
246 /*
247  * Match text and p, return TRUE, FALSE, or ABORT.
248  */
249 static int do_imap_match(const char *supplied_text, const char *supplied_p)
250 {
251         int matched, i;
252         char lcase_text[SIZ], lcase_p[SIZ];
253         char *text = lcase_text;
254         char *p = lcase_p;
255
256         /* Copy both strings and lowercase them, in order to
257          * make this entire operation case-insensitive.
258          */
259         for (i=0; i<=strlen(supplied_text); ++i)
260                 lcase_text[i] = tolower(supplied_text[i]);
261         for (i=0; i<=strlen(supplied_p); ++i)
262                 p[i] = tolower(supplied_p[i]);
263
264         /* Start matching */
265         for (; *p; text++, p++) {
266                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
267                         return WILDMAT_ABORT;
268                 }
269                 switch (*p) {
270                 default:
271                         if (*text != *p) {
272                                 return WILDMAT_FALSE;
273                         }
274                         continue;
275                 case '*':
276 star:
277                         while (++p, ((*p == '*') || (*p == '%'))) {
278                                 /* Consecutive stars or %'s act
279                                  * just like one star.
280                                  */
281                                 continue;
282                         }
283                         if (*p == '\0') {
284                                 /* Trailing star matches everything. */
285                                 return WILDMAT_TRUE;
286                         }
287                         while (*text) {
288                                 if ((matched = do_imap_match(text++, p))
289                                    != WILDMAT_FALSE) {
290                                         return matched;
291                                 }
292                         }
293                         return WILDMAT_ABORT;
294                 case '%':
295                         while (++p, ((*p == '*') || (*p == '%'))) {
296                                 /* Consecutive %'s act just like one, but even
297                                  * a single star makes the sequence act like
298                                  * one star, instead.
299                                  */
300                                 if (*p == '*') {
301                                         goto star;
302                                 }
303                                 continue;
304                         }
305                         if (*p == '\0') {
306                                 /*
307                                  * Trailing % matches everything
308                                  * without a delimiter.
309                                  */
310                                 while (*text) {
311                                         if (*text == WILDMAT_DELIM) {
312                                                 return WILDMAT_FALSE;
313                                         }
314                                         text++;
315                                 }
316                                 return WILDMAT_TRUE;
317                         }
318                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
319                                 if ((matched = do_imap_match(text++, p))
320                                    != WILDMAT_FALSE) {
321                                         return matched;
322                                 }
323                         }
324                         return WILDMAT_ABORT;
325                 }
326         }
327
328         return (*text == '\0');
329 }
330
331
332
333 /*
334  * Support function for mailbox pattern name matching in LIST and LSUB
335  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
336  */
337 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
338 {
339         /* handle just-star case quickly */
340         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
341                 return WILDMAT_TRUE;
342         }
343         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
344 }