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']);
460 /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
461 fieldptr = msg->cm_fields['Y'];
462 if (fieldptr != NULL) {
463 imap_output_envelope_addr(fieldptr);
466 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
467 imap_output_envelope_addr(fieldptr);
468 if (fieldptr != NULL) free(fieldptr);
472 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
473 imap_output_envelope_addr(fieldptr);
474 if (fieldptr != NULL) free(fieldptr);
477 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
478 imap_strout(fieldptr);
480 if (fieldptr != NULL) free(fieldptr);
483 imap_strout(msg->cm_fields['I']);
489 * This function is called only when CC->redirect_buffer contains a set of
490 * RFC822 headers with no body attached. Its job is to strip that set of
491 * headers down to *only* the ones we're interested in.
493 void imap_strip_headers(char *section) {
495 char *which_fields = NULL;
496 int doing_headers = 0;
501 char *boiled_headers = NULL;
503 int done_headers = 0;
506 if (CC->redirect_buffer == NULL) return;
508 which_fields = strdup(section);
510 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
512 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
515 for (i=0; i<strlen(which_fields); ++i) {
516 if (which_fields[i]=='(')
517 strcpy(which_fields, &which_fields[i+1]);
519 for (i=0; i<strlen(which_fields); ++i) {
520 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 (strlen(buf) == 0) 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", section, ((strlen(section)==0) ? "(empty)" : "") );
588 if (!strncasecmp(section, "HEADER", 6)) {
592 /* Burn the cache if we don't have the same section of the
593 * same message again.
595 if (IMAP->cached_body != NULL) {
596 if (IMAP->cached_bodymsgnum != msgnum) {
599 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
602 else if (strcasecmp(IMAP->cached_bodypart, section)) {
605 if (burn_the_cache) {
606 /* Yup, go ahead and burn the cache. */
607 free(IMAP->cached_body);
608 IMAP->cached_body_len = 0;
609 IMAP->cached_body = NULL;
610 IMAP->cached_bodymsgnum = (-1);
611 strcpy(IMAP->cached_bodypart, "");
615 /* extract partial */
616 safestrncpy(partial, item, sizeof partial);
617 if (strchr(partial, '<') != NULL) {
618 stripallbut(partial, '<', '>');
621 if (is_partial == 0) strcpy(partial, "");
622 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
624 if (IMAP->cached_body == NULL) {
625 CC->redirect_buffer = malloc(SIZ);
626 CC->redirect_len = 0;
627 CC->redirect_alloc = SIZ;
628 loading_body_now = 1;
629 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
632 /* Now figure out what the client wants, and get it */
634 if (!loading_body_now) {
635 /* What we want is already in memory */
638 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
639 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
642 else if (!strcmp(section, "")) {
643 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
647 * If the client asked for just headers, or just particular header
648 * fields, strip it down.
650 else if (!strncasecmp(section, "HEADER", 6)) {
651 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
652 imap_strip_headers(section);
656 * Strip it down if the client asked for everything _except_ headers.
658 else if (!strncasecmp(section, "TEXT", 4)) {
659 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
663 * Anything else must be a part specifier.
664 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
667 mime_parser(msg->cm_fields['M'], NULL,
668 *imap_load_part, NULL, NULL,
673 if (loading_body_now) {
674 IMAP->cached_body = CC->redirect_buffer;
675 IMAP->cached_body_len = CC->redirect_len;
676 IMAP->cached_bodymsgnum = msgnum;
677 IMAP->cached_body_withbody = need_body;
678 strcpy(IMAP->cached_bodypart, section);
679 CC->redirect_buffer = NULL;
680 CC->redirect_len = 0;
681 CC->redirect_alloc = 0;
684 if (is_partial == 0) {
685 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
687 pbytes = IMAP->cached_body_len;
690 sscanf(partial, "%d.%d", &pstart, &pbytes);
691 if (pbytes > (IMAP->cached_body_len - pstart)) {
692 pbytes = IMAP->cached_body_len - pstart;
694 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
697 /* Here we go -- output it */
698 client_write(&IMAP->cached_body[pstart], pbytes);
701 CtdlFreeMessage(msg);
704 /* Mark this message as "seen" *unless* this is a "peek" operation */
706 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen, NULL, NULL);
711 * Called immediately before outputting a multipart bodystructure
713 void imap_fetch_bodystructure_pre(
714 char *name, char *filename, char *partnum, char *disp,
715 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
725 * Called immediately after outputting a multipart bodystructure
727 void imap_fetch_bodystructure_post(
728 char *name, char *filename, char *partnum, char *disp,
729 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
738 extract_token(subtype, cbtype, 1, '/', sizeof subtype);
739 imap_strout(subtype);
750 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
753 void imap_fetch_bodystructure_part(
754 char *name, char *filename, char *partnum, char *disp,
755 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
760 int have_encoding = 0;
763 char cbmaintype[128];
766 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
768 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
769 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
772 strcpy(cbmaintype, "TEXT");
773 strcpy(cbsubtype, "PLAIN");
777 imap_strout(cbmaintype);
779 imap_strout(cbsubtype);
782 if (cbcharset == NULL) {
783 cprintf("(\"CHARSET\" \"US-ASCII\"");
785 else if (strlen(cbcharset) == 0) {
786 cprintf("(\"CHARSET\" \"US-ASCII\"");
789 cprintf("(\"CHARSET\" ");
790 imap_strout(cbcharset);
793 if (name != NULL) if (strlen(name)>0) {
794 cprintf(" \"NAME\" ");
800 cprintf("NIL "); /* Body ID */
801 cprintf("NIL "); /* Body description */
803 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
805 imap_strout(encoding);
812 /* The next field is the size of the part in bytes. */
813 cprintf("%ld ", (long)length); /* bytes */
815 /* The next field is the number of lines in the part, if and only
816 * if the part is TEXT. More gratuitous complexity.
818 if (!strcasecmp(cbmaintype, "TEXT")) {
819 if (length) for (i=0; i<length; ++i) {
820 if (((char *)content)[i] == '\n') ++lines;
822 cprintf("%d ", lines);
825 /* More gratuitous complexity */
826 if ((!strcasecmp(cbmaintype, "MESSAGE"))
827 && (!strcasecmp(cbsubtype, "RFC822"))) {
829 A body type of type MESSAGE and subtype RFC822
830 contains, immediately after the basic fields, the
831 envelope structure, body structure, and size in
832 text lines of the encapsulated message.
836 /* MD5 value of body part; we can get away with NIL'ing this */
843 else if (strlen(disp) == 0) {
849 if (filename != NULL) if (strlen(filename)>0) {
850 cprintf(" (\"FILENAME\" ");
851 imap_strout(filename);
857 /* Body language (not defined yet) */
864 * Spew the BODYSTRUCTURE data for a message.
867 void imap_fetch_bodystructure (long msgnum, char *item,
868 struct CtdlMessage *msg) {
870 char *rfc822_body = NULL;
872 size_t rfc822_headers_len;
873 size_t rfc822_body_len;
878 /* For non-RFC822 (ordinary Citadel) messages, this is short and
881 if (msg->cm_format_type != FMT_RFC822) {
883 /* *sigh* We have to RFC822-format the message just to be able
884 * to measure it. FIXME use smi cached fields if possible
887 CC->redirect_buffer = malloc(SIZ);
888 CC->redirect_len = 0;
889 CC->redirect_alloc = SIZ;
890 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1);
891 rfc822 = CC->redirect_buffer;
892 rfc822_len = CC->redirect_len;
893 CC->redirect_buffer = NULL;
894 CC->redirect_len = 0;
895 CC->redirect_alloc = 0;
898 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
900 if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
905 rfc822_headers_len = rfc822_body - rfc822;
906 rfc822_body_len = rfc822_len - rfc822_headers_len;
909 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
910 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
911 "\"7BIT\" %d %d)", rfc822_body_len, lines);
916 /* For messages already stored in RFC822 format, we have to parse. */
917 cprintf("BODYSTRUCTURE ");
918 mime_parser(msg->cm_fields['M'],
920 *imap_fetch_bodystructure_part, /* part */
921 *imap_fetch_bodystructure_pre, /* pre-multi */
922 *imap_fetch_bodystructure_post, /* post-multi */
924 1); /* don't decode -- we want it as-is */
929 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
930 * individual message, once it has been selected for output.
932 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
934 struct CtdlMessage *msg = NULL;
937 /* Don't attempt to fetch bogus messages or UID's */
939 if (IMAP->msgids[seq-1] < 1L) return;
942 cprintf("* %d FETCH (", seq);
944 for (i=0; i<num_items; ++i) {
946 /* Fetchable without going to the message store at all */
947 if (!strcasecmp(itemlist[i], "UID")) {
950 else if (!strcasecmp(itemlist[i], "FLAGS")) {
951 imap_fetch_flags(seq-1);
954 /* Potentially fetchable from cache, if the client requests
955 * stuff from the same message several times in a row.
957 else if (!strcasecmp(itemlist[i], "RFC822")) {
958 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
960 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
961 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
963 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
964 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
966 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
967 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
970 /* BODY fetches do their own fetching and caching too. */
971 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
972 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
974 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
975 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
978 /* Otherwise, load the message into memory.
980 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
981 if ((msg != NULL) && (!body_loaded)) {
982 CtdlFreeMessage(msg); /* need the whole thing */
986 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
989 imap_fetch_bodystructure(IMAP->msgids[seq-1],
992 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
994 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
997 imap_fetch_envelope(msg);
999 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1001 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1004 imap_fetch_internaldate(msg);
1007 if (i != num_items-1) cprintf(" ");
1013 CtdlFreeMessage(msg);
1020 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1021 * validated and boiled down the request a bit.
1023 void imap_do_fetch(int num_items, char **itemlist) {
1026 if (IMAP->num_msgs > 0) {
1027 for (i = 0; i < IMAP->num_msgs; ++i) {
1028 if (IMAP->flags[i] & IMAP_SELECTED) {
1029 imap_do_fetch_msg(i+1, num_items, itemlist);
1038 * Back end for imap_handle_macros()
1039 * Note that this function *only* looks at the beginning of the string. It
1040 * is not a generic search-and-replace function.
1042 void imap_macro_replace(char *str, char *find, char *replace) {
1045 if (!strncasecmp(str, find, strlen(find))) {
1046 if (str[strlen(find)]==' ') {
1047 strcpy(holdbuf, &str[strlen(find)+1]);
1048 strcpy(str, replace);
1050 strcat(str, holdbuf);
1052 if (str[strlen(find)]==0) {
1053 strcpy(holdbuf, &str[strlen(find)+1]);
1054 strcpy(str, replace);
1062 * Handle macros embedded in FETCH data items.
1063 * (What the heck are macros doing in a wire protocol? Are we trying to save
1064 * the computer at the other end the trouble of typing a lot of characters?)
1066 void imap_handle_macros(char *str) {
1070 for (i=0; i<strlen(str); ++i) {
1071 if (str[i]=='(') ++nest;
1072 if (str[i]=='[') ++nest;
1073 if (str[i]=='<') ++nest;
1074 if (str[i]=='{') ++nest;
1075 if (str[i]==')') --nest;
1076 if (str[i]==']') --nest;
1077 if (str[i]=='>') --nest;
1078 if (str[i]=='}') --nest;
1081 imap_macro_replace(&str[i],
1083 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1085 imap_macro_replace(&str[i],
1089 imap_macro_replace(&str[i],
1091 "FLAGS INTERNALDATE RFC822.SIZE"
1093 imap_macro_replace(&str[i],
1095 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1103 * Break out the data items requested, possibly a parenthesized list.
1104 * Returns the number of data items, or -1 if the list is invalid.
1105 * NOTE: this function alters the string it is fed, and uses it as a buffer
1106 * to hold the data for the pointers it returns.
1108 int imap_extract_data_items(char **argv, char *items) {
1114 /* Convert all whitespace to ordinary space characters. */
1115 for (i=0; i<strlen(items); ++i) {
1116 if (isspace(items[i])) items[i]=' ';
1119 /* Strip leading and trailing whitespace, then strip leading and
1120 * trailing parentheses if it's a list
1123 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1124 items[strlen(items)-1] = 0;
1125 strcpy(items, &items[1]);
1129 /* Parse any macro data items */
1130 imap_handle_macros(items);
1133 * Now break out the data items. We throw in one trailing space in
1134 * order to avoid having to break out the last one manually.
1138 initial_len = strlen(items);
1139 for (i=0; i<initial_len; ++i) {
1140 if (items[i]=='(') ++nest;
1141 if (items[i]=='[') ++nest;
1142 if (items[i]=='<') ++nest;
1143 if (items[i]=='{') ++nest;
1144 if (items[i]==')') --nest;
1145 if (items[i]==']') --nest;
1146 if (items[i]=='>') --nest;
1147 if (items[i]=='}') --nest;
1149 if (nest <= 0) if (items[i]==' ') {
1151 argv[num_items++] = start;
1152 start = &items[i+1];
1162 * One particularly hideous aspect of IMAP is that we have to allow the client
1163 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1164 * handles this by setting the IMAP_SELECTED flag for each message specified in
1165 * the ranges/sets, then looping through the message array, outputting messages
1166 * with the flag set. We don't bother returning an error if an out-of-range
1167 * number is specified (we just return quietly) because any client braindead
1168 * enough to request a bogus message number isn't going to notice the
1169 * difference anyway.
1171 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1172 * message included in the specified range.
1174 * Set is_uid to 1 to fetch by UID instead of sequence number.
1176 void imap_pick_range(char *supplied_range, int is_uid) {
1180 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1182 char actual_range[SIZ];
1185 * Handle the "ALL" macro
1187 if (!strcasecmp(supplied_range, "ALL")) {
1188 safestrncpy(actual_range, "1:*", sizeof actual_range);
1191 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1195 * Clear out the IMAP_SELECTED flags for all messages.
1197 for (i = 0; i < IMAP->num_msgs; ++i) {
1198 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1202 * Now set it for all specified messages.
1204 num_sets = num_tokens(actual_range, ',');
1205 for (s=0; s<num_sets; ++s) {
1206 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1208 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1209 if (num_tokens(setstr, ':') >= 2) {
1210 extract_token(histr, setstr, 1, ':', sizeof histr);
1211 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1214 safestrncpy(histr, lostr, sizeof histr);
1219 /* Loop through the array, flipping bits where appropriate */
1220 for (i = 1; i <= IMAP->num_msgs; ++i) {
1221 if (is_uid) { /* fetch by sequence number */
1222 if ( (IMAP->msgids[i-1]>=lo)
1223 && (IMAP->msgids[i-1]<=hi)) {
1224 IMAP->flags[i-1] |= IMAP_SELECTED;
1227 else { /* fetch by uid */
1228 if ( (i>=lo) && (i<=hi)) {
1229 IMAP->flags[i-1] |= IMAP_SELECTED;
1240 * This function is called by the main command loop.
1242 void imap_fetch(int num_parms, char *parms[]) {
1244 char *itemlist[512];
1248 if (num_parms < 4) {
1249 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1253 imap_pick_range(parms[2], 0);
1256 for (i=3; i<num_parms; ++i) {
1257 strcat(items, parms[i]);
1258 if (i < (num_parms-1)) strcat(items, " ");
1261 num_items = imap_extract_data_items(itemlist, items);
1262 if (num_items < 1) {
1263 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1267 imap_do_fetch(num_items, itemlist);
1268 cprintf("%s OK FETCH completed\r\n", parms[0]);
1272 * This function is called by the main command loop.
1274 void imap_uidfetch(int num_parms, char *parms[]) {
1276 char *itemlist[512];
1279 int have_uid_item = 0;
1281 if (num_parms < 5) {
1282 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1286 imap_pick_range(parms[3], 1);
1289 for (i=4; i<num_parms; ++i) {
1290 strcat(items, parms[i]);
1291 if (i < (num_parms-1)) strcat(items, " ");
1294 num_items = imap_extract_data_items(itemlist, items);
1295 if (num_items < 1) {
1296 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1300 /* If the "UID" item was not included, we include it implicitly
1301 * (at the beginning) because this is a UID FETCH command
1303 for (i=0; i<num_items; ++i) {
1304 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1306 if (have_uid_item == 0) {
1307 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1309 itemlist[0] = "UID";
1312 imap_do_fetch(num_items, itemlist);
1313 cprintf("%s OK UID FETCH completed\r\n", parms[0]);