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");
91 void imap_fetch_internaldate(struct CtdlMessage *msg) {
95 if (msg->cm_fields['T'] != NULL) {
96 msgdate = atol(msg->cm_fields['T']);
102 datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
103 cprintf("INTERNALDATE \"%s\"", buf);
108 * Fetch RFC822-formatted messages.
110 * 'whichfmt' should be set to one of:
111 * "RFC822" entire message
112 * "RFC822.HEADER" headers only (with trailing blank line)
113 * "RFC822.SIZE" size of translated message
114 * "RFC822.TEXT" body only (without leading blank line)
116 void imap_fetch_rfc822(long msgnum, char *whichfmt) {
119 long headers_size, text_size, total_size;
120 long bytes_remaining = 0;
124 /* Cache the most recent RFC822 FETCH because some clients like to
125 * fetch in pieces, and we don't want to have to go back to the
126 * message store for each piece.
128 if ((IMAP->cached_fetch != NULL) && (IMAP->cached_msgnum == msgnum)) {
130 tmp = IMAP->cached_fetch;
132 else if ((IMAP->cached_fetch != NULL) && (IMAP->cached_msgnum != msgnum)) {
133 /* Some other message is cached -- free it */
134 fclose(IMAP->cached_fetch);
135 IMAP->cached_fetch == NULL;
136 IMAP->cached_msgnum = (-1);
140 /* At this point, we now can fetch and convert the message iff it's not
141 * the one we had cached.
146 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n",
152 * Load the message into a temp file for translation
155 CtdlRedirectOutput(tmp, -1);
156 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
157 CtdlRedirectOutput(NULL, -1);
159 IMAP->cached_fetch = tmp;
160 IMAP->cached_msgnum = msgnum;
164 * Now figure out where the headers/text break is. IMAP considers the
165 * intervening blank line to be part of the headers, not the text.
170 ptr = fgets(buf, sizeof buf, tmp);
173 if (strlen(buf) == 0) {
174 headers_size = ftell(tmp);
177 } while ( (headers_size == 0L) && (ptr != NULL) );
178 fseek(tmp, 0L, SEEK_END);
179 total_size = ftell(tmp);
180 text_size = total_size - headers_size;
182 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
183 cprintf("RFC822.SIZE %ld", total_size);
187 else if (!strcasecmp(whichfmt, "RFC822")) {
188 bytes_remaining = total_size;
192 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
193 bytes_remaining = headers_size;
197 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
198 bytes_remaining = text_size;
199 fseek(tmp, headers_size, SEEK_SET);
202 cprintf("%s {%ld}\r\n", whichfmt, bytes_remaining);
203 blocksize = sizeof(buf);
204 while (bytes_remaining > 0L) {
205 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
206 fread(buf, blocksize, 1, tmp);
207 client_write(buf, blocksize);
208 bytes_remaining = bytes_remaining - blocksize;
216 * Load a specific part of a message into the temp file to be output to a
217 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
218 * but we still can't handle "2.HEADER" (which might not be a problem, because
219 * we currently don't have the ability to break out nested RFC822's anyway).
221 * Note: mime_parser() was called with dont_decode set to 1, so we have the
222 * luxury of simply spewing without having to re-encode.
224 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
225 void *content, char *cbtype, size_t length, char *encoding,
228 struct imap_fetch_part *imfp;
231 imfp = (struct imap_fetch_part *)cbuserdata;
233 if (!strcasecmp(partnum, imfp->desired_section)) {
234 fwrite(content, length, 1, imfp->output_fp);
237 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
239 if (!strcasecmp(imfp->desired_section, mbuf2)) {
240 fprintf(imfp->output_fp, "Content-type: %s", cbtype);
241 if (strlen(name) > 0)
242 fprintf(imfp->output_fp, "; name=\"%s\"", name);
243 fprintf(imfp->output_fp, "\r\n");
244 if (strlen(encoding) > 0)
245 fprintf(imfp->output_fp,
246 "Content-Transfer-Encoding: %s\r\n", encoding);
247 if (strlen(encoding) > 0) {
248 fprintf(imfp->output_fp, "Content-Disposition: %s",
250 if (strlen(filename) > 0) {
251 fprintf(imfp->output_fp, "; filename=\"%s\"",
254 fprintf(imfp->output_fp, "\r\n");
256 fprintf(imfp->output_fp, "Content-Length: %ld\r\n", (long)length);
257 fprintf(imfp->output_fp, "\r\n");
265 * Called by imap_fetch_envelope() to output the "From" field.
266 * This is in its own function because its logic is kind of complex. We
267 * really need to make this suck less.
269 void imap_output_envelope_from(struct CtdlMessage *msg) {
270 char user[1024], node[1024], name[1024];
272 /* For anonymous messages, it's so easy! */
273 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
274 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
277 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
278 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
282 /* For everything else, we do stuff. */
283 cprintf("(("); /* open double-parens */
284 imap_strout(msg->cm_fields['A']); /* personal name */
285 cprintf(" NIL "); /* source route (not used) */
288 if (msg->cm_fields['F'] != NULL) {
289 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
290 imap_strout(user); /* mailbox name (user id) */
292 if (!strcasecmp(node, config.c_nodename)) {
293 imap_strout(config.c_fqdn);
296 imap_strout(node); /* host name */
300 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
302 imap_strout(msg->cm_fields['N']); /* host name */
305 cprintf(")) "); /* close double-parens */
311 * Output an envelope address (or set of addresses) in the official,
312 * Crispin-approved braindead format. (Note that we can't use this for
313 * the "From" address because its data may come from a number of different
314 * fields. But we can use it for "To" and possibly others.
316 void imap_output_envelope_addr(char *addr) {
317 char individual_addr[SIZ];
329 if (strlen(addr) == 0) {
336 /* How many addresses are listed here? */
337 num_addrs = num_tokens(addr, ',');
339 /* Output them one by one. */
340 for (i=0; i<num_addrs; ++i) {
341 extract_token(individual_addr, addr, i, ',');
342 striplt(individual_addr);
343 process_rfc822_addr(individual_addr, user, node, name);
351 if (i < (num_addrs-1)) cprintf(" ");
359 * Implements the ENVELOPE fetch item
361 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
362 * so we don't have to check for that condition like we do elsewhere.
364 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
365 char datestringbuf[SIZ];
367 char *fieldptr = NULL;
369 /* Parse the message date into an IMAP-format date string */
370 if (msg->cm_fields['T'] != NULL) {
371 msgdate = atol(msg->cm_fields['T']);
374 msgdate = time(NULL);
376 datestring(datestringbuf, sizeof datestringbuf,
377 msgdate, DATESTRING_IMAP);
379 /* Now start spewing data fields. The order is important, as it is
380 * defined by the protocol specification. Nonexistent fields must
381 * be output as NIL, existent fields must be quoted or literalled.
382 * The imap_strout() function conveniently does all this for us.
384 cprintf("ENVELOPE (");
387 imap_strout(datestringbuf);
391 imap_strout(msg->cm_fields['U']);
395 imap_output_envelope_from(msg);
397 /* Sender (default to same as 'From' if not present) */
398 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
399 if (fieldptr != NULL) {
400 imap_output_envelope_addr(fieldptr);
404 imap_output_envelope_from(msg);
408 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
409 if (fieldptr != NULL) {
410 imap_output_envelope_addr(fieldptr);
414 imap_output_envelope_from(msg);
418 imap_output_envelope_addr(msg->cm_fields['R']);
421 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
422 imap_output_envelope_addr(fieldptr);
423 if (fieldptr != NULL) phree(fieldptr);
426 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
427 imap_output_envelope_addr(fieldptr);
428 if (fieldptr != NULL) phree(fieldptr);
431 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
432 imap_strout(fieldptr);
434 if (fieldptr != NULL) phree(fieldptr);
437 imap_strout(msg->cm_fields['I']);
444 * Strip any non header information out of a chunk of RFC822 data on disk,
445 * then boil it down to just the fields we want.
447 void imap_strip_headers(FILE *fp, char *section) {
449 char *which_fields = NULL;
450 int doing_headers = 0;
455 char *boiled_headers = NULL;
457 int done_headers = 0;
459 which_fields = strdoop(section);
461 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
463 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
466 for (i=0; i<strlen(which_fields); ++i) {
467 if (which_fields[i]=='(')
468 strcpy(which_fields, &which_fields[i+1]);
470 for (i=0; i<strlen(which_fields); ++i) {
471 if (which_fields[i]==')')
474 num_parms = imap_parameterize(parms, which_fields);
476 fseek(fp, 0L, SEEK_END);
477 boiled_headers = mallok((size_t)(ftell(fp) + 256L));
478 strcpy(boiled_headers, "");
482 while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
483 if (!isspace(buf[0])) {
485 if (doing_headers == 0) ok = 1;
487 if (headers_not) ok = 1;
489 for (i=0; i<num_parms; ++i) {
490 if ( (!strncasecmp(buf, parms[i],
491 strlen(parms[i]))) &&
492 (buf[strlen(parms[i])]==':') ) {
493 if (headers_not) ok = 0;
501 strcat(boiled_headers, buf);
504 if (strlen(buf) == 0) done_headers = 1;
505 if (buf[0]=='\r') done_headers = 1;
506 if (buf[0]=='\n') done_headers = 1;
509 strcat(boiled_headers, "\r\n");
511 /* Now write it back */
513 fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
515 ftruncate(fileno(fp), ftell(fp));
519 phree(boiled_headers);
524 * Implements the BODY and BODY.PEEK fetch items
526 void imap_fetch_body(long msgnum, char *item, int is_peek) {
527 struct CtdlMessage *msg = NULL;
534 long bytes_remaining = 0;
537 struct imap_fetch_part imfp;
539 /* extract section */
540 strcpy(section, item);
541 for (i=0; i<strlen(section); ++i) {
542 if (section[i]=='[') strcpy(section, §ion[i+1]);
544 for (i=0; i<strlen(section); ++i) {
545 if (section[i]==']') section[i] = 0;
547 lprintf(CTDL_DEBUG, "Section is %s\n", section);
549 /* Burn the cache if we don't have the same section of the
550 * same message again.
552 if (IMAP->cached_body != NULL) {
553 if ((IMAP->cached_bodymsgnum != msgnum)
554 || (strcasecmp(IMAP->cached_bodypart, section)) ) {
555 fclose(IMAP->cached_body);
556 IMAP->cached_body = NULL;
557 IMAP->cached_bodymsgnum = (-1);
558 strcpy(IMAP->cached_bodypart, "");
562 /* extract partial */
563 strcpy(partial, item);
564 for (i=0; i<strlen(partial); ++i) {
565 if (partial[i]=='<') {
566 strcpy(partial, &partial[i+1]);
570 for (i=0; i<strlen(partial); ++i) {
571 if (partial[i]=='>') partial[i] = 0;
573 if (is_partial == 0) strcpy(partial, "");
574 if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial);
576 if (IMAP->cached_body == NULL) {
579 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n", strerror(errno));
582 msg = CtdlFetchMessage(msgnum);
585 /* Now figure out what the client wants, and get it */
587 if (IMAP->cached_body != NULL) {
588 tmp = IMAP->cached_body;
590 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
591 CtdlRedirectOutput(tmp, -1);
592 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
594 CtdlRedirectOutput(NULL, -1);
597 else if (!strcmp(section, "")) {
598 CtdlRedirectOutput(tmp, -1);
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 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
612 CtdlRedirectOutput(NULL, -1);
613 imap_strip_headers(tmp, section);
617 * Strip it down if the client asked for everything _except_ headers.
619 else if (!strncasecmp(section, "TEXT", 4)) {
620 CtdlRedirectOutput(tmp, -1);
621 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
623 CtdlRedirectOutput(NULL, -1);
627 * Anything else must be a part specifier.
628 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
631 safestrncpy(imfp.desired_section, section,
632 sizeof(imfp.desired_section));
633 imfp.output_fp = tmp;
635 mime_parser(msg->cm_fields['M'], NULL,
636 *imap_load_part, NULL, NULL,
642 fseek(tmp, 0L, SEEK_END);
643 bytes_remaining = ftell(tmp);
645 if (is_partial == 0) {
647 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
650 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
651 if ((bytes_remaining - pstart) < pbytes) {
652 pbytes = bytes_remaining - pstart;
654 fseek(tmp, pstart, SEEK_SET);
655 bytes_remaining = pbytes;
656 cprintf("BODY[%s]<%ld> {%ld}\r\n",
657 section, pstart, bytes_remaining);
660 blocksize = sizeof(buf);
661 while (bytes_remaining > 0L) {
662 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
663 fread(buf, blocksize, 1, tmp);
664 client_write(buf, blocksize);
665 bytes_remaining = bytes_remaining - blocksize;
668 /* Don't close it ... cache it! */
670 IMAP->cached_body = tmp;
671 IMAP->cached_bodymsgnum = msgnum;
672 strcpy(IMAP->cached_bodypart, section);
675 CtdlFreeMessage(msg);
678 /* Mark this message as "seen" *unless* this is a "peek" operation */
680 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
685 * Called immediately before outputting a multipart bodystructure
687 void imap_fetch_bodystructure_pre(
688 char *name, char *filename, char *partnum, char *disp,
689 void *content, char *cbtype, size_t length, char *encoding,
699 * Called immediately after outputting a multipart bodystructure
701 void imap_fetch_bodystructure_post(
702 char *name, char *filename, char *partnum, char *disp,
703 void *content, char *cbtype, size_t length, char *encoding,
712 extract_token(subtype, cbtype, 1, '/');
713 imap_strout(subtype);
724 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
727 void imap_fetch_bodystructure_part(
728 char *name, char *filename, char *partnum, char *disp,
729 void *content, char *cbtype, size_t length, char *encoding,
734 int have_encoding = 0;
737 char cbmaintype[SIZ];
740 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
742 extract_token(cbmaintype, cbtype, 0, '/');
743 extract_token(cbsubtype, cbtype, 1, '/');
746 strcpy(cbmaintype, "TEXT");
747 strcpy(cbsubtype, "PLAIN");
751 imap_strout(cbmaintype);
753 imap_strout(cbsubtype);
756 cprintf("(\"CHARSET\" \"US-ASCII\"");
758 if (name != NULL) if (strlen(name)>0) {
759 cprintf(" \"NAME\" ");
765 cprintf("NIL "); /* Body ID */
766 cprintf("NIL "); /* Body description */
768 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
770 imap_strout(encoding);
777 /* The next field is the size of the part in bytes. */
778 cprintf("%ld ", (long)length); /* bytes */
780 /* The next field is the number of lines in the part, if and only
781 * if the part is TEXT. Crispin is a fscking idiot.
783 if (!strcasecmp(cbmaintype, "TEXT")) {
784 if (length) for (i=0; i<length; ++i) {
785 if (((char *)content)[i] == '\n') ++lines;
787 cprintf("%d ", lines);
790 /* More of Crispin being a fscking idiot */
791 if ((!strcasecmp(cbmaintype, "MESSAGE"))
792 && (!strcasecmp(cbsubtype, "RFC822"))) {
794 A body type of type MESSAGE and subtype RFC822
795 contains, immediately after the basic fields, the
796 envelope structure, body structure, and size in
797 text lines of the encapsulated message.
801 /* MD5 value of body part; we can get away with NIL'ing this */
808 else if (strlen(disp) == 0) {
814 if (filename != NULL) if (strlen(filename)>0) {
815 cprintf(" (\"FILENAME\" ");
816 imap_strout(filename);
822 /* Body language (not defined yet) */
829 * Spew the BODYSTRUCTURE data for a message. (Do you need a silencer if
830 * you're going to shoot a MIME? Do you need a reason to shoot Mark Crispin?
834 void imap_fetch_bodystructure (long msgnum, char *item,
835 struct CtdlMessage *msg) {
839 long start_of_body = 0L;
840 long body_bytes = 0L;
842 /* For non-RFC822 (ordinary Citadel) messages, this is short and
845 if (msg->cm_format_type != FMT_RFC822) {
847 /* *sigh* We have to RFC822-format the message just to be able
851 if (tmp == NULL) return;
852 CtdlRedirectOutput(tmp, -1);
853 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
854 CtdlRedirectOutput(NULL, -1);
857 while (fgets(buf, sizeof buf, tmp) != NULL) {
859 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
860 start_of_body = ftell(tmp);
863 body_bytes = ftell(tmp) - start_of_body;
866 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
867 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
868 "\"7BIT\" %ld %ld)", body_bytes, lines);
873 /* For messages already stored in RFC822 format, we have to parse. */
874 cprintf("BODYSTRUCTURE ");
875 mime_parser(msg->cm_fields['M'],
877 *imap_fetch_bodystructure_part, /* part */
878 *imap_fetch_bodystructure_pre, /* pre-multi */
879 *imap_fetch_bodystructure_post, /* post-multi */
881 1); /* don't decode -- we want it as-is */
886 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
887 * individual message, once it has been selected for output.
889 void imap_do_fetch_msg(int seq,
890 int num_items, char **itemlist) {
892 struct CtdlMessage *msg = NULL;
894 cprintf("* %d FETCH (", seq);
896 for (i=0; i<num_items; ++i) {
898 /* Fetchable without going to the message store at all */
899 if (!strcasecmp(itemlist[i], "UID")) {
902 else if (!strcasecmp(itemlist[i], "FLAGS")) {
903 imap_fetch_flags(seq-1);
906 /* Potentially fetchable from cache, if the client requests
907 * stuff from the same message several times in a row.
909 else if (!strcasecmp(itemlist[i], "RFC822")) {
910 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
912 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
913 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
915 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
916 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
918 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
919 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
922 /* BODY fetches do their own fetching and caching too. */
923 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
924 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
926 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
927 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
930 /* Otherwise, load the message into memory.
932 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
933 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1]);
934 imap_fetch_bodystructure(IMAP->msgids[seq-1],
937 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
938 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1]);
939 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
941 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
942 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1]);
943 imap_fetch_internaldate(msg);
946 if (i != num_items-1) cprintf(" ");
951 CtdlFreeMessage(msg);
958 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
959 * validated and boiled down the request a bit.
961 void imap_do_fetch(int num_items, char **itemlist) {
964 if (IMAP->num_msgs > 0) {
965 for (i = 0; i < IMAP->num_msgs; ++i) {
966 if (IMAP->flags[i] & IMAP_SELECTED) {
967 imap_do_fetch_msg(i+1, num_items, itemlist);
976 * Back end for imap_handle_macros()
977 * Note that this function *only* looks at the beginning of the string. It
978 * is not a generic search-and-replace function.
980 void imap_macro_replace(char *str, char *find, char *replace) {
983 if (!strncasecmp(str, find, strlen(find))) {
984 if (str[strlen(find)]==' ') {
985 strcpy(holdbuf, &str[strlen(find)+1]);
986 strcpy(str, replace);
988 strcat(str, holdbuf);
990 if (str[strlen(find)]==0) {
991 strcpy(holdbuf, &str[strlen(find)+1]);
992 strcpy(str, replace);
1000 * Handle macros embedded in FETCH data items.
1001 * (What the heck are macros doing in a wire protocol? Are we trying to save
1002 * the computer at the other end the trouble of typing a lot of characters?)
1004 void imap_handle_macros(char *str) {
1008 for (i=0; i<strlen(str); ++i) {
1009 if (str[i]=='(') ++nest;
1010 if (str[i]=='[') ++nest;
1011 if (str[i]=='<') ++nest;
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;
1019 imap_macro_replace(&str[i],
1021 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1023 imap_macro_replace(&str[i],
1027 imap_macro_replace(&str[i],
1029 "FLAGS INTERNALDATE RFC822.SIZE"
1031 imap_macro_replace(&str[i],
1033 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1041 * Break out the data items requested, possibly a parenthesized list.
1042 * Returns the number of data items, or -1 if the list is invalid.
1043 * NOTE: this function alters the string it is fed, and uses it as a buffer
1044 * to hold the data for the pointers it returns.
1046 int imap_extract_data_items(char **argv, char *items) {
1052 /* Convert all whitespace to ordinary space characters. */
1053 for (i=0; i<strlen(items); ++i) {
1054 if (isspace(items[i])) items[i]=' ';
1057 /* Strip leading and trailing whitespace, then strip leading and
1058 * trailing parentheses if it's a list
1061 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1062 items[strlen(items)-1] = 0;
1063 strcpy(items, &items[1]);
1067 /* Parse any macro data items */
1068 imap_handle_macros(items);
1071 * Now break out the data items. We throw in one trailing space in
1072 * order to avoid having to break out the last one manually.
1076 initial_len = strlen(items);
1077 for (i=0; i<initial_len; ++i) {
1078 if (items[i]=='(') ++nest;
1079 if (items[i]=='[') ++nest;
1080 if (items[i]=='<') ++nest;
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;
1087 if (nest <= 0) if (items[i]==' ') {
1089 argv[num_items++] = start;
1090 start = &items[i+1];
1100 * One particularly hideous aspect of IMAP is that we have to allow the client
1101 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1102 * handles this by setting the IMAP_SELECTED flag for each message specified in
1103 * the ranges/sets, then looping through the message array, outputting messages
1104 * with the flag set. We don't bother returning an error if an out-of-range
1105 * number is specified (we just return quietly) because any client braindead
1106 * enough to request a bogus message number isn't going to notice the
1107 * difference anyway.
1109 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1110 * message included in the specified range.
1112 * Set is_uid to 1 to fetch by UID instead of sequence number.
1114 void imap_pick_range(char *supplied_range, int is_uid) {
1118 char setstr[SIZ], lostr[SIZ], histr[SIZ]; /* was 1024 */
1120 char actual_range[SIZ];
1123 * Handle the "ALL" macro
1125 if (!strcasecmp(supplied_range, "ALL")) {
1126 safestrncpy(actual_range, "1:*", sizeof actual_range);
1129 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1133 * Clear out the IMAP_SELECTED flags for all messages.
1135 for (i = 0; i < IMAP->num_msgs; ++i) {
1136 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1140 * Now set it for all specified messages.
1142 num_sets = num_tokens(actual_range, ',');
1143 for (s=0; s<num_sets; ++s) {
1144 extract_token(setstr, actual_range, s, ',');
1146 extract_token(lostr, setstr, 0, ':');
1147 if (num_tokens(setstr, ':') >= 2) {
1148 extract_token(histr, setstr, 1, ':');
1149 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1152 strcpy(histr, lostr);
1157 /* Loop through the array, flipping bits where appropriate */
1158 for (i = 1; i <= IMAP->num_msgs; ++i) {
1159 if (is_uid) { /* fetch by sequence number */
1160 if ( (IMAP->msgids[i-1]>=lo)
1161 && (IMAP->msgids[i-1]<=hi)) {
1163 IMAP->flags[i-1] | IMAP_SELECTED;
1166 else { /* fetch by uid */
1167 if ( (i>=lo) && (i<=hi)) {
1169 IMAP->flags[i-1] | IMAP_SELECTED;
1180 * This function is called by the main command loop.
1182 void imap_fetch(int num_parms, char *parms[]) {
1183 char items[SIZ]; /* was 1024 */
1184 char *itemlist[SIZ];
1188 if (num_parms < 4) {
1189 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1193 imap_pick_range(parms[2], 0);
1196 for (i=3; i<num_parms; ++i) {
1197 strcat(items, parms[i]);
1198 if (i < (num_parms-1)) strcat(items, " ");
1201 num_items = imap_extract_data_items(itemlist, items);
1202 if (num_items < 1) {
1203 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1207 imap_do_fetch(num_items, itemlist);
1208 cprintf("%s OK FETCH completed\r\n", parms[0]);
1212 * This function is called by the main command loop.
1214 void imap_uidfetch(int num_parms, char *parms[]) {
1215 char items[SIZ]; /* was 1024 */
1216 char *itemlist[SIZ];
1219 int have_uid_item = 0;
1221 if (num_parms < 5) {
1222 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1226 imap_pick_range(parms[3], 1);
1229 for (i=4; i<num_parms; ++i) {
1230 strcat(items, parms[i]);
1231 if (i < (num_parms-1)) strcat(items, " ");
1234 num_items = imap_extract_data_items(itemlist, items);
1235 if (num_items < 1) {
1236 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1240 /* If the "UID" item was not included, we include it implicitly
1241 * because this is a UID FETCH command
1243 for (i=0; i<num_items; ++i) {
1244 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1246 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1248 imap_do_fetch(num_items, itemlist);
1249 cprintf("%s OK UID FETCH completed\r\n", parms[0]);