4 * Implements the FETCH command in IMAP.
5 * This command is way too convoluted. Marc Crispin is a fscking idiot.
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
160 CtdlRedirectOutput(tmp, -1);
162 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
164 CtdlRedirectOutput(NULL, -1);
167 IMAP->cached_fetch = tmp;
168 IMAP->cached_msgnum = msgnum;
172 * Now figure out where the headers/text break is. IMAP considers the
173 * intervening blank line to be part of the headers, not the text.
178 ptr = fgets(buf, sizeof buf, tmp);
181 if (strlen(buf) == 0) {
182 headers_size = ftell(tmp);
185 } while ( (headers_size == 0L) && (ptr != NULL) );
186 fseek(tmp, 0L, SEEK_END);
187 total_size = ftell(tmp);
188 text_size = total_size - headers_size;
189 lprintf(CTDL_DEBUG, "RFC822: headers=%ld, text=%ld, total=%ld\n",
190 headers_size, text_size, total_size);
192 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
193 cprintf("RFC822.SIZE %ld", total_size);
197 else if (!strcasecmp(whichfmt, "RFC822")) {
198 bytes_remaining = total_size;
202 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
203 bytes_remaining = headers_size;
207 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
208 bytes_remaining = text_size;
209 fseek(tmp, headers_size, SEEK_SET);
212 cprintf("%s {%ld}\r\n", whichfmt, bytes_remaining);
213 blocksize = (long)sizeof(buf);
214 while (bytes_remaining > 0L) {
215 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
216 fread(buf, (size_t)blocksize, 1, tmp);
217 client_write(buf, (int)blocksize);
218 bytes_remaining = bytes_remaining - blocksize;
226 * Load a specific part of a message into the temp file to be output to a
227 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
228 * but we still can't handle "2.HEADER" (which might not be a problem, because
229 * we currently don't have the ability to break out nested RFC822's anyway).
231 * Note: mime_parser() was called with dont_decode set to 1, so we have the
232 * luxury of simply spewing without having to re-encode.
234 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
235 void *content, char *cbtype, size_t length, char *encoding,
238 struct imap_fetch_part *imfp;
241 imfp = (struct imap_fetch_part *)cbuserdata;
243 if (!strcasecmp(partnum, imfp->desired_section)) {
244 fwrite(content, length, 1, imfp->output_fp);
247 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
249 if (!strcasecmp(imfp->desired_section, mbuf2)) {
250 fprintf(imfp->output_fp, "Content-type: %s", cbtype);
251 if (strlen(name) > 0)
252 fprintf(imfp->output_fp, "; name=\"%s\"", name);
253 fprintf(imfp->output_fp, "\r\n");
254 if (strlen(encoding) > 0)
255 fprintf(imfp->output_fp,
256 "Content-Transfer-Encoding: %s\r\n", encoding);
257 if (strlen(encoding) > 0) {
258 fprintf(imfp->output_fp, "Content-Disposition: %s",
260 if (strlen(filename) > 0) {
261 fprintf(imfp->output_fp, "; filename=\"%s\"",
264 fprintf(imfp->output_fp, "\r\n");
266 fprintf(imfp->output_fp, "Content-Length: %ld\r\n", (long)length);
267 fprintf(imfp->output_fp, "\r\n");
275 * Called by imap_fetch_envelope() to output the "From" field.
276 * This is in its own function because its logic is kind of complex. We
277 * really need to make this suck less.
279 void imap_output_envelope_from(struct CtdlMessage *msg) {
280 char user[SIZ], node[SIZ], name[SIZ];
282 /* For anonymous messages, it's so easy! */
283 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
284 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
287 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
288 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
292 /* For everything else, we do stuff. */
293 cprintf("(("); /* open double-parens */
294 imap_strout(msg->cm_fields['A']); /* personal name */
295 cprintf(" NIL "); /* source route (not used) */
298 if (msg->cm_fields['F'] != NULL) {
299 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
300 imap_strout(user); /* mailbox name (user id) */
302 if (!strcasecmp(node, config.c_nodename)) {
303 imap_strout(config.c_fqdn);
306 imap_strout(node); /* host name */
310 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
312 imap_strout(msg->cm_fields['N']); /* host name */
315 cprintf(")) "); /* close double-parens */
321 * Output an envelope address (or set of addresses) in the official,
322 * Crispin-approved braindead format. (Note that we can't use this for
323 * the "From" address because its data may come from a number of different
324 * fields. But we can use it for "To" and possibly others.
326 void imap_output_envelope_addr(char *addr) {
327 char individual_addr[SIZ];
339 if (strlen(addr) == 0) {
346 /* How many addresses are listed here? */
347 num_addrs = num_tokens(addr, ',');
349 /* Output them one by one. */
350 for (i=0; i<num_addrs; ++i) {
351 extract_token(individual_addr, addr, i, ',');
352 striplt(individual_addr);
353 process_rfc822_addr(individual_addr, user, node, name);
361 if (i < (num_addrs-1)) cprintf(" ");
369 * Implements the ENVELOPE fetch item
371 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
372 * so we don't have to check for that condition like we do elsewhere.
374 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
375 char datestringbuf[SIZ];
377 char *fieldptr = NULL;
379 /* Parse the message date into an IMAP-format date string */
380 if (msg->cm_fields['T'] != NULL) {
381 msgdate = atol(msg->cm_fields['T']);
384 msgdate = time(NULL);
386 datestring(datestringbuf, sizeof datestringbuf,
387 msgdate, DATESTRING_IMAP);
389 /* Now start spewing data fields. The order is important, as it is
390 * defined by the protocol specification. Nonexistent fields must
391 * be output as NIL, existent fields must be quoted or literalled.
392 * The imap_strout() function conveniently does all this for us.
394 cprintf("ENVELOPE (");
397 imap_strout(datestringbuf);
401 imap_strout(msg->cm_fields['U']);
405 imap_output_envelope_from(msg);
407 /* Sender (default to same as 'From' if not present) */
408 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
409 if (fieldptr != NULL) {
410 imap_output_envelope_addr(fieldptr);
414 imap_output_envelope_from(msg);
418 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
419 if (fieldptr != NULL) {
420 imap_output_envelope_addr(fieldptr);
424 imap_output_envelope_from(msg);
428 imap_output_envelope_addr(msg->cm_fields['R']);
431 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
432 imap_output_envelope_addr(fieldptr);
433 if (fieldptr != NULL) free(fieldptr);
436 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
437 imap_output_envelope_addr(fieldptr);
438 if (fieldptr != NULL) free(fieldptr);
441 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
442 imap_strout(fieldptr);
444 if (fieldptr != NULL) free(fieldptr);
447 imap_strout(msg->cm_fields['I']);
454 * Strip any non header information out of a chunk of RFC822 data on disk,
455 * then boil it down to just the fields we want.
457 void imap_strip_headers(FILE *fp, char *section) {
459 char *which_fields = NULL;
460 int doing_headers = 0;
465 char *boiled_headers = NULL;
467 int done_headers = 0;
469 which_fields = strdup(section);
471 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
473 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
476 for (i=0; i<strlen(which_fields); ++i) {
477 if (which_fields[i]=='(')
478 strcpy(which_fields, &which_fields[i+1]);
480 for (i=0; i<strlen(which_fields); ++i) {
481 if (which_fields[i]==')')
484 num_parms = imap_parameterize(parms, which_fields);
486 fseek(fp, 0L, SEEK_END);
487 boiled_headers = malloc((size_t)(ftell(fp) + 256L));
488 strcpy(boiled_headers, "");
492 while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
493 if (!isspace(buf[0])) {
495 if (doing_headers == 0) ok = 1;
497 if (headers_not) ok = 1;
499 for (i=0; i<num_parms; ++i) {
500 if ( (!strncasecmp(buf, parms[i],
501 strlen(parms[i]))) &&
502 (buf[strlen(parms[i])]==':') ) {
503 if (headers_not) ok = 0;
511 strcat(boiled_headers, buf);
514 if (strlen(buf) == 0) done_headers = 1;
515 if (buf[0]=='\r') done_headers = 1;
516 if (buf[0]=='\n') done_headers = 1;
519 strcat(boiled_headers, "\r\n");
521 /* Now write it back */
523 fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
525 ftruncate(fileno(fp), ftell(fp));
529 free(boiled_headers);
534 * Implements the BODY and BODY.PEEK fetch items
536 void imap_fetch_body(long msgnum, char *item, int is_peek) {
537 struct CtdlMessage *msg = NULL;
543 long bytes_remaining = 0;
546 struct imap_fetch_part imfp;
548 /* extract section */
549 safestrncpy(section, item, sizeof section);
550 if (strchr(section, '[') != NULL) {
551 stripallbut(section, '[', ']');
553 lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") );
555 /* Burn the cache if we don't have the same section of the
556 * same message again.
558 if (IMAP->cached_body != NULL) {
559 if ((IMAP->cached_bodymsgnum != msgnum)
560 || (strcasecmp(IMAP->cached_bodypart, section)) ) {
561 fclose(IMAP->cached_body);
562 IMAP->cached_body = NULL;
563 IMAP->cached_bodymsgnum = (-1);
564 strcpy(IMAP->cached_bodypart, "");
568 /* extract partial */
569 safestrncpy(partial, item, sizeof partial);
570 if (strchr(partial, '<') != NULL) {
571 stripallbut(partial, '<', '>');
574 if (is_partial == 0) strcpy(partial, "");
575 if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial);
577 if (IMAP->cached_body == NULL) {
580 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n", strerror(errno));
583 msg = CtdlFetchMessage(msgnum, 1);
587 /* Now figure out what the client wants, and get it */
590 if (IMAP->cached_body != NULL) {
591 tmp = IMAP->cached_body;
594 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
596 CtdlRedirectOutput(tmp, -1);
597 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
599 CtdlRedirectOutput(NULL, -1);
603 else if (!strcmp(section, "")) {
605 CtdlRedirectOutput(tmp, -1);
607 lprintf(CTDL_DEBUG, "calling CtdlOutputPreLoadedMsg()\n");
608 lprintf(CTDL_DEBUG, "msg %s null\n", ((msg == NULL) ? "is" : "is not") );
609 lprintf(CTDL_DEBUG, "msgnum is %ld\n", msgnum);
610 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
613 CtdlRedirectOutput(NULL, -1);
618 * If the client asked for just headers, or just particular header
619 * fields, strip it down.
621 else if (!strncasecmp(section, "HEADER", 6)) {
623 CtdlRedirectOutput(tmp, -1);
625 lprintf(CTDL_DEBUG, "calling CtdlOutputPreLoadedMsg()\n");
626 lprintf(CTDL_DEBUG, "msg %s null\n", ((msg == NULL) ? "is" : "is not") );
627 lprintf(CTDL_DEBUG, "msgnum is %ld\n", msgnum);
628 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
631 CtdlRedirectOutput(NULL, -1);
632 imap_strip_headers(tmp, section);
636 * Strip it down if the client asked for everything _except_ headers.
638 else if (!strncasecmp(section, "TEXT", 4)) {
639 CtdlRedirectOutput(tmp, -1);
640 lprintf(CTDL_DEBUG, "calling CtdlOutputPreLoadedMsg()\n");
641 lprintf(CTDL_DEBUG, "msg %s null\n", ((msg == NULL) ? "is" : "is not") );
642 lprintf(CTDL_DEBUG, "msgnum is %ld\n", msgnum);
643 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
646 CtdlRedirectOutput(NULL, -1);
651 * Anything else must be a part specifier.
652 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
656 safestrncpy(imfp.desired_section, section,
657 sizeof(imfp.desired_section));
659 imfp.output_fp = tmp;
662 mime_parser(msg->cm_fields['M'], NULL,
663 *imap_load_part, NULL, NULL,
671 fseek(tmp, 0L, SEEK_END);
673 bytes_remaining = ftell(tmp);
676 if (is_partial == 0) {
679 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
684 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
685 if ((bytes_remaining - pstart) < pbytes) {
686 pbytes = bytes_remaining - pstart;
689 fseek(tmp, pstart, SEEK_SET);
691 bytes_remaining = pbytes;
693 cprintf("BODY[%s]<%ld> {%ld}\r\n",
694 section, pstart, bytes_remaining);
698 blocksize = (long)sizeof(buf);
700 while (bytes_remaining > 0L) {
702 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
704 fread(buf, blocksize, 1, tmp);
706 client_write(buf, (int)blocksize);
708 bytes_remaining = bytes_remaining - blocksize;
713 /* Don't close it ... cache it! */
716 IMAP->cached_body = tmp;
717 IMAP->cached_bodymsgnum = msgnum;
718 strcpy(IMAP->cached_bodypart, section);
723 CtdlFreeMessage(msg);
726 /* Mark this message as "seen" *unless* this is a "peek" operation */
729 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
735 * Called immediately before outputting a multipart bodystructure
737 void imap_fetch_bodystructure_pre(
738 char *name, char *filename, char *partnum, char *disp,
739 void *content, char *cbtype, size_t length, char *encoding,
749 * Called immediately after outputting a multipart bodystructure
751 void imap_fetch_bodystructure_post(
752 char *name, char *filename, char *partnum, char *disp,
753 void *content, char *cbtype, size_t length, char *encoding,
762 extract_token(subtype, cbtype, 1, '/');
763 imap_strout(subtype);
774 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
777 void imap_fetch_bodystructure_part(
778 char *name, char *filename, char *partnum, char *disp,
779 void *content, char *cbtype, size_t length, char *encoding,
784 int have_encoding = 0;
787 char cbmaintype[SIZ];
790 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
792 extract_token(cbmaintype, cbtype, 0, '/');
793 extract_token(cbsubtype, cbtype, 1, '/');
796 strcpy(cbmaintype, "TEXT");
797 strcpy(cbsubtype, "PLAIN");
801 imap_strout(cbmaintype);
803 imap_strout(cbsubtype);
806 cprintf("(\"CHARSET\" \"US-ASCII\"");
808 if (name != NULL) if (strlen(name)>0) {
809 cprintf(" \"NAME\" ");
815 cprintf("NIL "); /* Body ID */
816 cprintf("NIL "); /* Body description */
818 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
820 imap_strout(encoding);
827 /* The next field is the size of the part in bytes. */
828 cprintf("%ld ", (long)length); /* bytes */
830 /* The next field is the number of lines in the part, if and only
831 * if the part is TEXT. Crispin is a fscking idiot.
833 if (!strcasecmp(cbmaintype, "TEXT")) {
834 if (length) for (i=0; i<length; ++i) {
835 if (((char *)content)[i] == '\n') ++lines;
837 cprintf("%d ", lines);
840 /* More of Crispin being a fscking idiot */
841 if ((!strcasecmp(cbmaintype, "MESSAGE"))
842 && (!strcasecmp(cbsubtype, "RFC822"))) {
844 A body type of type MESSAGE and subtype RFC822
845 contains, immediately after the basic fields, the
846 envelope structure, body structure, and size in
847 text lines of the encapsulated message.
851 /* MD5 value of body part; we can get away with NIL'ing this */
858 else if (strlen(disp) == 0) {
864 if (filename != NULL) if (strlen(filename)>0) {
865 cprintf(" (\"FILENAME\" ");
866 imap_strout(filename);
872 /* Body language (not defined yet) */
879 * Spew the BODYSTRUCTURE data for a message. (Do you need a silencer if
880 * you're going to shoot a MIME? Do you need a reason to shoot Mark Crispin?
884 void imap_fetch_bodystructure (long msgnum, char *item,
885 struct CtdlMessage *msg) {
889 long start_of_body = 0L;
890 long body_bytes = 0L;
892 /* For non-RFC822 (ordinary Citadel) messages, this is short and
895 if (msg->cm_format_type != FMT_RFC822) {
897 /* *sigh* We have to RFC822-format the message just to be able
901 if (tmp == NULL) return;
902 CtdlRedirectOutput(tmp, -1);
903 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
904 CtdlRedirectOutput(NULL, -1);
907 while (fgets(buf, sizeof buf, tmp) != NULL) {
909 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
910 start_of_body = ftell(tmp);
913 body_bytes = ftell(tmp) - start_of_body;
916 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
917 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
918 "\"7BIT\" %ld %ld)", body_bytes, lines);
923 /* For messages already stored in RFC822 format, we have to parse. */
924 cprintf("BODYSTRUCTURE ");
925 mime_parser(msg->cm_fields['M'],
927 *imap_fetch_bodystructure_part, /* part */
928 *imap_fetch_bodystructure_pre, /* pre-multi */
929 *imap_fetch_bodystructure_post, /* post-multi */
931 1); /* don't decode -- we want it as-is */
936 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
937 * individual message, once it has been selected for output.
939 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
941 struct CtdlMessage *msg = NULL;
943 lprintf(CTDL_DEBUG, "imap_do_fetch_msg(%d, %d)\n", seq, num_items);
944 for (i=0; i<num_items; ++i) {
945 lprintf(CTDL_DEBUG, " %s\n", itemlist[i]);
948 cprintf("* %d FETCH (", seq);
950 for (i=0; i<num_items; ++i) {
952 /* Fetchable without going to the message store at all */
953 if (!strcasecmp(itemlist[i], "UID")) {
956 else if (!strcasecmp(itemlist[i], "FLAGS")) {
957 imap_fetch_flags(seq-1);
960 /* Potentially fetchable from cache, if the client requests
961 * stuff from the same message several times in a row.
963 else if (!strcasecmp(itemlist[i], "RFC822")) {
964 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
966 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
967 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
969 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
970 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
972 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
973 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
976 /* BODY fetches do their own fetching and caching too. */
977 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
978 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
980 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
981 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
984 /* Otherwise, load the message into memory.
986 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
987 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
988 imap_fetch_bodystructure(IMAP->msgids[seq-1],
991 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
992 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
993 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
995 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
996 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
997 imap_fetch_internaldate(msg);
1000 if (i != num_items-1) cprintf(" ");
1005 CtdlFreeMessage(msg);
1012 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1013 * validated and boiled down the request a bit.
1015 void imap_do_fetch(int num_items, char **itemlist) {
1018 if (IMAP->num_msgs > 0) {
1019 for (i = 0; i < IMAP->num_msgs; ++i) {
1020 if (IMAP->flags[i] & IMAP_SELECTED) {
1021 imap_do_fetch_msg(i+1, num_items, itemlist);
1030 * Back end for imap_handle_macros()
1031 * Note that this function *only* looks at the beginning of the string. It
1032 * is not a generic search-and-replace function.
1034 void imap_macro_replace(char *str, char *find, char *replace) {
1037 if (!strncasecmp(str, find, strlen(find))) {
1038 if (str[strlen(find)]==' ') {
1039 strcpy(holdbuf, &str[strlen(find)+1]);
1040 strcpy(str, replace);
1042 strcat(str, holdbuf);
1044 if (str[strlen(find)]==0) {
1045 strcpy(holdbuf, &str[strlen(find)+1]);
1046 strcpy(str, replace);
1054 * Handle macros embedded in FETCH data items.
1055 * (What the heck are macros doing in a wire protocol? Are we trying to save
1056 * the computer at the other end the trouble of typing a lot of characters?)
1058 void imap_handle_macros(char *str) {
1062 for (i=0; i<strlen(str); ++i) {
1063 if (str[i]=='(') ++nest;
1064 if (str[i]=='[') ++nest;
1065 if (str[i]=='<') ++nest;
1066 if (str[i]=='{') ++nest;
1067 if (str[i]==')') --nest;
1068 if (str[i]==']') --nest;
1069 if (str[i]=='>') --nest;
1070 if (str[i]=='}') --nest;
1073 imap_macro_replace(&str[i],
1075 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1077 imap_macro_replace(&str[i],
1081 imap_macro_replace(&str[i],
1083 "FLAGS INTERNALDATE RFC822.SIZE"
1085 imap_macro_replace(&str[i],
1087 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1095 * Break out the data items requested, possibly a parenthesized list.
1096 * Returns the number of data items, or -1 if the list is invalid.
1097 * NOTE: this function alters the string it is fed, and uses it as a buffer
1098 * to hold the data for the pointers it returns.
1100 int imap_extract_data_items(char **argv, char *items) {
1106 /* Convert all whitespace to ordinary space characters. */
1107 for (i=0; i<strlen(items); ++i) {
1108 if (isspace(items[i])) items[i]=' ';
1111 /* Strip leading and trailing whitespace, then strip leading and
1112 * trailing parentheses if it's a list
1115 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1116 items[strlen(items)-1] = 0;
1117 strcpy(items, &items[1]);
1121 /* Parse any macro data items */
1122 imap_handle_macros(items);
1125 * Now break out the data items. We throw in one trailing space in
1126 * order to avoid having to break out the last one manually.
1130 initial_len = strlen(items);
1131 for (i=0; i<initial_len; ++i) {
1132 if (items[i]=='(') ++nest;
1133 if (items[i]=='[') ++nest;
1134 if (items[i]=='<') ++nest;
1135 if (items[i]=='{') ++nest;
1136 if (items[i]==')') --nest;
1137 if (items[i]==']') --nest;
1138 if (items[i]=='>') --nest;
1139 if (items[i]=='}') --nest;
1141 if (nest <= 0) if (items[i]==' ') {
1143 argv[num_items++] = start;
1144 start = &items[i+1];
1154 * One particularly hideous aspect of IMAP is that we have to allow the client
1155 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1156 * handles this by setting the IMAP_SELECTED flag for each message specified in
1157 * the ranges/sets, then looping through the message array, outputting messages
1158 * with the flag set. We don't bother returning an error if an out-of-range
1159 * number is specified (we just return quietly) because any client braindead
1160 * enough to request a bogus message number isn't going to notice the
1161 * difference anyway.
1163 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1164 * message included in the specified range.
1166 * Set is_uid to 1 to fetch by UID instead of sequence number.
1168 void imap_pick_range(char *supplied_range, int is_uid) {
1172 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1174 char actual_range[SIZ];
1177 * Handle the "ALL" macro
1179 if (!strcasecmp(supplied_range, "ALL")) {
1180 safestrncpy(actual_range, "1:*", sizeof actual_range);
1183 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1187 * Clear out the IMAP_SELECTED flags for all messages.
1189 for (i = 0; i < IMAP->num_msgs; ++i) {
1190 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1194 * Now set it for all specified messages.
1196 num_sets = num_tokens(actual_range, ',');
1197 for (s=0; s<num_sets; ++s) {
1198 extract_token(setstr, actual_range, s, ',');
1200 extract_token(lostr, setstr, 0, ':');
1201 if (num_tokens(setstr, ':') >= 2) {
1202 extract_token(histr, setstr, 1, ':');
1203 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1206 strcpy(histr, lostr);
1211 /* Loop through the array, flipping bits where appropriate */
1212 for (i = 1; i <= IMAP->num_msgs; ++i) {
1213 if (is_uid) { /* fetch by sequence number */
1214 if ( (IMAP->msgids[i-1]>=lo)
1215 && (IMAP->msgids[i-1]<=hi)) {
1217 IMAP->flags[i-1] | IMAP_SELECTED;
1220 else { /* fetch by uid */
1221 if ( (i>=lo) && (i<=hi)) {
1223 IMAP->flags[i-1] | IMAP_SELECTED;
1234 * This function is called by the main command loop.
1236 void imap_fetch(int num_parms, char *parms[]) {
1238 char *itemlist[SIZ];
1242 if (num_parms < 4) {
1243 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1247 imap_pick_range(parms[2], 0);
1250 for (i=3; i<num_parms; ++i) {
1251 strcat(items, parms[i]);
1252 if (i < (num_parms-1)) strcat(items, " ");
1255 num_items = imap_extract_data_items(itemlist, items);
1256 if (num_items < 1) {
1257 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1261 imap_do_fetch(num_items, itemlist);
1262 cprintf("%s OK FETCH completed\r\n", parms[0]);
1266 * This function is called by the main command loop.
1268 void imap_uidfetch(int num_parms, char *parms[]) {
1270 char *itemlist[SIZ];
1273 int have_uid_item = 0;
1275 if (num_parms < 5) {
1276 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1280 imap_pick_range(parms[3], 1);
1283 for (i=4; i<num_parms; ++i) {
1284 strcat(items, parms[i]);
1285 if (i < (num_parms-1)) strcat(items, " ");
1288 num_items = imap_extract_data_items(itemlist, items);
1289 if (num_items < 1) {
1290 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1294 /* If the "UID" item was not included, we include it implicitly
1295 * because this is a UID FETCH command
1297 for (i=0; i<num_items; ++i) {
1298 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1300 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1302 imap_do_fetch(num_items, itemlist);
1303 cprintf("%s OK UID FETCH completed\r\n", parms[0]);