4 * Implements the FETCH command in IMAP.
5 * This is a good example of the protocol's gratuitous complexity.
18 #include <sys/types.h>
20 #if TIME_WITH_SYS_TIME
21 # include <sys/time.h>
25 # include <sys/time.h>
37 #include "sysdep_decls.h"
38 #include "citserver.h"
41 #include "serv_extensions.h"
48 #include "internet_addressing.h"
49 #include "mime_parser.h"
50 #include "serv_imap.h"
51 #include "imap_tools.h"
52 #include "imap_fetch.h"
58 * Individual field functions for imap_do_fetch_msg() ...
61 void imap_fetch_uid(int seq) {
62 cprintf("UID %ld", IMAP->msgids[seq-1]);
65 void imap_fetch_flags(int seq) {
66 int num_flags_printed = 0;
68 if (IMAP->flags[seq] & IMAP_DELETED) {
69 if (num_flags_printed > 0) cprintf(" ");
73 if (IMAP->flags[seq] & IMAP_SEEN) {
74 if (num_flags_printed > 0) cprintf(" ");
78 if (IMAP->flags[seq] & IMAP_ANSWERED) {
79 if (num_flags_printed > 0) cprintf(" ");
80 cprintf("\\Answered");
83 if (IMAP->flags[seq] & IMAP_RECENT) {
84 if (num_flags_printed > 0) cprintf(" ");
91 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 (strlen(buf) == 0) {
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, because
255 * we currently don't have the ability to break out nested RFC822's anyway).
257 * Note: mime_parser() was called with dont_decode set to 1, so we have the
258 * luxury of simply spewing without having to re-encode.
260 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
261 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
265 char *desired_section;
267 desired_section = (char *)cbuserdata;
269 if (!strcasecmp(partnum, desired_section)) {
270 client_write(content, length);
273 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
275 if (!strcasecmp(desired_section, mbuf2)) {
276 cprintf("Content-type: %s", cbtype);
277 if (strlen(cbcharset) > 0)
278 cprintf("; charset=\"%s\"", cbcharset);
279 if (strlen(name) > 0)
280 cprintf("; name=\"%s\"", name);
282 if (strlen(encoding) > 0)
283 cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
284 if (strlen(encoding) > 0) {
285 cprintf("Content-Disposition: %s", disp);
286 if (strlen(filename) > 0) {
287 cprintf("; filename=\"%s\"", filename);
291 cprintf("Content-Length: %ld\r\n", (long)length);
300 * Called by imap_fetch_envelope() to output the "From" field.
301 * This is in its own function because its logic is kind of complex. We
302 * really need to make this suck less.
304 void imap_output_envelope_from(struct CtdlMessage *msg) {
305 char user[SIZ], node[SIZ], name[SIZ];
307 /* For anonymous messages, it's so easy! */
308 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
309 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
312 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
313 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
317 /* For everything else, we do stuff. */
318 cprintf("(("); /* open double-parens */
319 imap_strout(msg->cm_fields['A']); /* personal name */
320 cprintf(" NIL "); /* source route (not used) */
323 if (msg->cm_fields['F'] != NULL) {
324 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
325 imap_strout(user); /* mailbox name (user id) */
327 if (!strcasecmp(node, config.c_nodename)) {
328 imap_strout(config.c_fqdn);
331 imap_strout(node); /* host name */
335 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
337 imap_strout(msg->cm_fields['N']); /* host name */
340 cprintf(")) "); /* close double-parens */
346 * Output an envelope address (or set of addresses) in the official,
347 * convuluted, braindead format. (Note that we can't use this for
348 * the "From" address because its data may come from a number of different
349 * fields. But we can use it for "To" and possibly others.
351 void imap_output_envelope_addr(char *addr) {
352 char individual_addr[256];
364 if (strlen(addr) == 0) {
371 /* How many addresses are listed here? */
372 num_addrs = num_tokens(addr, ',');
374 /* Output them one by one. */
375 for (i=0; i<num_addrs; ++i) {
376 extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
377 striplt(individual_addr);
378 process_rfc822_addr(individual_addr, user, node, name);
386 if (i < (num_addrs-1)) cprintf(" ");
394 * Implements the ENVELOPE fetch item
396 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
397 * so we don't have to check for that condition like we do elsewhere.
399 void imap_fetch_envelope(struct CtdlMessage *msg) {
400 char datestringbuf[SIZ];
402 char *fieldptr = NULL;
404 /* Parse the message date into an IMAP-format date string */
405 if (msg->cm_fields['T'] != NULL) {
406 msgdate = atol(msg->cm_fields['T']);
409 msgdate = time(NULL);
411 datestring(datestringbuf, sizeof datestringbuf,
412 msgdate, DATESTRING_IMAP);
414 /* Now start spewing data fields. The order is important, as it is
415 * defined by the protocol specification. Nonexistent fields must
416 * be output as NIL, existent fields must be quoted or literalled.
417 * The imap_strout() function conveniently does all this for us.
419 cprintf("ENVELOPE (");
422 imap_strout(datestringbuf);
426 imap_strout(msg->cm_fields['U']);
430 imap_output_envelope_from(msg);
432 /* Sender (default to same as 'From' if not present) */
433 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
434 if (fieldptr != NULL) {
435 imap_output_envelope_addr(fieldptr);
439 imap_output_envelope_from(msg);
443 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
444 if (fieldptr != NULL) {
445 imap_output_envelope_addr(fieldptr);
449 imap_output_envelope_from(msg);
453 imap_output_envelope_addr(msg->cm_fields['R']);
456 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
457 imap_output_envelope_addr(fieldptr);
458 if (fieldptr != NULL) free(fieldptr);
461 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
462 imap_output_envelope_addr(fieldptr);
463 if (fieldptr != NULL) free(fieldptr);
466 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
467 imap_strout(fieldptr);
469 if (fieldptr != NULL) free(fieldptr);
472 imap_strout(msg->cm_fields['I']);
478 * This function is called only when CC->redirect_buffer contains a set of
479 * RFC822 headers with no body attached. Its job is to strip that set of
480 * headers down to *only* the ones we're interested in.
482 void imap_strip_headers(char *section) {
484 char *which_fields = NULL;
485 int doing_headers = 0;
490 char *boiled_headers = NULL;
492 int done_headers = 0;
495 if (CC->redirect_buffer == NULL) return;
497 which_fields = strdup(section);
499 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
501 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
504 for (i=0; i<strlen(which_fields); ++i) {
505 if (which_fields[i]=='(')
506 strcpy(which_fields, &which_fields[i+1]);
508 for (i=0; i<strlen(which_fields); ++i) {
509 if (which_fields[i]==')')
512 num_parms = imap_parameterize(parms, which_fields);
514 boiled_headers = malloc(CC->redirect_alloc);
515 strcpy(boiled_headers, "");
517 ptr = CC->redirect_buffer;
519 while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
520 if (!isspace(buf[0])) {
522 if (doing_headers == 0) ok = 1;
524 if (headers_not) ok = 1;
526 for (i=0; i<num_parms; ++i) {
527 if ( (!strncasecmp(buf, parms[i],
528 strlen(parms[i]))) &&
529 (buf[strlen(parms[i])]==':') ) {
530 if (headers_not) ok = 0;
538 strcat(boiled_headers, buf);
539 strcat(boiled_headers, "\r\n");
542 if (strlen(buf) == 0) done_headers = 1;
543 if (buf[0]=='\r') done_headers = 1;
544 if (buf[0]=='\n') done_headers = 1;
547 strcat(boiled_headers, "\r\n");
549 /* Now save it back (it'll always be smaller) */
550 strcpy(CC->redirect_buffer, boiled_headers);
551 CC->redirect_len = strlen(boiled_headers);
554 free(boiled_headers);
559 * Implements the BODY and BODY.PEEK fetch items
561 void imap_fetch_body(long msgnum, char *item, int is_peek) {
562 struct CtdlMessage *msg = NULL;
566 size_t pstart, pbytes;
567 int loading_body_now = 0;
569 int burn_the_cache = 0;
571 /* extract section */
572 safestrncpy(section, item, sizeof section);
573 if (strchr(section, '[') != NULL) {
574 stripallbut(section, '[', ']');
576 /* lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") ); */
577 if (!strncasecmp(section, "HEADER", 6)) {
581 /* Burn the cache if we don't have the same section of the
582 * same message again.
584 if (IMAP->cached_body != NULL) {
585 if (IMAP->cached_bodymsgnum != msgnum) {
588 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
591 else if (strcasecmp(IMAP->cached_bodypart, section)) {
594 if (burn_the_cache) {
595 /* Yup, go ahead and burn the cache. */
596 free(IMAP->cached_body);
597 IMAP->cached_body_len = 0;
598 IMAP->cached_body = NULL;
599 IMAP->cached_bodymsgnum = (-1);
600 strcpy(IMAP->cached_bodypart, "");
604 /* extract partial */
605 safestrncpy(partial, item, sizeof partial);
606 if (strchr(partial, '<') != NULL) {
607 stripallbut(partial, '<', '>');
610 if (is_partial == 0) strcpy(partial, "");
611 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
613 if (IMAP->cached_body == NULL) {
614 CC->redirect_buffer = malloc(SIZ);
615 CC->redirect_len = 0;
616 CC->redirect_alloc = SIZ;
617 loading_body_now = 1;
618 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
621 /* Now figure out what the client wants, and get it */
623 if (!loading_body_now) {
624 /* What we want is already in memory */
627 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
628 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
631 else if (!strcmp(section, "")) {
632 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
636 * If the client asked for just headers, or just particular header
637 * fields, strip it down.
639 else if (!strncasecmp(section, "HEADER", 6)) {
640 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ONLY, 0, 1);
641 imap_strip_headers(section);
645 * Strip it down if the client asked for everything _except_ headers.
647 else if (!strncasecmp(section, "TEXT", 4)) {
648 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
652 * Anything else must be a part specifier.
653 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
656 mime_parser(msg->cm_fields['M'], NULL,
657 *imap_load_part, NULL, NULL,
662 if (loading_body_now) {
663 IMAP->cached_body = CC->redirect_buffer;
664 IMAP->cached_body_len = CC->redirect_len;
665 IMAP->cached_bodymsgnum = msgnum;
666 IMAP->cached_body_withbody = need_body;
667 strcpy(IMAP->cached_bodypart, section);
668 CC->redirect_buffer = NULL;
669 CC->redirect_len = 0;
670 CC->redirect_alloc = 0;
673 if (is_partial == 0) {
674 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
676 pbytes = IMAP->cached_body_len;
679 sscanf(partial, "%d.%d", &pstart, &pbytes);
680 if (pbytes > (IMAP->cached_body_len - pstart)) {
681 pbytes = IMAP->cached_body_len - pstart;
683 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
686 /* Here we go -- output it */
687 client_write(&IMAP->cached_body[pstart], pbytes);
690 CtdlFreeMessage(msg);
693 /* Mark this message as "seen" *unless* this is a "peek" operation */
695 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen, NULL, NULL);
700 * Called immediately before outputting a multipart bodystructure
702 void imap_fetch_bodystructure_pre(
703 char *name, char *filename, char *partnum, char *disp,
704 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
714 * Called immediately after outputting a multipart bodystructure
716 void imap_fetch_bodystructure_post(
717 char *name, char *filename, char *partnum, char *disp,
718 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
727 extract_token(subtype, cbtype, 1, '/', sizeof subtype);
728 imap_strout(subtype);
739 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
742 void imap_fetch_bodystructure_part(
743 char *name, char *filename, char *partnum, char *disp,
744 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
749 int have_encoding = 0;
752 char cbmaintype[128];
755 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
757 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
758 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
761 strcpy(cbmaintype, "TEXT");
762 strcpy(cbsubtype, "PLAIN");
766 imap_strout(cbmaintype);
768 imap_strout(cbsubtype);
771 if (cbcharset == NULL) {
772 cprintf("(\"CHARSET\" \"US-ASCII\"");
774 else if (strlen(cbcharset) == 0) {
775 cprintf("(\"CHARSET\" \"US-ASCII\"");
778 cprintf("(\"CHARSET\" ");
779 imap_strout(cbcharset);
782 if (name != NULL) if (strlen(name)>0) {
783 cprintf(" \"NAME\" ");
789 cprintf("NIL "); /* Body ID */
790 cprintf("NIL "); /* Body description */
792 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
794 imap_strout(encoding);
801 /* The next field is the size of the part in bytes. */
802 cprintf("%ld ", (long)length); /* bytes */
804 /* The next field is the number of lines in the part, if and only
805 * if the part is TEXT. More gratuitous complexity.
807 if (!strcasecmp(cbmaintype, "TEXT")) {
808 if (length) for (i=0; i<length; ++i) {
809 if (((char *)content)[i] == '\n') ++lines;
811 cprintf("%d ", lines);
814 /* More gratuitous complexity */
815 if ((!strcasecmp(cbmaintype, "MESSAGE"))
816 && (!strcasecmp(cbsubtype, "RFC822"))) {
818 A body type of type MESSAGE and subtype RFC822
819 contains, immediately after the basic fields, the
820 envelope structure, body structure, and size in
821 text lines of the encapsulated message.
825 /* MD5 value of body part; we can get away with NIL'ing this */
832 else if (strlen(disp) == 0) {
838 if (filename != NULL) if (strlen(filename)>0) {
839 cprintf(" (\"FILENAME\" ");
840 imap_strout(filename);
846 /* Body language (not defined yet) */
853 * Spew the BODYSTRUCTURE data for a message.
856 void imap_fetch_bodystructure (long msgnum, char *item,
857 struct CtdlMessage *msg) {
859 char *rfc822_body = NULL;
861 size_t rfc822_headers_len;
862 size_t rfc822_body_len;
867 /* For non-RFC822 (ordinary Citadel) messages, this is short and
870 if (msg->cm_format_type != FMT_RFC822) {
872 /* *sigh* We have to RFC822-format the message just to be able
873 * to measure it. FIXME use smi cached fields if possible
876 CC->redirect_buffer = malloc(SIZ);
877 CC->redirect_len = 0;
878 CC->redirect_alloc = SIZ;
879 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
880 rfc822 = CC->redirect_buffer;
881 rfc822_len = CC->redirect_len;
882 CC->redirect_buffer = NULL;
883 CC->redirect_len = 0;
884 CC->redirect_alloc = 0;
887 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
889 if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
894 rfc822_headers_len = rfc822_body - rfc822;
895 rfc822_body_len = rfc822_len - rfc822_headers_len;
898 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
899 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
900 "\"7BIT\" %d %d)", rfc822_body_len, lines);
905 /* For messages already stored in RFC822 format, we have to parse. */
906 cprintf("BODYSTRUCTURE ");
907 mime_parser(msg->cm_fields['M'],
909 *imap_fetch_bodystructure_part, /* part */
910 *imap_fetch_bodystructure_pre, /* pre-multi */
911 *imap_fetch_bodystructure_post, /* post-multi */
913 1); /* don't decode -- we want it as-is */
918 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
919 * individual message, once it has been selected for output.
921 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
923 struct CtdlMessage *msg = NULL;
926 /* Don't attempt to fetch bogus messages or UID's */
928 if (IMAP->msgids[seq-1] < 1L) return;
931 cprintf("* %d FETCH (", seq);
933 for (i=0; i<num_items; ++i) {
935 /* Fetchable without going to the message store at all */
936 if (!strcasecmp(itemlist[i], "UID")) {
939 else if (!strcasecmp(itemlist[i], "FLAGS")) {
940 imap_fetch_flags(seq-1);
943 /* Potentially fetchable from cache, if the client requests
944 * stuff from the same message several times in a row.
946 else if (!strcasecmp(itemlist[i], "RFC822")) {
947 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
949 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
950 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
952 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
953 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
955 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
956 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
959 /* BODY fetches do their own fetching and caching too. */
960 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
961 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
963 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
964 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
967 /* Otherwise, load the message into memory.
969 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
970 if ((msg != NULL) && (!body_loaded)) {
971 CtdlFreeMessage(msg); /* need the whole thing */
975 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
978 imap_fetch_bodystructure(IMAP->msgids[seq-1],
981 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
983 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
986 imap_fetch_envelope(msg);
988 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
990 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
993 imap_fetch_internaldate(msg);
996 if (i != num_items-1) cprintf(" ");
1002 CtdlFreeMessage(msg);
1009 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1010 * validated and boiled down the request a bit.
1012 void imap_do_fetch(int num_items, char **itemlist) {
1015 if (IMAP->num_msgs > 0) {
1016 for (i = 0; i < IMAP->num_msgs; ++i) {
1017 if (IMAP->flags[i] & IMAP_SELECTED) {
1018 imap_do_fetch_msg(i+1, num_items, itemlist);
1027 * Back end for imap_handle_macros()
1028 * Note that this function *only* looks at the beginning of the string. It
1029 * is not a generic search-and-replace function.
1031 void imap_macro_replace(char *str, char *find, char *replace) {
1034 if (!strncasecmp(str, find, strlen(find))) {
1035 if (str[strlen(find)]==' ') {
1036 strcpy(holdbuf, &str[strlen(find)+1]);
1037 strcpy(str, replace);
1039 strcat(str, holdbuf);
1041 if (str[strlen(find)]==0) {
1042 strcpy(holdbuf, &str[strlen(find)+1]);
1043 strcpy(str, replace);
1051 * Handle macros embedded in FETCH data items.
1052 * (What the heck are macros doing in a wire protocol? Are we trying to save
1053 * the computer at the other end the trouble of typing a lot of characters?)
1055 void imap_handle_macros(char *str) {
1059 for (i=0; i<strlen(str); ++i) {
1060 if (str[i]=='(') ++nest;
1061 if (str[i]=='[') ++nest;
1062 if (str[i]=='<') ++nest;
1063 if (str[i]=='{') ++nest;
1064 if (str[i]==')') --nest;
1065 if (str[i]==']') --nest;
1066 if (str[i]=='>') --nest;
1067 if (str[i]=='}') --nest;
1070 imap_macro_replace(&str[i],
1072 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1074 imap_macro_replace(&str[i],
1078 imap_macro_replace(&str[i],
1080 "FLAGS INTERNALDATE RFC822.SIZE"
1082 imap_macro_replace(&str[i],
1084 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1092 * Break out the data items requested, possibly a parenthesized list.
1093 * Returns the number of data items, or -1 if the list is invalid.
1094 * NOTE: this function alters the string it is fed, and uses it as a buffer
1095 * to hold the data for the pointers it returns.
1097 int imap_extract_data_items(char **argv, char *items) {
1103 /* Convert all whitespace to ordinary space characters. */
1104 for (i=0; i<strlen(items); ++i) {
1105 if (isspace(items[i])) items[i]=' ';
1108 /* Strip leading and trailing whitespace, then strip leading and
1109 * trailing parentheses if it's a list
1112 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1113 items[strlen(items)-1] = 0;
1114 strcpy(items, &items[1]);
1118 /* Parse any macro data items */
1119 imap_handle_macros(items);
1122 * Now break out the data items. We throw in one trailing space in
1123 * order to avoid having to break out the last one manually.
1127 initial_len = strlen(items);
1128 for (i=0; i<initial_len; ++i) {
1129 if (items[i]=='(') ++nest;
1130 if (items[i]=='[') ++nest;
1131 if (items[i]=='<') ++nest;
1132 if (items[i]=='{') ++nest;
1133 if (items[i]==')') --nest;
1134 if (items[i]==']') --nest;
1135 if (items[i]=='>') --nest;
1136 if (items[i]=='}') --nest;
1138 if (nest <= 0) if (items[i]==' ') {
1140 argv[num_items++] = start;
1141 start = &items[i+1];
1151 * One particularly hideous aspect of IMAP is that we have to allow the client
1152 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1153 * handles this by setting the IMAP_SELECTED flag for each message specified in
1154 * the ranges/sets, then looping through the message array, outputting messages
1155 * with the flag set. We don't bother returning an error if an out-of-range
1156 * number is specified (we just return quietly) because any client braindead
1157 * enough to request a bogus message number isn't going to notice the
1158 * difference anyway.
1160 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1161 * message included in the specified range.
1163 * Set is_uid to 1 to fetch by UID instead of sequence number.
1165 void imap_pick_range(char *supplied_range, int is_uid) {
1169 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1171 char actual_range[SIZ];
1174 * Handle the "ALL" macro
1176 if (!strcasecmp(supplied_range, "ALL")) {
1177 safestrncpy(actual_range, "1:*", sizeof actual_range);
1180 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1184 * Clear out the IMAP_SELECTED flags for all messages.
1186 for (i = 0; i < IMAP->num_msgs; ++i) {
1187 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1191 * Now set it for all specified messages.
1193 num_sets = num_tokens(actual_range, ',');
1194 for (s=0; s<num_sets; ++s) {
1195 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1197 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1198 if (num_tokens(setstr, ':') >= 2) {
1199 extract_token(histr, setstr, 1, ':', sizeof histr);
1200 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1203 safestrncpy(histr, lostr, sizeof histr);
1208 /* Loop through the array, flipping bits where appropriate */
1209 for (i = 1; i <= IMAP->num_msgs; ++i) {
1210 if (is_uid) { /* fetch by sequence number */
1211 if ( (IMAP->msgids[i-1]>=lo)
1212 && (IMAP->msgids[i-1]<=hi)) {
1213 IMAP->flags[i-1] |= IMAP_SELECTED;
1216 else { /* fetch by uid */
1217 if ( (i>=lo) && (i<=hi)) {
1218 IMAP->flags[i-1] |= IMAP_SELECTED;
1229 * This function is called by the main command loop.
1231 void imap_fetch(int num_parms, char *parms[]) {
1233 char *itemlist[512];
1237 if (num_parms < 4) {
1238 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1242 imap_pick_range(parms[2], 0);
1245 for (i=3; i<num_parms; ++i) {
1246 strcat(items, parms[i]);
1247 if (i < (num_parms-1)) strcat(items, " ");
1250 num_items = imap_extract_data_items(itemlist, items);
1251 if (num_items < 1) {
1252 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1256 imap_do_fetch(num_items, itemlist);
1257 cprintf("%s OK FETCH completed\r\n", parms[0]);
1261 * This function is called by the main command loop.
1263 void imap_uidfetch(int num_parms, char *parms[]) {
1265 char *itemlist[512];
1268 int have_uid_item = 0;
1270 if (num_parms < 5) {
1271 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1275 imap_pick_range(parms[3], 1);
1278 for (i=4; i<num_parms; ++i) {
1279 strcat(items, parms[i]);
1280 if (i < (num_parms-1)) strcat(items, " ");
1283 num_items = imap_extract_data_items(itemlist, items);
1284 if (num_items < 1) {
1285 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1289 /* If the "UID" item was not included, we include it implicitly
1290 * (at the beginning) because this is a UID FETCH command
1292 for (i=0; i<num_items; ++i) {
1293 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1295 if (have_uid_item == 0) {
1296 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1298 itemlist[0] = "UID";
1301 imap_do_fetch(num_items, itemlist);
1302 cprintf("%s OK UID FETCH completed\r\n", parms[0]);