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 /* Fetchable without going to the message store at all */
873 if (!strcasecmp(itemlist[i], "UID")) {
876 else if (!strcasecmp(itemlist[i], "FLAGS")) {
877 imap_fetch_flags(seq-1);
880 /* Potentially fetchable from cache, if the client requests
881 * stuff from the same message several times in a row.
883 else if (!strcasecmp(itemlist[i], "RFC822")) {
884 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
886 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
887 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
889 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
890 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
892 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
893 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
896 /* Otherwise, load the message into memory.
898 msg = CtdlFetchMessage(IMAP->msgids[seq-1]);
899 if (!strncasecmp(itemlist[i], "BODY[", 5)) {
900 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
903 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
904 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
907 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
908 imap_fetch_bodystructure(IMAP->msgids[seq-1],
911 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
912 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
914 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
915 imap_fetch_internaldate(msg);
918 if (i != num_items-1) cprintf(" ");
923 CtdlFreeMessage(msg);
930 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
931 * validated and boiled down the request a bit.
933 void imap_do_fetch(int num_items, char **itemlist) {
936 if (IMAP->num_msgs > 0) {
937 for (i = 0; i < IMAP->num_msgs; ++i) {
938 if (IMAP->flags[i] & IMAP_SELECTED) {
939 imap_do_fetch_msg(i+1, num_items, itemlist);
948 * Back end for imap_handle_macros()
949 * Note that this function *only* looks at the beginning of the string. It
950 * is not a generic search-and-replace function.
952 void imap_macro_replace(char *str, char *find, char *replace) {
955 if (!strncasecmp(str, find, strlen(find))) {
956 if (str[strlen(find)]==' ') {
957 strcpy(holdbuf, &str[strlen(find)+1]);
958 strcpy(str, replace);
960 strcat(str, holdbuf);
962 if (str[strlen(find)]==0) {
963 strcpy(holdbuf, &str[strlen(find)+1]);
964 strcpy(str, replace);
972 * Handle macros embedded in FETCH data items.
973 * (What the heck are macros doing in a wire protocol? Are we trying to save
974 * the computer at the other end the trouble of typing a lot of characters?)
976 void imap_handle_macros(char *str) {
980 for (i=0; i<strlen(str); ++i) {
981 if (str[i]=='(') ++nest;
982 if (str[i]=='[') ++nest;
983 if (str[i]=='<') ++nest;
984 if (str[i]=='{') ++nest;
985 if (str[i]==')') --nest;
986 if (str[i]==']') --nest;
987 if (str[i]=='>') --nest;
988 if (str[i]=='}') --nest;
991 imap_macro_replace(&str[i],
993 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
995 imap_macro_replace(&str[i],
999 imap_macro_replace(&str[i],
1001 "FLAGS INTERNALDATE RFC822.SIZE"
1003 imap_macro_replace(&str[i],
1005 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1013 * Break out the data items requested, possibly a parenthesized list.
1014 * Returns the number of data items, or -1 if the list is invalid.
1015 * NOTE: this function alters the string it is fed, and uses it as a buffer
1016 * to hold the data for the pointers it returns.
1018 int imap_extract_data_items(char **argv, char *items) {
1024 /* Convert all whitespace to ordinary space characters. */
1025 for (i=0; i<strlen(items); ++i) {
1026 if (isspace(items[i])) items[i]=' ';
1029 /* Strip leading and trailing whitespace, then strip leading and
1030 * trailing parentheses if it's a list
1033 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1034 items[strlen(items)-1] = 0;
1035 strcpy(items, &items[1]);
1039 /* Parse any macro data items */
1040 imap_handle_macros(items);
1043 * Now break out the data items. We throw in one trailing space in
1044 * order to avoid having to break out the last one manually.
1048 initial_len = strlen(items);
1049 for (i=0; i<initial_len; ++i) {
1050 if (items[i]=='(') ++nest;
1051 if (items[i]=='[') ++nest;
1052 if (items[i]=='<') ++nest;
1053 if (items[i]=='{') ++nest;
1054 if (items[i]==')') --nest;
1055 if (items[i]==']') --nest;
1056 if (items[i]=='>') --nest;
1057 if (items[i]=='}') --nest;
1059 if (nest <= 0) if (items[i]==' ') {
1061 argv[num_items++] = start;
1062 start = &items[i+1];
1072 * One particularly hideous aspect of IMAP is that we have to allow the client
1073 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1074 * handles this by setting the IMAP_SELECTED flag for each message specified in
1075 * the ranges/sets, then looping through the message array, outputting messages
1076 * with the flag set. We don't bother returning an error if an out-of-range
1077 * number is specified (we just return quietly) because any client braindead
1078 * enough to request a bogus message number isn't going to notice the
1079 * difference anyway.
1081 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1082 * message included in the specified range.
1084 * Set is_uid to 1 to fetch by UID instead of sequence number.
1086 void imap_pick_range(char *supplied_range, int is_uid) {
1090 char setstr[SIZ], lostr[SIZ], histr[SIZ]; /* was 1024 */
1092 char actual_range[SIZ];
1095 * Handle the "ALL" macro
1097 if (!strcasecmp(supplied_range, "ALL")) {
1098 safestrncpy(actual_range, "1:*", sizeof actual_range);
1101 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1105 * Clear out the IMAP_SELECTED flags for all messages.
1107 for (i = 0; i < IMAP->num_msgs; ++i) {
1108 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1112 * Now set it for all specified messages.
1114 num_sets = num_tokens(actual_range, ',');
1115 for (s=0; s<num_sets; ++s) {
1116 extract_token(setstr, actual_range, s, ',');
1118 extract_token(lostr, setstr, 0, ':');
1119 if (num_tokens(setstr, ':') >= 2) {
1120 extract_token(histr, setstr, 1, ':');
1121 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1124 strcpy(histr, lostr);
1129 /* Loop through the array, flipping bits where appropriate */
1130 for (i = 1; i <= IMAP->num_msgs; ++i) {
1131 if (is_uid) { /* fetch by sequence number */
1132 if ( (IMAP->msgids[i-1]>=lo)
1133 && (IMAP->msgids[i-1]<=hi)) {
1135 IMAP->flags[i-1] | IMAP_SELECTED;
1138 else { /* fetch by uid */
1139 if ( (i>=lo) && (i<=hi)) {
1141 IMAP->flags[i-1] | IMAP_SELECTED;
1152 * This function is called by the main command loop.
1154 void imap_fetch(int num_parms, char *parms[]) {
1155 char items[SIZ]; /* was 1024 */
1156 char *itemlist[SIZ];
1160 if (num_parms < 4) {
1161 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1165 imap_pick_range(parms[2], 0);
1168 for (i=3; i<num_parms; ++i) {
1169 strcat(items, parms[i]);
1170 if (i < (num_parms-1)) strcat(items, " ");
1173 num_items = imap_extract_data_items(itemlist, items);
1174 if (num_items < 1) {
1175 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1179 imap_do_fetch(num_items, itemlist);
1180 cprintf("%s OK FETCH completed\r\n", parms[0]);
1184 * This function is called by the main command loop.
1186 void imap_uidfetch(int num_parms, char *parms[]) {
1187 char items[SIZ]; /* was 1024 */
1188 char *itemlist[SIZ];
1191 int have_uid_item = 0;
1193 if (num_parms < 5) {
1194 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1198 imap_pick_range(parms[3], 1);
1201 for (i=4; i<num_parms; ++i) {
1202 strcat(items, parms[i]);
1203 if (i < (num_parms-1)) strcat(items, " ");
1206 num_items = imap_extract_data_items(itemlist, items);
1207 if (num_items < 1) {
1208 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1212 /* If the "UID" item was not included, we include it implicitly
1213 * because this is a UID FETCH command
1215 for (i=0; i<num_items; ++i) {
1216 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1218 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1220 imap_do_fetch(num_items, itemlist);
1221 cprintf("%s OK UID FETCH completed\r\n", parms[0]);