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,
940 int num_items, char **itemlist) {
942 struct CtdlMessage *msg = NULL;
944 lprintf(CTDL_DEBUG, "imap_do_fetch_msg(%d, %d)\n", seq, num_items);
945 for (i=0; i<num_items; ++i) {
946 lprintf(CTDL_DEBUG, " %s\n", itemlist[i]);
949 cprintf("* %d FETCH (", seq);
951 for (i=0; i<num_items; ++i) {
953 /* Fetchable without going to the message store at all */
954 if (!strcasecmp(itemlist[i], "UID")) {
957 else if (!strcasecmp(itemlist[i], "FLAGS")) {
958 imap_fetch_flags(seq-1);
961 /* Potentially fetchable from cache, if the client requests
962 * stuff from the same message several times in a row.
964 else if (!strcasecmp(itemlist[i], "RFC822")) {
965 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
967 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
968 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
970 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
971 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
973 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
974 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
977 /* BODY fetches do their own fetching and caching too. */
978 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
979 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
981 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
982 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
985 /* Otherwise, load the message into memory.
987 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
988 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
989 imap_fetch_bodystructure(IMAP->msgids[seq-1],
992 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
993 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
994 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
996 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
997 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
998 imap_fetch_internaldate(msg);
1001 if (i != num_items-1) cprintf(" ");
1006 CtdlFreeMessage(msg);
1013 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1014 * validated and boiled down the request a bit.
1016 void imap_do_fetch(int num_items, char **itemlist) {
1019 if (IMAP->num_msgs > 0) {
1020 for (i = 0; i < IMAP->num_msgs; ++i) {
1021 if (IMAP->flags[i] & IMAP_SELECTED) {
1022 imap_do_fetch_msg(i+1, num_items, itemlist);
1031 * Back end for imap_handle_macros()
1032 * Note that this function *only* looks at the beginning of the string. It
1033 * is not a generic search-and-replace function.
1035 void imap_macro_replace(char *str, char *find, char *replace) {
1038 if (!strncasecmp(str, find, strlen(find))) {
1039 if (str[strlen(find)]==' ') {
1040 strcpy(holdbuf, &str[strlen(find)+1]);
1041 strcpy(str, replace);
1043 strcat(str, holdbuf);
1045 if (str[strlen(find)]==0) {
1046 strcpy(holdbuf, &str[strlen(find)+1]);
1047 strcpy(str, replace);
1055 * Handle macros embedded in FETCH data items.
1056 * (What the heck are macros doing in a wire protocol? Are we trying to save
1057 * the computer at the other end the trouble of typing a lot of characters?)
1059 void imap_handle_macros(char *str) {
1063 for (i=0; i<strlen(str); ++i) {
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;
1071 if (str[i]=='}') --nest;
1074 imap_macro_replace(&str[i],
1076 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1078 imap_macro_replace(&str[i],
1082 imap_macro_replace(&str[i],
1084 "FLAGS INTERNALDATE RFC822.SIZE"
1086 imap_macro_replace(&str[i],
1088 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1096 * Break out the data items requested, possibly a parenthesized list.
1097 * Returns the number of data items, or -1 if the list is invalid.
1098 * NOTE: this function alters the string it is fed, and uses it as a buffer
1099 * to hold the data for the pointers it returns.
1101 int imap_extract_data_items(char **argv, char *items) {
1107 /* Convert all whitespace to ordinary space characters. */
1108 for (i=0; i<strlen(items); ++i) {
1109 if (isspace(items[i])) items[i]=' ';
1112 /* Strip leading and trailing whitespace, then strip leading and
1113 * trailing parentheses if it's a list
1116 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1117 items[strlen(items)-1] = 0;
1118 strcpy(items, &items[1]);
1122 /* Parse any macro data items */
1123 imap_handle_macros(items);
1126 * Now break out the data items. We throw in one trailing space in
1127 * order to avoid having to break out the last one manually.
1131 initial_len = strlen(items);
1132 for (i=0; i<initial_len; ++i) {
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;
1140 if (items[i]=='}') --nest;
1142 if (nest <= 0) if (items[i]==' ') {
1144 argv[num_items++] = start;
1145 start = &items[i+1];
1155 * One particularly hideous aspect of IMAP is that we have to allow the client
1156 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1157 * handles this by setting the IMAP_SELECTED flag for each message specified in
1158 * the ranges/sets, then looping through the message array, outputting messages
1159 * with the flag set. We don't bother returning an error if an out-of-range
1160 * number is specified (we just return quietly) because any client braindead
1161 * enough to request a bogus message number isn't going to notice the
1162 * difference anyway.
1164 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1165 * message included in the specified range.
1167 * Set is_uid to 1 to fetch by UID instead of sequence number.
1169 void imap_pick_range(char *supplied_range, int is_uid) {
1173 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1175 char actual_range[SIZ];
1178 * Handle the "ALL" macro
1180 if (!strcasecmp(supplied_range, "ALL")) {
1181 safestrncpy(actual_range, "1:*", sizeof actual_range);
1184 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1188 * Clear out the IMAP_SELECTED flags for all messages.
1190 for (i = 0; i < IMAP->num_msgs; ++i) {
1191 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1195 * Now set it for all specified messages.
1197 num_sets = num_tokens(actual_range, ',');
1198 for (s=0; s<num_sets; ++s) {
1199 extract_token(setstr, actual_range, s, ',');
1201 extract_token(lostr, setstr, 0, ':');
1202 if (num_tokens(setstr, ':') >= 2) {
1203 extract_token(histr, setstr, 1, ':');
1204 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1207 strcpy(histr, lostr);
1212 /* Loop through the array, flipping bits where appropriate */
1213 for (i = 1; i <= IMAP->num_msgs; ++i) {
1214 if (is_uid) { /* fetch by sequence number */
1215 if ( (IMAP->msgids[i-1]>=lo)
1216 && (IMAP->msgids[i-1]<=hi)) {
1218 IMAP->flags[i-1] | IMAP_SELECTED;
1221 else { /* fetch by uid */
1222 if ( (i>=lo) && (i<=hi)) {
1224 IMAP->flags[i-1] | IMAP_SELECTED;
1235 * This function is called by the main command loop.
1237 void imap_fetch(int num_parms, char *parms[]) {
1239 char *itemlist[SIZ];
1243 if (num_parms < 4) {
1244 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1248 imap_pick_range(parms[2], 0);
1251 for (i=3; i<num_parms; ++i) {
1252 strcat(items, parms[i]);
1253 if (i < (num_parms-1)) strcat(items, " ");
1256 num_items = imap_extract_data_items(itemlist, items);
1257 if (num_items < 1) {
1258 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1262 imap_do_fetch(num_items, itemlist);
1263 cprintf("%s OK FETCH completed\r\n", parms[0]);
1267 * This function is called by the main command loop.
1269 void imap_uidfetch(int num_parms, char *parms[]) {
1271 char *itemlist[SIZ];
1274 int have_uid_item = 0;
1276 if (num_parms < 5) {
1277 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1281 imap_pick_range(parms[3], 1);
1284 for (i=4; i<num_parms; ++i) {
1285 strcat(items, parms[i]);
1286 if (i < (num_parms-1)) strcat(items, " ");
1289 num_items = imap_extract_data_items(itemlist, items);
1290 if (num_items < 1) {
1291 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1295 /* If the "UID" item was not included, we include it implicitly
1296 * because this is a UID FETCH command
1298 for (i=0; i<num_items; ++i) {
1299 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1301 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1303 imap_do_fetch(num_items, itemlist);
1304 cprintf("%s OK UID FETCH completed\r\n", parms[0]);