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).
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];
309 /* For anonymous messages, it's so easy! */
310 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
311 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
314 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
315 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
319 /* For everything else, we do stuff. */
320 cprintf("(("); /* open double-parens */
321 imap_strout(msg->cm_fields['A']); /* personal name */
322 cprintf(" NIL "); /* source route (not used) */
325 if (msg->cm_fields['F'] != NULL) {
326 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
327 imap_strout(user); /* mailbox name (user id) */
329 if (!strcasecmp(node, config.c_nodename)) {
330 imap_strout(config.c_fqdn);
333 imap_strout(node); /* host name */
337 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
339 imap_strout(msg->cm_fields['N']); /* host name */
342 cprintf(")) "); /* close double-parens */
348 * Output an envelope address (or set of addresses) in the official,
349 * convuluted, braindead format. (Note that we can't use this for
350 * the "From" address because its data may come from a number of different
351 * fields. But we can use it for "To" and possibly others.
353 void imap_output_envelope_addr(char *addr) {
354 char individual_addr[256];
366 if (strlen(addr) == 0) {
373 /* How many addresses are listed here? */
374 num_addrs = num_tokens(addr, ',');
376 /* Output them one by one. */
377 for (i=0; i<num_addrs; ++i) {
378 extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
379 striplt(individual_addr);
380 process_rfc822_addr(individual_addr, user, node, name);
388 if (i < (num_addrs-1)) cprintf(" ");
396 * Implements the ENVELOPE fetch item
398 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
399 * so we don't have to check for that condition like we do elsewhere.
401 void imap_fetch_envelope(struct CtdlMessage *msg) {
402 char datestringbuf[SIZ];
404 char *fieldptr = NULL;
408 /* Parse the message date into an IMAP-format date string */
409 if (msg->cm_fields['T'] != NULL) {
410 msgdate = atol(msg->cm_fields['T']);
413 msgdate = time(NULL);
415 datestring(datestringbuf, sizeof datestringbuf,
416 msgdate, DATESTRING_IMAP);
418 /* Now start spewing data fields. The order is important, as it is
419 * defined by the protocol specification. Nonexistent fields must
420 * be output as NIL, existent fields must be quoted or literalled.
421 * The imap_strout() function conveniently does all this for us.
423 cprintf("ENVELOPE (");
426 imap_strout(datestringbuf);
430 imap_strout(msg->cm_fields['U']);
434 imap_output_envelope_from(msg);
436 /* Sender (default to same as 'From' if not present) */
437 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
438 if (fieldptr != NULL) {
439 imap_output_envelope_addr(fieldptr);
443 imap_output_envelope_from(msg);
447 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
448 if (fieldptr != NULL) {
449 imap_output_envelope_addr(fieldptr);
453 imap_output_envelope_from(msg);
457 imap_output_envelope_addr(msg->cm_fields['R']);
459 /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
460 fieldptr = msg->cm_fields['Y'];
461 if (fieldptr != NULL) {
462 imap_output_envelope_addr(fieldptr);
465 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
466 imap_output_envelope_addr(fieldptr);
467 if (fieldptr != NULL) free(fieldptr);
471 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
472 imap_output_envelope_addr(fieldptr);
473 if (fieldptr != NULL) free(fieldptr);
476 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
477 imap_strout(fieldptr);
479 if (fieldptr != NULL) free(fieldptr);
482 imap_strout(msg->cm_fields['I']);
488 * This function is called only when CC->redirect_buffer contains a set of
489 * RFC822 headers with no body attached. Its job is to strip that set of
490 * headers down to *only* the ones we're interested in.
492 void imap_strip_headers(char *section) {
494 char *which_fields = NULL;
495 int doing_headers = 0;
500 char *boiled_headers = NULL;
502 int done_headers = 0;
505 if (CC->redirect_buffer == NULL) return;
507 which_fields = strdup(section);
509 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
511 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
514 for (i=0; i<strlen(which_fields); ++i) {
515 if (which_fields[i]=='(')
516 strcpy(which_fields, &which_fields[i+1]);
518 for (i=0; i<strlen(which_fields); ++i) {
519 if (which_fields[i]==')')
522 num_parms = imap_parameterize(parms, which_fields);
524 boiled_headers = malloc(CC->redirect_alloc);
525 strcpy(boiled_headers, "");
527 ptr = CC->redirect_buffer;
529 while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
530 if (!isspace(buf[0])) {
532 if (doing_headers == 0) ok = 1;
534 if (headers_not) ok = 1;
536 for (i=0; i<num_parms; ++i) {
537 if ( (!strncasecmp(buf, parms[i],
538 strlen(parms[i]))) &&
539 (buf[strlen(parms[i])]==':') ) {
540 if (headers_not) ok = 0;
548 strcat(boiled_headers, buf);
549 strcat(boiled_headers, "\r\n");
552 if (strlen(buf) == 0) done_headers = 1;
553 if (buf[0]=='\r') done_headers = 1;
554 if (buf[0]=='\n') done_headers = 1;
557 strcat(boiled_headers, "\r\n");
559 /* Now save it back (it'll always be smaller) */
560 strcpy(CC->redirect_buffer, boiled_headers);
561 CC->redirect_len = strlen(boiled_headers);
564 free(boiled_headers);
569 * Implements the BODY and BODY.PEEK fetch items
571 void imap_fetch_body(long msgnum, char *item, int is_peek) {
572 struct CtdlMessage *msg = NULL;
576 size_t pstart, pbytes;
577 int loading_body_now = 0;
579 int burn_the_cache = 0;
581 /* extract section */
582 safestrncpy(section, item, sizeof section);
583 if (strchr(section, '[') != NULL) {
584 stripallbut(section, '[', ']');
586 lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") );
587 if (!strncasecmp(section, "HEADER", 6)) {
591 /* Burn the cache if we don't have the same section of the
592 * same message again.
594 if (IMAP->cached_body != NULL) {
595 if (IMAP->cached_bodymsgnum != msgnum) {
598 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
601 else if (strcasecmp(IMAP->cached_bodypart, section)) {
604 if (burn_the_cache) {
605 /* Yup, go ahead and burn the cache. */
606 free(IMAP->cached_body);
607 IMAP->cached_body_len = 0;
608 IMAP->cached_body = NULL;
609 IMAP->cached_bodymsgnum = (-1);
610 strcpy(IMAP->cached_bodypart, "");
614 /* extract partial */
615 safestrncpy(partial, item, sizeof partial);
616 if (strchr(partial, '<') != NULL) {
617 stripallbut(partial, '<', '>');
620 if (is_partial == 0) strcpy(partial, "");
621 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
623 if (IMAP->cached_body == NULL) {
624 CC->redirect_buffer = malloc(SIZ);
625 CC->redirect_len = 0;
626 CC->redirect_alloc = SIZ;
627 loading_body_now = 1;
628 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
631 /* Now figure out what the client wants, and get it */
633 if (!loading_body_now) {
634 /* What we want is already in memory */
637 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
638 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
641 else if (!strcmp(section, "")) {
642 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
646 * If the client asked for just headers, or just particular header
647 * fields, strip it down.
649 else if (!strncasecmp(section, "HEADER", 6)) {
650 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
651 imap_strip_headers(section);
655 * Strip it down if the client asked for everything _except_ headers.
657 else if (!strncasecmp(section, "TEXT", 4)) {
658 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
662 * Anything else must be a part specifier.
663 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
666 mime_parser(msg->cm_fields['M'], NULL,
667 *imap_load_part, NULL, NULL,
672 if (loading_body_now) {
673 IMAP->cached_body = CC->redirect_buffer;
674 IMAP->cached_body_len = CC->redirect_len;
675 IMAP->cached_bodymsgnum = msgnum;
676 IMAP->cached_body_withbody = need_body;
677 strcpy(IMAP->cached_bodypart, section);
678 CC->redirect_buffer = NULL;
679 CC->redirect_len = 0;
680 CC->redirect_alloc = 0;
683 if (is_partial == 0) {
684 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
686 pbytes = IMAP->cached_body_len;
689 sscanf(partial, "%d.%d", &pstart, &pbytes);
690 if (pbytes > (IMAP->cached_body_len - pstart)) {
691 pbytes = IMAP->cached_body_len - pstart;
693 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
696 /* Here we go -- output it */
697 client_write(&IMAP->cached_body[pstart], pbytes);
700 CtdlFreeMessage(msg);
703 /* Mark this message as "seen" *unless* this is a "peek" operation */
705 CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
710 * Called immediately before outputting a multipart bodystructure
712 void imap_fetch_bodystructure_pre(
713 char *name, char *filename, char *partnum, char *disp,
714 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
724 * Called immediately after outputting a multipart bodystructure
726 void imap_fetch_bodystructure_post(
727 char *name, char *filename, char *partnum, char *disp,
728 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
737 extract_token(subtype, cbtype, 1, '/', sizeof subtype);
738 imap_strout(subtype);
749 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
752 void imap_fetch_bodystructure_part(
753 char *name, char *filename, char *partnum, char *disp,
754 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
759 int have_encoding = 0;
762 char cbmaintype[128];
765 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
767 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
768 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
771 strcpy(cbmaintype, "TEXT");
772 strcpy(cbsubtype, "PLAIN");
776 imap_strout(cbmaintype);
778 imap_strout(cbsubtype);
781 if (cbcharset == NULL) {
782 cprintf("(\"CHARSET\" \"US-ASCII\"");
784 else if (strlen(cbcharset) == 0) {
785 cprintf("(\"CHARSET\" \"US-ASCII\"");
788 cprintf("(\"CHARSET\" ");
789 imap_strout(cbcharset);
792 if (name != NULL) if (strlen(name)>0) {
793 cprintf(" \"NAME\" ");
799 cprintf("NIL "); /* Body ID */
800 cprintf("NIL "); /* Body description */
802 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
804 imap_strout(encoding);
811 /* The next field is the size of the part in bytes. */
812 cprintf("%ld ", (long)length); /* bytes */
814 /* The next field is the number of lines in the part, if and only
815 * if the part is TEXT. More gratuitous complexity.
817 if (!strcasecmp(cbmaintype, "TEXT")) {
818 if (length) for (i=0; i<length; ++i) {
819 if (((char *)content)[i] == '\n') ++lines;
821 cprintf("%d ", lines);
824 /* More gratuitous complexity */
825 if ((!strcasecmp(cbmaintype, "MESSAGE"))
826 && (!strcasecmp(cbsubtype, "RFC822"))) {
828 A body type of type MESSAGE and subtype RFC822
829 contains, immediately after the basic fields, the
830 envelope structure, body structure, and size in
831 text lines of the encapsulated message.
835 /* MD5 value of body part; we can get away with NIL'ing this */
842 else if (strlen(disp) == 0) {
848 if (filename != NULL) if (strlen(filename)>0) {
849 cprintf(" (\"FILENAME\" ");
850 imap_strout(filename);
856 /* Body language (not defined yet) */
863 * Spew the BODYSTRUCTURE data for a message.
866 void imap_fetch_bodystructure (long msgnum, char *item,
867 struct CtdlMessage *msg) {
869 char *rfc822_body = NULL;
871 size_t rfc822_headers_len;
872 size_t rfc822_body_len;
877 /* Handle NULL message gracefully */
879 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
880 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
885 /* For non-RFC822 (ordinary Citadel) messages, this is short and
888 if (msg->cm_format_type != FMT_RFC822) {
890 /* *sigh* We have to RFC822-format the message just to be able
891 * to measure it. FIXME use smi cached fields if possible
894 CC->redirect_buffer = malloc(SIZ);
895 CC->redirect_len = 0;
896 CC->redirect_alloc = SIZ;
897 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1);
898 rfc822 = CC->redirect_buffer;
899 rfc822_len = CC->redirect_len;
900 CC->redirect_buffer = NULL;
901 CC->redirect_len = 0;
902 CC->redirect_alloc = 0;
905 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
907 if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
912 rfc822_headers_len = rfc822_body - rfc822;
913 rfc822_body_len = rfc822_len - rfc822_headers_len;
916 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
917 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
918 "\"7BIT\" %d %d)", rfc822_body_len, lines);
923 /* For messages already stored in RFC822 format, we have to parse. */
924 cprintf("BODYSTRUCTURE ");
925 mime_parser(msg->cm_fields['M'],
927 *imap_fetch_bodystructure_part, /* part */
928 *imap_fetch_bodystructure_pre, /* pre-multi */
929 *imap_fetch_bodystructure_post, /* post-multi */
931 1); /* don't decode -- we want it as-is */
936 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
937 * individual message, once it has been selected for output.
939 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
941 struct CtdlMessage *msg = NULL;
944 /* Don't attempt to fetch bogus messages or UID's */
946 if (IMAP->msgids[seq-1] < 1L) return;
949 cprintf("* %d FETCH (", seq);
951 for (i=0; i<num_items; ++i) {
953 /* Fetchable without going to the message store at all */
954 if (!strcasecmp(itemlist[i], "UID")) {
957 else if (!strcasecmp(itemlist[i], "FLAGS")) {
958 imap_fetch_flags(seq-1);
961 /* Potentially fetchable from cache, if the client requests
962 * stuff from the same message several times in a row.
964 else if (!strcasecmp(itemlist[i], "RFC822")) {
965 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
967 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
968 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
970 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
971 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
973 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
974 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
977 /* BODY fetches do their own fetching and caching too. */
978 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
979 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
981 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
982 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
985 /* Otherwise, load the message into memory.
987 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
988 if ((msg != NULL) && (!body_loaded)) {
989 CtdlFreeMessage(msg); /* need the whole thing */
993 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
996 imap_fetch_bodystructure(IMAP->msgids[seq-1],
999 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
1001 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1004 imap_fetch_envelope(msg);
1006 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1008 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1011 imap_fetch_internaldate(msg);
1014 if (i != num_items-1) cprintf(" ");
1020 CtdlFreeMessage(msg);
1027 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1028 * validated and boiled down the request a bit.
1030 void imap_do_fetch(int num_items, char **itemlist) {
1033 if (IMAP->num_msgs > 0) {
1034 for (i = 0; i < IMAP->num_msgs; ++i) {
1036 /* Abort the fetch loop if the session breaks.
1037 * This is important for users who keep mailboxes
1038 * that are too big *and* are too impatient to
1039 * let them finish loading. :)
1041 if (CC->kill_me) return;
1043 /* Get any message marked for fetch. */
1044 if (IMAP->flags[i] & IMAP_SELECTED) {
1045 imap_do_fetch_msg(i+1, num_items, itemlist);
1054 * Back end for imap_handle_macros()
1055 * Note that this function *only* looks at the beginning of the string. It
1056 * is not a generic search-and-replace function.
1058 void imap_macro_replace(char *str, char *find, char *replace) {
1061 if (!strncasecmp(str, find, strlen(find))) {
1062 if (str[strlen(find)]==' ') {
1063 strcpy(holdbuf, &str[strlen(find)+1]);
1064 strcpy(str, replace);
1066 strcat(str, holdbuf);
1068 if (str[strlen(find)]==0) {
1069 strcpy(holdbuf, &str[strlen(find)+1]);
1070 strcpy(str, replace);
1078 * Handle macros embedded in FETCH data items.
1079 * (What the heck are macros doing in a wire protocol? Are we trying to save
1080 * the computer at the other end the trouble of typing a lot of characters?)
1082 void imap_handle_macros(char *str) {
1086 for (i=0; i<strlen(str); ++i) {
1087 if (str[i]=='(') ++nest;
1088 if (str[i]=='[') ++nest;
1089 if (str[i]=='<') ++nest;
1090 if (str[i]=='{') ++nest;
1091 if (str[i]==')') --nest;
1092 if (str[i]==']') --nest;
1093 if (str[i]=='>') --nest;
1094 if (str[i]=='}') --nest;
1097 imap_macro_replace(&str[i],
1099 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1101 imap_macro_replace(&str[i],
1105 imap_macro_replace(&str[i],
1107 "FLAGS INTERNALDATE RFC822.SIZE"
1109 imap_macro_replace(&str[i],
1111 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1119 * Break out the data items requested, possibly a parenthesized list.
1120 * Returns the number of data items, or -1 if the list is invalid.
1121 * NOTE: this function alters the string it is fed, and uses it as a buffer
1122 * to hold the data for the pointers it returns.
1124 int imap_extract_data_items(char **argv, char *items) {
1130 /* Convert all whitespace to ordinary space characters. */
1131 for (i=0; i<strlen(items); ++i) {
1132 if (isspace(items[i])) items[i]=' ';
1135 /* Strip leading and trailing whitespace, then strip leading and
1136 * trailing parentheses if it's a list
1139 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1140 items[strlen(items)-1] = 0;
1141 strcpy(items, &items[1]);
1145 /* Parse any macro data items */
1146 imap_handle_macros(items);
1149 * Now break out the data items. We throw in one trailing space in
1150 * order to avoid having to break out the last one manually.
1154 initial_len = strlen(items);
1155 for (i=0; i<initial_len; ++i) {
1156 if (items[i]=='(') ++nest;
1157 if (items[i]=='[') ++nest;
1158 if (items[i]=='<') ++nest;
1159 if (items[i]=='{') ++nest;
1160 if (items[i]==')') --nest;
1161 if (items[i]==']') --nest;
1162 if (items[i]=='>') --nest;
1163 if (items[i]=='}') --nest;
1165 if (nest <= 0) if (items[i]==' ') {
1167 argv[num_items++] = start;
1168 start = &items[i+1];
1178 * One particularly hideous aspect of IMAP is that we have to allow the client
1179 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1180 * handles this by setting the IMAP_SELECTED flag for each message specified in
1181 * the ranges/sets, then looping through the message array, outputting messages
1182 * with the flag set. We don't bother returning an error if an out-of-range
1183 * number is specified (we just return quietly) because any client braindead
1184 * enough to request a bogus message number isn't going to notice the
1185 * difference anyway.
1187 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1188 * message included in the specified range.
1190 * Set is_uid to 1 to fetch by UID instead of sequence number.
1192 void imap_pick_range(char *supplied_range, int is_uid) {
1196 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1198 char actual_range[SIZ];
1201 * Handle the "ALL" macro
1203 if (!strcasecmp(supplied_range, "ALL")) {
1204 safestrncpy(actual_range, "1:*", sizeof actual_range);
1207 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1211 * Clear out the IMAP_SELECTED flags for all messages.
1213 for (i = 0; i < IMAP->num_msgs; ++i) {
1214 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1218 * Now set it for all specified messages.
1220 num_sets = num_tokens(actual_range, ',');
1221 for (s=0; s<num_sets; ++s) {
1222 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1224 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1225 if (num_tokens(setstr, ':') >= 2) {
1226 extract_token(histr, setstr, 1, ':', sizeof histr);
1227 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1230 safestrncpy(histr, lostr, sizeof histr);
1235 /* Loop through the array, flipping bits where appropriate */
1236 for (i = 1; i <= IMAP->num_msgs; ++i) {
1237 if (is_uid) { /* fetch by sequence number */
1238 if ( (IMAP->msgids[i-1]>=lo)
1239 && (IMAP->msgids[i-1]<=hi)) {
1240 IMAP->flags[i-1] |= IMAP_SELECTED;
1243 else { /* fetch by uid */
1244 if ( (i>=lo) && (i<=hi)) {
1245 IMAP->flags[i-1] |= IMAP_SELECTED;
1256 * This function is called by the main command loop.
1258 void imap_fetch(int num_parms, char *parms[]) {
1260 char *itemlist[512];
1264 if (num_parms < 4) {
1265 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1269 imap_pick_range(parms[2], 0);
1272 for (i=3; i<num_parms; ++i) {
1273 strcat(items, parms[i]);
1274 if (i < (num_parms-1)) strcat(items, " ");
1277 num_items = imap_extract_data_items(itemlist, items);
1278 if (num_items < 1) {
1279 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1283 imap_do_fetch(num_items, itemlist);
1284 cprintf("%s OK FETCH completed\r\n", parms[0]);
1288 * This function is called by the main command loop.
1290 void imap_uidfetch(int num_parms, char *parms[]) {
1292 char *itemlist[512];
1295 int have_uid_item = 0;
1297 if (num_parms < 5) {
1298 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1302 imap_pick_range(parms[3], 1);
1305 for (i=4; i<num_parms; ++i) {
1306 strcat(items, parms[i]);
1307 if (i < (num_parms-1)) strcat(items, " ");
1310 num_items = imap_extract_data_items(itemlist, items);
1311 if (num_items < 1) {
1312 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1316 /* If the "UID" item was not included, we include it implicitly
1317 * (at the beginning) because this is a UID FETCH command
1319 for (i=0; i<num_items; ++i) {
1320 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1322 if (have_uid_item == 0) {
1323 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1325 itemlist[0] = "UID";
1328 imap_do_fetch(num_items, itemlist);
1329 cprintf("%s OK UID FETCH completed\r\n", parms[0]);