]> code.citadel.org Git - citadel.git/blob - citadel/imap_fetch.c
* Tweaks to msgbase.c and imap_fetch.c to fix slightly incorrect byte counts
[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         extract_token(subtype, cbtype, 1, '/');
576         imap_strout(subtype);
577         cprintf(")");
578 }
579
580
581
582 /*
583  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
584  *
585  */
586 void imap_fetch_bodystructure_part(
587                 char *name, char *filename, char *partnum, char *disp,
588                 void *content, char *cbtype, size_t length, char *encoding,
589                 void *cbuserdata
590                 ) {
591
592         char buf[SIZ];
593         int have_cbtype = 0;
594         int have_encoding = 0;
595
596         cprintf("(");
597
598         if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
599
600         if (have_cbtype) {
601                 extract_token(buf, cbtype, 0, '/');
602                 imap_strout(buf);
603                 cprintf(" ");
604                 extract_token(buf, cbtype, 1, '/');
605                 imap_strout(buf);
606                 cprintf(" ");
607         }
608         else {
609                 cprintf("\"TEXT\" \"PLAIN\" ");
610         }
611
612         cprintf("(\"CHARSET\" \"US-ASCII\"");
613
614         if (name != NULL) if (strlen(name)>0) {
615                 cprintf(" \"NAME\" ");
616                 imap_strout(name);
617         }
618
619         if (filename != NULL) if (strlen(filename)>0) {
620                 cprintf(" \"FILENAME\" ");
621                 imap_strout(name);
622         }
623
624         cprintf(") ");
625
626         cprintf("NIL NIL ");
627
628         if (encoding != NULL) if (strlen(encoding) > 0)  have_encoding = 1;
629
630         if (have_encoding) {
631                 imap_strout(encoding);
632         }
633         else {
634                 imap_strout("7BIT");
635         }
636         cprintf(" ");
637
638         cprintf("%ld ", (long)length);  /* bytes */
639         cprintf("NIL) ");               /* lines */
640 }
641
642
643
644 /*
645  * Spew the BODYSTRUCTURE data for a message.  (Do you need a silencer if
646  * you're going to shoot a MIME?  Do you need a reason to shoot Mark Crispin?
647  * No, and no.)
648  *
649  */
650 void imap_fetch_bodystructure (long msgnum, char *item,
651                 struct CtdlMessage *msg) {
652         FILE *tmp;
653         char buf[1024];
654         long lines = 0L;
655         long start_of_body = 0L;
656         long body_bytes = 0L;
657
658         /* For non-RFC822 (ordinary Citadel) messages, this is short and
659          * sweet...
660          */
661         if (msg->cm_format_type != FMT_RFC822) {
662
663                 /* *sigh* We have to RFC822-format the message just to be able
664                  * to measure it.
665                  */
666                 tmp = tmpfile();
667                 if (tmp == NULL) return;
668                 CtdlRedirectOutput(tmp, -1);
669                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
670                 CtdlRedirectOutput(NULL, -1);
671
672                 rewind(tmp);
673                 while (fgets(buf, sizeof buf, tmp) != NULL) {
674                         ++lines;
675                         if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
676                                 start_of_body = ftell(tmp);
677                         }
678                 }
679                 body_bytes = ftell(tmp) - start_of_body;
680                 fclose(tmp);
681
682                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
683                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
684                         "\"7BIT\" %ld %ld)", body_bytes, lines);
685
686                 return;
687         }
688
689         /* For messages already stored in RFC822 format, we have to parse. */
690         cprintf("BODYSTRUCTURE ");
691         mime_parser(msg->cm_fields['M'],
692                         NULL,
693                         *imap_fetch_bodystructure_part, /* part */
694                         *imap_fetch_bodystructure_pre,  /* pre-multi */
695                         *imap_fetch_bodystructure_post, /* post-multi */
696                         NULL,
697                         1);     /* don't decode -- we want it as-is */
698 }
699
700
701
702
703
704
705 /*
706  * imap_do_fetch() calls imap_do_fetch_msg() to output the deta of an
707  * individual message, once it has been successfully loaded from disk.
708  */
709 void imap_do_fetch_msg(int seq, struct CtdlMessage *msg,
710                         int num_items, char **itemlist) {
711         int i;
712
713         cprintf("* %d FETCH (", seq);
714
715         for (i=0; i<num_items; ++i) {
716
717                 if (!strncasecmp(itemlist[i], "BODY[", 5)) {
718                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
719                                         0, msg);
720                 }
721                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
722                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
723                                         1, msg);
724                 }
725                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
726                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
727                                         itemlist[i], msg);
728                 }
729                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
730                         imap_fetch_envelope(IMAP->msgids[seq-1], msg);
731                 }
732                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
733                         imap_fetch_flags(seq-1);
734                 }
735                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
736                         imap_fetch_internaldate(msg);
737                 }
738                 else if (!strcasecmp(itemlist[i], "RFC822")) {
739                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
740                 }
741                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
742                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
743                 }
744                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
745                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
746                 }
747                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
748                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
749                 }
750                 else if (!strcasecmp(itemlist[i], "UID")) {
751                         imap_fetch_uid(seq);
752                 }
753
754                 if (i != num_items-1) cprintf(" ");
755         }
756
757         cprintf(")\r\n");
758 }
759
760
761
762 /*
763  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
764  * validated and boiled down the request a bit.
765  */
766 void imap_do_fetch(int num_items, char **itemlist) {
767         int i;
768         struct CtdlMessage *msg;
769
770         if (IMAP->num_msgs > 0)
771          for (i = 0; i < IMAP->num_msgs; ++i)
772           if (IMAP->flags[i] & IMAP_SELECTED) {
773                 msg = CtdlFetchMessage(IMAP->msgids[i]);
774                 if (msg != NULL) {
775                         imap_do_fetch_msg(i+1, msg, num_items, itemlist);
776                         CtdlFreeMessage(msg);
777                 }
778                 else {
779                         cprintf("* %d FETCH <internal error>\r\n", i+1);
780                 }
781         }
782 }
783
784
785
786 /*
787  * Back end for imap_handle_macros()
788  * Note that this function *only* looks at the beginning of the string.  It
789  * is not a generic search-and-replace function.
790  */
791 void imap_macro_replace(char *str, char *find, char *replace) {
792         char holdbuf[1024];
793
794         if (!strncasecmp(str, find, strlen(find))) {
795                 if (str[strlen(find)]==' ') {
796                         strcpy(holdbuf, &str[strlen(find)+1]);
797                         strcpy(str, replace);
798                         strcat(str, " ");
799                         strcat(str, holdbuf);
800                 }
801                 if (str[strlen(find)]==0) {
802                         strcpy(holdbuf, &str[strlen(find)+1]);
803                         strcpy(str, replace);
804                 }
805         }
806 }
807
808
809
810 /*
811  * Handle macros embedded in FETCH data items.
812  * (What the heck are macros doing in a wire protocol?  Are we trying to save
813  * the computer at the other end the trouble of typing a lot of characters?)
814  */
815 void imap_handle_macros(char *str) {
816         int i;
817         int nest = 0;
818
819         for (i=0; i<strlen(str); ++i) {
820                 if (str[i]=='(') ++nest;
821                 if (str[i]=='[') ++nest;
822                 if (str[i]=='<') ++nest;
823                 if (str[i]=='{') ++nest;
824                 if (str[i]==')') --nest;
825                 if (str[i]==']') --nest;
826                 if (str[i]=='>') --nest;
827                 if (str[i]=='}') --nest;
828
829                 if (nest <= 0) {
830                         imap_macro_replace(&str[i],
831                                 "ALL",
832                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
833                         );
834                         imap_macro_replace(&str[i],
835                                 "BODY",
836                                 "BODYSTRUCTURE"
837                         );
838                         imap_macro_replace(&str[i],
839                                 "FAST",
840                                 "FLAGS INTERNALDATE RFC822.SIZE"
841                         );
842                         imap_macro_replace(&str[i],
843                                 "FULL",
844                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
845                         );
846                 }
847         }
848 }
849
850
851 /*
852  * Break out the data items requested, possibly a parenthesized list.
853  * Returns the number of data items, or -1 if the list is invalid.
854  * NOTE: this function alters the string it is fed, and uses it as a buffer
855  * to hold the data for the pointers it returns.
856  */
857 int imap_extract_data_items(char **argv, char *items) {
858         int num_items = 0;
859         int nest = 0;
860         int i, initial_len;
861         char *start;
862
863         /* Convert all whitespace to ordinary space characters. */
864         for (i=0; i<strlen(items); ++i) {
865                 if (isspace(items[i])) items[i]=' ';
866         }
867
868         /* Strip leading and trailing whitespace, then strip leading and
869          * trailing parentheses if it's a list
870          */
871         striplt(items);
872         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
873                 items[strlen(items)-1] = 0;
874                 strcpy(items, &items[1]);
875                 striplt(items);
876         }
877
878         /* Parse any macro data items */
879         imap_handle_macros(items);
880
881         /*
882          * Now break out the data items.  We throw in one trailing space in
883          * order to avoid having to break out the last one manually.
884          */
885         strcat(items, " ");
886         start = items;
887         initial_len = strlen(items);
888         for (i=0; i<initial_len; ++i) {
889                 if (items[i]=='(') ++nest;
890                 if (items[i]=='[') ++nest;
891                 if (items[i]=='<') ++nest;
892                 if (items[i]=='{') ++nest;
893                 if (items[i]==')') --nest;
894                 if (items[i]==']') --nest;
895                 if (items[i]=='>') --nest;
896                 if (items[i]=='}') --nest;
897
898                 if (nest <= 0) if (items[i]==' ') {
899                         items[i] = 0;
900                         argv[num_items++] = start;
901                         start = &items[i+1];
902                 }
903         }
904
905         return(num_items);
906
907 }
908
909
910 /*
911  * One particularly hideous aspect of IMAP is that we have to allow the client
912  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
913  * handles this by setting the IMAP_SELECTED flag for each message specified in
914  * the ranges/sets, then looping through the message array, outputting messages
915  * with the flag set.  We don't bother returning an error if an out-of-range
916  * number is specified (we just return quietly) because any client braindead
917  * enough to request a bogus message number isn't going to notice the
918  * difference anyway.
919  *
920  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
921  * message included in the specified range.
922  *
923  * Set is_uid to 1 to fetch by UID instead of sequence number.
924  */
925 void imap_pick_range(char *supplied_range, int is_uid) {
926         int i;
927         int num_sets;
928         int s;
929         char setstr[SIZ], lostr[SIZ], histr[SIZ];       /* was 1024 */
930         int lo, hi;
931         char actual_range[SIZ];
932
933         /* 
934          * Handle the "ALL" macro
935          */
936         if (!strcasecmp(supplied_range, "ALL")) {
937                 safestrncpy(actual_range, "1:*", sizeof actual_range);
938         }
939         else {
940                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
941         }
942
943         /*
944          * Clear out the IMAP_SELECTED flags for all messages.
945          */
946         for (i = 0; i < IMAP->num_msgs; ++i) {
947                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
948         }
949
950         /*
951          * Now set it for all specified messages.
952          */
953         num_sets = num_tokens(actual_range, ',');
954         for (s=0; s<num_sets; ++s) {
955                 extract_token(setstr, actual_range, s, ',');
956
957                 extract_token(lostr, setstr, 0, ':');
958                 if (num_tokens(setstr, ':') >= 2) {
959                         extract_token(histr, setstr, 1, ':');
960                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
961                 } 
962                 else {
963                         strcpy(histr, lostr);
964                 }
965                 lo = atoi(lostr);
966                 hi = atoi(histr);
967
968                 /* Loop through the array, flipping bits where appropriate */
969                 for (i = 1; i <= IMAP->num_msgs; ++i) {
970                         if (is_uid) {   /* fetch by sequence number */
971                                 if ( (IMAP->msgids[i-1]>=lo)
972                                    && (IMAP->msgids[i-1]<=hi)) {
973                                         IMAP->flags[i-1] =
974                                                 IMAP->flags[i-1] | IMAP_SELECTED;
975                                 }
976                         }
977                         else {          /* fetch by uid */
978                                 if ( (i>=lo) && (i<=hi)) {
979                                         IMAP->flags[i-1] =
980                                                 IMAP->flags[i-1] | IMAP_SELECTED;
981                                 }
982                         }
983                 }
984         }
985
986 }
987
988
989
990 /*
991  * This function is called by the main command loop.
992  */
993 void imap_fetch(int num_parms, char *parms[]) {
994         char items[SIZ];        /* was 1024 */
995         char *itemlist[SIZ];
996         int num_items;
997         int i;
998
999         if (num_parms < 4) {
1000                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1001                 return;
1002         }
1003
1004         imap_pick_range(parms[2], 0);
1005
1006         strcpy(items, "");
1007         for (i=3; i<num_parms; ++i) {
1008                 strcat(items, parms[i]);
1009                 if (i < (num_parms-1)) strcat(items, " ");
1010         }
1011
1012         num_items = imap_extract_data_items(itemlist, items);
1013         if (num_items < 1) {
1014                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1015                 return;
1016         }
1017
1018         imap_do_fetch(num_items, itemlist);
1019         cprintf("%s OK FETCH completed\r\n", parms[0]);
1020 }
1021
1022 /*
1023  * This function is called by the main command loop.
1024  */
1025 void imap_uidfetch(int num_parms, char *parms[]) {
1026         char items[SIZ];        /* was 1024 */
1027         char *itemlist[SIZ];
1028         int num_items;
1029         int i;
1030         int have_uid_item = 0;
1031
1032         if (num_parms < 5) {
1033                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1034                 return;
1035         }
1036
1037         imap_pick_range(parms[3], 1);
1038
1039         strcpy(items, "");
1040         for (i=4; i<num_parms; ++i) {
1041                 strcat(items, parms[i]);
1042                 if (i < (num_parms-1)) strcat(items, " ");
1043         }
1044
1045         num_items = imap_extract_data_items(itemlist, items);
1046         if (num_items < 1) {
1047                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1048                 return;
1049         }
1050
1051         /* If the "UID" item was not included, we include it implicitly
1052          * because this is a UID FETCH command
1053          */
1054         for (i=0; i<num_items; ++i) {
1055                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1056         }
1057         if (have_uid_item == 0) itemlist[num_items++] = "UID";
1058
1059         imap_do_fetch(num_items, itemlist);
1060         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1061 }
1062
1063