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 long headers_size, text_size, total_size;
125 long bytes_remaining = 0;
129 /* Cache the most recent RFC822 FETCH because some clients like to
130 * fetch in pieces, and we don't want to have to go back to the
131 * message store for each piece.
133 if ((IMAP->cached_fetch != NULL) && (IMAP->cached_msgnum == msgnum)) {
135 tmp = IMAP->cached_fetch;
137 else if (IMAP->cached_fetch != NULL) {
138 /* Some other message is cached -- free it */
139 fclose(IMAP->cached_fetch);
140 IMAP->cached_fetch == NULL;
141 IMAP->cached_msgnum = (-1);
144 /* At this point, we now can fetch and convert the message iff it's not
145 * the one we had cached.
150 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n",
156 * Load the message into a temp file for translation
159 CtdlRedirectOutput(tmp, -1);
160 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
161 CtdlRedirectOutput(NULL, -1);
163 IMAP->cached_fetch = tmp;
164 IMAP->cached_msgnum = msgnum;
168 * Now figure out where the headers/text break is. IMAP considers the
169 * intervening blank line to be part of the headers, not the text.
174 ptr = fgets(buf, sizeof buf, tmp);
177 if (strlen(buf) == 0) {
178 headers_size = ftell(tmp);
181 } while ( (headers_size == 0L) && (ptr != NULL) );
182 fseek(tmp, 0L, SEEK_END);
183 total_size = ftell(tmp);
184 text_size = total_size - headers_size;
185 /* lprintf(CTDL_DEBUG, "RFC822: headers=%ld, text=%ld, total=%ld\n",
186 headers_size, text_size, total_size); */
188 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
189 cprintf("RFC822.SIZE %ld", total_size);
193 else if (!strcasecmp(whichfmt, "RFC822")) {
194 bytes_remaining = total_size;
198 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
199 bytes_remaining = headers_size;
203 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
204 bytes_remaining = text_size;
205 fseek(tmp, headers_size, SEEK_SET);
208 cprintf("%s {%ld}\r\n", whichfmt, bytes_remaining);
209 blocksize = (long)sizeof(buf);
210 while (bytes_remaining > 0L) {
211 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
212 fread(buf, (size_t)blocksize, 1, tmp);
213 client_write(buf, (int)blocksize);
214 bytes_remaining = bytes_remaining - blocksize;
222 * Load a specific part of a message into the temp file to be output to a
223 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
224 * but we still can't handle "2.HEADER" (which might not be a problem, because
225 * we currently don't have the ability to break out nested RFC822's anyway).
227 * Note: mime_parser() was called with dont_decode set to 1, so we have the
228 * luxury of simply spewing without having to re-encode.
230 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
231 void *content, char *cbtype, size_t length, char *encoding,
234 struct imap_fetch_part *imfp;
237 imfp = (struct imap_fetch_part *)cbuserdata;
239 if (!strcasecmp(partnum, imfp->desired_section)) {
240 fwrite(content, length, 1, imfp->output_fp);
243 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
245 if (!strcasecmp(imfp->desired_section, mbuf2)) {
246 fprintf(imfp->output_fp, "Content-type: %s", cbtype);
247 if (strlen(name) > 0)
248 fprintf(imfp->output_fp, "; name=\"%s\"", name);
249 fprintf(imfp->output_fp, "\r\n");
250 if (strlen(encoding) > 0)
251 fprintf(imfp->output_fp,
252 "Content-Transfer-Encoding: %s\r\n", encoding);
253 if (strlen(encoding) > 0) {
254 fprintf(imfp->output_fp, "Content-Disposition: %s",
256 if (strlen(filename) > 0) {
257 fprintf(imfp->output_fp, "; filename=\"%s\"",
260 fprintf(imfp->output_fp, "\r\n");
262 fprintf(imfp->output_fp, "Content-Length: %ld\r\n", (long)length);
263 fprintf(imfp->output_fp, "\r\n");
271 * Called by imap_fetch_envelope() to output the "From" field.
272 * This is in its own function because its logic is kind of complex. We
273 * really need to make this suck less.
275 void imap_output_envelope_from(struct CtdlMessage *msg) {
276 char user[SIZ], node[SIZ], name[SIZ];
278 /* For anonymous messages, it's so easy! */
279 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
280 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
283 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
284 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
288 /* For everything else, we do stuff. */
289 cprintf("(("); /* open double-parens */
290 imap_strout(msg->cm_fields['A']); /* personal name */
291 cprintf(" NIL "); /* source route (not used) */
294 if (msg->cm_fields['F'] != NULL) {
295 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
296 imap_strout(user); /* mailbox name (user id) */
298 if (!strcasecmp(node, config.c_nodename)) {
299 imap_strout(config.c_fqdn);
302 imap_strout(node); /* host name */
306 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
308 imap_strout(msg->cm_fields['N']); /* host name */
311 cprintf(")) "); /* close double-parens */
317 * Output an envelope address (or set of addresses) in the official,
318 * convuluted, braindead format. (Note that we can't use this for
319 * the "From" address because its data may come from a number of different
320 * fields. But we can use it for "To" and possibly others.
322 void imap_output_envelope_addr(char *addr) {
323 char individual_addr[SIZ];
335 if (strlen(addr) == 0) {
342 /* How many addresses are listed here? */
343 num_addrs = num_tokens(addr, ',');
345 /* Output them one by one. */
346 for (i=0; i<num_addrs; ++i) {
347 extract_token(individual_addr, addr, i, ',');
348 striplt(individual_addr);
349 process_rfc822_addr(individual_addr, user, node, name);
357 if (i < (num_addrs-1)) cprintf(" ");
365 * Implements the ENVELOPE fetch item
367 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
368 * so we don't have to check for that condition like we do elsewhere.
370 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
371 char datestringbuf[SIZ];
373 char *fieldptr = NULL;
375 /* Parse the message date into an IMAP-format date string */
376 if (msg->cm_fields['T'] != NULL) {
377 msgdate = atol(msg->cm_fields['T']);
380 msgdate = time(NULL);
382 datestring(datestringbuf, sizeof datestringbuf,
383 msgdate, DATESTRING_IMAP);
385 /* Now start spewing data fields. The order is important, as it is
386 * defined by the protocol specification. Nonexistent fields must
387 * be output as NIL, existent fields must be quoted or literalled.
388 * The imap_strout() function conveniently does all this for us.
390 cprintf("ENVELOPE (");
393 imap_strout(datestringbuf);
397 imap_strout(msg->cm_fields['U']);
401 imap_output_envelope_from(msg);
403 /* Sender (default to same as 'From' if not present) */
404 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
405 if (fieldptr != NULL) {
406 imap_output_envelope_addr(fieldptr);
410 imap_output_envelope_from(msg);
414 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
415 if (fieldptr != NULL) {
416 imap_output_envelope_addr(fieldptr);
420 imap_output_envelope_from(msg);
424 imap_output_envelope_addr(msg->cm_fields['R']);
427 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
428 imap_output_envelope_addr(fieldptr);
429 if (fieldptr != NULL) free(fieldptr);
432 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
433 imap_output_envelope_addr(fieldptr);
434 if (fieldptr != NULL) free(fieldptr);
437 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
438 imap_strout(fieldptr);
440 if (fieldptr != NULL) free(fieldptr);
443 imap_strout(msg->cm_fields['I']);
450 * Strip any non header information out of a chunk of RFC822 data on disk,
451 * then boil it down to just the fields we want.
453 void imap_strip_headers(FILE *fp, char *section) {
455 char *which_fields = NULL;
456 int doing_headers = 0;
461 char *boiled_headers = NULL;
463 int done_headers = 0;
465 which_fields = strdup(section);
467 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
469 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
472 for (i=0; i<strlen(which_fields); ++i) {
473 if (which_fields[i]=='(')
474 strcpy(which_fields, &which_fields[i+1]);
476 for (i=0; i<strlen(which_fields); ++i) {
477 if (which_fields[i]==')')
480 num_parms = imap_parameterize(parms, which_fields);
482 fseek(fp, 0L, SEEK_END);
483 boiled_headers = malloc((size_t)(ftell(fp) + 256L));
484 strcpy(boiled_headers, "");
488 while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
489 if (!isspace(buf[0])) {
491 if (doing_headers == 0) ok = 1;
493 if (headers_not) ok = 1;
495 for (i=0; i<num_parms; ++i) {
496 if ( (!strncasecmp(buf, parms[i],
497 strlen(parms[i]))) &&
498 (buf[strlen(parms[i])]==':') ) {
499 if (headers_not) ok = 0;
507 strcat(boiled_headers, buf);
510 if (strlen(buf) == 0) done_headers = 1;
511 if (buf[0]=='\r') done_headers = 1;
512 if (buf[0]=='\n') done_headers = 1;
515 strcat(boiled_headers, "\r\n");
517 /* Now write it back */
519 fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
521 ftruncate(fileno(fp), ftell(fp));
525 free(boiled_headers);
530 * Implements the BODY and BODY.PEEK fetch items
532 void imap_fetch_body(long msgnum, char *item, int is_peek) {
533 struct CtdlMessage *msg = NULL;
539 long bytes_remaining = 0;
542 struct imap_fetch_part imfp;
544 /* extract section */
545 safestrncpy(section, item, sizeof section);
546 if (strchr(section, '[') != NULL) {
547 stripallbut(section, '[', ']');
549 /* lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") ); */
551 /* Burn the cache if we don't have the same section of the
552 * same message again.
554 if (IMAP->cached_body != NULL) {
555 if ((IMAP->cached_bodymsgnum != msgnum)
556 || (strcasecmp(IMAP->cached_bodypart, section)) ) {
557 fclose(IMAP->cached_body);
558 IMAP->cached_body = NULL;
559 IMAP->cached_bodymsgnum = (-1);
560 strcpy(IMAP->cached_bodypart, "");
564 /* extract partial */
565 safestrncpy(partial, item, sizeof partial);
566 if (strchr(partial, '<') != NULL) {
567 stripallbut(partial, '<', '>');
570 if (is_partial == 0) strcpy(partial, "");
571 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
573 if (IMAP->cached_body == NULL) {
576 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n", strerror(errno));
579 msg = CtdlFetchMessage(msgnum, 1);
582 /* Now figure out what the client wants, and get it */
584 if (IMAP->cached_body != NULL) {
585 tmp = IMAP->cached_body;
587 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
588 CtdlRedirectOutput(tmp, -1);
589 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
591 CtdlRedirectOutput(NULL, -1);
594 else if (!strcmp(section, "")) {
595 CtdlRedirectOutput(tmp, -1);
596 /* lprintf(CTDL_DEBUG, "calling CtdlOutputPreLoadedMsg()\n");
597 lprintf(CTDL_DEBUG, "msg %s null\n", ((msg == NULL) ? "is" : "is not") );
598 lprintf(CTDL_DEBUG, "msgnum is %ld\n", msgnum); */
599 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
601 CtdlRedirectOutput(NULL, -1);
605 * If the client asked for just headers, or just particular header
606 * fields, strip it down.
608 else if (!strncasecmp(section, "HEADER", 6)) {
609 CtdlRedirectOutput(tmp, -1);
610 /* lprintf(CTDL_DEBUG, "calling CtdlOutputPreLoadedMsg()\n");
611 lprintf(CTDL_DEBUG, "msg %s null\n", ((msg == NULL) ? "is" : "is not") );
612 lprintf(CTDL_DEBUG, "msgnum is %ld\n", msgnum); */
613 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
615 CtdlRedirectOutput(NULL, -1);
616 imap_strip_headers(tmp, section);
620 * Strip it down if the client asked for everything _except_ headers.
622 else if (!strncasecmp(section, "TEXT", 4)) {
623 CtdlRedirectOutput(tmp, -1);
624 /* lprintf(CTDL_DEBUG, "calling CtdlOutputPreLoadedMsg()\n");
625 lprintf(CTDL_DEBUG, "msg %s null\n", ((msg == NULL) ? "is" : "is not") );
626 lprintf(CTDL_DEBUG, "msgnum is %ld\n", msgnum); */
627 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
629 CtdlRedirectOutput(NULL, -1);
633 * Anything else must be a part specifier.
634 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
637 safestrncpy(imfp.desired_section, section,
638 sizeof(imfp.desired_section));
639 imfp.output_fp = tmp;
641 mime_parser(msg->cm_fields['M'], NULL,
642 *imap_load_part, NULL, NULL,
648 fseek(tmp, 0L, SEEK_END);
649 bytes_remaining = ftell(tmp);
651 if (is_partial == 0) {
653 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
656 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
657 if ((bytes_remaining - pstart) < pbytes) {
658 pbytes = bytes_remaining - pstart;
660 fseek(tmp, pstart, SEEK_SET);
661 bytes_remaining = pbytes;
662 cprintf("BODY[%s]<%ld> {%ld}\r\n",
663 section, pstart, bytes_remaining);
666 blocksize = (long)sizeof(buf);
667 while (bytes_remaining > 0L) {
668 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
669 fread(buf, blocksize, 1, tmp);
670 client_write(buf, (int)blocksize);
671 bytes_remaining = bytes_remaining - blocksize;
674 /* Don't close it ... cache it! */
676 IMAP->cached_body = tmp;
677 IMAP->cached_bodymsgnum = msgnum;
678 strcpy(IMAP->cached_bodypart, section);
681 CtdlFreeMessage(msg);
684 /* Mark this message as "seen" *unless* this is a "peek" operation */
686 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
691 * Called immediately before outputting a multipart bodystructure
693 void imap_fetch_bodystructure_pre(
694 char *name, char *filename, char *partnum, char *disp,
695 void *content, char *cbtype, size_t length, char *encoding,
705 * Called immediately after outputting a multipart bodystructure
707 void imap_fetch_bodystructure_post(
708 char *name, char *filename, char *partnum, char *disp,
709 void *content, char *cbtype, size_t length, char *encoding,
718 extract_token(subtype, cbtype, 1, '/');
719 imap_strout(subtype);
730 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
733 void imap_fetch_bodystructure_part(
734 char *name, char *filename, char *partnum, char *disp,
735 void *content, char *cbtype, size_t length, char *encoding,
740 int have_encoding = 0;
743 char cbmaintype[SIZ];
746 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
748 extract_token(cbmaintype, cbtype, 0, '/');
749 extract_token(cbsubtype, cbtype, 1, '/');
752 strcpy(cbmaintype, "TEXT");
753 strcpy(cbsubtype, "PLAIN");
757 imap_strout(cbmaintype);
759 imap_strout(cbsubtype);
762 cprintf("(\"CHARSET\" \"US-ASCII\"");
764 if (name != NULL) if (strlen(name)>0) {
765 cprintf(" \"NAME\" ");
771 cprintf("NIL "); /* Body ID */
772 cprintf("NIL "); /* Body description */
774 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
776 imap_strout(encoding);
783 /* The next field is the size of the part in bytes. */
784 cprintf("%ld ", (long)length); /* bytes */
786 /* The next field is the number of lines in the part, if and only
787 * if the part is TEXT. More gratuitous complexity.
789 if (!strcasecmp(cbmaintype, "TEXT")) {
790 if (length) for (i=0; i<length; ++i) {
791 if (((char *)content)[i] == '\n') ++lines;
793 cprintf("%d ", lines);
796 /* More gratuitous complexity */
797 if ((!strcasecmp(cbmaintype, "MESSAGE"))
798 && (!strcasecmp(cbsubtype, "RFC822"))) {
800 A body type of type MESSAGE and subtype RFC822
801 contains, immediately after the basic fields, the
802 envelope structure, body structure, and size in
803 text lines of the encapsulated message.
807 /* MD5 value of body part; we can get away with NIL'ing this */
814 else if (strlen(disp) == 0) {
820 if (filename != NULL) if (strlen(filename)>0) {
821 cprintf(" (\"FILENAME\" ");
822 imap_strout(filename);
828 /* Body language (not defined yet) */
835 * Spew the BODYSTRUCTURE data for a message.
838 void imap_fetch_bodystructure (long msgnum, char *item,
839 struct CtdlMessage *msg) {
843 long start_of_body = 0L;
844 long body_bytes = 0L;
846 /* For non-RFC822 (ordinary Citadel) messages, this is short and
849 if (msg->cm_format_type != FMT_RFC822) {
851 /* *sigh* We have to RFC822-format the message just to be able
855 if (tmp == NULL) return;
856 CtdlRedirectOutput(tmp, -1);
857 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
858 CtdlRedirectOutput(NULL, -1);
861 while (fgets(buf, sizeof buf, tmp) != NULL) {
863 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
864 start_of_body = ftell(tmp);
867 body_bytes = ftell(tmp) - start_of_body;
870 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
871 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
872 "\"7BIT\" %ld %ld)", body_bytes, lines);
877 /* For messages already stored in RFC822 format, we have to parse. */
878 cprintf("BODYSTRUCTURE ");
879 mime_parser(msg->cm_fields['M'],
881 *imap_fetch_bodystructure_part, /* part */
882 *imap_fetch_bodystructure_pre, /* pre-multi */
883 *imap_fetch_bodystructure_post, /* post-multi */
885 1); /* don't decode -- we want it as-is */
890 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
891 * individual message, once it has been selected for output.
893 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
895 struct CtdlMessage *msg = NULL;
897 cprintf("* %d FETCH (", seq);
899 for (i=0; i<num_items; ++i) {
901 /* Fetchable without going to the message store at all */
902 if (!strcasecmp(itemlist[i], "UID")) {
905 else if (!strcasecmp(itemlist[i], "FLAGS")) {
906 imap_fetch_flags(seq-1);
909 /* Potentially fetchable from cache, if the client requests
910 * stuff from the same message several times in a row.
912 else if (!strcasecmp(itemlist[i], "RFC822")) {
913 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
915 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
916 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
918 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
919 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
921 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
922 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
925 /* BODY fetches do their own fetching and caching too. */
926 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
927 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
929 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
930 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
933 /* Otherwise, load the message into memory.
935 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
936 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
937 imap_fetch_bodystructure(IMAP->msgids[seq-1],
940 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
941 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
942 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
944 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
945 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
946 imap_fetch_internaldate(msg);
949 if (i != num_items-1) cprintf(" ");
954 CtdlFreeMessage(msg);
961 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
962 * validated and boiled down the request a bit.
964 void imap_do_fetch(int num_items, char **itemlist) {
967 if (IMAP->num_msgs > 0) {
968 for (i = 0; i < IMAP->num_msgs; ++i) {
969 if (IMAP->flags[i] & IMAP_SELECTED) {
970 imap_do_fetch_msg(i+1, num_items, itemlist);
979 * Back end for imap_handle_macros()
980 * Note that this function *only* looks at the beginning of the string. It
981 * is not a generic search-and-replace function.
983 void imap_macro_replace(char *str, char *find, char *replace) {
986 if (!strncasecmp(str, find, strlen(find))) {
987 if (str[strlen(find)]==' ') {
988 strcpy(holdbuf, &str[strlen(find)+1]);
989 strcpy(str, replace);
991 strcat(str, holdbuf);
993 if (str[strlen(find)]==0) {
994 strcpy(holdbuf, &str[strlen(find)+1]);
995 strcpy(str, replace);
1003 * Handle macros embedded in FETCH data items.
1004 * (What the heck are macros doing in a wire protocol? Are we trying to save
1005 * the computer at the other end the trouble of typing a lot of characters?)
1007 void imap_handle_macros(char *str) {
1011 for (i=0; i<strlen(str); ++i) {
1012 if (str[i]=='(') ++nest;
1013 if (str[i]=='[') ++nest;
1014 if (str[i]=='<') ++nest;
1015 if (str[i]=='{') ++nest;
1016 if (str[i]==')') --nest;
1017 if (str[i]==']') --nest;
1018 if (str[i]=='>') --nest;
1019 if (str[i]=='}') --nest;
1022 imap_macro_replace(&str[i],
1024 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1026 imap_macro_replace(&str[i],
1030 imap_macro_replace(&str[i],
1032 "FLAGS INTERNALDATE RFC822.SIZE"
1034 imap_macro_replace(&str[i],
1036 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1044 * Break out the data items requested, possibly a parenthesized list.
1045 * Returns the number of data items, or -1 if the list is invalid.
1046 * NOTE: this function alters the string it is fed, and uses it as a buffer
1047 * to hold the data for the pointers it returns.
1049 int imap_extract_data_items(char **argv, char *items) {
1055 /* Convert all whitespace to ordinary space characters. */
1056 for (i=0; i<strlen(items); ++i) {
1057 if (isspace(items[i])) items[i]=' ';
1060 /* Strip leading and trailing whitespace, then strip leading and
1061 * trailing parentheses if it's a list
1064 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1065 items[strlen(items)-1] = 0;
1066 strcpy(items, &items[1]);
1070 /* Parse any macro data items */
1071 imap_handle_macros(items);
1074 * Now break out the data items. We throw in one trailing space in
1075 * order to avoid having to break out the last one manually.
1079 initial_len = strlen(items);
1080 for (i=0; i<initial_len; ++i) {
1081 if (items[i]=='(') ++nest;
1082 if (items[i]=='[') ++nest;
1083 if (items[i]=='<') ++nest;
1084 if (items[i]=='{') ++nest;
1085 if (items[i]==')') --nest;
1086 if (items[i]==']') --nest;
1087 if (items[i]=='>') --nest;
1088 if (items[i]=='}') --nest;
1090 if (nest <= 0) if (items[i]==' ') {
1092 argv[num_items++] = start;
1093 start = &items[i+1];
1103 * One particularly hideous aspect of IMAP is that we have to allow the client
1104 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1105 * handles this by setting the IMAP_SELECTED flag for each message specified in
1106 * the ranges/sets, then looping through the message array, outputting messages
1107 * with the flag set. We don't bother returning an error if an out-of-range
1108 * number is specified (we just return quietly) because any client braindead
1109 * enough to request a bogus message number isn't going to notice the
1110 * difference anyway.
1112 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1113 * message included in the specified range.
1115 * Set is_uid to 1 to fetch by UID instead of sequence number.
1117 void imap_pick_range(char *supplied_range, int is_uid) {
1121 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1123 char actual_range[SIZ];
1126 * Handle the "ALL" macro
1128 if (!strcasecmp(supplied_range, "ALL")) {
1129 safestrncpy(actual_range, "1:*", sizeof actual_range);
1132 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1136 * Clear out the IMAP_SELECTED flags for all messages.
1138 for (i = 0; i < IMAP->num_msgs; ++i) {
1139 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1143 * Now set it for all specified messages.
1145 num_sets = num_tokens(actual_range, ',');
1146 for (s=0; s<num_sets; ++s) {
1147 extract_token(setstr, actual_range, s, ',');
1149 extract_token(lostr, setstr, 0, ':');
1150 if (num_tokens(setstr, ':') >= 2) {
1151 extract_token(histr, setstr, 1, ':');
1152 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1155 strcpy(histr, lostr);
1160 /* Loop through the array, flipping bits where appropriate */
1161 for (i = 1; i <= IMAP->num_msgs; ++i) {
1162 if (is_uid) { /* fetch by sequence number */
1163 if ( (IMAP->msgids[i-1]>=lo)
1164 && (IMAP->msgids[i-1]<=hi)) {
1166 IMAP->flags[i-1] | IMAP_SELECTED;
1169 else { /* fetch by uid */
1170 if ( (i>=lo) && (i<=hi)) {
1172 IMAP->flags[i-1] | IMAP_SELECTED;
1183 * This function is called by the main command loop.
1185 void imap_fetch(int num_parms, char *parms[]) {
1187 char *itemlist[SIZ];
1191 if (num_parms < 4) {
1192 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1196 imap_pick_range(parms[2], 0);
1199 for (i=3; i<num_parms; ++i) {
1200 strcat(items, parms[i]);
1201 if (i < (num_parms-1)) strcat(items, " ");
1204 num_items = imap_extract_data_items(itemlist, items);
1205 if (num_items < 1) {
1206 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1210 imap_do_fetch(num_items, itemlist);
1211 cprintf("%s OK FETCH completed\r\n", parms[0]);
1215 * This function is called by the main command loop.
1217 void imap_uidfetch(int num_parms, char *parms[]) {
1219 char *itemlist[SIZ];
1222 int have_uid_item = 0;
1224 if (num_parms < 5) {
1225 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1229 imap_pick_range(parms[3], 1);
1232 for (i=4; i<num_parms; ++i) {
1233 strcat(items, parms[i]);
1234 if (i < (num_parms-1)) strcat(items, " ");
1237 num_items = imap_extract_data_items(itemlist, items);
1238 if (num_items < 1) {
1239 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1243 /* If the "UID" item was not included, we include it implicitly
1244 * because this is a UID FETCH command
1246 for (i=0; i<num_items; ++i) {
1247 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1249 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1251 imap_do_fetch(num_items, itemlist);
1252 cprintf("%s OK UID FETCH completed\r\n", parms[0]);