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>
35 #include <libcitadel.h>
38 #include "sysdep_decls.h"
39 #include "citserver.h"
47 #include "internet_addressing.h"
48 #include "serv_imap.h"
49 #include "imap_tools.h"
50 #include "imap_fetch.h"
56 * Individual field functions for imap_do_fetch_msg() ...
59 void imap_fetch_uid(int seq) {
60 cprintf("UID %ld", IMAP->msgids[seq-1]);
63 void imap_fetch_flags(int seq) {
64 int num_flags_printed = 0;
66 if (IMAP->flags[seq] & IMAP_DELETED) {
67 if (num_flags_printed > 0) cprintf(" ");
71 if (IMAP->flags[seq] & IMAP_SEEN) {
72 if (num_flags_printed > 0) cprintf(" ");
76 if (IMAP->flags[seq] & IMAP_ANSWERED) {
77 if (num_flags_printed > 0) cprintf(" ");
78 cprintf("\\Answered");
81 if (IMAP->flags[seq] & IMAP_RECENT) {
82 if (num_flags_printed > 0) cprintf(" ");
89 void imap_fetch_internaldate(struct CtdlMessage *msg) {
94 if (msg->cm_fields['T'] != NULL) {
95 msgdate = atol(msg->cm_fields['T']);
101 datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
102 cprintf("INTERNALDATE \"%s\"", buf);
107 * Fetch RFC822-formatted messages.
109 * 'whichfmt' should be set to one of:
110 * "RFC822" entire message
111 * "RFC822.HEADER" headers only (with trailing blank line)
112 * "RFC822.SIZE" size of translated message
113 * "RFC822.TEXT" body only (without leading blank line)
115 void imap_fetch_rfc822(long msgnum, char *whichfmt) {
118 size_t headers_size, text_size, total_size;
119 size_t bytes_to_send = 0;
121 int need_to_rewrite_metadata = 0;
124 /* Determine whether this particular fetch operation requires
125 * us to fetch the message body from disk. If not, we can save
126 * on some disk operations...
128 if ( (!strcasecmp(whichfmt, "RFC822"))
129 || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
133 /* If this is an RFC822.SIZE fetch, first look in the message's
134 * metadata record to see if we've saved that information.
136 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
137 GetMetaData(&smi, msgnum);
138 if (smi.meta_rfc822_length > 0L) {
139 cprintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
142 need_to_rewrite_metadata = 1;
146 /* Cache the most recent RFC822 FETCH because some clients like to
147 * fetch in pieces, and we don't want to have to go back to the
148 * message store for each piece. We also burn the cache if the
149 * client requests something that involves reading the message
150 * body, but we haven't fetched the body yet.
152 if ((IMAP->cached_rfc822_data != NULL)
153 && (IMAP->cached_rfc822_msgnum == msgnum)
154 && (IMAP->cached_rfc822_withbody || (!need_body)) ) {
157 else if (IMAP->cached_rfc822_data != NULL) {
158 /* Some other message is cached -- free it */
159 free(IMAP->cached_rfc822_data);
160 IMAP->cached_rfc822_data = NULL;
161 IMAP->cached_rfc822_msgnum = (-1);
162 IMAP->cached_rfc822_len = 0;
165 /* At this point, we now can fetch and convert the message iff it's not
166 * the one we had cached.
168 if (IMAP->cached_rfc822_data == NULL) {
170 * Load the message into memory for translation & measurement
172 CC->redirect_buffer = malloc(SIZ);
173 CC->redirect_len = 0;
174 CC->redirect_alloc = SIZ;
175 CtdlOutputMsg(msgnum, MT_RFC822,
176 (need_body ? HEADERS_ALL : HEADERS_ONLY),
178 if (!need_body) cprintf("\r\n"); /* extra trailing newline */
179 IMAP->cached_rfc822_data = CC->redirect_buffer;
180 IMAP->cached_rfc822_len = CC->redirect_len;
181 IMAP->cached_rfc822_msgnum = msgnum;
182 IMAP->cached_rfc822_withbody = need_body;
183 CC->redirect_buffer = NULL;
184 CC->redirect_len = 0;
185 CC->redirect_alloc = 0;
186 if ( (need_to_rewrite_metadata) && (IMAP->cached_rfc822_len > 0) ) {
187 smi.meta_rfc822_length = (long)IMAP->cached_rfc822_len;
193 * Now figure out where the headers/text break is. IMAP considers the
194 * intervening blank line to be part of the headers, not the text.
201 ptr = IMAP->cached_rfc822_data;
203 ptr = memreadline(ptr, buf, sizeof buf);
206 if (IsEmptyStr(buf)) {
207 headers_size = ptr - IMAP->cached_rfc822_data;
210 } while ( (headers_size == 0) && (*ptr != 0) );
212 total_size = IMAP->cached_rfc822_len;
213 text_size = total_size - headers_size;
216 headers_size = IMAP->cached_rfc822_len;
217 total_size = IMAP->cached_rfc822_len;
222 "RFC822: headers=" SIZE_T_FMT
224 ", total=" SIZE_T_FMT "\n",
225 headers_size, text_size, total_size);
227 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
228 cprintf("RFC822.SIZE " SIZE_T_FMT, total_size);
232 else if (!strcasecmp(whichfmt, "RFC822")) {
233 ptr = IMAP->cached_rfc822_data;
234 bytes_to_send = total_size;
237 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
238 ptr = IMAP->cached_rfc822_data;
239 bytes_to_send = headers_size;
242 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
243 ptr = &IMAP->cached_rfc822_data[headers_size];
244 bytes_to_send = text_size;
247 cprintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send);
248 client_write(ptr, bytes_to_send);
254 * Load a specific part of a message into the temp file to be output to a
255 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
256 * but we still can't handle "2.HEADER" (which might not be a problem).
258 * Note: mime_parser() was called with dont_decode set to 1, so we have the
259 * luxury of simply spewing without having to re-encode.
261 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
262 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
266 char *desired_section;
268 desired_section = (char *)cbuserdata;
270 if (!strcasecmp(partnum, desired_section)) {
271 client_write(content, length);
274 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
276 if (!strcasecmp(desired_section, mbuf2)) {
277 cprintf("Content-type: %s", cbtype);
278 if (!IsEmptyStr(cbcharset))
279 cprintf("; charset=\"%s\"", cbcharset);
280 if (!IsEmptyStr(name))
281 cprintf("; name=\"%s\"", name);
283 if (!IsEmptyStr(encoding))
284 cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
285 if (!IsEmptyStr(encoding)) {
286 cprintf("Content-Disposition: %s", disp);
287 if (!IsEmptyStr(filename)) {
288 cprintf("; filename=\"%s\"", filename);
292 cprintf("Content-Length: %ld\r\n", (long)length);
301 * Called by imap_fetch_envelope() to output the "From" field.
302 * This is in its own function because its logic is kind of complex. We
303 * really need to make this suck less.
305 void imap_output_envelope_from(struct CtdlMessage *msg) {
306 char user[SIZ], node[SIZ], name[SIZ];
310 /* For anonymous messages, it's so easy! */
311 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
312 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
315 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
316 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
320 /* For everything else, we do stuff. */
321 cprintf("(("); /* open double-parens */
322 imap_strout(msg->cm_fields['A']); /* personal name */
323 cprintf(" NIL "); /* source route (not used) */
326 if (msg->cm_fields['F'] != NULL) {
327 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
328 imap_strout(user); /* mailbox name (user id) */
330 if (!strcasecmp(node, config.c_nodename)) {
331 imap_strout(config.c_fqdn);
334 imap_strout(node); /* host name */
338 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
340 imap_strout(msg->cm_fields['N']); /* host name */
343 cprintf(")) "); /* close double-parens */
349 * Output an envelope address (or set of addresses) in the official,
350 * convuluted, braindead format. (Note that we can't use this for
351 * the "From" address because its data may come from a number of different
352 * fields. But we can use it for "To" and possibly others.
354 void imap_output_envelope_addr(char *addr) {
355 char individual_addr[256];
367 if (IsEmptyStr(addr)) {
374 /* How many addresses are listed here? */
375 num_addrs = num_tokens(addr, ',');
377 /* Output them one by one. */
378 for (i=0; i<num_addrs; ++i) {
379 extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
380 striplt(individual_addr);
381 process_rfc822_addr(individual_addr, user, node, name);
389 if (i < (num_addrs-1)) cprintf(" ");
397 * Implements the ENVELOPE fetch item
399 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
400 * so we don't have to check for that condition like we do elsewhere.
402 void imap_fetch_envelope(struct CtdlMessage *msg) {
403 char datestringbuf[SIZ];
405 char *fieldptr = NULL;
409 /* Parse the message date into an IMAP-format date string */
410 if (msg->cm_fields['T'] != NULL) {
411 msgdate = atol(msg->cm_fields['T']);
414 msgdate = time(NULL);
416 datestring(datestringbuf, sizeof datestringbuf,
417 msgdate, DATESTRING_IMAP);
419 /* Now start spewing data fields. The order is important, as it is
420 * defined by the protocol specification. Nonexistent fields must
421 * be output as NIL, existent fields must be quoted or literalled.
422 * The imap_strout() function conveniently does all this for us.
424 cprintf("ENVELOPE (");
427 imap_strout(datestringbuf);
431 imap_strout(msg->cm_fields['U']);
435 imap_output_envelope_from(msg);
437 /* Sender (default to same as 'From' if not present) */
438 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
439 if (fieldptr != NULL) {
440 imap_output_envelope_addr(fieldptr);
444 imap_output_envelope_from(msg);
448 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
449 if (fieldptr != NULL) {
450 imap_output_envelope_addr(fieldptr);
454 imap_output_envelope_from(msg);
458 imap_output_envelope_addr(msg->cm_fields['R']);
460 /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
461 fieldptr = msg->cm_fields['Y'];
462 if (fieldptr != NULL) {
463 imap_output_envelope_addr(fieldptr);
466 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
467 imap_output_envelope_addr(fieldptr);
468 if (fieldptr != NULL) free(fieldptr);
472 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
473 imap_output_envelope_addr(fieldptr);
474 if (fieldptr != NULL) free(fieldptr);
477 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
478 imap_strout(fieldptr);
480 if (fieldptr != NULL) free(fieldptr);
483 imap_strout(msg->cm_fields['I']);
489 * This function is called only when CC->redirect_buffer contains a set of
490 * RFC822 headers with no body attached. Its job is to strip that set of
491 * headers down to *only* the ones we're interested in.
493 void imap_strip_headers(char *section) {
495 char *which_fields = NULL;
496 int doing_headers = 0;
501 char *boiled_headers = NULL;
503 int done_headers = 0;
506 if (CC->redirect_buffer == NULL) return;
508 which_fields = strdup(section);
510 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
512 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
515 for (i=0; which_fields[i]; ++i) {
516 if (which_fields[i]=='(')
517 strcpy(which_fields, &which_fields[i+1]);
519 for (i=0; which_fields[i]; ++i) {
520 if (which_fields[i]==')') {
525 num_parms = imap_parameterize(parms, which_fields);
527 boiled_headers = malloc(CC->redirect_alloc);
528 strcpy(boiled_headers, "");
530 ptr = CC->redirect_buffer;
532 while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
533 if (!isspace(buf[0])) {
535 if (doing_headers == 0) ok = 1;
537 if (headers_not) ok = 1;
539 for (i=0; i<num_parms; ++i) {
540 if ( (!strncasecmp(buf, parms[i],
541 strlen(parms[i]))) &&
542 (buf[strlen(parms[i])]==':') ) {
543 if (headers_not) ok = 0;
551 strcat(boiled_headers, buf);
552 strcat(boiled_headers, "\r\n");
555 if (IsEmptyStr(buf)) done_headers = 1;
556 if (buf[0]=='\r') done_headers = 1;
557 if (buf[0]=='\n') done_headers = 1;
560 strcat(boiled_headers, "\r\n");
562 /* Now save it back (it'll always be smaller) */
563 strcpy(CC->redirect_buffer, boiled_headers);
564 CC->redirect_len = strlen(boiled_headers);
567 free(boiled_headers);
572 * Implements the BODY and BODY.PEEK fetch items
574 void imap_fetch_body(long msgnum, char *item, int is_peek) {
575 struct CtdlMessage *msg = NULL;
579 size_t pstart, pbytes;
580 int loading_body_now = 0;
582 int burn_the_cache = 0;
584 /* extract section */
585 safestrncpy(section, item, sizeof section);
586 if (strchr(section, '[') != NULL) {
587 stripallbut(section, '[', ']');
589 lprintf(CTDL_DEBUG, "Section is: %s%s\n",
591 IsEmptyStr(section) ? "(empty)" : "");
594 * We used to have this great optimization in place that would avoid
595 * fetching the entire RFC822 message from disk if the client was only
596 * asking for the headers. Unfortunately, fetching only the Citadel
597 * headers omits "Content-type:" and this behavior breaks the iPhone
598 * email client. So we have to fetch the whole message from disk. The
600 * if (!strncasecmp(section, "HEADER", 6)) {
606 /* Burn the cache if we don't have the same section of the
607 * same message again.
609 if (IMAP->cached_body != NULL) {
610 if (IMAP->cached_bodymsgnum != msgnum) {
613 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
616 else if (strcasecmp(IMAP->cached_bodypart, section)) {
619 if (burn_the_cache) {
620 /* Yup, go ahead and burn the cache. */
621 free(IMAP->cached_body);
622 IMAP->cached_body_len = 0;
623 IMAP->cached_body = NULL;
624 IMAP->cached_bodymsgnum = (-1);
625 strcpy(IMAP->cached_bodypart, "");
629 /* extract partial */
630 safestrncpy(partial, item, sizeof partial);
631 if (strchr(partial, '<') != NULL) {
632 stripallbut(partial, '<', '>');
635 if (is_partial == 0) strcpy(partial, "");
636 /* if (!IsEmptyStr(partial)) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
638 if (IMAP->cached_body == NULL) {
639 CC->redirect_buffer = malloc(SIZ);
640 CC->redirect_len = 0;
641 CC->redirect_alloc = SIZ;
642 loading_body_now = 1;
643 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
646 /* Now figure out what the client wants, and get it */
648 if (!loading_body_now) {
649 /* What we want is already in memory */
652 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
653 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
656 else if (!strcmp(section, "")) {
657 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
661 * If the client asked for just headers, or just particular header
662 * fields, strip it down.
664 else if (!strncasecmp(section, "HEADER", 6)) {
665 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
666 imap_strip_headers(section);
670 * Strip it down if the client asked for everything _except_ headers.
672 else if (!strncasecmp(section, "TEXT", 4)) {
673 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
677 * Anything else must be a part specifier.
678 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
681 mime_parser(msg->cm_fields['M'], NULL,
682 *imap_load_part, NULL, NULL,
687 if (loading_body_now) {
688 IMAP->cached_body = CC->redirect_buffer;
689 IMAP->cached_body_len = CC->redirect_len;
690 IMAP->cached_bodymsgnum = msgnum;
691 IMAP->cached_body_withbody = need_body;
692 strcpy(IMAP->cached_bodypart, section);
693 CC->redirect_buffer = NULL;
694 CC->redirect_len = 0;
695 CC->redirect_alloc = 0;
698 if (is_partial == 0) {
699 cprintf("BODY[%s] {" SIZE_T_FMT "}\r\n", section, IMAP->cached_body_len);
701 pbytes = IMAP->cached_body_len;
704 sscanf(partial, SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
705 if (pbytes > (IMAP->cached_body_len - pstart)) {
706 pbytes = IMAP->cached_body_len - pstart;
708 cprintf("BODY[%s]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", section, pstart, pbytes);
711 /* Here we go -- output it */
712 client_write(&IMAP->cached_body[pstart], pbytes);
715 CtdlFreeMessage(msg);
718 /* Mark this message as "seen" *unless* this is a "peek" operation */
720 CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
725 * Called immediately before outputting a multipart bodystructure
727 void imap_fetch_bodystructure_pre(
728 char *name, char *filename, char *partnum, char *disp,
729 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
739 * Called immediately after outputting a multipart bodystructure
741 void imap_fetch_bodystructure_post(
742 char *name, char *filename, char *partnum, char *disp,
743 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
752 extract_token(subtype, cbtype, 1, '/', sizeof subtype);
753 imap_strout(subtype);
756 /* cprintf(" NIL"); We thought we needed this at one point, but maybe we don't... */
764 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
767 void imap_fetch_bodystructure_part(
768 char *name, char *filename, char *partnum, char *disp,
769 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
774 int have_encoding = 0;
777 char cbmaintype[128];
780 if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
782 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
783 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
786 strcpy(cbmaintype, "TEXT");
787 strcpy(cbsubtype, "PLAIN");
791 imap_strout(cbmaintype); /* body type */
793 imap_strout(cbsubtype); /* body subtype */
796 cprintf("("); /* begin body parameter list */
798 /* "NAME" must appear as the first parameter. This is not required by IMAP,
799 * but the Asterisk voicemail application blindly assumes that NAME will be in
800 * the first position. If it isn't, it rejects the message.
802 if (name != NULL) if (!IsEmptyStr(name)) {
803 cprintf("\"NAME\" ");
808 cprintf("\"CHARSET\" ");
809 if (cbcharset == NULL) {
810 imap_strout("US-ASCII");
812 else if (cbcharset[0] == 0) {
813 imap_strout("US-ASCII");
816 imap_strout(cbcharset);
818 cprintf(") "); /* end body parameter list */
820 cprintf("NIL "); /* Body ID */
821 cprintf("NIL "); /* Body description */
823 if (encoding != NULL) if (encoding[0] != 0) have_encoding = 1;
825 imap_strout(encoding);
832 /* The next field is the size of the part in bytes. */
833 cprintf("%ld ", (long)length); /* bytes */
835 /* The next field is the number of lines in the part, if and only
836 * if the part is TEXT. More gratuitous complexity.
838 if (!strcasecmp(cbmaintype, "TEXT")) {
839 if (length) for (i=0; i<length; ++i) {
840 if (((char *)content)[i] == '\n') ++lines;
842 cprintf("%d ", lines);
845 /* More gratuitous complexity */
846 if ((!strcasecmp(cbmaintype, "MESSAGE"))
847 && (!strcasecmp(cbsubtype, "RFC822"))) {
849 A body type of type MESSAGE and subtype RFC822
850 contains, immediately after the basic fields, the
851 envelope structure, body structure, and size in
852 text lines of the encapsulated message.
856 /* MD5 value of body part; we can get away with NIL'ing this */
863 else if (IsEmptyStr(disp)) {
869 if (filename != NULL) if (!IsEmptyStr(filename)) {
870 cprintf(" (\"FILENAME\" ");
871 imap_strout(filename);
877 /* Body language (not defined yet) */
884 * Spew the BODYSTRUCTURE data for a message.
887 void imap_fetch_bodystructure (long msgnum, char *item,
888 struct CtdlMessage *msg) {
890 char *rfc822_body = NULL;
892 size_t rfc822_headers_len;
893 size_t rfc822_body_len;
898 /* Handle NULL message gracefully */
900 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
901 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
906 /* For non-RFC822 (ordinary Citadel) messages, this is short and
909 if (msg->cm_format_type != FMT_RFC822) {
911 /* *sigh* We have to RFC822-format the message just to be able
912 * to measure it. FIXME use smi cached fields if possible
915 CC->redirect_buffer = malloc(SIZ);
916 CC->redirect_len = 0;
917 CC->redirect_alloc = SIZ;
918 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1);
919 rfc822 = CC->redirect_buffer;
920 rfc822_len = CC->redirect_len;
921 CC->redirect_buffer = NULL;
922 CC->redirect_len = 0;
923 CC->redirect_alloc = 0;
926 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
928 if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
933 rfc822_headers_len = rfc822_body - rfc822;
934 rfc822_body_len = rfc822_len - rfc822_headers_len;
937 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
938 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
939 "\"7BIT\" " SIZE_T_FMT " %d)", rfc822_body_len, lines);
944 /* For messages already stored in RFC822 format, we have to parse. */
945 cprintf("BODYSTRUCTURE ");
946 mime_parser(msg->cm_fields['M'],
948 *imap_fetch_bodystructure_part, /* part */
949 *imap_fetch_bodystructure_pre, /* pre-multi */
950 *imap_fetch_bodystructure_post, /* post-multi */
952 1); /* don't decode -- we want it as-is */
957 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
958 * individual message, once it has been selected for output.
960 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
962 struct CtdlMessage *msg = NULL;
965 /* Don't attempt to fetch bogus messages or UID's */
967 if (IMAP->msgids[seq-1] < 1L) return;
970 cprintf("* %d FETCH (", seq);
972 for (i=0; i<num_items; ++i) {
974 /* Fetchable without going to the message store at all */
975 if (!strcasecmp(itemlist[i], "UID")) {
978 else if (!strcasecmp(itemlist[i], "FLAGS")) {
979 imap_fetch_flags(seq-1);
982 /* Potentially fetchable from cache, if the client requests
983 * stuff from the same message several times in a row.
985 else if (!strcasecmp(itemlist[i], "RFC822")) {
986 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
988 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
989 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
991 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
992 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
994 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
995 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
998 /* BODY fetches do their own fetching and caching too. */
999 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
1000 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
1002 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
1003 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
1006 /* Otherwise, load the message into memory.
1008 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
1009 if ((msg != NULL) && (!body_loaded)) {
1010 CtdlFreeMessage(msg); /* need the whole thing */
1014 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
1017 imap_fetch_bodystructure(IMAP->msgids[seq-1],
1020 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
1022 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1025 imap_fetch_envelope(msg);
1027 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1029 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1032 imap_fetch_internaldate(msg);
1035 if (i != num_items-1) cprintf(" ");
1041 CtdlFreeMessage(msg);
1048 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1049 * validated and boiled down the request a bit.
1051 void imap_do_fetch(int num_items, char **itemlist) {
1054 if (IMAP->num_msgs > 0) {
1055 for (i = 0; i < IMAP->num_msgs; ++i) {
1057 /* Abort the fetch loop if the session breaks.
1058 * This is important for users who keep mailboxes
1059 * that are too big *and* are too impatient to
1060 * let them finish loading. :)
1062 if (CC->kill_me) return;
1064 /* Get any message marked for fetch. */
1065 if (IMAP->flags[i] & IMAP_SELECTED) {
1066 imap_do_fetch_msg(i+1, num_items, itemlist);
1075 * Back end for imap_handle_macros()
1076 * Note that this function *only* looks at the beginning of the string. It
1077 * is not a generic search-and-replace function.
1079 void imap_macro_replace(char *str, char *find, char *replace) {
1083 findlen = strlen(find);
1085 if (!strncasecmp(str, find, findlen)) {
1086 if (str[findlen]==' ') {
1087 strcpy(holdbuf, &str[findlen+1]);
1088 strcpy(str, replace);
1090 strcat(str, holdbuf);
1092 if (str[findlen]==0) {
1093 strcpy(holdbuf, &str[findlen+1]);
1094 strcpy(str, replace);
1102 * Handle macros embedded in FETCH data items.
1103 * (What the heck are macros doing in a wire protocol? Are we trying to save
1104 * the computer at the other end the trouble of typing a lot of characters?)
1106 void imap_handle_macros(char *str) {
1110 for (i=0; str[i]; ++i) {
1111 if (str[i]=='(') ++nest;
1112 if (str[i]=='[') ++nest;
1113 if (str[i]=='<') ++nest;
1114 if (str[i]=='{') ++nest;
1115 if (str[i]==')') --nest;
1116 if (str[i]==']') --nest;
1117 if (str[i]=='>') --nest;
1118 if (str[i]=='}') --nest;
1121 imap_macro_replace(&str[i],
1123 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1125 imap_macro_replace(&str[i],
1129 imap_macro_replace(&str[i],
1131 "FLAGS INTERNALDATE RFC822.SIZE"
1133 imap_macro_replace(&str[i],
1135 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1143 * Break out the data items requested, possibly a parenthesized list.
1144 * Returns the number of data items, or -1 if the list is invalid.
1145 * NOTE: this function alters the string it is fed, and uses it as a buffer
1146 * to hold the data for the pointers it returns.
1148 int imap_extract_data_items(char **argv, char *items) {
1155 /* Convert all whitespace to ordinary space characters. */
1156 for (i=0; items[i]; ++i) {
1157 if (isspace(items[i])) items[i]=' ';
1160 /* Strip leading and trailing whitespace, then strip leading and
1161 * trailing parentheses if it's a list
1164 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1165 items[strlen(items)-1] = 0;
1166 strcpy(items, &items[1]);
1170 /* Parse any macro data items */
1171 imap_handle_macros(items);
1174 * Now break out the data items. We throw in one trailing space in
1175 * order to avoid having to break out the last one manually.
1179 initial_len = strlen(items);
1180 for (i=0; i<initial_len; ++i) {
1181 if (items[i]=='(') ++nest;
1182 if (items[i]=='[') ++nest;
1183 if (items[i]=='<') ++nest;
1184 if (items[i]=='{') ++nest;
1185 if (items[i]==')') --nest;
1186 if (items[i]==']') --nest;
1187 if (items[i]=='>') --nest;
1188 if (items[i]=='}') --nest;
1190 if (nest <= 0) if (items[i]==' ') {
1192 argv[num_items++] = start;
1193 start = &items[i+1];
1203 * One particularly hideous aspect of IMAP is that we have to allow the client
1204 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1205 * handles this by setting the IMAP_SELECTED flag for each message specified in
1206 * the ranges/sets, then looping through the message array, outputting messages
1207 * with the flag set. We don't bother returning an error if an out-of-range
1208 * number is specified (we just return quietly) because any client braindead
1209 * enough to request a bogus message number isn't going to notice the
1210 * difference anyway.
1212 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1213 * message included in the specified range.
1215 * Set is_uid to 1 to fetch by UID instead of sequence number.
1217 void imap_pick_range(char *supplied_range, int is_uid) {
1221 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1223 char actual_range[SIZ];
1224 struct citimap *Imap;
1227 * Handle the "ALL" macro
1229 if (!strcasecmp(supplied_range, "ALL")) {
1230 safestrncpy(actual_range, "1:*", sizeof actual_range);
1233 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1238 * Clear out the IMAP_SELECTED flags for all messages.
1240 for (i = 0; i < Imap->num_msgs; ++i) {
1241 Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
1245 * Now set it for all specified messages.
1247 num_sets = num_tokens(actual_range, ',');
1248 for (s=0; s<num_sets; ++s) {
1249 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1251 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1252 if (num_tokens(setstr, ':') >= 2) {
1253 extract_token(histr, setstr, 1, ':', sizeof histr);
1254 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1257 safestrncpy(histr, lostr, sizeof histr);
1262 /* Loop through the array, flipping bits where appropriate */
1263 for (i = 1; i <= Imap->num_msgs; ++i) {
1264 if (is_uid) { /* fetch by sequence number */
1265 if ( (Imap->msgids[i-1]>=lo)
1266 && (Imap->msgids[i-1]<=hi)) {
1267 Imap->flags[i-1] |= IMAP_SELECTED;
1270 else { /* fetch by uid */
1271 if ( (i>=lo) && (i<=hi)) {
1272 Imap->flags[i-1] |= IMAP_SELECTED;
1283 * This function is called by the main command loop.
1285 void imap_fetch(int num_parms, char *parms[]) {
1287 char *itemlist[512];
1291 if (num_parms < 4) {
1292 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1296 imap_pick_range(parms[2], 0);
1299 for (i=3; i<num_parms; ++i) {
1300 strcat(items, parms[i]);
1301 if (i < (num_parms-1)) strcat(items, " ");
1304 num_items = imap_extract_data_items(itemlist, items);
1305 if (num_items < 1) {
1306 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1310 imap_do_fetch(num_items, itemlist);
1311 cprintf("%s OK FETCH completed\r\n", parms[0]);
1315 * This function is called by the main command loop.
1317 void imap_uidfetch(int num_parms, char *parms[]) {
1319 char *itemlist[512];
1322 int have_uid_item = 0;
1324 if (num_parms < 5) {
1325 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1329 imap_pick_range(parms[3], 1);
1332 for (i=4; i<num_parms; ++i) {
1333 strcat(items, parms[i]);
1334 if (i < (num_parms-1)) strcat(items, " ");
1337 num_items = imap_extract_data_items(itemlist, items);
1338 if (num_items < 1) {
1339 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1343 /* If the "UID" item was not included, we include it implicitly
1344 * (at the beginning) because this is a UID FETCH command
1346 for (i=0; i<num_items; ++i) {
1347 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1349 if (have_uid_item == 0) {
1350 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1352 itemlist[0] = "UID";
1355 imap_do_fetch(num_items, itemlist);
1356 cprintf("%s OK UID FETCH completed\r\n", parms[0]);