* MUCH faster implementation of rfc822_fetch_field()
[citadel.git] / citadel / imap_fetch.c
1 /*
2  * $Id$
3  *
4  * Implements the FETCH command in IMAP.
5  * This command is way too convoluted.  Marc Crispin is a fscking idiot.
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 struct imap_fetch_part {
58         char desired_section[SIZ];
59         FILE *output_fp;
60 };
61
62 /*
63  * Individual field functions for imap_do_fetch_msg() ...
64  */
65
66
67
68 void imap_fetch_uid(int seq) {
69         cprintf("UID %ld", IMAP->msgids[seq-1]);
70 }
71
72 void imap_fetch_flags(int seq) {
73         int num_flags_printed = 0;
74         cprintf("FLAGS (");
75         if (IMAP->flags[seq] & IMAP_DELETED) {
76                 if (num_flags_printed > 0) cprintf(" ");
77                 cprintf("\\Deleted");
78                 ++num_flags_printed;
79         }
80         if (IMAP->flags[seq] & IMAP_SEEN) {
81                 if (num_flags_printed > 0) cprintf(" ");
82                 cprintf("\\Seen");
83                 ++num_flags_printed;
84         }
85         cprintf(")");
86 }
87
88 void imap_fetch_internaldate(struct CtdlMessage *msg) {
89         char buf[SIZ];
90         time_t msgdate;
91
92         if (msg->cm_fields['T'] != NULL) {
93                 msgdate = atol(msg->cm_fields['T']);
94         }
95         else {
96                 msgdate = time(NULL);
97         }
98
99         datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
100         cprintf("INTERNALDATE \"%s\"", buf);
101 }
102
103
104 /*
105  * Fetch RFC822-formatted messages.
106  *
107  * 'whichfmt' should be set to one of:
108  *      "RFC822"        entire message
109  *      "RFC822.HEADER" headers only (with trailing blank line)
110  *      "RFC822.SIZE"   size of translated message
111  *      "RFC822.TEXT"   body only (without leading blank line)
112  */
113 void imap_fetch_rfc822(int msgnum, char *whichfmt, struct CtdlMessage *msg) {
114         FILE *tmp;
115         char buf[1024];
116         char *ptr;
117         long headers_size, text_size, total_size;
118         long bytes_remaining = 0;
119         long blocksize;
120
121         tmp = tmpfile();
122         if (tmp == NULL) {
123                 lprintf(1, "Cannot open temp file: %s\n", strerror(errno));
124                 return;
125         }
126
127         /*
128          * Load the message into a temp file for translation and measurement
129          */ 
130         CtdlRedirectOutput(tmp, -1);
131         CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
132         CtdlRedirectOutput(NULL, -1);
133         if (!is_valid_message(msg)) {
134                 lprintf(1, "WARNING: output clobbered the message!\n");
135         }
136
137         /*
138          * Now figure out where the headers/text break is.  IMAP considers the
139          * intervening blank line to be part of the headers, not the text.
140          */
141         rewind(tmp);
142         headers_size = 0L;
143         do {
144                 ptr = fgets(buf, sizeof buf, tmp);
145                 if (ptr != NULL) {
146                         striplt(buf);
147                         if (strlen(buf) == 0) headers_size = ftell(tmp);
148                 }
149         } while ( (headers_size == 0L) && (ptr != NULL) );
150         fseek(tmp, 0L, SEEK_END);
151         total_size = ftell(tmp);
152         text_size = total_size - headers_size;
153
154         if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
155                 cprintf("RFC822.SIZE %ld", total_size);
156                 fclose(tmp);
157                 return;
158         }
159
160         else if (!strcasecmp(whichfmt, "RFC822")) {
161                 bytes_remaining = total_size;
162                 rewind(tmp);
163         }
164
165         else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
166                 bytes_remaining = headers_size;
167                 rewind(tmp);
168         }
169
170         else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
171                 bytes_remaining = text_size;
172                 fseek(tmp, headers_size, SEEK_SET);
173         }
174
175         cprintf("%s {%ld}\r\n", whichfmt, bytes_remaining);
176         blocksize = sizeof(buf);
177         while (bytes_remaining > 0L) {
178                 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
179                 fread(buf, blocksize, 1, tmp);
180                 client_write(buf, blocksize);
181                 bytes_remaining = bytes_remaining - blocksize;
182         }
183
184         fclose(tmp);
185 }
186
187
188
189 /*
190  * Load a specific part of a message into the temp file to be output to a
191  * client.  FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
192  * but we still can't handle "2.HEADER" (which might not be a problem, because
193  * we currently don't have the ability to break out nested RFC822's anyway).
194  *
195  * Note: mime_parser() was called with dont_decode set to 1, so we have the
196  * luxury of simply spewing without having to re-encode.
197  */
198 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
199                     void *content, char *cbtype, size_t length, char *encoding,
200                     void *cbuserdata)
201 {
202         struct imap_fetch_part *imfp;
203         char mbuf2[1024];
204
205         imfp = (struct imap_fetch_part *)cbuserdata;
206
207         if (!strcasecmp(partnum, imfp->desired_section)) {
208                 fwrite(content, length, 1, imfp->output_fp);
209         }
210
211         snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
212
213         if (!strcasecmp(imfp->desired_section, mbuf2)) {
214                 fprintf(imfp->output_fp, "Content-type: %s", cbtype);
215                 if (strlen(name) > 0)
216                         fprintf(imfp->output_fp, "; name=\"%s\"", name);
217                 fprintf(imfp->output_fp, "\r\n");
218                 if (strlen(encoding) > 0)
219                         fprintf(imfp->output_fp,
220                                 "Content-Transfer-Encoding: %s\r\n", encoding);
221                 if (strlen(encoding) > 0) {
222                         fprintf(imfp->output_fp, "Content-Disposition: %s",
223                                         disp);
224                         if (strlen(filename) > 0) {
225                                 fprintf(imfp->output_fp, "; filename=\"%s\"",
226                                         filename);
227                         }
228                         fprintf(imfp->output_fp, "\r\n");
229                 }
230                 fprintf(imfp->output_fp, "Content-Length: %ld\r\n", (long)length);
231                 fprintf(imfp->output_fp, "\r\n");
232         }
233                         
234
235 }
236
237
238 /* 
239  * Called by imap_fetch_envelope() to output the "From" field.
240  * This is in its own function because its logic is kind of complex.  We
241  * really need to make this suck less.
242  */
243 void imap_output_envelope_from(struct CtdlMessage *msg) {
244         char user[1024], node[1024], name[1024];
245
246         cprintf("((");                          /* open double-parens */
247         imap_strout(msg->cm_fields['A']);       /* personal name */
248         cprintf(" NIL ");                       /* source route (not used) */
249
250         if (msg->cm_fields['F'] != NULL) {
251                 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
252                 imap_strout(user);              /* mailbox name (user id) */
253                 cprintf(" ");
254                 if (!strcasecmp(node, config.c_nodename)) {
255                         imap_strout(config.c_fqdn);
256                 }
257                 else {
258                         imap_strout(node);              /* host name */
259                 }
260         }
261         else {
262                 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
263                 cprintf(" ");
264                 imap_strout(msg->cm_fields['N']);       /* host name */
265         }
266         
267         cprintf(")) ");                         /* close double-parens */
268 }
269
270
271 /*
272  * Implements the ENVELOPE fetch item
273  * 
274  * FIXME ... we only output some of the fields right now.  Definitely need
275  *           to do all of them.  Accurately, too.
276  *
277  * Note that the imap_strout() function can cleverly output NULL fields as NIL,
278  * so we don't have to check for that condition like we do elsewhere.
279  */
280 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
281         char datestringbuf[SIZ];
282         time_t msgdate;
283         char *fieldptr = NULL;
284
285         /* Parse the message date into an IMAP-format date string */
286         if (msg->cm_fields['T'] != NULL) {
287                 msgdate = atol(msg->cm_fields['T']);
288         }
289         else {
290                 msgdate = time(NULL);
291         }
292         datestring(datestringbuf, sizeof datestringbuf, msgdate, DATESTRING_IMAP);
293
294         /* Now start spewing data fields.  The order is important, as it is
295          * defined by the protocol specification.  Nonexistent fields must
296          * be output as NIL, existent fields must be quoted or literalled.
297          * The imap_strout() function conveniently does all this for us.
298          */
299         cprintf("ENVELOPE (");
300
301         /* Date */
302         imap_strout(datestringbuf);
303         cprintf(" ");
304
305         /* Subject */
306         imap_strout(msg->cm_fields['U']);
307         cprintf(" ");
308
309         /* From */
310         imap_output_envelope_from(msg);
311
312         /* Sender */
313         if (0) {
314                 /* FIXME ... check for a *real* Sender: field */
315         }
316         else {
317                 imap_output_envelope_from(msg);
318         }
319
320         /* Reply-to */
321         if (0) {
322                 /* FIXME ... check for a *real* Reply-to: field */
323         }
324         else {
325                 imap_output_envelope_from(msg);
326         }
327
328         cprintf("NIL ");        /* to */
329
330         cprintf("NIL ");        /* cc */
331
332         cprintf("NIL ");        /* bcc */
333
334         /* In-reply-to */
335         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
336         imap_strout(fieldptr);
337         cprintf(" ");
338         if (fieldptr != NULL) phree(fieldptr);
339
340         /* message ID */
341         imap_strout(msg->cm_fields['I']);
342
343         cprintf(") ");
344 }
345
346
347 /*
348  * Strip any non header information out of a chunk of RFC822 data on disk,
349  * then boil it down to just the fields we want.
350  */
351 void imap_strip_headers(FILE *fp, char *section) {
352         char buf[1024];
353         char *which_fields = NULL;
354         int doing_headers = 0;
355         int headers_not = 0;
356         char *parms[SIZ];
357         int num_parms = 0;
358         int i;
359         char *boiled_headers = NULL;
360         int ok = 0;
361         int done_headers = 0;
362
363         which_fields = strdoop(section);
364
365         if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
366                 doing_headers = 1;
367         if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
368                 headers_not = 1;
369
370         for (i=0; i<strlen(which_fields); ++i) {
371                 if (which_fields[i]=='(')
372                         strcpy(which_fields, &which_fields[i+1]);
373         }
374         for (i=0; i<strlen(which_fields); ++i) {
375                 if (which_fields[i]==')')
376                         which_fields[i] = 0;
377         }
378         num_parms = imap_parameterize(parms, which_fields);
379
380         fseek(fp, 0L, SEEK_END);
381         boiled_headers = mallok((size_t)(ftell(fp) + 256L));
382         strcpy(boiled_headers, "");
383
384         rewind(fp);
385         ok = 0;
386         while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
387                 if (!isspace(buf[0])) {
388                         ok = 0;
389                         if (doing_headers == 0) ok = 1;
390                         else {
391                                 if (headers_not) ok = 1;
392                                 else ok = 0;
393                                 for (i=0; i<num_parms; ++i) {
394                                         if ( (!strncasecmp(buf, parms[i],
395                                            strlen(parms[i]))) &&
396                                            (buf[strlen(parms[i])]==':') ) {
397                                                 if (headers_not) ok = 0;
398                                                 else ok = 1;
399                                         }
400                                 }
401                         }
402                 }
403
404                 if (ok) {
405                         strcat(boiled_headers, buf);
406                 }
407
408                 if (strlen(buf) == 0) done_headers = 1;
409                 if (buf[0]=='\r') done_headers = 1;
410                 if (buf[0]=='\n') done_headers = 1;
411         }
412
413         /* Now write it back */
414         rewind(fp);
415         fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
416         fflush(fp);
417         ftruncate(fileno(fp), ftell(fp));
418         fflush(fp);
419         rewind(fp);
420         phree(which_fields);
421         phree(boiled_headers);
422 }
423
424
425 /*
426  * Implements the BODY and BODY.PEEK fetch items
427  */
428 void imap_fetch_body(long msgnum, char *item, int is_peek,
429                 struct CtdlMessage *msg) {
430         char section[1024];
431         char partial[1024];
432         int is_partial = 0;
433         char buf[1024];
434         int i;
435         FILE *tmp;
436         long bytes_remaining = 0;
437         long blocksize;
438         long pstart, pbytes;
439         struct imap_fetch_part imfp;
440
441         /* extract section */
442         strcpy(section, item);
443         for (i=0; i<strlen(section); ++i) {
444                 if (section[i]=='[') strcpy(section, &section[i+1]);
445         }
446         for (i=0; i<strlen(section); ++i) {
447                 if (section[i]==']') section[i] = 0;
448         }
449         lprintf(9, "Section is %s\n", section);
450
451         /* extract partial */
452         strcpy(partial, item);
453         for (i=0; i<strlen(partial); ++i) {
454                 if (partial[i]=='<') {
455                         strcpy(partial, &partial[i+1]);
456                         is_partial = 1;
457                 }
458         }
459         for (i=0; i<strlen(partial); ++i) {
460                 if (partial[i]=='>') partial[i] = 0;
461         }
462         if (is_partial == 0) strcpy(partial, "");
463         if (strlen(partial) > 0) lprintf(9, "Partial is %s\n", partial);
464
465         tmp = tmpfile();
466         if (tmp == NULL) {
467                 lprintf(1, "Cannot open temp file: %s\n", strerror(errno));
468                 return;
469         }
470
471         /* Now figure out what the client wants, and get it */
472
473         if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
474                 CtdlRedirectOutput(tmp, -1);
475                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
476                                                 HEADERS_NONE, 0, 1);
477                 CtdlRedirectOutput(NULL, -1);
478         }
479
480         else if (!strcmp(section, "")) {
481                 CtdlRedirectOutput(tmp, -1);
482                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
483                                                 HEADERS_ALL, 0, 1);
484                 CtdlRedirectOutput(NULL, -1);
485         }
486
487         /*
488          * If the client asked for just headers, or just particular header
489          * fields, strip it down.
490          */
491         else if (!strncasecmp(section, "HEADER", 6)) {
492                 CtdlRedirectOutput(tmp, -1);
493                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
494                                                 HEADERS_ONLY, 0, 1);
495                 CtdlRedirectOutput(NULL, -1);
496                 imap_strip_headers(tmp, section);
497         }
498
499         /*
500          * Strip it down if the client asked for everything _except_ headers.
501          */
502         else if (!strncasecmp(section, "TEXT", 4)) {
503                 CtdlRedirectOutput(tmp, -1);
504                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
505                                                 HEADERS_NONE, 0, 1);
506                 CtdlRedirectOutput(NULL, -1);
507         }
508
509         /*
510          * Anything else must be a part specifier.
511          * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
512          */
513         else {
514                 safestrncpy(imfp.desired_section, section,
515                                 sizeof(imfp.desired_section));
516                 imfp.output_fp = tmp;
517
518                 mime_parser(msg->cm_fields['M'], NULL,
519                                 *imap_load_part, NULL, NULL,
520                                 (void *)&imfp,
521                                 1);
522         }
523
524
525         fseek(tmp, 0L, SEEK_END);
526         bytes_remaining = ftell(tmp);
527
528         if (is_partial == 0) {
529                 rewind(tmp);
530                 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
531         }
532         else {
533                 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
534                 if ((bytes_remaining - pstart) < pbytes) {
535                         pbytes = bytes_remaining - pstart;
536                 }
537                 fseek(tmp, pstart, SEEK_SET);
538                 bytes_remaining = pbytes;
539                 cprintf("BODY[%s]<%ld> {%ld}\r\n",
540                         section, pstart, bytes_remaining);
541         }
542
543         blocksize = sizeof(buf);
544         while (bytes_remaining > 0L) {
545                 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
546                 fread(buf, blocksize, 1, tmp);
547                 client_write(buf, blocksize);
548                 bytes_remaining = bytes_remaining - blocksize;
549         }
550
551         fclose(tmp);
552
553         /* Mark this message as "seen" *unless* this is a "peek" operation */
554         if (is_peek == 0) {
555                 CtdlSetSeen(msgnum, 1);
556         }
557 }
558
559 /*
560  * Called immediately before outputting a multipart bodystructure
561  */
562 void imap_fetch_bodystructure_pre(
563                 char *name, char *filename, char *partnum, char *disp,
564                 void *content, char *cbtype, size_t length, char *encoding,
565                 void *cbuserdata
566                 ) {
567
568         cprintf("(");
569 }
570
571
572
573 /*
574  * Called immediately after outputting a multipart bodystructure
575  */
576 void imap_fetch_bodystructure_post(
577                 char *name, char *filename, char *partnum, char *disp,
578                 void *content, char *cbtype, size_t length, char *encoding,
579                 void *cbuserdata
580                 ) {
581
582         char subtype[SIZ];
583
584         cprintf(" ");
585
586         /* disposition */
587         extract_token(subtype, cbtype, 1, '/');
588         imap_strout(subtype);
589
590         /* body language */
591         cprintf(" NIL");
592
593         cprintf(")");
594 }
595
596
597
598 /*
599  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
600  *
601  */
602 void imap_fetch_bodystructure_part(
603                 char *name, char *filename, char *partnum, char *disp,
604                 void *content, char *cbtype, size_t length, char *encoding,
605                 void *cbuserdata
606                 ) {
607
608         int have_cbtype = 0;
609         int have_encoding = 0;
610         int lines = 0;
611         size_t i;
612         char cbmaintype[SIZ];
613         char cbsubtype[SIZ];
614
615         if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
616         if (have_cbtype) {
617                 extract_token(cbmaintype, cbtype, 0, '/');
618                 extract_token(cbsubtype, cbtype, 1, '/');
619         }
620         else {
621                 strcpy(cbmaintype, "TEXT");
622                 strcpy(cbsubtype, "PLAIN");
623         }
624
625         cprintf("(");
626         imap_strout(cbmaintype);
627         cprintf(" ");
628         imap_strout(cbsubtype);
629         cprintf(" ");
630
631         cprintf("(\"CHARSET\" \"US-ASCII\"");
632
633         if (name != NULL) if (strlen(name)>0) {
634                 cprintf(" \"NAME\" ");
635                 imap_strout(name);
636         }
637
638         cprintf(") ");
639
640         cprintf("NIL ");        /* Body ID */
641         cprintf("NIL ");        /* Body description */
642
643         if (encoding != NULL) if (strlen(encoding) > 0)  have_encoding = 1;
644         if (have_encoding) {
645                 imap_strout(encoding);
646         }
647         else {
648                 imap_strout("7BIT");
649         }
650         cprintf(" ");
651
652         /* The next field is the size of the part in bytes. */
653         cprintf("%ld ", (long)length);  /* bytes */
654
655         /* The next field is the number of lines in the part, if and only
656          * if the part is TEXT.  Crispin is a fscking idiot.
657          */
658         if (!strcasecmp(cbmaintype, "TEXT")) {
659                 if (length) for (i=0; i<length; ++i) {
660                         if (((char *)content)[i] == '\n') ++lines;
661                 }
662                 cprintf("%d ", lines);
663         }
664
665         /* More of Crispin being a fscking idiot */
666         if ((!strcasecmp(cbmaintype, "MESSAGE"))
667            && (!strcasecmp(cbsubtype, "RFC822"))) {
668                 /* FIXME
669                      A body type of type MESSAGE and subtype RFC822
670                      contains, immediately after the basic fields, the
671                      envelope structure, body structure, and size in
672                      text lines of the encapsulated message.
673                 */
674         }
675
676         /* MD5 value of body part; we can get away with NIL'ing this */
677         cprintf("NIL ");
678
679         /* Disposition */
680         if (disp == NULL) {
681                 cprintf("NIL");
682         }
683         else if (strlen(disp) == 0) {
684                 cprintf("NIL");
685         }
686         else {
687                 cprintf("(");
688                 imap_strout(disp);
689                 if (filename != NULL) if (strlen(filename)>0) {
690                         cprintf(" (\"FILENAME\" ");
691                         imap_strout(filename);
692                         cprintf(")");
693                 }
694                 cprintf(")");
695         }
696
697         /* Body language (not defined yet) */
698         cprintf(" NIL)");
699 }
700
701
702
703 /*
704  * Spew the BODYSTRUCTURE data for a message.  (Do you need a silencer if
705  * you're going to shoot a MIME?  Do you need a reason to shoot Mark Crispin?
706  * No, and no.)
707  *
708  */
709 void imap_fetch_bodystructure (long msgnum, char *item,
710                 struct CtdlMessage *msg) {
711         FILE *tmp;
712         char buf[1024];
713         long lines = 0L;
714         long start_of_body = 0L;
715         long body_bytes = 0L;
716
717         /* For non-RFC822 (ordinary Citadel) messages, this is short and
718          * sweet...
719          */
720         if (msg->cm_format_type != FMT_RFC822) {
721
722                 /* *sigh* We have to RFC822-format the message just to be able
723                  * to measure it.
724                  */
725                 tmp = tmpfile();
726                 if (tmp == NULL) return;
727                 CtdlRedirectOutput(tmp, -1);
728                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
729                 CtdlRedirectOutput(NULL, -1);
730
731                 rewind(tmp);
732                 while (fgets(buf, sizeof buf, tmp) != NULL) {
733                         ++lines;
734                         if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
735                                 start_of_body = ftell(tmp);
736                         }
737                 }
738                 body_bytes = ftell(tmp) - start_of_body;
739                 fclose(tmp);
740
741                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
742                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
743                         "\"7BIT\" %ld %ld)", body_bytes, lines);
744
745                 return;
746         }
747
748         /* For messages already stored in RFC822 format, we have to parse. */
749         cprintf("BODYSTRUCTURE ");
750         mime_parser(msg->cm_fields['M'],
751                         NULL,
752                         *imap_fetch_bodystructure_part, /* part */
753                         *imap_fetch_bodystructure_pre,  /* pre-multi */
754                         *imap_fetch_bodystructure_post, /* post-multi */
755                         NULL,
756                         1);     /* don't decode -- we want it as-is */
757 }
758
759
760
761
762
763
764 /*
765  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
766  * individual message, once it has been successfully loaded from disk.
767  */
768 void imap_do_fetch_msg(int seq, struct CtdlMessage *msg,
769                         int num_items, char **itemlist) {
770         int i;
771
772         cprintf("* %d FETCH (", seq);
773
774         for (i=0; i<num_items; ++i) {
775
776                 if (!strncasecmp(itemlist[i], "BODY[", 5)) {
777                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
778                                         0, msg);
779                 }
780                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
781                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
782                                         1, msg);
783                 }
784                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
785                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
786                                         itemlist[i], msg);
787                 }
788                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
789                         imap_fetch_envelope(IMAP->msgids[seq-1], msg);
790                 }
791                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
792                         imap_fetch_flags(seq-1);
793                 }
794                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
795                         imap_fetch_internaldate(msg);
796                 }
797                 else if (!strcasecmp(itemlist[i], "RFC822")) {
798                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
799                 }
800                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
801                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
802                 }
803                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
804                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
805                 }
806                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
807                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
808                 }
809                 else if (!strcasecmp(itemlist[i], "UID")) {
810                         imap_fetch_uid(seq);
811                 }
812
813                 if (i != num_items-1) cprintf(" ");
814         }
815
816         cprintf(")\r\n");
817 }
818
819
820
821 /*
822  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
823  * validated and boiled down the request a bit.
824  */
825 void imap_do_fetch(int num_items, char **itemlist) {
826         int i;
827         struct CtdlMessage *msg;
828
829         if (IMAP->num_msgs > 0)
830          for (i = 0; i < IMAP->num_msgs; ++i)
831           if (IMAP->flags[i] & IMAP_SELECTED) {
832                 msg = CtdlFetchMessage(IMAP->msgids[i]);
833                 if (msg != NULL) {
834                         imap_do_fetch_msg(i+1, msg, num_items, itemlist);
835                         CtdlFreeMessage(msg);
836                 }
837                 else {
838                         cprintf("* %d FETCH <internal error>\r\n", i+1);
839                 }
840         }
841 }
842
843
844
845 /*
846  * Back end for imap_handle_macros()
847  * Note that this function *only* looks at the beginning of the string.  It
848  * is not a generic search-and-replace function.
849  */
850 void imap_macro_replace(char *str, char *find, char *replace) {
851         char holdbuf[1024];
852
853         if (!strncasecmp(str, find, strlen(find))) {
854                 if (str[strlen(find)]==' ') {
855                         strcpy(holdbuf, &str[strlen(find)+1]);
856                         strcpy(str, replace);
857                         strcat(str, " ");
858                         strcat(str, holdbuf);
859                 }
860                 if (str[strlen(find)]==0) {
861                         strcpy(holdbuf, &str[strlen(find)+1]);
862                         strcpy(str, replace);
863                 }
864         }
865 }
866
867
868
869 /*
870  * Handle macros embedded in FETCH data items.
871  * (What the heck are macros doing in a wire protocol?  Are we trying to save
872  * the computer at the other end the trouble of typing a lot of characters?)
873  */
874 void imap_handle_macros(char *str) {
875         int i;
876         int nest = 0;
877
878         for (i=0; i<strlen(str); ++i) {
879                 if (str[i]=='(') ++nest;
880                 if (str[i]=='[') ++nest;
881                 if (str[i]=='<') ++nest;
882                 if (str[i]=='{') ++nest;
883                 if (str[i]==')') --nest;
884                 if (str[i]==']') --nest;
885                 if (str[i]=='>') --nest;
886                 if (str[i]=='}') --nest;
887
888                 if (nest <= 0) {
889                         imap_macro_replace(&str[i],
890                                 "ALL",
891                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
892                         );
893                         imap_macro_replace(&str[i],
894                                 "BODY",
895                                 "BODYSTRUCTURE"
896                         );
897                         imap_macro_replace(&str[i],
898                                 "FAST",
899                                 "FLAGS INTERNALDATE RFC822.SIZE"
900                         );
901                         imap_macro_replace(&str[i],
902                                 "FULL",
903                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
904                         );
905                 }
906         }
907 }
908
909
910 /*
911  * Break out the data items requested, possibly a parenthesized list.
912  * Returns the number of data items, or -1 if the list is invalid.
913  * NOTE: this function alters the string it is fed, and uses it as a buffer
914  * to hold the data for the pointers it returns.
915  */
916 int imap_extract_data_items(char **argv, char *items) {
917         int num_items = 0;
918         int nest = 0;
919         int i, initial_len;
920         char *start;
921
922         /* Convert all whitespace to ordinary space characters. */
923         for (i=0; i<strlen(items); ++i) {
924                 if (isspace(items[i])) items[i]=' ';
925         }
926
927         /* Strip leading and trailing whitespace, then strip leading and
928          * trailing parentheses if it's a list
929          */
930         striplt(items);
931         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
932                 items[strlen(items)-1] = 0;
933                 strcpy(items, &items[1]);
934                 striplt(items);
935         }
936
937         /* Parse any macro data items */
938         imap_handle_macros(items);
939
940         /*
941          * Now break out the data items.  We throw in one trailing space in
942          * order to avoid having to break out the last one manually.
943          */
944         strcat(items, " ");
945         start = items;
946         initial_len = strlen(items);
947         for (i=0; i<initial_len; ++i) {
948                 if (items[i]=='(') ++nest;
949                 if (items[i]=='[') ++nest;
950                 if (items[i]=='<') ++nest;
951                 if (items[i]=='{') ++nest;
952                 if (items[i]==')') --nest;
953                 if (items[i]==']') --nest;
954                 if (items[i]=='>') --nest;
955                 if (items[i]=='}') --nest;
956
957                 if (nest <= 0) if (items[i]==' ') {
958                         items[i] = 0;
959                         argv[num_items++] = start;
960                         start = &items[i+1];
961                 }
962         }
963
964         return(num_items);
965
966 }
967
968
969 /*
970  * One particularly hideous aspect of IMAP is that we have to allow the client
971  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
972  * handles this by setting the IMAP_SELECTED flag for each message specified in
973  * the ranges/sets, then looping through the message array, outputting messages
974  * with the flag set.  We don't bother returning an error if an out-of-range
975  * number is specified (we just return quietly) because any client braindead
976  * enough to request a bogus message number isn't going to notice the
977  * difference anyway.
978  *
979  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
980  * message included in the specified range.
981  *
982  * Set is_uid to 1 to fetch by UID instead of sequence number.
983  */
984 void imap_pick_range(char *supplied_range, int is_uid) {
985         int i;
986         int num_sets;
987         int s;
988         char setstr[SIZ], lostr[SIZ], histr[SIZ];       /* was 1024 */
989         int lo, hi;
990         char actual_range[SIZ];
991
992         /* 
993          * Handle the "ALL" macro
994          */
995         if (!strcasecmp(supplied_range, "ALL")) {
996                 safestrncpy(actual_range, "1:*", sizeof actual_range);
997         }
998         else {
999                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1000         }
1001
1002         /*
1003          * Clear out the IMAP_SELECTED flags for all messages.
1004          */
1005         for (i = 0; i < IMAP->num_msgs; ++i) {
1006                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1007         }
1008
1009         /*
1010          * Now set it for all specified messages.
1011          */
1012         num_sets = num_tokens(actual_range, ',');
1013         for (s=0; s<num_sets; ++s) {
1014                 extract_token(setstr, actual_range, s, ',');
1015
1016                 extract_token(lostr, setstr, 0, ':');
1017                 if (num_tokens(setstr, ':') >= 2) {
1018                         extract_token(histr, setstr, 1, ':');
1019                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1020                 } 
1021                 else {
1022                         strcpy(histr, lostr);
1023                 }
1024                 lo = atoi(lostr);
1025                 hi = atoi(histr);
1026
1027                 /* Loop through the array, flipping bits where appropriate */
1028                 for (i = 1; i <= IMAP->num_msgs; ++i) {
1029                         if (is_uid) {   /* fetch by sequence number */
1030                                 if ( (IMAP->msgids[i-1]>=lo)
1031                                    && (IMAP->msgids[i-1]<=hi)) {
1032                                         IMAP->flags[i-1] =
1033                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1034                                 }
1035                         }
1036                         else {          /* fetch by uid */
1037                                 if ( (i>=lo) && (i<=hi)) {
1038                                         IMAP->flags[i-1] =
1039                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1040                                 }
1041                         }
1042                 }
1043         }
1044
1045 }
1046
1047
1048
1049 /*
1050  * This function is called by the main command loop.
1051  */
1052 void imap_fetch(int num_parms, char *parms[]) {
1053         char items[SIZ];        /* was 1024 */
1054         char *itemlist[SIZ];
1055         int num_items;
1056         int i;
1057
1058         if (num_parms < 4) {
1059                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1060                 return;
1061         }
1062
1063         imap_pick_range(parms[2], 0);
1064
1065         strcpy(items, "");
1066         for (i=3; i<num_parms; ++i) {
1067                 strcat(items, parms[i]);
1068                 if (i < (num_parms-1)) strcat(items, " ");
1069         }
1070
1071         num_items = imap_extract_data_items(itemlist, items);
1072         if (num_items < 1) {
1073                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1074                 return;
1075         }
1076
1077         imap_do_fetch(num_items, itemlist);
1078         cprintf("%s OK FETCH completed\r\n", parms[0]);
1079 }
1080
1081 /*
1082  * This function is called by the main command loop.
1083  */
1084 void imap_uidfetch(int num_parms, char *parms[]) {
1085         char items[SIZ];        /* was 1024 */
1086         char *itemlist[SIZ];
1087         int num_items;
1088         int i;
1089         int have_uid_item = 0;
1090
1091         if (num_parms < 5) {
1092                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1093                 return;
1094         }
1095
1096         imap_pick_range(parms[3], 1);
1097
1098         strcpy(items, "");
1099         for (i=4; i<num_parms; ++i) {
1100                 strcat(items, parms[i]);
1101                 if (i < (num_parms-1)) strcat(items, " ");
1102         }
1103
1104         num_items = imap_extract_data_items(itemlist, items);
1105         if (num_items < 1) {
1106                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1107                 return;
1108         }
1109
1110         /* If the "UID" item was not included, we include it implicitly
1111          * because this is a UID FETCH command
1112          */
1113         for (i=0; i<num_items; ++i) {
1114                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1115         }
1116         if (have_uid_item == 0) itemlist[num_items++] = "UID";
1117
1118         imap_do_fetch(num_items, itemlist);
1119         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1120 }
1121
1122