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
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 * Crispin-approved 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. Crispin is a fscking idiot.
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 of Crispin being a fscking idiot */
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. (Do you need a silencer if
836 * you're going to shoot a MIME? Do you need a reason to shoot Mark Crispin?
840 void imap_fetch_bodystructure (long msgnum, char *item,
841 struct CtdlMessage *msg) {
845 long start_of_body = 0L;
846 long body_bytes = 0L;
848 /* For non-RFC822 (ordinary Citadel) messages, this is short and
851 if (msg->cm_format_type != FMT_RFC822) {
853 /* *sigh* We have to RFC822-format the message just to be able
857 if (tmp == NULL) return;
858 CtdlRedirectOutput(tmp, -1);
859 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
860 CtdlRedirectOutput(NULL, -1);
863 while (fgets(buf, sizeof buf, tmp) != NULL) {
865 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
866 start_of_body = ftell(tmp);
869 body_bytes = ftell(tmp) - start_of_body;
872 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
873 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
874 "\"7BIT\" %ld %ld)", body_bytes, lines);
879 /* For messages already stored in RFC822 format, we have to parse. */
880 cprintf("BODYSTRUCTURE ");
881 mime_parser(msg->cm_fields['M'],
883 *imap_fetch_bodystructure_part, /* part */
884 *imap_fetch_bodystructure_pre, /* pre-multi */
885 *imap_fetch_bodystructure_post, /* post-multi */
887 1); /* don't decode -- we want it as-is */
892 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
893 * individual message, once it has been selected for output.
895 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
897 struct CtdlMessage *msg = NULL;
899 lprintf(CTDL_DEBUG, "imap_do_fetch_msg(%d, %d)\n", seq, num_items);
900 for (i=0; i<num_items; ++i) {
901 lprintf(CTDL_DEBUG, " %s\n", itemlist[i]);
904 cprintf("* %d FETCH (", seq);
906 for (i=0; i<num_items; ++i) {
908 /* Fetchable without going to the message store at all */
909 if (!strcasecmp(itemlist[i], "UID")) {
912 else if (!strcasecmp(itemlist[i], "FLAGS")) {
913 imap_fetch_flags(seq-1);
916 /* Potentially fetchable from cache, if the client requests
917 * stuff from the same message several times in a row.
919 else if (!strcasecmp(itemlist[i], "RFC822")) {
920 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
922 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
923 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
925 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
926 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
928 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
929 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
932 /* BODY fetches do their own fetching and caching too. */
933 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
934 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
936 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
937 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
940 /* Otherwise, load the message into memory.
942 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
943 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
944 imap_fetch_bodystructure(IMAP->msgids[seq-1],
947 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
948 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
949 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
951 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
952 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
953 imap_fetch_internaldate(msg);
956 if (i != num_items-1) cprintf(" ");
961 CtdlFreeMessage(msg);
968 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
969 * validated and boiled down the request a bit.
971 void imap_do_fetch(int num_items, char **itemlist) {
974 if (IMAP->num_msgs > 0) {
975 for (i = 0; i < IMAP->num_msgs; ++i) {
976 if (IMAP->flags[i] & IMAP_SELECTED) {
977 imap_do_fetch_msg(i+1, num_items, itemlist);
986 * Back end for imap_handle_macros()
987 * Note that this function *only* looks at the beginning of the string. It
988 * is not a generic search-and-replace function.
990 void imap_macro_replace(char *str, char *find, char *replace) {
993 if (!strncasecmp(str, find, strlen(find))) {
994 if (str[strlen(find)]==' ') {
995 strcpy(holdbuf, &str[strlen(find)+1]);
996 strcpy(str, replace);
998 strcat(str, holdbuf);
1000 if (str[strlen(find)]==0) {
1001 strcpy(holdbuf, &str[strlen(find)+1]);
1002 strcpy(str, replace);
1010 * Handle macros embedded in FETCH data items.
1011 * (What the heck are macros doing in a wire protocol? Are we trying to save
1012 * the computer at the other end the trouble of typing a lot of characters?)
1014 void imap_handle_macros(char *str) {
1018 for (i=0; i<strlen(str); ++i) {
1019 if (str[i]=='(') ++nest;
1020 if (str[i]=='[') ++nest;
1021 if (str[i]=='<') ++nest;
1022 if (str[i]=='{') ++nest;
1023 if (str[i]==')') --nest;
1024 if (str[i]==']') --nest;
1025 if (str[i]=='>') --nest;
1026 if (str[i]=='}') --nest;
1029 imap_macro_replace(&str[i],
1031 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1033 imap_macro_replace(&str[i],
1037 imap_macro_replace(&str[i],
1039 "FLAGS INTERNALDATE RFC822.SIZE"
1041 imap_macro_replace(&str[i],
1043 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1051 * Break out the data items requested, possibly a parenthesized list.
1052 * Returns the number of data items, or -1 if the list is invalid.
1053 * NOTE: this function alters the string it is fed, and uses it as a buffer
1054 * to hold the data for the pointers it returns.
1056 int imap_extract_data_items(char **argv, char *items) {
1062 /* Convert all whitespace to ordinary space characters. */
1063 for (i=0; i<strlen(items); ++i) {
1064 if (isspace(items[i])) items[i]=' ';
1067 /* Strip leading and trailing whitespace, then strip leading and
1068 * trailing parentheses if it's a list
1071 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1072 items[strlen(items)-1] = 0;
1073 strcpy(items, &items[1]);
1077 /* Parse any macro data items */
1078 imap_handle_macros(items);
1081 * Now break out the data items. We throw in one trailing space in
1082 * order to avoid having to break out the last one manually.
1086 initial_len = strlen(items);
1087 for (i=0; i<initial_len; ++i) {
1088 if (items[i]=='(') ++nest;
1089 if (items[i]=='[') ++nest;
1090 if (items[i]=='<') ++nest;
1091 if (items[i]=='{') ++nest;
1092 if (items[i]==')') --nest;
1093 if (items[i]==']') --nest;
1094 if (items[i]=='>') --nest;
1095 if (items[i]=='}') --nest;
1097 if (nest <= 0) if (items[i]==' ') {
1099 argv[num_items++] = start;
1100 start = &items[i+1];
1110 * One particularly hideous aspect of IMAP is that we have to allow the client
1111 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1112 * handles this by setting the IMAP_SELECTED flag for each message specified in
1113 * the ranges/sets, then looping through the message array, outputting messages
1114 * with the flag set. We don't bother returning an error if an out-of-range
1115 * number is specified (we just return quietly) because any client braindead
1116 * enough to request a bogus message number isn't going to notice the
1117 * difference anyway.
1119 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1120 * message included in the specified range.
1122 * Set is_uid to 1 to fetch by UID instead of sequence number.
1124 void imap_pick_range(char *supplied_range, int is_uid) {
1128 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1130 char actual_range[SIZ];
1133 * Handle the "ALL" macro
1135 if (!strcasecmp(supplied_range, "ALL")) {
1136 safestrncpy(actual_range, "1:*", sizeof actual_range);
1139 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1143 * Clear out the IMAP_SELECTED flags for all messages.
1145 for (i = 0; i < IMAP->num_msgs; ++i) {
1146 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1150 * Now set it for all specified messages.
1152 num_sets = num_tokens(actual_range, ',');
1153 for (s=0; s<num_sets; ++s) {
1154 extract_token(setstr, actual_range, s, ',');
1156 extract_token(lostr, setstr, 0, ':');
1157 if (num_tokens(setstr, ':') >= 2) {
1158 extract_token(histr, setstr, 1, ':');
1159 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1162 strcpy(histr, lostr);
1167 /* Loop through the array, flipping bits where appropriate */
1168 for (i = 1; i <= IMAP->num_msgs; ++i) {
1169 if (is_uid) { /* fetch by sequence number */
1170 if ( (IMAP->msgids[i-1]>=lo)
1171 && (IMAP->msgids[i-1]<=hi)) {
1173 IMAP->flags[i-1] | IMAP_SELECTED;
1176 else { /* fetch by uid */
1177 if ( (i>=lo) && (i<=hi)) {
1179 IMAP->flags[i-1] | IMAP_SELECTED;
1190 * This function is called by the main command loop.
1192 void imap_fetch(int num_parms, char *parms[]) {
1194 char *itemlist[SIZ];
1198 if (num_parms < 4) {
1199 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1203 imap_pick_range(parms[2], 0);
1206 for (i=3; i<num_parms; ++i) {
1207 strcat(items, parms[i]);
1208 if (i < (num_parms-1)) strcat(items, " ");
1211 num_items = imap_extract_data_items(itemlist, items);
1212 if (num_items < 1) {
1213 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1217 imap_do_fetch(num_items, itemlist);
1218 cprintf("%s OK FETCH completed\r\n", parms[0]);
1222 * This function is called by the main command loop.
1224 void imap_uidfetch(int num_parms, char *parms[]) {
1226 char *itemlist[SIZ];
1229 int have_uid_item = 0;
1231 if (num_parms < 5) {
1232 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1236 imap_pick_range(parms[3], 1);
1239 for (i=4; i<num_parms; ++i) {
1240 strcat(items, parms[i]);
1241 if (i < (num_parms-1)) strcat(items, " ");
1244 num_items = imap_extract_data_items(itemlist, items);
1245 if (num_items < 1) {
1246 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1250 /* If the "UID" item was not included, we include it implicitly
1251 * because this is a UID FETCH command
1253 for (i=0; i<num_items; ++i) {
1254 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1256 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1258 imap_do_fetch(num_items, itemlist);
1259 cprintf("%s OK UID FETCH completed\r\n", parms[0]);