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