HUGE PATCH. This moves all of mime_parser.c and all
[citadel.git] / citadel / modules / imap / imap_fetch.c
1 /*
2  * $Id$
3  *
4  * Implements the FETCH command in IMAP.
5  * This is a good example of the protocol's gratuitous complexity.
6  *
7  */
8
9
10 #include "sysdep.h"
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <stdio.h>
14 #include <fcntl.h>
15 #include <signal.h>
16 #include <pwd.h>
17 #include <errno.h>
18 #include <sys/types.h>
19
20 #if TIME_WITH_SYS_TIME
21 # include <sys/time.h>
22 # include <time.h>
23 #else
24 # if HAVE_SYS_TIME_H
25 #  include <sys/time.h>
26 # else
27 #  include <time.h>
28 # endif
29 #endif
30
31 #include <sys/wait.h>
32 #include <ctype.h>
33 #include <string.h>
34 #include <limits.h>
35 #include <libcitadel.h>
36 #include "citadel.h"
37 #include "server.h"
38 #include "sysdep_decls.h"
39 #include "citserver.h"
40 #include "support.h"
41 #include "config.h"
42 #include "room_ops.h"
43 #include "user_ops.h"
44 #include "policy.h"
45 #include "database.h"
46 #include "msgbase.h"
47 #include "internet_addressing.h"
48 #include "serv_imap.h"
49 #include "imap_tools.h"
50 #include "imap_fetch.h"
51 #include "genstamp.h"
52
53
54
55 /*
56  * Individual field functions for imap_do_fetch_msg() ...
57  */
58
59 void imap_fetch_uid(int seq) {
60         cprintf("UID %ld", IMAP->msgids[seq-1]);
61 }
62
63 void imap_fetch_flags(int seq) {
64         int num_flags_printed = 0;
65         cprintf("FLAGS (");
66         if (IMAP->flags[seq] & IMAP_DELETED) {
67                 if (num_flags_printed > 0) cprintf(" ");
68                 cprintf("\\Deleted");
69                 ++num_flags_printed;
70         }
71         if (IMAP->flags[seq] & IMAP_SEEN) {
72                 if (num_flags_printed > 0) cprintf(" ");
73                 cprintf("\\Seen");
74                 ++num_flags_printed;
75         }
76         if (IMAP->flags[seq] & IMAP_ANSWERED) {
77                 if (num_flags_printed > 0) cprintf(" ");
78                 cprintf("\\Answered");
79                 ++num_flags_printed;
80         }
81         if (IMAP->flags[seq] & IMAP_RECENT) {
82                 if (num_flags_printed > 0) cprintf(" ");
83                 cprintf("\\Recent");
84                 ++num_flags_printed;
85         }
86         cprintf(")");
87 }
88
89 void imap_fetch_internaldate(struct CtdlMessage *msg) {
90         char buf[SIZ];
91         time_t msgdate;
92
93         if (!msg) return;
94         if (msg->cm_fields['T'] != NULL) {
95                 msgdate = atol(msg->cm_fields['T']);
96         }
97         else {
98                 msgdate = time(NULL);
99         }
100
101         datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
102         cprintf("INTERNALDATE \"%s\"", buf);
103 }
104
105
106 /*
107  * Fetch RFC822-formatted messages.
108  *
109  * 'whichfmt' should be set to one of:
110  *      "RFC822"        entire message
111  *      "RFC822.HEADER" headers only (with trailing blank line)
112  *      "RFC822.SIZE"   size of translated message
113  *      "RFC822.TEXT"   body only (without leading blank line)
114  */
115 void imap_fetch_rfc822(long msgnum, char *whichfmt) {
116         char buf[SIZ];
117         char *ptr = NULL;
118         size_t headers_size, text_size, total_size;
119         size_t bytes_to_send = 0;
120         struct MetaData smi;
121         int need_to_rewrite_metadata = 0;
122         int need_body = 0;
123
124         /* Determine whether this particular fetch operation requires
125          * us to fetch the message body from disk.  If not, we can save
126          * on some disk operations...
127          */
128         if ( (!strcasecmp(whichfmt, "RFC822"))
129            || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
130                 need_body = 1;
131         }
132
133         /* If this is an RFC822.SIZE fetch, first look in the message's
134          * metadata record to see if we've saved that information.
135          */
136         if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
137                 GetMetaData(&smi, msgnum);
138                 if (smi.meta_rfc822_length > 0L) {
139                         cprintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
140                         return;
141                 }
142                 need_to_rewrite_metadata = 1;
143                 need_body = 1;
144         }
145         
146         /* Cache the most recent RFC822 FETCH because some clients like to
147          * fetch in pieces, and we don't want to have to go back to the
148          * message store for each piece.  We also burn the cache if the
149          * client requests something that involves reading the message
150          * body, but we haven't fetched the body yet.
151          */
152         if ((IMAP->cached_rfc822_data != NULL)
153            && (IMAP->cached_rfc822_msgnum == msgnum)
154            && (IMAP->cached_rfc822_withbody || (!need_body)) ) {
155                 /* Good to go! */
156         }
157         else if (IMAP->cached_rfc822_data != NULL) {
158                 /* Some other message is cached -- free it */
159                 free(IMAP->cached_rfc822_data);
160                 IMAP->cached_rfc822_data = NULL;
161                 IMAP->cached_rfc822_msgnum = (-1);
162                 IMAP->cached_rfc822_len = 0;
163         }
164
165         /* At this point, we now can fetch and convert the message iff it's not
166          * the one we had cached.
167          */
168         if (IMAP->cached_rfc822_data == NULL) {
169                 /*
170                  * Load the message into memory for translation & measurement
171                  */
172                 CC->redirect_buffer = malloc(SIZ);
173                 CC->redirect_len = 0;
174                 CC->redirect_alloc = SIZ;
175                 CtdlOutputMsg(msgnum, MT_RFC822,
176                         (need_body ? HEADERS_ALL : HEADERS_ONLY),
177                         0, 1, NULL);
178                 if (!need_body) cprintf("\r\n");        /* extra trailing newline */
179                 IMAP->cached_rfc822_data = CC->redirect_buffer;
180                 IMAP->cached_rfc822_len = CC->redirect_len;
181                 IMAP->cached_rfc822_msgnum = msgnum;
182                 IMAP->cached_rfc822_withbody = need_body;
183                 CC->redirect_buffer = NULL;
184                 CC->redirect_len = 0;
185                 CC->redirect_alloc = 0;
186                 if ( (need_to_rewrite_metadata) && (IMAP->cached_rfc822_len > 0) ) {
187                         smi.meta_rfc822_length = (long)IMAP->cached_rfc822_len;
188                         PutMetaData(&smi);
189                 }
190         }
191
192         /*
193          * Now figure out where the headers/text break is.  IMAP considers the
194          * intervening blank line to be part of the headers, not the text.
195          */
196         headers_size = 0;
197         text_size = 0;
198         total_size = 0;
199
200         if (need_body) {
201                 ptr = IMAP->cached_rfc822_data;
202                 do {
203                         ptr = memreadline(ptr, buf, sizeof buf);
204                         if (*ptr != 0) {
205                                 striplt(buf);
206                                 if (IsEmptyStr(buf)) {
207                                         headers_size = ptr - IMAP->cached_rfc822_data;
208                                 }
209                         }
210                 } while ( (headers_size == 0) && (*ptr != 0) );
211
212                 total_size = IMAP->cached_rfc822_len;
213                 text_size = total_size - headers_size;
214         }
215         else {
216                 headers_size = IMAP->cached_rfc822_len;
217                 total_size = IMAP->cached_rfc822_len;
218                 text_size = 0;
219         }
220
221         lprintf(CTDL_DEBUG, 
222                 "RFC822: headers=" SIZE_T_FMT 
223                 ", text=" SIZE_T_FMT
224                 ", total=" SIZE_T_FMT "\n",
225                 headers_size, text_size, total_size);
226
227         if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
228                 cprintf("RFC822.SIZE " SIZE_T_FMT, total_size);
229                 return;
230         }
231
232         else if (!strcasecmp(whichfmt, "RFC822")) {
233                 ptr = IMAP->cached_rfc822_data;
234                 bytes_to_send = total_size;
235         }
236
237         else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
238                 ptr = IMAP->cached_rfc822_data;
239                 bytes_to_send = headers_size;
240         }
241
242         else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
243                 ptr = &IMAP->cached_rfc822_data[headers_size];
244                 bytes_to_send = text_size;
245         }
246
247         cprintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send);
248         client_write(ptr, bytes_to_send);
249 }
250
251
252
253 /*
254  * Load a specific part of a message into the temp file to be output to a
255  * client.  FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
256  * but we still can't handle "2.HEADER" (which might not be a problem).
257  *
258  * Note: mime_parser() was called with dont_decode set to 1, so we have the
259  * luxury of simply spewing without having to re-encode.
260  */
261 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
262                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
263                     void *cbuserdata)
264 {
265         char mbuf2[SIZ];
266         char *desired_section;
267
268         desired_section = (char *)cbuserdata;
269
270         if (!strcasecmp(partnum, desired_section)) {
271                 client_write(content, length);
272         }
273
274         snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
275
276         if (!strcasecmp(desired_section, mbuf2)) {
277                 cprintf("Content-type: %s", cbtype);
278                 if (!IsEmptyStr(cbcharset))
279                         cprintf("; charset=\"%s\"", cbcharset);
280                 if (!IsEmptyStr(name))
281                         cprintf("; name=\"%s\"", name);
282                 cprintf("\r\n");
283                 if (!IsEmptyStr(encoding))
284                         cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
285                 if (!IsEmptyStr(encoding)) {
286                         cprintf("Content-Disposition: %s", disp);
287                         if (!IsEmptyStr(filename)) {
288                                 cprintf("; filename=\"%s\"", filename);
289                         }
290                         cprintf("\r\n");
291                 }
292                 cprintf("Content-Length: %ld\r\n", (long)length);
293                 cprintf("\r\n");
294         }
295                         
296
297 }
298
299
300 /* 
301  * Called by imap_fetch_envelope() to output the "From" field.
302  * This is in its own function because its logic is kind of complex.  We
303  * really need to make this suck less.
304  */
305 void imap_output_envelope_from(struct CtdlMessage *msg) {
306         char user[SIZ], node[SIZ], name[SIZ];
307
308         if (!msg) return;
309
310         /* For anonymous messages, it's so easy! */
311         if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
312                 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
313                 return;
314         }
315         if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
316                 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
317                 return;
318         }
319
320         /* For everything else, we do stuff. */
321         cprintf("((");                          /* open double-parens */
322         imap_strout(msg->cm_fields['A']);       /* personal name */
323         cprintf(" NIL ");                       /* source route (not used) */
324
325
326         if (msg->cm_fields['F'] != NULL) {
327                 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
328                 imap_strout(user);              /* mailbox name (user id) */
329                 cprintf(" ");
330                 if (!strcasecmp(node, config.c_nodename)) {
331                         imap_strout(config.c_fqdn);
332                 }
333                 else {
334                         imap_strout(node);              /* host name */
335                 }
336         }
337         else {
338                 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
339                 cprintf(" ");
340                 imap_strout(msg->cm_fields['N']);       /* host name */
341         }
342         
343         cprintf(")) ");                         /* close double-parens */
344 }
345
346
347
348 /*
349  * Output an envelope address (or set of addresses) in the official,
350  * convuluted, braindead format.  (Note that we can't use this for
351  * the "From" address because its data may come from a number of different
352  * fields.  But we can use it for "To" and possibly others.
353  */
354 void imap_output_envelope_addr(char *addr) {
355         char individual_addr[256];
356         int num_addrs;
357         int i;
358         char user[256];
359         char node[256];
360         char name[256];
361
362         if (addr == NULL) {
363                 cprintf("NIL ");
364                 return;
365         }
366
367         if (IsEmptyStr(addr)) {
368                 cprintf("NIL ");
369                 return;
370         }
371
372         cprintf("(");
373
374         /* How many addresses are listed here? */
375         num_addrs = num_tokens(addr, ',');
376
377         /* Output them one by one. */
378         for (i=0; i<num_addrs; ++i) {
379                 extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
380                 striplt(individual_addr);
381                 process_rfc822_addr(individual_addr, user, node, name);
382                 cprintf("(");
383                 imap_strout(name);
384                 cprintf(" NIL ");
385                 imap_strout(user);
386                 cprintf(" ");
387                 imap_strout(node);
388                 cprintf(")");
389                 if (i < (num_addrs-1)) cprintf(" ");
390         }
391
392         cprintf(") ");
393 }
394
395
396 /*
397  * Implements the ENVELOPE fetch item
398  * 
399  * Note that the imap_strout() function can cleverly output NULL fields as NIL,
400  * so we don't have to check for that condition like we do elsewhere.
401  */
402 void imap_fetch_envelope(struct CtdlMessage *msg) {
403         char datestringbuf[SIZ];
404         time_t msgdate;
405         char *fieldptr = NULL;
406
407         if (!msg) return;
408
409         /* Parse the message date into an IMAP-format date string */
410         if (msg->cm_fields['T'] != NULL) {
411                 msgdate = atol(msg->cm_fields['T']);
412         }
413         else {
414                 msgdate = time(NULL);
415         }
416         datestring(datestringbuf, sizeof datestringbuf,
417                 msgdate, DATESTRING_IMAP);
418
419         /* Now start spewing data fields.  The order is important, as it is
420          * defined by the protocol specification.  Nonexistent fields must
421          * be output as NIL, existent fields must be quoted or literalled.
422          * The imap_strout() function conveniently does all this for us.
423          */
424         cprintf("ENVELOPE (");
425
426         /* Date */
427         imap_strout(datestringbuf);
428         cprintf(" ");
429
430         /* Subject */
431         imap_strout(msg->cm_fields['U']);
432         cprintf(" ");
433
434         /* From */
435         imap_output_envelope_from(msg);
436
437         /* Sender (default to same as 'From' if not present) */
438         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
439         if (fieldptr != NULL) {
440                 imap_output_envelope_addr(fieldptr);
441                 free(fieldptr);
442         }
443         else {
444                 imap_output_envelope_from(msg);
445         }
446
447         /* Reply-to */
448         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
449         if (fieldptr != NULL) {
450                 imap_output_envelope_addr(fieldptr);
451                 free(fieldptr);
452         }
453         else {
454                 imap_output_envelope_from(msg);
455         }
456
457         /* To */
458         imap_output_envelope_addr(msg->cm_fields['R']);
459
460         /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
461         fieldptr = msg->cm_fields['Y'];
462         if (fieldptr != NULL) {
463                 imap_output_envelope_addr(fieldptr);
464         }
465         else {
466                 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
467                 imap_output_envelope_addr(fieldptr);
468                 if (fieldptr != NULL) free(fieldptr);
469         }
470
471         /* Bcc */
472         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
473         imap_output_envelope_addr(fieldptr);
474         if (fieldptr != NULL) free(fieldptr);
475
476         /* In-reply-to */
477         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
478         imap_strout(fieldptr);
479         cprintf(" ");
480         if (fieldptr != NULL) free(fieldptr);
481
482         /* message ID */
483         imap_strout(msg->cm_fields['I']);
484
485         cprintf(")");
486 }
487
488 /*
489  * This function is called only when CC->redirect_buffer contains a set of
490  * RFC822 headers with no body attached.  Its job is to strip that set of
491  * headers down to *only* the ones we're interested in.
492  */
493 void imap_strip_headers(char *section) {
494         char buf[SIZ];
495         char *which_fields = NULL;
496         int doing_headers = 0;
497         int headers_not = 0;
498         char *parms[SIZ];
499         int num_parms = 0;
500         int i;
501         char *boiled_headers = NULL;
502         int ok = 0;
503         int done_headers = 0;
504         char *ptr = NULL;
505
506         if (CC->redirect_buffer == NULL) return;
507
508         which_fields = strdup(section);
509
510         if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
511                 doing_headers = 1;
512         if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
513                 headers_not = 1;
514
515         for (i=0; which_fields[i]; ++i) {
516                 if (which_fields[i]=='(')
517                         strcpy(which_fields, &which_fields[i+1]);
518         }
519         for (i=0; which_fields[i]; ++i) {
520                 if (which_fields[i]==')') {
521                         which_fields[i] = 0;
522                         break;
523                 }
524         }
525         num_parms = imap_parameterize(parms, which_fields);
526
527         boiled_headers = malloc(CC->redirect_alloc);
528         strcpy(boiled_headers, "");
529
530         ptr = CC->redirect_buffer;
531         ok = 0;
532         while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
533                 if (!isspace(buf[0])) {
534                         ok = 0;
535                         if (doing_headers == 0) ok = 1;
536                         else {
537                                 if (headers_not) ok = 1;
538                                 else ok = 0;
539                                 for (i=0; i<num_parms; ++i) {
540                                         if ( (!strncasecmp(buf, parms[i],
541                                            strlen(parms[i]))) &&
542                                            (buf[strlen(parms[i])]==':') ) {
543                                                 if (headers_not) ok = 0;
544                                                 else ok = 1;
545                                         }
546                                 }
547                         }
548                 }
549
550                 if (ok) {
551                         strcat(boiled_headers, buf);
552                         strcat(boiled_headers, "\r\n");
553                 }
554
555                 if (IsEmptyStr(buf)) done_headers = 1;
556                 if (buf[0]=='\r') done_headers = 1;
557                 if (buf[0]=='\n') done_headers = 1;
558         }
559
560         strcat(boiled_headers, "\r\n");
561
562         /* Now save it back (it'll always be smaller) */
563         strcpy(CC->redirect_buffer, boiled_headers);
564         CC->redirect_len = strlen(boiled_headers);
565
566         free(which_fields);
567         free(boiled_headers);
568 }
569
570
571 /*
572  * Implements the BODY and BODY.PEEK fetch items
573  */
574 void imap_fetch_body(long msgnum, char *item, int is_peek) {
575         struct CtdlMessage *msg = NULL;
576         char section[SIZ];
577         char partial[SIZ];
578         int is_partial = 0;
579         size_t pstart, pbytes;
580         int loading_body_now = 0;
581         int need_body = 1;
582         int burn_the_cache = 0;
583
584         /* extract section */
585         safestrncpy(section, item, sizeof section);
586         if (strchr(section, '[') != NULL) {
587                 stripallbut(section, '[', ']');
588         }
589         lprintf(CTDL_DEBUG, "Section is: %s%s\n", 
590                 section, 
591                 IsEmptyStr(section) ? "(empty)" : "");
592
593         /*
594          * We used to have this great optimization in place that would avoid
595          * fetching the entire RFC822 message from disk if the client was only
596          * asking for the headers.  Unfortunately, fetching only the Citadel
597          * headers omits "Content-type:" and this behavior breaks the iPhone
598          * email client.  So we have to fetch the whole message from disk.  The
599          *
600          *      if (!strncasecmp(section, "HEADER", 6)) {
601          *              need_body = 0;
602          *      }
603          *
604          */
605
606         /* Burn the cache if we don't have the same section of the 
607          * same message again.
608          */
609         if (IMAP->cached_body != NULL) {
610                 if (IMAP->cached_bodymsgnum != msgnum) {
611                         burn_the_cache = 1;
612                 }
613                 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
614                         burn_the_cache = 1;
615                 }
616                 else if (strcasecmp(IMAP->cached_bodypart, section)) {
617                         burn_the_cache = 1;
618                 }
619                 if (burn_the_cache) {
620                         /* Yup, go ahead and burn the cache. */
621                         free(IMAP->cached_body);
622                         IMAP->cached_body_len = 0;
623                         IMAP->cached_body = NULL;
624                         IMAP->cached_bodymsgnum = (-1);
625                         strcpy(IMAP->cached_bodypart, "");
626                 }
627         }
628
629         /* extract partial */
630         safestrncpy(partial, item, sizeof partial);
631         if (strchr(partial, '<') != NULL) {
632                 stripallbut(partial, '<', '>');
633                 is_partial = 1;
634         }
635         if (is_partial == 0) strcpy(partial, "");
636         /* if (!IsEmptyStr(partial)) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
637
638         if (IMAP->cached_body == NULL) {
639                 CC->redirect_buffer = malloc(SIZ);
640                 CC->redirect_len = 0;
641                 CC->redirect_alloc = SIZ;
642                 loading_body_now = 1;
643                 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
644         }
645
646         /* Now figure out what the client wants, and get it */
647
648         if (!loading_body_now) {
649                 /* What we want is already in memory */
650         }
651
652         else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
653                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
654         }
655
656         else if (!strcmp(section, "")) {
657                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
658         }
659
660         /*
661          * If the client asked for just headers, or just particular header
662          * fields, strip it down.
663          */
664         else if (!strncasecmp(section, "HEADER", 6)) {
665                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
666                 imap_strip_headers(section);
667         }
668
669         /*
670          * Strip it down if the client asked for everything _except_ headers.
671          */
672         else if (!strncasecmp(section, "TEXT", 4)) {
673                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
674         }
675
676         /*
677          * Anything else must be a part specifier.
678          * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
679          */
680         else {
681                 mime_parser(msg->cm_fields['M'], NULL,
682                                 *imap_load_part, NULL, NULL,
683                                 section,
684                                 1);
685         }
686
687         if (loading_body_now) {
688                 IMAP->cached_body = CC->redirect_buffer;
689                 IMAP->cached_body_len = CC->redirect_len;
690                 IMAP->cached_bodymsgnum = msgnum;
691                 IMAP->cached_body_withbody = need_body;
692                 strcpy(IMAP->cached_bodypart, section);
693                 CC->redirect_buffer = NULL;
694                 CC->redirect_len = 0;
695                 CC->redirect_alloc = 0;
696         }
697
698         if (is_partial == 0) {
699                 cprintf("BODY[%s] {" SIZE_T_FMT "}\r\n", section, IMAP->cached_body_len);
700                 pstart = 0;
701                 pbytes = IMAP->cached_body_len;
702         }
703         else {
704                 sscanf(partial, SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
705                 if (pbytes > (IMAP->cached_body_len - pstart)) {
706                         pbytes = IMAP->cached_body_len - pstart;
707                 }
708                 cprintf("BODY[%s]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", section, pstart, pbytes);
709         }
710
711         /* Here we go -- output it */
712         client_write(&IMAP->cached_body[pstart], pbytes);
713
714         if (msg != NULL) {
715                 CtdlFreeMessage(msg);
716         }
717
718         /* Mark this message as "seen" *unless* this is a "peek" operation */
719         if (is_peek == 0) {
720                 CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
721         }
722 }
723
724 /*
725  * Called immediately before outputting a multipart bodystructure
726  */
727 void imap_fetch_bodystructure_pre(
728                 char *name, char *filename, char *partnum, char *disp,
729                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
730                 void *cbuserdata
731                 ) {
732
733         cprintf("(");
734 }
735
736
737
738 /*
739  * Called immediately after outputting a multipart bodystructure
740  */
741 void imap_fetch_bodystructure_post(
742                 char *name, char *filename, char *partnum, char *disp,
743                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
744                 void *cbuserdata
745                 ) {
746
747         char subtype[128];
748
749         cprintf(" ");
750
751         /* disposition */
752         extract_token(subtype, cbtype, 1, '/', sizeof subtype);
753         imap_strout(subtype);
754
755         /* body language */
756         /* cprintf(" NIL"); We thought we needed this at one point, but maybe we don't... */
757
758         cprintf(")");
759 }
760
761
762
763 /*
764  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
765  *
766  */
767 void imap_fetch_bodystructure_part(
768                 char *name, char *filename, char *partnum, char *disp,
769                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
770                 void *cbuserdata
771                 ) {
772
773         int have_cbtype = 0;
774         int have_encoding = 0;
775         int lines = 0;
776         size_t i;
777         char cbmaintype[128];
778         char cbsubtype[128];
779
780         if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
781         if (have_cbtype) {
782                 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
783                 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
784         }
785         else {
786                 strcpy(cbmaintype, "TEXT");
787                 strcpy(cbsubtype, "PLAIN");
788         }
789
790         cprintf("(");
791         imap_strout(cbmaintype);                                        /* body type */
792         cprintf(" ");
793         imap_strout(cbsubtype);                                         /* body subtype */
794         cprintf(" ");
795
796         cprintf("(");                                                   /* begin body parameter list */
797
798         /* "NAME" must appear as the first parameter.  This is not required by IMAP,
799          * but the Asterisk voicemail application blindly assumes that NAME will be in
800          * the first position.  If it isn't, it rejects the message.
801          */
802         if (name != NULL) if (!IsEmptyStr(name)) {
803                 cprintf("\"NAME\" ");
804                 imap_strout(name);
805                 cprintf(" ");
806         }
807
808         cprintf("\"CHARSET\" ");
809         if (cbcharset == NULL) {
810                 imap_strout("US-ASCII");
811         }
812         else if (cbcharset[0] == 0) {
813                 imap_strout("US-ASCII");
814         }
815         else {
816                 imap_strout(cbcharset);
817         }
818         cprintf(") ");                                                  /* end body parameter list */
819
820         cprintf("NIL ");                                                /* Body ID */
821         cprintf("NIL ");                                                /* Body description */
822
823         if (encoding != NULL) if (encoding[0] != 0)  have_encoding = 1;
824         if (have_encoding) {
825                 imap_strout(encoding);
826         }
827         else {
828                 imap_strout("7BIT");
829         }
830         cprintf(" ");
831
832         /* The next field is the size of the part in bytes. */
833         cprintf("%ld ", (long)length);  /* bytes */
834
835         /* The next field is the number of lines in the part, if and only
836          * if the part is TEXT.  More gratuitous complexity.
837          */
838         if (!strcasecmp(cbmaintype, "TEXT")) {
839                 if (length) for (i=0; i<length; ++i) {
840                         if (((char *)content)[i] == '\n') ++lines;
841                 }
842                 cprintf("%d ", lines);
843         }
844
845         /* More gratuitous complexity */
846         if ((!strcasecmp(cbmaintype, "MESSAGE"))
847            && (!strcasecmp(cbsubtype, "RFC822"))) {
848                 /* FIXME
849                      A body type of type MESSAGE and subtype RFC822
850                      contains, immediately after the basic fields, the
851                      envelope structure, body structure, and size in
852                      text lines of the encapsulated message.
853                 */
854         }
855
856         /* MD5 value of body part; we can get away with NIL'ing this */
857         cprintf("NIL ");
858
859         /* Disposition */
860         if (disp == NULL) {
861                 cprintf("NIL");
862         }
863         else if (IsEmptyStr(disp)) {
864                 cprintf("NIL");
865         }
866         else {
867                 cprintf("(");
868                 imap_strout(disp);
869                 if (filename != NULL) if (!IsEmptyStr(filename)) {
870                         cprintf(" (\"FILENAME\" ");
871                         imap_strout(filename);
872                         cprintf(")");
873                 }
874                 cprintf(")");
875         }
876
877         /* Body language (not defined yet) */
878         cprintf(" NIL)");
879 }
880
881
882
883 /*
884  * Spew the BODYSTRUCTURE data for a message.
885  *
886  */
887 void imap_fetch_bodystructure (long msgnum, char *item,
888                 struct CtdlMessage *msg) {
889         char *rfc822 = NULL;
890         char *rfc822_body = NULL;
891         size_t rfc822_len;
892         size_t rfc822_headers_len;
893         size_t rfc822_body_len;
894         char *ptr = NULL;
895         char buf[SIZ];
896         int lines = 0;
897
898         /* Handle NULL message gracefully */
899         if (msg == NULL) {
900                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
901                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
902                         "\"7BIT\" 0 0)");
903                 return;
904         }
905
906         /* For non-RFC822 (ordinary Citadel) messages, this is short and
907          * sweet...
908          */
909         if (msg->cm_format_type != FMT_RFC822) {
910
911                 /* *sigh* We have to RFC822-format the message just to be able
912                  * to measure it.  FIXME use smi cached fields if possible
913                  */
914
915                 CC->redirect_buffer = malloc(SIZ);
916                 CC->redirect_len = 0;
917                 CC->redirect_alloc = SIZ;
918                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1);
919                 rfc822 = CC->redirect_buffer;
920                 rfc822_len = CC->redirect_len;
921                 CC->redirect_buffer = NULL;
922                 CC->redirect_len = 0;
923                 CC->redirect_alloc = 0;
924
925                 ptr = rfc822;
926                 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
927                         ++lines;
928                         if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
929                                 rfc822_body = ptr;
930                         }
931                 }
932
933                 rfc822_headers_len = rfc822_body - rfc822;
934                 rfc822_body_len = rfc822_len - rfc822_headers_len;
935                 free(rfc822);
936
937                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
938                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
939                         "\"7BIT\" " SIZE_T_FMT " %d)", rfc822_body_len, lines);
940
941                 return;
942         }
943
944         /* For messages already stored in RFC822 format, we have to parse. */
945         cprintf("BODYSTRUCTURE ");
946         mime_parser(msg->cm_fields['M'],
947                         NULL,
948                         *imap_fetch_bodystructure_part, /* part */
949                         *imap_fetch_bodystructure_pre,  /* pre-multi */
950                         *imap_fetch_bodystructure_post, /* post-multi */
951                         NULL,
952                         1);     /* don't decode -- we want it as-is */
953 }
954
955
956 /*
957  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
958  * individual message, once it has been selected for output.
959  */
960 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
961         int i;
962         struct CtdlMessage *msg = NULL;
963         int body_loaded = 0;
964
965         /* Don't attempt to fetch bogus messages or UID's */
966         if (seq < 1) return;
967         if (IMAP->msgids[seq-1] < 1L) return;
968
969         buffer_output();
970         cprintf("* %d FETCH (", seq);
971
972         for (i=0; i<num_items; ++i) {
973
974                 /* Fetchable without going to the message store at all */
975                 if (!strcasecmp(itemlist[i], "UID")) {
976                         imap_fetch_uid(seq);
977                 }
978                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
979                         imap_fetch_flags(seq-1);
980                 }
981
982                 /* Potentially fetchable from cache, if the client requests
983                  * stuff from the same message several times in a row.
984                  */
985                 else if (!strcasecmp(itemlist[i], "RFC822")) {
986                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
987                 }
988                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
989                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
990                 }
991                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
992                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
993                 }
994                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
995                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
996                 }
997
998                 /* BODY fetches do their own fetching and caching too. */
999                 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
1000                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
1001                 }
1002                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
1003                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
1004                 }
1005
1006                 /* Otherwise, load the message into memory.
1007                  */
1008                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
1009                         if ((msg != NULL) && (!body_loaded)) {
1010                                 CtdlFreeMessage(msg);   /* need the whole thing */
1011                                 msg = NULL;
1012                         }
1013                         if (msg == NULL) {
1014                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
1015                                 body_loaded = 1;
1016                         }
1017                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
1018                                         itemlist[i], msg);
1019                 }
1020                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
1021                         if (msg == NULL) {
1022                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1023                                 body_loaded = 0;
1024                         }
1025                         imap_fetch_envelope(msg);
1026                 }
1027                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1028                         if (msg == NULL) {
1029                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1030                                 body_loaded = 0;
1031                         }
1032                         imap_fetch_internaldate(msg);
1033                 }
1034
1035                 if (i != num_items-1) cprintf(" ");
1036         }
1037
1038         cprintf(")\r\n");
1039         unbuffer_output();
1040         if (msg != NULL) {
1041                 CtdlFreeMessage(msg);
1042         }
1043 }
1044
1045
1046
1047 /*
1048  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1049  * validated and boiled down the request a bit.
1050  */
1051 void imap_do_fetch(int num_items, char **itemlist) {
1052         int i;
1053
1054         if (IMAP->num_msgs > 0) {
1055                 for (i = 0; i < IMAP->num_msgs; ++i) {
1056
1057                         /* Abort the fetch loop if the session breaks.
1058                          * This is important for users who keep mailboxes
1059                          * that are too big *and* are too impatient to
1060                          * let them finish loading.  :)
1061                          */
1062                         if (CC->kill_me) return;
1063
1064                         /* Get any message marked for fetch. */
1065                         if (IMAP->flags[i] & IMAP_SELECTED) {
1066                                 imap_do_fetch_msg(i+1, num_items, itemlist);
1067                         }
1068                 }
1069         }
1070 }
1071
1072
1073
1074 /*
1075  * Back end for imap_handle_macros()
1076  * Note that this function *only* looks at the beginning of the string.  It
1077  * is not a generic search-and-replace function.
1078  */
1079 void imap_macro_replace(char *str, char *find, char *replace) {
1080         char holdbuf[SIZ];
1081         int findlen;
1082
1083         findlen = strlen(find);
1084
1085         if (!strncasecmp(str, find, findlen)) {
1086                 if (str[findlen]==' ') {
1087                         strcpy(holdbuf, &str[findlen+1]);
1088                         strcpy(str, replace);
1089                         strcat(str, " ");
1090                         strcat(str, holdbuf);
1091                 }
1092                 if (str[findlen]==0) {
1093                         strcpy(holdbuf, &str[findlen+1]);
1094                         strcpy(str, replace);
1095                 }
1096         }
1097 }
1098
1099
1100
1101 /*
1102  * Handle macros embedded in FETCH data items.
1103  * (What the heck are macros doing in a wire protocol?  Are we trying to save
1104  * the computer at the other end the trouble of typing a lot of characters?)
1105  */
1106 void imap_handle_macros(char *str) {
1107         int i;
1108         int nest = 0;
1109
1110         for (i=0; str[i]; ++i) {
1111                 if (str[i]=='(') ++nest;
1112                 if (str[i]=='[') ++nest;
1113                 if (str[i]=='<') ++nest;
1114                 if (str[i]=='{') ++nest;
1115                 if (str[i]==')') --nest;
1116                 if (str[i]==']') --nest;
1117                 if (str[i]=='>') --nest;
1118                 if (str[i]=='}') --nest;
1119
1120                 if (nest <= 0) {
1121                         imap_macro_replace(&str[i],
1122                                 "ALL",
1123                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1124                         );
1125                         imap_macro_replace(&str[i],
1126                                 "BODY",
1127                                 "BODYSTRUCTURE"
1128                         );
1129                         imap_macro_replace(&str[i],
1130                                 "FAST",
1131                                 "FLAGS INTERNALDATE RFC822.SIZE"
1132                         );
1133                         imap_macro_replace(&str[i],
1134                                 "FULL",
1135                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1136                         );
1137                 }
1138         }
1139 }
1140
1141
1142 /*
1143  * Break out the data items requested, possibly a parenthesized list.
1144  * Returns the number of data items, or -1 if the list is invalid.
1145  * NOTE: this function alters the string it is fed, and uses it as a buffer
1146  * to hold the data for the pointers it returns.
1147  */
1148 int imap_extract_data_items(char **argv, char *items) {
1149         int num_items = 0;
1150         int nest = 0;
1151         int i;
1152         char *start;
1153         long initial_len;
1154
1155         /* Convert all whitespace to ordinary space characters. */
1156         for (i=0; items[i]; ++i) {
1157                 if (isspace(items[i])) items[i]=' ';
1158         }
1159
1160         /* Strip leading and trailing whitespace, then strip leading and
1161          * trailing parentheses if it's a list
1162          */
1163         striplt(items);
1164         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1165                 items[strlen(items)-1] = 0;
1166                 strcpy(items, &items[1]);
1167                 striplt(items);
1168         }
1169
1170         /* Parse any macro data items */
1171         imap_handle_macros(items);
1172
1173         /*
1174          * Now break out the data items.  We throw in one trailing space in
1175          * order to avoid having to break out the last one manually.
1176          */
1177         strcat(items, " ");
1178         start = items;
1179         initial_len = strlen(items);
1180         for (i=0; i<initial_len; ++i) {
1181                 if (items[i]=='(') ++nest;
1182                 if (items[i]=='[') ++nest;
1183                 if (items[i]=='<') ++nest;
1184                 if (items[i]=='{') ++nest;
1185                 if (items[i]==')') --nest;
1186                 if (items[i]==']') --nest;
1187                 if (items[i]=='>') --nest;
1188                 if (items[i]=='}') --nest;
1189
1190                 if (nest <= 0) if (items[i]==' ') {
1191                         items[i] = 0;
1192                         argv[num_items++] = start;
1193                         start = &items[i+1];
1194                 }
1195         }
1196
1197         return(num_items);
1198
1199 }
1200
1201
1202 /*
1203  * One particularly hideous aspect of IMAP is that we have to allow the client
1204  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1205  * handles this by setting the IMAP_SELECTED flag for each message specified in
1206  * the ranges/sets, then looping through the message array, outputting messages
1207  * with the flag set.  We don't bother returning an error if an out-of-range
1208  * number is specified (we just return quietly) because any client braindead
1209  * enough to request a bogus message number isn't going to notice the
1210  * difference anyway.
1211  *
1212  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1213  * message included in the specified range.
1214  *
1215  * Set is_uid to 1 to fetch by UID instead of sequence number.
1216  */
1217 void imap_pick_range(char *supplied_range, int is_uid) {
1218         int i;
1219         int num_sets;
1220         int s;
1221         char setstr[SIZ], lostr[SIZ], histr[SIZ];
1222         long lo, hi;
1223         char actual_range[SIZ];
1224         struct citimap *Imap;
1225
1226         /* 
1227          * Handle the "ALL" macro
1228          */
1229         if (!strcasecmp(supplied_range, "ALL")) {
1230                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1231         }
1232         else {
1233                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1234         }
1235
1236         Imap = IMAP;
1237         /*
1238          * Clear out the IMAP_SELECTED flags for all messages.
1239          */
1240         for (i = 0; i < Imap->num_msgs; ++i) {
1241                 Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
1242         }
1243
1244         /*
1245          * Now set it for all specified messages.
1246          */
1247         num_sets = num_tokens(actual_range, ',');
1248         for (s=0; s<num_sets; ++s) {
1249                 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1250
1251                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1252                 if (num_tokens(setstr, ':') >= 2) {
1253                         extract_token(histr, setstr, 1, ':', sizeof histr);
1254                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1255                 } 
1256                 else {
1257                         safestrncpy(histr, lostr, sizeof histr);
1258                 }
1259                 lo = atol(lostr);
1260                 hi = atol(histr);
1261
1262                 /* Loop through the array, flipping bits where appropriate */
1263                 for (i = 1; i <= Imap->num_msgs; ++i) {
1264                         if (is_uid) {   /* fetch by sequence number */
1265                                 if ( (Imap->msgids[i-1]>=lo)
1266                                    && (Imap->msgids[i-1]<=hi)) {
1267                                         Imap->flags[i-1] |= IMAP_SELECTED;
1268                                 }
1269                         }
1270                         else {          /* fetch by uid */
1271                                 if ( (i>=lo) && (i<=hi)) {
1272                                         Imap->flags[i-1] |= IMAP_SELECTED;
1273                                 }
1274                         }
1275                 }
1276         }
1277
1278 }
1279
1280
1281
1282 /*
1283  * This function is called by the main command loop.
1284  */
1285 void imap_fetch(int num_parms, char *parms[]) {
1286         char items[SIZ];
1287         char *itemlist[512];
1288         int num_items;
1289         int i;
1290
1291         if (num_parms < 4) {
1292                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1293                 return;
1294         }
1295
1296         imap_pick_range(parms[2], 0);
1297
1298         strcpy(items, "");
1299         for (i=3; i<num_parms; ++i) {
1300                 strcat(items, parms[i]);
1301                 if (i < (num_parms-1)) strcat(items, " ");
1302         }
1303
1304         num_items = imap_extract_data_items(itemlist, items);
1305         if (num_items < 1) {
1306                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1307                 return;
1308         }
1309
1310         imap_do_fetch(num_items, itemlist);
1311         cprintf("%s OK FETCH completed\r\n", parms[0]);
1312 }
1313
1314 /*
1315  * This function is called by the main command loop.
1316  */
1317 void imap_uidfetch(int num_parms, char *parms[]) {
1318         char items[SIZ];
1319         char *itemlist[512];
1320         int num_items;
1321         int i;
1322         int have_uid_item = 0;
1323
1324         if (num_parms < 5) {
1325                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1326                 return;
1327         }
1328
1329         imap_pick_range(parms[3], 1);
1330
1331         strcpy(items, "");
1332         for (i=4; i<num_parms; ++i) {
1333                 strcat(items, parms[i]);
1334                 if (i < (num_parms-1)) strcat(items, " ");
1335         }
1336
1337         num_items = imap_extract_data_items(itemlist, items);
1338         if (num_items < 1) {
1339                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1340                 return;
1341         }
1342
1343         /* If the "UID" item was not included, we include it implicitly
1344          * (at the beginning) because this is a UID FETCH command
1345          */
1346         for (i=0; i<num_items; ++i) {
1347                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1348         }
1349         if (have_uid_item == 0) {
1350                 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1351                 ++num_items;
1352                 itemlist[0] = "UID";
1353         }
1354
1355         imap_do_fetch(num_items, itemlist);
1356         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1357 }
1358
1359