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"
57 struct imap_fetch_part {
58 char desired_section[SIZ];
63 * Individual field functions for imap_do_fetch_msg() ...
66 void imap_fetch_uid(int seq) {
67 cprintf("UID %ld", IMAP->msgids[seq-1]);
70 void imap_fetch_flags(int seq) {
71 int num_flags_printed = 0;
73 if (IMAP->flags[seq] & IMAP_DELETED) {
74 if (num_flags_printed > 0) cprintf(" ");
78 if (IMAP->flags[seq] & IMAP_SEEN) {
79 if (num_flags_printed > 0) cprintf(" ");
83 if (IMAP->flags[seq] & IMAP_ANSWERED) {
84 if (num_flags_printed > 0) cprintf(" ");
85 cprintf("\\Answered");
88 if (IMAP->flags[seq] & IMAP_RECENT) {
89 if (num_flags_printed > 0) cprintf(" ");
96 void imap_fetch_internaldate(struct CtdlMessage *msg) {
100 if (msg->cm_fields['T'] != NULL) {
101 msgdate = atol(msg->cm_fields['T']);
104 msgdate = time(NULL);
107 datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
108 cprintf("INTERNALDATE \"%s\"", buf);
113 * Fetch RFC822-formatted messages.
115 * 'whichfmt' should be set to one of:
116 * "RFC822" entire message
117 * "RFC822.HEADER" headers only (with trailing blank line)
118 * "RFC822.SIZE" size of translated message
119 * "RFC822.TEXT" body only (without leading blank line)
121 void imap_fetch_rfc822(long msgnum, char *whichfmt) {
124 size_t headers_size, text_size, total_size;
125 size_t bytes_to_send;
127 /* Cache the most recent RFC822 FETCH because some clients like to
128 * fetch in pieces, and we don't want to have to go back to the
129 * message store for each piece.
131 if ((IMAP->cached_rfc822_data != NULL)
132 && (IMAP->cached_rfc822_msgnum == msgnum)) {
135 else if (IMAP->cached_rfc822_data != NULL) {
136 /* Some other message is cached -- free it */
137 free(IMAP->cached_rfc822_data);
138 IMAP->cached_rfc822_data = NULL;
139 IMAP->cached_rfc822_msgnum = (-1);
140 IMAP->cached_rfc822_len = 0;
143 /* At this point, we now can fetch and convert the message iff it's not
144 * the one we had cached.
146 if (IMAP->cached_rfc822_data == NULL) {
148 * Load the message into memory for translation & measurement
150 CC->redirect_buffer = malloc(SIZ);
151 CC->redirect_len = 0;
152 CC->redirect_alloc = SIZ;
153 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
154 IMAP->cached_rfc822_data = CC->redirect_buffer;
155 IMAP->cached_rfc822_len = CC->redirect_len;
156 IMAP->cached_rfc822_msgnum = msgnum;
157 CC->redirect_buffer = NULL;
158 CC->redirect_len = 0;
159 CC->redirect_alloc = 0;
163 * Now figure out where the headers/text break is. IMAP considers the
164 * intervening blank line to be part of the headers, not the text.
170 ptr = IMAP->cached_rfc822_data;
172 ptr = memreadline(ptr, buf, sizeof buf);
175 if (strlen(buf) == 0) {
176 headers_size = ptr - IMAP->cached_rfc822_data;
179 } while ( (headers_size == 0) && (*ptr != 0) );
181 total_size = IMAP->cached_rfc822_len;
182 text_size = total_size - headers_size;
184 lprintf(CTDL_DEBUG, "RFC822: headers=%d, text=%d, total=%d\n",
185 headers_size, text_size, total_size);
187 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
188 cprintf("RFC822.SIZE %d", total_size);
192 else if (!strcasecmp(whichfmt, "RFC822")) {
193 ptr = IMAP->cached_rfc822_data;
194 bytes_to_send = total_size;
197 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
198 ptr = IMAP->cached_rfc822_data;
199 bytes_to_send = headers_size;
202 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
203 ptr = &IMAP->cached_rfc822_data[headers_size];
204 bytes_to_send = text_size;
207 cprintf("%s {%d}\r\n", whichfmt, bytes_to_send);
208 client_write(ptr, bytes_to_send);
214 * Load a specific part of a message into the temp file to be output to a
215 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
216 * but we still can't handle "2.HEADER" (which might not be a problem, because
217 * we currently don't have the ability to break out nested RFC822's anyway).
219 * Note: mime_parser() was called with dont_decode set to 1, so we have the
220 * luxury of simply spewing without having to re-encode.
222 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
223 void *content, char *cbtype, size_t length, char *encoding,
226 struct imap_fetch_part *imfp;
229 imfp = (struct imap_fetch_part *)cbuserdata;
231 if (!strcasecmp(partnum, imfp->desired_section)) {
232 fwrite(content, length, 1, imfp->output_fp);
235 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
237 if (!strcasecmp(imfp->desired_section, mbuf2)) {
238 fprintf(imfp->output_fp, "Content-type: %s", cbtype);
239 if (strlen(name) > 0)
240 fprintf(imfp->output_fp, "; name=\"%s\"", name);
241 fprintf(imfp->output_fp, "\r\n");
242 if (strlen(encoding) > 0)
243 fprintf(imfp->output_fp,
244 "Content-Transfer-Encoding: %s\r\n", encoding);
245 if (strlen(encoding) > 0) {
246 fprintf(imfp->output_fp, "Content-Disposition: %s",
248 if (strlen(filename) > 0) {
249 fprintf(imfp->output_fp, "; filename=\"%s\"",
252 fprintf(imfp->output_fp, "\r\n");
254 fprintf(imfp->output_fp, "Content-Length: %ld\r\n", (long)length);
255 fprintf(imfp->output_fp, "\r\n");
263 * Called by imap_fetch_envelope() to output the "From" field.
264 * This is in its own function because its logic is kind of complex. We
265 * really need to make this suck less.
267 void imap_output_envelope_from(struct CtdlMessage *msg) {
268 char user[SIZ], node[SIZ], name[SIZ];
270 /* For anonymous messages, it's so easy! */
271 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
272 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
275 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
276 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
280 /* For everything else, we do stuff. */
281 cprintf("(("); /* open double-parens */
282 imap_strout(msg->cm_fields['A']); /* personal name */
283 cprintf(" NIL "); /* source route (not used) */
286 if (msg->cm_fields['F'] != NULL) {
287 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
288 imap_strout(user); /* mailbox name (user id) */
290 if (!strcasecmp(node, config.c_nodename)) {
291 imap_strout(config.c_fqdn);
294 imap_strout(node); /* host name */
298 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
300 imap_strout(msg->cm_fields['N']); /* host name */
303 cprintf(")) "); /* close double-parens */
309 * Output an envelope address (or set of addresses) in the official,
310 * convuluted, braindead format. (Note that we can't use this for
311 * the "From" address because its data may come from a number of different
312 * fields. But we can use it for "To" and possibly others.
314 void imap_output_envelope_addr(char *addr) {
315 char individual_addr[SIZ];
327 if (strlen(addr) == 0) {
334 /* How many addresses are listed here? */
335 num_addrs = num_tokens(addr, ',');
337 /* Output them one by one. */
338 for (i=0; i<num_addrs; ++i) {
339 extract_token(individual_addr, addr, i, ',');
340 striplt(individual_addr);
341 process_rfc822_addr(individual_addr, user, node, name);
349 if (i < (num_addrs-1)) cprintf(" ");
357 * Implements the ENVELOPE fetch item
359 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
360 * so we don't have to check for that condition like we do elsewhere.
362 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
363 char datestringbuf[SIZ];
365 char *fieldptr = NULL;
367 /* Parse the message date into an IMAP-format date string */
368 if (msg->cm_fields['T'] != NULL) {
369 msgdate = atol(msg->cm_fields['T']);
372 msgdate = time(NULL);
374 datestring(datestringbuf, sizeof datestringbuf,
375 msgdate, DATESTRING_IMAP);
377 /* Now start spewing data fields. The order is important, as it is
378 * defined by the protocol specification. Nonexistent fields must
379 * be output as NIL, existent fields must be quoted or literalled.
380 * The imap_strout() function conveniently does all this for us.
382 cprintf("ENVELOPE (");
385 imap_strout(datestringbuf);
389 imap_strout(msg->cm_fields['U']);
393 imap_output_envelope_from(msg);
395 /* Sender (default to same as 'From' if not present) */
396 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
397 if (fieldptr != NULL) {
398 imap_output_envelope_addr(fieldptr);
402 imap_output_envelope_from(msg);
406 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
407 if (fieldptr != NULL) {
408 imap_output_envelope_addr(fieldptr);
412 imap_output_envelope_from(msg);
416 imap_output_envelope_addr(msg->cm_fields['R']);
419 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
420 imap_output_envelope_addr(fieldptr);
421 if (fieldptr != NULL) free(fieldptr);
424 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
425 imap_output_envelope_addr(fieldptr);
426 if (fieldptr != NULL) free(fieldptr);
429 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
430 imap_strout(fieldptr);
432 if (fieldptr != NULL) free(fieldptr);
435 imap_strout(msg->cm_fields['I']);
442 * Strip any non header information out of a chunk of RFC822 data on disk,
443 * then boil it down to just the fields we want.
445 void imap_strip_headers(FILE *fp, char *section) {
447 char *which_fields = NULL;
448 int doing_headers = 0;
453 char *boiled_headers = NULL;
455 int done_headers = 0;
457 which_fields = strdup(section);
459 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
461 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
464 for (i=0; i<strlen(which_fields); ++i) {
465 if (which_fields[i]=='(')
466 strcpy(which_fields, &which_fields[i+1]);
468 for (i=0; i<strlen(which_fields); ++i) {
469 if (which_fields[i]==')')
472 num_parms = imap_parameterize(parms, which_fields);
474 fseek(fp, 0L, SEEK_END);
475 boiled_headers = malloc((size_t)(ftell(fp) + 256L));
476 strcpy(boiled_headers, "");
480 while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
481 if (!isspace(buf[0])) {
483 if (doing_headers == 0) ok = 1;
485 if (headers_not) ok = 1;
487 for (i=0; i<num_parms; ++i) {
488 if ( (!strncasecmp(buf, parms[i],
489 strlen(parms[i]))) &&
490 (buf[strlen(parms[i])]==':') ) {
491 if (headers_not) ok = 0;
499 strcat(boiled_headers, buf);
502 if (strlen(buf) == 0) done_headers = 1;
503 if (buf[0]=='\r') done_headers = 1;
504 if (buf[0]=='\n') done_headers = 1;
507 strcat(boiled_headers, "\r\n");
509 /* Now write it back */
511 fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
513 ftruncate(fileno(fp), ftell(fp));
517 free(boiled_headers);
522 * Implements the BODY and BODY.PEEK fetch items
524 void imap_fetch_body(long msgnum, char *item, int is_peek) {
525 struct CtdlMessage *msg = NULL;
531 long bytes_remaining = 0;
534 struct imap_fetch_part imfp;
536 /* extract section */
537 safestrncpy(section, item, sizeof section);
538 if (strchr(section, '[') != NULL) {
539 stripallbut(section, '[', ']');
541 /* lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") ); */
543 /* Burn the cache if we don't have the same section of the
544 * same message again.
546 if (IMAP->cached_body != NULL) {
547 if ((IMAP->cached_bodymsgnum != msgnum)
548 || (strcasecmp(IMAP->cached_bodypart, section)) ) {
549 fclose(IMAP->cached_body);
550 IMAP->cached_body = NULL;
551 IMAP->cached_bodymsgnum = (-1);
552 strcpy(IMAP->cached_bodypart, "");
556 /* extract partial */
557 safestrncpy(partial, item, sizeof partial);
558 if (strchr(partial, '<') != NULL) {
559 stripallbut(partial, '<', '>');
562 if (is_partial == 0) strcpy(partial, "");
563 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
565 if (IMAP->cached_body == NULL) {
568 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n", strerror(errno));
571 msg = CtdlFetchMessage(msgnum, 1);
574 /* Now figure out what the client wants, and get it */
576 if (IMAP->cached_body != NULL) {
577 tmp = IMAP->cached_body;
579 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
580 CtdlRedirectOutput(tmp);
581 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
583 CtdlRedirectOutput(NULL);
586 else if (!strcmp(section, "")) {
587 CtdlRedirectOutput(tmp);
588 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
589 CtdlRedirectOutput(NULL);
593 * If the client asked for just headers, or just particular header
594 * fields, strip it down.
596 else if (!strncasecmp(section, "HEADER", 6)) {
597 CtdlRedirectOutput(tmp);
598 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ONLY, 0, 1);
599 CtdlRedirectOutput(NULL);
600 imap_strip_headers(tmp, section);
604 * Strip it down if the client asked for everything _except_ headers.
606 else if (!strncasecmp(section, "TEXT", 4)) {
607 CtdlRedirectOutput(tmp);
608 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
609 CtdlRedirectOutput(NULL);
613 * Anything else must be a part specifier.
614 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
617 safestrncpy(imfp.desired_section, section, sizeof(imfp.desired_section));
618 imfp.output_fp = tmp;
619 mime_parser(msg->cm_fields['M'], NULL,
620 *imap_load_part, NULL, NULL,
626 fseek(tmp, 0L, SEEK_END);
627 bytes_remaining = ftell(tmp);
629 if (is_partial == 0) {
631 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
634 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
635 if ((bytes_remaining - pstart) < pbytes) {
636 pbytes = bytes_remaining - pstart;
638 fseek(tmp, pstart, SEEK_SET);
639 bytes_remaining = pbytes;
640 cprintf("BODY[%s]<%ld> {%ld}\r\n",
641 section, pstart, bytes_remaining);
644 blocksize = (long)sizeof(buf);
645 while (bytes_remaining > 0L) {
646 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
647 fread(buf, blocksize, 1, tmp);
648 client_write(buf, (int)blocksize);
649 bytes_remaining = bytes_remaining - blocksize;
652 /* Don't close it ... cache it! */
654 IMAP->cached_body = tmp;
655 IMAP->cached_bodymsgnum = msgnum;
656 strcpy(IMAP->cached_bodypart, section);
659 CtdlFreeMessage(msg);
662 /* Mark this message as "seen" *unless* this is a "peek" operation */
664 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
669 * Called immediately before outputting a multipart bodystructure
671 void imap_fetch_bodystructure_pre(
672 char *name, char *filename, char *partnum, char *disp,
673 void *content, char *cbtype, size_t length, char *encoding,
683 * Called immediately after outputting a multipart bodystructure
685 void imap_fetch_bodystructure_post(
686 char *name, char *filename, char *partnum, char *disp,
687 void *content, char *cbtype, size_t length, char *encoding,
696 extract_token(subtype, cbtype, 1, '/');
697 imap_strout(subtype);
708 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
711 void imap_fetch_bodystructure_part(
712 char *name, char *filename, char *partnum, char *disp,
713 void *content, char *cbtype, size_t length, char *encoding,
718 int have_encoding = 0;
721 char cbmaintype[SIZ];
724 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
726 extract_token(cbmaintype, cbtype, 0, '/');
727 extract_token(cbsubtype, cbtype, 1, '/');
730 strcpy(cbmaintype, "TEXT");
731 strcpy(cbsubtype, "PLAIN");
735 imap_strout(cbmaintype);
737 imap_strout(cbsubtype);
740 cprintf("(\"CHARSET\" \"US-ASCII\"");
742 if (name != NULL) if (strlen(name)>0) {
743 cprintf(" \"NAME\" ");
749 cprintf("NIL "); /* Body ID */
750 cprintf("NIL "); /* Body description */
752 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
754 imap_strout(encoding);
761 /* The next field is the size of the part in bytes. */
762 cprintf("%ld ", (long)length); /* bytes */
764 /* The next field is the number of lines in the part, if and only
765 * if the part is TEXT. More gratuitous complexity.
767 if (!strcasecmp(cbmaintype, "TEXT")) {
768 if (length) for (i=0; i<length; ++i) {
769 if (((char *)content)[i] == '\n') ++lines;
771 cprintf("%d ", lines);
774 /* More gratuitous complexity */
775 if ((!strcasecmp(cbmaintype, "MESSAGE"))
776 && (!strcasecmp(cbsubtype, "RFC822"))) {
778 A body type of type MESSAGE and subtype RFC822
779 contains, immediately after the basic fields, the
780 envelope structure, body structure, and size in
781 text lines of the encapsulated message.
785 /* MD5 value of body part; we can get away with NIL'ing this */
792 else if (strlen(disp) == 0) {
798 if (filename != NULL) if (strlen(filename)>0) {
799 cprintf(" (\"FILENAME\" ");
800 imap_strout(filename);
806 /* Body language (not defined yet) */
813 * Spew the BODYSTRUCTURE data for a message.
816 void imap_fetch_bodystructure (long msgnum, char *item,
817 struct CtdlMessage *msg) {
819 char *rfc822_body = NULL;
821 size_t rfc822_headers_len;
822 size_t rfc822_body_len;
827 /* For non-RFC822 (ordinary Citadel) messages, this is short and
830 if (msg->cm_format_type != FMT_RFC822) {
832 /* *sigh* We have to RFC822-format the message just to be able
833 * to measure it. FIXME use smi cached fields if possible
836 CC->redirect_buffer = malloc(SIZ);
837 CC->redirect_len = 0;
838 CC->redirect_alloc = SIZ;
839 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
840 rfc822 = CC->redirect_buffer;
841 rfc822_len = CC->redirect_len;
842 CC->redirect_buffer = NULL;
843 CC->redirect_len = 0;
844 CC->redirect_alloc = 0;
847 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
849 if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
854 rfc822_headers_len = rfc822_body - rfc822;
855 rfc822_body_len = rfc822_len - rfc822_headers_len;
858 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
859 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
860 "\"7BIT\" %d %d)", rfc822_body_len, lines);
865 /* For messages already stored in RFC822 format, we have to parse. */
866 cprintf("BODYSTRUCTURE ");
867 mime_parser(msg->cm_fields['M'],
869 *imap_fetch_bodystructure_part, /* part */
870 *imap_fetch_bodystructure_pre, /* pre-multi */
871 *imap_fetch_bodystructure_post, /* post-multi */
873 1); /* don't decode -- we want it as-is */
878 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
879 * individual message, once it has been selected for output.
881 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
883 struct CtdlMessage *msg = NULL;
885 cprintf("* %d FETCH (", seq);
887 for (i=0; i<num_items; ++i) {
889 /* Fetchable without going to the message store at all */
890 if (!strcasecmp(itemlist[i], "UID")) {
893 else if (!strcasecmp(itemlist[i], "FLAGS")) {
894 imap_fetch_flags(seq-1);
897 /* Potentially fetchable from cache, if the client requests
898 * stuff from the same message several times in a row.
900 else if (!strcasecmp(itemlist[i], "RFC822")) {
901 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
903 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
904 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
906 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
907 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
909 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
910 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
913 /* BODY fetches do their own fetching and caching too. */
914 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
915 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
917 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
918 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
921 /* Otherwise, load the message into memory.
923 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
924 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
925 imap_fetch_bodystructure(IMAP->msgids[seq-1],
928 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
929 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
930 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
932 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
933 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
934 imap_fetch_internaldate(msg);
937 if (i != num_items-1) cprintf(" ");
942 CtdlFreeMessage(msg);
949 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
950 * validated and boiled down the request a bit.
952 void imap_do_fetch(int num_items, char **itemlist) {
955 if (IMAP->num_msgs > 0) {
956 for (i = 0; i < IMAP->num_msgs; ++i) {
957 if (IMAP->flags[i] & IMAP_SELECTED) {
958 imap_do_fetch_msg(i+1, num_items, itemlist);
967 * Back end for imap_handle_macros()
968 * Note that this function *only* looks at the beginning of the string. It
969 * is not a generic search-and-replace function.
971 void imap_macro_replace(char *str, char *find, char *replace) {
974 if (!strncasecmp(str, find, strlen(find))) {
975 if (str[strlen(find)]==' ') {
976 strcpy(holdbuf, &str[strlen(find)+1]);
977 strcpy(str, replace);
979 strcat(str, holdbuf);
981 if (str[strlen(find)]==0) {
982 strcpy(holdbuf, &str[strlen(find)+1]);
983 strcpy(str, replace);
991 * Handle macros embedded in FETCH data items.
992 * (What the heck are macros doing in a wire protocol? Are we trying to save
993 * the computer at the other end the trouble of typing a lot of characters?)
995 void imap_handle_macros(char *str) {
999 for (i=0; i<strlen(str); ++i) {
1000 if (str[i]=='(') ++nest;
1001 if (str[i]=='[') ++nest;
1002 if (str[i]=='<') ++nest;
1003 if (str[i]=='{') ++nest;
1004 if (str[i]==')') --nest;
1005 if (str[i]==']') --nest;
1006 if (str[i]=='>') --nest;
1007 if (str[i]=='}') --nest;
1010 imap_macro_replace(&str[i],
1012 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1014 imap_macro_replace(&str[i],
1018 imap_macro_replace(&str[i],
1020 "FLAGS INTERNALDATE RFC822.SIZE"
1022 imap_macro_replace(&str[i],
1024 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1032 * Break out the data items requested, possibly a parenthesized list.
1033 * Returns the number of data items, or -1 if the list is invalid.
1034 * NOTE: this function alters the string it is fed, and uses it as a buffer
1035 * to hold the data for the pointers it returns.
1037 int imap_extract_data_items(char **argv, char *items) {
1043 /* Convert all whitespace to ordinary space characters. */
1044 for (i=0; i<strlen(items); ++i) {
1045 if (isspace(items[i])) items[i]=' ';
1048 /* Strip leading and trailing whitespace, then strip leading and
1049 * trailing parentheses if it's a list
1052 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1053 items[strlen(items)-1] = 0;
1054 strcpy(items, &items[1]);
1058 /* Parse any macro data items */
1059 imap_handle_macros(items);
1062 * Now break out the data items. We throw in one trailing space in
1063 * order to avoid having to break out the last one manually.
1067 initial_len = strlen(items);
1068 for (i=0; i<initial_len; ++i) {
1069 if (items[i]=='(') ++nest;
1070 if (items[i]=='[') ++nest;
1071 if (items[i]=='<') ++nest;
1072 if (items[i]=='{') ++nest;
1073 if (items[i]==')') --nest;
1074 if (items[i]==']') --nest;
1075 if (items[i]=='>') --nest;
1076 if (items[i]=='}') --nest;
1078 if (nest <= 0) if (items[i]==' ') {
1080 argv[num_items++] = start;
1081 start = &items[i+1];
1091 * One particularly hideous aspect of IMAP is that we have to allow the client
1092 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1093 * handles this by setting the IMAP_SELECTED flag for each message specified in
1094 * the ranges/sets, then looping through the message array, outputting messages
1095 * with the flag set. We don't bother returning an error if an out-of-range
1096 * number is specified (we just return quietly) because any client braindead
1097 * enough to request a bogus message number isn't going to notice the
1098 * difference anyway.
1100 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1101 * message included in the specified range.
1103 * Set is_uid to 1 to fetch by UID instead of sequence number.
1105 void imap_pick_range(char *supplied_range, int is_uid) {
1109 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1111 char actual_range[SIZ];
1114 * Handle the "ALL" macro
1116 if (!strcasecmp(supplied_range, "ALL")) {
1117 safestrncpy(actual_range, "1:*", sizeof actual_range);
1120 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1124 * Clear out the IMAP_SELECTED flags for all messages.
1126 for (i = 0; i < IMAP->num_msgs; ++i) {
1127 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1131 * Now set it for all specified messages.
1133 num_sets = num_tokens(actual_range, ',');
1134 for (s=0; s<num_sets; ++s) {
1135 extract_token(setstr, actual_range, s, ',');
1137 extract_token(lostr, setstr, 0, ':');
1138 if (num_tokens(setstr, ':') >= 2) {
1139 extract_token(histr, setstr, 1, ':');
1140 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1143 strcpy(histr, lostr);
1148 /* Loop through the array, flipping bits where appropriate */
1149 for (i = 1; i <= IMAP->num_msgs; ++i) {
1150 if (is_uid) { /* fetch by sequence number */
1151 if ( (IMAP->msgids[i-1]>=lo)
1152 && (IMAP->msgids[i-1]<=hi)) {
1154 IMAP->flags[i-1] | IMAP_SELECTED;
1157 else { /* fetch by uid */
1158 if ( (i>=lo) && (i<=hi)) {
1160 IMAP->flags[i-1] | IMAP_SELECTED;
1171 * This function is called by the main command loop.
1173 void imap_fetch(int num_parms, char *parms[]) {
1175 char *itemlist[SIZ];
1179 if (num_parms < 4) {
1180 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1184 imap_pick_range(parms[2], 0);
1187 for (i=3; i<num_parms; ++i) {
1188 strcat(items, parms[i]);
1189 if (i < (num_parms-1)) strcat(items, " ");
1192 num_items = imap_extract_data_items(itemlist, items);
1193 if (num_items < 1) {
1194 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1198 imap_do_fetch(num_items, itemlist);
1199 cprintf("%s OK FETCH completed\r\n", parms[0]);
1203 * This function is called by the main command loop.
1205 void imap_uidfetch(int num_parms, char *parms[]) {
1207 char *itemlist[SIZ];
1210 int have_uid_item = 0;
1212 if (num_parms < 5) {
1213 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1217 imap_pick_range(parms[3], 1);
1220 for (i=4; i<num_parms; ++i) {
1221 strcat(items, parms[i]);
1222 if (i < (num_parms-1)) strcat(items, " ");
1225 num_items = imap_extract_data_items(itemlist, items);
1226 if (num_items < 1) {
1227 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1231 /* If the "UID" item was not included, we include it implicitly
1232 * because this is a UID FETCH command
1234 for (i=0; i<num_items; ++i) {
1235 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1237 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1239 imap_do_fetch(num_items, itemlist);
1240 cprintf("%s OK UID FETCH completed\r\n", parms[0]);