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, 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(name) > 0)
278 cprintf("; name=\"%s\"", name);
280 if (strlen(encoding) > 0)
281 cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
282 if (strlen(encoding) > 0) {
283 cprintf("Content-Disposition: %s", disp);
284 if (strlen(filename) > 0) {
285 cprintf("; filename=\"%s\"", filename);
289 cprintf("Content-Length: %ld\r\n", (long)length);
298 * Called by imap_fetch_envelope() to output the "From" field.
299 * This is in its own function because its logic is kind of complex. We
300 * really need to make this suck less.
302 void imap_output_envelope_from(struct CtdlMessage *msg) {
303 char user[SIZ], node[SIZ], name[SIZ];
305 /* For anonymous messages, it's so easy! */
306 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
307 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
310 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
311 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
315 /* For everything else, we do stuff. */
316 cprintf("(("); /* open double-parens */
317 imap_strout(msg->cm_fields['A']); /* personal name */
318 cprintf(" NIL "); /* source route (not used) */
321 if (msg->cm_fields['F'] != NULL) {
322 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
323 imap_strout(user); /* mailbox name (user id) */
325 if (!strcasecmp(node, config.c_nodename)) {
326 imap_strout(config.c_fqdn);
329 imap_strout(node); /* host name */
333 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
335 imap_strout(msg->cm_fields['N']); /* host name */
338 cprintf(")) "); /* close double-parens */
344 * Output an envelope address (or set of addresses) in the official,
345 * convuluted, braindead format. (Note that we can't use this for
346 * the "From" address because its data may come from a number of different
347 * fields. But we can use it for "To" and possibly others.
349 void imap_output_envelope_addr(char *addr) {
350 char individual_addr[256];
362 if (strlen(addr) == 0) {
369 /* How many addresses are listed here? */
370 num_addrs = num_tokens(addr, ',');
372 /* Output them one by one. */
373 for (i=0; i<num_addrs; ++i) {
374 extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
375 striplt(individual_addr);
376 process_rfc822_addr(individual_addr, user, node, name);
384 if (i < (num_addrs-1)) cprintf(" ");
392 * Implements the ENVELOPE fetch item
394 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
395 * so we don't have to check for that condition like we do elsewhere.
397 void imap_fetch_envelope(struct CtdlMessage *msg) {
398 char datestringbuf[SIZ];
400 char *fieldptr = NULL;
402 /* Parse the message date into an IMAP-format date string */
403 if (msg->cm_fields['T'] != NULL) {
404 msgdate = atol(msg->cm_fields['T']);
407 msgdate = time(NULL);
409 datestring(datestringbuf, sizeof datestringbuf,
410 msgdate, DATESTRING_IMAP);
412 /* Now start spewing data fields. The order is important, as it is
413 * defined by the protocol specification. Nonexistent fields must
414 * be output as NIL, existent fields must be quoted or literalled.
415 * The imap_strout() function conveniently does all this for us.
417 cprintf("ENVELOPE (");
420 imap_strout(datestringbuf);
424 imap_strout(msg->cm_fields['U']);
428 imap_output_envelope_from(msg);
430 /* Sender (default to same as 'From' if not present) */
431 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
432 if (fieldptr != NULL) {
433 imap_output_envelope_addr(fieldptr);
437 imap_output_envelope_from(msg);
441 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
442 if (fieldptr != NULL) {
443 imap_output_envelope_addr(fieldptr);
447 imap_output_envelope_from(msg);
451 imap_output_envelope_addr(msg->cm_fields['R']);
454 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
455 imap_output_envelope_addr(fieldptr);
456 if (fieldptr != NULL) free(fieldptr);
459 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
460 imap_output_envelope_addr(fieldptr);
461 if (fieldptr != NULL) free(fieldptr);
464 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
465 imap_strout(fieldptr);
467 if (fieldptr != NULL) free(fieldptr);
470 imap_strout(msg->cm_fields['I']);
476 * This function is called only when CC->redirect_buffer contains a set of
477 * RFC822 headers with no body attached. Its job is to strip that set of
478 * headers down to *only* the ones we're interested in.
480 void imap_strip_headers(char *section) {
482 char *which_fields = NULL;
483 int doing_headers = 0;
488 char *boiled_headers = NULL;
490 int done_headers = 0;
493 if (CC->redirect_buffer == NULL) return;
495 which_fields = strdup(section);
497 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
499 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
502 for (i=0; i<strlen(which_fields); ++i) {
503 if (which_fields[i]=='(')
504 strcpy(which_fields, &which_fields[i+1]);
506 for (i=0; i<strlen(which_fields); ++i) {
507 if (which_fields[i]==')')
510 num_parms = imap_parameterize(parms, which_fields);
512 boiled_headers = malloc(CC->redirect_alloc);
513 strcpy(boiled_headers, "");
515 ptr = CC->redirect_buffer;
517 while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
518 if (!isspace(buf[0])) {
520 if (doing_headers == 0) ok = 1;
522 if (headers_not) ok = 1;
524 for (i=0; i<num_parms; ++i) {
525 if ( (!strncasecmp(buf, parms[i],
526 strlen(parms[i]))) &&
527 (buf[strlen(parms[i])]==':') ) {
528 if (headers_not) ok = 0;
536 strcat(boiled_headers, buf);
537 strcat(boiled_headers, "\r\n");
540 if (strlen(buf) == 0) done_headers = 1;
541 if (buf[0]=='\r') done_headers = 1;
542 if (buf[0]=='\n') done_headers = 1;
545 strcat(boiled_headers, "\r\n");
547 /* Now save it back (it'll always be smaller) */
548 strcpy(CC->redirect_buffer, boiled_headers);
549 CC->redirect_len = strlen(boiled_headers);
552 free(boiled_headers);
557 * Implements the BODY and BODY.PEEK fetch items
559 void imap_fetch_body(long msgnum, char *item, int is_peek) {
560 struct CtdlMessage *msg = NULL;
564 size_t pstart, pbytes;
565 int loading_body_now = 0;
568 /* extract section */
569 safestrncpy(section, item, sizeof section);
570 if (strchr(section, '[') != NULL) {
571 stripallbut(section, '[', ']');
573 /* lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") ); */
574 if (!strncasecmp(section, "HEADER", 6)) {
578 /* Burn the cache if we don't have the same section of the
579 * same message again.
581 if (IMAP->cached_body != NULL) {
582 if ((IMAP->cached_bodymsgnum != msgnum)
583 || ( (IMAP->cached_body_withbody) || (!need_body) )
584 || (strcasecmp(IMAP->cached_bodypart, section)) ) {
585 free(IMAP->cached_body);
586 IMAP->cached_body_len = 0;
587 IMAP->cached_body = NULL;
588 IMAP->cached_bodymsgnum = (-1);
589 strcpy(IMAP->cached_bodypart, "");
593 /* extract partial */
594 safestrncpy(partial, item, sizeof partial);
595 if (strchr(partial, '<') != NULL) {
596 stripallbut(partial, '<', '>');
599 if (is_partial == 0) strcpy(partial, "");
600 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
602 if (IMAP->cached_body == NULL) {
603 CC->redirect_buffer = malloc(SIZ);
604 CC->redirect_len = 0;
605 CC->redirect_alloc = SIZ;
606 loading_body_now = 1;
607 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
610 /* Now figure out what the client wants, and get it */
612 if (!loading_body_now) {
613 /* What we want is already in memory */
616 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
617 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
620 else if (!strcmp(section, "")) {
621 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
625 * If the client asked for just headers, or just particular header
626 * fields, strip it down.
628 else if (!strncasecmp(section, "HEADER", 6)) {
629 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ONLY, 0, 1);
630 imap_strip_headers(section);
634 * Strip it down if the client asked for everything _except_ headers.
636 else if (!strncasecmp(section, "TEXT", 4)) {
637 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
641 * Anything else must be a part specifier.
642 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
645 mime_parser(msg->cm_fields['M'], NULL,
646 *imap_load_part, NULL, NULL,
651 if (loading_body_now) {
652 IMAP->cached_body = CC->redirect_buffer;
653 IMAP->cached_body_len = CC->redirect_len;
654 IMAP->cached_bodymsgnum = msgnum;
655 IMAP->cached_body_withbody = need_body;
656 strcpy(IMAP->cached_bodypart, section);
657 CC->redirect_buffer = NULL;
658 CC->redirect_len = 0;
659 CC->redirect_alloc = 0;
662 if (is_partial == 0) {
663 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
665 pbytes = IMAP->cached_body_len;
668 sscanf(partial, "%d.%d", &pstart, &pbytes);
669 if (pbytes > (IMAP->cached_body_len - pstart)) {
670 pbytes = IMAP->cached_body_len - pstart;
672 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
675 /* Here we go -- output it */
676 client_write(&IMAP->cached_body[pstart], pbytes);
679 CtdlFreeMessage(msg);
682 /* Mark this message as "seen" *unless* this is a "peek" operation */
684 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
689 * Called immediately before outputting a multipart bodystructure
691 void imap_fetch_bodystructure_pre(
692 char *name, char *filename, char *partnum, char *disp,
693 void *content, char *cbtype, size_t length, char *encoding,
703 * Called immediately after outputting a multipart bodystructure
705 void imap_fetch_bodystructure_post(
706 char *name, char *filename, char *partnum, char *disp,
707 void *content, char *cbtype, size_t length, char *encoding,
716 extract_token(subtype, cbtype, 1, '/', sizeof subtype);
717 imap_strout(subtype);
728 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
731 void imap_fetch_bodystructure_part(
732 char *name, char *filename, char *partnum, char *disp,
733 void *content, char *cbtype, size_t length, char *encoding,
738 int have_encoding = 0;
741 char cbmaintype[128];
744 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
746 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
747 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
750 strcpy(cbmaintype, "TEXT");
751 strcpy(cbsubtype, "PLAIN");
755 imap_strout(cbmaintype);
757 imap_strout(cbsubtype);
760 cprintf("(\"CHARSET\" \"US-ASCII\"");
762 if (name != NULL) if (strlen(name)>0) {
763 cprintf(" \"NAME\" ");
769 cprintf("NIL "); /* Body ID */
770 cprintf("NIL "); /* Body description */
772 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
774 imap_strout(encoding);
781 /* The next field is the size of the part in bytes. */
782 cprintf("%ld ", (long)length); /* bytes */
784 /* The next field is the number of lines in the part, if and only
785 * if the part is TEXT. More gratuitous complexity.
787 if (!strcasecmp(cbmaintype, "TEXT")) {
788 if (length) for (i=0; i<length; ++i) {
789 if (((char *)content)[i] == '\n') ++lines;
791 cprintf("%d ", lines);
794 /* More gratuitous complexity */
795 if ((!strcasecmp(cbmaintype, "MESSAGE"))
796 && (!strcasecmp(cbsubtype, "RFC822"))) {
798 A body type of type MESSAGE and subtype RFC822
799 contains, immediately after the basic fields, the
800 envelope structure, body structure, and size in
801 text lines of the encapsulated message.
805 /* MD5 value of body part; we can get away with NIL'ing this */
812 else if (strlen(disp) == 0) {
818 if (filename != NULL) if (strlen(filename)>0) {
819 cprintf(" (\"FILENAME\" ");
820 imap_strout(filename);
826 /* Body language (not defined yet) */
833 * Spew the BODYSTRUCTURE data for a message.
836 void imap_fetch_bodystructure (long msgnum, char *item,
837 struct CtdlMessage *msg) {
839 char *rfc822_body = NULL;
841 size_t rfc822_headers_len;
842 size_t rfc822_body_len;
847 /* For non-RFC822 (ordinary Citadel) messages, this is short and
850 if (msg->cm_format_type != FMT_RFC822) {
852 /* *sigh* We have to RFC822-format the message just to be able
853 * to measure it. FIXME use smi cached fields if possible
856 CC->redirect_buffer = malloc(SIZ);
857 CC->redirect_len = 0;
858 CC->redirect_alloc = SIZ;
859 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
860 rfc822 = CC->redirect_buffer;
861 rfc822_len = CC->redirect_len;
862 CC->redirect_buffer = NULL;
863 CC->redirect_len = 0;
864 CC->redirect_alloc = 0;
867 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
869 if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
874 rfc822_headers_len = rfc822_body - rfc822;
875 rfc822_body_len = rfc822_len - rfc822_headers_len;
878 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
879 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
880 "\"7BIT\" %d %d)", rfc822_body_len, lines);
885 /* For messages already stored in RFC822 format, we have to parse. */
886 cprintf("BODYSTRUCTURE ");
887 mime_parser(msg->cm_fields['M'],
889 *imap_fetch_bodystructure_part, /* part */
890 *imap_fetch_bodystructure_pre, /* pre-multi */
891 *imap_fetch_bodystructure_post, /* post-multi */
893 1); /* don't decode -- we want it as-is */
898 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
899 * individual message, once it has been selected for output.
901 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
903 struct CtdlMessage *msg = NULL;
906 cprintf("* %d FETCH (", seq);
908 for (i=0; i<num_items; ++i) {
910 /* Fetchable without going to the message store at all */
911 if (!strcasecmp(itemlist[i], "UID")) {
914 else if (!strcasecmp(itemlist[i], "FLAGS")) {
915 imap_fetch_flags(seq-1);
918 /* Potentially fetchable from cache, if the client requests
919 * stuff from the same message several times in a row.
921 else if (!strcasecmp(itemlist[i], "RFC822")) {
922 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
924 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
925 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
927 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
928 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
930 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
931 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
934 /* BODY fetches do their own fetching and caching too. */
935 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
936 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
938 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
939 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
942 /* Otherwise, load the message into memory.
944 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
945 if ((msg != NULL) && (!body_loaded)) {
946 CtdlFreeMessage(msg); /* need the whole thing */
950 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
953 imap_fetch_bodystructure(IMAP->msgids[seq-1],
956 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
958 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
961 imap_fetch_envelope(msg);
963 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
965 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
968 imap_fetch_internaldate(msg);
971 if (i != num_items-1) cprintf(" ");
976 CtdlFreeMessage(msg);
983 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
984 * validated and boiled down the request a bit.
986 void imap_do_fetch(int num_items, char **itemlist) {
989 if (IMAP->num_msgs > 0) {
990 for (i = 0; i < IMAP->num_msgs; ++i) {
991 if (IMAP->flags[i] & IMAP_SELECTED) {
992 imap_do_fetch_msg(i+1, num_items, itemlist);
1001 * Back end for imap_handle_macros()
1002 * Note that this function *only* looks at the beginning of the string. It
1003 * is not a generic search-and-replace function.
1005 void imap_macro_replace(char *str, char *find, char *replace) {
1008 if (!strncasecmp(str, find, strlen(find))) {
1009 if (str[strlen(find)]==' ') {
1010 strcpy(holdbuf, &str[strlen(find)+1]);
1011 strcpy(str, replace);
1013 strcat(str, holdbuf);
1015 if (str[strlen(find)]==0) {
1016 strcpy(holdbuf, &str[strlen(find)+1]);
1017 strcpy(str, replace);
1025 * Handle macros embedded in FETCH data items.
1026 * (What the heck are macros doing in a wire protocol? Are we trying to save
1027 * the computer at the other end the trouble of typing a lot of characters?)
1029 void imap_handle_macros(char *str) {
1033 for (i=0; i<strlen(str); ++i) {
1034 if (str[i]=='(') ++nest;
1035 if (str[i]=='[') ++nest;
1036 if (str[i]=='<') ++nest;
1037 if (str[i]=='{') ++nest;
1038 if (str[i]==')') --nest;
1039 if (str[i]==']') --nest;
1040 if (str[i]=='>') --nest;
1041 if (str[i]=='}') --nest;
1044 imap_macro_replace(&str[i],
1046 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1048 imap_macro_replace(&str[i],
1052 imap_macro_replace(&str[i],
1054 "FLAGS INTERNALDATE RFC822.SIZE"
1056 imap_macro_replace(&str[i],
1058 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1066 * Break out the data items requested, possibly a parenthesized list.
1067 * Returns the number of data items, or -1 if the list is invalid.
1068 * NOTE: this function alters the string it is fed, and uses it as a buffer
1069 * to hold the data for the pointers it returns.
1071 int imap_extract_data_items(char **argv, char *items) {
1077 /* Convert all whitespace to ordinary space characters. */
1078 for (i=0; i<strlen(items); ++i) {
1079 if (isspace(items[i])) items[i]=' ';
1082 /* Strip leading and trailing whitespace, then strip leading and
1083 * trailing parentheses if it's a list
1086 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1087 items[strlen(items)-1] = 0;
1088 strcpy(items, &items[1]);
1092 /* Parse any macro data items */
1093 imap_handle_macros(items);
1096 * Now break out the data items. We throw in one trailing space in
1097 * order to avoid having to break out the last one manually.
1101 initial_len = strlen(items);
1102 for (i=0; i<initial_len; ++i) {
1103 if (items[i]=='(') ++nest;
1104 if (items[i]=='[') ++nest;
1105 if (items[i]=='<') ++nest;
1106 if (items[i]=='{') ++nest;
1107 if (items[i]==')') --nest;
1108 if (items[i]==']') --nest;
1109 if (items[i]=='>') --nest;
1110 if (items[i]=='}') --nest;
1112 if (nest <= 0) if (items[i]==' ') {
1114 argv[num_items++] = start;
1115 start = &items[i+1];
1125 * One particularly hideous aspect of IMAP is that we have to allow the client
1126 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1127 * handles this by setting the IMAP_SELECTED flag for each message specified in
1128 * the ranges/sets, then looping through the message array, outputting messages
1129 * with the flag set. We don't bother returning an error if an out-of-range
1130 * number is specified (we just return quietly) because any client braindead
1131 * enough to request a bogus message number isn't going to notice the
1132 * difference anyway.
1134 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1135 * message included in the specified range.
1137 * Set is_uid to 1 to fetch by UID instead of sequence number.
1139 void imap_pick_range(char *supplied_range, int is_uid) {
1143 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1145 char actual_range[SIZ];
1148 * Handle the "ALL" macro
1150 if (!strcasecmp(supplied_range, "ALL")) {
1151 safestrncpy(actual_range, "1:*", sizeof actual_range);
1154 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1158 * Clear out the IMAP_SELECTED flags for all messages.
1160 for (i = 0; i < IMAP->num_msgs; ++i) {
1161 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1165 * Now set it for all specified messages.
1167 num_sets = num_tokens(actual_range, ',');
1168 for (s=0; s<num_sets; ++s) {
1169 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1171 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1172 if (num_tokens(setstr, ':') >= 2) {
1173 extract_token(histr, setstr, 1, ':', sizeof histr);
1174 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1177 safestrncpy(histr, lostr, sizeof histr);
1182 /* Loop through the array, flipping bits where appropriate */
1183 for (i = 1; i <= IMAP->num_msgs; ++i) {
1184 if (is_uid) { /* fetch by sequence number */
1185 if ( (IMAP->msgids[i-1]>=lo)
1186 && (IMAP->msgids[i-1]<=hi)) {
1187 IMAP->flags[i-1] |= IMAP_SELECTED;
1190 else { /* fetch by uid */
1191 if ( (i>=lo) && (i<=hi)) {
1192 IMAP->flags[i-1] |= IMAP_SELECTED;
1203 * This function is called by the main command loop.
1205 void imap_fetch(int num_parms, char *parms[]) {
1207 char *itemlist[512];
1211 if (num_parms < 4) {
1212 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1216 imap_pick_range(parms[2], 0);
1219 for (i=3; i<num_parms; ++i) {
1220 strcat(items, parms[i]);
1221 if (i < (num_parms-1)) strcat(items, " ");
1224 num_items = imap_extract_data_items(itemlist, items);
1225 if (num_items < 1) {
1226 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1230 imap_do_fetch(num_items, itemlist);
1231 cprintf("%s OK FETCH completed\r\n", parms[0]);
1235 * This function is called by the main command loop.
1237 void imap_uidfetch(int num_parms, char *parms[]) {
1239 char *itemlist[512];
1242 int have_uid_item = 0;
1244 if (num_parms < 5) {
1245 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1249 imap_pick_range(parms[3], 1);
1252 for (i=4; i<num_parms; ++i) {
1253 strcat(items, parms[i]);
1254 if (i < (num_parms-1)) strcat(items, " ");
1257 num_items = imap_extract_data_items(itemlist, items);
1258 if (num_items < 1) {
1259 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1263 /* If the "UID" item was not included, we include it implicitly
1264 * (at the beginning) because this is a UID FETCH command
1266 for (i=0; i<num_items; ++i) {
1267 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1269 if (have_uid_item == 0) {
1270 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1272 itemlist[0] = "UID";
1275 imap_do_fetch(num_items, itemlist);
1276 cprintf("%s OK UID FETCH completed\r\n", parms[0]);