]> code.citadel.org Git - citadel.git/blob - citadel/imap_tools.c
f6c66689aead2784718422cd96d4f8173f69e364
[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 quickroom to an IMAP-compatible mailbox name.
93  */
94 void imap_mailboxname(char *buf, int bufsize, struct quickroom *qrbuf)
95 {
96         struct floor *fl;
97         int i;
98
99         /*
100          * For mailboxes, just do it straight...
101          */
102         if (qrbuf->QRflags & QR_MAILBOX) {
103                 safestrncpy(buf, qrbuf->QRname, bufsize);
104                 strcpy(buf, &buf[11]);
105                 if (!strcasecmp(buf, MAILROOM))
106                         strcpy(buf, "INBOX");
107         }
108         /*
109          * Otherwise, prefix the floor name as a "public folders" moniker
110          */
111         else {
112                 fl = cgetfloor(qrbuf->QRfloor);
113                 snprintf(buf, bufsize, "%s|%s",
114                          fl->f_name,
115                          qrbuf->QRname);
116         }
117
118         /*
119          * Replace "/" characters with "|" for pseudo-folder-delimiting
120          */
121         for (i=0; i<strlen(buf); ++i) {
122                 if (buf[i] == '/') buf[i] = '|';
123         }
124 }
125
126
127 /*
128  * Convert an inputted folder name to our best guess as to what an equivalent
129  * room name should be.
130  *
131  * If an error occurs, it returns -1.  Otherwise...
132  *
133  * The lower eight bits of the return value are the floor number on which the
134  * room most likely resides.   The upper eight bits may contain flags,
135  * including IR_MAILBOX if we're dealing with a personal room.
136  *
137  */
138 int imap_roomname(char *rbuf, int bufsize, char *foldername)
139 {
140         int levels;
141         char floorname[SIZ];
142         char roomname[SIZ];
143         char buf[SIZ];
144         int i;
145         struct floor *fl;
146         int ret = (-1);
147
148         if (foldername == NULL) return(-1);
149         levels = num_parms(foldername);
150
151         /*
152          * Convert the crispy idiot's reserved names to our reserved names.
153          */
154         if (!strcasecmp(foldername, "INBOX")) {
155                 safestrncpy(rbuf, MAILROOM, bufsize);
156                 ret = (0 | IR_MAILBOX);
157         }
158         else if (levels > 1) {
159                 extract(floorname, foldername, 0);
160                 strcpy(roomname, &foldername[strlen(floorname)+1]);
161                 for (i = 0; i < MAXFLOORS; ++i) {
162                         fl = cgetfloor(i);
163                         if (fl->f_flags & F_INUSE) {
164                                 if (!strcasecmp(floorname, fl->f_name)) {
165                                         strcpy(rbuf, roomname);
166                                         ret = i;
167                                 }
168                         }
169                 }
170
171                 if (ret < 0) {
172                         /* No subfolderificationalisticism on this one... */
173                         safestrncpy(rbuf, foldername, bufsize);
174                         ret = (0 | IR_MAILBOX);
175                 }
176
177         }
178         else {
179                 safestrncpy(rbuf, foldername, bufsize);
180                 ret = (0 | IR_MAILBOX);
181         }
182
183         /* Undelimiterizationalize the room name (change '|' to '/') */
184         for (i=0; i<strlen(rbuf); ++i) {
185                 if (rbuf[i] == '|') rbuf[i] = '/';
186         }
187
188         if (ret & IR_MAILBOX) {
189                 if (atol(rbuf) == 0L) {
190                         strcpy(buf, rbuf);
191                         sprintf(rbuf, "%010ld.%s", CC->usersupp.usernum, buf);
192                 }
193         }
194
195         lprintf(9, "(That translates to \"%s\")\n", rbuf);
196         return(ret);
197 }
198
199
200
201
202
203 /*
204  * Output a struct internet_address_list in the form an IMAP client wants
205  */
206 void imap_ial_out(struct internet_address_list *ialist)
207 {
208         struct internet_address_list *iptr;
209
210         if (ialist == NULL) {
211                 cprintf("NIL");
212                 return;
213         }
214         cprintf("(");
215
216         for (iptr = ialist; iptr != NULL; iptr = iptr->next) {
217                 cprintf("(");
218                 imap_strout(iptr->ial_name);
219                 cprintf(" NIL ");
220                 imap_strout(iptr->ial_user);
221                 cprintf(" ");
222                 imap_strout(iptr->ial_node);
223                 cprintf(")");
224         }
225
226         cprintf(")");
227 }
228
229
230
231 /*
232  * Determine whether the supplied string is a valid message set.
233  * If the string contains only numbers, colons, commas, and asterisks,
234  * return 1 for a valid message set.  If any other character is found, 
235  * return 0.
236  */
237 int imap_is_message_set(char *buf)
238 {
239         int i;
240
241         if (buf == NULL)
242                 return (0);     /* stupidity checks */
243         if (strlen(buf) == 0)
244                 return (0);
245
246         if (!strcasecmp(buf, "ALL"))
247                 return (1);     /* macro?  why?  */
248
249         for (i = 0; i < strlen(buf); ++i) {     /* now start the scan */
250                 if (
251                            (!isdigit(buf[i]))
252                            && (buf[i] != ':')
253                            && (buf[i] != ',')
254                            && (buf[i] != '*')
255                     )
256                         return (0);
257         }
258
259         return (1);             /* looks like we're good */
260 }
261
262
263 /*
264  * imap_match.c, based on wildmat.c from INN
265  * hacked for Citadel/IMAP by Daniel Malament
266  */
267
268 /* note: not all return statements use these; don't change them */
269 #define WILDMAT_TRUE    1
270 #define WILDMAT_FALSE   0
271 #define WILDMAT_ABORT   -1
272 #define WILDMAT_DELIM   '|'
273
274 /*
275  * Match text and p, return TRUE, FALSE, or ABORT.
276  */
277 static int do_imap_match(const char *supplied_text, const char *supplied_p)
278 {
279         int matched, i;
280         char lcase_text[SIZ], lcase_p[SIZ];
281         char *text = lcase_text;
282         char *p = lcase_p;
283
284         /* Copy both strings and lowercase them, in order to
285          * make this entire operation case-insensitive.
286          */
287         for (i=0; i<=strlen(supplied_text); ++i)
288                 lcase_text[i] = tolower(supplied_text[i]);
289         for (i=0; i<=strlen(supplied_p); ++i)
290                 p[i] = tolower(supplied_p[i]);
291
292         /* Start matching */
293         for (; *p; text++, p++) {
294                 if ((*text == '\0') && (*p != '*') && (*p != '%')) {
295                         return WILDMAT_ABORT;
296                 }
297                 switch (*p) {
298                 default:
299                         if (*text != *p) {
300                                 return WILDMAT_FALSE;
301                         }
302                         continue;
303                 case '*':
304 star:
305                         while (++p, ((*p == '*') || (*p == '%'))) {
306                                 /* Consecutive stars or %'s act
307                                  * just like one star.
308                                  */
309                                 continue;
310                         }
311                         if (*p == '\0') {
312                                 /* Trailing star matches everything. */
313                                 return WILDMAT_TRUE;
314                         }
315                         while (*text) {
316                                 if ((matched = do_imap_match(text++, p))
317                                    != WILDMAT_FALSE) {
318                                         return matched;
319                                 }
320                         }
321                         return WILDMAT_ABORT;
322                 case '%':
323                         while (++p, ((*p == '*') || (*p == '%'))) {
324                                 /* Consecutive %'s act just like one, but even
325                                  * a single star makes the sequence act like
326                                  * one star, instead.
327                                  */
328                                 if (*p == '*') {
329                                         goto star;
330                                 }
331                                 continue;
332                         }
333                         if (*p == '\0') {
334                                 /*
335                                  * Trailing % matches everything
336                                  * without a delimiter.
337                                  */
338                                 while (*text) {
339                                         if (*text == WILDMAT_DELIM) {
340                                                 return WILDMAT_FALSE;
341                                         }
342                                         text++;
343                                 }
344                                 return WILDMAT_TRUE;
345                         }
346                         while (*text && (*(text - 1) != WILDMAT_DELIM)) {
347                                 if ((matched = do_imap_match(text++, p))
348                                    != WILDMAT_FALSE) {
349                                         return matched;
350                                 }
351                         }
352                         return WILDMAT_ABORT;
353                 }
354         }
355
356         return (*text == '\0');
357 }
358
359
360
361 /*
362  * Support function for mailbox pattern name matching in LIST and LSUB
363  * Returns nonzero if the supplied mailbox name matches the supplied pattern.
364  */
365 int imap_mailbox_matches_pattern(char *pattern, char *mailboxname)
366 {
367         /* handle just-star case quickly */
368         if ((pattern[0] == '*') && (pattern[1] == '\0')) {
369                 return WILDMAT_TRUE;
370         }
371         return (do_imap_match(mailboxname, pattern) == WILDMAT_TRUE);
372 }