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) {
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 /* extract partial */
550 strcpy(partial, item);
551 for (i=0; i<strlen(partial); ++i) {
552 if (partial[i]=='<') {
553 strcpy(partial, &partial[i+1]);
557 for (i=0; i<strlen(partial); ++i) {
558 if (partial[i]=='>') partial[i] = 0;
560 if (is_partial == 0) strcpy(partial, "");
561 if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial);
565 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n", strerror(errno));
569 /* Now figure out what the client wants, and get it */
571 if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
572 CtdlRedirectOutput(tmp, -1);
573 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
575 CtdlRedirectOutput(NULL, -1);
578 else if (!strcmp(section, "")) {
579 CtdlRedirectOutput(tmp, -1);
580 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
582 CtdlRedirectOutput(NULL, -1);
586 * If the client asked for just headers, or just particular header
587 * fields, strip it down.
589 else if (!strncasecmp(section, "HEADER", 6)) {
590 CtdlRedirectOutput(tmp, -1);
591 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
593 CtdlRedirectOutput(NULL, -1);
594 imap_strip_headers(tmp, section);
598 * Strip it down if the client asked for everything _except_ headers.
600 else if (!strncasecmp(section, "TEXT", 4)) {
601 CtdlRedirectOutput(tmp, -1);
602 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
604 CtdlRedirectOutput(NULL, -1);
608 * Anything else must be a part specifier.
609 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
612 safestrncpy(imfp.desired_section, section,
613 sizeof(imfp.desired_section));
614 imfp.output_fp = tmp;
616 mime_parser(msg->cm_fields['M'], NULL,
617 *imap_load_part, NULL, NULL,
623 fseek(tmp, 0L, SEEK_END);
624 bytes_remaining = ftell(tmp);
626 if (is_partial == 0) {
628 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
631 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
632 if ((bytes_remaining - pstart) < pbytes) {
633 pbytes = bytes_remaining - pstart;
635 fseek(tmp, pstart, SEEK_SET);
636 bytes_remaining = pbytes;
637 cprintf("BODY[%s]<%ld> {%ld}\r\n",
638 section, pstart, bytes_remaining);
641 blocksize = sizeof(buf);
642 while (bytes_remaining > 0L) {
643 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
644 fread(buf, blocksize, 1, tmp);
645 client_write(buf, blocksize);
646 bytes_remaining = bytes_remaining - blocksize;
651 /* Mark this message as "seen" *unless* this is a "peek" operation */
653 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
658 * Called immediately before outputting a multipart bodystructure
660 void imap_fetch_bodystructure_pre(
661 char *name, char *filename, char *partnum, char *disp,
662 void *content, char *cbtype, size_t length, char *encoding,
672 * Called immediately after outputting a multipart bodystructure
674 void imap_fetch_bodystructure_post(
675 char *name, char *filename, char *partnum, char *disp,
676 void *content, char *cbtype, size_t length, char *encoding,
685 extract_token(subtype, cbtype, 1, '/');
686 imap_strout(subtype);
697 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
700 void imap_fetch_bodystructure_part(
701 char *name, char *filename, char *partnum, char *disp,
702 void *content, char *cbtype, size_t length, char *encoding,
707 int have_encoding = 0;
710 char cbmaintype[SIZ];
713 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
715 extract_token(cbmaintype, cbtype, 0, '/');
716 extract_token(cbsubtype, cbtype, 1, '/');
719 strcpy(cbmaintype, "TEXT");
720 strcpy(cbsubtype, "PLAIN");
724 imap_strout(cbmaintype);
726 imap_strout(cbsubtype);
729 cprintf("(\"CHARSET\" \"US-ASCII\"");
731 if (name != NULL) if (strlen(name)>0) {
732 cprintf(" \"NAME\" ");
738 cprintf("NIL "); /* Body ID */
739 cprintf("NIL "); /* Body description */
741 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
743 imap_strout(encoding);
750 /* The next field is the size of the part in bytes. */
751 cprintf("%ld ", (long)length); /* bytes */
753 /* The next field is the number of lines in the part, if and only
754 * if the part is TEXT. Crispin is a fscking idiot.
756 if (!strcasecmp(cbmaintype, "TEXT")) {
757 if (length) for (i=0; i<length; ++i) {
758 if (((char *)content)[i] == '\n') ++lines;
760 cprintf("%d ", lines);
763 /* More of Crispin being a fscking idiot */
764 if ((!strcasecmp(cbmaintype, "MESSAGE"))
765 && (!strcasecmp(cbsubtype, "RFC822"))) {
767 A body type of type MESSAGE and subtype RFC822
768 contains, immediately after the basic fields, the
769 envelope structure, body structure, and size in
770 text lines of the encapsulated message.
774 /* MD5 value of body part; we can get away with NIL'ing this */
781 else if (strlen(disp) == 0) {
787 if (filename != NULL) if (strlen(filename)>0) {
788 cprintf(" (\"FILENAME\" ");
789 imap_strout(filename);
795 /* Body language (not defined yet) */
802 * Spew the BODYSTRUCTURE data for a message. (Do you need a silencer if
803 * you're going to shoot a MIME? Do you need a reason to shoot Mark Crispin?
807 void imap_fetch_bodystructure (long msgnum, char *item,
808 struct CtdlMessage *msg) {
812 long start_of_body = 0L;
813 long body_bytes = 0L;
815 /* For non-RFC822 (ordinary Citadel) messages, this is short and
818 if (msg->cm_format_type != FMT_RFC822) {
820 /* *sigh* We have to RFC822-format the message just to be able
824 if (tmp == NULL) return;
825 CtdlRedirectOutput(tmp, -1);
826 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
827 CtdlRedirectOutput(NULL, -1);
830 while (fgets(buf, sizeof buf, tmp) != NULL) {
832 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
833 start_of_body = ftell(tmp);
836 body_bytes = ftell(tmp) - start_of_body;
839 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
840 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
841 "\"7BIT\" %ld %ld)", body_bytes, lines);
846 /* For messages already stored in RFC822 format, we have to parse. */
847 cprintf("BODYSTRUCTURE ");
848 mime_parser(msg->cm_fields['M'],
850 *imap_fetch_bodystructure_part, /* part */
851 *imap_fetch_bodystructure_pre, /* pre-multi */
852 *imap_fetch_bodystructure_post, /* post-multi */
854 1); /* don't decode -- we want it as-is */
859 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
860 * individual message, once it has been selected for output.
862 void imap_do_fetch_msg(int seq,
863 int num_items, char **itemlist) {
865 struct CtdlMessage *msg = NULL;
868 cprintf("* %d FETCH (", seq);
870 for (i=0; i<num_items; ++i) {
872 if (!strcasecmp(itemlist[i], "RFC822")) {
873 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
875 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
876 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
878 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
879 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
881 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
882 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
885 /* Not an RFC822.* fetch item? Load the message into memory. */
886 msg = CtdlFetchMessage(IMAP->msgids[seq-1]);
887 if (!strncasecmp(itemlist[i], "BODY[", 5)) {
888 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
891 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
892 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
895 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
896 imap_fetch_bodystructure(IMAP->msgids[seq-1],
899 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
900 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
902 else if (!strcasecmp(itemlist[i], "FLAGS")) {
903 imap_fetch_flags(seq-1);
905 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
906 imap_fetch_internaldate(msg);
908 else if (!strcasecmp(itemlist[i], "UID")) {
912 if (i != num_items-1) cprintf(" ");
917 CtdlFreeMessage(msg);
924 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
925 * validated and boiled down the request a bit.
927 void imap_do_fetch(int num_items, char **itemlist) {
930 if (IMAP->num_msgs > 0) {
931 for (i = 0; i < IMAP->num_msgs; ++i) {
932 if (IMAP->flags[i] & IMAP_SELECTED) {
933 imap_do_fetch_msg(i+1, num_items, itemlist);
942 * Back end for imap_handle_macros()
943 * Note that this function *only* looks at the beginning of the string. It
944 * is not a generic search-and-replace function.
946 void imap_macro_replace(char *str, char *find, char *replace) {
949 if (!strncasecmp(str, find, strlen(find))) {
950 if (str[strlen(find)]==' ') {
951 strcpy(holdbuf, &str[strlen(find)+1]);
952 strcpy(str, replace);
954 strcat(str, holdbuf);
956 if (str[strlen(find)]==0) {
957 strcpy(holdbuf, &str[strlen(find)+1]);
958 strcpy(str, replace);
966 * Handle macros embedded in FETCH data items.
967 * (What the heck are macros doing in a wire protocol? Are we trying to save
968 * the computer at the other end the trouble of typing a lot of characters?)
970 void imap_handle_macros(char *str) {
974 for (i=0; i<strlen(str); ++i) {
975 if (str[i]=='(') ++nest;
976 if (str[i]=='[') ++nest;
977 if (str[i]=='<') ++nest;
978 if (str[i]=='{') ++nest;
979 if (str[i]==')') --nest;
980 if (str[i]==']') --nest;
981 if (str[i]=='>') --nest;
982 if (str[i]=='}') --nest;
985 imap_macro_replace(&str[i],
987 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
989 imap_macro_replace(&str[i],
993 imap_macro_replace(&str[i],
995 "FLAGS INTERNALDATE RFC822.SIZE"
997 imap_macro_replace(&str[i],
999 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1007 * Break out the data items requested, possibly a parenthesized list.
1008 * Returns the number of data items, or -1 if the list is invalid.
1009 * NOTE: this function alters the string it is fed, and uses it as a buffer
1010 * to hold the data for the pointers it returns.
1012 int imap_extract_data_items(char **argv, char *items) {
1018 /* Convert all whitespace to ordinary space characters. */
1019 for (i=0; i<strlen(items); ++i) {
1020 if (isspace(items[i])) items[i]=' ';
1023 /* Strip leading and trailing whitespace, then strip leading and
1024 * trailing parentheses if it's a list
1027 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1028 items[strlen(items)-1] = 0;
1029 strcpy(items, &items[1]);
1033 /* Parse any macro data items */
1034 imap_handle_macros(items);
1037 * Now break out the data items. We throw in one trailing space in
1038 * order to avoid having to break out the last one manually.
1042 initial_len = strlen(items);
1043 for (i=0; i<initial_len; ++i) {
1044 if (items[i]=='(') ++nest;
1045 if (items[i]=='[') ++nest;
1046 if (items[i]=='<') ++nest;
1047 if (items[i]=='{') ++nest;
1048 if (items[i]==')') --nest;
1049 if (items[i]==']') --nest;
1050 if (items[i]=='>') --nest;
1051 if (items[i]=='}') --nest;
1053 if (nest <= 0) if (items[i]==' ') {
1055 argv[num_items++] = start;
1056 start = &items[i+1];
1066 * One particularly hideous aspect of IMAP is that we have to allow the client
1067 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1068 * handles this by setting the IMAP_SELECTED flag for each message specified in
1069 * the ranges/sets, then looping through the message array, outputting messages
1070 * with the flag set. We don't bother returning an error if an out-of-range
1071 * number is specified (we just return quietly) because any client braindead
1072 * enough to request a bogus message number isn't going to notice the
1073 * difference anyway.
1075 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1076 * message included in the specified range.
1078 * Set is_uid to 1 to fetch by UID instead of sequence number.
1080 void imap_pick_range(char *supplied_range, int is_uid) {
1084 char setstr[SIZ], lostr[SIZ], histr[SIZ]; /* was 1024 */
1086 char actual_range[SIZ];
1089 * Handle the "ALL" macro
1091 if (!strcasecmp(supplied_range, "ALL")) {
1092 safestrncpy(actual_range, "1:*", sizeof actual_range);
1095 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1099 * Clear out the IMAP_SELECTED flags for all messages.
1101 for (i = 0; i < IMAP->num_msgs; ++i) {
1102 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1106 * Now set it for all specified messages.
1108 num_sets = num_tokens(actual_range, ',');
1109 for (s=0; s<num_sets; ++s) {
1110 extract_token(setstr, actual_range, s, ',');
1112 extract_token(lostr, setstr, 0, ':');
1113 if (num_tokens(setstr, ':') >= 2) {
1114 extract_token(histr, setstr, 1, ':');
1115 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1118 strcpy(histr, lostr);
1123 /* Loop through the array, flipping bits where appropriate */
1124 for (i = 1; i <= IMAP->num_msgs; ++i) {
1125 if (is_uid) { /* fetch by sequence number */
1126 if ( (IMAP->msgids[i-1]>=lo)
1127 && (IMAP->msgids[i-1]<=hi)) {
1129 IMAP->flags[i-1] | IMAP_SELECTED;
1132 else { /* fetch by uid */
1133 if ( (i>=lo) && (i<=hi)) {
1135 IMAP->flags[i-1] | IMAP_SELECTED;
1146 * This function is called by the main command loop.
1148 void imap_fetch(int num_parms, char *parms[]) {
1149 char items[SIZ]; /* was 1024 */
1150 char *itemlist[SIZ];
1154 if (num_parms < 4) {
1155 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1159 imap_pick_range(parms[2], 0);
1162 for (i=3; i<num_parms; ++i) {
1163 strcat(items, parms[i]);
1164 if (i < (num_parms-1)) strcat(items, " ");
1167 num_items = imap_extract_data_items(itemlist, items);
1168 if (num_items < 1) {
1169 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1173 imap_do_fetch(num_items, itemlist);
1174 cprintf("%s OK FETCH completed\r\n", parms[0]);
1178 * This function is called by the main command loop.
1180 void imap_uidfetch(int num_parms, char *parms[]) {
1181 char items[SIZ]; /* was 1024 */
1182 char *itemlist[SIZ];
1185 int have_uid_item = 0;
1187 if (num_parms < 5) {
1188 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1192 imap_pick_range(parms[3], 1);
1195 for (i=4; i<num_parms; ++i) {
1196 strcat(items, parms[i]);
1197 if (i < (num_parms-1)) strcat(items, " ");
1200 num_items = imap_extract_data_items(itemlist, items);
1201 if (num_items < 1) {
1202 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1206 /* If the "UID" item was not included, we include it implicitly
1207 * because this is a UID FETCH command
1209 for (i=0; i<num_items; ++i) {
1210 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1212 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1214 imap_do_fetch(num_items, itemlist);
1215 cprintf("%s OK UID FETCH completed\r\n", parms[0]);