]> code.citadel.org Git - citadel.git/blob - citadel/imap_fetch.c
* IMAP FETCH BODY<xxx.yyy> was outputting the offset and length in the
[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                         buffer_output();
786                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
787                                         itemlist[i], msg);
788                         unbuffer_output();
789                 }
790                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
791                         buffer_output();
792                         imap_fetch_envelope(IMAP->msgids[seq-1], msg);
793                         unbuffer_output();
794                 }
795                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
796                         buffer_output();
797                         imap_fetch_flags(seq-1);
798                         unbuffer_output();
799                 }
800                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
801                         imap_fetch_internaldate(msg);
802                 }
803                 else if (!strcasecmp(itemlist[i], "RFC822")) {
804                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
805                 }
806                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
807                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
808                 }
809                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
810                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
811                 }
812                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
813                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
814                 }
815                 else if (!strcasecmp(itemlist[i], "UID")) {
816                         imap_fetch_uid(seq);
817                 }
818
819                 if (i != num_items-1) cprintf(" ");
820         }
821
822         cprintf(")\r\n");
823 }
824
825
826
827 /*
828  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
829  * validated and boiled down the request a bit.
830  */
831 void imap_do_fetch(int num_items, char **itemlist) {
832         int i;
833         struct CtdlMessage *msg;
834
835         if (IMAP->num_msgs > 0)
836          for (i = 0; i < IMAP->num_msgs; ++i)
837           if (IMAP->flags[i] & IMAP_SELECTED) {
838                 msg = CtdlFetchMessage(IMAP->msgids[i]);
839                 if (msg != NULL) {
840                         imap_do_fetch_msg(i+1, msg, num_items, itemlist);
841                         CtdlFreeMessage(msg);
842                 }
843                 else {
844                         cprintf("* %d FETCH <internal error>\r\n", i+1);
845                 }
846         }
847 }
848
849
850
851 /*
852  * Back end for imap_handle_macros()
853  * Note that this function *only* looks at the beginning of the string.  It
854  * is not a generic search-and-replace function.
855  */
856 void imap_macro_replace(char *str, char *find, char *replace) {
857         char holdbuf[1024];
858
859         if (!strncasecmp(str, find, strlen(find))) {
860                 if (str[strlen(find)]==' ') {
861                         strcpy(holdbuf, &str[strlen(find)+1]);
862                         strcpy(str, replace);
863                         strcat(str, " ");
864                         strcat(str, holdbuf);
865                 }
866                 if (str[strlen(find)]==0) {
867                         strcpy(holdbuf, &str[strlen(find)+1]);
868                         strcpy(str, replace);
869                 }
870         }
871 }
872
873
874
875 /*
876  * Handle macros embedded in FETCH data items.
877  * (What the heck are macros doing in a wire protocol?  Are we trying to save
878  * the computer at the other end the trouble of typing a lot of characters?)
879  */
880 void imap_handle_macros(char *str) {
881         int i;
882         int nest = 0;
883
884         for (i=0; i<strlen(str); ++i) {
885                 if (str[i]=='(') ++nest;
886                 if (str[i]=='[') ++nest;
887                 if (str[i]=='<') ++nest;
888                 if (str[i]=='{') ++nest;
889                 if (str[i]==')') --nest;
890                 if (str[i]==']') --nest;
891                 if (str[i]=='>') --nest;
892                 if (str[i]=='}') --nest;
893
894                 if (nest <= 0) {
895                         imap_macro_replace(&str[i],
896                                 "ALL",
897                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
898                         );
899                         imap_macro_replace(&str[i],
900                                 "BODY",
901                                 "BODYSTRUCTURE"
902                         );
903                         imap_macro_replace(&str[i],
904                                 "FAST",
905                                 "FLAGS INTERNALDATE RFC822.SIZE"
906                         );
907                         imap_macro_replace(&str[i],
908                                 "FULL",
909                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
910                         );
911                 }
912         }
913 }
914
915
916 /*
917  * Break out the data items requested, possibly a parenthesized list.
918  * Returns the number of data items, or -1 if the list is invalid.
919  * NOTE: this function alters the string it is fed, and uses it as a buffer
920  * to hold the data for the pointers it returns.
921  */
922 int imap_extract_data_items(char **argv, char *items) {
923         int num_items = 0;
924         int nest = 0;
925         int i, initial_len;
926         char *start;
927
928         /* Convert all whitespace to ordinary space characters. */
929         for (i=0; i<strlen(items); ++i) {
930                 if (isspace(items[i])) items[i]=' ';
931         }
932
933         /* Strip leading and trailing whitespace, then strip leading and
934          * trailing parentheses if it's a list
935          */
936         striplt(items);
937         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
938                 items[strlen(items)-1] = 0;
939                 strcpy(items, &items[1]);
940                 striplt(items);
941         }
942
943         /* Parse any macro data items */
944         imap_handle_macros(items);
945
946         /*
947          * Now break out the data items.  We throw in one trailing space in
948          * order to avoid having to break out the last one manually.
949          */
950         strcat(items, " ");
951         start = items;
952         initial_len = strlen(items);
953         for (i=0; i<initial_len; ++i) {
954                 if (items[i]=='(') ++nest;
955                 if (items[i]=='[') ++nest;
956                 if (items[i]=='<') ++nest;
957                 if (items[i]=='{') ++nest;
958                 if (items[i]==')') --nest;
959                 if (items[i]==']') --nest;
960                 if (items[i]=='>') --nest;
961                 if (items[i]=='}') --nest;
962
963                 if (nest <= 0) if (items[i]==' ') {
964                         items[i] = 0;
965                         argv[num_items++] = start;
966                         start = &items[i+1];
967                 }
968         }
969
970         return(num_items);
971
972 }
973
974
975 /*
976  * One particularly hideous aspect of IMAP is that we have to allow the client
977  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
978  * handles this by setting the IMAP_SELECTED flag for each message specified in
979  * the ranges/sets, then looping through the message array, outputting messages
980  * with the flag set.  We don't bother returning an error if an out-of-range
981  * number is specified (we just return quietly) because any client braindead
982  * enough to request a bogus message number isn't going to notice the
983  * difference anyway.
984  *
985  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
986  * message included in the specified range.
987  *
988  * Set is_uid to 1 to fetch by UID instead of sequence number.
989  */
990 void imap_pick_range(char *supplied_range, int is_uid) {
991         int i;
992         int num_sets;
993         int s;
994         char setstr[SIZ], lostr[SIZ], histr[SIZ];       /* was 1024 */
995         int lo, hi;
996         char actual_range[SIZ];
997
998         /* 
999          * Handle the "ALL" macro
1000          */
1001         if (!strcasecmp(supplied_range, "ALL")) {
1002                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1003         }
1004         else {
1005                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1006         }
1007
1008         /*
1009          * Clear out the IMAP_SELECTED flags for all messages.
1010          */
1011         for (i = 0; i < IMAP->num_msgs; ++i) {
1012                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1013         }
1014
1015         /*
1016          * Now set it for all specified messages.
1017          */
1018         num_sets = num_tokens(actual_range, ',');
1019         for (s=0; s<num_sets; ++s) {
1020                 extract_token(setstr, actual_range, s, ',');
1021
1022                 extract_token(lostr, setstr, 0, ':');
1023                 if (num_tokens(setstr, ':') >= 2) {
1024                         extract_token(histr, setstr, 1, ':');
1025                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1026                 } 
1027                 else {
1028                         strcpy(histr, lostr);
1029                 }
1030                 lo = atoi(lostr);
1031                 hi = atoi(histr);
1032
1033                 /* Loop through the array, flipping bits where appropriate */
1034                 for (i = 1; i <= IMAP->num_msgs; ++i) {
1035                         if (is_uid) {   /* fetch by sequence number */
1036                                 if ( (IMAP->msgids[i-1]>=lo)
1037                                    && (IMAP->msgids[i-1]<=hi)) {
1038                                         IMAP->flags[i-1] =
1039                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1040                                 }
1041                         }
1042                         else {          /* fetch by uid */
1043                                 if ( (i>=lo) && (i<=hi)) {
1044                                         IMAP->flags[i-1] =
1045                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1046                                 }
1047                         }
1048                 }
1049         }
1050
1051 }
1052
1053
1054
1055 /*
1056  * This function is called by the main command loop.
1057  */
1058 void imap_fetch(int num_parms, char *parms[]) {
1059         char items[SIZ];        /* was 1024 */
1060         char *itemlist[SIZ];
1061         int num_items;
1062         int i;
1063
1064         if (num_parms < 4) {
1065                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1066                 return;
1067         }
1068
1069         imap_pick_range(parms[2], 0);
1070
1071         strcpy(items, "");
1072         for (i=3; i<num_parms; ++i) {
1073                 strcat(items, parms[i]);
1074                 if (i < (num_parms-1)) strcat(items, " ");
1075         }
1076
1077         num_items = imap_extract_data_items(itemlist, items);
1078         if (num_items < 1) {
1079                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1080                 return;
1081         }
1082
1083         imap_do_fetch(num_items, itemlist);
1084         cprintf("%s OK FETCH completed\r\n", parms[0]);
1085 }
1086
1087 /*
1088  * This function is called by the main command loop.
1089  */
1090 void imap_uidfetch(int num_parms, char *parms[]) {
1091         char items[SIZ];        /* was 1024 */
1092         char *itemlist[SIZ];
1093         int num_items;
1094         int i;
1095         int have_uid_item = 0;
1096
1097         if (num_parms < 5) {
1098                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1099                 return;
1100         }
1101
1102         imap_pick_range(parms[3], 1);
1103
1104         strcpy(items, "");
1105         for (i=4; i<num_parms; ++i) {
1106                 strcat(items, parms[i]);
1107                 if (i < (num_parms-1)) strcat(items, " ");
1108         }
1109
1110         num_items = imap_extract_data_items(itemlist, items);
1111         if (num_items < 1) {
1112                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1113                 return;
1114         }
1115
1116         /* If the "UID" item was not included, we include it implicitly
1117          * because this is a UID FETCH command
1118          */
1119         for (i=0; i<num_items; ++i) {
1120                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1121         }
1122         if (have_uid_item == 0) itemlist[num_items++] = "UID";
1123
1124         imap_do_fetch(num_items, itemlist);
1125         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1126 }
1127
1128