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(1, "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(1, "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 */
294 * Implements the ENVELOPE fetch item
296 * FIXME ... we only output some of the fields right now. Definitely need
297 * to do all of them. Accurately, too.
299 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
300 * so we don't have to check for that condition like we do elsewhere.
302 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
303 char datestringbuf[SIZ];
305 char *fieldptr = NULL;
307 /* Parse the message date into an IMAP-format date string */
308 if (msg->cm_fields['T'] != NULL) {
309 msgdate = atol(msg->cm_fields['T']);
312 msgdate = time(NULL);
314 datestring(datestringbuf, sizeof datestringbuf,
315 msgdate, DATESTRING_IMAP);
317 /* Now start spewing data fields. The order is important, as it is
318 * defined by the protocol specification. Nonexistent fields must
319 * be output as NIL, existent fields must be quoted or literalled.
320 * The imap_strout() function conveniently does all this for us.
322 cprintf("ENVELOPE (");
325 imap_strout(datestringbuf);
329 imap_strout(msg->cm_fields['U']);
333 imap_output_envelope_from(msg);
340 imap_output_envelope_from(msg);
348 imap_output_envelope_from(msg);
351 cprintf("NIL "); /* to */
353 cprintf("NIL "); /* cc */
355 cprintf("NIL "); /* bcc */
358 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
359 imap_strout(fieldptr);
361 if (fieldptr != NULL) phree(fieldptr);
364 imap_strout(msg->cm_fields['I']);
371 * Strip any non header information out of a chunk of RFC822 data on disk,
372 * then boil it down to just the fields we want.
374 void imap_strip_headers(FILE *fp, char *section) {
376 char *which_fields = NULL;
377 int doing_headers = 0;
382 char *boiled_headers = NULL;
384 int done_headers = 0;
386 which_fields = strdoop(section);
388 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
390 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
393 for (i=0; i<strlen(which_fields); ++i) {
394 if (which_fields[i]=='(')
395 strcpy(which_fields, &which_fields[i+1]);
397 for (i=0; i<strlen(which_fields); ++i) {
398 if (which_fields[i]==')')
401 num_parms = imap_parameterize(parms, which_fields);
403 fseek(fp, 0L, SEEK_END);
404 boiled_headers = mallok((size_t)(ftell(fp) + 256L));
405 strcpy(boiled_headers, "");
409 while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
410 if (!isspace(buf[0])) {
412 if (doing_headers == 0) ok = 1;
414 if (headers_not) ok = 1;
416 for (i=0; i<num_parms; ++i) {
417 if ( (!strncasecmp(buf, parms[i],
418 strlen(parms[i]))) &&
419 (buf[strlen(parms[i])]==':') ) {
420 if (headers_not) ok = 0;
428 strcat(boiled_headers, buf);
431 if (strlen(buf) == 0) done_headers = 1;
432 if (buf[0]=='\r') done_headers = 1;
433 if (buf[0]=='\n') done_headers = 1;
436 strcat(boiled_headers, "\r\n");
438 /* Now write it back */
440 fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
442 ftruncate(fileno(fp), ftell(fp));
446 phree(boiled_headers);
451 * Implements the BODY and BODY.PEEK fetch items
453 void imap_fetch_body(long msgnum, char *item, int is_peek,
454 struct CtdlMessage *msg) {
461 long bytes_remaining = 0;
464 struct imap_fetch_part imfp;
466 /* extract section */
467 strcpy(section, item);
468 for (i=0; i<strlen(section); ++i) {
469 if (section[i]=='[') strcpy(section, §ion[i+1]);
471 for (i=0; i<strlen(section); ++i) {
472 if (section[i]==']') section[i] = 0;
474 lprintf(9, "Section is %s\n", section);
476 /* extract partial */
477 strcpy(partial, item);
478 for (i=0; i<strlen(partial); ++i) {
479 if (partial[i]=='<') {
480 strcpy(partial, &partial[i+1]);
484 for (i=0; i<strlen(partial); ++i) {
485 if (partial[i]=='>') partial[i] = 0;
487 if (is_partial == 0) strcpy(partial, "");
488 if (strlen(partial) > 0) lprintf(9, "Partial is %s\n", partial);
492 lprintf(1, "Cannot open temp file: %s\n", strerror(errno));
496 /* Now figure out what the client wants, and get it */
498 if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
499 CtdlRedirectOutput(tmp, -1);
500 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
502 CtdlRedirectOutput(NULL, -1);
505 else if (!strcmp(section, "")) {
506 CtdlRedirectOutput(tmp, -1);
507 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
509 CtdlRedirectOutput(NULL, -1);
513 * If the client asked for just headers, or just particular header
514 * fields, strip it down.
516 else if (!strncasecmp(section, "HEADER", 6)) {
517 CtdlRedirectOutput(tmp, -1);
518 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
520 CtdlRedirectOutput(NULL, -1);
521 imap_strip_headers(tmp, section);
525 * Strip it down if the client asked for everything _except_ headers.
527 else if (!strncasecmp(section, "TEXT", 4)) {
528 CtdlRedirectOutput(tmp, -1);
529 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
531 CtdlRedirectOutput(NULL, -1);
535 * Anything else must be a part specifier.
536 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
539 safestrncpy(imfp.desired_section, section,
540 sizeof(imfp.desired_section));
541 imfp.output_fp = tmp;
543 mime_parser(msg->cm_fields['M'], NULL,
544 *imap_load_part, NULL, NULL,
550 fseek(tmp, 0L, SEEK_END);
551 bytes_remaining = ftell(tmp);
553 if (is_partial == 0) {
555 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
558 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
559 if ((bytes_remaining - pstart) < pbytes) {
560 pbytes = bytes_remaining - pstart;
562 fseek(tmp, pstart, SEEK_SET);
563 bytes_remaining = pbytes;
564 cprintf("BODY[%s]<%ld> {%ld}\r\n",
565 section, pstart, bytes_remaining);
568 blocksize = sizeof(buf);
569 while (bytes_remaining > 0L) {
570 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
571 fread(buf, blocksize, 1, tmp);
572 client_write(buf, blocksize);
573 bytes_remaining = bytes_remaining - blocksize;
578 /* Mark this message as "seen" *unless* this is a "peek" operation */
580 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
585 * Called immediately before outputting a multipart bodystructure
587 void imap_fetch_bodystructure_pre(
588 char *name, char *filename, char *partnum, char *disp,
589 void *content, char *cbtype, size_t length, char *encoding,
599 * Called immediately after outputting a multipart bodystructure
601 void imap_fetch_bodystructure_post(
602 char *name, char *filename, char *partnum, char *disp,
603 void *content, char *cbtype, size_t length, char *encoding,
612 extract_token(subtype, cbtype, 1, '/');
613 imap_strout(subtype);
624 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
627 void imap_fetch_bodystructure_part(
628 char *name, char *filename, char *partnum, char *disp,
629 void *content, char *cbtype, size_t length, char *encoding,
634 int have_encoding = 0;
637 char cbmaintype[SIZ];
640 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
642 extract_token(cbmaintype, cbtype, 0, '/');
643 extract_token(cbsubtype, cbtype, 1, '/');
646 strcpy(cbmaintype, "TEXT");
647 strcpy(cbsubtype, "PLAIN");
651 imap_strout(cbmaintype);
653 imap_strout(cbsubtype);
656 cprintf("(\"CHARSET\" \"US-ASCII\"");
658 if (name != NULL) if (strlen(name)>0) {
659 cprintf(" \"NAME\" ");
665 cprintf("NIL "); /* Body ID */
666 cprintf("NIL "); /* Body description */
668 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
670 imap_strout(encoding);
677 /* The next field is the size of the part in bytes. */
678 cprintf("%ld ", (long)length); /* bytes */
680 /* The next field is the number of lines in the part, if and only
681 * if the part is TEXT. Crispin is a fscking idiot.
683 if (!strcasecmp(cbmaintype, "TEXT")) {
684 if (length) for (i=0; i<length; ++i) {
685 if (((char *)content)[i] == '\n') ++lines;
687 cprintf("%d ", lines);
690 /* More of Crispin being a fscking idiot */
691 if ((!strcasecmp(cbmaintype, "MESSAGE"))
692 && (!strcasecmp(cbsubtype, "RFC822"))) {
694 A body type of type MESSAGE and subtype RFC822
695 contains, immediately after the basic fields, the
696 envelope structure, body structure, and size in
697 text lines of the encapsulated message.
701 /* MD5 value of body part; we can get away with NIL'ing this */
708 else if (strlen(disp) == 0) {
714 if (filename != NULL) if (strlen(filename)>0) {
715 cprintf(" (\"FILENAME\" ");
716 imap_strout(filename);
722 /* Body language (not defined yet) */
729 * Spew the BODYSTRUCTURE data for a message. (Do you need a silencer if
730 * you're going to shoot a MIME? Do you need a reason to shoot Mark Crispin?
734 void imap_fetch_bodystructure (long msgnum, char *item,
735 struct CtdlMessage *msg) {
739 long start_of_body = 0L;
740 long body_bytes = 0L;
742 /* For non-RFC822 (ordinary Citadel) messages, this is short and
745 if (msg->cm_format_type != FMT_RFC822) {
747 /* *sigh* We have to RFC822-format the message just to be able
751 if (tmp == NULL) return;
752 CtdlRedirectOutput(tmp, -1);
753 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
754 CtdlRedirectOutput(NULL, -1);
757 while (fgets(buf, sizeof buf, tmp) != NULL) {
759 if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
760 start_of_body = ftell(tmp);
763 body_bytes = ftell(tmp) - start_of_body;
766 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
767 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
768 "\"7BIT\" %ld %ld)", body_bytes, lines);
773 /* For messages already stored in RFC822 format, we have to parse. */
774 cprintf("BODYSTRUCTURE ");
775 mime_parser(msg->cm_fields['M'],
777 *imap_fetch_bodystructure_part, /* part */
778 *imap_fetch_bodystructure_pre, /* pre-multi */
779 *imap_fetch_bodystructure_post, /* post-multi */
781 1); /* don't decode -- we want it as-is */
790 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
791 * individual message, once it has been successfully loaded from disk.
793 void imap_do_fetch_msg(int seq, struct CtdlMessage *msg,
794 int num_items, char **itemlist) {
797 cprintf("* %d FETCH (", seq);
799 for (i=0; i<num_items; ++i) {
801 if (!strncasecmp(itemlist[i], "BODY[", 5)) {
802 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
805 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
806 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
809 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
810 imap_fetch_bodystructure(IMAP->msgids[seq-1],
813 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
814 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
816 else if (!strcasecmp(itemlist[i], "FLAGS")) {
817 imap_fetch_flags(seq-1);
819 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
820 imap_fetch_internaldate(msg);
822 else if (!strcasecmp(itemlist[i], "RFC822")) {
823 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
825 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
826 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
828 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
829 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
831 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
832 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
834 else if (!strcasecmp(itemlist[i], "UID")) {
838 if (i != num_items-1) cprintf(" ");
847 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
848 * validated and boiled down the request a bit.
850 void imap_do_fetch(int num_items, char **itemlist) {
852 struct CtdlMessage *msg;
854 if (IMAP->num_msgs > 0)
855 for (i = 0; i < IMAP->num_msgs; ++i)
856 if (IMAP->flags[i] & IMAP_SELECTED) {
857 msg = CtdlFetchMessage(IMAP->msgids[i]);
859 imap_do_fetch_msg(i+1, msg, num_items, itemlist);
860 CtdlFreeMessage(msg);
863 cprintf("* %d FETCH <internal error>\r\n", i+1);
871 * Back end for imap_handle_macros()
872 * Note that this function *only* looks at the beginning of the string. It
873 * is not a generic search-and-replace function.
875 void imap_macro_replace(char *str, char *find, char *replace) {
878 if (!strncasecmp(str, find, strlen(find))) {
879 if (str[strlen(find)]==' ') {
880 strcpy(holdbuf, &str[strlen(find)+1]);
881 strcpy(str, replace);
883 strcat(str, holdbuf);
885 if (str[strlen(find)]==0) {
886 strcpy(holdbuf, &str[strlen(find)+1]);
887 strcpy(str, replace);
895 * Handle macros embedded in FETCH data items.
896 * (What the heck are macros doing in a wire protocol? Are we trying to save
897 * the computer at the other end the trouble of typing a lot of characters?)
899 void imap_handle_macros(char *str) {
903 for (i=0; i<strlen(str); ++i) {
904 if (str[i]=='(') ++nest;
905 if (str[i]=='[') ++nest;
906 if (str[i]=='<') ++nest;
907 if (str[i]=='{') ++nest;
908 if (str[i]==')') --nest;
909 if (str[i]==']') --nest;
910 if (str[i]=='>') --nest;
911 if (str[i]=='}') --nest;
914 imap_macro_replace(&str[i],
916 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
918 imap_macro_replace(&str[i],
922 imap_macro_replace(&str[i],
924 "FLAGS INTERNALDATE RFC822.SIZE"
926 imap_macro_replace(&str[i],
928 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
936 * Break out the data items requested, possibly a parenthesized list.
937 * Returns the number of data items, or -1 if the list is invalid.
938 * NOTE: this function alters the string it is fed, and uses it as a buffer
939 * to hold the data for the pointers it returns.
941 int imap_extract_data_items(char **argv, char *items) {
947 /* Convert all whitespace to ordinary space characters. */
948 for (i=0; i<strlen(items); ++i) {
949 if (isspace(items[i])) items[i]=' ';
952 /* Strip leading and trailing whitespace, then strip leading and
953 * trailing parentheses if it's a list
956 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
957 items[strlen(items)-1] = 0;
958 strcpy(items, &items[1]);
962 /* Parse any macro data items */
963 imap_handle_macros(items);
966 * Now break out the data items. We throw in one trailing space in
967 * order to avoid having to break out the last one manually.
971 initial_len = strlen(items);
972 for (i=0; i<initial_len; ++i) {
973 if (items[i]=='(') ++nest;
974 if (items[i]=='[') ++nest;
975 if (items[i]=='<') ++nest;
976 if (items[i]=='{') ++nest;
977 if (items[i]==')') --nest;
978 if (items[i]==']') --nest;
979 if (items[i]=='>') --nest;
980 if (items[i]=='}') --nest;
982 if (nest <= 0) if (items[i]==' ') {
984 argv[num_items++] = start;
995 * One particularly hideous aspect of IMAP is that we have to allow the client
996 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
997 * handles this by setting the IMAP_SELECTED flag for each message specified in
998 * the ranges/sets, then looping through the message array, outputting messages
999 * with the flag set. We don't bother returning an error if an out-of-range
1000 * number is specified (we just return quietly) because any client braindead
1001 * enough to request a bogus message number isn't going to notice the
1002 * difference anyway.
1004 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1005 * message included in the specified range.
1007 * Set is_uid to 1 to fetch by UID instead of sequence number.
1009 void imap_pick_range(char *supplied_range, int is_uid) {
1013 char setstr[SIZ], lostr[SIZ], histr[SIZ]; /* was 1024 */
1015 char actual_range[SIZ];
1018 * Handle the "ALL" macro
1020 if (!strcasecmp(supplied_range, "ALL")) {
1021 safestrncpy(actual_range, "1:*", sizeof actual_range);
1024 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1028 * Clear out the IMAP_SELECTED flags for all messages.
1030 for (i = 0; i < IMAP->num_msgs; ++i) {
1031 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1035 * Now set it for all specified messages.
1037 num_sets = num_tokens(actual_range, ',');
1038 for (s=0; s<num_sets; ++s) {
1039 extract_token(setstr, actual_range, s, ',');
1041 extract_token(lostr, setstr, 0, ':');
1042 if (num_tokens(setstr, ':') >= 2) {
1043 extract_token(histr, setstr, 1, ':');
1044 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1047 strcpy(histr, lostr);
1052 /* Loop through the array, flipping bits where appropriate */
1053 for (i = 1; i <= IMAP->num_msgs; ++i) {
1054 if (is_uid) { /* fetch by sequence number */
1055 if ( (IMAP->msgids[i-1]>=lo)
1056 && (IMAP->msgids[i-1]<=hi)) {
1058 IMAP->flags[i-1] | IMAP_SELECTED;
1061 else { /* fetch by uid */
1062 if ( (i>=lo) && (i<=hi)) {
1064 IMAP->flags[i-1] | IMAP_SELECTED;
1075 * This function is called by the main command loop.
1077 void imap_fetch(int num_parms, char *parms[]) {
1078 char items[SIZ]; /* was 1024 */
1079 char *itemlist[SIZ];
1083 if (num_parms < 4) {
1084 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1088 imap_pick_range(parms[2], 0);
1091 for (i=3; i<num_parms; ++i) {
1092 strcat(items, parms[i]);
1093 if (i < (num_parms-1)) strcat(items, " ");
1096 num_items = imap_extract_data_items(itemlist, items);
1097 if (num_items < 1) {
1098 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1102 imap_do_fetch(num_items, itemlist);
1103 cprintf("%s OK FETCH completed\r\n", parms[0]);
1107 * This function is called by the main command loop.
1109 void imap_uidfetch(int num_parms, char *parms[]) {
1110 char items[SIZ]; /* was 1024 */
1111 char *itemlist[SIZ];
1114 int have_uid_item = 0;
1116 if (num_parms < 5) {
1117 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1121 imap_pick_range(parms[3], 1);
1124 for (i=4; i<num_parms; ++i) {
1125 strcat(items, parms[i]);
1126 if (i < (num_parms-1)) strcat(items, " ");
1129 num_items = imap_extract_data_items(itemlist, items);
1130 if (num_items < 1) {
1131 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1135 /* If the "UID" item was not included, we include it implicitly
1136 * because this is a UID FETCH command
1138 for (i=0; i<num_items; ++i) {
1139 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1141 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1143 imap_do_fetch(num_items, itemlist);
1144 cprintf("%s OK UID FETCH completed\r\n", parms[0]);