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