4 * Implements the FETCH command in IMAP.
5 * This is a good example of the protocol's gratuitous complexity.
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"
58 * Individual field functions for imap_do_fetch_msg() ...
61 void imap_fetch_uid(int seq) {
62 cprintf("UID %ld", IMAP->msgids[seq-1]);
65 void imap_fetch_flags(int seq) {
66 int num_flags_printed = 0;
68 if (IMAP->flags[seq] & IMAP_DELETED) {
69 if (num_flags_printed > 0) cprintf(" ");
73 if (IMAP->flags[seq] & IMAP_SEEN) {
74 if (num_flags_printed > 0) cprintf(" ");
78 if (IMAP->flags[seq] & IMAP_ANSWERED) {
79 if (num_flags_printed > 0) cprintf(" ");
80 cprintf("\\Answered");
83 if (IMAP->flags[seq] & IMAP_RECENT) {
84 if (num_flags_printed > 0) cprintf(" ");
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 size_t headers_size, text_size, total_size;
120 size_t bytes_to_send = 0;
122 int need_to_rewrite_metadata = 0;
124 /* If this is an RFC822.SIZE fetch, first look in the message's
125 * metadata record to see if we've saved that information.
127 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
128 GetMetaData(&smi, msgnum);
129 if (smi.meta_rfc822_length > 0L) {
130 cprintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
133 need_to_rewrite_metadata = 1;
136 /* Cache the most recent RFC822 FETCH because some clients like to
137 * fetch in pieces, and we don't want to have to go back to the
138 * message store for each piece.
140 if ((IMAP->cached_rfc822_data != NULL)
141 && (IMAP->cached_rfc822_msgnum == msgnum)) {
144 else if (IMAP->cached_rfc822_data != NULL) {
145 /* Some other message is cached -- free it */
146 free(IMAP->cached_rfc822_data);
147 IMAP->cached_rfc822_data = NULL;
148 IMAP->cached_rfc822_msgnum = (-1);
149 IMAP->cached_rfc822_len = 0;
152 /* At this point, we now can fetch and convert the message iff it's not
153 * the one we had cached.
155 if (IMAP->cached_rfc822_data == NULL) {
157 * Load the message into memory for translation & measurement
159 CC->redirect_buffer = malloc(SIZ);
160 CC->redirect_len = 0;
161 CC->redirect_alloc = SIZ;
162 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
163 IMAP->cached_rfc822_data = CC->redirect_buffer;
164 IMAP->cached_rfc822_len = CC->redirect_len;
165 IMAP->cached_rfc822_msgnum = msgnum;
166 CC->redirect_buffer = NULL;
167 CC->redirect_len = 0;
168 CC->redirect_alloc = 0;
169 if (need_to_rewrite_metadata) {
170 smi.meta_rfc822_length = (long)IMAP->cached_rfc822_len;
176 * Now figure out where the headers/text break is. IMAP considers the
177 * intervening blank line to be part of the headers, not the text.
183 ptr = IMAP->cached_rfc822_data;
185 ptr = memreadline(ptr, buf, sizeof buf);
188 if (strlen(buf) == 0) {
189 headers_size = ptr - IMAP->cached_rfc822_data;
192 } while ( (headers_size == 0) && (*ptr != 0) );
194 total_size = IMAP->cached_rfc822_len;
195 text_size = total_size - headers_size;
197 lprintf(CTDL_DEBUG, "RFC822: headers=%d, text=%d, total=%d\n",
198 headers_size, text_size, total_size);
200 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
201 cprintf("RFC822.SIZE %d", total_size);
205 else if (!strcasecmp(whichfmt, "RFC822")) {
206 ptr = IMAP->cached_rfc822_data;
207 bytes_to_send = total_size;
210 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
211 ptr = IMAP->cached_rfc822_data;
212 bytes_to_send = headers_size;
215 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
216 ptr = &IMAP->cached_rfc822_data[headers_size];
217 bytes_to_send = text_size;
220 cprintf("%s {%d}\r\n", whichfmt, bytes_to_send);
221 client_write(ptr, bytes_to_send);
227 * Load a specific part of a message into the temp file to be output to a
228 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
229 * but we still can't handle "2.HEADER" (which might not be a problem, because
230 * we currently don't have the ability to break out nested RFC822's anyway).
232 * Note: mime_parser() was called with dont_decode set to 1, so we have the
233 * luxury of simply spewing without having to re-encode.
235 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
236 void *content, char *cbtype, size_t length, char *encoding,
240 char *desired_section;
242 desired_section = (char *)cbuserdata;
244 if (!strcasecmp(partnum, desired_section)) {
245 client_write(content, length);
248 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
250 if (!strcasecmp(desired_section, mbuf2)) {
251 cprintf("Content-type: %s", cbtype);
252 if (strlen(name) > 0)
253 cprintf("; name=\"%s\"", name);
255 if (strlen(encoding) > 0)
256 cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
257 if (strlen(encoding) > 0) {
258 cprintf("Content-Disposition: %s", disp);
259 if (strlen(filename) > 0) {
260 cprintf("; filename=\"%s\"", filename);
264 cprintf("Content-Length: %ld\r\n", (long)length);
273 * Called by imap_fetch_envelope() to output the "From" field.
274 * This is in its own function because its logic is kind of complex. We
275 * really need to make this suck less.
277 void imap_output_envelope_from(struct CtdlMessage *msg) {
278 char user[SIZ], node[SIZ], name[SIZ];
280 /* For anonymous messages, it's so easy! */
281 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
282 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
285 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
286 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
290 /* For everything else, we do stuff. */
291 cprintf("(("); /* open double-parens */
292 imap_strout(msg->cm_fields['A']); /* personal name */
293 cprintf(" NIL "); /* source route (not used) */
296 if (msg->cm_fields['F'] != NULL) {
297 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
298 imap_strout(user); /* mailbox name (user id) */
300 if (!strcasecmp(node, config.c_nodename)) {
301 imap_strout(config.c_fqdn);
304 imap_strout(node); /* host name */
308 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
310 imap_strout(msg->cm_fields['N']); /* host name */
313 cprintf(")) "); /* close double-parens */
319 * Output an envelope address (or set of addresses) in the official,
320 * convuluted, braindead format. (Note that we can't use this for
321 * the "From" address because its data may come from a number of different
322 * fields. But we can use it for "To" and possibly others.
324 void imap_output_envelope_addr(char *addr) {
325 char individual_addr[SIZ];
337 if (strlen(addr) == 0) {
344 /* How many addresses are listed here? */
345 num_addrs = num_tokens(addr, ',');
347 /* Output them one by one. */
348 for (i=0; i<num_addrs; ++i) {
349 extract_token(individual_addr, addr, i, ',');
350 striplt(individual_addr);
351 process_rfc822_addr(individual_addr, user, node, name);
359 if (i < (num_addrs-1)) cprintf(" ");
367 * Implements the ENVELOPE fetch item
369 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
370 * so we don't have to check for that condition like we do elsewhere.
372 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
373 char datestringbuf[SIZ];
375 char *fieldptr = NULL;
377 /* Parse the message date into an IMAP-format date string */
378 if (msg->cm_fields['T'] != NULL) {
379 msgdate = atol(msg->cm_fields['T']);
382 msgdate = time(NULL);
384 datestring(datestringbuf, sizeof datestringbuf,
385 msgdate, DATESTRING_IMAP);
387 /* Now start spewing data fields. The order is important, as it is
388 * defined by the protocol specification. Nonexistent fields must
389 * be output as NIL, existent fields must be quoted or literalled.
390 * The imap_strout() function conveniently does all this for us.
392 cprintf("ENVELOPE (");
395 imap_strout(datestringbuf);
399 imap_strout(msg->cm_fields['U']);
403 imap_output_envelope_from(msg);
405 /* Sender (default to same as 'From' if not present) */
406 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
407 if (fieldptr != NULL) {
408 imap_output_envelope_addr(fieldptr);
412 imap_output_envelope_from(msg);
416 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
417 if (fieldptr != NULL) {
418 imap_output_envelope_addr(fieldptr);
422 imap_output_envelope_from(msg);
426 imap_output_envelope_addr(msg->cm_fields['R']);
429 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
430 imap_output_envelope_addr(fieldptr);
431 if (fieldptr != NULL) free(fieldptr);
434 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
435 imap_output_envelope_addr(fieldptr);
436 if (fieldptr != NULL) free(fieldptr);
439 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
440 imap_strout(fieldptr);
442 if (fieldptr != NULL) free(fieldptr);
445 imap_strout(msg->cm_fields['I']);
452 * This function is called only when CC->redirect_buffer contains a set of
453 * RFC822 headers with no body attached. Its job is to strip that set of
454 * headers down to *only* the ones we're interested in.
456 void imap_strip_headers(char *section) {
458 char *which_fields = NULL;
459 int doing_headers = 0;
464 char *boiled_headers = NULL;
466 int done_headers = 0;
469 if (CC->redirect_buffer == NULL) return;
471 which_fields = strdup(section);
473 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
475 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
478 for (i=0; i<strlen(which_fields); ++i) {
479 if (which_fields[i]=='(')
480 strcpy(which_fields, &which_fields[i+1]);
482 for (i=0; i<strlen(which_fields); ++i) {
483 if (which_fields[i]==')')
486 num_parms = imap_parameterize(parms, which_fields);
488 boiled_headers = malloc(CC->redirect_alloc);
489 strcpy(boiled_headers, "");
491 ptr = CC->redirect_buffer;
493 while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
494 if (!isspace(buf[0])) {
496 if (doing_headers == 0) ok = 1;
498 if (headers_not) ok = 1;
500 for (i=0; i<num_parms; ++i) {
501 if ( (!strncasecmp(buf, parms[i],
502 strlen(parms[i]))) &&
503 (buf[strlen(parms[i])]==':') ) {
504 if (headers_not) ok = 0;
512 strcat(boiled_headers, buf);
513 strcat(boiled_headers, "\r\n");
516 if (strlen(buf) == 0) done_headers = 1;
517 if (buf[0]=='\r') done_headers = 1;
518 if (buf[0]=='\n') done_headers = 1;
521 strcat(boiled_headers, "\r\n");
523 /* Now save it back (it'll always be smaller) */
524 strcpy(CC->redirect_buffer, boiled_headers);
525 CC->redirect_len = strlen(boiled_headers);
528 free(boiled_headers);
533 * Implements the BODY and BODY.PEEK fetch items
535 void imap_fetch_body(long msgnum, char *item, int is_peek) {
536 struct CtdlMessage *msg = NULL;
540 size_t pstart, pbytes;
541 int loading_body_now = 0;
543 /* extract section */
544 safestrncpy(section, item, sizeof section);
545 if (strchr(section, '[') != NULL) {
546 stripallbut(section, '[', ']');
548 /* lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") ); */
550 /* Burn the cache if we don't have the same section of the
551 * same message again.
553 if (IMAP->cached_body != NULL) {
554 if ((IMAP->cached_bodymsgnum != msgnum)
555 || (strcasecmp(IMAP->cached_bodypart, section)) ) {
556 free(IMAP->cached_body);
557 IMAP->cached_body_len = 0;
558 IMAP->cached_body = NULL;
559 IMAP->cached_bodymsgnum = (-1);
560 strcpy(IMAP->cached_bodypart, "");
564 /* extract partial */
565 safestrncpy(partial, item, sizeof partial);
566 if (strchr(partial, '<') != NULL) {
567 stripallbut(partial, '<', '>');
570 if (is_partial == 0) strcpy(partial, "");
571 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
573 if (IMAP->cached_body == NULL) {
574 CC->redirect_buffer = malloc(SIZ);
575 CC->redirect_len = 0;
576 CC->redirect_alloc = SIZ;
577 loading_body_now = 1;
578 msg = CtdlFetchMessage(msgnum, 1);
581 /* Now figure out what the client wants, and get it */
583 if (!loading_body_now) {
584 /* What we want is already in memory */
587 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
588 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
591 else if (!strcmp(section, "")) {
592 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
596 * If the client asked for just headers, or just particular header
597 * fields, strip it down.
599 else if (!strncasecmp(section, "HEADER", 6)) {
600 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ONLY, 0, 1);
601 imap_strip_headers(section);
605 * Strip it down if the client asked for everything _except_ headers.
607 else if (!strncasecmp(section, "TEXT", 4)) {
608 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
612 * Anything else must be a part specifier.
613 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
616 mime_parser(msg->cm_fields['M'], NULL,
617 *imap_load_part, NULL, NULL,
622 if (loading_body_now) {
623 IMAP->cached_body = CC->redirect_buffer;
624 IMAP->cached_body_len = CC->redirect_len;
625 IMAP->cached_bodymsgnum = msgnum;
626 strcpy(IMAP->cached_bodypart, section);
627 CC->redirect_buffer = NULL;
628 CC->redirect_len = 0;
629 CC->redirect_alloc = 0;
632 if (is_partial == 0) {
633 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
635 pbytes = IMAP->cached_body_len;
638 sscanf(partial, "%d.%d", &pstart, &pbytes);
639 if (pbytes > (IMAP->cached_body_len - pstart)) {
640 pbytes = IMAP->cached_body_len - pstart;
642 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
645 /* Here we go -- output it */
646 client_write(&IMAP->cached_body[pstart], pbytes);
649 CtdlFreeMessage(msg);
652 /* Mark this message as "seen" *unless* this is a "peek" operation */
654 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
659 * Called immediately before outputting a multipart bodystructure
661 void imap_fetch_bodystructure_pre(
662 char *name, char *filename, char *partnum, char *disp,
663 void *content, char *cbtype, size_t length, char *encoding,
673 * Called immediately after outputting a multipart bodystructure
675 void imap_fetch_bodystructure_post(
676 char *name, char *filename, char *partnum, char *disp,
677 void *content, char *cbtype, size_t length, char *encoding,
686 extract_token(subtype, cbtype, 1, '/');
687 imap_strout(subtype);
698 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
701 void imap_fetch_bodystructure_part(
702 char *name, char *filename, char *partnum, char *disp,
703 void *content, char *cbtype, size_t length, char *encoding,
708 int have_encoding = 0;
711 char cbmaintype[SIZ];
714 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
716 extract_token(cbmaintype, cbtype, 0, '/');
717 extract_token(cbsubtype, cbtype, 1, '/');
720 strcpy(cbmaintype, "TEXT");
721 strcpy(cbsubtype, "PLAIN");
725 imap_strout(cbmaintype);
727 imap_strout(cbsubtype);
730 cprintf("(\"CHARSET\" \"US-ASCII\"");
732 if (name != NULL) if (strlen(name)>0) {
733 cprintf(" \"NAME\" ");
739 cprintf("NIL "); /* Body ID */
740 cprintf("NIL "); /* Body description */
742 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
744 imap_strout(encoding);
751 /* The next field is the size of the part in bytes. */
752 cprintf("%ld ", (long)length); /* bytes */
754 /* The next field is the number of lines in the part, if and only
755 * if the part is TEXT. More gratuitous complexity.
757 if (!strcasecmp(cbmaintype, "TEXT")) {
758 if (length) for (i=0; i<length; ++i) {
759 if (((char *)content)[i] == '\n') ++lines;
761 cprintf("%d ", lines);
764 /* More gratuitous complexity */
765 if ((!strcasecmp(cbmaintype, "MESSAGE"))
766 && (!strcasecmp(cbsubtype, "RFC822"))) {
768 A body type of type MESSAGE and subtype RFC822
769 contains, immediately after the basic fields, the
770 envelope structure, body structure, and size in
771 text lines of the encapsulated message.
775 /* MD5 value of body part; we can get away with NIL'ing this */
782 else if (strlen(disp) == 0) {
788 if (filename != NULL) if (strlen(filename)>0) {
789 cprintf(" (\"FILENAME\" ");
790 imap_strout(filename);
796 /* Body language (not defined yet) */
803 * Spew the BODYSTRUCTURE data for a message.
806 void imap_fetch_bodystructure (long msgnum, char *item,
807 struct CtdlMessage *msg) {
809 char *rfc822_body = NULL;
811 size_t rfc822_headers_len;
812 size_t rfc822_body_len;
817 /* For non-RFC822 (ordinary Citadel) messages, this is short and
820 if (msg->cm_format_type != FMT_RFC822) {
822 /* *sigh* We have to RFC822-format the message just to be able
823 * to measure it. FIXME use smi cached fields if possible
826 CC->redirect_buffer = malloc(SIZ);
827 CC->redirect_len = 0;
828 CC->redirect_alloc = SIZ;
829 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
830 rfc822 = CC->redirect_buffer;
831 rfc822_len = CC->redirect_len;
832 CC->redirect_buffer = NULL;
833 CC->redirect_len = 0;
834 CC->redirect_alloc = 0;
837 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
839 if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
844 rfc822_headers_len = rfc822_body - rfc822;
845 rfc822_body_len = rfc822_len - rfc822_headers_len;
848 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
849 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
850 "\"7BIT\" %d %d)", rfc822_body_len, lines);
855 /* For messages already stored in RFC822 format, we have to parse. */
856 cprintf("BODYSTRUCTURE ");
857 mime_parser(msg->cm_fields['M'],
859 *imap_fetch_bodystructure_part, /* part */
860 *imap_fetch_bodystructure_pre, /* pre-multi */
861 *imap_fetch_bodystructure_post, /* post-multi */
863 1); /* don't decode -- we want it as-is */
868 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
869 * individual message, once it has been selected for output.
871 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
873 struct CtdlMessage *msg = NULL;
875 cprintf("* %d FETCH (", seq);
877 for (i=0; i<num_items; ++i) {
879 /* Fetchable without going to the message store at all */
880 if (!strcasecmp(itemlist[i], "UID")) {
883 else if (!strcasecmp(itemlist[i], "FLAGS")) {
884 imap_fetch_flags(seq-1);
887 /* Potentially fetchable from cache, if the client requests
888 * stuff from the same message several times in a row.
890 else if (!strcasecmp(itemlist[i], "RFC822")) {
891 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
893 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
894 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
896 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
897 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
899 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
900 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
903 /* BODY fetches do their own fetching and caching too. */
904 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
905 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
907 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
908 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
911 /* Otherwise, load the message into memory.
913 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
914 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
915 imap_fetch_bodystructure(IMAP->msgids[seq-1],
918 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
919 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
920 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
922 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
923 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
924 imap_fetch_internaldate(msg);
927 if (i != num_items-1) cprintf(" ");
932 CtdlFreeMessage(msg);
939 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
940 * validated and boiled down the request a bit.
942 void imap_do_fetch(int num_items, char **itemlist) {
945 if (IMAP->num_msgs > 0) {
946 for (i = 0; i < IMAP->num_msgs; ++i) {
947 if (IMAP->flags[i] & IMAP_SELECTED) {
948 imap_do_fetch_msg(i+1, num_items, itemlist);
957 * Back end for imap_handle_macros()
958 * Note that this function *only* looks at the beginning of the string. It
959 * is not a generic search-and-replace function.
961 void imap_macro_replace(char *str, char *find, char *replace) {
964 if (!strncasecmp(str, find, strlen(find))) {
965 if (str[strlen(find)]==' ') {
966 strcpy(holdbuf, &str[strlen(find)+1]);
967 strcpy(str, replace);
969 strcat(str, holdbuf);
971 if (str[strlen(find)]==0) {
972 strcpy(holdbuf, &str[strlen(find)+1]);
973 strcpy(str, replace);
981 * Handle macros embedded in FETCH data items.
982 * (What the heck are macros doing in a wire protocol? Are we trying to save
983 * the computer at the other end the trouble of typing a lot of characters?)
985 void imap_handle_macros(char *str) {
989 for (i=0; i<strlen(str); ++i) {
990 if (str[i]=='(') ++nest;
991 if (str[i]=='[') ++nest;
992 if (str[i]=='<') ++nest;
993 if (str[i]=='{') ++nest;
994 if (str[i]==')') --nest;
995 if (str[i]==']') --nest;
996 if (str[i]=='>') --nest;
997 if (str[i]=='}') --nest;
1000 imap_macro_replace(&str[i],
1002 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1004 imap_macro_replace(&str[i],
1008 imap_macro_replace(&str[i],
1010 "FLAGS INTERNALDATE RFC822.SIZE"
1012 imap_macro_replace(&str[i],
1014 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1022 * Break out the data items requested, possibly a parenthesized list.
1023 * Returns the number of data items, or -1 if the list is invalid.
1024 * NOTE: this function alters the string it is fed, and uses it as a buffer
1025 * to hold the data for the pointers it returns.
1027 int imap_extract_data_items(char **argv, char *items) {
1033 /* Convert all whitespace to ordinary space characters. */
1034 for (i=0; i<strlen(items); ++i) {
1035 if (isspace(items[i])) items[i]=' ';
1038 /* Strip leading and trailing whitespace, then strip leading and
1039 * trailing parentheses if it's a list
1042 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1043 items[strlen(items)-1] = 0;
1044 strcpy(items, &items[1]);
1048 /* Parse any macro data items */
1049 imap_handle_macros(items);
1052 * Now break out the data items. We throw in one trailing space in
1053 * order to avoid having to break out the last one manually.
1057 initial_len = strlen(items);
1058 for (i=0; i<initial_len; ++i) {
1059 if (items[i]=='(') ++nest;
1060 if (items[i]=='[') ++nest;
1061 if (items[i]=='<') ++nest;
1062 if (items[i]=='{') ++nest;
1063 if (items[i]==')') --nest;
1064 if (items[i]==']') --nest;
1065 if (items[i]=='>') --nest;
1066 if (items[i]=='}') --nest;
1068 if (nest <= 0) if (items[i]==' ') {
1070 argv[num_items++] = start;
1071 start = &items[i+1];
1081 * One particularly hideous aspect of IMAP is that we have to allow the client
1082 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1083 * handles this by setting the IMAP_SELECTED flag for each message specified in
1084 * the ranges/sets, then looping through the message array, outputting messages
1085 * with the flag set. We don't bother returning an error if an out-of-range
1086 * number is specified (we just return quietly) because any client braindead
1087 * enough to request a bogus message number isn't going to notice the
1088 * difference anyway.
1090 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1091 * message included in the specified range.
1093 * Set is_uid to 1 to fetch by UID instead of sequence number.
1095 void imap_pick_range(char *supplied_range, int is_uid) {
1099 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1101 char actual_range[SIZ];
1104 * Handle the "ALL" macro
1106 if (!strcasecmp(supplied_range, "ALL")) {
1107 safestrncpy(actual_range, "1:*", sizeof actual_range);
1110 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1114 * Clear out the IMAP_SELECTED flags for all messages.
1116 for (i = 0; i < IMAP->num_msgs; ++i) {
1117 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1121 * Now set it for all specified messages.
1123 num_sets = num_tokens(actual_range, ',');
1124 for (s=0; s<num_sets; ++s) {
1125 extract_token(setstr, actual_range, s, ',');
1127 extract_token(lostr, setstr, 0, ':');
1128 if (num_tokens(setstr, ':') >= 2) {
1129 extract_token(histr, setstr, 1, ':');
1130 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1133 strcpy(histr, lostr);
1138 /* Loop through the array, flipping bits where appropriate */
1139 for (i = 1; i <= IMAP->num_msgs; ++i) {
1140 if (is_uid) { /* fetch by sequence number */
1141 if ( (IMAP->msgids[i-1]>=lo)
1142 && (IMAP->msgids[i-1]<=hi)) {
1144 IMAP->flags[i-1] | IMAP_SELECTED;
1147 else { /* fetch by uid */
1148 if ( (i>=lo) && (i<=hi)) {
1150 IMAP->flags[i-1] | IMAP_SELECTED;
1161 * This function is called by the main command loop.
1163 void imap_fetch(int num_parms, char *parms[]) {
1165 char *itemlist[SIZ];
1169 if (num_parms < 4) {
1170 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1174 imap_pick_range(parms[2], 0);
1177 for (i=3; i<num_parms; ++i) {
1178 strcat(items, parms[i]);
1179 if (i < (num_parms-1)) strcat(items, " ");
1182 num_items = imap_extract_data_items(itemlist, items);
1183 if (num_items < 1) {
1184 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1188 imap_do_fetch(num_items, itemlist);
1189 cprintf("%s OK FETCH completed\r\n", parms[0]);
1193 * This function is called by the main command loop.
1195 void imap_uidfetch(int num_parms, char *parms[]) {
1197 char *itemlist[SIZ];
1200 int have_uid_item = 0;
1202 if (num_parms < 5) {
1203 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1207 imap_pick_range(parms[3], 1);
1210 for (i=4; i<num_parms; ++i) {
1211 strcat(items, parms[i]);
1212 if (i < (num_parms-1)) strcat(items, " ");
1215 num_items = imap_extract_data_items(itemlist, items);
1216 if (num_items < 1) {
1217 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1221 /* If the "UID" item was not included, we include it implicitly
1222 * because this is a UID FETCH command
1224 for (i=0; i<num_items; ++i) {
1225 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1227 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1229 imap_do_fetch(num_items, itemlist);
1230 cprintf("%s OK UID FETCH completed\r\n", parms[0]);