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() ...
68 void imap_fetch_uid(int seq) {
69 cprintf("UID %ld", IMAP->msgids[seq-1]);
72 void imap_fetch_flags(int seq) {
73 int num_flags_printed = 0;
75 if (IMAP->flags[seq] & IMAP_DELETED) {
76 if (num_flags_printed > 0) cprintf(" ");
80 if (IMAP->flags[seq] & IMAP_SEEN) {
81 if (num_flags_printed > 0) cprintf(" ");
85 if (IMAP->flags[seq] & IMAP_ANSWERED) {
86 if (num_flags_printed > 0) cprintf(" ");
87 cprintf("\\Answered");
93 void imap_fetch_internaldate(struct CtdlMessage *msg) {
97 if (msg->cm_fields['T'] != NULL) {
98 msgdate = atol(msg->cm_fields['T']);
101 msgdate = time(NULL);
104 datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
105 cprintf("INTERNALDATE \"%s\"", buf);
110 * Fetch RFC822-formatted messages.
112 * 'whichfmt' should be set to one of:
113 * "RFC822" entire message
114 * "RFC822.HEADER" headers only (with trailing blank line)
115 * "RFC822.SIZE" size of translated message
116 * "RFC822.TEXT" body only (without leading blank line)
118 void imap_fetch_rfc822(int msgnum, char *whichfmt, struct CtdlMessage *msg) {
121 long headers_size, text_size, total_size;
122 long bytes_remaining = 0;
128 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n",
134 * Load the message into a temp file for translation
137 CtdlRedirectOutput(tmp, -1);
138 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
140 CtdlRedirectOutput(NULL, -1);
141 if (!is_valid_message(msg)) {
142 lprintf(CTDL_ERR, "WARNING: output clobbered the message!\n");
146 * Now figure out where the headers/text break is. IMAP considers the
147 * intervening blank line to be part of the headers, not the text.
152 ptr = fgets(buf, sizeof buf, tmp);
155 if (strlen(buf) == 0) {
156 headers_size = ftell(tmp);
159 } while ( (headers_size == 0L) && (ptr != NULL) );
160 fseek(tmp, 0L, SEEK_END);
161 total_size = ftell(tmp);
162 text_size = total_size - headers_size;
164 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
165 cprintf("RFC822.SIZE %ld", total_size);
170 else if (!strcasecmp(whichfmt, "RFC822")) {
171 bytes_remaining = total_size;
175 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
176 bytes_remaining = headers_size;
180 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
181 bytes_remaining = text_size;
182 fseek(tmp, headers_size, SEEK_SET);
185 cprintf("%s {%ld}\r\n", whichfmt, bytes_remaining);
186 blocksize = sizeof(buf);
187 while (bytes_remaining > 0L) {
188 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
189 fread(buf, blocksize, 1, tmp);
190 client_write(buf, blocksize);
191 bytes_remaining = bytes_remaining - blocksize;
200 * Load a specific part of a message into the temp file to be output to a
201 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
202 * but we still can't handle "2.HEADER" (which might not be a problem, because
203 * we currently don't have the ability to break out nested RFC822's anyway).
205 * Note: mime_parser() was called with dont_decode set to 1, so we have the
206 * luxury of simply spewing without having to re-encode.
208 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
209 void *content, char *cbtype, size_t length, char *encoding,
212 struct imap_fetch_part *imfp;
215 imfp = (struct imap_fetch_part *)cbuserdata;
217 if (!strcasecmp(partnum, imfp->desired_section)) {
218 fwrite(content, length, 1, imfp->output_fp);
221 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
223 if (!strcasecmp(imfp->desired_section, mbuf2)) {
224 fprintf(imfp->output_fp, "Content-type: %s", cbtype);
225 if (strlen(name) > 0)
226 fprintf(imfp->output_fp, "; name=\"%s\"", name);
227 fprintf(imfp->output_fp, "\r\n");
228 if (strlen(encoding) > 0)
229 fprintf(imfp->output_fp,
230 "Content-Transfer-Encoding: %s\r\n", encoding);
231 if (strlen(encoding) > 0) {
232 fprintf(imfp->output_fp, "Content-Disposition: %s",
234 if (strlen(filename) > 0) {
235 fprintf(imfp->output_fp, "; filename=\"%s\"",
238 fprintf(imfp->output_fp, "\r\n");
240 fprintf(imfp->output_fp, "Content-Length: %ld\r\n", (long)length);
241 fprintf(imfp->output_fp, "\r\n");
249 * Called by imap_fetch_envelope() to output the "From" field.
250 * This is in its own function because its logic is kind of complex. We
251 * really need to make this suck less.
253 void imap_output_envelope_from(struct CtdlMessage *msg) {
254 char user[1024], node[1024], name[1024];
256 /* For anonymous messages, it's so easy! */
257 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
258 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
261 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
262 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
266 /* For everything else, we do stuff. */
267 cprintf("(("); /* open double-parens */
268 imap_strout(msg->cm_fields['A']); /* personal name */
269 cprintf(" NIL "); /* source route (not used) */
272 if (msg->cm_fields['F'] != NULL) {
273 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
274 imap_strout(user); /* mailbox name (user id) */
276 if (!strcasecmp(node, config.c_nodename)) {
277 imap_strout(config.c_fqdn);
280 imap_strout(node); /* host name */
284 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
286 imap_strout(msg->cm_fields['N']); /* host name */
289 cprintf(")) "); /* close double-parens */
295 * Output an envelope address (or set of addresses) in the official,
296 * Crispin-approved braindead format. (Note that we can't use this for
297 * the "From" address because its data may come from a number of different
298 * fields. But we can use it for "To" and possibly others.
300 void imap_output_envelope_addr(char *addr) {
301 char individual_addr[SIZ];
313 if (strlen(addr) == 0) {
320 /* How many addresses are listed here? */
321 num_addrs = num_tokens(addr, ',');
323 /* Output them one by one. */
324 for (i=0; i<num_addrs; ++i) {
325 extract_token(individual_addr, addr, i, ',');
326 striplt(individual_addr);
327 process_rfc822_addr(individual_addr, user, node, name);
335 if (i < (num_addrs-1)) cprintf(" ");
343 * Implements the ENVELOPE fetch item
345 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
346 * so we don't have to check for that condition like we do elsewhere.
348 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
349 char datestringbuf[SIZ];
351 char *fieldptr = NULL;
353 /* Parse the message date into an IMAP-format date string */
354 if (msg->cm_fields['T'] != NULL) {
355 msgdate = atol(msg->cm_fields['T']);
358 msgdate = time(NULL);
360 datestring(datestringbuf, sizeof datestringbuf,
361 msgdate, DATESTRING_IMAP);
363 /* Now start spewing data fields. The order is important, as it is
364 * defined by the protocol specification. Nonexistent fields must
365 * be output as NIL, existent fields must be quoted or literalled.
366 * The imap_strout() function conveniently does all this for us.
368 cprintf("ENVELOPE (");
371 imap_strout(datestringbuf);
375 imap_strout(msg->cm_fields['U']);
379 imap_output_envelope_from(msg);
381 /* Sender (default to same as 'From' if not present) */
382 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
383 if (fieldptr != NULL) {
384 imap_output_envelope_addr(fieldptr);
388 imap_output_envelope_from(msg);
392 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
393 if (fieldptr != NULL) {
394 imap_output_envelope_addr(fieldptr);
398 imap_output_envelope_from(msg);
402 imap_output_envelope_addr(msg->cm_fields['R']);
405 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
406 imap_output_envelope_addr(fieldptr);
407 if (fieldptr != NULL) phree(fieldptr);
410 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
411 imap_output_envelope_addr(fieldptr);
412 if (fieldptr != NULL) phree(fieldptr);
415 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
416 imap_strout(fieldptr);
418 if (fieldptr != NULL) phree(fieldptr);
421 imap_strout(msg->cm_fields['I']);
428 * Strip any non header information out of a chunk of RFC822 data on disk,
429 * then boil it down to just the fields we want.
431 void imap_strip_headers(FILE *fp, char *section) {
433 char *which_fields = NULL;
434 int doing_headers = 0;
439 char *boiled_headers = NULL;
441 int done_headers = 0;
443 which_fields = strdoop(section);
445 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
447 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
450 for (i=0; i<strlen(which_fields); ++i) {
451 if (which_fields[i]=='(')
452 strcpy(which_fields, &which_fields[i+1]);
454 for (i=0; i<strlen(which_fields); ++i) {
455 if (which_fields[i]==')')
458 num_parms = imap_parameterize(parms, which_fields);
460 fseek(fp, 0L, SEEK_END);
461 boiled_headers = mallok((size_t)(ftell(fp) + 256L));
462 strcpy(boiled_headers, "");
466 while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
467 if (!isspace(buf[0])) {
469 if (doing_headers == 0) ok = 1;
471 if (headers_not) ok = 1;
473 for (i=0; i<num_parms; ++i) {
474 if ( (!strncasecmp(buf, parms[i],
475 strlen(parms[i]))) &&
476 (buf[strlen(parms[i])]==':') ) {
477 if (headers_not) ok = 0;
485 strcat(boiled_headers, buf);
488 if (strlen(buf) == 0) done_headers = 1;
489 if (buf[0]=='\r') done_headers = 1;
490 if (buf[0]=='\n') done_headers = 1;
493 strcat(boiled_headers, "\r\n");
495 /* Now write it back */
497 fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
499 ftruncate(fileno(fp), ftell(fp));
503 phree(boiled_headers);
508 * Implements the BODY and BODY.PEEK fetch items
510 void imap_fetch_body(long msgnum, char *item, int is_peek,
511 struct CtdlMessage *msg) {
518 long bytes_remaining = 0;
521 struct imap_fetch_part imfp;
523 /* extract section */
524 strcpy(section, item);
525 for (i=0; i<strlen(section); ++i) {
526 if (section[i]=='[') strcpy(section, §ion[i+1]);
528 for (i=0; i<strlen(section); ++i) {
529 if (section[i]==']') section[i] = 0;
531 lprintf(CTDL_DEBUG, "Section is %s\n", section);
533 /* extract partial */
534 strcpy(partial, item);
535 for (i=0; i<strlen(partial); ++i) {
536 if (partial[i]=='<') {
537 strcpy(partial, &partial[i+1]);
541 for (i=0; i<strlen(partial); ++i) {
542 if (partial[i]=='>') partial[i] = 0;
544 if (is_partial == 0) strcpy(partial, "");
545 if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial);
549 lprintf(CTDL_CRIT, "Cannot open temp file: %s\n", strerror(errno));
553 /* Now figure out what the client wants, and get it */
555 if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
556 CtdlRedirectOutput(tmp, -1);
557 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
559 CtdlRedirectOutput(NULL, -1);
562 else if (!strcmp(section, "")) {
563 CtdlRedirectOutput(tmp, -1);
564 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
566 CtdlRedirectOutput(NULL, -1);
570 * If the client asked for just headers, or just particular header
571 * fields, strip it down.
573 else if (!strncasecmp(section, "HEADER", 6)) {
574 CtdlRedirectOutput(tmp, -1);
575 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
577 CtdlRedirectOutput(NULL, -1);
578 imap_strip_headers(tmp, section);
582 * Strip it down if the client asked for everything _except_ headers.
584 else if (!strncasecmp(section, "TEXT", 4)) {
585 CtdlRedirectOutput(tmp, -1);
586 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
588 CtdlRedirectOutput(NULL, -1);
592 * Anything else must be a part specifier.
593 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
596 safestrncpy(imfp.desired_section, section,
597 sizeof(imfp.desired_section));
598 imfp.output_fp = tmp;
600 mime_parser(msg->cm_fields['M'], NULL,
601 *imap_load_part, NULL, NULL,
607 fseek(tmp, 0L, SEEK_END);
608 bytes_remaining = ftell(tmp);
610 if (is_partial == 0) {
612 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
615 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
616 if ((bytes_remaining - pstart) < pbytes) {
617 pbytes = bytes_remaining - pstart;
619 fseek(tmp, pstart, SEEK_SET);
620 bytes_remaining = pbytes;
621 cprintf("BODY[%s]<%ld> {%ld}\r\n",
622 section, pstart, bytes_remaining);
625 blocksize = sizeof(buf);
626 while (bytes_remaining > 0L) {
627 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
628 fread(buf, blocksize, 1, tmp);
629 client_write(buf, blocksize);
630 bytes_remaining = bytes_remaining - blocksize;
635 /* Mark this message as "seen" *unless* this is a "peek" operation */
637 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
642 * Called immediately before outputting a multipart bodystructure
644 void imap_fetch_bodystructure_pre(
645 char *name, char *filename, char *partnum, char *disp,
646 void *content, char *cbtype, size_t length, char *encoding,
656 * Called immediately after outputting a multipart bodystructure
658 void imap_fetch_bodystructure_post(
659 char *name, char *filename, char *partnum, char *disp,
660 void *content, char *cbtype, size_t length, char *encoding,
669 extract_token(subtype, cbtype, 1, '/');
670 imap_strout(subtype);
681 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
684 void imap_fetch_bodystructure_part(
685 char *name, char *filename, char *partnum, char *disp,
686 void *content, char *cbtype, size_t length, char *encoding,
691 int have_encoding = 0;
694 char cbmaintype[SIZ];
697 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
699 extract_token(cbmaintype, cbtype, 0, '/');
700 extract_token(cbsubtype, cbtype, 1, '/');
703 strcpy(cbmaintype, "TEXT");
704 strcpy(cbsubtype, "PLAIN");
708 imap_strout(cbmaintype);
710 imap_strout(cbsubtype);
713 cprintf("(\"CHARSET\" \"US-ASCII\"");
715 if (name != NULL) if (strlen(name)>0) {
716 cprintf(" \"NAME\" ");
722 cprintf("NIL "); /* Body ID */
723 cprintf("NIL "); /* Body description */
725 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
727 imap_strout(encoding);
734 /* The next field is the size of the part in bytes. */
735 cprintf("%ld ", (long)length); /* bytes */
737 /* The next field is the number of lines in the part, if and only
738 * if the part is TEXT. Crispin is a fscking idiot.
740 if (!strcasecmp(cbmaintype, "TEXT")) {
741 if (length) for (i=0; i<length; ++i) {
742 if (((char *)content)[i] == '\n') ++lines;
744 cprintf("%d ", lines);
747 /* More of Crispin being a fscking idiot */
748 if ((!strcasecmp(cbmaintype, "MESSAGE"))
749 && (!strcasecmp(cbsubtype, "RFC822"))) {
751 A body type of type MESSAGE and subtype RFC822
752 contains, immediately after the basic fields, the
753 envelope structure, body structure, and size in
754 text lines of the encapsulated message.
758 /* MD5 value of body part; we can get away with NIL'ing this */
765 else if (strlen(disp) == 0) {
771 if (filename != NULL) if (strlen(filename)>0) {
772 cprintf(" (\"FILENAME\" ");
773 imap_strout(filename);
779 /* Body language (not defined yet) */
786 * Spew the BODYSTRUCTURE data for a message. (Do you need a silencer if
787 * you're going to shoot a MIME? Do you need a reason to shoot Mark Crispin?
791 void imap_fetch_bodystructure (long msgnum, char *item,
792 struct CtdlMessage *msg) {
796 long start_of_body = 0L;
797 long body_bytes = 0L;
799 /* For non-RFC822 (ordinary Citadel) messages, this is short and
802 if (msg->cm_format_type != FMT_RFC822) {
804 /* *sigh* We have to RFC822-format the message just to be able
808 if (tmp == NULL) return;
809 CtdlRedirectOutput(tmp, -1);
810 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
811 CtdlRedirectOutput(NULL, -1);
814 while (fgets(buf, sizeof buf, tmp) != NULL) {
816 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
817 start_of_body = ftell(tmp);
820 body_bytes = ftell(tmp) - start_of_body;
823 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
824 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
825 "\"7BIT\" %ld %ld)", body_bytes, lines);
830 /* For messages already stored in RFC822 format, we have to parse. */
831 cprintf("BODYSTRUCTURE ");
832 mime_parser(msg->cm_fields['M'],
834 *imap_fetch_bodystructure_part, /* part */
835 *imap_fetch_bodystructure_pre, /* pre-multi */
836 *imap_fetch_bodystructure_post, /* post-multi */
838 1); /* don't decode -- we want it as-is */
847 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
848 * individual message, once it has been successfully loaded from disk.
850 void imap_do_fetch_msg(int seq, struct CtdlMessage *msg,
851 int num_items, char **itemlist) {
854 cprintf("* %d FETCH (", seq);
856 for (i=0; i<num_items; ++i) {
858 if (!strncasecmp(itemlist[i], "BODY[", 5)) {
859 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
862 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
863 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
866 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
867 imap_fetch_bodystructure(IMAP->msgids[seq-1],
870 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
871 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
873 else if (!strcasecmp(itemlist[i], "FLAGS")) {
874 imap_fetch_flags(seq-1);
876 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
877 imap_fetch_internaldate(msg);
879 else if (!strcasecmp(itemlist[i], "RFC822")) {
880 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
882 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
883 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
885 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
886 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
888 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
889 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
891 else if (!strcasecmp(itemlist[i], "UID")) {
895 if (i != num_items-1) cprintf(" ");
904 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
905 * validated and boiled down the request a bit.
907 void imap_do_fetch(int num_items, char **itemlist) {
909 struct CtdlMessage *msg;
911 if (IMAP->num_msgs > 0)
912 for (i = 0; i < IMAP->num_msgs; ++i)
913 if (IMAP->flags[i] & IMAP_SELECTED) {
914 msg = CtdlFetchMessage(IMAP->msgids[i]);
916 imap_do_fetch_msg(i+1, msg, num_items, itemlist);
917 CtdlFreeMessage(msg);
920 cprintf("* %d FETCH <internal error>\r\n", i+1);
928 * Back end for imap_handle_macros()
929 * Note that this function *only* looks at the beginning of the string. It
930 * is not a generic search-and-replace function.
932 void imap_macro_replace(char *str, char *find, char *replace) {
935 if (!strncasecmp(str, find, strlen(find))) {
936 if (str[strlen(find)]==' ') {
937 strcpy(holdbuf, &str[strlen(find)+1]);
938 strcpy(str, replace);
940 strcat(str, holdbuf);
942 if (str[strlen(find)]==0) {
943 strcpy(holdbuf, &str[strlen(find)+1]);
944 strcpy(str, replace);
952 * Handle macros embedded in FETCH data items.
953 * (What the heck are macros doing in a wire protocol? Are we trying to save
954 * the computer at the other end the trouble of typing a lot of characters?)
956 void imap_handle_macros(char *str) {
960 for (i=0; i<strlen(str); ++i) {
961 if (str[i]=='(') ++nest;
962 if (str[i]=='[') ++nest;
963 if (str[i]=='<') ++nest;
964 if (str[i]=='{') ++nest;
965 if (str[i]==')') --nest;
966 if (str[i]==']') --nest;
967 if (str[i]=='>') --nest;
968 if (str[i]=='}') --nest;
971 imap_macro_replace(&str[i],
973 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
975 imap_macro_replace(&str[i],
979 imap_macro_replace(&str[i],
981 "FLAGS INTERNALDATE RFC822.SIZE"
983 imap_macro_replace(&str[i],
985 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
993 * Break out the data items requested, possibly a parenthesized list.
994 * Returns the number of data items, or -1 if the list is invalid.
995 * NOTE: this function alters the string it is fed, and uses it as a buffer
996 * to hold the data for the pointers it returns.
998 int imap_extract_data_items(char **argv, char *items) {
1004 /* Convert all whitespace to ordinary space characters. */
1005 for (i=0; i<strlen(items); ++i) {
1006 if (isspace(items[i])) items[i]=' ';
1009 /* Strip leading and trailing whitespace, then strip leading and
1010 * trailing parentheses if it's a list
1013 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1014 items[strlen(items)-1] = 0;
1015 strcpy(items, &items[1]);
1019 /* Parse any macro data items */
1020 imap_handle_macros(items);
1023 * Now break out the data items. We throw in one trailing space in
1024 * order to avoid having to break out the last one manually.
1028 initial_len = strlen(items);
1029 for (i=0; i<initial_len; ++i) {
1030 if (items[i]=='(') ++nest;
1031 if (items[i]=='[') ++nest;
1032 if (items[i]=='<') ++nest;
1033 if (items[i]=='{') ++nest;
1034 if (items[i]==')') --nest;
1035 if (items[i]==']') --nest;
1036 if (items[i]=='>') --nest;
1037 if (items[i]=='}') --nest;
1039 if (nest <= 0) if (items[i]==' ') {
1041 argv[num_items++] = start;
1042 start = &items[i+1];
1052 * One particularly hideous aspect of IMAP is that we have to allow the client
1053 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1054 * handles this by setting the IMAP_SELECTED flag for each message specified in
1055 * the ranges/sets, then looping through the message array, outputting messages
1056 * with the flag set. We don't bother returning an error if an out-of-range
1057 * number is specified (we just return quietly) because any client braindead
1058 * enough to request a bogus message number isn't going to notice the
1059 * difference anyway.
1061 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1062 * message included in the specified range.
1064 * Set is_uid to 1 to fetch by UID instead of sequence number.
1066 void imap_pick_range(char *supplied_range, int is_uid) {
1070 char setstr[SIZ], lostr[SIZ], histr[SIZ]; /* was 1024 */
1072 char actual_range[SIZ];
1075 * Handle the "ALL" macro
1077 if (!strcasecmp(supplied_range, "ALL")) {
1078 safestrncpy(actual_range, "1:*", sizeof actual_range);
1081 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1085 * Clear out the IMAP_SELECTED flags for all messages.
1087 for (i = 0; i < IMAP->num_msgs; ++i) {
1088 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1092 * Now set it for all specified messages.
1094 num_sets = num_tokens(actual_range, ',');
1095 for (s=0; s<num_sets; ++s) {
1096 extract_token(setstr, actual_range, s, ',');
1098 extract_token(lostr, setstr, 0, ':');
1099 if (num_tokens(setstr, ':') >= 2) {
1100 extract_token(histr, setstr, 1, ':');
1101 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1104 strcpy(histr, lostr);
1109 /* Loop through the array, flipping bits where appropriate */
1110 for (i = 1; i <= IMAP->num_msgs; ++i) {
1111 if (is_uid) { /* fetch by sequence number */
1112 if ( (IMAP->msgids[i-1]>=lo)
1113 && (IMAP->msgids[i-1]<=hi)) {
1115 IMAP->flags[i-1] | IMAP_SELECTED;
1118 else { /* fetch by uid */
1119 if ( (i>=lo) && (i<=hi)) {
1121 IMAP->flags[i-1] | IMAP_SELECTED;
1132 * This function is called by the main command loop.
1134 void imap_fetch(int num_parms, char *parms[]) {
1135 char items[SIZ]; /* was 1024 */
1136 char *itemlist[SIZ];
1140 if (num_parms < 4) {
1141 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1145 imap_pick_range(parms[2], 0);
1148 for (i=3; i<num_parms; ++i) {
1149 strcat(items, parms[i]);
1150 if (i < (num_parms-1)) strcat(items, " ");
1153 num_items = imap_extract_data_items(itemlist, items);
1154 if (num_items < 1) {
1155 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1159 imap_do_fetch(num_items, itemlist);
1160 cprintf("%s OK FETCH completed\r\n", parms[0]);
1164 * This function is called by the main command loop.
1166 void imap_uidfetch(int num_parms, char *parms[]) {
1167 char items[SIZ]; /* was 1024 */
1168 char *itemlist[SIZ];
1171 int have_uid_item = 0;
1173 if (num_parms < 5) {
1174 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1178 imap_pick_range(parms[3], 1);
1181 for (i=4; i<num_parms; ++i) {
1182 strcat(items, parms[i]);
1183 if (i < (num_parms-1)) strcat(items, " ");
1186 num_items = imap_extract_data_items(itemlist, items);
1187 if (num_items < 1) {
1188 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1192 /* If the "UID" item was not included, we include it implicitly
1193 * because this is a UID FETCH command
1195 for (i=0; i<num_items; ++i) {
1196 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1198 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1200 imap_do_fetch(num_items, itemlist);
1201 cprintf("%s OK UID FETCH completed\r\n", parms[0]);