4 * Implements the FETCH command in IMAP.
5 * This command is way too convoluted. Marc Crispin is a fscking idiot.
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 long headers_size, text_size, total_size;
125 long bytes_remaining = 0;
129 /* Cache the most recent RFC822 FETCH because some clients like to
130 * fetch in pieces, and we don't want to have to go back to the
131 * message store for each piece.
133 if ((IMAP->cached_fetch != NULL) && (IMAP->cached_msgnum == msgnum)) {
135 tmp = IMAP->cached_fetch;
137 else if ((IMAP->cached_fetch != NULL) && (IMAP->cached_msgnum != msgnum)) {
138 /* Some other message is cached -- free it */
139 fclose(IMAP->cached_fetch);
140 IMAP->cached_fetch == NULL;
141 IMAP->cached_msgnum = (-1);
145 /* At this point, we now can fetch and convert the message iff it's not
146 * the one we had cached.
151 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n",
157 * Load the message into a temp file for translation
160 CtdlRedirectOutput(tmp, -1);
161 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
162 CtdlRedirectOutput(NULL, -1);
164 IMAP->cached_fetch = tmp;
165 IMAP->cached_msgnum = msgnum;
169 * Now figure out where the headers/text break is. IMAP considers the
170 * intervening blank line to be part of the headers, not the text.
175 ptr = fgets(buf, sizeof buf, tmp);
178 if (strlen(buf) == 0) {
179 headers_size = ftell(tmp);
182 } while ( (headers_size == 0L) && (ptr != NULL) );
183 fseek(tmp, 0L, SEEK_END);
184 total_size = ftell(tmp);
185 text_size = total_size - headers_size;
187 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
188 cprintf("RFC822.SIZE %ld", total_size);
192 else if (!strcasecmp(whichfmt, "RFC822")) {
193 bytes_remaining = total_size;
197 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
198 bytes_remaining = headers_size;
202 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
203 bytes_remaining = text_size;
204 fseek(tmp, headers_size, SEEK_SET);
207 cprintf("%s {%ld}\r\n", whichfmt, bytes_remaining);
208 blocksize = sizeof(buf);
209 while (bytes_remaining > 0L) {
210 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
211 fread(buf, blocksize, 1, tmp);
212 client_write(buf, blocksize);
213 bytes_remaining = bytes_remaining - blocksize;
221 * Load a specific part of a message into the temp file to be output to a
222 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
223 * but we still can't handle "2.HEADER" (which might not be a problem, because
224 * we currently don't have the ability to break out nested RFC822's anyway).
226 * Note: mime_parser() was called with dont_decode set to 1, so we have the
227 * luxury of simply spewing without having to re-encode.
229 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
230 void *content, char *cbtype, size_t length, char *encoding,
233 struct imap_fetch_part *imfp;
236 imfp = (struct imap_fetch_part *)cbuserdata;
238 if (!strcasecmp(partnum, imfp->desired_section)) {
239 fwrite(content, length, 1, imfp->output_fp);
242 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
244 if (!strcasecmp(imfp->desired_section, mbuf2)) {
245 fprintf(imfp->output_fp, "Content-type: %s", cbtype);
246 if (strlen(name) > 0)
247 fprintf(imfp->output_fp, "; name=\"%s\"", name);
248 fprintf(imfp->output_fp, "\r\n");
249 if (strlen(encoding) > 0)
250 fprintf(imfp->output_fp,
251 "Content-Transfer-Encoding: %s\r\n", encoding);
252 if (strlen(encoding) > 0) {
253 fprintf(imfp->output_fp, "Content-Disposition: %s",
255 if (strlen(filename) > 0) {
256 fprintf(imfp->output_fp, "; filename=\"%s\"",
259 fprintf(imfp->output_fp, "\r\n");
261 fprintf(imfp->output_fp, "Content-Length: %ld\r\n", (long)length);
262 fprintf(imfp->output_fp, "\r\n");
270 * Called by imap_fetch_envelope() to output the "From" field.
271 * This is in its own function because its logic is kind of complex. We
272 * really need to make this suck less.
274 void imap_output_envelope_from(struct CtdlMessage *msg) {
275 char user[1024], node[1024], name[1024];
277 /* For anonymous messages, it's so easy! */
278 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
279 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
282 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
283 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
287 /* For everything else, we do stuff. */
288 cprintf("(("); /* open double-parens */
289 imap_strout(msg->cm_fields['A']); /* personal name */
290 cprintf(" NIL "); /* source route (not used) */
293 if (msg->cm_fields['F'] != NULL) {
294 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
295 imap_strout(user); /* mailbox name (user id) */
297 if (!strcasecmp(node, config.c_nodename)) {
298 imap_strout(config.c_fqdn);
301 imap_strout(node); /* host name */
305 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
307 imap_strout(msg->cm_fields['N']); /* host name */
310 cprintf(")) "); /* close double-parens */
316 * Output an envelope address (or set of addresses) in the official,
317 * Crispin-approved braindead format. (Note that we can't use this for
318 * the "From" address because its data may come from a number of different
319 * fields. But we can use it for "To" and possibly others.
321 void imap_output_envelope_addr(char *addr) {
322 char individual_addr[SIZ];
334 if (strlen(addr) == 0) {
341 /* How many addresses are listed here? */
342 num_addrs = num_tokens(addr, ',');
344 /* Output them one by one. */
345 for (i=0; i<num_addrs; ++i) {
346 extract_token(individual_addr, addr, i, ',');
347 striplt(individual_addr);
348 process_rfc822_addr(individual_addr, user, node, name);
356 if (i < (num_addrs-1)) cprintf(" ");
364 * Implements the ENVELOPE fetch item
366 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
367 * so we don't have to check for that condition like we do elsewhere.
369 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
370 char datestringbuf[SIZ];
372 char *fieldptr = NULL;
374 /* Parse the message date into an IMAP-format date string */
375 if (msg->cm_fields['T'] != NULL) {
376 msgdate = atol(msg->cm_fields['T']);
379 msgdate = time(NULL);
381 datestring(datestringbuf, sizeof datestringbuf,
382 msgdate, DATESTRING_IMAP);
384 /* Now start spewing data fields. The order is important, as it is
385 * defined by the protocol specification. Nonexistent fields must
386 * be output as NIL, existent fields must be quoted or literalled.
387 * The imap_strout() function conveniently does all this for us.
389 cprintf("ENVELOPE (");
392 imap_strout(datestringbuf);
396 imap_strout(msg->cm_fields['U']);
400 imap_output_envelope_from(msg);
402 /* Sender (default to same as 'From' if not present) */
403 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
404 if (fieldptr != NULL) {
405 imap_output_envelope_addr(fieldptr);
409 imap_output_envelope_from(msg);
413 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
414 if (fieldptr != NULL) {
415 imap_output_envelope_addr(fieldptr);
419 imap_output_envelope_from(msg);
423 imap_output_envelope_addr(msg->cm_fields['R']);
426 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
427 imap_output_envelope_addr(fieldptr);
428 if (fieldptr != NULL) free(fieldptr);
431 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
432 imap_output_envelope_addr(fieldptr);
433 if (fieldptr != NULL) free(fieldptr);
436 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
437 imap_strout(fieldptr);
439 if (fieldptr != NULL) free(fieldptr);
442 imap_strout(msg->cm_fields['I']);
449 * Strip any non header information out of a chunk of RFC822 data on disk,
450 * then boil it down to just the fields we want.
452 void imap_strip_headers(FILE *fp, char *section) {
454 char *which_fields = NULL;
455 int doing_headers = 0;
460 char *boiled_headers = NULL;
462 int done_headers = 0;
464 which_fields = strdup(section);
466 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
468 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
471 for (i=0; i<strlen(which_fields); ++i) {
472 if (which_fields[i]=='(')
473 strcpy(which_fields, &which_fields[i+1]);
475 for (i=0; i<strlen(which_fields); ++i) {
476 if (which_fields[i]==')')
479 num_parms = imap_parameterize(parms, which_fields);
481 fseek(fp, 0L, SEEK_END);
482 boiled_headers = malloc((size_t)(ftell(fp) + 256L));
483 strcpy(boiled_headers, "");
487 while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
488 if (!isspace(buf[0])) {
490 if (doing_headers == 0) ok = 1;
492 if (headers_not) ok = 1;
494 for (i=0; i<num_parms; ++i) {
495 if ( (!strncasecmp(buf, parms[i],
496 strlen(parms[i]))) &&
497 (buf[strlen(parms[i])]==':') ) {
498 if (headers_not) ok = 0;
506 strcat(boiled_headers, buf);
509 if (strlen(buf) == 0) done_headers = 1;
510 if (buf[0]=='\r') done_headers = 1;
511 if (buf[0]=='\n') done_headers = 1;
514 strcat(boiled_headers, "\r\n");
516 /* Now write it back */
518 fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
520 ftruncate(fileno(fp), ftell(fp));
524 free(boiled_headers);
529 * Implements the BODY and BODY.PEEK fetch items
531 void imap_fetch_body(long msgnum, char *item, int is_peek) {
532 struct CtdlMessage *msg = NULL;
539 long bytes_remaining = 0;
542 struct imap_fetch_part imfp;
544 /* extract section */
545 strcpy(section, item);
546 for (i=0; i<strlen(section); ++i) {
547 if (section[i]=='[') strcpy(section, §ion[i+1]);
549 for (i=0; i<strlen(section); ++i) {
550 if (section[i]==']') section[i] = 0;
552 lprintf(CTDL_DEBUG, "Section is %s\n", section);
554 /* Burn the cache if we don't have the same section of the
555 * same message again.
557 if (IMAP->cached_body != NULL) {
558 if ((IMAP->cached_bodymsgnum != msgnum)
559 || (strcasecmp(IMAP->cached_bodypart, section)) ) {
560 fclose(IMAP->cached_body);
561 IMAP->cached_body = NULL;
562 IMAP->cached_bodymsgnum = (-1);
563 strcpy(IMAP->cached_bodypart, "");
567 /* extract partial */
568 strcpy(partial, item);
569 for (i=0; i<strlen(partial); ++i) {
570 if (partial[i]=='<') {
571 strcpy(partial, &partial[i+1]);
575 for (i=0; i<strlen(partial); ++i) {
576 if (partial[i]=='>') partial[i] = 0;
578 if (is_partial == 0) strcpy(partial, "");
579 if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial);
581 if (IMAP->cached_body == NULL) {
584 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n", strerror(errno));
587 msg = CtdlFetchMessage(msgnum, 1);
590 /* Now figure out what the client wants, and get it */
592 if (IMAP->cached_body != NULL) {
593 tmp = IMAP->cached_body;
595 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
596 CtdlRedirectOutput(tmp, -1);
597 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
599 CtdlRedirectOutput(NULL, -1);
602 else if (!strcmp(section, "")) {
603 CtdlRedirectOutput(tmp, -1);
604 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
606 CtdlRedirectOutput(NULL, -1);
610 * If the client asked for just headers, or just particular header
611 * fields, strip it down.
613 else if (!strncasecmp(section, "HEADER", 6)) {
614 CtdlRedirectOutput(tmp, -1);
615 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
617 CtdlRedirectOutput(NULL, -1);
618 imap_strip_headers(tmp, section);
622 * Strip it down if the client asked for everything _except_ headers.
624 else if (!strncasecmp(section, "TEXT", 4)) {
625 CtdlRedirectOutput(tmp, -1);
626 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
628 CtdlRedirectOutput(NULL, -1);
632 * Anything else must be a part specifier.
633 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
636 safestrncpy(imfp.desired_section, section,
637 sizeof(imfp.desired_section));
638 imfp.output_fp = tmp;
640 mime_parser(msg->cm_fields['M'], NULL,
641 *imap_load_part, NULL, NULL,
647 fseek(tmp, 0L, SEEK_END);
648 bytes_remaining = ftell(tmp);
650 if (is_partial == 0) {
652 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
655 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
656 if ((bytes_remaining - pstart) < pbytes) {
657 pbytes = bytes_remaining - pstart;
659 fseek(tmp, pstart, SEEK_SET);
660 bytes_remaining = pbytes;
661 cprintf("BODY[%s]<%ld> {%ld}\r\n",
662 section, pstart, bytes_remaining);
665 blocksize = sizeof(buf);
666 while (bytes_remaining > 0L) {
667 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
668 fread(buf, blocksize, 1, tmp);
669 client_write(buf, blocksize);
670 bytes_remaining = bytes_remaining - blocksize;
673 /* Don't close it ... cache it! */
675 IMAP->cached_body = tmp;
676 IMAP->cached_bodymsgnum = msgnum;
677 strcpy(IMAP->cached_bodypart, section);
680 CtdlFreeMessage(msg);
683 /* Mark this message as "seen" *unless* this is a "peek" operation */
685 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
690 * Called immediately before outputting a multipart bodystructure
692 void imap_fetch_bodystructure_pre(
693 char *name, char *filename, char *partnum, char *disp,
694 void *content, char *cbtype, size_t length, char *encoding,
704 * Called immediately after outputting a multipart bodystructure
706 void imap_fetch_bodystructure_post(
707 char *name, char *filename, char *partnum, char *disp,
708 void *content, char *cbtype, size_t length, char *encoding,
717 extract_token(subtype, cbtype, 1, '/');
718 imap_strout(subtype);
729 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
732 void imap_fetch_bodystructure_part(
733 char *name, char *filename, char *partnum, char *disp,
734 void *content, char *cbtype, size_t length, char *encoding,
739 int have_encoding = 0;
742 char cbmaintype[SIZ];
745 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
747 extract_token(cbmaintype, cbtype, 0, '/');
748 extract_token(cbsubtype, cbtype, 1, '/');
751 strcpy(cbmaintype, "TEXT");
752 strcpy(cbsubtype, "PLAIN");
756 imap_strout(cbmaintype);
758 imap_strout(cbsubtype);
761 cprintf("(\"CHARSET\" \"US-ASCII\"");
763 if (name != NULL) if (strlen(name)>0) {
764 cprintf(" \"NAME\" ");
770 cprintf("NIL "); /* Body ID */
771 cprintf("NIL "); /* Body description */
773 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
775 imap_strout(encoding);
782 /* The next field is the size of the part in bytes. */
783 cprintf("%ld ", (long)length); /* bytes */
785 /* The next field is the number of lines in the part, if and only
786 * if the part is TEXT. Crispin is a fscking idiot.
788 if (!strcasecmp(cbmaintype, "TEXT")) {
789 if (length) for (i=0; i<length; ++i) {
790 if (((char *)content)[i] == '\n') ++lines;
792 cprintf("%d ", lines);
795 /* More of Crispin being a fscking idiot */
796 if ((!strcasecmp(cbmaintype, "MESSAGE"))
797 && (!strcasecmp(cbsubtype, "RFC822"))) {
799 A body type of type MESSAGE and subtype RFC822
800 contains, immediately after the basic fields, the
801 envelope structure, body structure, and size in
802 text lines of the encapsulated message.
806 /* MD5 value of body part; we can get away with NIL'ing this */
813 else if (strlen(disp) == 0) {
819 if (filename != NULL) if (strlen(filename)>0) {
820 cprintf(" (\"FILENAME\" ");
821 imap_strout(filename);
827 /* Body language (not defined yet) */
834 * Spew the BODYSTRUCTURE data for a message. (Do you need a silencer if
835 * you're going to shoot a MIME? Do you need a reason to shoot Mark Crispin?
839 void imap_fetch_bodystructure (long msgnum, char *item,
840 struct CtdlMessage *msg) {
844 long start_of_body = 0L;
845 long body_bytes = 0L;
847 /* For non-RFC822 (ordinary Citadel) messages, this is short and
850 if (msg->cm_format_type != FMT_RFC822) {
852 /* *sigh* We have to RFC822-format the message just to be able
856 if (tmp == NULL) return;
857 CtdlRedirectOutput(tmp, -1);
858 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
859 CtdlRedirectOutput(NULL, -1);
862 while (fgets(buf, sizeof buf, tmp) != NULL) {
864 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
865 start_of_body = ftell(tmp);
868 body_bytes = ftell(tmp) - start_of_body;
871 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
872 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
873 "\"7BIT\" %ld %ld)", body_bytes, lines);
878 /* For messages already stored in RFC822 format, we have to parse. */
879 cprintf("BODYSTRUCTURE ");
880 mime_parser(msg->cm_fields['M'],
882 *imap_fetch_bodystructure_part, /* part */
883 *imap_fetch_bodystructure_pre, /* pre-multi */
884 *imap_fetch_bodystructure_post, /* post-multi */
886 1); /* don't decode -- we want it as-is */
891 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
892 * individual message, once it has been selected for output.
894 void imap_do_fetch_msg(int seq,
895 int num_items, char **itemlist) {
897 struct CtdlMessage *msg = NULL;
899 cprintf("* %d FETCH (", seq);
901 for (i=0; i<num_items; ++i) {
903 /* Fetchable without going to the message store at all */
904 if (!strcasecmp(itemlist[i], "UID")) {
907 else if (!strcasecmp(itemlist[i], "FLAGS")) {
908 imap_fetch_flags(seq-1);
911 /* Potentially fetchable from cache, if the client requests
912 * stuff from the same message several times in a row.
914 else if (!strcasecmp(itemlist[i], "RFC822")) {
915 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
917 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
918 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
920 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
921 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
923 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
924 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
927 /* BODY fetches do their own fetching and caching too. */
928 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
929 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
931 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
932 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
935 /* Otherwise, load the message into memory.
937 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
938 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
939 imap_fetch_bodystructure(IMAP->msgids[seq-1],
942 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
943 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
944 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
946 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
947 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
948 imap_fetch_internaldate(msg);
951 if (i != num_items-1) cprintf(" ");
956 CtdlFreeMessage(msg);
963 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
964 * validated and boiled down the request a bit.
966 void imap_do_fetch(int num_items, char **itemlist) {
969 if (IMAP->num_msgs > 0) {
970 for (i = 0; i < IMAP->num_msgs; ++i) {
971 if (IMAP->flags[i] & IMAP_SELECTED) {
972 imap_do_fetch_msg(i+1, num_items, itemlist);
981 * Back end for imap_handle_macros()
982 * Note that this function *only* looks at the beginning of the string. It
983 * is not a generic search-and-replace function.
985 void imap_macro_replace(char *str, char *find, char *replace) {
988 if (!strncasecmp(str, find, strlen(find))) {
989 if (str[strlen(find)]==' ') {
990 strcpy(holdbuf, &str[strlen(find)+1]);
991 strcpy(str, replace);
993 strcat(str, holdbuf);
995 if (str[strlen(find)]==0) {
996 strcpy(holdbuf, &str[strlen(find)+1]);
997 strcpy(str, replace);
1005 * Handle macros embedded in FETCH data items.
1006 * (What the heck are macros doing in a wire protocol? Are we trying to save
1007 * the computer at the other end the trouble of typing a lot of characters?)
1009 void imap_handle_macros(char *str) {
1013 for (i=0; i<strlen(str); ++i) {
1014 if (str[i]=='(') ++nest;
1015 if (str[i]=='[') ++nest;
1016 if (str[i]=='<') ++nest;
1017 if (str[i]=='{') ++nest;
1018 if (str[i]==')') --nest;
1019 if (str[i]==']') --nest;
1020 if (str[i]=='>') --nest;
1021 if (str[i]=='}') --nest;
1024 imap_macro_replace(&str[i],
1026 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1028 imap_macro_replace(&str[i],
1032 imap_macro_replace(&str[i],
1034 "FLAGS INTERNALDATE RFC822.SIZE"
1036 imap_macro_replace(&str[i],
1038 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1046 * Break out the data items requested, possibly a parenthesized list.
1047 * Returns the number of data items, or -1 if the list is invalid.
1048 * NOTE: this function alters the string it is fed, and uses it as a buffer
1049 * to hold the data for the pointers it returns.
1051 int imap_extract_data_items(char **argv, char *items) {
1057 /* Convert all whitespace to ordinary space characters. */
1058 for (i=0; i<strlen(items); ++i) {
1059 if (isspace(items[i])) items[i]=' ';
1062 /* Strip leading and trailing whitespace, then strip leading and
1063 * trailing parentheses if it's a list
1066 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1067 items[strlen(items)-1] = 0;
1068 strcpy(items, &items[1]);
1072 /* Parse any macro data items */
1073 imap_handle_macros(items);
1076 * Now break out the data items. We throw in one trailing space in
1077 * order to avoid having to break out the last one manually.
1081 initial_len = strlen(items);
1082 for (i=0; i<initial_len; ++i) {
1083 if (items[i]=='(') ++nest;
1084 if (items[i]=='[') ++nest;
1085 if (items[i]=='<') ++nest;
1086 if (items[i]=='{') ++nest;
1087 if (items[i]==')') --nest;
1088 if (items[i]==']') --nest;
1089 if (items[i]=='>') --nest;
1090 if (items[i]=='}') --nest;
1092 if (nest <= 0) if (items[i]==' ') {
1094 argv[num_items++] = start;
1095 start = &items[i+1];
1105 * One particularly hideous aspect of IMAP is that we have to allow the client
1106 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1107 * handles this by setting the IMAP_SELECTED flag for each message specified in
1108 * the ranges/sets, then looping through the message array, outputting messages
1109 * with the flag set. We don't bother returning an error if an out-of-range
1110 * number is specified (we just return quietly) because any client braindead
1111 * enough to request a bogus message number isn't going to notice the
1112 * difference anyway.
1114 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1115 * message included in the specified range.
1117 * Set is_uid to 1 to fetch by UID instead of sequence number.
1119 void imap_pick_range(char *supplied_range, int is_uid) {
1123 char setstr[SIZ], lostr[SIZ], histr[SIZ]; /* was 1024 */
1125 char actual_range[SIZ];
1128 * Handle the "ALL" macro
1130 if (!strcasecmp(supplied_range, "ALL")) {
1131 safestrncpy(actual_range, "1:*", sizeof actual_range);
1134 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1138 * Clear out the IMAP_SELECTED flags for all messages.
1140 for (i = 0; i < IMAP->num_msgs; ++i) {
1141 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1145 * Now set it for all specified messages.
1147 num_sets = num_tokens(actual_range, ',');
1148 for (s=0; s<num_sets; ++s) {
1149 extract_token(setstr, actual_range, s, ',');
1151 extract_token(lostr, setstr, 0, ':');
1152 if (num_tokens(setstr, ':') >= 2) {
1153 extract_token(histr, setstr, 1, ':');
1154 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1157 strcpy(histr, lostr);
1162 /* Loop through the array, flipping bits where appropriate */
1163 for (i = 1; i <= IMAP->num_msgs; ++i) {
1164 if (is_uid) { /* fetch by sequence number */
1165 if ( (IMAP->msgids[i-1]>=lo)
1166 && (IMAP->msgids[i-1]<=hi)) {
1168 IMAP->flags[i-1] | IMAP_SELECTED;
1171 else { /* fetch by uid */
1172 if ( (i>=lo) && (i<=hi)) {
1174 IMAP->flags[i-1] | IMAP_SELECTED;
1185 * This function is called by the main command loop.
1187 void imap_fetch(int num_parms, char *parms[]) {
1188 char items[SIZ]; /* was 1024 */
1189 char *itemlist[SIZ];
1193 if (num_parms < 4) {
1194 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1198 imap_pick_range(parms[2], 0);
1201 for (i=3; i<num_parms; ++i) {
1202 strcat(items, parms[i]);
1203 if (i < (num_parms-1)) strcat(items, " ");
1206 num_items = imap_extract_data_items(itemlist, items);
1207 if (num_items < 1) {
1208 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1212 imap_do_fetch(num_items, itemlist);
1213 cprintf("%s OK FETCH completed\r\n", parms[0]);
1217 * This function is called by the main command loop.
1219 void imap_uidfetch(int num_parms, char *parms[]) {
1220 char items[SIZ]; /* was 1024 */
1221 char *itemlist[SIZ];
1224 int have_uid_item = 0;
1226 if (num_parms < 5) {
1227 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1231 imap_pick_range(parms[3], 1);
1234 for (i=4; i<num_parms; ++i) {
1235 strcat(items, parms[i]);
1236 if (i < (num_parms-1)) strcat(items, " ");
1239 num_items = imap_extract_data_items(itemlist, items);
1240 if (num_items < 1) {
1241 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1245 /* If the "UID" item was not included, we include it implicitly
1246 * because this is a UID FETCH command
1248 for (i=0; i<num_items; ++i) {
1249 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1251 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1253 imap_do_fetch(num_items, itemlist);
1254 cprintf("%s OK UID FETCH completed\r\n", parms[0]);