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) {
96 if (msg->cm_fields['T'] != NULL) {
97 msgdate = atol(msg->cm_fields['T']);
100 msgdate = time(NULL);
103 datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
104 cprintf("INTERNALDATE \"%s\"", buf);
109 * Fetch RFC822-formatted messages.
111 * 'whichfmt' should be set to one of:
112 * "RFC822" entire message
113 * "RFC822.HEADER" headers only (with trailing blank line)
114 * "RFC822.SIZE" size of translated message
115 * "RFC822.TEXT" body only (without leading blank line)
117 void imap_fetch_rfc822(long msgnum, char *whichfmt) {
120 size_t headers_size, text_size, total_size;
121 size_t bytes_to_send = 0;
123 int need_to_rewrite_metadata = 0;
126 /* Determine whether this particular fetch operation requires
127 * us to fetch the message body from disk. If not, we can save
128 * on some disk operations...
130 if ( (!strcasecmp(whichfmt, "RFC822"))
131 || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
135 /* If this is an RFC822.SIZE fetch, first look in the message's
136 * metadata record to see if we've saved that information.
138 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
139 GetMetaData(&smi, msgnum);
140 if (smi.meta_rfc822_length > 0L) {
141 cprintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
144 need_to_rewrite_metadata = 1;
148 /* Cache the most recent RFC822 FETCH because some clients like to
149 * fetch in pieces, and we don't want to have to go back to the
150 * message store for each piece. We also burn the cache if the
151 * client requests something that involves reading the message
152 * body, but we haven't fetched the body yet.
154 if ((IMAP->cached_rfc822_data != NULL)
155 && (IMAP->cached_rfc822_msgnum == msgnum)
156 && (IMAP->cached_rfc822_withbody || (!need_body)) ) {
159 else if (IMAP->cached_rfc822_data != NULL) {
160 /* Some other message is cached -- free it */
161 free(IMAP->cached_rfc822_data);
162 IMAP->cached_rfc822_data = NULL;
163 IMAP->cached_rfc822_msgnum = (-1);
164 IMAP->cached_rfc822_len = 0;
167 /* At this point, we now can fetch and convert the message iff it's not
168 * the one we had cached.
170 if (IMAP->cached_rfc822_data == NULL) {
172 * Load the message into memory for translation & measurement
174 CC->redirect_buffer = malloc(SIZ);
175 CC->redirect_len = 0;
176 CC->redirect_alloc = SIZ;
177 CtdlOutputMsg(msgnum, MT_RFC822,
178 (need_body ? HEADERS_ALL : HEADERS_ONLY),
180 if (!need_body) cprintf("\r\n"); /* extra trailing newline */
181 IMAP->cached_rfc822_data = CC->redirect_buffer;
182 IMAP->cached_rfc822_len = CC->redirect_len;
183 IMAP->cached_rfc822_msgnum = msgnum;
184 IMAP->cached_rfc822_withbody = need_body;
185 CC->redirect_buffer = NULL;
186 CC->redirect_len = 0;
187 CC->redirect_alloc = 0;
188 if ( (need_to_rewrite_metadata) && (IMAP->cached_rfc822_len > 0) ) {
189 smi.meta_rfc822_length = (long)IMAP->cached_rfc822_len;
195 * Now figure out where the headers/text break is. IMAP considers the
196 * intervening blank line to be part of the headers, not the text.
203 ptr = IMAP->cached_rfc822_data;
205 ptr = memreadline(ptr, buf, sizeof buf);
208 if (strlen(buf) == 0) {
209 headers_size = ptr - IMAP->cached_rfc822_data;
212 } while ( (headers_size == 0) && (*ptr != 0) );
214 total_size = IMAP->cached_rfc822_len;
215 text_size = total_size - headers_size;
218 headers_size = IMAP->cached_rfc822_len;
219 total_size = IMAP->cached_rfc822_len;
223 lprintf(CTDL_DEBUG, "RFC822: headers=%d, text=%d, total=%d\n",
224 headers_size, text_size, total_size);
226 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
227 cprintf("RFC822.SIZE %d", total_size);
231 else if (!strcasecmp(whichfmt, "RFC822")) {
232 ptr = IMAP->cached_rfc822_data;
233 bytes_to_send = total_size;
236 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
237 ptr = IMAP->cached_rfc822_data;
238 bytes_to_send = headers_size;
241 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
242 ptr = &IMAP->cached_rfc822_data[headers_size];
243 bytes_to_send = text_size;
246 cprintf("%s {%d}\r\n", whichfmt, bytes_to_send);
247 client_write(ptr, bytes_to_send);
253 * Load a specific part of a message into the temp file to be output to a
254 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
255 * but we still can't handle "2.HEADER" (which might not be a problem, because
256 * we currently don't have the ability to break out nested RFC822's anyway).
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 (strlen(cbcharset) > 0)
279 cprintf("; charset=\"%s\"", cbcharset);
280 if (strlen(name) > 0)
281 cprintf("; name=\"%s\"", name);
283 if (strlen(encoding) > 0)
284 cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
285 if (strlen(encoding) > 0) {
286 cprintf("Content-Disposition: %s", disp);
287 if (strlen(filename) > 0) {
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 (strlen(addr) == 0) {
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']);
461 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
462 imap_output_envelope_addr(fieldptr);
463 if (fieldptr != NULL) free(fieldptr);
466 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
467 imap_output_envelope_addr(fieldptr);
468 if (fieldptr != NULL) free(fieldptr);
471 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
472 imap_strout(fieldptr);
474 if (fieldptr != NULL) free(fieldptr);
477 imap_strout(msg->cm_fields['I']);
483 * This function is called only when CC->redirect_buffer contains a set of
484 * RFC822 headers with no body attached. Its job is to strip that set of
485 * headers down to *only* the ones we're interested in.
487 void imap_strip_headers(char *section) {
489 char *which_fields = NULL;
490 int doing_headers = 0;
495 char *boiled_headers = NULL;
497 int done_headers = 0;
500 if (CC->redirect_buffer == NULL) return;
502 which_fields = strdup(section);
504 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
506 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
509 for (i=0; i<strlen(which_fields); ++i) {
510 if (which_fields[i]=='(')
511 strcpy(which_fields, &which_fields[i+1]);
513 for (i=0; i<strlen(which_fields); ++i) {
514 if (which_fields[i]==')')
517 num_parms = imap_parameterize(parms, which_fields);
519 boiled_headers = malloc(CC->redirect_alloc);
520 strcpy(boiled_headers, "");
522 ptr = CC->redirect_buffer;
524 while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
525 if (!isspace(buf[0])) {
527 if (doing_headers == 0) ok = 1;
529 if (headers_not) ok = 1;
531 for (i=0; i<num_parms; ++i) {
532 if ( (!strncasecmp(buf, parms[i],
533 strlen(parms[i]))) &&
534 (buf[strlen(parms[i])]==':') ) {
535 if (headers_not) ok = 0;
543 strcat(boiled_headers, buf);
544 strcat(boiled_headers, "\r\n");
547 if (strlen(buf) == 0) done_headers = 1;
548 if (buf[0]=='\r') done_headers = 1;
549 if (buf[0]=='\n') done_headers = 1;
552 strcat(boiled_headers, "\r\n");
554 /* Now save it back (it'll always be smaller) */
555 strcpy(CC->redirect_buffer, boiled_headers);
556 CC->redirect_len = strlen(boiled_headers);
559 free(boiled_headers);
564 * Implements the BODY and BODY.PEEK fetch items
566 void imap_fetch_body(long msgnum, char *item, int is_peek) {
567 struct CtdlMessage *msg = NULL;
571 size_t pstart, pbytes;
572 int loading_body_now = 0;
574 int burn_the_cache = 0;
576 /* extract section */
577 safestrncpy(section, item, sizeof section);
578 if (strchr(section, '[') != NULL) {
579 stripallbut(section, '[', ']');
581 /* lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") ); */
582 if (!strncasecmp(section, "HEADER", 6)) {
586 /* Burn the cache if we don't have the same section of the
587 * same message again.
589 if (IMAP->cached_body != NULL) {
590 if (IMAP->cached_bodymsgnum != msgnum) {
593 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
596 else if (strcasecmp(IMAP->cached_bodypart, section)) {
599 if (burn_the_cache) {
600 /* Yup, go ahead and burn the cache. */
601 free(IMAP->cached_body);
602 IMAP->cached_body_len = 0;
603 IMAP->cached_body = NULL;
604 IMAP->cached_bodymsgnum = (-1);
605 strcpy(IMAP->cached_bodypart, "");
609 /* extract partial */
610 safestrncpy(partial, item, sizeof partial);
611 if (strchr(partial, '<') != NULL) {
612 stripallbut(partial, '<', '>');
615 if (is_partial == 0) strcpy(partial, "");
616 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
618 if (IMAP->cached_body == NULL) {
619 CC->redirect_buffer = malloc(SIZ);
620 CC->redirect_len = 0;
621 CC->redirect_alloc = SIZ;
622 loading_body_now = 1;
623 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
626 /* Now figure out what the client wants, and get it */
628 if (!loading_body_now) {
629 /* What we want is already in memory */
632 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
633 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
636 else if (!strcmp(section, "")) {
637 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
641 * If the client asked for just headers, or just particular header
642 * fields, strip it down.
644 else if (!strncasecmp(section, "HEADER", 6)) {
645 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ONLY, 0, 1);
646 imap_strip_headers(section);
650 * Strip it down if the client asked for everything _except_ headers.
652 else if (!strncasecmp(section, "TEXT", 4)) {
653 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
657 * Anything else must be a part specifier.
658 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
661 mime_parser(msg->cm_fields['M'], NULL,
662 *imap_load_part, NULL, NULL,
667 if (loading_body_now) {
668 IMAP->cached_body = CC->redirect_buffer;
669 IMAP->cached_body_len = CC->redirect_len;
670 IMAP->cached_bodymsgnum = msgnum;
671 IMAP->cached_body_withbody = need_body;
672 strcpy(IMAP->cached_bodypart, section);
673 CC->redirect_buffer = NULL;
674 CC->redirect_len = 0;
675 CC->redirect_alloc = 0;
678 if (is_partial == 0) {
679 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
681 pbytes = IMAP->cached_body_len;
684 sscanf(partial, "%d.%d", &pstart, &pbytes);
685 if (pbytes > (IMAP->cached_body_len - pstart)) {
686 pbytes = IMAP->cached_body_len - pstart;
688 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
691 /* Here we go -- output it */
692 client_write(&IMAP->cached_body[pstart], pbytes);
695 CtdlFreeMessage(msg);
698 /* Mark this message as "seen" *unless* this is a "peek" operation */
700 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen, NULL, NULL);
705 * Called immediately before outputting a multipart bodystructure
707 void imap_fetch_bodystructure_pre(
708 char *name, char *filename, char *partnum, char *disp,
709 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
719 * Called immediately after outputting a multipart bodystructure
721 void imap_fetch_bodystructure_post(
722 char *name, char *filename, char *partnum, char *disp,
723 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
732 extract_token(subtype, cbtype, 1, '/', sizeof subtype);
733 imap_strout(subtype);
744 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
747 void imap_fetch_bodystructure_part(
748 char *name, char *filename, char *partnum, char *disp,
749 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
754 int have_encoding = 0;
757 char cbmaintype[128];
760 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
762 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
763 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
766 strcpy(cbmaintype, "TEXT");
767 strcpy(cbsubtype, "PLAIN");
771 imap_strout(cbmaintype);
773 imap_strout(cbsubtype);
776 if (cbcharset == NULL) {
777 cprintf("(\"CHARSET\" \"US-ASCII\"");
779 else if (strlen(cbcharset) == 0) {
780 cprintf("(\"CHARSET\" \"US-ASCII\"");
783 cprintf("(\"CHARSET\" ");
784 imap_strout(cbcharset);
787 if (name != NULL) if (strlen(name)>0) {
788 cprintf(" \"NAME\" ");
794 cprintf("NIL "); /* Body ID */
795 cprintf("NIL "); /* Body description */
797 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
799 imap_strout(encoding);
806 /* The next field is the size of the part in bytes. */
807 cprintf("%ld ", (long)length); /* bytes */
809 /* The next field is the number of lines in the part, if and only
810 * if the part is TEXT. More gratuitous complexity.
812 if (!strcasecmp(cbmaintype, "TEXT")) {
813 if (length) for (i=0; i<length; ++i) {
814 if (((char *)content)[i] == '\n') ++lines;
816 cprintf("%d ", lines);
819 /* More gratuitous complexity */
820 if ((!strcasecmp(cbmaintype, "MESSAGE"))
821 && (!strcasecmp(cbsubtype, "RFC822"))) {
823 A body type of type MESSAGE and subtype RFC822
824 contains, immediately after the basic fields, the
825 envelope structure, body structure, and size in
826 text lines of the encapsulated message.
830 /* MD5 value of body part; we can get away with NIL'ing this */
837 else if (strlen(disp) == 0) {
843 if (filename != NULL) if (strlen(filename)>0) {
844 cprintf(" (\"FILENAME\" ");
845 imap_strout(filename);
851 /* Body language (not defined yet) */
858 * Spew the BODYSTRUCTURE data for a message.
861 void imap_fetch_bodystructure (long msgnum, char *item,
862 struct CtdlMessage *msg) {
864 char *rfc822_body = NULL;
866 size_t rfc822_headers_len;
867 size_t rfc822_body_len;
872 /* For non-RFC822 (ordinary Citadel) messages, this is short and
875 if (msg->cm_format_type != FMT_RFC822) {
877 /* *sigh* We have to RFC822-format the message just to be able
878 * to measure it. FIXME use smi cached fields if possible
881 CC->redirect_buffer = malloc(SIZ);
882 CC->redirect_len = 0;
883 CC->redirect_alloc = SIZ;
884 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
885 rfc822 = CC->redirect_buffer;
886 rfc822_len = CC->redirect_len;
887 CC->redirect_buffer = NULL;
888 CC->redirect_len = 0;
889 CC->redirect_alloc = 0;
892 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
894 if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
899 rfc822_headers_len = rfc822_body - rfc822;
900 rfc822_body_len = rfc822_len - rfc822_headers_len;
903 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
904 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
905 "\"7BIT\" %d %d)", rfc822_body_len, lines);
910 /* For messages already stored in RFC822 format, we have to parse. */
911 cprintf("BODYSTRUCTURE ");
912 mime_parser(msg->cm_fields['M'],
914 *imap_fetch_bodystructure_part, /* part */
915 *imap_fetch_bodystructure_pre, /* pre-multi */
916 *imap_fetch_bodystructure_post, /* post-multi */
918 1); /* don't decode -- we want it as-is */
923 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
924 * individual message, once it has been selected for output.
926 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
928 struct CtdlMessage *msg = NULL;
931 /* Don't attempt to fetch bogus messages or UID's */
933 if (IMAP->msgids[seq-1] < 1L) return;
936 cprintf("* %d FETCH (", seq);
938 for (i=0; i<num_items; ++i) {
940 /* Fetchable without going to the message store at all */
941 if (!strcasecmp(itemlist[i], "UID")) {
944 else if (!strcasecmp(itemlist[i], "FLAGS")) {
945 imap_fetch_flags(seq-1);
948 /* Potentially fetchable from cache, if the client requests
949 * stuff from the same message several times in a row.
951 else if (!strcasecmp(itemlist[i], "RFC822")) {
952 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
954 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
955 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
957 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
958 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
960 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
961 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
964 /* BODY fetches do their own fetching and caching too. */
965 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
966 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
968 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
969 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
972 /* Otherwise, load the message into memory.
974 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
975 if ((msg != NULL) && (!body_loaded)) {
976 CtdlFreeMessage(msg); /* need the whole thing */
980 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
983 imap_fetch_bodystructure(IMAP->msgids[seq-1],
986 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
988 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
991 imap_fetch_envelope(msg);
993 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
995 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
998 imap_fetch_internaldate(msg);
1001 if (i != num_items-1) cprintf(" ");
1007 CtdlFreeMessage(msg);
1014 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1015 * validated and boiled down the request a bit.
1017 void imap_do_fetch(int num_items, char **itemlist) {
1020 if (IMAP->num_msgs > 0) {
1021 for (i = 0; i < IMAP->num_msgs; ++i) {
1022 if (IMAP->flags[i] & IMAP_SELECTED) {
1023 imap_do_fetch_msg(i+1, num_items, itemlist);
1032 * Back end for imap_handle_macros()
1033 * Note that this function *only* looks at the beginning of the string. It
1034 * is not a generic search-and-replace function.
1036 void imap_macro_replace(char *str, char *find, char *replace) {
1039 if (!strncasecmp(str, find, strlen(find))) {
1040 if (str[strlen(find)]==' ') {
1041 strcpy(holdbuf, &str[strlen(find)+1]);
1042 strcpy(str, replace);
1044 strcat(str, holdbuf);
1046 if (str[strlen(find)]==0) {
1047 strcpy(holdbuf, &str[strlen(find)+1]);
1048 strcpy(str, replace);
1056 * Handle macros embedded in FETCH data items.
1057 * (What the heck are macros doing in a wire protocol? Are we trying to save
1058 * the computer at the other end the trouble of typing a lot of characters?)
1060 void imap_handle_macros(char *str) {
1064 for (i=0; i<strlen(str); ++i) {
1065 if (str[i]=='(') ++nest;
1066 if (str[i]=='[') ++nest;
1067 if (str[i]=='<') ++nest;
1068 if (str[i]=='{') ++nest;
1069 if (str[i]==')') --nest;
1070 if (str[i]==']') --nest;
1071 if (str[i]=='>') --nest;
1072 if (str[i]=='}') --nest;
1075 imap_macro_replace(&str[i],
1077 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1079 imap_macro_replace(&str[i],
1083 imap_macro_replace(&str[i],
1085 "FLAGS INTERNALDATE RFC822.SIZE"
1087 imap_macro_replace(&str[i],
1089 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1097 * Break out the data items requested, possibly a parenthesized list.
1098 * Returns the number of data items, or -1 if the list is invalid.
1099 * NOTE: this function alters the string it is fed, and uses it as a buffer
1100 * to hold the data for the pointers it returns.
1102 int imap_extract_data_items(char **argv, char *items) {
1108 /* Convert all whitespace to ordinary space characters. */
1109 for (i=0; i<strlen(items); ++i) {
1110 if (isspace(items[i])) items[i]=' ';
1113 /* Strip leading and trailing whitespace, then strip leading and
1114 * trailing parentheses if it's a list
1117 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1118 items[strlen(items)-1] = 0;
1119 strcpy(items, &items[1]);
1123 /* Parse any macro data items */
1124 imap_handle_macros(items);
1127 * Now break out the data items. We throw in one trailing space in
1128 * order to avoid having to break out the last one manually.
1132 initial_len = strlen(items);
1133 for (i=0; i<initial_len; ++i) {
1134 if (items[i]=='(') ++nest;
1135 if (items[i]=='[') ++nest;
1136 if (items[i]=='<') ++nest;
1137 if (items[i]=='{') ++nest;
1138 if (items[i]==')') --nest;
1139 if (items[i]==']') --nest;
1140 if (items[i]=='>') --nest;
1141 if (items[i]=='}') --nest;
1143 if (nest <= 0) if (items[i]==' ') {
1145 argv[num_items++] = start;
1146 start = &items[i+1];
1156 * One particularly hideous aspect of IMAP is that we have to allow the client
1157 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1158 * handles this by setting the IMAP_SELECTED flag for each message specified in
1159 * the ranges/sets, then looping through the message array, outputting messages
1160 * with the flag set. We don't bother returning an error if an out-of-range
1161 * number is specified (we just return quietly) because any client braindead
1162 * enough to request a bogus message number isn't going to notice the
1163 * difference anyway.
1165 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1166 * message included in the specified range.
1168 * Set is_uid to 1 to fetch by UID instead of sequence number.
1170 void imap_pick_range(char *supplied_range, int is_uid) {
1174 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1176 char actual_range[SIZ];
1179 * Handle the "ALL" macro
1181 if (!strcasecmp(supplied_range, "ALL")) {
1182 safestrncpy(actual_range, "1:*", sizeof actual_range);
1185 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1189 * Clear out the IMAP_SELECTED flags for all messages.
1191 for (i = 0; i < IMAP->num_msgs; ++i) {
1192 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1196 * Now set it for all specified messages.
1198 num_sets = num_tokens(actual_range, ',');
1199 for (s=0; s<num_sets; ++s) {
1200 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1202 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1203 if (num_tokens(setstr, ':') >= 2) {
1204 extract_token(histr, setstr, 1, ':', sizeof histr);
1205 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1208 safestrncpy(histr, lostr, sizeof histr);
1213 /* Loop through the array, flipping bits where appropriate */
1214 for (i = 1; i <= IMAP->num_msgs; ++i) {
1215 if (is_uid) { /* fetch by sequence number */
1216 if ( (IMAP->msgids[i-1]>=lo)
1217 && (IMAP->msgids[i-1]<=hi)) {
1218 IMAP->flags[i-1] |= IMAP_SELECTED;
1221 else { /* fetch by uid */
1222 if ( (i>=lo) && (i<=hi)) {
1223 IMAP->flags[i-1] |= IMAP_SELECTED;
1234 * This function is called by the main command loop.
1236 void imap_fetch(int num_parms, char *parms[]) {
1238 char *itemlist[512];
1242 if (num_parms < 4) {
1243 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1247 imap_pick_range(parms[2], 0);
1250 for (i=3; i<num_parms; ++i) {
1251 strcat(items, parms[i]);
1252 if (i < (num_parms-1)) strcat(items, " ");
1255 num_items = imap_extract_data_items(itemlist, items);
1256 if (num_items < 1) {
1257 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1261 imap_do_fetch(num_items, itemlist);
1262 cprintf("%s OK FETCH completed\r\n", parms[0]);
1266 * This function is called by the main command loop.
1268 void imap_uidfetch(int num_parms, char *parms[]) {
1270 char *itemlist[512];
1273 int have_uid_item = 0;
1275 if (num_parms < 5) {
1276 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1280 imap_pick_range(parms[3], 1);
1283 for (i=4; i<num_parms; ++i) {
1284 strcat(items, parms[i]);
1285 if (i < (num_parms-1)) strcat(items, " ");
1288 num_items = imap_extract_data_items(itemlist, items);
1289 if (num_items < 1) {
1290 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1294 /* If the "UID" item was not included, we include it implicitly
1295 * (at the beginning) because this is a UID FETCH command
1297 for (i=0; i<num_items; ++i) {
1298 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1300 if (have_uid_item == 0) {
1301 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1303 itemlist[0] = "UID";
1306 imap_do_fetch(num_items, itemlist);
1307 cprintf("%s OK UID FETCH completed\r\n", parms[0]);