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;
122 /* Cache the most recent RFC822 FETCH because some clients like to
123 * fetch in pieces, and we don't want to have to go back to the
124 * message store for each piece.
126 if ((IMAP->cached_rfc822_data != NULL)
127 && (IMAP->cached_rfc822_msgnum == msgnum)) {
130 else if (IMAP->cached_rfc822_data != NULL) {
131 /* Some other message is cached -- free it */
132 free(IMAP->cached_rfc822_data);
133 IMAP->cached_rfc822_data = NULL;
134 IMAP->cached_rfc822_msgnum = (-1);
135 IMAP->cached_rfc822_len = 0;
138 /* At this point, we now can fetch and convert the message iff it's not
139 * the one we had cached.
141 if (IMAP->cached_rfc822_data == NULL) {
143 * Load the message into memory for translation & measurement
145 CC->redirect_buffer = malloc(SIZ);
146 CC->redirect_len = 0;
147 CC->redirect_alloc = SIZ;
148 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
149 IMAP->cached_rfc822_data = CC->redirect_buffer;
150 IMAP->cached_rfc822_len = CC->redirect_len;
151 IMAP->cached_rfc822_msgnum = msgnum;
152 CC->redirect_buffer = NULL;
153 CC->redirect_len = 0;
154 CC->redirect_alloc = 0;
158 * Now figure out where the headers/text break is. IMAP considers the
159 * intervening blank line to be part of the headers, not the text.
165 ptr = IMAP->cached_rfc822_data;
167 ptr = memreadline(ptr, buf, sizeof buf);
170 if (strlen(buf) == 0) {
171 headers_size = ptr - IMAP->cached_rfc822_data;
174 } while ( (headers_size == 0) && (*ptr != 0) );
176 total_size = IMAP->cached_rfc822_len;
177 text_size = total_size - headers_size;
179 lprintf(CTDL_DEBUG, "RFC822: headers=%d, text=%d, total=%d\n",
180 headers_size, text_size, total_size);
182 if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
183 cprintf("RFC822.SIZE %d", total_size);
187 else if (!strcasecmp(whichfmt, "RFC822")) {
188 ptr = IMAP->cached_rfc822_data;
189 bytes_to_send = total_size;
192 else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
193 ptr = IMAP->cached_rfc822_data;
194 bytes_to_send = headers_size;
197 else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
198 ptr = &IMAP->cached_rfc822_data[headers_size];
199 bytes_to_send = text_size;
202 cprintf("%s {%d}\r\n", whichfmt, bytes_to_send);
203 client_write(ptr, bytes_to_send);
209 * Load a specific part of a message into the temp file to be output to a
210 * client. FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
211 * but we still can't handle "2.HEADER" (which might not be a problem, because
212 * we currently don't have the ability to break out nested RFC822's anyway).
214 * Note: mime_parser() was called with dont_decode set to 1, so we have the
215 * luxury of simply spewing without having to re-encode.
217 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
218 void *content, char *cbtype, size_t length, char *encoding,
222 char *desired_section;
224 desired_section = (char *)cbuserdata;
226 if (!strcasecmp(partnum, desired_section)) {
227 client_write(content, length);
230 snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
232 if (!strcasecmp(desired_section, mbuf2)) {
233 cprintf("Content-type: %s", cbtype);
234 if (strlen(name) > 0)
235 cprintf("; name=\"%s\"", name);
237 if (strlen(encoding) > 0)
238 cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
239 if (strlen(encoding) > 0) {
240 cprintf("Content-Disposition: %s", disp);
241 if (strlen(filename) > 0) {
242 cprintf("; filename=\"%s\"", filename);
246 cprintf("Content-Length: %ld\r\n", (long)length);
255 * Called by imap_fetch_envelope() to output the "From" field.
256 * This is in its own function because its logic is kind of complex. We
257 * really need to make this suck less.
259 void imap_output_envelope_from(struct CtdlMessage *msg) {
260 char user[SIZ], node[SIZ], name[SIZ];
262 /* For anonymous messages, it's so easy! */
263 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
264 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
267 if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
268 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
272 /* For everything else, we do stuff. */
273 cprintf("(("); /* open double-parens */
274 imap_strout(msg->cm_fields['A']); /* personal name */
275 cprintf(" NIL "); /* source route (not used) */
278 if (msg->cm_fields['F'] != NULL) {
279 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
280 imap_strout(user); /* mailbox name (user id) */
282 if (!strcasecmp(node, config.c_nodename)) {
283 imap_strout(config.c_fqdn);
286 imap_strout(node); /* host name */
290 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
292 imap_strout(msg->cm_fields['N']); /* host name */
295 cprintf(")) "); /* close double-parens */
301 * Output an envelope address (or set of addresses) in the official,
302 * convuluted, braindead format. (Note that we can't use this for
303 * the "From" address because its data may come from a number of different
304 * fields. But we can use it for "To" and possibly others.
306 void imap_output_envelope_addr(char *addr) {
307 char individual_addr[SIZ];
319 if (strlen(addr) == 0) {
326 /* How many addresses are listed here? */
327 num_addrs = num_tokens(addr, ',');
329 /* Output them one by one. */
330 for (i=0; i<num_addrs; ++i) {
331 extract_token(individual_addr, addr, i, ',');
332 striplt(individual_addr);
333 process_rfc822_addr(individual_addr, user, node, name);
341 if (i < (num_addrs-1)) cprintf(" ");
349 * Implements the ENVELOPE fetch item
351 * Note that the imap_strout() function can cleverly output NULL fields as NIL,
352 * so we don't have to check for that condition like we do elsewhere.
354 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
355 char datestringbuf[SIZ];
357 char *fieldptr = NULL;
359 /* Parse the message date into an IMAP-format date string */
360 if (msg->cm_fields['T'] != NULL) {
361 msgdate = atol(msg->cm_fields['T']);
364 msgdate = time(NULL);
366 datestring(datestringbuf, sizeof datestringbuf,
367 msgdate, DATESTRING_IMAP);
369 /* Now start spewing data fields. The order is important, as it is
370 * defined by the protocol specification. Nonexistent fields must
371 * be output as NIL, existent fields must be quoted or literalled.
372 * The imap_strout() function conveniently does all this for us.
374 cprintf("ENVELOPE (");
377 imap_strout(datestringbuf);
381 imap_strout(msg->cm_fields['U']);
385 imap_output_envelope_from(msg);
387 /* Sender (default to same as 'From' if not present) */
388 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
389 if (fieldptr != NULL) {
390 imap_output_envelope_addr(fieldptr);
394 imap_output_envelope_from(msg);
398 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
399 if (fieldptr != NULL) {
400 imap_output_envelope_addr(fieldptr);
404 imap_output_envelope_from(msg);
408 imap_output_envelope_addr(msg->cm_fields['R']);
411 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
412 imap_output_envelope_addr(fieldptr);
413 if (fieldptr != NULL) free(fieldptr);
416 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
417 imap_output_envelope_addr(fieldptr);
418 if (fieldptr != NULL) free(fieldptr);
421 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
422 imap_strout(fieldptr);
424 if (fieldptr != NULL) free(fieldptr);
427 imap_strout(msg->cm_fields['I']);
434 * This function is called only when CC->redirect_buffer contains a set of
435 * RFC822 headers with no body attached. Its job is to strip that set of
436 * headers down to *only* the ones we're interested in.
438 void imap_strip_headers(char *section) {
440 char *which_fields = NULL;
441 int doing_headers = 0;
446 char *boiled_headers = NULL;
448 int done_headers = 0;
451 if (CC->redirect_buffer == NULL) return;
453 which_fields = strdup(section);
455 if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
457 if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
460 for (i=0; i<strlen(which_fields); ++i) {
461 if (which_fields[i]=='(')
462 strcpy(which_fields, &which_fields[i+1]);
464 for (i=0; i<strlen(which_fields); ++i) {
465 if (which_fields[i]==')')
468 num_parms = imap_parameterize(parms, which_fields);
470 boiled_headers = malloc(CC->redirect_alloc);
471 strcpy(boiled_headers, "");
473 ptr = CC->redirect_buffer;
475 while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
476 if (!isspace(buf[0])) {
478 if (doing_headers == 0) ok = 1;
480 if (headers_not) ok = 1;
482 for (i=0; i<num_parms; ++i) {
483 if ( (!strncasecmp(buf, parms[i],
484 strlen(parms[i]))) &&
485 (buf[strlen(parms[i])]==':') ) {
486 if (headers_not) ok = 0;
494 strcat(boiled_headers, buf);
495 strcat(boiled_headers, "\r\n");
498 if (strlen(buf) == 0) done_headers = 1;
499 if (buf[0]=='\r') done_headers = 1;
500 if (buf[0]=='\n') done_headers = 1;
503 strcat(boiled_headers, "\r\n");
505 /* Now save it back (it'll always be smaller) */
506 strcpy(CC->redirect_buffer, boiled_headers);
507 CC->redirect_len = strlen(boiled_headers);
510 free(boiled_headers);
515 * Implements the BODY and BODY.PEEK fetch items
517 void imap_fetch_body(long msgnum, char *item, int is_peek) {
518 struct CtdlMessage *msg = NULL;
522 size_t pstart, pbytes;
523 int loading_body_now = 0;
525 /* extract section */
526 safestrncpy(section, item, sizeof section);
527 if (strchr(section, '[') != NULL) {
528 stripallbut(section, '[', ']');
530 /* lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") ); */
532 /* Burn the cache if we don't have the same section of the
533 * same message again.
535 if (IMAP->cached_body != NULL) {
536 if ((IMAP->cached_bodymsgnum != msgnum)
537 || (strcasecmp(IMAP->cached_bodypart, section)) ) {
538 free(IMAP->cached_body);
539 IMAP->cached_body_len = 0;
540 IMAP->cached_body = NULL;
541 IMAP->cached_bodymsgnum = (-1);
542 strcpy(IMAP->cached_bodypart, "");
546 /* extract partial */
547 safestrncpy(partial, item, sizeof partial);
548 if (strchr(partial, '<') != NULL) {
549 stripallbut(partial, '<', '>');
552 if (is_partial == 0) strcpy(partial, "");
553 /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
555 if (IMAP->cached_body == NULL) {
556 CC->redirect_buffer = malloc(SIZ);
557 CC->redirect_len = 0;
558 CC->redirect_alloc = SIZ;
559 loading_body_now = 1;
560 msg = CtdlFetchMessage(msgnum, 1);
563 /* Now figure out what the client wants, and get it */
565 if (!loading_body_now) {
566 /* What we want is already in memory */
569 else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
570 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
573 else if (!strcmp(section, "")) {
574 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
578 * If the client asked for just headers, or just particular header
579 * fields, strip it down.
581 else if (!strncasecmp(section, "HEADER", 6)) {
582 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ONLY, 0, 1);
583 imap_strip_headers(section);
587 * Strip it down if the client asked for everything _except_ headers.
589 else if (!strncasecmp(section, "TEXT", 4)) {
590 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
594 * Anything else must be a part specifier.
595 * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
598 mime_parser(msg->cm_fields['M'], NULL,
599 *imap_load_part, NULL, NULL,
604 if (loading_body_now) {
605 IMAP->cached_body = CC->redirect_buffer;
606 IMAP->cached_body_len = CC->redirect_len;
607 IMAP->cached_bodymsgnum = msgnum;
608 strcpy(IMAP->cached_bodypart, section);
609 CC->redirect_buffer = NULL;
610 CC->redirect_len = 0;
611 CC->redirect_alloc = 0;
614 if (is_partial == 0) {
615 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
617 pbytes = IMAP->cached_body_len;
620 sscanf(partial, "%d.%d", &pstart, &pbytes);
621 if (pbytes > (IMAP->cached_body_len - pstart)) {
622 pbytes = IMAP->cached_body_len - pstart;
624 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
627 /* Here we go -- output it */
628 client_write(&IMAP->cached_body[pstart], pbytes);
631 CtdlFreeMessage(msg);
634 /* Mark this message as "seen" *unless* this is a "peek" operation */
636 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
641 * Called immediately before outputting a multipart bodystructure
643 void imap_fetch_bodystructure_pre(
644 char *name, char *filename, char *partnum, char *disp,
645 void *content, char *cbtype, size_t length, char *encoding,
655 * Called immediately after outputting a multipart bodystructure
657 void imap_fetch_bodystructure_post(
658 char *name, char *filename, char *partnum, char *disp,
659 void *content, char *cbtype, size_t length, char *encoding,
668 extract_token(subtype, cbtype, 1, '/');
669 imap_strout(subtype);
680 * Output the info for a MIME part in the format required by BODYSTRUCTURE.
683 void imap_fetch_bodystructure_part(
684 char *name, char *filename, char *partnum, char *disp,
685 void *content, char *cbtype, size_t length, char *encoding,
690 int have_encoding = 0;
693 char cbmaintype[SIZ];
696 if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
698 extract_token(cbmaintype, cbtype, 0, '/');
699 extract_token(cbsubtype, cbtype, 1, '/');
702 strcpy(cbmaintype, "TEXT");
703 strcpy(cbsubtype, "PLAIN");
707 imap_strout(cbmaintype);
709 imap_strout(cbsubtype);
712 cprintf("(\"CHARSET\" \"US-ASCII\"");
714 if (name != NULL) if (strlen(name)>0) {
715 cprintf(" \"NAME\" ");
721 cprintf("NIL "); /* Body ID */
722 cprintf("NIL "); /* Body description */
724 if (encoding != NULL) if (strlen(encoding) > 0) have_encoding = 1;
726 imap_strout(encoding);
733 /* The next field is the size of the part in bytes. */
734 cprintf("%ld ", (long)length); /* bytes */
736 /* The next field is the number of lines in the part, if and only
737 * if the part is TEXT. More gratuitous complexity.
739 if (!strcasecmp(cbmaintype, "TEXT")) {
740 if (length) for (i=0; i<length; ++i) {
741 if (((char *)content)[i] == '\n') ++lines;
743 cprintf("%d ", lines);
746 /* More gratuitous complexity */
747 if ((!strcasecmp(cbmaintype, "MESSAGE"))
748 && (!strcasecmp(cbsubtype, "RFC822"))) {
750 A body type of type MESSAGE and subtype RFC822
751 contains, immediately after the basic fields, the
752 envelope structure, body structure, and size in
753 text lines of the encapsulated message.
757 /* MD5 value of body part; we can get away with NIL'ing this */
764 else if (strlen(disp) == 0) {
770 if (filename != NULL) if (strlen(filename)>0) {
771 cprintf(" (\"FILENAME\" ");
772 imap_strout(filename);
778 /* Body language (not defined yet) */
785 * Spew the BODYSTRUCTURE data for a message.
788 void imap_fetch_bodystructure (long msgnum, char *item,
789 struct CtdlMessage *msg) {
791 char *rfc822_body = NULL;
793 size_t rfc822_headers_len;
794 size_t rfc822_body_len;
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
805 * to measure it. FIXME use smi cached fields if possible
808 CC->redirect_buffer = malloc(SIZ);
809 CC->redirect_len = 0;
810 CC->redirect_alloc = SIZ;
811 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
812 rfc822 = CC->redirect_buffer;
813 rfc822_len = CC->redirect_len;
814 CC->redirect_buffer = NULL;
815 CC->redirect_len = 0;
816 CC->redirect_alloc = 0;
819 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
821 if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
826 rfc822_headers_len = rfc822_body - rfc822;
827 rfc822_body_len = rfc822_len - rfc822_headers_len;
830 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
831 "(\"CHARSET\" \"US-ASCII\") NIL NIL "
832 "\"7BIT\" %d %d)", rfc822_body_len, lines);
837 /* For messages already stored in RFC822 format, we have to parse. */
838 cprintf("BODYSTRUCTURE ");
839 mime_parser(msg->cm_fields['M'],
841 *imap_fetch_bodystructure_part, /* part */
842 *imap_fetch_bodystructure_pre, /* pre-multi */
843 *imap_fetch_bodystructure_post, /* post-multi */
845 1); /* don't decode -- we want it as-is */
850 * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
851 * individual message, once it has been selected for output.
853 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
855 struct CtdlMessage *msg = NULL;
857 cprintf("* %d FETCH (", seq);
859 for (i=0; i<num_items; ++i) {
861 /* Fetchable without going to the message store at all */
862 if (!strcasecmp(itemlist[i], "UID")) {
865 else if (!strcasecmp(itemlist[i], "FLAGS")) {
866 imap_fetch_flags(seq-1);
869 /* Potentially fetchable from cache, if the client requests
870 * stuff from the same message several times in a row.
872 else if (!strcasecmp(itemlist[i], "RFC822")) {
873 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
875 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
876 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
878 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
879 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
881 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
882 imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
885 /* BODY fetches do their own fetching and caching too. */
886 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
887 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
889 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
890 imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
893 /* Otherwise, load the message into memory.
895 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
896 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
897 imap_fetch_bodystructure(IMAP->msgids[seq-1],
900 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
901 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
902 imap_fetch_envelope(IMAP->msgids[seq-1], msg);
904 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
905 if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
906 imap_fetch_internaldate(msg);
909 if (i != num_items-1) cprintf(" ");
914 CtdlFreeMessage(msg);
921 * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
922 * validated and boiled down the request a bit.
924 void imap_do_fetch(int num_items, char **itemlist) {
927 if (IMAP->num_msgs > 0) {
928 for (i = 0; i < IMAP->num_msgs; ++i) {
929 if (IMAP->flags[i] & IMAP_SELECTED) {
930 imap_do_fetch_msg(i+1, num_items, itemlist);
939 * Back end for imap_handle_macros()
940 * Note that this function *only* looks at the beginning of the string. It
941 * is not a generic search-and-replace function.
943 void imap_macro_replace(char *str, char *find, char *replace) {
946 if (!strncasecmp(str, find, strlen(find))) {
947 if (str[strlen(find)]==' ') {
948 strcpy(holdbuf, &str[strlen(find)+1]);
949 strcpy(str, replace);
951 strcat(str, holdbuf);
953 if (str[strlen(find)]==0) {
954 strcpy(holdbuf, &str[strlen(find)+1]);
955 strcpy(str, replace);
963 * Handle macros embedded in FETCH data items.
964 * (What the heck are macros doing in a wire protocol? Are we trying to save
965 * the computer at the other end the trouble of typing a lot of characters?)
967 void imap_handle_macros(char *str) {
971 for (i=0; i<strlen(str); ++i) {
972 if (str[i]=='(') ++nest;
973 if (str[i]=='[') ++nest;
974 if (str[i]=='<') ++nest;
975 if (str[i]=='{') ++nest;
976 if (str[i]==')') --nest;
977 if (str[i]==']') --nest;
978 if (str[i]=='>') --nest;
979 if (str[i]=='}') --nest;
982 imap_macro_replace(&str[i],
984 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
986 imap_macro_replace(&str[i],
990 imap_macro_replace(&str[i],
992 "FLAGS INTERNALDATE RFC822.SIZE"
994 imap_macro_replace(&str[i],
996 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1004 * Break out the data items requested, possibly a parenthesized list.
1005 * Returns the number of data items, or -1 if the list is invalid.
1006 * NOTE: this function alters the string it is fed, and uses it as a buffer
1007 * to hold the data for the pointers it returns.
1009 int imap_extract_data_items(char **argv, char *items) {
1015 /* Convert all whitespace to ordinary space characters. */
1016 for (i=0; i<strlen(items); ++i) {
1017 if (isspace(items[i])) items[i]=' ';
1020 /* Strip leading and trailing whitespace, then strip leading and
1021 * trailing parentheses if it's a list
1024 if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1025 items[strlen(items)-1] = 0;
1026 strcpy(items, &items[1]);
1030 /* Parse any macro data items */
1031 imap_handle_macros(items);
1034 * Now break out the data items. We throw in one trailing space in
1035 * order to avoid having to break out the last one manually.
1039 initial_len = strlen(items);
1040 for (i=0; i<initial_len; ++i) {
1041 if (items[i]=='(') ++nest;
1042 if (items[i]=='[') ++nest;
1043 if (items[i]=='<') ++nest;
1044 if (items[i]=='{') ++nest;
1045 if (items[i]==')') --nest;
1046 if (items[i]==']') --nest;
1047 if (items[i]=='>') --nest;
1048 if (items[i]=='}') --nest;
1050 if (nest <= 0) if (items[i]==' ') {
1052 argv[num_items++] = start;
1053 start = &items[i+1];
1063 * One particularly hideous aspect of IMAP is that we have to allow the client
1064 * to specify arbitrary ranges and/or sets of messages to fetch. Citadel IMAP
1065 * handles this by setting the IMAP_SELECTED flag for each message specified in
1066 * the ranges/sets, then looping through the message array, outputting messages
1067 * with the flag set. We don't bother returning an error if an out-of-range
1068 * number is specified (we just return quietly) because any client braindead
1069 * enough to request a bogus message number isn't going to notice the
1070 * difference anyway.
1072 * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1073 * message included in the specified range.
1075 * Set is_uid to 1 to fetch by UID instead of sequence number.
1077 void imap_pick_range(char *supplied_range, int is_uid) {
1081 char setstr[SIZ], lostr[SIZ], histr[SIZ];
1083 char actual_range[SIZ];
1086 * Handle the "ALL" macro
1088 if (!strcasecmp(supplied_range, "ALL")) {
1089 safestrncpy(actual_range, "1:*", sizeof actual_range);
1092 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1096 * Clear out the IMAP_SELECTED flags for all messages.
1098 for (i = 0; i < IMAP->num_msgs; ++i) {
1099 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1103 * Now set it for all specified messages.
1105 num_sets = num_tokens(actual_range, ',');
1106 for (s=0; s<num_sets; ++s) {
1107 extract_token(setstr, actual_range, s, ',');
1109 extract_token(lostr, setstr, 0, ':');
1110 if (num_tokens(setstr, ':') >= 2) {
1111 extract_token(histr, setstr, 1, ':');
1112 if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1115 strcpy(histr, lostr);
1120 /* Loop through the array, flipping bits where appropriate */
1121 for (i = 1; i <= IMAP->num_msgs; ++i) {
1122 if (is_uid) { /* fetch by sequence number */
1123 if ( (IMAP->msgids[i-1]>=lo)
1124 && (IMAP->msgids[i-1]<=hi)) {
1126 IMAP->flags[i-1] | IMAP_SELECTED;
1129 else { /* fetch by uid */
1130 if ( (i>=lo) && (i<=hi)) {
1132 IMAP->flags[i-1] | IMAP_SELECTED;
1143 * This function is called by the main command loop.
1145 void imap_fetch(int num_parms, char *parms[]) {
1147 char *itemlist[SIZ];
1151 if (num_parms < 4) {
1152 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1156 imap_pick_range(parms[2], 0);
1159 for (i=3; i<num_parms; ++i) {
1160 strcat(items, parms[i]);
1161 if (i < (num_parms-1)) strcat(items, " ");
1164 num_items = imap_extract_data_items(itemlist, items);
1165 if (num_items < 1) {
1166 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1170 imap_do_fetch(num_items, itemlist);
1171 cprintf("%s OK FETCH completed\r\n", parms[0]);
1175 * This function is called by the main command loop.
1177 void imap_uidfetch(int num_parms, char *parms[]) {
1179 char *itemlist[SIZ];
1182 int have_uid_item = 0;
1184 if (num_parms < 5) {
1185 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1189 imap_pick_range(parms[3], 1);
1192 for (i=4; i<num_parms; ++i) {
1193 strcat(items, parms[i]);
1194 if (i < (num_parms-1)) strcat(items, " ");
1197 num_items = imap_extract_data_items(itemlist, items);
1198 if (num_items < 1) {
1199 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1203 /* If the "UID" item was not included, we include it implicitly
1204 * because this is a UID FETCH command
1206 for (i=0; i<num_items; ++i) {
1207 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1209 if (have_uid_item == 0) itemlist[num_items++] = "UID";
1211 imap_do_fetch(num_items, itemlist);
1212 cprintf("%s OK UID FETCH completed\r\n", parms[0]);