4 * Implements the FETCH command in IMAP.
5 * This is a good example of the protocol's gratuitous complexity.
18 #include <sys/types.h>
20 #if TIME_WITH_SYS_TIME
21 # include <sys/time.h>
25 # include <sys/time.h>
37 #include "sysdep_decls.h"
38 #include "citserver.h"
41 #include "serv_extensions.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"
57 struct imap_fetch_part {
58 char desired_section[SIZ];
63 * Individual field functions for imap_do_fetch_msg() ...
66 void imap_fetch_uid(int seq) {
67 cprintf("UID %ld", IMAP->msgids[seq-1]);
70 void imap_fetch_flags(int seq) {
71 int num_flags_printed = 0;
73 if (IMAP->flags[seq] & IMAP_DELETED) {
74 if (num_flags_printed > 0) cprintf(" ");
78 if (IMAP->flags[seq] & IMAP_SEEN) {
79 if (num_flags_printed > 0) cprintf(" ");
83 if (IMAP->flags[seq] & IMAP_ANSWERED) {
84 if (num_flags_printed > 0) cprintf(" ");
85 cprintf("\\Answered");
88 if (IMAP->flags[seq] & IMAP_RECENT) {
89 if (num_flags_printed > 0) cprintf(" ");
96 void imap_fetch_internaldate(struct CtdlMessage *msg) {
100 if (msg->cm_fields['T'] != NULL) {
101 msgdate = atol(msg->cm_fields['T']);
104 msgdate = time(NULL);
107 datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
108 cprintf("INTERNALDATE \"%s\"", buf);
113 * Fetch RFC822-formatted messages.
115 * 'whichfmt' should be set to one of:
116 * "RFC822" entire message
117 * "RFC822.HEADER" headers only (with trailing blank line)
118 * "RFC822.SIZE" size of translated message
119 * "RFC822.TEXT" body only (without leading blank line)
121 void imap_fetch_rfc822(long msgnum, char *whichfmt) {
124 size_t headers_size, text_size, total_size;
125 size_t bytes_to_send;
127 /* Cache the most recent RFC822 FETCH because some clients like to
128 * fetch in pieces, and we don't want to have to go back to the
129 * message store for each piece.
131 if ((IMAP->cached_rfc822_data != NULL)
132 && (IMAP->cached_rfc822_msgnum == msgnum)) {
135 else if (IMAP->cached_rfc822_data != NULL) {
136 /* Some other message is cached -- free it */
137 free(IMAP->cached_rfc822_data);
138 IMAP->cached_rfc822_data = NULL;
139 IMAP->cached_rfc822_msgnum = (-1);
140 IMAP->cached_rfc822_len = 0;
143 /* At this point, we now can fetch and convert the message iff it's not
144 * the one we had cached.
146 if (IMAP->cached_rfc822_data == NULL) {
148 * Load the message into memory for translation & measurement
150 CC->redirect_buffer = malloc(SIZ);
151 CC->redirect_len = 0;
152 CC->redirect_alloc = SIZ;
153 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
154 IMAP->cached_rfc822_data = CC->redirect_buffer;
155 IMAP->cached_rfc822_len = CC->redirect_len;
156 IMAP->cached_rfc822_msgnum = msgnum;
157 CC->redirect_buffer = NULL;
158 CC->redirect_len = 0;
159 CC->redirect_alloc = 0;
163 * Now figure out where the headers/text break is. IMAP considers the
164 * intervening blank line to be part of the headers, not the text.
170 ptr = IMAP->cached_rfc822_data;
172 ptr = memreadline(ptr, buf, sizeof buf);
175 if (strlen(buf) == 0) {
176 headers_size = ptr - IMAP->cached_rfc822_data;
179 } while ( (headers_size == 0) && (ptr != NULL) );
181 total_size = IMAP->cached_rfc822_len;
182 text_size = total_size - headers_size;
184 lprintf(CTDL_DEBUG, "RFC822: headers=%d, text=%d, total=%d\n",
185 headers_size, text_size, total_size);
187 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
188 cprintf("RFC822.SIZE %d", total_size);
192 else if (!strcasecmp(whichfmt, "RFC822")) {
193 ptr = IMAP->cached_rfc822_data;
194 bytes_to_send = total_size;
197 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
198 ptr = IMAP->cached_rfc822_data;
199 bytes_to_send = headers_size;
202 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
203 ptr = &IMAP->cached_rfc822_data[headers_size];
204 bytes_to_send = text_size;
207 cprintf("%s {%d}\r\n", whichfmt, bytes_to_send);
208 client_write(ptr, bytes_to_send);
214 * Load a specific part of a message into the temp file to be output to a
215 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
216 * but we still can't handle "2.HEADER" (which might not be a problem, because
217 * we currently don't have the ability to break out nested RFC822's anyway).
219 * Note: mime_parser() was called with dont_decode set to 1, so we have the
220 * luxury of simply spewing without having to re-encode.
222 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
223 void *content, char *cbtype, size_t length, char *encoding,
226 struct imap_fetch_part *imfp;
229 imfp = (struct imap_fetch_part *)cbuserdata;
231 if (!strcasecmp(partnum, imfp->desired_section)) {
232 fwrite(content, length, 1, imfp->output_fp);
235 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
237 if (!strcasecmp(imfp->desired_section, mbuf2)) {
238 fprintf(imfp->output_fp, "Content-type: %s", cbtype);
239 if (strlen(name) > 0)
240 fprintf(imfp->output_fp, "; name=\"%s\"", name);
241 fprintf(imfp->output_fp, "\r\n");
242 if (strlen(encoding) > 0)
243 fprintf(imfp->output_fp,
244 "Content-Transfer-Encoding: %s\r\n", encoding);
245 if (strlen(encoding) > 0) {
246 fprintf(imfp->output_fp, "Content-Disposition: %s",
248 if (strlen(filename) > 0) {
249 fprintf(imfp->output_fp, "; filename=\"%s\"",
252 fprintf(imfp->output_fp, "\r\n");
254 fprintf(imfp->output_fp, "Content-Length: %ld\r\n", (long)length);
255 fprintf(imfp->output_fp, "\r\n");
263 * Called by imap_fetch_envelope() to output the "From" field.
264 * This is in its own function because its logic is kind of complex. We
265 * really need to make this suck less.
267 void imap_output_envelope_from(struct CtdlMessage *msg) {
268 char user[SIZ], node[SIZ], name[SIZ];
270 /* For anonymous messages, it's so easy! */
271 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
272 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
275 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
276 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
280 /* For everything else, we do stuff. */
281 cprintf("(("); /* open double-parens */
282 imap_strout(msg->cm_fields['A']); /* personal name */
283 cprintf(" NIL "); /* source route (not used) */
286 if (msg->cm_fields['F'] != NULL) {
287 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
288 imap_strout(user); /* mailbox name (user id) */
290 if (!strcasecmp(node, config.c_nodename)) {
291 imap_strout(config.c_fqdn);
294 imap_strout(node); /* host name */
298 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
300 imap_strout(msg->cm_fields['N']); /* host name */
303 cprintf(")) "); /* close double-parens */
309 * Output an envelope address (or set of addresses) in the official,
310 * convuluted, braindead format. (Note that we can't use this for
311 * the "From" address because its data may come from a number of different
312 * fields. But we can use it for "To" and possibly others.
314 void imap_output_envelope_addr(char *addr) {
315 char individual_addr[SIZ];
327 if (strlen(addr) == 0) {
334 /* How many addresses are listed here? */
335 num_addrs = num_tokens(addr, ',');
337 /* Output them one by one. */
338 for (i=0; i<num_addrs; ++i) {
339 extract_token(individual_addr, addr, i, ',');
340 striplt(individual_addr);
341 process_rfc822_addr(individual_addr, user, node, name);
349 if (i < (num_addrs-1)) cprintf(" ");
357 * Implements the ENVELOPE fetch item
359 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
360 * so we don't have to check for that condition like we do elsewhere.
362 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
363 char datestringbuf[SIZ];
365 char *fieldptr = NULL;
367 /* Parse the message date into an IMAP-format date string */
368 if (msg->cm_fields['T'] != NULL) {
369 msgdate = atol(msg->cm_fields['T']);
372 msgdate = time(NULL);
374 datestring(datestringbuf, sizeof datestringbuf,
375 msgdate, DATESTRING_IMAP);
377 /* Now start spewing data fields. The order is important, as it is
378 * defined by the protocol specification. Nonexistent fields must
379 * be output as NIL, existent fields must be quoted or literalled.
380 * The imap_strout() function conveniently does all this for us.
382 cprintf("ENVELOPE (");
385 imap_strout(datestringbuf);
389 imap_strout(msg->cm_fields['U']);
393 imap_output_envelope_from(msg);
395 /* Sender (default to same as 'From' if not present) */
396 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
397 if (fieldptr != NULL) {
398 imap_output_envelope_addr(fieldptr);
402 imap_output_envelope_from(msg);
406 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
407 if (fieldptr != NULL) {
408 imap_output_envelope_addr(fieldptr);
412 imap_output_envelope_from(msg);
416 imap_output_envelope_addr(msg->cm_fields['R']);
419 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
420 imap_output_envelope_addr(fieldptr);
421 if (fieldptr != NULL) free(fieldptr);
424 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
425 imap_output_envelope_addr(fieldptr);
426 if (fieldptr != NULL) free(fieldptr);
429 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
430 imap_strout(fieldptr);
432 if (fieldptr != NULL) free(fieldptr);
435 imap_strout(msg->cm_fields['I']);
442 * Strip any non header information out of a chunk of RFC822 data on disk,
443 * then boil it down to just the fields we want.
445 void imap_strip_headers(FILE *fp, char *section) {
447 char *which_fields = NULL;
448 int doing_headers = 0;
453 char *boiled_headers = NULL;
455 int done_headers = 0;
457 which_fields = strdup(section);
459 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
461 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
464 for (i=0; i<strlen(which_fields); ++i) {
465 if (which_fields[i]=='(')
466 strcpy(which_fields, &which_fields[i+1]);
468 for (i=0; i<strlen(which_fields); ++i) {
469 if (which_fields[i]==')')
472 num_parms = imap_parameterize(parms, which_fields);
474 fseek(fp, 0L, SEEK_END);
475 boiled_headers = malloc((size_t)(ftell(fp) + 256L));
476 strcpy(boiled_headers, "");
480 while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
481 if (!isspace(buf[0])) {
483 if (doing_headers == 0) ok = 1;
485 if (headers_not) ok = 1;
487 for (i=0; i<num_parms; ++i) {
488 if ( (!strncasecmp(buf, parms[i],
489 strlen(parms[i]))) &&
490 (buf[strlen(parms[i])]==':') ) {
491 if (headers_not) ok = 0;
499 strcat(boiled_headers, buf);
502 if (strlen(buf) == 0) done_headers = 1;
503 if (buf[0]=='\r') done_headers = 1;
504 if (buf[0]=='\n') done_headers = 1;
507 strcat(boiled_headers, "\r\n");
509 /* Now write it back */
511 fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
513 ftruncate(fileno(fp), ftell(fp));
517 free(boiled_headers);
522 * Implements the BODY and BODY.PEEK fetch items
524 void imap_fetch_body(long msgnum, char *item, int is_peek) {
525 struct CtdlMessage *msg = NULL;
531 long bytes_remaining = 0;
534 struct imap_fetch_part imfp;
536 /* extract section */
537 safestrncpy(section, item, sizeof section);
538 if (strchr(section, '[') != NULL) {
539 stripallbut(section, '[', ']');
541 /* lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") ); */
543 /* Burn the cache if we don't have the same section of the
544 * same message again.
546 if (IMAP->cached_body != NULL) {
547 if ((IMAP->cached_bodymsgnum != msgnum)
548 || (strcasecmp(IMAP->cached_bodypart, section)) ) {
549 fclose(IMAP->cached_body);
550 IMAP->cached_body = NULL;
551 IMAP->cached_bodymsgnum = (-1);
552 strcpy(IMAP->cached_bodypart, "");
556 /* extract partial */
557 safestrncpy(partial, item, sizeof partial);
558 if (strchr(partial, '<') != NULL) {
559 stripallbut(partial, '<', '>');
562 if (is_partial == 0) strcpy(partial, "");
563 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
565 if (IMAP->cached_body == NULL) {
568 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n", strerror(errno));
571 msg = CtdlFetchMessage(msgnum, 1);
574 /* Now figure out what the client wants, and get it */
576 if (IMAP->cached_body != NULL) {
577 tmp = IMAP->cached_body;
579 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
580 CtdlRedirectOutput(tmp);
581 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
583 CtdlRedirectOutput(NULL);
586 else if (!strcmp(section, "")) {
587 CtdlRedirectOutput(tmp);
588 /* lprintf(CTDL_DEBUG, "calling CtdlOutputPreLoadedMsg()\n");
589 lprintf(CTDL_DEBUG, "msg %s null\n", ((msg == NULL) ? "is" : "is not") );
590 lprintf(CTDL_DEBUG, "msgnum is %ld\n", msgnum); */
591 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
593 CtdlRedirectOutput(NULL);
597 * If the client asked for just headers, or just particular header
598 * fields, strip it down.
600 else if (!strncasecmp(section, "HEADER", 6)) {
601 CtdlRedirectOutput(tmp);
602 /* lprintf(CTDL_DEBUG, "calling CtdlOutputPreLoadedMsg()\n");
603 lprintf(CTDL_DEBUG, "msg %s null\n", ((msg == NULL) ? "is" : "is not") );
604 lprintf(CTDL_DEBUG, "msgnum is %ld\n", msgnum); */
605 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
607 CtdlRedirectOutput(NULL);
608 imap_strip_headers(tmp, section);
612 * Strip it down if the client asked for everything _except_ headers.
614 else if (!strncasecmp(section, "TEXT", 4)) {
615 CtdlRedirectOutput(tmp);
616 /* lprintf(CTDL_DEBUG, "calling CtdlOutputPreLoadedMsg()\n");
617 lprintf(CTDL_DEBUG, "msg %s null\n", ((msg == NULL) ? "is" : "is not") );
618 lprintf(CTDL_DEBUG, "msgnum is %ld\n", msgnum); */
619 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
621 CtdlRedirectOutput(NULL);
625 * Anything else must be a part specifier.
626 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
629 safestrncpy(imfp.desired_section, section,
630 sizeof(imfp.desired_section));
631 imfp.output_fp = tmp;
633 mime_parser(msg->cm_fields['M'], NULL,
634 *imap_load_part, NULL, NULL,
640 fseek(tmp, 0L, SEEK_END);
641 bytes_remaining = ftell(tmp);
643 if (is_partial == 0) {
645 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
648 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
649 if ((bytes_remaining - pstart) < pbytes) {
650 pbytes = bytes_remaining - pstart;
652 fseek(tmp, pstart, SEEK_SET);
653 bytes_remaining = pbytes;
654 cprintf("BODY[%s]<%ld> {%ld}\r\n",
655 section, pstart, bytes_remaining);
658 blocksize = (long)sizeof(buf);
659 while (bytes_remaining > 0L) {
660 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
661 fread(buf, blocksize, 1, tmp);
662 client_write(buf, (int)blocksize);
663 bytes_remaining = bytes_remaining - blocksize;
666 /* Don't close it ... cache it! */
668 IMAP->cached_body = tmp;
669 IMAP->cached_bodymsgnum = msgnum;
670 strcpy(IMAP->cached_bodypart, section);
673 CtdlFreeMessage(msg);
676 /* Mark this message as "seen" *unless* this is a "peek" operation */
678 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
683 * Called immediately before outputting a multipart bodystructure
685 void imap_fetch_bodystructure_pre(
686 char *name, char *filename, char *partnum, char *disp,
687 void *content, char *cbtype, size_t length, char *encoding,
697 * Called immediately after outputting a multipart bodystructure
699 void imap_fetch_bodystructure_post(
700 char *name, char *filename, char *partnum, char *disp,
701 void *content, char *cbtype, size_t length, char *encoding,
710 extract_token(subtype, cbtype, 1, '/');
711 imap_strout(subtype);
722 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
725 void imap_fetch_bodystructure_part(
726 char *name, char *filename, char *partnum, char *disp,
727 void *content, char *cbtype, size_t length, char *encoding,
732 int have_encoding = 0;
735 char cbmaintype[SIZ];
738 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
740 extract_token(cbmaintype, cbtype, 0, '/');
741 extract_token(cbsubtype, cbtype, 1, '/');
744 strcpy(cbmaintype, "TEXT");
745 strcpy(cbsubtype, "PLAIN");
749 imap_strout(cbmaintype);
751 imap_strout(cbsubtype);
754 cprintf("(\"CHARSET\" \"US-ASCII\"");
756 if (name != NULL) if (strlen(name)>0) {
757 cprintf(" \"NAME\" ");
763 cprintf("NIL "); /* Body ID */
764 cprintf("NIL "); /* Body description */
766 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
768 imap_strout(encoding);
775 /* The next field is the size of the part in bytes. */
776 cprintf("%ld ", (long)length); /* bytes */
778 /* The next field is the number of lines in the part, if and only
779 * if the part is TEXT. More gratuitous complexity.
781 if (!strcasecmp(cbmaintype, "TEXT")) {
782 if (length) for (i=0; i<length; ++i) {
783 if (((char *)content)[i] == '\n') ++lines;
785 cprintf("%d ", lines);
788 /* More gratuitous complexity */
789 if ((!strcasecmp(cbmaintype, "MESSAGE"))
790 && (!strcasecmp(cbsubtype, "RFC822"))) {
792 A body type of type MESSAGE and subtype RFC822
793 contains, immediately after the basic fields, the
794 envelope structure, body structure, and size in
795 text lines of the encapsulated message.
799 /* MD5 value of body part; we can get away with NIL'ing this */
806 else if (strlen(disp) == 0) {
812 if (filename != NULL) if (strlen(filename)>0) {
813 cprintf(" (\"FILENAME\" ");
814 imap_strout(filename);
820 /* Body language (not defined yet) */
827 * Spew the BODYSTRUCTURE data for a message.
830 void imap_fetch_bodystructure (long msgnum, char *item,
831 struct CtdlMessage *msg) {
835 long start_of_body = 0L;
836 long body_bytes = 0L;
838 /* For non-RFC822 (ordinary Citadel) messages, this is short and
841 if (msg->cm_format_type != FMT_RFC822) {
843 /* *sigh* We have to RFC822-format the message just to be able
847 if (tmp == NULL) return;
848 CtdlRedirectOutput(tmp);
849 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
850 CtdlRedirectOutput(NULL);
853 while (fgets(buf, sizeof buf, tmp) != NULL) {
855 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
856 start_of_body = ftell(tmp);
859 body_bytes = ftell(tmp) - start_of_body;
862 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
863 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
864 "\"7BIT\" %ld %ld)", body_bytes, lines);
869 /* For messages already stored in RFC822 format, we have to parse. */
870 cprintf("BODYSTRUCTURE ");
871 mime_parser(msg->cm_fields['M'],
873 *imap_fetch_bodystructure_part, /* part */
874 *imap_fetch_bodystructure_pre, /* pre-multi */
875 *imap_fetch_bodystructure_post, /* post-multi */
877 1); /* don't decode -- we want it as-is */
882 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
883 * individual message, once it has been selected for output.
885 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
887 struct CtdlMessage *msg = NULL;
889 cprintf("* %d FETCH (", seq);
891 for (i=0; i<num_items; ++i) {
893 /* Fetchable without going to the message store at all */
894 if (!strcasecmp(itemlist[i], "UID")) {
897 else if (!strcasecmp(itemlist[i], "FLAGS")) {
898 imap_fetch_flags(seq-1);
901 /* Potentially fetchable from cache, if the client requests
902 * stuff from the same message several times in a row.
904 else if (!strcasecmp(itemlist[i], "RFC822")) {
905 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
907 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
908 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
910 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
911 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
913 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
914 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
917 /* BODY fetches do their own fetching and caching too. */
918 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
919 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
921 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
922 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
925 /* Otherwise, load the message into memory.
927 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
928 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
929 imap_fetch_bodystructure(IMAP->msgids[seq-1],
932 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
933 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
934 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
936 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
937 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
938 imap_fetch_internaldate(msg);
941 if (i != num_items-1) cprintf(" ");
946 CtdlFreeMessage(msg);
953 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
954 * validated and boiled down the request a bit.
956 void imap_do_fetch(int num_items, char **itemlist) {
959 if (IMAP->num_msgs > 0) {
960 for (i = 0; i < IMAP->num_msgs; ++i) {
961 if (IMAP->flags[i] & IMAP_SELECTED) {
962 imap_do_fetch_msg(i+1, num_items, itemlist);
971 * Back end for imap_handle_macros()
972 * Note that this function *only* looks at the beginning of the string. It
973 * is not a generic search-and-replace function.
975 void imap_macro_replace(char *str, char *find, char *replace) {
978 if (!strncasecmp(str, find, strlen(find))) {
979 if (str[strlen(find)]==' ') {
980 strcpy(holdbuf, &str[strlen(find)+1]);
981 strcpy(str, replace);
983 strcat(str, holdbuf);
985 if (str[strlen(find)]==0) {
986 strcpy(holdbuf, &str[strlen(find)+1]);
987 strcpy(str, replace);
995 * Handle macros embedded in FETCH data items.
996 * (What the heck are macros doing in a wire protocol? Are we trying to save
997 * the computer at the other end the trouble of typing a lot of characters?)
999 void imap_handle_macros(char *str) {
1003 for (i=0; i<strlen(str); ++i) {
1004 if (str[i]=='(') ++nest;
1005 if (str[i]=='[') ++nest;
1006 if (str[i]=='<') ++nest;
1007 if (str[i]=='{') ++nest;
1008 if (str[i]==')') --nest;
1009 if (str[i]==']') --nest;
1010 if (str[i]=='>') --nest;
1011 if (str[i]=='}') --nest;
1014 imap_macro_replace(&str[i],
1016 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1018 imap_macro_replace(&str[i],
1022 imap_macro_replace(&str[i],
1024 "FLAGS INTERNALDATE RFC822.SIZE"
1026 imap_macro_replace(&str[i],
1028 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1036 * Break out the data items requested, possibly a parenthesized list.
1037 * Returns the number of data items, or -1 if the list is invalid.
1038 * NOTE: this function alters the string it is fed, and uses it as a buffer
1039 * to hold the data for the pointers it returns.
1041 int imap_extract_data_items(char **argv, char *items) {
1047 /* Convert all whitespace to ordinary space characters. */
1048 for (i=0; i<strlen(items); ++i) {
1049 if (isspace(items[i])) items[i]=' ';
1052 /* Strip leading and trailing whitespace, then strip leading and
1053 * trailing parentheses if it's a list
1056 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1057 items[strlen(items)-1] = 0;
1058 strcpy(items, &items[1]);
1062 /* Parse any macro data items */
1063 imap_handle_macros(items);
1066 * Now break out the data items. We throw in one trailing space in
1067 * order to avoid having to break out the last one manually.
1071 initial_len = strlen(items);
1072 for (i=0; i<initial_len; ++i) {
1073 if (items[i]=='(') ++nest;
1074 if (items[i]=='[') ++nest;
1075 if (items[i]=='<') ++nest;
1076 if (items[i]=='{') ++nest;
1077 if (items[i]==')') --nest;
1078 if (items[i]==']') --nest;
1079 if (items[i]=='>') --nest;
1080 if (items[i]=='}') --nest;
1082 if (nest <= 0) if (items[i]==' ') {
1084 argv[num_items++] = start;
1085 start = &items[i+1];
1095 * One particularly hideous aspect of IMAP is that we have to allow the client
1096 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1097 * handles this by setting the IMAP_SELECTED flag for each message specified in
1098 * the ranges/sets, then looping through the message array, outputting messages
1099 * with the flag set. We don't bother returning an error if an out-of-range
1100 * number is specified (we just return quietly) because any client braindead
1101 * enough to request a bogus message number isn't going to notice the
1102 * difference anyway.
1104 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1105 * message included in the specified range.
1107 * Set is_uid to 1 to fetch by UID instead of sequence number.
1109 void imap_pick_range(char *supplied_range, int is_uid) {
1113 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1115 char actual_range[SIZ];
1118 * Handle the "ALL" macro
1120 if (!strcasecmp(supplied_range, "ALL")) {
1121 safestrncpy(actual_range, "1:*", sizeof actual_range);
1124 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1128 * Clear out the IMAP_SELECTED flags for all messages.
1130 for (i = 0; i < IMAP->num_msgs; ++i) {
1131 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1135 * Now set it for all specified messages.
1137 num_sets = num_tokens(actual_range, ',');
1138 for (s=0; s<num_sets; ++s) {
1139 extract_token(setstr, actual_range, s, ',');
1141 extract_token(lostr, setstr, 0, ':');
1142 if (num_tokens(setstr, ':') >= 2) {
1143 extract_token(histr, setstr, 1, ':');
1144 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1147 strcpy(histr, lostr);
1152 /* Loop through the array, flipping bits where appropriate */
1153 for (i = 1; i <= IMAP->num_msgs; ++i) {
1154 if (is_uid) { /* fetch by sequence number */
1155 if ( (IMAP->msgids[i-1]>=lo)
1156 && (IMAP->msgids[i-1]<=hi)) {
1158 IMAP->flags[i-1] | IMAP_SELECTED;
1161 else { /* fetch by uid */
1162 if ( (i>=lo) && (i<=hi)) {
1164 IMAP->flags[i-1] | IMAP_SELECTED;
1175 * This function is called by the main command loop.
1177 void imap_fetch(int num_parms, char *parms[]) {
1179 char *itemlist[SIZ];
1183 if (num_parms < 4) {
1184 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1188 imap_pick_range(parms[2], 0);
1191 for (i=3; i<num_parms; ++i) {
1192 strcat(items, parms[i]);
1193 if (i < (num_parms-1)) strcat(items, " ");
1196 num_items = imap_extract_data_items(itemlist, items);
1197 if (num_items < 1) {
1198 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1202 imap_do_fetch(num_items, itemlist);
1203 cprintf("%s OK FETCH completed\r\n", parms[0]);
1207 * This function is called by the main command loop.
1209 void imap_uidfetch(int num_parms, char *parms[]) {
1211 char *itemlist[SIZ];
1214 int have_uid_item = 0;
1216 if (num_parms < 5) {
1217 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1221 imap_pick_range(parms[3], 1);
1224 for (i=4; i<num_parms; ++i) {
1225 strcat(items, parms[i]);
1226 if (i < (num_parms-1)) strcat(items, " ");
1229 num_items = imap_extract_data_items(itemlist, items);
1230 if (num_items < 1) {
1231 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1235 /* If the "UID" item was not included, we include it implicitly
1236 * because this is a UID FETCH command
1238 for (i=0; i<num_items; ++i) {
1239 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1241 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1243 imap_do_fetch(num_items, itemlist);
1244 cprintf("%s OK UID FETCH completed\r\n", parms[0]);