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"
52 #include "ctdl_module.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_FAST),
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 CtdlLogPrintf(CTDL_DEBUG,
223 "RFC822: headers=" SIZE_T_FMT
225 ", total=" SIZE_T_FMT "\n",
226 headers_size, text_size, total_size);
228 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
229 cprintf("RFC822.SIZE " SIZE_T_FMT, total_size);
233 else if (!strcasecmp(whichfmt, "RFC822")) {
234 ptr = IMAP->cached_rfc822_data;
235 bytes_to_send = total_size;
238 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
239 ptr = IMAP->cached_rfc822_data;
240 bytes_to_send = headers_size;
243 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
244 ptr = &IMAP->cached_rfc822_data[headers_size];
245 bytes_to_send = text_size;
248 cprintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send);
249 client_write(ptr, bytes_to_send);
255 * Load a specific part of a message into the temp file to be output to a
256 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
257 * but we still can't handle "2.HEADER" (which might not be a problem).
259 * Note: mime_parser() was called with dont_decode set to 1, so we have the
260 * luxury of simply spewing without having to re-encode.
262 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
263 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
264 char *cbid, void *cbuserdata)
267 char *desired_section;
269 desired_section = (char *)cbuserdata;
271 if (!strcasecmp(partnum, desired_section)) {
272 client_write(content, length);
275 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
277 if (!strcasecmp(desired_section, mbuf2)) {
278 cprintf("Content-type: %s", cbtype);
279 if (!IsEmptyStr(cbcharset))
280 cprintf("; charset=\"%s\"", cbcharset);
281 if (!IsEmptyStr(name))
282 cprintf("; name=\"%s\"", name);
284 if (!IsEmptyStr(encoding))
285 cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
286 if (!IsEmptyStr(encoding)) {
287 cprintf("Content-Disposition: %s", disp);
288 if (!IsEmptyStr(filename)) {
289 cprintf("; filename=\"%s\"", filename);
293 cprintf("Content-Length: %ld\r\n", (long)length);
302 * Called by imap_fetch_envelope() to output the "From" field.
303 * This is in its own function because its logic is kind of complex. We
304 * really need to make this suck less.
306 void imap_output_envelope_from(struct CtdlMessage *msg) {
307 char user[SIZ], node[SIZ], name[SIZ];
311 /* For anonymous messages, it's so easy! */
312 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
313 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
316 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
317 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
321 /* For everything else, we do stuff. */
322 cprintf("(("); /* open double-parens */
323 imap_strout(msg->cm_fields['A']); /* personal name */
324 cprintf(" NIL "); /* source route (not used) */
327 if (msg->cm_fields['F'] != NULL) {
328 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
329 imap_strout(user); /* mailbox name (user id) */
331 if (!strcasecmp(node, config.c_nodename)) {
332 imap_strout(config.c_fqdn);
335 imap_strout(node); /* host name */
339 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
341 imap_strout(msg->cm_fields['N']); /* host name */
344 cprintf(")) "); /* close double-parens */
350 * Output an envelope address (or set of addresses) in the official,
351 * convuluted, braindead format. (Note that we can't use this for
352 * the "From" address because its data may come from a number of different
353 * fields. But we can use it for "To" and possibly others.
355 void imap_output_envelope_addr(char *addr) {
356 char individual_addr[256];
368 if (IsEmptyStr(addr)) {
375 /* How many addresses are listed here? */
376 num_addrs = num_tokens(addr, ',');
378 /* Output them one by one. */
379 for (i=0; i<num_addrs; ++i) {
380 extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
381 striplt(individual_addr);
382 process_rfc822_addr(individual_addr, user, node, name);
390 if (i < (num_addrs-1)) cprintf(" ");
398 * Implements the ENVELOPE fetch item
400 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
401 * so we don't have to check for that condition like we do elsewhere.
403 void imap_fetch_envelope(struct CtdlMessage *msg) {
404 char datestringbuf[SIZ];
406 char *fieldptr = NULL;
410 /* Parse the message date into an IMAP-format date string */
411 if (msg->cm_fields['T'] != NULL) {
412 msgdate = atol(msg->cm_fields['T']);
415 msgdate = time(NULL);
417 datestring(datestringbuf, sizeof datestringbuf,
418 msgdate, DATESTRING_IMAP);
420 /* Now start spewing data fields. The order is important, as it is
421 * defined by the protocol specification. Nonexistent fields must
422 * be output as NIL, existent fields must be quoted or literalled.
423 * The imap_strout() function conveniently does all this for us.
425 cprintf("ENVELOPE (");
428 imap_strout(datestringbuf);
432 imap_strout(msg->cm_fields['U']);
436 imap_output_envelope_from(msg);
438 /* Sender (default to same as 'From' if not present) */
439 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
440 if (fieldptr != NULL) {
441 imap_output_envelope_addr(fieldptr);
445 imap_output_envelope_from(msg);
449 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
450 if (fieldptr != NULL) {
451 imap_output_envelope_addr(fieldptr);
455 imap_output_envelope_from(msg);
459 imap_output_envelope_addr(msg->cm_fields['R']);
461 /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
462 fieldptr = msg->cm_fields['Y'];
463 if (fieldptr != NULL) {
464 imap_output_envelope_addr(fieldptr);
467 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
468 imap_output_envelope_addr(fieldptr);
469 if (fieldptr != NULL) free(fieldptr);
473 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
474 imap_output_envelope_addr(fieldptr);
475 if (fieldptr != NULL) free(fieldptr);
478 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
479 imap_strout(fieldptr);
481 if (fieldptr != NULL) free(fieldptr);
484 imap_strout(msg->cm_fields['I']);
490 * This function is called only when CC->redirect_buffer contains a set of
491 * RFC822 headers with no body attached. Its job is to strip that set of
492 * headers down to *only* the ones we're interested in.
494 void imap_strip_headers(char *section) {
496 char *which_fields = NULL;
497 int doing_headers = 0;
502 char *boiled_headers = NULL;
504 int done_headers = 0;
507 if (CC->redirect_buffer == NULL) return;
509 which_fields = strdup(section);
511 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
513 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
516 for (i=0; which_fields[i]; ++i) {
517 if (which_fields[i]=='(')
518 strcpy(which_fields, &which_fields[i+1]);
520 for (i=0; which_fields[i]; ++i) {
521 if (which_fields[i]==')') {
526 num_parms = imap_parameterize(parms, which_fields);
528 boiled_headers = malloc(CC->redirect_alloc);
529 strcpy(boiled_headers, "");
531 ptr = CC->redirect_buffer;
534 ptr = memreadline(ptr, buf, sizeof buf);
535 if (!isspace(buf[0])) {
537 if (doing_headers == 0) ok = 1;
539 if (headers_not) ok = 1;
541 for (i=0; i<num_parms; ++i) {
542 if ( (!strncasecmp(buf, parms[i],
543 strlen(parms[i]))) &&
544 (buf[strlen(parms[i])]==':') ) {
545 if (headers_not) ok = 0;
553 strcat(boiled_headers, buf);
554 strcat(boiled_headers, "\r\n");
557 if (IsEmptyStr(buf)) done_headers = 1;
558 if (buf[0]=='\r') done_headers = 1;
559 if (buf[0]=='\n') done_headers = 1;
560 if (*ptr == 0) done_headers = 1;
561 } while (!done_headers);
563 strcat(boiled_headers, "\r\n");
565 /* Now save it back (it'll always be smaller) */
566 strcpy(CC->redirect_buffer, boiled_headers);
567 CC->redirect_len = strlen(boiled_headers);
570 free(boiled_headers);
575 * Implements the BODY and BODY.PEEK fetch items
577 void imap_fetch_body(long msgnum, char *item, int is_peek) {
578 struct CtdlMessage *msg = NULL;
582 size_t pstart, pbytes;
583 int loading_body_now = 0;
585 int burn_the_cache = 0;
587 /* extract section */
588 safestrncpy(section, item, sizeof section);
589 if (strchr(section, '[') != NULL) {
590 stripallbut(section, '[', ']');
592 CtdlLogPrintf(CTDL_DEBUG, "Section is: %s%s\n",
594 IsEmptyStr(section) ? "(empty)" : "");
597 * We used to have this great optimization in place that would avoid
598 * fetching the entire RFC822 message from disk if the client was only
599 * asking for the headers. Unfortunately, fetching only the Citadel
600 * headers omits "Content-type:" and this behavior breaks the iPhone
601 * email client. So we have to fetch the whole message from disk. The
603 * if (!strncasecmp(section, "HEADER", 6)) {
609 /* Burn the cache if we don't have the same section of the
610 * same message again.
612 if (IMAP->cached_body != NULL) {
613 if (IMAP->cached_bodymsgnum != msgnum) {
616 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
619 else if (strcasecmp(IMAP->cached_bodypart, section)) {
622 if (burn_the_cache) {
623 /* Yup, go ahead and burn the cache. */
624 free(IMAP->cached_body);
625 IMAP->cached_body_len = 0;
626 IMAP->cached_body = NULL;
627 IMAP->cached_bodymsgnum = (-1);
628 strcpy(IMAP->cached_bodypart, "");
632 /* extract partial */
633 safestrncpy(partial, item, sizeof partial);
634 if (strchr(partial, '<') != NULL) {
635 stripallbut(partial, '<', '>');
638 if (is_partial == 0) strcpy(partial, "");
639 /* if (!IsEmptyStr(partial)) CtdlLogPrintf(CTDL_DEBUG, "Partial is %s\n", partial); */
641 if (IMAP->cached_body == NULL) {
642 CC->redirect_buffer = malloc(SIZ);
643 CC->redirect_len = 0;
644 CC->redirect_alloc = SIZ;
645 loading_body_now = 1;
646 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
649 /* Now figure out what the client wants, and get it */
651 if (!loading_body_now) {
652 /* What we want is already in memory */
655 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
656 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, 0);
659 else if (!strcmp(section, "")) {
660 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
664 * If the client asked for just headers, or just particular header
665 * fields, strip it down.
667 else if (!strncasecmp(section, "HEADER", 6)) {
668 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_FAST, 0, 1, 0);
669 imap_strip_headers(section);
673 * Strip it down if the client asked for everything _except_ headers.
675 else if (!strncasecmp(section, "TEXT", 4)) {
676 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, 0);
680 * Anything else must be a part specifier.
681 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
684 mime_parser(msg->cm_fields['M'], NULL,
685 *imap_load_part, NULL, NULL,
690 if (loading_body_now) {
691 IMAP->cached_body = CC->redirect_buffer;
692 IMAP->cached_body_len = CC->redirect_len;
693 IMAP->cached_bodymsgnum = msgnum;
694 IMAP->cached_body_withbody = need_body;
695 strcpy(IMAP->cached_bodypart, section);
696 CC->redirect_buffer = NULL;
697 CC->redirect_len = 0;
698 CC->redirect_alloc = 0;
701 if (is_partial == 0) {
702 cprintf("BODY[%s] {" SIZE_T_FMT "}\r\n", section, IMAP->cached_body_len);
704 pbytes = IMAP->cached_body_len;
707 sscanf(partial, SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
708 if (pbytes > (IMAP->cached_body_len - pstart)) {
709 pbytes = IMAP->cached_body_len - pstart;
711 cprintf("BODY[%s]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", section, pstart, pbytes);
714 /* Here we go -- output it */
715 client_write(&IMAP->cached_body[pstart], pbytes);
718 CtdlFreeMessage(msg);
721 /* Mark this message as "seen" *unless* this is a "peek" operation */
723 CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
728 * Called immediately before outputting a multipart bodystructure
730 void imap_fetch_bodystructure_pre(
731 char *name, char *filename, char *partnum, char *disp,
732 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
733 char *cbid, void *cbuserdata
742 * Called immediately after outputting a multipart bodystructure
744 void imap_fetch_bodystructure_post(
745 char *name, char *filename, char *partnum, char *disp,
746 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
747 char *cbid, void *cbuserdata
755 extract_token(subtype, cbtype, 1, '/', sizeof subtype);
756 imap_strout(subtype);
759 /* cprintf(" NIL"); We thought we needed this at one point, but maybe we don't... */
767 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
770 void imap_fetch_bodystructure_part(
771 char *name, char *filename, char *partnum, char *disp,
772 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
773 char *cbid, void *cbuserdata
777 int have_encoding = 0;
780 char cbmaintype[128];
783 if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
785 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
786 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
789 strcpy(cbmaintype, "TEXT");
790 strcpy(cbsubtype, "PLAIN");
794 imap_strout(cbmaintype); /* body type */
796 imap_strout(cbsubtype); /* body subtype */
799 cprintf("("); /* begin body parameter list */
801 /* "NAME" must appear as the first parameter. This is not required by IMAP,
802 * but the Asterisk voicemail application blindly assumes that NAME will be in
803 * the first position. If it isn't, it rejects the message.
805 if (name != NULL) if (!IsEmptyStr(name)) {
806 cprintf("\"NAME\" ");
811 cprintf("\"CHARSET\" ");
812 if (cbcharset == NULL) {
813 imap_strout("US-ASCII");
815 else if (cbcharset[0] == 0) {
816 imap_strout("US-ASCII");
819 imap_strout(cbcharset);
821 cprintf(") "); /* end body parameter list */
823 cprintf("NIL "); /* Body ID */
824 cprintf("NIL "); /* Body description */
826 if (encoding != NULL) if (encoding[0] != 0) have_encoding = 1;
828 imap_strout(encoding);
835 /* The next field is the size of the part in bytes. */
836 cprintf("%ld ", (long)length); /* bytes */
838 /* The next field is the number of lines in the part, if and only
839 * if the part is TEXT. More gratuitous complexity.
841 if (!strcasecmp(cbmaintype, "TEXT")) {
842 if (length) for (i=0; i<length; ++i) {
843 if (((char *)content)[i] == '\n') ++lines;
845 cprintf("%d ", lines);
848 /* More gratuitous complexity */
849 if ((!strcasecmp(cbmaintype, "MESSAGE"))
850 && (!strcasecmp(cbsubtype, "RFC822"))) {
852 A body type of type MESSAGE and subtype RFC822
853 contains, immediately after the basic fields, the
854 envelope structure, body structure, and size in
855 text lines of the encapsulated message.
859 /* MD5 value of body part; we can get away with NIL'ing this */
866 else if (IsEmptyStr(disp)) {
872 if (filename != NULL) if (!IsEmptyStr(filename)) {
873 cprintf(" (\"FILENAME\" ");
874 imap_strout(filename);
880 /* Body language (not defined yet) */
887 * Spew the BODYSTRUCTURE data for a message.
890 void imap_fetch_bodystructure (long msgnum, char *item,
891 struct CtdlMessage *msg) {
893 char *rfc822_body = NULL;
895 size_t rfc822_headers_len;
896 size_t rfc822_body_len;
901 /* Handle NULL message gracefully */
903 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
904 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
909 /* For non-RFC822 (ordinary Citadel) messages, this is short and
912 if (msg->cm_format_type != FMT_RFC822) {
914 /* *sigh* We have to RFC822-format the message just to be able
915 * to measure it. FIXME use smi cached fields if possible
918 CC->redirect_buffer = malloc(SIZ);
919 CC->redirect_len = 0;
920 CC->redirect_alloc = SIZ;
921 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, 0);
922 rfc822 = CC->redirect_buffer;
923 rfc822_len = CC->redirect_len;
924 CC->redirect_buffer = NULL;
925 CC->redirect_len = 0;
926 CC->redirect_alloc = 0;
930 ptr = memreadline(ptr, buf, sizeof buf);
932 if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
937 rfc822_headers_len = rfc822_body - rfc822;
938 rfc822_body_len = rfc822_len - rfc822_headers_len;
941 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
942 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
943 "\"7BIT\" " SIZE_T_FMT " %d)", rfc822_body_len, lines);
948 /* For messages already stored in RFC822 format, we have to parse. */
949 cprintf("BODYSTRUCTURE ");
950 mime_parser(msg->cm_fields['M'],
952 *imap_fetch_bodystructure_part, /* part */
953 *imap_fetch_bodystructure_pre, /* pre-multi */
954 *imap_fetch_bodystructure_post, /* post-multi */
956 1); /* don't decode -- we want it as-is */
961 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
962 * individual message, once it has been selected for output.
964 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
966 struct CtdlMessage *msg = NULL;
969 /* Don't attempt to fetch bogus messages or UID's */
971 if (IMAP->msgids[seq-1] < 1L) return;
974 cprintf("* %d FETCH (", seq);
976 for (i=0; i<num_items; ++i) {
978 /* Fetchable without going to the message store at all */
979 if (!strcasecmp(itemlist[i], "UID")) {
982 else if (!strcasecmp(itemlist[i], "FLAGS")) {
983 imap_fetch_flags(seq-1);
986 /* Potentially fetchable from cache, if the client requests
987 * stuff from the same message several times in a row.
989 else if (!strcasecmp(itemlist[i], "RFC822")) {
990 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
992 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
993 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
995 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
996 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
998 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
999 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
1002 /* BODY fetches do their own fetching and caching too. */
1003 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
1004 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
1006 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
1007 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
1010 /* Otherwise, load the message into memory.
1012 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
1013 if ((msg != NULL) && (!body_loaded)) {
1014 CtdlFreeMessage(msg); /* need the whole thing */
1018 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
1021 imap_fetch_bodystructure(IMAP->msgids[seq-1],
1024 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
1026 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1029 imap_fetch_envelope(msg);
1031 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1033 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1036 imap_fetch_internaldate(msg);
1039 if (i != num_items-1) cprintf(" ");
1045 CtdlFreeMessage(msg);
1052 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1053 * validated and boiled down the request a bit.
1055 void imap_do_fetch(int num_items, char **itemlist) {
1058 if (IMAP->num_msgs > 0) {
1059 for (i = 0; i < IMAP->num_msgs; ++i) {
1061 /* Abort the fetch loop if the session breaks.
1062 * This is important for users who keep mailboxes
1063 * that are too big *and* are too impatient to
1064 * let them finish loading. :)
1066 if (CC->kill_me) return;
1068 /* Get any message marked for fetch. */
1069 if (IMAP->flags[i] & IMAP_SELECTED) {
1070 imap_do_fetch_msg(i+1, num_items, itemlist);
1079 * Back end for imap_handle_macros()
1080 * Note that this function *only* looks at the beginning of the string. It
1081 * is not a generic search-and-replace function.
1083 void imap_macro_replace(char *str, char *find, char *replace) {
1087 findlen = strlen(find);
1089 if (!strncasecmp(str, find, findlen)) {
1090 if (str[findlen]==' ') {
1091 strcpy(holdbuf, &str[findlen+1]);
1092 strcpy(str, replace);
1094 strcat(str, holdbuf);
1096 if (str[findlen]==0) {
1097 strcpy(holdbuf, &str[findlen+1]);
1098 strcpy(str, replace);
1106 * Handle macros embedded in FETCH data items.
1107 * (What the heck are macros doing in a wire protocol? Are we trying to save
1108 * the computer at the other end the trouble of typing a lot of characters?)
1110 void imap_handle_macros(char *str) {
1114 for (i=0; str[i]; ++i) {
1115 if (str[i]=='(') ++nest;
1116 if (str[i]=='[') ++nest;
1117 if (str[i]=='<') ++nest;
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;
1125 imap_macro_replace(&str[i],
1127 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1129 imap_macro_replace(&str[i],
1133 imap_macro_replace(&str[i],
1135 "FLAGS INTERNALDATE RFC822.SIZE"
1137 imap_macro_replace(&str[i],
1139 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1147 * Break out the data items requested, possibly a parenthesized list.
1148 * Returns the number of data items, or -1 if the list is invalid.
1149 * NOTE: this function alters the string it is fed, and uses it as a buffer
1150 * to hold the data for the pointers it returns.
1152 int imap_extract_data_items(char **argv, char *items) {
1159 /* Convert all whitespace to ordinary space characters. */
1160 for (i=0; items[i]; ++i) {
1161 if (isspace(items[i])) items[i]=' ';
1164 /* Strip leading and trailing whitespace, then strip leading and
1165 * trailing parentheses if it's a list
1168 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1169 items[strlen(items)-1] = 0;
1170 strcpy(items, &items[1]);
1174 /* Parse any macro data items */
1175 imap_handle_macros(items);
1178 * Now break out the data items. We throw in one trailing space in
1179 * order to avoid having to break out the last one manually.
1183 initial_len = strlen(items);
1184 for (i=0; i<initial_len; ++i) {
1185 if (items[i]=='(') ++nest;
1186 if (items[i]=='[') ++nest;
1187 if (items[i]=='<') ++nest;
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;
1194 if (nest <= 0) if (items[i]==' ') {
1196 argv[num_items++] = start;
1197 start = &items[i+1];
1207 * One particularly hideous aspect of IMAP is that we have to allow the client
1208 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1209 * handles this by setting the IMAP_SELECTED flag for each message specified in
1210 * the ranges/sets, then looping through the message array, outputting messages
1211 * with the flag set. We don't bother returning an error if an out-of-range
1212 * number is specified (we just return quietly) because any client braindead
1213 * enough to request a bogus message number isn't going to notice the
1214 * difference anyway.
1216 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1217 * message included in the specified range.
1219 * Set is_uid to 1 to fetch by UID instead of sequence number.
1221 void imap_pick_range(char *supplied_range, int is_uid) {
1225 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1227 char actual_range[SIZ];
1228 struct citimap *Imap;
1231 * Handle the "ALL" macro
1233 if (!strcasecmp(supplied_range, "ALL")) {
1234 safestrncpy(actual_range, "1:*", sizeof actual_range);
1237 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1242 * Clear out the IMAP_SELECTED flags for all messages.
1244 for (i = 0; i < Imap->num_msgs; ++i) {
1245 Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
1249 * Now set it for all specified messages.
1251 num_sets = num_tokens(actual_range, ',');
1252 for (s=0; s<num_sets; ++s) {
1253 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1255 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1256 if (num_tokens(setstr, ':') >= 2) {
1257 extract_token(histr, setstr, 1, ':', sizeof histr);
1258 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1261 safestrncpy(histr, lostr, sizeof histr);
1266 /* Loop through the array, flipping bits where appropriate */
1267 for (i = 1; i <= Imap->num_msgs; ++i) {
1268 if (is_uid) { /* fetch by sequence number */
1269 if ( (Imap->msgids[i-1]>=lo)
1270 && (Imap->msgids[i-1]<=hi)) {
1271 Imap->flags[i-1] |= IMAP_SELECTED;
1274 else { /* fetch by uid */
1275 if ( (i>=lo) && (i<=hi)) {
1276 Imap->flags[i-1] |= IMAP_SELECTED;
1287 * This function is called by the main command loop.
1289 void imap_fetch(int num_parms, char *parms[]) {
1291 char *itemlist[512];
1295 if (num_parms < 4) {
1296 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1300 imap_pick_range(parms[2], 0);
1303 for (i=3; i<num_parms; ++i) {
1304 strcat(items, parms[i]);
1305 if (i < (num_parms-1)) strcat(items, " ");
1308 num_items = imap_extract_data_items(itemlist, items);
1309 if (num_items < 1) {
1310 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1314 imap_do_fetch(num_items, itemlist);
1315 cprintf("%s OK FETCH completed\r\n", parms[0]);
1319 * This function is called by the main command loop.
1321 void imap_uidfetch(int num_parms, char *parms[]) {
1323 char *itemlist[512];
1326 int have_uid_item = 0;
1328 if (num_parms < 5) {
1329 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1333 imap_pick_range(parms[3], 1);
1336 for (i=4; i<num_parms; ++i) {
1337 strcat(items, parms[i]);
1338 if (i < (num_parms-1)) strcat(items, " ");
1341 num_items = imap_extract_data_items(itemlist, items);
1342 if (num_items < 1) {
1343 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1347 /* If the "UID" item was not included, we include it implicitly
1348 * (at the beginning) because this is a UID FETCH command
1350 for (i=0; i<num_items; ++i) {
1351 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1353 if (have_uid_item == 0) {
1354 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1356 itemlist[0] = "UID";
1359 imap_do_fetch(num_items, itemlist);
1360 cprintf("%s OK UID FETCH completed\r\n", parms[0]);