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