dammit, learn to spell
[citadel.git] / citadel / server / modules / imap / imap_fetch.c
1 /*
2  * Implements the FETCH command in IMAP.
3  * This is a good example of the protocol's gratuitous complexity.
4  *
5  * Copyright (c) 2001-2020 by the citadel.org team
6  *
7  * This program is open source software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  */
17
18
19 #include "../../sysdep.h"
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <pwd.h>
26 #include <errno.h>
27 #include <sys/types.h>
28 #include <time.h>
29 #include <sys/wait.h>
30 #include <ctype.h>
31 #include <string.h>
32 #include <limits.h>
33 #include <libcitadel.h>
34 #include "../../citadel_defs.h"
35 #include "../../server.h"
36 #include "../../sysdep_decls.h"
37 #include "../../citserver.h"
38 #include "../../support.h"
39 #include "../../config.h"
40 #include "../../user_ops.h"
41 #include "../../database.h"
42 #include "../../msgbase.h"
43 #include "../../internet_addressing.h"
44 #include "serv_imap.h"
45 #include "imap_tools.h"
46 #include "imap_fetch.h"
47 #include "../../genstamp.h"
48 #include "../../ctdl_module.h"
49
50
51
52 /*
53  * Individual field functions for imap_do_fetch_msg() ...
54  */
55
56 void imap_fetch_uid(int seq) {
57         IAPrintf("UID %ld", IMAP->msgids[seq-1]);
58 }
59
60 void imap_fetch_flags(int seq) 
61 {
62         citimap *Imap = IMAP;
63         int num_flags_printed = 0;
64         IAPuts("FLAGS (");
65         if (Imap->flags[seq] & IMAP_DELETED) {
66                 if (num_flags_printed > 0) 
67                         IAPuts(" ");
68                 IAPuts("\\Deleted");
69                 ++num_flags_printed;
70         }
71         if (Imap->flags[seq] & IMAP_SEEN) {
72                 if (num_flags_printed > 0) 
73                         IAPuts(" ");
74                 IAPuts("\\Seen");
75                 ++num_flags_printed;
76         }
77         if (Imap->flags[seq] & IMAP_ANSWERED) {
78                 if (num_flags_printed > 0) 
79                         IAPuts(" ");
80                 IAPuts("\\Answered");
81                 ++num_flags_printed;
82         }
83         if (Imap->flags[seq] & IMAP_RECENT) {
84                 if (num_flags_printed > 0) 
85                         IAPuts(" ");
86                 IAPuts("\\Recent");
87                 ++num_flags_printed;
88         }
89         IAPuts(")");
90 }
91
92
93 void imap_fetch_internaldate(struct CtdlMessage *msg) {
94         char datebuf[64];
95         time_t msgdate;
96
97         if (!msg) return;
98         if (!CM_IsEmpty(msg, eTimestamp)) {
99                 msgdate = atol(msg->cm_fields[eTimestamp]);
100         }
101         else {
102                 msgdate = time(NULL);
103         }
104
105         datestring(datebuf, sizeof datebuf, msgdate, DATESTRING_IMAP);
106         IAPrintf( "INTERNALDATE \"%s\"", datebuf);
107 }
108
109
110 /*
111  * Fetch RFC822-formatted messages.
112  *
113  * 'whichfmt' should be set to one of:
114  *      "RFC822"        entire message
115  *      "RFC822.HEADER" headers only (with trailing blank line)
116  *      "RFC822.SIZE"   size of translated message
117  *      "RFC822.TEXT"   body only (without leading blank line)
118  */
119 void imap_fetch_rfc822(long msgnum, const char *whichfmt) {
120         citimap *Imap = IMAP;
121         const char *ptr = NULL;
122         size_t headers_size, text_size, total_size;
123         size_t bytes_to_send = 0;
124         struct MetaData smi;
125         int need_to_rewrite_metadata = 0;
126         int need_body = 0;
127
128         /* Determine whether this particular fetch operation requires
129          * us to fetch the message body from disk.  If not, we can save
130          * on some disk operations...
131          */
132         if ( (!strcasecmp(whichfmt, "RFC822")) || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
133                 need_body = 1;
134         }
135
136         /* If this is an RFC822.SIZE fetch, first look in the message's
137          * metadata record to see if we've saved that information.
138          */
139         if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
140                 GetMetaData(&smi, msgnum);
141                 if (smi.meta_rfc822_length > 0L) {
142                         IAPrintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
143                         return;
144                 }
145                 need_to_rewrite_metadata = 1;
146                 need_body = 1;
147         }
148         
149         /* Cache the most recent RFC822 FETCH because some clients like to
150          * fetch in pieces, and we don't want to have to go back to the
151          * message store for each piece.  We also burn the cache if the
152          * client requests something that involves reading the message
153          * body, but we haven't fetched the body yet.
154          */
155         if ((Imap->cached_rfc822 != NULL)
156            && (Imap->cached_rfc822_msgnum == msgnum)
157            && (Imap->cached_rfc822_withbody || (!need_body)) ) {
158                 /* Good to go! */
159         }
160         else if (Imap->cached_rfc822 != NULL) {
161                 /* Some other message is cached -- free it */
162                 FreeStrBuf(&Imap->cached_rfc822);
163                 Imap->cached_rfc822_msgnum = (-1);
164         }
165
166         /* At this point, we now can fetch and convert the message iff it's not
167          * the one we had cached.
168          */
169         if (Imap->cached_rfc822 == NULL) {
170                 /*
171                  * Load the message into memory for translation & measurement
172                  */
173                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
174                 CtdlOutputMsg(msgnum, MT_RFC822,
175                         (need_body ? HEADERS_ALL : HEADERS_FAST),
176                         0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL
177                 );
178                 if (!need_body) {
179                         client_write(HKEY("\r\n"));             // extra trailing newline -- to the redirect_buffer, *not* to the client
180                 }
181                 Imap->cached_rfc822 = CC->redirect_buffer;
182                 CC->redirect_buffer = NULL;
183                 Imap->cached_rfc822_msgnum = msgnum;
184                 Imap->cached_rfc822_withbody = need_body;
185                 if ( (need_to_rewrite_metadata) && 
186                      (StrLength(Imap->cached_rfc822) > 0) ) {
187                         smi.meta_rfc822_length = StrLength(Imap->cached_rfc822);
188                         PutMetaData(&smi);
189                 }
190         }
191
192         /*
193          * Now figure out where the headers/text break is.  IMAP considers the
194          * intervening blank line to be part of the headers, not the text.
195          */
196         headers_size = 0;
197
198         if (need_body) {
199                 StrBuf *Line = NewStrBuf();
200                 ptr = NULL;
201                 do {
202                         StrBufSipLine(Line, Imap->cached_rfc822, &ptr);
203
204                         if ((StrLength(Line) != 0)  && (ptr != StrBufNOTNULL))
205                         {
206                                 StrBufTrim(Line);
207                                 if ((StrLength(Line) != 0) && 
208                                     (ptr != StrBufNOTNULL)    )
209                                 {
210                                         headers_size = ptr - ChrPtr(Imap->cached_rfc822);
211                                 }
212                         }
213                 } while ( (headers_size == 0)    && 
214                           (ptr != StrBufNOTNULL) );
215
216                 total_size = StrLength(Imap->cached_rfc822);
217                 text_size = total_size - headers_size;
218                 FreeStrBuf(&Line);
219         }
220         else {
221                 headers_size = total_size = StrLength(Imap->cached_rfc822);
222                 text_size = 0;
223         }
224
225         syslog(LOG_DEBUG, "imap: RFC822 headers=" SIZE_T_FMT ", text=" SIZE_T_FMT ", total=" SIZE_T_FMT, headers_size, text_size, total_size);
226
227         if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
228                 IAPrintf("RFC822.SIZE " SIZE_T_FMT, total_size);
229                 return;
230         }
231
232         else if (!strcasecmp(whichfmt, "RFC822")) {
233                 ptr = ChrPtr(Imap->cached_rfc822);
234                 bytes_to_send = total_size;
235         }
236
237         else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
238                 ptr = ChrPtr(Imap->cached_rfc822);
239                 bytes_to_send = headers_size;
240         }
241
242         else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
243                 ptr = &ChrPtr(Imap->cached_rfc822)[headers_size];
244                 bytes_to_send = text_size;
245         }
246
247         IAPrintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send);
248         iaputs(ptr, bytes_to_send);
249 }
250
251
252 /*
253  * Load a specific part of a message into the temp file to be output to a
254  * client.  FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
255  * but we still can't handle "2.HEADER" (which might not be a problem).
256  *
257  * Note: mime_parser() was called with dont_decode set to 1, so we have the
258  * luxury of simply spewing without having to re-encode.
259  */
260 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
261                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
262                     char *cbid, void *cbuserdata)
263 {
264         char mimebuf2[SIZ];
265         StrBuf *desired_section;
266
267         desired_section = (StrBuf *)cbuserdata;
268         syslog(LOG_DEBUG, "imap: imap_load_part() looking for %s, found %s", ChrPtr(desired_section), partnum);
269
270         if (!strcasecmp(partnum, ChrPtr(desired_section))) {
271                 client_write(content, length);
272         }
273
274         snprintf(mimebuf2, sizeof mimebuf2, "%s.MIME", partnum);
275
276         if (!strcasecmp(ChrPtr(desired_section), mimebuf2)) {
277                 client_write(HKEY("Content-type: "));
278                 client_write(cbtype, strlen(cbtype));
279                 if (!IsEmptyStr(cbcharset)) {
280                         client_write(HKEY("; charset=\""));
281                         client_write(cbcharset, strlen(cbcharset));
282                         client_write(HKEY("\""));
283                 }
284                 if (!IsEmptyStr(name)) {
285                         client_write(HKEY("; name=\""));
286                         client_write(name, strlen(name));
287                         client_write(HKEY("\""));
288                 }
289                 client_write(HKEY("\r\n"));
290                 if (!IsEmptyStr(encoding)) {
291                         client_write(HKEY("Content-Transfer-Encoding: "));
292                         client_write(encoding, strlen(encoding));
293                         client_write(HKEY("\r\n"));
294                 }
295                 if (!IsEmptyStr(encoding)) {
296                         client_write(HKEY("Content-Disposition: "));
297                         client_write(disp, strlen(disp));
298                 
299                         if (!IsEmptyStr(filename)) {
300                                 client_write(HKEY("; filename=\""));
301                                 client_write(filename, strlen(filename));
302                                 client_write(HKEY("\""));
303                         }
304                         client_write(HKEY("\r\n"));
305                 }
306                 cprintf("Content-Length: %ld\r\n\r\n", (long)length);
307         }
308 }
309
310
311 /* 
312  * Called by imap_fetch_envelope() to output the "From" field.
313  * This is in its own function because its logic is kind of complex.  We
314  * really need to make this suck less.
315  */
316 void imap_output_envelope_from(struct CtdlMessage *msg) {
317         char user[SIZ], node[SIZ], name[SIZ];
318
319         if (!msg) return;
320
321         /* For anonymous messages, it's so easy! */
322         if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
323                 IAPuts("((\"----\" NIL \"x\" \"x.org\")) ");
324                 return;
325         }
326         if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
327                 IAPuts("((\"anonymous\" NIL \"x\" \"x.org\")) ");
328                 return;
329         }
330
331         /* For everything else, we do stuff. */
332         IAPuts("((");                           // open double-parens
333         IPutMsgField(eAuthor);                  // display name
334         IAPuts(" NIL ");                        // source route (not used)
335
336         if (!CM_IsEmpty(msg, erFc822Addr)) {
337                 process_rfc822_addr(msg->cm_fields[erFc822Addr], user, node, name);
338                 IPutStr(user, strlen(user));                    /* mailbox name (user id) */
339                 IAPuts(" ");
340                 IPutStr(node, strlen(node));                    /* host name */
341         }
342         else {
343                 IPutMsgField(eAuthor);                          /* Make up a synthetic address */
344                 IAPuts(" ");
345                 IPutStr(CtdlGetConfigStr("c_fqdn"), strlen(CtdlGetConfigStr("c_fqdn")));
346         }
347         
348         IAPuts(")) "); /* close double-parens */
349 }
350
351
352 /*
353  * Output an envelope address (or set of addresses) in the official,
354  * convoluted, braindead format.  (Note that we can't use this for
355  * the "From" address because its data may come from a number of different
356  * fields.  But we can use it for "To" and possibly others.
357  */
358 void imap_output_envelope_addr(char *addr) {
359         char individual_addr[256];
360         int num_addrs;
361         int i;
362         char user[256];
363         char node[256];
364         char name[256];
365
366         if (addr == NULL) {
367                 IAPuts("NIL ");
368                 return;
369         }
370
371         if (IsEmptyStr(addr)) {
372                 IAPuts("NIL ");
373                 return;
374         }
375
376         IAPuts("(");
377
378         /* How many addresses are listed here? */
379         num_addrs = num_tokens(addr, ',');
380
381         /* Output them one by one. */
382         for (i=0; i<num_addrs; ++i) {
383                 extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
384                 string_trim(individual_addr);
385                 process_rfc822_addr(individual_addr, user, node, name);
386                 IAPuts("(");
387                 IPutStr(name, strlen(name));
388                 IAPuts(" NIL ");
389                 IPutStr(user, strlen(user));
390                 IAPuts(" ");
391                 IPutStr(node, strlen(node));
392                 IAPuts(")");
393                 if (i < (num_addrs-1)) 
394                         IAPuts(" ");
395         }
396
397         IAPuts(") ");
398 }
399
400
401 /*
402  * Implements the ENVELOPE fetch item
403  * 
404  * Note that the imap_strout() function can cleverly output NULL fields as NIL,
405  * so we don't have to check for that condition like we do elsewhere.
406  */
407 void imap_fetch_envelope(struct CtdlMessage *msg) {
408         char datestringbuf[SIZ];
409         time_t msgdate;
410         char *fieldptr = NULL;
411         long len;
412
413         if (!msg) return;
414
415         /* Parse the message date into an IMAP-format date string */
416         if (!CM_IsEmpty(msg, eTimestamp)) {
417                 msgdate = atol(msg->cm_fields[eTimestamp]);
418         }
419         else {
420                 msgdate = time(NULL);
421         }
422         len = datestring(datestringbuf, sizeof datestringbuf, msgdate, DATESTRING_IMAP);
423
424         /* Now start spewing data fields.  The order is important, as it is
425          * defined by the protocol specification.  Nonexistent fields must
426          * be output as NIL, existent fields must be quoted or literalled.
427          * The imap_strout() function conveniently does all this for us.
428          */
429         IAPuts("ENVELOPE (");
430
431         /* Date */
432         IPutStr(datestringbuf, len);
433         IAPuts(" ");
434
435         /* Subject */
436         IPutMsgField(eMsgSubject);
437         IAPuts(" ");
438
439         /* From */
440         imap_output_envelope_from(msg);
441
442         /* Sender (default to same as 'From' if not present) */
443         fieldptr = rfc822_fetch_field(msg->cm_fields[eMessageText], "Sender");
444         if (fieldptr != NULL) {
445                 imap_output_envelope_addr(fieldptr);
446                 free(fieldptr);
447         }
448         else {
449                 imap_output_envelope_from(msg);
450         }
451
452         /* Reply-to */
453         fieldptr = rfc822_fetch_field(msg->cm_fields[eMessageText], "Reply-to");
454         if (fieldptr != NULL) {
455                 imap_output_envelope_addr(fieldptr);
456                 free(fieldptr);
457         }
458         else {
459                 imap_output_envelope_from(msg);
460         }
461
462         /* To */
463         imap_output_envelope_addr(msg->cm_fields[eRecipient]);
464
465         /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
466         fieldptr = msg->cm_fields[eCarbonCopY];
467         if (fieldptr != NULL) {
468                 imap_output_envelope_addr(fieldptr);
469         }
470         else {
471                 fieldptr = rfc822_fetch_field(msg->cm_fields[eMessageText], "Cc");
472                 imap_output_envelope_addr(fieldptr);
473                 if (fieldptr != NULL) free(fieldptr);
474         }
475
476         /* Bcc */
477         fieldptr = rfc822_fetch_field(msg->cm_fields[eMessageText], "Bcc");
478         imap_output_envelope_addr(fieldptr);
479         if (fieldptr != NULL) free(fieldptr);
480
481         /* In-reply-to */
482         fieldptr = rfc822_fetch_field(msg->cm_fields[eMessageText], "In-reply-to");
483         IPutStr(fieldptr, (fieldptr)?strlen(fieldptr):0);
484         IAPuts(" ");
485         if (fieldptr != NULL) free(fieldptr);
486
487         /* message ID */
488         len = msg->cm_lengths[emessageId];
489         
490         if ((len == 0) || (
491                 (msg->cm_fields[emessageId][0] == '<') && 
492                 (msg->cm_fields[emessageId][len - 1] == '>'))
493         ) {
494                 IPutMsgField(emessageId);
495         }
496         else 
497         {
498                 char *Buf = malloc(len + 3);
499                 long pos = 0;
500                 
501                 if (msg->cm_fields[emessageId][0] != '<')
502                 {
503                         Buf[pos] = '<';
504                         pos ++;
505                 }
506                 memcpy(&Buf[pos], msg->cm_fields[emessageId], len);
507                 pos += len;
508                 if (msg->cm_fields[emessageId][len] != '>')
509                 {
510                         Buf[pos] = '>';
511                         pos++;
512                 }
513                 Buf[pos] = '\0';
514                 IPutStr(Buf, pos);
515                 free(Buf);
516         }
517         IAPuts(")");
518 }
519
520
521 /*
522  * This function is called only when CC->redirect_buffer contains a set of
523  * RFC822 headers with no body attached.  Its job is to strip that set of
524  * headers down to *only* the ones we're interested in.
525  */
526 void imap_strip_headers(StrBuf *section) {
527         citimap_command Cmd;
528         StrBuf *which_fields = NULL;
529         int doing_headers = 0;
530         int headers_not = 0;
531         int num_parms = 0;
532         int i;
533         StrBuf *boiled_headers = NULL;
534         StrBuf *Line;
535         int ok = 0;
536         int done_headers = 0;
537         const char *Ptr = NULL;
538
539         if (CC->redirect_buffer == NULL) return;
540
541         which_fields = NewStrBufDup(section);
542
543         if (!strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS", 13))
544                 doing_headers = 1;
545         if (doing_headers && 
546             !strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS.NOT", 17))
547                 headers_not = 1;
548
549         for (i=0; i < StrLength(which_fields); ++i) {
550                 if (ChrPtr(which_fields)[i]=='(')
551                         StrBufReplaceToken(which_fields, i, 1, HKEY(""));
552         }
553         for (i=0; i < StrLength(which_fields); ++i) {
554                 if (ChrPtr(which_fields)[i]==')') {
555                         StrBufCutAt(which_fields, i, NULL);
556                         break;
557                 }
558         }
559         memset(&Cmd, 0, sizeof(citimap_command));
560         Cmd.CmdBuf = which_fields;
561         num_parms = imap_parameterize(&Cmd);
562
563         boiled_headers = NewStrBufPlain(NULL, StrLength(CC->redirect_buffer));
564         Line = NewStrBufPlain(NULL, SIZ);
565         Ptr = NULL;
566         ok = 0;
567         do {
568                 StrBufSipLine(Line, CC->redirect_buffer, &Ptr);
569
570                 if (!isspace(ChrPtr(Line)[0])) {
571
572                         if (doing_headers == 0) ok = 1;
573                         else {
574                                 /* we're supposed to print all headers that are not matching the filter list */
575                                 if (headers_not) for (i=0, ok = 1; (i < num_parms) && (ok == 1); ++i) {
576                                                 if ( (!strncasecmp(ChrPtr(Line), 
577                                                                    Cmd.Params[i].Key,
578                                                                    Cmd.Params[i].len)) &&
579                                                      (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
580                                                         ok = 0;
581                                                 }
582                                 }
583                                 /* we're supposed to print all headers matching the filterlist */
584                                 else for (i=0, ok = 0; ((i < num_parms) && (ok == 0)); ++i) {
585                                                 if ( (!strncasecmp(ChrPtr(Line), 
586                                                                    Cmd.Params[i].Key,
587                                                                    Cmd.Params[i].len)) &&
588                                                      (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
589                                                         ok = 1;
590                                         }
591                                 }
592                         }
593                 }
594
595                 if (ok) {
596                         StrBufAppendBuf(boiled_headers, Line, 0);
597                         StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
598                 }
599
600                 if ((Ptr == StrBufNOTNULL)  ||
601                     (StrLength(Line) == 0)  ||
602                     (ChrPtr(Line)[0]=='\r') ||
603                     (ChrPtr(Line)[0]=='\n')   ) done_headers = 1;
604         } while (!done_headers);
605
606         StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
607
608         /* Now save it back (it'll always be smaller) */
609         FreeStrBuf(&CC->redirect_buffer);
610         CC->redirect_buffer = boiled_headers;
611
612         free(Cmd.Params);
613         FreeStrBuf(&which_fields);
614         FreeStrBuf(&Line);
615 }
616
617
618 /*
619  * Implements the BODY and BODY.PEEK fetch items
620  */
621 void imap_fetch_body(long msgnum, ConstStr item, int is_peek) {
622         struct CtdlMessage *msg = NULL;
623         StrBuf *section;
624         StrBuf *partial;
625         int is_partial = 0;
626         size_t pstart, pbytes;
627         int loading_body_now = 0;
628         int need_body = 1;
629         int burn_the_cache = 0;
630         citimap *Imap = IMAP;
631
632         /* extract section */
633         section = NewStrBufPlain(CKEY(item));
634         
635         if (strchr(ChrPtr(section), '[') != NULL) {
636                 StrBufStripAllBut(section, '[', ']');
637         }
638         syslog(LOG_DEBUG, "imap: selected section is [%s]", (StrLength(section) == 0) ? "(empty)" : ChrPtr(section));
639
640         /* Burn the cache if we don't have the same section of the 
641          * same message again.
642          */
643         if (Imap->cached_body != NULL) {
644                 if (Imap->cached_bodymsgnum != msgnum) {
645                         burn_the_cache = 1;
646                 }
647                 else if ( (!Imap->cached_body_withbody) && (need_body) ) {
648                         burn_the_cache = 1;
649                 }
650                 else if (strcasecmp(Imap->cached_bodypart, ChrPtr(section))) {
651                         burn_the_cache = 1;
652                 }
653                 if (burn_the_cache) {
654                         /* Yup, go ahead and burn the cache. */
655                         free(Imap->cached_body);
656                         Imap->cached_body_len = 0;
657                         Imap->cached_body = NULL;
658                         Imap->cached_bodymsgnum = (-1);
659                         strcpy(Imap->cached_bodypart, "");
660                 }
661         }
662
663         /* extract partial */
664         partial = NewStrBufPlain(CKEY(item));
665         if (strchr(ChrPtr(partial), '<') != NULL) {
666                 StrBufStripAllBut(partial, '<', '>');
667                 is_partial = 1;
668         }
669         if ( (is_partial == 1) && (StrLength(partial) > 0) ) {
670                 syslog(LOG_DEBUG, "imap: selected partial is <%s>", ChrPtr(partial));
671         }
672
673         if (Imap->cached_body == NULL) {
674                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
675                 loading_body_now = 1;
676                 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
677         }
678
679         /* Now figure out what the client wants, and get it */
680
681         if (!loading_body_now) {
682                 /* What we want is already in memory */
683         }
684
685         else if ( (!strcmp(ChrPtr(section), "1")) && (msg->cm_format_type != 4) ) {
686                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
687         }
688
689         else if (StrLength(section) == 0) {
690                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, SUPPRESS_ENV_TO);
691         }
692
693         /*
694          * If the client asked for just headers, or just particular header
695          * fields, strip it down.
696          */
697         else if (!strncasecmp(ChrPtr(section), "HEADER", 6)) {
698                 /* This used to work with HEADERS_FAST, but then Apple got stupid with their
699                  * IMAP library and this broke Mail.App and iPhone Mail, so we had to change it
700                  * to HEADERS_ONLY so the trendy hipsters with their iPhones can read mail.
701                  */
702                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, SUPPRESS_ENV_TO);
703                 imap_strip_headers(section);
704         }
705
706         /*
707          * Strip it down if the client asked for everything _except_ headers.
708          */
709         else if (!strncasecmp(ChrPtr(section), "TEXT", 4)) {
710                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
711         }
712
713         /*
714          * Anything else must be a part specifier.
715          * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
716          */
717         else {
718                 mime_parser(CM_RANGE(msg, eMessageText),
719                             *imap_load_part, NULL, NULL,
720                             section,
721                             1
722                         );
723         }
724
725         if (loading_body_now) {
726                 Imap->cached_body_len = StrLength(CC->redirect_buffer);
727                 Imap->cached_body = SmashStrBuf(&CC->redirect_buffer);
728                 Imap->cached_bodymsgnum = msgnum;
729                 Imap->cached_body_withbody = need_body;
730                 strcpy(Imap->cached_bodypart, ChrPtr(section));
731         }
732
733         if (is_partial == 0) {
734                 IAPuts("BODY[");
735                 iaputs(SKEY(section));
736                 IAPrintf("] {" SIZE_T_FMT "}\r\n", Imap->cached_body_len);
737                 pstart = 0;
738                 pbytes = Imap->cached_body_len;
739         }
740         else {
741                 sscanf(ChrPtr(partial), SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
742                 if (pbytes > (Imap->cached_body_len - pstart)) {
743                         pbytes = Imap->cached_body_len - pstart;
744                 }
745                 IAPuts("BODY[");
746                 iaputs(SKEY(section));
747                 IAPrintf("]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", pstart, pbytes);
748         }
749
750         FreeStrBuf(&partial);
751
752         /* Here we go -- output it */
753         iaputs(&Imap->cached_body[pstart], pbytes);
754
755         if (msg != NULL) {
756                 CM_Free(msg);
757         }
758
759         /* Mark this message as "seen" *unless* this is a "peek" operation */
760         if (is_peek == 0) {
761                 CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
762         }
763         FreeStrBuf(&section);
764 }
765
766 /*
767  * Called immediately before outputting a multipart bodystructure
768  */
769 void imap_fetch_bodystructure_pre(
770                 char *name, char *filename, char *partnum, char *disp,
771                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
772                 char *cbid, void *cbuserdata
773                 ) {
774
775         IAPuts("(");
776 }
777
778
779
780 /*
781  * Called immediately after outputting a multipart bodystructure
782  */
783 void imap_fetch_bodystructure_post(
784                 char *name, char *filename, char *partnum, char *disp,
785                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
786                 char *cbid, void *cbuserdata
787                 ) {
788         long len;
789         char subtype[128];
790
791         IAPuts(" ");
792
793         /* disposition */
794         len = extract_token(subtype, cbtype, 1, '/', sizeof subtype);
795         IPutStr(subtype, len);
796
797         /* body language */
798         /* IAPuts(" NIL"); We thought we needed this at one point, but maybe we don't... */
799
800         IAPuts(")");
801 }
802
803
804
805 /*
806  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
807  *
808  */
809 void imap_fetch_bodystructure_part(
810                 char *name, char *filename, char *partnum, char *disp,
811                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
812                 char *cbid, void *cbuserdata
813                 ) {
814
815         int have_cbtype = 0;
816         int have_encoding = 0;
817         int lines = 0;
818         size_t i;
819         char cbmaintype[128];
820         char cbsubtype[128];
821         long cbmaintype_len;
822         long cbsubtype_len;
823
824         if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
825         if (have_cbtype) {
826                 cbmaintype_len = extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
827                 cbsubtype_len = extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
828         }
829         else {
830                 strcpy(cbmaintype, "TEXT");
831                 cbmaintype_len = 4;
832                 strcpy(cbsubtype, "PLAIN");
833                 cbsubtype_len = 5;
834         }
835
836         IAPuts("(");
837         IPutStr(cbmaintype, cbmaintype_len);                    /* body type */
838         IAPuts(" ");
839         IPutStr(cbsubtype, cbsubtype_len);                      /* body subtype */
840         IAPuts(" ");
841
842         IAPuts("(");                                            /* begin body parameter list */
843
844         /* "NAME" must appear as the first parameter.  This is not required by IMAP,
845          * but the Asterisk voicemail application blindly assumes that NAME will be in
846          * the first position.  If it isn't, it rejects the message.
847          */
848         if ((name != NULL) && (!IsEmptyStr(name))) {
849                 IAPuts("\"NAME\" ");
850                 IPutStr(name, strlen(name));
851                 IAPuts(" ");
852         }
853
854         IAPuts("\"CHARSET\" ");
855         if ((cbcharset == NULL) || (cbcharset[0] == 0)){
856                 IPutStr(HKEY("US-ASCII"));
857         }
858         else {
859                 IPutStr(cbcharset, strlen(cbcharset));
860         }
861         IAPuts(") ");                                           /* end body parameter list */
862
863         IAPuts("NIL ");                                         /* Body ID */
864         IAPuts("NIL ");                                         /* Body description */
865
866         if ((encoding != NULL) && (encoding[0] != 0))  have_encoding = 1;
867         if (have_encoding) {
868                 IPutStr(encoding, strlen(encoding));
869         }
870         else {
871                 IPutStr(HKEY("7BIT"));
872         }
873         IAPuts(" ");
874
875         /* The next field is the size of the part in bytes. */
876         IAPrintf("%ld ", (long)length); /* bytes */
877
878         /* The next field is the number of lines in the part, if and only
879          * if the part is TEXT.  More gratuitous complexity.
880          */
881         if (!strcasecmp(cbmaintype, "TEXT")) {
882                 if (length) for (i=0; i<length; ++i) {
883                         if (((char *)content)[i] == '\n') ++lines;
884                 }
885                 IAPrintf("%d ", lines);
886         }
887
888         /* More gratuitous complexity */
889         if ((!strcasecmp(cbmaintype, "MESSAGE"))
890            && (!strcasecmp(cbsubtype, "RFC822"))) {
891                 /* FIXME: message/rfc822 also needs to output the envelope structure,
892                  * body structure, and line count of the encapsulated message.  Fortunately
893                  * there are not yet any clients depending on this, so we can get away
894                  * with not implementing it for now.
895                  */
896         }
897
898         /* MD5 value of body part; we can get away with NIL'ing this */
899         IAPuts("NIL ");
900
901         /* Disposition */
902         if ((disp == NULL) || IsEmptyStr(disp)) {
903                 IAPuts("NIL");
904         }
905         else {
906                 IAPuts("(");
907                 IPutStr(disp, strlen(disp));
908                 if ((filename != NULL) && (!IsEmptyStr(filename))) {
909                         IAPuts(" (\"FILENAME\" ");
910                         IPutStr(filename, strlen(filename));
911                         IAPuts(")");
912                 }
913                 IAPuts(")");
914         }
915
916         /* Body language (not defined yet) */
917         IAPuts(" NIL)");
918 }
919
920
921
922 /*
923  * Spew the BODYSTRUCTURE data for a message.
924  *
925  */
926 void imap_fetch_bodystructure (long msgnum, const char *item,
927                 struct CtdlMessage *msg) {
928         const char *rfc822 = NULL;
929         const char *rfc822_body = NULL;
930         size_t rfc822_len;
931         size_t rfc822_headers_len;
932         size_t rfc822_body_len;
933         const char *ptr = NULL;
934         char *pch;
935         char buf[SIZ];
936         int lines = 0;
937
938         /* Handle NULL message gracefully */
939         if (msg == NULL) {
940                 IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
941                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
942                         "\"7BIT\" 0 0)");
943                 return;
944         }
945
946         /* For non-RFC822 (ordinary Citadel) messages, this is short and
947          * sweet...
948          */
949         if (msg->cm_format_type != FMT_RFC822) {
950
951                 /* *sigh* We have to RFC822-format the message just to be able
952                  * to measure it.  FIXME use smi cached fields if possible
953                  */
954
955                 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
956                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, SUPPRESS_ENV_TO);
957                 rfc822_len = StrLength(CC->redirect_buffer);
958                 rfc822 = pch = SmashStrBuf(&CC->redirect_buffer);
959
960                 ptr = rfc822;
961                 do {
962                         ptr = cmemreadline(ptr, buf, sizeof buf);
963                         ++lines;
964                         if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
965                                 rfc822_body = ptr;
966                         }
967                 } while (*ptr != 0);
968
969                 rfc822_headers_len = rfc822_body - rfc822;
970                 rfc822_body_len = rfc822_len - rfc822_headers_len;
971                 free(pch);
972
973                 IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
974                        "(\"CHARSET\" \"US-ASCII\") NIL NIL "
975                        "\"7BIT\" ");
976                 IAPrintf(SIZE_T_FMT " %d)", rfc822_body_len, lines);
977
978                 return;
979         }
980
981         /* For messages already stored in RFC822 format, we have to parse. */
982         IAPuts("BODYSTRUCTURE ");
983         mime_parser(CM_RANGE(msg, eMessageText),
984                     *imap_fetch_bodystructure_part,     /* part */
985                     *imap_fetch_bodystructure_pre,      /* pre-multi */
986                     *imap_fetch_bodystructure_post,     /* post-multi */
987                     NULL,
988                     1); /* don't decode -- we want it as-is */
989 }
990
991
992 /*
993  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
994  * individual message, once it has been selected for output.
995  */
996 void imap_do_fetch_msg(int seq, citimap_command *Cmd) {
997         int i;
998         citimap *Imap = IMAP;
999         struct CtdlMessage *msg = NULL;
1000         int body_loaded = 0;
1001
1002         /* Don't attempt to fetch bogus messages or UID's */
1003         if (seq < 1) return;
1004         if (Imap->msgids[seq-1] < 1L) return;
1005
1006         buffer_output();
1007         IAPrintf("* %d FETCH (", seq);
1008
1009         for (i=0; i<Cmd->num_parms; ++i) {
1010
1011                 /* Fetchable without going to the message store at all */
1012                 if (!strcasecmp(Cmd->Params[i].Key, "UID")) {
1013                         imap_fetch_uid(seq);
1014                 }
1015                 else if (!strcasecmp(Cmd->Params[i].Key, "FLAGS")) {
1016                         imap_fetch_flags(seq-1);
1017                 }
1018
1019                 /* Potentially fetchable from cache, if the client requests
1020                  * stuff from the same message several times in a row.
1021                  */
1022                 else if (!strcasecmp(Cmd->Params[i].Key, "RFC822")) {
1023                         imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
1024                 }
1025                 else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.HEADER")) {
1026                         imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
1027                 }
1028                 else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.SIZE")) {
1029                         imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
1030                 }
1031                 else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.TEXT")) {
1032                         imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
1033                 }
1034
1035                 /* BODY fetches do their own fetching and caching too. */
1036                 else if (!strncasecmp(Cmd->Params[i].Key, "BODY[", 5)) {
1037                         imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 0);
1038                 }
1039                 else if (!strncasecmp(Cmd->Params[i].Key, "BODY.PEEK[", 10)) {
1040                         imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 1);
1041                 }
1042
1043                 /* Otherwise, load the message into memory.
1044                  */
1045                 else if (!strcasecmp(Cmd->Params[i].Key, "BODYSTRUCTURE")) {
1046                         if ((msg != NULL) && (!body_loaded)) {
1047                                 CM_Free(msg);   /* need the whole thing */
1048                                 msg = NULL;
1049                         }
1050                         if (msg == NULL) {
1051                                 msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
1052                                 body_loaded = 1;
1053                         }
1054                         imap_fetch_bodystructure(Imap->msgids[seq-1],
1055                                         Cmd->Params[i].Key, msg);
1056                 }
1057                 else if (!strcasecmp(Cmd->Params[i].Key, "ENVELOPE")) {
1058                         if (msg == NULL) {
1059                                 msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
1060                                 body_loaded = 0;
1061                         }
1062                         imap_fetch_envelope(msg);
1063                 }
1064                 else if (!strcasecmp(Cmd->Params[i].Key, "INTERNALDATE")) {
1065                         if (msg == NULL) {
1066                                 msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
1067                                 body_loaded = 0;
1068                         }
1069                         imap_fetch_internaldate(msg);
1070                 }
1071
1072                 if (i != Cmd->num_parms-1) IAPuts(" ");
1073         }
1074
1075         IAPuts(")\r\n");
1076         unbuffer_output();
1077         if (msg != NULL) {
1078                 CM_Free(msg);
1079         }
1080 }
1081
1082
1083
1084 /*
1085  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1086  * validated and boiled down the request a bit.
1087  */
1088 void imap_do_fetch(citimap_command *Cmd) {
1089         citimap *Imap = IMAP;
1090         int i;
1091 #if 0
1092 /* debug output the parsed vector */
1093         {
1094                 int i;
1095                 syslog(LOG_DEBUG, "imap: ----- %ld params", Cmd->num_parms);
1096
1097         for (i=0; i < Cmd->num_parms; i++) {
1098                 if (Cmd->Params[i].len != strlen(Cmd->Params[i].Key))
1099                         syslog(LOG_DEBUG, "imap: *********** %ld != %ld : %s",
1100                                     Cmd->Params[i].len, 
1101                                     strlen(Cmd->Params[i].Key),
1102                                     Cmd->Params[i].Key);
1103                 else
1104                         syslog(LOG_DEBUG, "imap: %ld : %s",
1105                                     Cmd->Params[i].len, 
1106                                     Cmd->Params[i].Key);
1107         }}
1108
1109 #endif
1110
1111         if (Imap->num_msgs > 0) {
1112                 for (i = 0; i < Imap->num_msgs; ++i) {
1113
1114                         /* Abort the fetch loop if the session breaks.
1115                          * This is important for users who keep mailboxes
1116                          * that are too big *and* are too impatient to
1117                          * let them finish loading.  :)
1118                          */
1119                         if (CC->kill_me) return;
1120
1121                         /* Get any message marked for fetch. */
1122                         if (Imap->flags[i] & IMAP_SELECTED) {
1123                                 imap_do_fetch_msg(i+1, Cmd);
1124                         }
1125                 }
1126         }
1127 }
1128
1129
1130
1131 /*
1132  * Back end for imap_handle_macros()
1133  * Note that this function *only* looks at the beginning of the string.  It
1134  * is not a generic search-and-replace function.
1135  */
1136 void imap_macro_replace(StrBuf *Buf, long where, 
1137                         StrBuf *TmpBuf,
1138                         char *find, long findlen, 
1139                         char *replace, long replacelen) 
1140 {
1141
1142         if (StrLength(Buf) - where > findlen)
1143                 return;
1144
1145         if (!strncasecmp(ChrPtr(Buf) + where, find, findlen)) {
1146                 if (ChrPtr(Buf)[where + findlen] == ' ') {
1147                         StrBufPlain(TmpBuf, replace, replacelen);
1148                         StrBufAppendBufPlain(TmpBuf, HKEY(" "), 0);
1149                         StrBufReplaceToken(Buf, where, findlen, 
1150                                            SKEY(TmpBuf));
1151                 }
1152                 if (where + findlen == StrLength(Buf)) {
1153                         StrBufReplaceToken(Buf, where, findlen, 
1154                                            replace, replacelen);
1155                 }
1156         }
1157 }
1158
1159
1160
1161 /*
1162  * Handle macros embedded in FETCH data items.
1163  * (What the heck are macros doing in a wire protocol?  Are we trying to save
1164  * the computer at the other end the trouble of typing a lot of characters?)
1165  */
1166 void imap_handle_macros(citimap_command *Cmd) {
1167         long i;
1168         int nest = 0;
1169         StrBuf *Tmp = NewStrBuf();
1170
1171         for (i=0; i < StrLength(Cmd->CmdBuf); ++i) {
1172                 char ch = ChrPtr(Cmd->CmdBuf)[i];
1173                 if ((ch=='(') ||
1174                     (ch=='[') ||
1175                     (ch=='<') ||
1176                     (ch=='{')) ++nest;
1177                 else if ((ch==')') ||
1178                          (ch==']') ||
1179                          (ch=='>') ||
1180                          (ch=='}')) --nest;
1181
1182                 if (nest <= 0) {
1183                         imap_macro_replace(Cmd->CmdBuf, i,
1184                                            Tmp, 
1185                                            HKEY("ALL"),
1186                                            HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE")
1187                         );
1188                         imap_macro_replace(Cmd->CmdBuf, i,
1189                                            Tmp, 
1190                                            HKEY("BODY"),
1191                                            HKEY("BODYSTRUCTURE")
1192                         );
1193                         imap_macro_replace(Cmd->CmdBuf, i,
1194                                            Tmp, 
1195                                            HKEY("FAST"),
1196                                            HKEY("FLAGS INTERNALDATE RFC822.SIZE")
1197                         );
1198                         imap_macro_replace(Cmd->CmdBuf, i,
1199                                            Tmp, 
1200                                            HKEY("FULL"),
1201                                            HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY")
1202                         );
1203                 }
1204         }
1205         FreeStrBuf(&Tmp);
1206 }
1207
1208
1209 /*
1210  * Break out the data items requested, possibly a parenthesized list.
1211  * Returns the number of data items, or -1 if the list is invalid.
1212  * NOTE: this function alters the string it is fed, and uses it as a buffer
1213  * to hold the data for the pointers it returns.
1214  */
1215 int imap_extract_data_items(citimap_command *Cmd) 
1216 {
1217         int nArgs;
1218         int nest = 0;
1219         const char *pch, *end;
1220
1221         /* Convert all whitespace to ordinary space characters. */
1222         pch = ChrPtr(Cmd->CmdBuf);
1223         end = pch + StrLength(Cmd->CmdBuf);
1224
1225         while (pch < end)
1226         {
1227                 if (isspace(*pch)) 
1228                         StrBufPeek(Cmd->CmdBuf, pch, 0, ' ');
1229                 pch++;
1230         }
1231
1232         /* Strip leading and trailing whitespace, then strip leading and
1233          * trailing parentheses if it's a list
1234          */
1235         StrBufTrim(Cmd->CmdBuf);
1236         pch = ChrPtr(Cmd->CmdBuf);
1237         if ( (pch[0]=='(') && 
1238              (pch[StrLength(Cmd->CmdBuf)-1]==')') ) 
1239         {
1240                 StrBufCutRight(Cmd->CmdBuf, 1);
1241                 StrBufCutLeft(Cmd->CmdBuf, 1);
1242                 StrBufTrim(Cmd->CmdBuf);
1243         }
1244
1245         /* Parse any macro data items */
1246         imap_handle_macros(Cmd);
1247
1248         /*
1249          * Now break out the data items.  We throw in one trailing space in
1250          * order to avoid having to break out the last one manually.
1251          */
1252         nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
1253         nArgs = CmdAdjust(Cmd, nArgs, 0);
1254         Cmd->num_parms = 0;
1255         Cmd->Params[Cmd->num_parms].Key = pch = ChrPtr(Cmd->CmdBuf);
1256         end = Cmd->Params[Cmd->num_parms].Key + StrLength(Cmd->CmdBuf);
1257
1258         while (pch < end) 
1259         {
1260                 if ((*pch=='(') ||
1261                     (*pch=='[') ||
1262                     (*pch=='<') ||
1263                     (*pch=='{'))
1264                         ++nest;
1265
1266                 else if ((*pch==')') ||
1267                          (*pch==']') ||
1268                          (*pch=='>') ||
1269                          (*pch=='}'))
1270                         --nest;
1271
1272                 if ((nest <= 0) && (*pch==' ')) {
1273                         StrBufPeek(Cmd->CmdBuf, pch, 0, '\0');
1274                         Cmd->Params[Cmd->num_parms].len = 
1275                                 pch - Cmd->Params[Cmd->num_parms].Key;
1276
1277                         if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
1278                                 nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
1279                         }
1280                         Cmd->num_parms++;                       
1281                         Cmd->Params[Cmd->num_parms].Key = ++pch;
1282                 }
1283                 else if (pch + 1 == end) {
1284                         Cmd->Params[Cmd->num_parms].len = 
1285                                 pch - Cmd->Params[Cmd->num_parms].Key + 1;
1286
1287                         Cmd->num_parms++;                       
1288                 }
1289                 pch ++;
1290         }
1291         return Cmd->num_parms;
1292
1293 }
1294
1295
1296 /*
1297  * One particularly hideous aspect of IMAP is that we have to allow the client
1298  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1299  * handles this by setting the IMAP_SELECTED flag for each message specified in
1300  * the ranges/sets, then looping through the message array, outputting messages
1301  * with the flag set.  We don't bother returning an error if an out-of-range
1302  * number is specified (we just return quietly) because any client braindead
1303  * enough to request a bogus message number isn't going to notice the
1304  * difference anyway.
1305  *
1306  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1307  * message included in the specified range.
1308  *
1309  * Set is_uid to 1 to fetch by UID instead of sequence number.
1310  */
1311 void imap_pick_range(const char *supplied_range, int is_uid) {
1312         citimap *Imap = IMAP;
1313         int i;
1314         int num_sets;
1315         int s;
1316         char setstr[SIZ], lostr[SIZ], histr[SIZ];
1317         long lo, hi;
1318         char actual_range[SIZ];
1319
1320         /* 
1321          * Handle the "ALL" macro
1322          */
1323         if (!strcasecmp(supplied_range, "ALL")) {
1324                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1325         }
1326         else {
1327                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1328         }
1329
1330         /*
1331          * Clear out the IMAP_SELECTED flags for all messages.
1332          */
1333         for (i = 0; i < Imap->num_msgs; ++i) {
1334                 Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
1335         }
1336
1337         /*
1338          * Now set it for all specified messages.
1339          */
1340         num_sets = num_tokens(actual_range, ',');
1341         for (s=0; s<num_sets; ++s) {
1342                 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1343
1344                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1345                 if (num_tokens(setstr, ':') >= 2) {
1346                         extract_token(histr, setstr, 1, ':', sizeof histr);
1347                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1348                 } 
1349                 else {
1350                         safestrncpy(histr, lostr, sizeof histr);
1351                 }
1352                 lo = atol(lostr);
1353                 hi = atol(histr);
1354
1355                 /* Loop through the array, flipping bits where appropriate */
1356                 for (i = 1; i <= Imap->num_msgs; ++i) {
1357                         if (is_uid) {   /* fetch by sequence number */
1358                                 if ( (Imap->msgids[i-1]>=lo)
1359                                    && (Imap->msgids[i-1]<=hi)) {
1360                                         Imap->flags[i-1] |= IMAP_SELECTED;
1361                                 }
1362                         }
1363                         else {          /* fetch by uid */
1364                                 if ( (i>=lo) && (i<=hi)) {
1365                                         Imap->flags[i-1] |= IMAP_SELECTED;
1366                                 }
1367                         }
1368                 }
1369         }
1370 }
1371
1372
1373
1374 /*
1375  * This function is called by the main command loop.
1376  */
1377 void imap_fetch(int num_parms, ConstStr *Params) {
1378         citimap_command Cmd;
1379         int num_items;
1380         
1381         if (num_parms < 4) {
1382                 IReply("BAD invalid parameters");
1383                 return;
1384         }
1385
1386         imap_pick_range(Params[2].Key, 0);
1387
1388         memset(&Cmd, 0, sizeof(citimap_command));
1389         Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
1390         MakeStringOf(Cmd.CmdBuf, 3);
1391
1392         num_items = imap_extract_data_items(&Cmd);
1393         if (num_items < 1) {
1394                 IReply("BAD invalid data item list");
1395                 FreeStrBuf(&Cmd.CmdBuf);
1396                 free(Cmd.Params);
1397                 return;
1398         }
1399
1400         imap_do_fetch(&Cmd);
1401         IReply("OK FETCH completed");
1402         FreeStrBuf(&Cmd.CmdBuf);
1403         free(Cmd.Params);
1404 }
1405
1406 /*
1407  * This function is called by the main command loop.
1408  */
1409 void imap_uidfetch(int num_parms, ConstStr *Params) {
1410         citimap_command Cmd;
1411         int num_items;
1412         int i;
1413         int have_uid_item = 0;
1414
1415         if (num_parms < 5) {
1416                 IReply("BAD invalid parameters");
1417                 return;
1418         }
1419
1420         imap_pick_range(Params[3].Key, 1);
1421
1422         memset(&Cmd, 0, sizeof(citimap_command));
1423         Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
1424
1425         MakeStringOf(Cmd.CmdBuf, 4);
1426 #if 0
1427         syslog(LOG_DEBUG, "imap: -------%s--------", ChrPtr(Cmd.CmdBuf));
1428 #endif
1429         num_items = imap_extract_data_items(&Cmd);
1430         if (num_items < 1) {
1431                 IReply("BAD invalid data item list");
1432                 FreeStrBuf(&Cmd.CmdBuf);
1433                 free(Cmd.Params);
1434                 return;
1435         }
1436
1437         /* If the "UID" item was not included, we include it implicitly
1438          * (at the beginning) because this is a UID FETCH command
1439          */
1440         for (i=0; i<num_items; ++i) {
1441                 if (!strcasecmp(Cmd.Params[i].Key, "UID")) ++have_uid_item;
1442         }
1443         if (have_uid_item == 0) {
1444                 if (Cmd.num_parms + 1 >= Cmd.avail_parms)
1445                         CmdAdjust(&Cmd, Cmd.avail_parms + 1, 1);
1446                 memmove(&Cmd.Params[1], 
1447                         &Cmd.Params[0], 
1448                         sizeof(ConstStr) * Cmd.num_parms);
1449
1450                 Cmd.num_parms++;
1451                 Cmd.Params[0] = (ConstStr){HKEY("UID")};
1452         }
1453
1454         imap_do_fetch(&Cmd);
1455         IReply("OK UID FETCH completed");
1456         FreeStrBuf(&Cmd.CmdBuf);
1457         free(Cmd.Params);
1458 }
1459
1460