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"
47 #include "internet_addressing.h"
48 #include "mime_parser.h"
49 #include "serv_imap.h"
50 #include "imap_tools.h"
51 #include "imap_fetch.h"
57 * Individual field functions for imap_do_fetch_msg() ...
60 void imap_fetch_uid(int seq) {
61 cprintf("UID %ld", IMAP->msgids[seq-1]);
64 void imap_fetch_flags(int seq) {
65 int num_flags_printed = 0;
67 if (IMAP->flags[seq] & IMAP_DELETED) {
68 if (num_flags_printed > 0) cprintf(" ");
72 if (IMAP->flags[seq] & IMAP_SEEN) {
73 if (num_flags_printed > 0) cprintf(" ");
77 if (IMAP->flags[seq] & IMAP_ANSWERED) {
78 if (num_flags_printed > 0) cprintf(" ");
79 cprintf("\\Answered");
82 if (IMAP->flags[seq] & IMAP_RECENT) {
83 if (num_flags_printed > 0) cprintf(" ");
90 void imap_fetch_internaldate(struct CtdlMessage *msg) {
95 if (msg->cm_fields['T'] != NULL) {
96 msgdate = atol(msg->cm_fields['T']);
102 datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
103 cprintf("INTERNALDATE \"%s\"", buf);
108 * Fetch RFC822-formatted messages.
110 * 'whichfmt' should be set to one of:
111 * "RFC822" entire message
112 * "RFC822.HEADER" headers only (with trailing blank line)
113 * "RFC822.SIZE" size of translated message
114 * "RFC822.TEXT" body only (without leading blank line)
116 void imap_fetch_rfc822(long msgnum, char *whichfmt) {
119 size_t headers_size, text_size, total_size;
120 size_t bytes_to_send = 0;
122 int need_to_rewrite_metadata = 0;
125 /* Determine whether this particular fetch operation requires
126 * us to fetch the message body from disk. If not, we can save
127 * on some disk operations...
129 if ( (!strcasecmp(whichfmt, "RFC822"))
130 || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
134 /* If this is an RFC822.SIZE fetch, first look in the message's
135 * metadata record to see if we've saved that information.
137 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
138 GetMetaData(&smi, msgnum);
139 if (smi.meta_rfc822_length > 0L) {
140 cprintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
143 need_to_rewrite_metadata = 1;
147 /* Cache the most recent RFC822 FETCH because some clients like to
148 * fetch in pieces, and we don't want to have to go back to the
149 * message store for each piece. We also burn the cache if the
150 * client requests something that involves reading the message
151 * body, but we haven't fetched the body yet.
153 if ((IMAP->cached_rfc822_data != NULL)
154 && (IMAP->cached_rfc822_msgnum == msgnum)
155 && (IMAP->cached_rfc822_withbody || (!need_body)) ) {
158 else if (IMAP->cached_rfc822_data != NULL) {
159 /* Some other message is cached -- free it */
160 free(IMAP->cached_rfc822_data);
161 IMAP->cached_rfc822_data = NULL;
162 IMAP->cached_rfc822_msgnum = (-1);
163 IMAP->cached_rfc822_len = 0;
166 /* At this point, we now can fetch and convert the message iff it's not
167 * the one we had cached.
169 if (IMAP->cached_rfc822_data == NULL) {
171 * Load the message into memory for translation & measurement
173 CC->redirect_buffer = malloc(SIZ);
174 CC->redirect_len = 0;
175 CC->redirect_alloc = SIZ;
176 CtdlOutputMsg(msgnum, MT_RFC822,
177 (need_body ? HEADERS_ALL : HEADERS_ONLY),
179 if (!need_body) cprintf("\r\n"); /* extra trailing newline */
180 IMAP->cached_rfc822_data = CC->redirect_buffer;
181 IMAP->cached_rfc822_len = CC->redirect_len;
182 IMAP->cached_rfc822_msgnum = msgnum;
183 IMAP->cached_rfc822_withbody = need_body;
184 CC->redirect_buffer = NULL;
185 CC->redirect_len = 0;
186 CC->redirect_alloc = 0;
187 if ( (need_to_rewrite_metadata) && (IMAP->cached_rfc822_len > 0) ) {
188 smi.meta_rfc822_length = (long)IMAP->cached_rfc822_len;
194 * Now figure out where the headers/text break is. IMAP considers the
195 * intervening blank line to be part of the headers, not the text.
202 ptr = IMAP->cached_rfc822_data;
204 ptr = memreadline(ptr, buf, sizeof buf);
207 if (IsEmptyStr(buf)) {
208 headers_size = ptr - IMAP->cached_rfc822_data;
211 } while ( (headers_size == 0) && (*ptr != 0) );
213 total_size = IMAP->cached_rfc822_len;
214 text_size = total_size - headers_size;
217 headers_size = IMAP->cached_rfc822_len;
218 total_size = IMAP->cached_rfc822_len;
222 lprintf(CTDL_DEBUG, "RFC822: headers=%d, text=%d, total=%d\n",
223 headers_size, text_size, total_size);
225 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
226 cprintf("RFC822.SIZE %d", total_size);
230 else if (!strcasecmp(whichfmt, "RFC822")) {
231 ptr = IMAP->cached_rfc822_data;
232 bytes_to_send = total_size;
235 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
236 ptr = IMAP->cached_rfc822_data;
237 bytes_to_send = headers_size;
240 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
241 ptr = &IMAP->cached_rfc822_data[headers_size];
242 bytes_to_send = text_size;
245 cprintf("%s {%d}\r\n", whichfmt, bytes_to_send);
246 client_write(ptr, bytes_to_send);
252 * Load a specific part of a message into the temp file to be output to a
253 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
254 * but we still can't handle "2.HEADER" (which might not be a problem).
256 * Note: mime_parser() was called with dont_decode set to 1, so we have the
257 * luxury of simply spewing without having to re-encode.
259 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
260 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
264 char *desired_section;
266 desired_section = (char *)cbuserdata;
268 if (!strcasecmp(partnum, desired_section)) {
269 client_write(content, length);
272 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
274 if (!strcasecmp(desired_section, mbuf2)) {
275 cprintf("Content-type: %s", cbtype);
276 if (!IsEmptyStr(cbcharset))
277 cprintf("; charset=\"%s\"", cbcharset);
278 if (!IsEmptyStr(name))
279 cprintf("; name=\"%s\"", name);
281 if (!IsEmptyStr(encoding))
282 cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
283 if (!IsEmptyStr(encoding)) {
284 cprintf("Content-Disposition: %s", disp);
285 if (!IsEmptyStr(filename)) {
286 cprintf("; filename=\"%s\"", filename);
290 cprintf("Content-Length: %ld\r\n", (long)length);
299 * Called by imap_fetch_envelope() to output the "From" field.
300 * This is in its own function because its logic is kind of complex. We
301 * really need to make this suck less.
303 void imap_output_envelope_from(struct CtdlMessage *msg) {
304 char user[SIZ], node[SIZ], name[SIZ];
308 /* For anonymous messages, it's so easy! */
309 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
310 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
313 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
314 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
318 /* For everything else, we do stuff. */
319 cprintf("(("); /* open double-parens */
320 imap_strout(msg->cm_fields['A']); /* personal name */
321 cprintf(" NIL "); /* source route (not used) */
324 if (msg->cm_fields['F'] != NULL) {
325 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
326 imap_strout(user); /* mailbox name (user id) */
328 if (!strcasecmp(node, config.c_nodename)) {
329 imap_strout(config.c_fqdn);
332 imap_strout(node); /* host name */
336 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
338 imap_strout(msg->cm_fields['N']); /* host name */
341 cprintf(")) "); /* close double-parens */
347 * Output an envelope address (or set of addresses) in the official,
348 * convuluted, braindead format. (Note that we can't use this for
349 * the "From" address because its data may come from a number of different
350 * fields. But we can use it for "To" and possibly others.
352 void imap_output_envelope_addr(char *addr) {
353 char individual_addr[256];
365 if (IsEmptyStr(addr)) {
372 /* How many addresses are listed here? */
373 num_addrs = num_tokens(addr, ',');
375 /* Output them one by one. */
376 for (i=0; i<num_addrs; ++i) {
377 extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
378 striplt(individual_addr);
379 process_rfc822_addr(individual_addr, user, node, name);
387 if (i < (num_addrs-1)) cprintf(" ");
395 * Implements the ENVELOPE fetch item
397 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
398 * so we don't have to check for that condition like we do elsewhere.
400 void imap_fetch_envelope(struct CtdlMessage *msg) {
401 char datestringbuf[SIZ];
403 char *fieldptr = NULL;
407 /* Parse the message date into an IMAP-format date string */
408 if (msg->cm_fields['T'] != NULL) {
409 msgdate = atol(msg->cm_fields['T']);
412 msgdate = time(NULL);
414 datestring(datestringbuf, sizeof datestringbuf,
415 msgdate, DATESTRING_IMAP);
417 /* Now start spewing data fields. The order is important, as it is
418 * defined by the protocol specification. Nonexistent fields must
419 * be output as NIL, existent fields must be quoted or literalled.
420 * The imap_strout() function conveniently does all this for us.
422 cprintf("ENVELOPE (");
425 imap_strout(datestringbuf);
429 imap_strout(msg->cm_fields['U']);
433 imap_output_envelope_from(msg);
435 /* Sender (default to same as 'From' if not present) */
436 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
437 if (fieldptr != NULL) {
438 imap_output_envelope_addr(fieldptr);
442 imap_output_envelope_from(msg);
446 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
447 if (fieldptr != NULL) {
448 imap_output_envelope_addr(fieldptr);
452 imap_output_envelope_from(msg);
456 imap_output_envelope_addr(msg->cm_fields['R']);
458 /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
459 fieldptr = msg->cm_fields['Y'];
460 if (fieldptr != NULL) {
461 imap_output_envelope_addr(fieldptr);
464 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
465 imap_output_envelope_addr(fieldptr);
466 if (fieldptr != NULL) free(fieldptr);
470 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
471 imap_output_envelope_addr(fieldptr);
472 if (fieldptr != NULL) free(fieldptr);
475 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
476 imap_strout(fieldptr);
478 if (fieldptr != NULL) free(fieldptr);
481 imap_strout(msg->cm_fields['I']);
487 * This function is called only when CC->redirect_buffer contains a set of
488 * RFC822 headers with no body attached. Its job is to strip that set of
489 * headers down to *only* the ones we're interested in.
491 void imap_strip_headers(char *section) {
493 char *which_fields = NULL;
494 int doing_headers = 0;
499 char *boiled_headers = NULL;
501 int done_headers = 0;
504 if (CC->redirect_buffer == NULL) return;
506 which_fields = strdup(section);
508 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
510 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
513 for (i=0; which_fields[i]; ++i) {
514 if (which_fields[i]=='(')
515 strcpy(which_fields, &which_fields[i+1]);
517 for (i=0; which_fields[i]; ++i) {
518 if (which_fields[i]==')') {
523 num_parms = imap_parameterize(parms, which_fields);
525 boiled_headers = malloc(CC->redirect_alloc);
526 strcpy(boiled_headers, "");
528 ptr = CC->redirect_buffer;
530 while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
531 if (!isspace(buf[0])) {
533 if (doing_headers == 0) ok = 1;
535 if (headers_not) ok = 1;
537 for (i=0; i<num_parms; ++i) {
538 if ( (!strncasecmp(buf, parms[i],
539 strlen(parms[i]))) &&
540 (buf[strlen(parms[i])]==':') ) {
541 if (headers_not) ok = 0;
549 strcat(boiled_headers, buf);
550 strcat(boiled_headers, "\r\n");
553 if (IsEmptyStr(buf)) done_headers = 1;
554 if (buf[0]=='\r') done_headers = 1;
555 if (buf[0]=='\n') done_headers = 1;
558 strcat(boiled_headers, "\r\n");
560 /* Now save it back (it'll always be smaller) */
561 strcpy(CC->redirect_buffer, boiled_headers);
562 CC->redirect_len = strlen(boiled_headers);
565 free(boiled_headers);
570 * Implements the BODY and BODY.PEEK fetch items
572 void imap_fetch_body(long msgnum, char *item, int is_peek) {
573 struct CtdlMessage *msg = NULL;
577 size_t pstart, pbytes;
578 int loading_body_now = 0;
580 int burn_the_cache = 0;
582 /* extract section */
583 safestrncpy(section, item, sizeof section);
584 if (strchr(section, '[') != NULL) {
585 stripallbut(section, '[', ']');
587 lprintf(CTDL_DEBUG, "Section is: %s%s\n",
589 IsEmptyStr(section) ? "(empty)" : "");
592 * We used to have this great optimization in place that would avoid
593 * fetching the entire RFC822 message from disk if the client was only
594 * asking for the headers. Unfortunately, fetching only the Citadel
595 * headers omits "Content-type:" and this behavior breaks the iPhone
596 * email client. So we have to fetch the whole message from disk. The
598 * if (!strncasecmp(section, "HEADER", 6)) {
604 /* Burn the cache if we don't have the same section of the
605 * same message again.
607 if (IMAP->cached_body != NULL) {
608 if (IMAP->cached_bodymsgnum != msgnum) {
611 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
614 else if (strcasecmp(IMAP->cached_bodypart, section)) {
617 if (burn_the_cache) {
618 /* Yup, go ahead and burn the cache. */
619 free(IMAP->cached_body);
620 IMAP->cached_body_len = 0;
621 IMAP->cached_body = NULL;
622 IMAP->cached_bodymsgnum = (-1);
623 strcpy(IMAP->cached_bodypart, "");
627 /* extract partial */
628 safestrncpy(partial, item, sizeof partial);
629 if (strchr(partial, '<') != NULL) {
630 stripallbut(partial, '<', '>');
633 if (is_partial == 0) strcpy(partial, "");
634 /* if (!IsEmptyStr(partial)) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
636 if (IMAP->cached_body == NULL) {
637 CC->redirect_buffer = malloc(SIZ);
638 CC->redirect_len = 0;
639 CC->redirect_alloc = SIZ;
640 loading_body_now = 1;
641 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
644 /* Now figure out what the client wants, and get it */
646 if (!loading_body_now) {
647 /* What we want is already in memory */
650 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
651 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
654 else if (!strcmp(section, "")) {
655 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
659 * If the client asked for just headers, or just particular header
660 * fields, strip it down.
662 else if (!strncasecmp(section, "HEADER", 6)) {
663 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
664 imap_strip_headers(section);
668 * Strip it down if the client asked for everything _except_ headers.
670 else if (!strncasecmp(section, "TEXT", 4)) {
671 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
675 * Anything else must be a part specifier.
676 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
679 mime_parser(msg->cm_fields['M'], NULL,
680 *imap_load_part, NULL, NULL,
685 if (loading_body_now) {
686 IMAP->cached_body = CC->redirect_buffer;
687 IMAP->cached_body_len = CC->redirect_len;
688 IMAP->cached_bodymsgnum = msgnum;
689 IMAP->cached_body_withbody = need_body;
690 strcpy(IMAP->cached_bodypart, section);
691 CC->redirect_buffer = NULL;
692 CC->redirect_len = 0;
693 CC->redirect_alloc = 0;
696 if (is_partial == 0) {
697 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
699 pbytes = IMAP->cached_body_len;
702 sscanf(partial, "%d.%d", &pstart, &pbytes);
703 if (pbytes > (IMAP->cached_body_len - pstart)) {
704 pbytes = IMAP->cached_body_len - pstart;
706 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
709 /* Here we go -- output it */
710 client_write(&IMAP->cached_body[pstart], pbytes);
713 CtdlFreeMessage(msg);
716 /* Mark this message as "seen" *unless* this is a "peek" operation */
718 CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
723 * Called immediately before outputting a multipart bodystructure
725 void imap_fetch_bodystructure_pre(
726 char *name, char *filename, char *partnum, char *disp,
727 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
737 * Called immediately after outputting a multipart bodystructure
739 void imap_fetch_bodystructure_post(
740 char *name, char *filename, char *partnum, char *disp,
741 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
750 extract_token(subtype, cbtype, 1, '/', sizeof subtype);
751 imap_strout(subtype);
762 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
765 void imap_fetch_bodystructure_part(
766 char *name, char *filename, char *partnum, char *disp,
767 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
772 int have_encoding = 0;
775 char cbmaintype[128];
779 if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
781 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
782 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
785 strcpy(cbmaintype, "TEXT");
786 strcpy(cbsubtype, "PLAIN");
789 /* If this is the second or subsequent part of a multipart sequence,
790 * we need to output another space here. We do this by obtaining the
791 * last subpart token of the partnum and converting it to a number.
793 if (strrchr(partnum, '.')) {
794 safestrncpy(iteration, (strrchr(partnum, '.')+1), sizeof iteration);
797 safestrncpy(iteration, partnum, sizeof iteration);
799 if (atoi(iteration) > 1) {
807 imap_strout(cbmaintype);
809 imap_strout(cbsubtype);
812 if (cbcharset == NULL) {
813 cprintf("(\"CHARSET\" \"US-ASCII\"");
815 else if (IsEmptyStr(cbcharset)) {
816 cprintf("(\"CHARSET\" \"US-ASCII\"");
819 cprintf("(\"CHARSET\" ");
820 imap_strout(cbcharset);
823 if (name != NULL) if (!IsEmptyStr(name)) {
824 cprintf(" \"NAME\" ");
830 cprintf("NIL "); /* Body ID */
831 cprintf("NIL "); /* Body description */
833 if (encoding != NULL) if (!IsEmptyStr(encoding)) have_encoding = 1;
835 imap_strout(encoding);
842 /* The next field is the size of the part in bytes. */
843 cprintf("%ld ", (long)length); /* bytes */
845 /* The next field is the number of lines in the part, if and only
846 * if the part is TEXT. More gratuitous complexity.
848 if (!strcasecmp(cbmaintype, "TEXT")) {
849 if (length) for (i=0; i<length; ++i) {
850 if (((char *)content)[i] == '\n') ++lines;
852 cprintf("%d ", lines);
855 /* More gratuitous complexity */
856 if ((!strcasecmp(cbmaintype, "MESSAGE"))
857 && (!strcasecmp(cbsubtype, "RFC822"))) {
859 A body type of type MESSAGE and subtype RFC822
860 contains, immediately after the basic fields, the
861 envelope structure, body structure, and size in
862 text lines of the encapsulated message.
866 /* MD5 value of body part; we can get away with NIL'ing this */
873 else if (IsEmptyStr(disp)) {
879 if (filename != NULL) if (!IsEmptyStr(filename)) {
880 cprintf(" (\"FILENAME\" ");
881 imap_strout(filename);
887 /* Body language (not defined yet) */
894 * Spew the BODYSTRUCTURE data for a message.
897 void imap_fetch_bodystructure (long msgnum, char *item,
898 struct CtdlMessage *msg) {
900 char *rfc822_body = NULL;
902 size_t rfc822_headers_len;
903 size_t rfc822_body_len;
908 /* Handle NULL message gracefully */
910 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
911 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
916 /* For non-RFC822 (ordinary Citadel) messages, this is short and
919 if (msg->cm_format_type != FMT_RFC822) {
921 /* *sigh* We have to RFC822-format the message just to be able
922 * to measure it. FIXME use smi cached fields if possible
925 CC->redirect_buffer = malloc(SIZ);
926 CC->redirect_len = 0;
927 CC->redirect_alloc = SIZ;
928 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1);
929 rfc822 = CC->redirect_buffer;
930 rfc822_len = CC->redirect_len;
931 CC->redirect_buffer = NULL;
932 CC->redirect_len = 0;
933 CC->redirect_alloc = 0;
936 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
938 if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
943 rfc822_headers_len = rfc822_body - rfc822;
944 rfc822_body_len = rfc822_len - rfc822_headers_len;
947 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
948 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
949 "\"7BIT\" %d %d)", rfc822_body_len, lines);
954 /* For messages already stored in RFC822 format, we have to parse. */
955 cprintf("BODYSTRUCTURE ");
956 mime_parser(msg->cm_fields['M'],
958 *imap_fetch_bodystructure_part, /* part */
959 *imap_fetch_bodystructure_pre, /* pre-multi */
960 *imap_fetch_bodystructure_post, /* post-multi */
962 1); /* don't decode -- we want it as-is */
967 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
968 * individual message, once it has been selected for output.
970 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
972 struct CtdlMessage *msg = NULL;
975 /* Don't attempt to fetch bogus messages or UID's */
977 if (IMAP->msgids[seq-1] < 1L) return;
980 cprintf("* %d FETCH (", seq);
982 for (i=0; i<num_items; ++i) {
984 /* Fetchable without going to the message store at all */
985 if (!strcasecmp(itemlist[i], "UID")) {
988 else if (!strcasecmp(itemlist[i], "FLAGS")) {
989 imap_fetch_flags(seq-1);
992 /* Potentially fetchable from cache, if the client requests
993 * stuff from the same message several times in a row.
995 else if (!strcasecmp(itemlist[i], "RFC822")) {
996 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
998 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
999 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
1001 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
1002 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
1004 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
1005 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
1008 /* BODY fetches do their own fetching and caching too. */
1009 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
1010 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
1012 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
1013 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
1016 /* Otherwise, load the message into memory.
1018 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
1019 if ((msg != NULL) && (!body_loaded)) {
1020 CtdlFreeMessage(msg); /* need the whole thing */
1024 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
1027 imap_fetch_bodystructure(IMAP->msgids[seq-1],
1030 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
1032 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1035 imap_fetch_envelope(msg);
1037 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1039 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1042 imap_fetch_internaldate(msg);
1045 if (i != num_items-1) cprintf(" ");
1051 CtdlFreeMessage(msg);
1058 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1059 * validated and boiled down the request a bit.
1061 void imap_do_fetch(int num_items, char **itemlist) {
1064 if (IMAP->num_msgs > 0) {
1065 for (i = 0; i < IMAP->num_msgs; ++i) {
1067 /* Abort the fetch loop if the session breaks.
1068 * This is important for users who keep mailboxes
1069 * that are too big *and* are too impatient to
1070 * let them finish loading. :)
1072 if (CC->kill_me) return;
1074 /* Get any message marked for fetch. */
1075 if (IMAP->flags[i] & IMAP_SELECTED) {
1076 imap_do_fetch_msg(i+1, num_items, itemlist);
1085 * Back end for imap_handle_macros()
1086 * Note that this function *only* looks at the beginning of the string. It
1087 * is not a generic search-and-replace function.
1089 void imap_macro_replace(char *str, char *find, char *replace) {
1092 if (!strncasecmp(str, find, strlen(find))) {
1093 if (str[strlen(find)]==' ') {
1094 strcpy(holdbuf, &str[strlen(find)+1]);
1095 strcpy(str, replace);
1097 strcat(str, holdbuf);
1099 if (str[strlen(find)]==0) {
1100 strcpy(holdbuf, &str[strlen(find)+1]);
1101 strcpy(str, replace);
1109 * Handle macros embedded in FETCH data items.
1110 * (What the heck are macros doing in a wire protocol? Are we trying to save
1111 * the computer at the other end the trouble of typing a lot of characters?)
1113 void imap_handle_macros(char *str) {
1117 for (i=0; str[i]; ++i) {
1118 if (str[i]=='(') ++nest;
1119 if (str[i]=='[') ++nest;
1120 if (str[i]=='<') ++nest;
1121 if (str[i]=='{') ++nest;
1122 if (str[i]==')') --nest;
1123 if (str[i]==']') --nest;
1124 if (str[i]=='>') --nest;
1125 if (str[i]=='}') --nest;
1128 imap_macro_replace(&str[i],
1130 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1132 imap_macro_replace(&str[i],
1136 imap_macro_replace(&str[i],
1138 "FLAGS INTERNALDATE RFC822.SIZE"
1140 imap_macro_replace(&str[i],
1142 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1150 * Break out the data items requested, possibly a parenthesized list.
1151 * Returns the number of data items, or -1 if the list is invalid.
1152 * NOTE: this function alters the string it is fed, and uses it as a buffer
1153 * to hold the data for the pointers it returns.
1155 int imap_extract_data_items(char **argv, char *items) {
1162 /* Convert all whitespace to ordinary space characters. */
1163 for (i=0; items[i]; ++i) {
1164 if (isspace(items[i])) items[i]=' ';
1167 /* Strip leading and trailing whitespace, then strip leading and
1168 * trailing parentheses if it's a list
1171 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1172 items[strlen(items)-1] = 0;
1173 strcpy(items, &items[1]);
1177 /* Parse any macro data items */
1178 imap_handle_macros(items);
1181 * Now break out the data items. We throw in one trailing space in
1182 * order to avoid having to break out the last one manually.
1186 initial_len = strlen(items);
1187 for (i=0; i<initial_len; ++i) {
1188 if (items[i]=='(') ++nest;
1189 if (items[i]=='[') ++nest;
1190 if (items[i]=='<') ++nest;
1191 if (items[i]=='{') ++nest;
1192 if (items[i]==')') --nest;
1193 if (items[i]==']') --nest;
1194 if (items[i]=='>') --nest;
1195 if (items[i]=='}') --nest;
1197 if (nest <= 0) if (items[i]==' ') {
1199 argv[num_items++] = start;
1200 start = &items[i+1];
1210 * One particularly hideous aspect of IMAP is that we have to allow the client
1211 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1212 * handles this by setting the IMAP_SELECTED flag for each message specified in
1213 * the ranges/sets, then looping through the message array, outputting messages
1214 * with the flag set. We don't bother returning an error if an out-of-range
1215 * number is specified (we just return quietly) because any client braindead
1216 * enough to request a bogus message number isn't going to notice the
1217 * difference anyway.
1219 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1220 * message included in the specified range.
1222 * Set is_uid to 1 to fetch by UID instead of sequence number.
1224 void imap_pick_range(char *supplied_range, int is_uid) {
1228 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1230 char actual_range[SIZ];
1233 * Handle the "ALL" macro
1235 if (!strcasecmp(supplied_range, "ALL")) {
1236 safestrncpy(actual_range, "1:*", sizeof actual_range);
1239 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1243 * Clear out the IMAP_SELECTED flags for all messages.
1245 for (i = 0; i < IMAP->num_msgs; ++i) {
1246 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1250 * Now set it for all specified messages.
1252 num_sets = num_tokens(actual_range, ',');
1253 for (s=0; s<num_sets; ++s) {
1254 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1256 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1257 if (num_tokens(setstr, ':') >= 2) {
1258 extract_token(histr, setstr, 1, ':', sizeof histr);
1259 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1262 safestrncpy(histr, lostr, sizeof histr);
1267 /* Loop through the array, flipping bits where appropriate */
1268 for (i = 1; i <= IMAP->num_msgs; ++i) {
1269 if (is_uid) { /* fetch by sequence number */
1270 if ( (IMAP->msgids[i-1]>=lo)
1271 && (IMAP->msgids[i-1]<=hi)) {
1272 IMAP->flags[i-1] |= IMAP_SELECTED;
1275 else { /* fetch by uid */
1276 if ( (i>=lo) && (i<=hi)) {
1277 IMAP->flags[i-1] |= IMAP_SELECTED;
1288 * This function is called by the main command loop.
1290 void imap_fetch(int num_parms, char *parms[]) {
1292 char *itemlist[512];
1296 if (num_parms < 4) {
1297 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1301 imap_pick_range(parms[2], 0);
1304 for (i=3; i<num_parms; ++i) {
1305 strcat(items, parms[i]);
1306 if (i < (num_parms-1)) strcat(items, " ");
1309 num_items = imap_extract_data_items(itemlist, items);
1310 if (num_items < 1) {
1311 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1315 imap_do_fetch(num_items, itemlist);
1316 cprintf("%s OK FETCH completed\r\n", parms[0]);
1320 * This function is called by the main command loop.
1322 void imap_uidfetch(int num_parms, char *parms[]) {
1324 char *itemlist[512];
1327 int have_uid_item = 0;
1329 if (num_parms < 5) {
1330 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1334 imap_pick_range(parms[3], 1);
1337 for (i=4; i<num_parms; ++i) {
1338 strcat(items, parms[i]);
1339 if (i < (num_parms-1)) strcat(items, " ");
1342 num_items = imap_extract_data_items(itemlist, items);
1343 if (num_items < 1) {
1344 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1348 /* If the "UID" item was not included, we include it implicitly
1349 * (at the beginning) because this is a UID FETCH command
1351 for (i=0; i<num_items; ++i) {
1352 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1354 if (have_uid_item == 0) {
1355 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1357 itemlist[0] = "UID";
1360 imap_do_fetch(num_items, itemlist);
1361 cprintf("%s OK UID FETCH completed\r\n", parms[0]);