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 != NULL) );
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) {
821 long start_of_body = 0L;
822 long body_bytes = 0L;
824 /* For non-RFC822 (ordinary Citadel) messages, this is short and
827 if (msg->cm_format_type != FMT_RFC822) {
829 /* *sigh* We have to RFC822-format the message just to be able
833 if (tmp == NULL) return;
834 CtdlRedirectOutput(tmp);
835 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
836 CtdlRedirectOutput(NULL);
839 while (fgets(buf, sizeof buf, tmp) != NULL) {
841 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
842 start_of_body = ftell(tmp);
845 body_bytes = ftell(tmp) - start_of_body;
848 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
849 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
850 "\"7BIT\" %ld %ld)", body_bytes, lines);
855 /* For messages already stored in RFC822 format, we have to parse. */
856 cprintf("BODYSTRUCTURE ");
857 mime_parser(msg->cm_fields['M'],
859 *imap_fetch_bodystructure_part, /* part */
860 *imap_fetch_bodystructure_pre, /* pre-multi */
861 *imap_fetch_bodystructure_post, /* post-multi */
863 1); /* don't decode -- we want it as-is */
868 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
869 * individual message, once it has been selected for output.
871 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
873 struct CtdlMessage *msg = NULL;
875 cprintf("* %d FETCH (", seq);
877 for (i=0; i<num_items; ++i) {
879 /* Fetchable without going to the message store at all */
880 if (!strcasecmp(itemlist[i], "UID")) {
883 else if (!strcasecmp(itemlist[i], "FLAGS")) {
884 imap_fetch_flags(seq-1);
887 /* Potentially fetchable from cache, if the client requests
888 * stuff from the same message several times in a row.
890 else if (!strcasecmp(itemlist[i], "RFC822")) {
891 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
893 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
894 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
896 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
897 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
899 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
900 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
903 /* BODY fetches do their own fetching and caching too. */
904 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
905 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
907 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
908 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
911 /* Otherwise, load the message into memory.
913 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
914 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
915 imap_fetch_bodystructure(IMAP->msgids[seq-1],
918 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
919 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
920 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
922 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
923 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
924 imap_fetch_internaldate(msg);
927 if (i != num_items-1) cprintf(" ");
932 CtdlFreeMessage(msg);
939 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
940 * validated and boiled down the request a bit.
942 void imap_do_fetch(int num_items, char **itemlist) {
945 if (IMAP->num_msgs > 0) {
946 for (i = 0; i < IMAP->num_msgs; ++i) {
947 if (IMAP->flags[i] & IMAP_SELECTED) {
948 imap_do_fetch_msg(i+1, num_items, itemlist);
957 * Back end for imap_handle_macros()
958 * Note that this function *only* looks at the beginning of the string. It
959 * is not a generic search-and-replace function.
961 void imap_macro_replace(char *str, char *find, char *replace) {
964 if (!strncasecmp(str, find, strlen(find))) {
965 if (str[strlen(find)]==' ') {
966 strcpy(holdbuf, &str[strlen(find)+1]);
967 strcpy(str, replace);
969 strcat(str, holdbuf);
971 if (str[strlen(find)]==0) {
972 strcpy(holdbuf, &str[strlen(find)+1]);
973 strcpy(str, replace);
981 * Handle macros embedded in FETCH data items.
982 * (What the heck are macros doing in a wire protocol? Are we trying to save
983 * the computer at the other end the trouble of typing a lot of characters?)
985 void imap_handle_macros(char *str) {
989 for (i=0; i<strlen(str); ++i) {
990 if (str[i]=='(') ++nest;
991 if (str[i]=='[') ++nest;
992 if (str[i]=='<') ++nest;
993 if (str[i]=='{') ++nest;
994 if (str[i]==')') --nest;
995 if (str[i]==']') --nest;
996 if (str[i]=='>') --nest;
997 if (str[i]=='}') --nest;
1000 imap_macro_replace(&str[i],
1002 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1004 imap_macro_replace(&str[i],
1008 imap_macro_replace(&str[i],
1010 "FLAGS INTERNALDATE RFC822.SIZE"
1012 imap_macro_replace(&str[i],
1014 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1022 * Break out the data items requested, possibly a parenthesized list.
1023 * Returns the number of data items, or -1 if the list is invalid.
1024 * NOTE: this function alters the string it is fed, and uses it as a buffer
1025 * to hold the data for the pointers it returns.
1027 int imap_extract_data_items(char **argv, char *items) {
1033 /* Convert all whitespace to ordinary space characters. */
1034 for (i=0; i<strlen(items); ++i) {
1035 if (isspace(items[i])) items[i]=' ';
1038 /* Strip leading and trailing whitespace, then strip leading and
1039 * trailing parentheses if it's a list
1042 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1043 items[strlen(items)-1] = 0;
1044 strcpy(items, &items[1]);
1048 /* Parse any macro data items */
1049 imap_handle_macros(items);
1052 * Now break out the data items. We throw in one trailing space in
1053 * order to avoid having to break out the last one manually.
1057 initial_len = strlen(items);
1058 for (i=0; i<initial_len; ++i) {
1059 if (items[i]=='(') ++nest;
1060 if (items[i]=='[') ++nest;
1061 if (items[i]=='<') ++nest;
1062 if (items[i]=='{') ++nest;
1063 if (items[i]==')') --nest;
1064 if (items[i]==']') --nest;
1065 if (items[i]=='>') --nest;
1066 if (items[i]=='}') --nest;
1068 if (nest <= 0) if (items[i]==' ') {
1070 argv[num_items++] = start;
1071 start = &items[i+1];
1081 * One particularly hideous aspect of IMAP is that we have to allow the client
1082 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1083 * handles this by setting the IMAP_SELECTED flag for each message specified in
1084 * the ranges/sets, then looping through the message array, outputting messages
1085 * with the flag set. We don't bother returning an error if an out-of-range
1086 * number is specified (we just return quietly) because any client braindead
1087 * enough to request a bogus message number isn't going to notice the
1088 * difference anyway.
1090 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1091 * message included in the specified range.
1093 * Set is_uid to 1 to fetch by UID instead of sequence number.
1095 void imap_pick_range(char *supplied_range, int is_uid) {
1099 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1101 char actual_range[SIZ];
1104 * Handle the "ALL" macro
1106 if (!strcasecmp(supplied_range, "ALL")) {
1107 safestrncpy(actual_range, "1:*", sizeof actual_range);
1110 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1114 * Clear out the IMAP_SELECTED flags for all messages.
1116 for (i = 0; i < IMAP->num_msgs; ++i) {
1117 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1121 * Now set it for all specified messages.
1123 num_sets = num_tokens(actual_range, ',');
1124 for (s=0; s<num_sets; ++s) {
1125 extract_token(setstr, actual_range, s, ',');
1127 extract_token(lostr, setstr, 0, ':');
1128 if (num_tokens(setstr, ':') >= 2) {
1129 extract_token(histr, setstr, 1, ':');
1130 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1133 strcpy(histr, lostr);
1138 /* Loop through the array, flipping bits where appropriate */
1139 for (i = 1; i <= IMAP->num_msgs; ++i) {
1140 if (is_uid) { /* fetch by sequence number */
1141 if ( (IMAP->msgids[i-1]>=lo)
1142 && (IMAP->msgids[i-1]<=hi)) {
1144 IMAP->flags[i-1] | IMAP_SELECTED;
1147 else { /* fetch by uid */
1148 if ( (i>=lo) && (i<=hi)) {
1150 IMAP->flags[i-1] | IMAP_SELECTED;
1161 * This function is called by the main command loop.
1163 void imap_fetch(int num_parms, char *parms[]) {
1165 char *itemlist[SIZ];
1169 if (num_parms < 4) {
1170 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1174 imap_pick_range(parms[2], 0);
1177 for (i=3; i<num_parms; ++i) {
1178 strcat(items, parms[i]);
1179 if (i < (num_parms-1)) strcat(items, " ");
1182 num_items = imap_extract_data_items(itemlist, items);
1183 if (num_items < 1) {
1184 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1188 imap_do_fetch(num_items, itemlist);
1189 cprintf("%s OK FETCH completed\r\n", parms[0]);
1193 * This function is called by the main command loop.
1195 void imap_uidfetch(int num_parms, char *parms[]) {
1197 char *itemlist[SIZ];
1200 int have_uid_item = 0;
1202 if (num_parms < 5) {
1203 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1207 imap_pick_range(parms[3], 1);
1210 for (i=4; i<num_parms; ++i) {
1211 strcat(items, parms[i]);
1212 if (i < (num_parms-1)) strcat(items, " ");
1215 num_items = imap_extract_data_items(itemlist, items);
1216 if (num_items < 1) {
1217 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1221 /* If the "UID" item was not included, we include it implicitly
1222 * because this is a UID FETCH command
1224 for (i=0; i<num_items; ++i) {
1225 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1227 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1229 imap_do_fetch(num_items, itemlist);
1230 cprintf("%s OK UID FETCH completed\r\n", parms[0]);