Back to index

citadel  8.12
imap_fetch.c
Go to the documentation of this file.
00001 /*
00002  * Implements the FETCH command in IMAP.
00003  * This is a good example of the protocol's gratuitous complexity.
00004  *
00005  * Copyright (c) 2001-2011 by the citadel.org team
00006  *
00007  *  This program is open source software; you can redistribute it and/or modify
00008  *  it under the terms of the GNU General Public License as published by
00009  *  the Free Software Foundation; either version 3 of the License, or
00010  *  (at your option) any later version.
00011  *
00012  *  This program is distributed in the hope that it will be useful,
00013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  *  GNU General Public License for more details.
00016  *
00017  *  You should have received a copy of the GNU General Public License
00018  *  along with this program; if not, write to the Free Software
00019  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00020  */
00021 
00022 
00023 #include "sysdep.h"
00024 #include <stdlib.h>
00025 #include <unistd.h>
00026 #include <stdio.h>
00027 #include <fcntl.h>
00028 #include <signal.h>
00029 #include <pwd.h>
00030 #include <errno.h>
00031 #include <sys/types.h>
00032 
00033 #if TIME_WITH_SYS_TIME
00034 # include <sys/time.h>
00035 # include <time.h>
00036 #else
00037 # if HAVE_SYS_TIME_H
00038 #  include <sys/time.h>
00039 # else
00040 #  include <time.h>
00041 # endif
00042 #endif
00043 
00044 #include <sys/wait.h>
00045 #include <ctype.h>
00046 #include <string.h>
00047 #include <limits.h>
00048 #include <libcitadel.h>
00049 #include "citadel.h"
00050 #include "server.h"
00051 #include "sysdep_decls.h"
00052 #include "citserver.h"
00053 #include "support.h"
00054 #include "config.h"
00055 #include "user_ops.h"
00056 #include "database.h"
00057 #include "msgbase.h"
00058 #include "internet_addressing.h"
00059 #include "serv_imap.h"
00060 #include "imap_tools.h"
00061 #include "imap_fetch.h"
00062 #include "genstamp.h"
00063 #include "ctdl_module.h"
00064 
00065 
00066 
00067 /*
00068  * Individual field functions for imap_do_fetch_msg() ...
00069  */
00070 
00071 void imap_fetch_uid(int seq) {
00072        IAPrintf("UID %ld", IMAP->msgids[seq-1]);
00073 }
00074 
00075 void imap_fetch_flags(int seq) 
00076 {
00077        citimap *Imap = IMAP;
00078        int num_flags_printed = 0;
00079        IAPuts("FLAGS (");
00080        if (Imap->flags[seq] & IMAP_DELETED) {
00081               if (num_flags_printed > 0) 
00082                      IAPuts(" ");
00083               IAPuts("\\Deleted");
00084               ++num_flags_printed;
00085        }
00086        if (Imap->flags[seq] & IMAP_SEEN) {
00087               if (num_flags_printed > 0) 
00088                      IAPuts(" ");
00089               IAPuts("\\Seen");
00090               ++num_flags_printed;
00091        }
00092        if (Imap->flags[seq] & IMAP_ANSWERED) {
00093               if (num_flags_printed > 0) 
00094                      IAPuts(" ");
00095               IAPuts("\\Answered");
00096               ++num_flags_printed;
00097        }
00098        if (Imap->flags[seq] & IMAP_RECENT) {
00099               if (num_flags_printed > 0) 
00100                      IAPuts(" ");
00101               IAPuts("\\Recent");
00102               ++num_flags_printed;
00103        }
00104        IAPuts(")");
00105 }
00106 
00107 
00108 void imap_fetch_internaldate(struct CtdlMessage *msg) {
00109        char datebuf[64];
00110        time_t msgdate;
00111 
00112        if (!msg) return;
00113        if (msg->cm_fields['T'] != NULL) {
00114               msgdate = atol(msg->cm_fields['T']);
00115        }
00116        else {
00117               msgdate = time(NULL);
00118        }
00119 
00120        datestring(datebuf, sizeof datebuf, msgdate, DATESTRING_IMAP);
00121        IAPrintf( "INTERNALDATE \"%s\"", datebuf);
00122 }
00123 
00124 
00125 /*
00126  * Fetch RFC822-formatted messages.
00127  *
00128  * 'whichfmt' should be set to one of:
00129  *     "RFC822"      entire message
00130  *     "RFC822.HEADER"      headers only (with trailing blank line)
00131  *     "RFC822.SIZE" size of translated message
00132  *     "RFC822.TEXT" body only (without leading blank line)
00133  */
00134 void imap_fetch_rfc822(long msgnum, const char *whichfmt) {
00135        CitContext *CCC = CC;
00136        citimap *Imap = CCCIMAP;
00137        const char *ptr = NULL;
00138        size_t headers_size, text_size, total_size;
00139        size_t bytes_to_send = 0;
00140        struct MetaData smi;
00141        int need_to_rewrite_metadata = 0;
00142        int need_body = 0;
00143 
00144        /* Determine whether this particular fetch operation requires
00145         * us to fetch the message body from disk.  If not, we can save
00146         * on some disk operations...
00147         */
00148        if ( (!strcasecmp(whichfmt, "RFC822"))
00149           || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
00150               need_body = 1;
00151        }
00152 
00153        /* If this is an RFC822.SIZE fetch, first look in the message's
00154         * metadata record to see if we've saved that information.
00155         */
00156        if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
00157               GetMetaData(&smi, msgnum);
00158               if (smi.meta_rfc822_length > 0L) {
00159                      IAPrintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
00160                      return;
00161               }
00162               need_to_rewrite_metadata = 1;
00163               need_body = 1;
00164        }
00165        
00166        /* Cache the most recent RFC822 FETCH because some clients like to
00167         * fetch in pieces, and we don't want to have to go back to the
00168         * message store for each piece.  We also burn the cache if the
00169         * client requests something that involves reading the message
00170         * body, but we haven't fetched the body yet.
00171         */
00172        if ((Imap->cached_rfc822 != NULL)
00173           && (Imap->cached_rfc822_msgnum == msgnum)
00174           && (Imap->cached_rfc822_withbody || (!need_body)) ) {
00175               /* Good to go! */
00176        }
00177        else if (Imap->cached_rfc822 != NULL) {
00178               /* Some other message is cached -- free it */
00179               FreeStrBuf(&Imap->cached_rfc822);
00180               Imap->cached_rfc822_msgnum = (-1);
00181        }
00182 
00183        /* At this point, we now can fetch and convert the message iff it's not
00184         * the one we had cached.
00185         */
00186        if (Imap->cached_rfc822 == NULL) {
00187               /*
00188                * Load the message into memory for translation & measurement
00189                */
00190               CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
00191               CtdlOutputMsg(msgnum, MT_RFC822,
00192                      (need_body ? HEADERS_ALL : HEADERS_FAST),
00193                      0, 1, NULL, SUPPRESS_ENV_TO
00194               );
00195               if (!need_body) IAPuts("\r\n");    /* extra trailing newline */
00196               Imap->cached_rfc822 = CCC->redirect_buffer;
00197               CCC->redirect_buffer = NULL;
00198               Imap->cached_rfc822_msgnum = msgnum;
00199               Imap->cached_rfc822_withbody = need_body;
00200               if ( (need_to_rewrite_metadata) && 
00201                    (StrLength(Imap->cached_rfc822) > 0) ) {
00202                      smi.meta_rfc822_length = StrLength(Imap->cached_rfc822);
00203                      PutMetaData(&smi);
00204               }
00205        }
00206 
00207        /*
00208         * Now figure out where the headers/text break is.  IMAP considers the
00209         * intervening blank line to be part of the headers, not the text.
00210         */
00211        headers_size = 0;
00212 
00213        if (need_body) {
00214               StrBuf *Line = NewStrBuf();
00215               ptr = NULL;
00216               do {
00217                      StrBufSipLine(Line, Imap->cached_rfc822, &ptr);
00218 
00219                      if ((StrLength(Line) != 0)  && (ptr != StrBufNOTNULL))
00220                      {
00221                             StrBufTrim(Line);
00222                             if ((StrLength(Line) != 0) && 
00223                                 (ptr != StrBufNOTNULL)    )
00224                             {
00225                                    headers_size = ptr - ChrPtr(Imap->cached_rfc822);
00226                             }
00227                      }
00228               } while ( (headers_size == 0)    && 
00229                        (ptr != StrBufNOTNULL) );
00230 
00231               total_size = StrLength(Imap->cached_rfc822);
00232               text_size = total_size - headers_size;
00233               FreeStrBuf(&Line);
00234        }
00235        else {
00236               headers_size = 
00237                      total_size = StrLength(Imap->cached_rfc822);
00238               text_size = 0;
00239        }
00240 
00241        IMAP_syslog(LOG_DEBUG, 
00242                   "RFC822: headers=" SIZE_T_FMT 
00243                   ", text=" SIZE_T_FMT
00244                   ", total=" SIZE_T_FMT,
00245                   headers_size, text_size, total_size);
00246 
00247        if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
00248               IAPrintf("RFC822.SIZE " SIZE_T_FMT, total_size);
00249               return;
00250        }
00251 
00252        else if (!strcasecmp(whichfmt, "RFC822")) {
00253               ptr = ChrPtr(Imap->cached_rfc822);
00254               bytes_to_send = total_size;
00255        }
00256 
00257        else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
00258               ptr = ChrPtr(Imap->cached_rfc822);
00259               bytes_to_send = headers_size;
00260        }
00261 
00262        else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
00263               ptr = &ChrPtr(Imap->cached_rfc822)[headers_size];
00264               bytes_to_send = text_size;
00265        }
00266 
00267        IAPrintf("%s {" SIZE_T_FMT "}\r\n", whichfmt, bytes_to_send);
00268        iaputs(ptr, bytes_to_send);
00269 }
00270 
00271 
00272 
00273 /*
00274  * Load a specific part of a message into the temp file to be output to a
00275  * client.  FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
00276  * but we still can't handle "2.HEADER" (which might not be a problem).
00277  *
00278  * Note: mime_parser() was called with dont_decode set to 1, so we have the
00279  * luxury of simply spewing without having to re-encode.
00280  */
00281 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
00282                   void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
00283                   char *cbid, void *cbuserdata)
00284 {
00285        struct CitContext *CCC = CC;
00286        char mimebuf2[SIZ];
00287        StrBuf *desired_section;
00288 
00289        desired_section = (StrBuf *)cbuserdata;
00290        IMAP_syslog(LOG_DEBUG, "imap_load_part() looking for %s, found %s",
00291                   ChrPtr(desired_section),
00292                   partnum
00293               );
00294 
00295        if (!strcasecmp(partnum, ChrPtr(desired_section))) {
00296               client_write(content, length);
00297        }
00298 
00299        snprintf(mimebuf2, sizeof mimebuf2, "%s.MIME", partnum);
00300 
00301        if (!strcasecmp(ChrPtr(desired_section), mimebuf2)) {
00302               client_write(HKEY("Content-type: "));
00303               client_write(cbtype, strlen(cbtype));
00304               if (!IsEmptyStr(cbcharset)) {
00305                      client_write(HKEY("; charset=\""));
00306                      client_write(cbcharset, strlen(cbcharset));
00307                      client_write(HKEY("\""));
00308               }
00309               if (!IsEmptyStr(name)) {
00310                      client_write(HKEY("; name=\""));
00311                      client_write(name, strlen(name));
00312                      client_write(HKEY("\""));
00313               }
00314               client_write(HKEY("\r\n"));
00315               if (!IsEmptyStr(encoding)) {
00316                      client_write(HKEY("Content-Transfer-Encoding: "));
00317                      client_write(encoding, strlen(encoding));
00318                      client_write(HKEY("\r\n"));
00319               }
00320               if (!IsEmptyStr(encoding)) {
00321                      client_write(HKEY("Content-Disposition: "));
00322                      client_write(disp, strlen(disp));
00323               
00324                      if (!IsEmptyStr(filename)) {
00325                             client_write(HKEY("; filename=\""));
00326                             client_write(filename, strlen(filename));
00327                             client_write(HKEY("\""));
00328                      }
00329                      client_write(HKEY("\r\n"));
00330               }
00331               cprintf("Content-Length: %ld\r\n\r\n", (long)length);
00332        }
00333 }
00334 
00335 
00336 /* 
00337  * Called by imap_fetch_envelope() to output the "From" field.
00338  * This is in its own function because its logic is kind of complex.  We
00339  * really need to make this suck less.
00340  */
00341 void imap_output_envelope_from(struct CtdlMessage *msg) {
00342        char user[SIZ], node[SIZ], name[SIZ];
00343 
00344        if (!msg) return;
00345 
00346        /* For anonymous messages, it's so easy! */
00347        if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
00348               IAPuts("((\"----\" NIL \"x\" \"x.org\")) ");
00349               return;
00350        }
00351        if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
00352               IAPuts("((\"anonymous\" NIL \"x\" \"x.org\")) ");
00353               return;
00354        }
00355 
00356        /* For everything else, we do stuff. */
00357        IAPuts("(("); /* open double-parens */
00358        plain_imap_strout(msg->cm_fields['A']);   /* personal name */
00359        IAPuts(" NIL ");     /* source route (not used) */
00360 
00361 
00362        if (msg->cm_fields['F'] != NULL) {
00363               process_rfc822_addr(msg->cm_fields['F'], user, node, name);
00364               plain_imap_strout(user);           /* mailbox name (user id) */
00365               IAPuts(" ");
00366               if (!strcasecmp(node, config.c_nodename)) {
00367                      plain_imap_strout(config.c_fqdn);
00368               }
00369               else {
00370                      plain_imap_strout(node);           /* host name */
00371               }
00372        }
00373        else {
00374               plain_imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
00375               IAPuts(" ");
00376               plain_imap_strout(msg->cm_fields['N']);   /* host name */
00377        }
00378        
00379        IAPuts(")) "); /* close double-parens */
00380 }
00381 
00382 
00383 
00384 /*
00385  * Output an envelope address (or set of addresses) in the official,
00386  * convoluted, braindead format.  (Note that we can't use this for
00387  * the "From" address because its data may come from a number of different
00388  * fields.  But we can use it for "To" and possibly others.
00389  */
00390 void imap_output_envelope_addr(char *addr) {
00391        char individual_addr[256];
00392        int num_addrs;
00393        int i;
00394        char user[256];
00395        char node[256];
00396        char name[256];
00397 
00398        if (addr == NULL) {
00399               IAPuts("NIL ");
00400               return;
00401        }
00402 
00403        if (IsEmptyStr(addr)) {
00404               IAPuts("NIL ");
00405               return;
00406        }
00407 
00408        IAPuts("(");
00409 
00410        /* How many addresses are listed here? */
00411        num_addrs = num_tokens(addr, ',');
00412 
00413        /* Output them one by one. */
00414        for (i=0; i<num_addrs; ++i) {
00415               extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
00416               striplt(individual_addr);
00417               process_rfc822_addr(individual_addr, user, node, name);
00418               IAPuts("(");
00419               plain_imap_strout(name);
00420               IAPuts(" NIL ");
00421               plain_imap_strout(user);
00422               IAPuts(" ");
00423               plain_imap_strout(node);
00424               IAPuts(")");
00425               if (i < (num_addrs-1)) 
00426                      IAPuts(" ");
00427        }
00428 
00429        IAPuts(") ");
00430 }
00431 
00432 
00433 /*
00434  * Implements the ENVELOPE fetch item
00435  * 
00436  * Note that the imap_strout() function can cleverly output NULL fields as NIL,
00437  * so we don't have to check for that condition like we do elsewhere.
00438  */
00439 void imap_fetch_envelope(struct CtdlMessage *msg) {
00440        char datestringbuf[SIZ];
00441        time_t msgdate;
00442        char *fieldptr = NULL;
00443        long len;
00444 
00445        if (!msg) return;
00446 
00447        /* Parse the message date into an IMAP-format date string */
00448        if (msg->cm_fields['T'] != NULL) {
00449               msgdate = atol(msg->cm_fields['T']);
00450        }
00451        else {
00452               msgdate = time(NULL);
00453        }
00454        datestring(datestringbuf, sizeof datestringbuf,
00455               msgdate, DATESTRING_IMAP);
00456 
00457        /* Now start spewing data fields.  The order is important, as it is
00458         * defined by the protocol specification.  Nonexistent fields must
00459         * be output as NIL, existent fields must be quoted or literalled.
00460         * The imap_strout() function conveniently does all this for us.
00461         */
00462        IAPuts("ENVELOPE (");
00463 
00464        /* Date */
00465        plain_imap_strout(datestringbuf);
00466        IAPuts(" ");
00467 
00468        /* Subject */
00469        plain_imap_strout(msg->cm_fields['U']);
00470        IAPuts(" ");
00471 
00472        /* From */
00473        imap_output_envelope_from(msg);
00474 
00475        /* Sender (default to same as 'From' if not present) */
00476        fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
00477        if (fieldptr != NULL) {
00478               imap_output_envelope_addr(fieldptr);
00479               free(fieldptr);
00480        }
00481        else {
00482               imap_output_envelope_from(msg);
00483        }
00484 
00485        /* Reply-to */
00486        fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
00487        if (fieldptr != NULL) {
00488               imap_output_envelope_addr(fieldptr);
00489               free(fieldptr);
00490        }
00491        else {
00492               imap_output_envelope_from(msg);
00493        }
00494 
00495        /* To */
00496        imap_output_envelope_addr(msg->cm_fields['R']);
00497 
00498        /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
00499        fieldptr = msg->cm_fields['Y'];
00500        if (fieldptr != NULL) {
00501               imap_output_envelope_addr(fieldptr);
00502        }
00503        else {
00504               fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
00505               imap_output_envelope_addr(fieldptr);
00506               if (fieldptr != NULL) free(fieldptr);
00507        }
00508 
00509        /* Bcc */
00510        fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
00511        imap_output_envelope_addr(fieldptr);
00512        if (fieldptr != NULL) free(fieldptr);
00513 
00514        /* In-reply-to */
00515        fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
00516        plain_imap_strout(fieldptr);
00517        IAPuts(" ");
00518        if (fieldptr != NULL) free(fieldptr);
00519 
00520        /* message ID */
00521        len = strlen(msg->cm_fields['I']);
00522        
00523        if ((len == 0) || (
00524                   (msg->cm_fields['I'][0] == '<') && 
00525                   (msg->cm_fields['I'][len - 1] == '>'))
00526               )
00527        {
00528               plain_imap_strout(msg->cm_fields['I']);
00529        }
00530        else 
00531        {
00532               char *Buf = malloc(len + 3);
00533               long pos = 0;
00534               
00535               if (msg->cm_fields['I'][0] != '<')
00536               {
00537                      Buf[pos] = '<';
00538                      pos ++;
00539               }
00540               memcpy(&Buf[pos], msg->cm_fields['I'], len);
00541               pos += len;
00542               if (msg->cm_fields['I'][len] != '>')
00543               {
00544                      Buf[pos] = '>';
00545                      pos++;
00546               }
00547               Buf[pos] = '\0';
00548               IPutStr(Buf, pos);
00549               free(Buf);
00550        }
00551        IAPuts(")");
00552 }
00553 
00554 /*
00555  * This function is called only when CC->redirect_buffer contains a set of
00556  * RFC822 headers with no body attached.  Its job is to strip that set of
00557  * headers down to *only* the ones we're interested in.
00558  */
00559 void imap_strip_headers(StrBuf *section) {
00560        citimap_command Cmd;
00561        StrBuf *which_fields = NULL;
00562        int doing_headers = 0;
00563        int headers_not = 0;
00564        int num_parms = 0;
00565        int i;
00566        StrBuf *boiled_headers = NULL;
00567        StrBuf *Line;
00568        int ok = 0;
00569        int done_headers = 0;
00570        const char *Ptr = NULL;
00571        CitContext *CCC = CC;
00572 
00573        if (CCC->redirect_buffer == NULL) return;
00574 
00575        which_fields = NewStrBufDup(section);
00576 
00577        if (!strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS", 13))
00578               doing_headers = 1;
00579        if (doing_headers && 
00580            !strncasecmp(ChrPtr(which_fields), "HEADER.FIELDS.NOT", 17))
00581               headers_not = 1;
00582 
00583        for (i=0; i < StrLength(which_fields); ++i) {
00584               if (ChrPtr(which_fields)[i]=='(')
00585                      StrBufReplaceToken(which_fields, i, 1, HKEY(""));
00586        }
00587        for (i=0; i < StrLength(which_fields); ++i) {
00588               if (ChrPtr(which_fields)[i]==')') {
00589                      StrBufCutAt(which_fields, i, NULL);
00590                      break;
00591               }
00592        }
00593        memset(&Cmd, 0, sizeof(citimap_command));
00594        Cmd.CmdBuf = which_fields;
00595        num_parms = imap_parameterize(&Cmd);
00596 
00597        boiled_headers = NewStrBufPlain(NULL, StrLength(CCC->redirect_buffer));
00598        Line = NewStrBufPlain(NULL, SIZ);
00599        Ptr = NULL;
00600        ok = 0;
00601        do {
00602               StrBufSipLine(Line, CCC->redirect_buffer, &Ptr);
00603 
00604               if (!isspace(ChrPtr(Line)[0])) {
00605 
00606                      if (doing_headers == 0) ok = 1;
00607                      else {
00608                             /* we're supposed to print all headers that are not matching the filter list */
00609                             if (headers_not) for (i=0, ok = 1; (i < num_parms) && (ok == 1); ++i) {
00610                                           if ( (!strncasecmp(ChrPtr(Line), 
00611                                                            Cmd.Params[i].Key,
00612                                                            Cmd.Params[i].len)) &&
00613                                                (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
00614                                                  ok = 0;
00615                                           }
00616                             }
00617                             /* we're supposed to print all headers matching the filterlist */
00618                             else for (i=0, ok = 0; ((i < num_parms) && (ok == 0)); ++i) {
00619                                           if ( (!strncasecmp(ChrPtr(Line), 
00620                                                            Cmd.Params[i].Key,
00621                                                            Cmd.Params[i].len)) &&
00622                                                (ChrPtr(Line)[Cmd.Params[i].len]==':') ) {
00623                                                  ok = 1;
00624                                    }
00625                             }
00626                      }
00627               }
00628 
00629               if (ok) {
00630                      StrBufAppendBuf(boiled_headers, Line, 0);
00631                      StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
00632               }
00633 
00634               if ((Ptr == StrBufNOTNULL)  ||
00635                   (StrLength(Line) == 0)  ||
00636                   (ChrPtr(Line)[0]=='\r') ||
00637                   (ChrPtr(Line)[0]=='\n')   ) done_headers = 1;
00638        } while (!done_headers);
00639 
00640        StrBufAppendBufPlain(boiled_headers, HKEY("\r\n"), 0);
00641 
00642        /* Now save it back (it'll always be smaller) */
00643        FreeStrBuf(&CCC->redirect_buffer);
00644        CCC->redirect_buffer = boiled_headers;
00645 
00646        free(Cmd.Params);
00647        FreeStrBuf(&which_fields);
00648        FreeStrBuf(&Line);
00649 }
00650 
00651 
00652 /*
00653  * Implements the BODY and BODY.PEEK fetch items
00654  */
00655 void imap_fetch_body(long msgnum, ConstStr item, int is_peek) {
00656        struct CtdlMessage *msg = NULL;
00657        StrBuf *section;
00658        StrBuf *partial;
00659        int is_partial = 0;
00660        size_t pstart, pbytes;
00661        int loading_body_now = 0;
00662        int need_body = 1;
00663        int burn_the_cache = 0;
00664        CitContext *CCC = CC;
00665        citimap *Imap = CCCIMAP;
00666 
00667        /* extract section */
00668        section = NewStrBufPlain(CKEY(item));
00669        
00670        if (strchr(ChrPtr(section), '[') != NULL) {
00671               StrBufStripAllBut(section, '[', ']');
00672        }
00673        IMAP_syslog(LOG_DEBUG, "Section is: [%s]", 
00674                   (StrLength(section) == 0) ? "(empty)" : ChrPtr(section)
00675        );
00676 
00677        /* Burn the cache if we don't have the same section of the 
00678         * same message again.
00679         */
00680        if (Imap->cached_body != NULL) {
00681               if (Imap->cached_bodymsgnum != msgnum) {
00682                      burn_the_cache = 1;
00683               }
00684               else if ( (!Imap->cached_body_withbody) && (need_body) ) {
00685                      burn_the_cache = 1;
00686               }
00687               else if (strcasecmp(Imap->cached_bodypart, ChrPtr(section))) {
00688                      burn_the_cache = 1;
00689               }
00690               if (burn_the_cache) {
00691                      /* Yup, go ahead and burn the cache. */
00692                      free(Imap->cached_body);
00693                      Imap->cached_body_len = 0;
00694                      Imap->cached_body = NULL;
00695                      Imap->cached_bodymsgnum = (-1);
00696                      strcpy(Imap->cached_bodypart, "");
00697               }
00698        }
00699 
00700        /* extract partial */
00701        partial = NewStrBufPlain(CKEY(item));
00702        if (strchr(ChrPtr(partial), '<') != NULL) {
00703               StrBufStripAllBut(partial, '<', '>');
00704               is_partial = 1;
00705        }
00706        if ( (is_partial == 1) && (StrLength(partial) > 0) ) {
00707               IMAP_syslog(LOG_DEBUG, "Partial is <%s>", ChrPtr(partial));
00708        }
00709 
00710        if (Imap->cached_body == NULL) {
00711               CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
00712               loading_body_now = 1;
00713               msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
00714        }
00715 
00716        /* Now figure out what the client wants, and get it */
00717 
00718        if (!loading_body_now) {
00719               /* What we want is already in memory */
00720        }
00721 
00722        else if ( (!strcmp(ChrPtr(section), "1")) && (msg->cm_format_type != 4) ) {
00723               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
00724        }
00725 
00726        else if (StrLength(section) == 0) {
00727               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, SUPPRESS_ENV_TO);
00728        }
00729 
00730        /*
00731         * If the client asked for just headers, or just particular header
00732         * fields, strip it down.
00733         */
00734        else if (!strncasecmp(ChrPtr(section), "HEADER", 6)) {
00735               /* This used to work with HEADERS_FAST, but then Apple got stupid with their
00736                * IMAP library and this broke Mail.App and iPhone Mail, so we had to change it
00737                * to HEADERS_ONLY so the trendy hipsters with their iPhones can read mail.
00738                */
00739               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, SUPPRESS_ENV_TO);
00740               imap_strip_headers(section);
00741        }
00742 
00743        /*
00744         * Strip it down if the client asked for everything _except_ headers.
00745         */
00746        else if (!strncasecmp(ChrPtr(section), "TEXT", 4)) {
00747               CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, SUPPRESS_ENV_TO);
00748        }
00749 
00750        /*
00751         * Anything else must be a part specifier.
00752         * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
00753         */
00754        else {
00755               mime_parser(msg->cm_fields['M'], NULL,
00756                          *imap_load_part, NULL, NULL,
00757                          section,
00758                          1
00759                      );
00760        }
00761 
00762        if (loading_body_now) {
00763               Imap->cached_body_len = StrLength(CCC->redirect_buffer);
00764               Imap->cached_body = SmashStrBuf(&CCC->redirect_buffer);
00765               Imap->cached_bodymsgnum = msgnum;
00766               Imap->cached_body_withbody = need_body;
00767               strcpy(Imap->cached_bodypart, ChrPtr(section));
00768        }
00769 
00770        if (is_partial == 0) {
00771               IAPuts("BODY[");
00772               iaputs(SKEY(section));
00773               IAPrintf("] {" SIZE_T_FMT "}\r\n", Imap->cached_body_len);
00774               pstart = 0;
00775               pbytes = Imap->cached_body_len;
00776        }
00777        else {
00778               sscanf(ChrPtr(partial), SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
00779               if (pbytes > (Imap->cached_body_len - pstart)) {
00780                      pbytes = Imap->cached_body_len - pstart;
00781               }
00782               IAPuts("BODY[");
00783               iaputs(SKEY(section));
00784               IAPrintf("]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", pstart, pbytes);
00785        }
00786 
00787        FreeStrBuf(&partial);
00788 
00789        /* Here we go -- output it */
00790        iaputs(&Imap->cached_body[pstart], pbytes);
00791 
00792        if (msg != NULL) {
00793               CtdlFreeMessage(msg);
00794        }
00795 
00796        /* Mark this message as "seen" *unless* this is a "peek" operation */
00797        if (is_peek == 0) {
00798               CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
00799        }
00800        FreeStrBuf(&section);
00801 }
00802 
00803 /*
00804  * Called immediately before outputting a multipart bodystructure
00805  */
00806 void imap_fetch_bodystructure_pre(
00807               char *name, char *filename, char *partnum, char *disp,
00808               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
00809               char *cbid, void *cbuserdata
00810               ) {
00811 
00812        IAPuts("(");
00813 }
00814 
00815 
00816 
00817 /*
00818  * Called immediately after outputting a multipart bodystructure
00819  */
00820 void imap_fetch_bodystructure_post(
00821               char *name, char *filename, char *partnum, char *disp,
00822               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
00823               char *cbid, void *cbuserdata
00824               ) {
00825 
00826        char subtype[128];
00827 
00828        IAPuts(" ");
00829 
00830        /* disposition */
00831        extract_token(subtype, cbtype, 1, '/', sizeof subtype);
00832        plain_imap_strout(subtype);
00833 
00834        /* body language */
00835        /* IAPuts(" NIL"); We thought we needed this at one point, but maybe we don't... */
00836 
00837        IAPuts(")");
00838 }
00839 
00840 
00841 
00842 /*
00843  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
00844  *
00845  */
00846 void imap_fetch_bodystructure_part(
00847               char *name, char *filename, char *partnum, char *disp,
00848               void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
00849               char *cbid, void *cbuserdata
00850               ) {
00851 
00852        int have_cbtype = 0;
00853        int have_encoding = 0;
00854        int lines = 0;
00855        size_t i;
00856        char cbmaintype[128];
00857        char cbsubtype[128];
00858 
00859        if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
00860        if (have_cbtype) {
00861               extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
00862               extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
00863        }
00864        else {
00865               strcpy(cbmaintype, "TEXT");
00866               strcpy(cbsubtype, "PLAIN");
00867        }
00868 
00869        IAPuts("(");
00870        plain_imap_strout(cbmaintype);                                 /* body type */
00871        IAPuts(" ");
00872        plain_imap_strout(cbsubtype);                                         /* body subtype */
00873        IAPuts(" ");
00874 
00875        IAPuts("(");                                            /* begin body parameter list */
00876 
00877        /* "NAME" must appear as the first parameter.  This is not required by IMAP,
00878         * but the Asterisk voicemail application blindly assumes that NAME will be in
00879         * the first position.  If it isn't, it rejects the message.
00880         */
00881        if (name != NULL) if (!IsEmptyStr(name)) {
00882               IAPuts("\"NAME\" ");
00883               plain_imap_strout(name);
00884               IAPuts(" ");
00885        }
00886 
00887        IAPuts("\"CHARSET\" ");
00888        if (cbcharset == NULL) {
00889               plain_imap_strout("US-ASCII");
00890        }
00891        else if (cbcharset[0] == 0) {
00892               plain_imap_strout("US-ASCII");
00893        }
00894        else {
00895               plain_imap_strout(cbcharset);
00896        }
00897        IAPuts(") ");                                           /* end body parameter list */
00898 
00899        IAPuts("NIL ");                                         /* Body ID */
00900        IAPuts("NIL ");                                         /* Body description */
00901 
00902        if (encoding != NULL) if (encoding[0] != 0)  have_encoding = 1;
00903        if (have_encoding) {
00904               plain_imap_strout(encoding);
00905        }
00906        else {
00907               plain_imap_strout("7BIT");
00908        }
00909        IAPuts(" ");
00910 
00911        /* The next field is the size of the part in bytes. */
00912        IAPrintf("%ld ", (long)length);    /* bytes */
00913 
00914        /* The next field is the number of lines in the part, if and only
00915         * if the part is TEXT.  More gratuitous complexity.
00916         */
00917        if (!strcasecmp(cbmaintype, "TEXT")) {
00918               if (length) for (i=0; i<length; ++i) {
00919                      if (((char *)content)[i] == '\n') ++lines;
00920               }
00921               IAPrintf("%d ", lines);
00922        }
00923 
00924        /* More gratuitous complexity */
00925        if ((!strcasecmp(cbmaintype, "MESSAGE"))
00926           && (!strcasecmp(cbsubtype, "RFC822"))) {
00927               /* FIXME: message/rfc822 also needs to output the envelope structure,
00928                * body structure, and line count of the encapsulated message.  Fortunately
00929                * there are not yet any clients depending on this, so we can get away
00930                * with not implementing it for now.
00931                */
00932        }
00933 
00934        /* MD5 value of body part; we can get away with NIL'ing this */
00935        IAPuts("NIL ");
00936 
00937        /* Disposition */
00938        if (disp == NULL) {
00939               IAPuts("NIL");
00940        }
00941        else if (IsEmptyStr(disp)) {
00942               IAPuts("NIL");
00943        }
00944        else {
00945               IAPuts("(");
00946               plain_imap_strout(disp);
00947               if (filename != NULL) if (!IsEmptyStr(filename)) {
00948                      IAPuts(" (\"FILENAME\" ");
00949                      plain_imap_strout(filename);
00950                      IAPuts(")");
00951               }
00952               IAPuts(")");
00953        }
00954 
00955        /* Body language (not defined yet) */
00956        IAPuts(" NIL)");
00957 }
00958 
00959 
00960 
00961 /*
00962  * Spew the BODYSTRUCTURE data for a message.
00963  *
00964  */
00965 void imap_fetch_bodystructure (long msgnum, const char *item,
00966               struct CtdlMessage *msg) {
00967        const char *rfc822 = NULL;
00968        const char *rfc822_body = NULL;
00969        size_t rfc822_len;
00970        size_t rfc822_headers_len;
00971        size_t rfc822_body_len;
00972        const char *ptr = NULL;
00973        char *pch;
00974        char buf[SIZ];
00975        int lines = 0;
00976 
00977        /* Handle NULL message gracefully */
00978        if (msg == NULL) {
00979               IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
00980                      "(\"CHARSET\" \"US-ASCII\") NIL NIL "
00981                      "\"7BIT\" 0 0)");
00982               return;
00983        }
00984 
00985        /* For non-RFC822 (ordinary Citadel) messages, this is short and
00986         * sweet...
00987         */
00988        if (msg->cm_format_type != FMT_RFC822) {
00989 
00990               /* *sigh* We have to RFC822-format the message just to be able
00991                * to measure it.  FIXME use smi cached fields if possible
00992                */
00993 
00994               CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
00995               CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, SUPPRESS_ENV_TO);
00996               rfc822_len = StrLength(CC->redirect_buffer);
00997               rfc822 = pch = SmashStrBuf(&CC->redirect_buffer);
00998 
00999               ptr = rfc822;
01000               do {
01001                      ptr = cmemreadline(ptr, buf, sizeof buf);
01002                      ++lines;
01003                      if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
01004                             rfc822_body = ptr;
01005                      }
01006               } while (*ptr != 0);
01007 
01008               rfc822_headers_len = rfc822_body - rfc822;
01009               rfc822_body_len = rfc822_len - rfc822_headers_len;
01010               free(pch);
01011 
01012               IAPuts("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
01013                      "(\"CHARSET\" \"US-ASCII\") NIL NIL "
01014                      "\"7BIT\" ");
01015               IAPrintf(SIZE_T_FMT " %d)", rfc822_body_len, lines);
01016 
01017               return;
01018        }
01019 
01020        /* For messages already stored in RFC822 format, we have to parse. */
01021        IAPuts("BODYSTRUCTURE ");
01022        mime_parser(msg->cm_fields['M'],
01023                      NULL,
01024                      *imap_fetch_bodystructure_part,    /* part */
01025                      *imap_fetch_bodystructure_pre,     /* pre-multi */
01026                      *imap_fetch_bodystructure_post,    /* post-multi */
01027                      NULL,
01028                      1);    /* don't decode -- we want it as-is */
01029 }
01030 
01031 
01032 /*
01033  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
01034  * individual message, once it has been selected for output.
01035  */
01036 void imap_do_fetch_msg(int seq, citimap_command *Cmd) {
01037        int i;
01038        citimap *Imap = IMAP;
01039        struct CtdlMessage *msg = NULL;
01040        int body_loaded = 0;
01041 
01042        /* Don't attempt to fetch bogus messages or UID's */
01043        if (seq < 1) return;
01044        if (Imap->msgids[seq-1] < 1L) return;
01045 
01046        buffer_output();
01047        IAPrintf("* %d FETCH (", seq);
01048 
01049        for (i=0; i<Cmd->num_parms; ++i) {
01050 
01051               /* Fetchable without going to the message store at all */
01052               if (!strcasecmp(Cmd->Params[i].Key, "UID")) {
01053                      imap_fetch_uid(seq);
01054               }
01055               else if (!strcasecmp(Cmd->Params[i].Key, "FLAGS")) {
01056                      imap_fetch_flags(seq-1);
01057               }
01058 
01059               /* Potentially fetchable from cache, if the client requests
01060                * stuff from the same message several times in a row.
01061                */
01062               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822")) {
01063                      imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
01064               }
01065               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.HEADER")) {
01066                      imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
01067               }
01068               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.SIZE")) {
01069                      imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
01070               }
01071               else if (!strcasecmp(Cmd->Params[i].Key, "RFC822.TEXT")) {
01072                      imap_fetch_rfc822(Imap->msgids[seq-1], Cmd->Params[i].Key);
01073               }
01074 
01075               /* BODY fetches do their own fetching and caching too. */
01076               else if (!strncasecmp(Cmd->Params[i].Key, "BODY[", 5)) {
01077                      imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 0);
01078               }
01079               else if (!strncasecmp(Cmd->Params[i].Key, "BODY.PEEK[", 10)) {
01080                      imap_fetch_body(Imap->msgids[seq-1], Cmd->Params[i], 1);
01081               }
01082 
01083               /* Otherwise, load the message into memory.
01084                */
01085               else if (!strcasecmp(Cmd->Params[i].Key, "BODYSTRUCTURE")) {
01086                      if ((msg != NULL) && (!body_loaded)) {
01087                             CtdlFreeMessage(msg);       /* need the whole thing */
01088                             msg = NULL;
01089                      }
01090                      if (msg == NULL) {
01091                             msg = CtdlFetchMessage(Imap->msgids[seq-1], 1);
01092                             body_loaded = 1;
01093                      }
01094                      imap_fetch_bodystructure(Imap->msgids[seq-1],
01095                                    Cmd->Params[i].Key, msg);
01096               }
01097               else if (!strcasecmp(Cmd->Params[i].Key, "ENVELOPE")) {
01098                      if (msg == NULL) {
01099                             msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
01100                             body_loaded = 0;
01101                      }
01102                      imap_fetch_envelope(msg);
01103               }
01104               else if (!strcasecmp(Cmd->Params[i].Key, "INTERNALDATE")) {
01105                      if (msg == NULL) {
01106                             msg = CtdlFetchMessage(Imap->msgids[seq-1], 0);
01107                             body_loaded = 0;
01108                      }
01109                      imap_fetch_internaldate(msg);
01110               }
01111 
01112               if (i != Cmd->num_parms-1) IAPuts(" ");
01113        }
01114 
01115        IAPuts(")\r\n");
01116        unbuffer_output();
01117        if (msg != NULL) {
01118               CtdlFreeMessage(msg);
01119        }
01120 }
01121 
01122 
01123 
01124 /*
01125  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
01126  * validated and boiled down the request a bit.
01127  */
01128 void imap_do_fetch(citimap_command *Cmd) {
01129        citimap *Imap = IMAP;
01130        int i;
01131 #if 0
01132 /* debug output the parsed vector */
01133        {
01134               int i;
01135               IMAP_syslog(LOG_DEBUG, "----- %ld params", Cmd->num_parms);
01136 
01137        for (i=0; i < Cmd->num_parms; i++) {
01138               if (Cmd->Params[i].len != strlen(Cmd->Params[i].Key))
01139                      IMAP_syslog(LOG_DEBUG, "*********** %ld != %ld : %s",
01140                                 Cmd->Params[i].len, 
01141                                 strlen(Cmd->Params[i].Key),
01142                                 Cmd->Params[i].Key);
01143               else
01144                      IMAP_syslog(LOG_DEBUG, "%ld : %s",
01145                                 Cmd->Params[i].len, 
01146                                 Cmd->Params[i].Key);
01147        }}
01148 
01149 #endif
01150 
01151        if (Imap->num_msgs > 0) {
01152               for (i = 0; i < Imap->num_msgs; ++i) {
01153 
01154                      /* Abort the fetch loop if the session breaks.
01155                       * This is important for users who keep mailboxes
01156                       * that are too big *and* are too impatient to
01157                       * let them finish loading.  :)
01158                       */
01159                      if (CC->kill_me) return;
01160 
01161                      /* Get any message marked for fetch. */
01162                      if (Imap->flags[i] & IMAP_SELECTED) {
01163                             imap_do_fetch_msg(i+1, Cmd);
01164                      }
01165               }
01166        }
01167 }
01168 
01169 
01170 
01171 /*
01172  * Back end for imap_handle_macros()
01173  * Note that this function *only* looks at the beginning of the string.  It
01174  * is not a generic search-and-replace function.
01175  */
01176 void imap_macro_replace(StrBuf *Buf, long where, 
01177                      StrBuf *TmpBuf,
01178                      char *find, long findlen, 
01179                      char *replace, long replacelen) 
01180 {
01181 
01182        if (StrLength(Buf) - where > findlen)
01183               return;
01184 
01185        if (!strncasecmp(ChrPtr(Buf) + where, find, findlen)) {
01186               if (ChrPtr(Buf)[where + findlen] == ' ') {
01187                      StrBufPlain(TmpBuf, replace, replacelen);
01188                      StrBufAppendBufPlain(TmpBuf, HKEY(" "), 0);
01189                      StrBufReplaceToken(Buf, where, findlen, 
01190                                       SKEY(TmpBuf));
01191               }
01192               if (where + findlen == StrLength(Buf)) {
01193                      StrBufReplaceToken(Buf, where, findlen, 
01194                                       replace, replacelen);
01195               }
01196        }
01197 }
01198 
01199 
01200 
01201 /*
01202  * Handle macros embedded in FETCH data items.
01203  * (What the heck are macros doing in a wire protocol?  Are we trying to save
01204  * the computer at the other end the trouble of typing a lot of characters?)
01205  */
01206 void imap_handle_macros(citimap_command *Cmd) {
01207        long i;
01208        int nest = 0;
01209        StrBuf *Tmp = NewStrBuf();
01210 
01211        for (i=0; i < StrLength(Cmd->CmdBuf); ++i) {
01212               char ch = ChrPtr(Cmd->CmdBuf)[i];
01213               if ((ch=='(') ||
01214                   (ch=='[') ||
01215                   (ch=='<') ||
01216                   (ch=='{')) ++nest;
01217               else if ((ch==')') ||
01218                       (ch==']') ||
01219                       (ch=='>') ||
01220                       (ch=='}')) --nest;
01221 
01222               if (nest <= 0) {
01223                      imap_macro_replace(Cmd->CmdBuf, i,
01224                                       Tmp, 
01225                                       HKEY("ALL"),
01226                                       HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE")
01227                      );
01228                      imap_macro_replace(Cmd->CmdBuf, i,
01229                                       Tmp, 
01230                                       HKEY("BODY"),
01231                                       HKEY("BODYSTRUCTURE")
01232                      );
01233                      imap_macro_replace(Cmd->CmdBuf, i,
01234                                       Tmp, 
01235                                       HKEY("FAST"),
01236                                       HKEY("FLAGS INTERNALDATE RFC822.SIZE")
01237                      );
01238                      imap_macro_replace(Cmd->CmdBuf, i,
01239                                       Tmp, 
01240                                       HKEY("FULL"),
01241                                       HKEY("FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY")
01242                      );
01243               }
01244        }
01245        FreeStrBuf(&Tmp);
01246 }
01247 
01248 
01249 /*
01250  * Break out the data items requested, possibly a parenthesized list.
01251  * Returns the number of data items, or -1 if the list is invalid.
01252  * NOTE: this function alters the string it is fed, and uses it as a buffer
01253  * to hold the data for the pointers it returns.
01254  */
01255 int imap_extract_data_items(citimap_command *Cmd) 
01256 {
01257        int nArgs;
01258        int nest = 0;
01259        const char *pch, *end;
01260 
01261        /* Convert all whitespace to ordinary space characters. */
01262        pch = ChrPtr(Cmd->CmdBuf);
01263        end = pch + StrLength(Cmd->CmdBuf);
01264 
01265        while (pch < end)
01266        {
01267               if (isspace(*pch)) 
01268                      StrBufPeek(Cmd->CmdBuf, pch, 0, ' ');
01269               pch++;
01270        }
01271 
01272        /* Strip leading and trailing whitespace, then strip leading and
01273         * trailing parentheses if it's a list
01274         */
01275        StrBufTrim(Cmd->CmdBuf);
01276        pch = ChrPtr(Cmd->CmdBuf);
01277        if ( (pch[0]=='(') && 
01278             (pch[StrLength(Cmd->CmdBuf)-1]==')') ) 
01279        {
01280               StrBufCutRight(Cmd->CmdBuf, 1);
01281               StrBufCutLeft(Cmd->CmdBuf, 1);
01282               StrBufTrim(Cmd->CmdBuf);
01283        }
01284 
01285        /* Parse any macro data items */
01286        imap_handle_macros(Cmd);
01287 
01288        /*
01289         * Now break out the data items.  We throw in one trailing space in
01290         * order to avoid having to break out the last one manually.
01291         */
01292        nArgs = StrLength(Cmd->CmdBuf) / 10 + 10;
01293        nArgs = CmdAdjust(Cmd, nArgs, 0);
01294        Cmd->num_parms = 0;
01295        Cmd->Params[Cmd->num_parms].Key = pch = ChrPtr(Cmd->CmdBuf);
01296        end = Cmd->Params[Cmd->num_parms].Key + StrLength(Cmd->CmdBuf);
01297 
01298        while (pch < end) 
01299        {
01300               if ((*pch=='(') ||
01301                   (*pch=='[') ||
01302                   (*pch=='<') ||
01303                   (*pch=='{'))
01304                      ++nest;
01305 
01306               else if ((*pch==')') ||
01307                       (*pch==']') ||
01308                       (*pch=='>') ||
01309                       (*pch=='}'))
01310                      --nest;
01311 
01312               if ((nest <= 0) && (*pch==' '))    {
01313                      StrBufPeek(Cmd->CmdBuf, pch, 0, '\0');
01314                      Cmd->Params[Cmd->num_parms].len = 
01315                             pch - Cmd->Params[Cmd->num_parms].Key;
01316 
01317                      if (Cmd->num_parms + 1 >= Cmd->avail_parms) {
01318                             nArgs = CmdAdjust(Cmd, nArgs * 2, 1);
01319                      }
01320                      Cmd->num_parms++;                  
01321                      Cmd->Params[Cmd->num_parms].Key = ++pch;
01322               }
01323               else if (pch + 1 == end) {
01324                      Cmd->Params[Cmd->num_parms].len = 
01325                             pch - Cmd->Params[Cmd->num_parms].Key + 1;
01326 
01327                      Cmd->num_parms++;                  
01328               }
01329               pch ++;
01330        }
01331        return Cmd->num_parms;
01332 
01333 }
01334 
01335 
01336 /*
01337  * One particularly hideous aspect of IMAP is that we have to allow the client
01338  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
01339  * handles this by setting the IMAP_SELECTED flag for each message specified in
01340  * the ranges/sets, then looping through the message array, outputting messages
01341  * with the flag set.  We don't bother returning an error if an out-of-range
01342  * number is specified (we just return quietly) because any client braindead
01343  * enough to request a bogus message number isn't going to notice the
01344  * difference anyway.
01345  *
01346  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
01347  * message included in the specified range.
01348  *
01349  * Set is_uid to 1 to fetch by UID instead of sequence number.
01350  */
01351 void imap_pick_range(const char *supplied_range, int is_uid) {
01352        citimap *Imap = IMAP;
01353        int i;
01354        int num_sets;
01355        int s;
01356        char setstr[SIZ], lostr[SIZ], histr[SIZ];
01357        long lo, hi;
01358        char actual_range[SIZ];
01359 
01360        /* 
01361         * Handle the "ALL" macro
01362         */
01363        if (!strcasecmp(supplied_range, "ALL")) {
01364               safestrncpy(actual_range, "1:*", sizeof actual_range);
01365        }
01366        else {
01367               safestrncpy(actual_range, supplied_range, sizeof actual_range);
01368        }
01369 
01370        /*
01371         * Clear out the IMAP_SELECTED flags for all messages.
01372         */
01373        for (i = 0; i < Imap->num_msgs; ++i) {
01374               Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
01375        }
01376 
01377        /*
01378         * Now set it for all specified messages.
01379         */
01380        num_sets = num_tokens(actual_range, ',');
01381        for (s=0; s<num_sets; ++s) {
01382               extract_token(setstr, actual_range, s, ',', sizeof setstr);
01383 
01384               extract_token(lostr, setstr, 0, ':', sizeof lostr);
01385               if (num_tokens(setstr, ':') >= 2) {
01386                      extract_token(histr, setstr, 1, ':', sizeof histr);
01387                      if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
01388               } 
01389               else {
01390                      safestrncpy(histr, lostr, sizeof histr);
01391               }
01392               lo = atol(lostr);
01393               hi = atol(histr);
01394 
01395               /* Loop through the array, flipping bits where appropriate */
01396               for (i = 1; i <= Imap->num_msgs; ++i) {
01397                      if (is_uid) { /* fetch by sequence number */
01398                             if ( (Imap->msgids[i-1]>=lo)
01399                                && (Imap->msgids[i-1]<=hi)) {
01400                                    Imap->flags[i-1] |= IMAP_SELECTED;
01401                             }
01402                      }
01403                      else {        /* fetch by uid */
01404                             if ( (i>=lo) && (i<=hi)) {
01405                                    Imap->flags[i-1] |= IMAP_SELECTED;
01406                             }
01407                      }
01408               }
01409        }
01410 }
01411 
01412 
01413 
01414 /*
01415  * This function is called by the main command loop.
01416  */
01417 void imap_fetch(int num_parms, ConstStr *Params) {
01418        citimap_command Cmd;
01419        int num_items;
01420        
01421        if (num_parms < 4) {
01422               IReply("BAD invalid parameters");
01423               return;
01424        }
01425 
01426        imap_pick_range(Params[2].Key, 0);
01427 
01428        memset(&Cmd, 0, sizeof(citimap_command));
01429        Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
01430        MakeStringOf(Cmd.CmdBuf, 3);
01431 
01432        num_items = imap_extract_data_items(&Cmd);
01433        if (num_items < 1) {
01434               IReply("BAD invalid data item list");
01435               FreeStrBuf(&Cmd.CmdBuf);
01436               free(Cmd.Params);
01437               return;
01438        }
01439 
01440        imap_do_fetch(&Cmd);
01441        IReply("OK FETCH completed");
01442        FreeStrBuf(&Cmd.CmdBuf);
01443        free(Cmd.Params);
01444 }
01445 
01446 /*
01447  * This function is called by the main command loop.
01448  */
01449 void imap_uidfetch(int num_parms, ConstStr *Params) {
01450        citimap_command Cmd;
01451        int num_items;
01452        int i;
01453        int have_uid_item = 0;
01454 
01455        if (num_parms < 5) {
01456               IReply("BAD invalid parameters");
01457               return;
01458        }
01459 
01460        imap_pick_range(Params[3].Key, 1);
01461 
01462        memset(&Cmd, 0, sizeof(citimap_command));
01463        Cmd.CmdBuf = NewStrBufPlain(NULL, StrLength(IMAP->Cmd.CmdBuf));
01464 
01465        MakeStringOf(Cmd.CmdBuf, 4);
01466 #if 0
01467        IMAP_syslog(LOG_DEBUG, "-------%s--------", ChrPtr(Cmd.CmdBuf));
01468 #endif
01469        num_items = imap_extract_data_items(&Cmd);
01470        if (num_items < 1) {
01471               IReply("BAD invalid data item list");
01472               FreeStrBuf(&Cmd.CmdBuf);
01473               free(Cmd.Params);
01474               return;
01475        }
01476 
01477        /* If the "UID" item was not included, we include it implicitly
01478         * (at the beginning) because this is a UID FETCH command
01479         */
01480        for (i=0; i<num_items; ++i) {
01481               if (!strcasecmp(Cmd.Params[i].Key, "UID")) ++have_uid_item;
01482        }
01483        if (have_uid_item == 0) {
01484               if (Cmd.num_parms + 1 >= Cmd.avail_parms)
01485                      CmdAdjust(&Cmd, Cmd.avail_parms + 1, 1);
01486               memmove(&Cmd.Params[1], 
01487                      &Cmd.Params[0], 
01488                      sizeof(ConstStr) * Cmd.num_parms);
01489 
01490               Cmd.num_parms++;
01491               Cmd.Params[0] = (ConstStr){HKEY("UID")};
01492        }
01493 
01494        imap_do_fetch(&Cmd);
01495        IReply("OK UID FETCH completed");
01496        FreeStrBuf(&Cmd.CmdBuf);
01497        free(Cmd.Params);
01498 }
01499 
01500