eudora-mac/CrispinIMAP/imap4r1.c

1 line
111 KiB
C
Executable File

/* Copyright (c) 2017, Computer History Museum
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
the limitations in the disclaimer below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE. */
/*
* Program: Interactive Message Access Protocol 4rev1 (IMAP4R1) routines
*
* Author: Mark Crispin
* Networks and Distributed Computing
* Computing & Communications
* University of Washington
* Administration Building, AG-44
* Seattle, WA 98195
* Internet: MRC@CAC.Washington.EDU
*
* Date: 15 June 1988
* Last Edited: 4 March 1997
*
* Sponsorship: The original version of this work was developed in the
* Symbolic Systems Resources Group of the Knowledge Systems
* Laboratory at Stanford University in 1987-88, and was funded
* by the Biomedical Research Technology Program of the National
* Institutes of Health under grant number RR-00785.
*
* Original version Copyright 1988 by The Leland Stanford Junior University
* Copyright 1997 by the University of Washington
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted, provided
* that the above copyright notices appear in all copies and that both the
* above copyright notices and this permission notice appear in supporting
* documentation, and that the name of the University of Washington or The
* Leland Stanford Junior University not be used in advertising or publicity
* pertaining to distribution of the software without specific, written prior
* permission. This software is made available "as is", and
* THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
* DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
* INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
* WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "mail.h"
#include "osdep.h"
#include "imap4r1.h"
#include "rfc822.h"
#include "misc.h"
#include "myssl.h"
#pragma segment IMAP
/* allow ucase(NULL) */
#define ucase MyUpperCase
static char *extraheaders =
" BODY.PEEK[HEADER.FIELDS (Path Message-ID Newsgroups Followup-To References)]";
/* Driver dispatch used by MAIL */
DRIVER imapdriver =
{
"imap", // driver name
DR_MAIL|DR_NEWS|DR_NAMESPACE, // driver flags
(DRIVER *) NULL, // next driver
imap_valid, // mailbox is valid for us
imap_parameters, // manipulate parameters
imap_scan, // scan mailboxes
imap_list, // find mailboxes
imap_lsub, // find subscribed mailboxes
imap_subscribe, // subscribe to mailbox
imap_unsubscribe, // unsubscribe from mailbox
imap_create, // create mailbox
imap_delete, // delete mailbox
imap_rename, // rename mailbox
imap_status, // status of mailbox
imap_open, // open mailbox
imap_close, // close mailbox
imap_fast, // fetch message "fast" attributes
imap_flags, // fetch message flags
NIL, // fetch overview
// JOK - added imap_envelope
imap_envelope,
imap_structure, // fetch message envelopes
NULL, // fetch message IMAPheader
NULL, // fetch message body
imap_msgdata, // fetch partial message
imap_uid, // unique identifier
imap_msgno, // message number
imap_flag, // modify flags
NULL, // per-message modify flags
imap_search, // search for message based on criteria
NULL, // sort messages
NULL, // thread messages
imap_ping, // ping mailbox to see if still alive
imap_check, // check for new messages
imap_expunge, // expunge deleted messages
imap_copy, // copy messages to another mailbox
imap_append, // append string message to mailbox
imap_gc, // garbage collect stream
// added these, too ...
imap_connected, // return if this stream is connected.
imap_rfc822size
};
/* driver parameters */
static unsigned long imap_maxlogintrials = MAXLOGINTRIALS;
static long imap_lookahead = IMAPLOOKAHEAD;
static long imap_uidlookahead = IMAPUIDLOOKAHEAD;
static long imap_defaultport = 0;
static long imap_prefetch = IMAPLOOKAHEAD;
static long imap_closeonerror = NULL;
// UIDPLUS Support
void VerifyUIDValidity(MAILSTREAM *stream, char *pUids, int len);
OSErr UIDStringToUIDs(char *pUids, int len, Accumulator *pAccu);
OSErr StoreUIDPLUSResponses(MAILSTREAM *stream, IMAPPARSEDREPLY *reply);
/* IMAP validate mailbox
* Accepts: mailbox name
* Returns: our driver if name is valid, NULL otherwise
*/
DRIVER *imap_valid (char *name)
{
return mail_valid_net (name,&imapdriver,NULL,NULL);
}
/* IMAP manipulate driver parameters
* Accepts: function code
* function-dependent value
* Returns: function-dependent return value
*/
void *imap_parameters (long function,void *value)
{
switch ((int) function)
{
case SET_MAXLOGINTRIALS:
imap_maxlogintrials = (long) value;
break;
case GET_MAXLOGINTRIALS:
value = (void *) imap_maxlogintrials;
break;
case SET_LOOKAHEAD:
imap_lookahead = (long) value;
break;
case GET_LOOKAHEAD:
value = (void *) imap_lookahead;
break;
case SET_UIDLOOKAHEAD:
imap_uidlookahead = (long) value;
break;
case GET_UIDLOOKAHEAD:
value = (void *) imap_uidlookahead;
break;
case SET_IMAPPORT:
imap_defaultport = (long) value;
break;
case GET_IMAPPORT:
value = (void *) imap_defaultport;
break;
case SET_PREFETCH:
imap_prefetch = (long) value;
break;
case GET_PREFETCH:
value = (void *) imap_prefetch;
break;
case SET_CLOSEONERROR:
imap_closeonerror = (long) value;
break;
case GET_CLOSEONERROR:
value = (void *) imap_closeonerror;
break;
default:
value = NULL; // error case
break;
}
return value;
}
/* IMAP scan mailboxes
* Accepts: mail stream
* reference
* pattern to search
* string to scan
*/
void imap_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
{
imap_list_work (stream,ref,pat,T,contents);
}
/* IMAP list mailboxes
* Accepts: mail stream
* reference
* pattern to search
*/
void imap_list (MAILSTREAM *stream,char *ref,char *pat)
{
imap_list_work (stream,ref,pat,T,NULL);
}
/* IMAP list subscribed mailboxes
* Accepts: mail stream
* reference
* pattern to search
*/
void imap_lsub (MAILSTREAM *stream,char *ref,char *pat)
{
// we don't currently do anything with subscriptions
// JDB 6-23-98
#ifdef NOT_USED
void *sdb = NULL;
char *s;
imap_list_work (stream,ref,pat,NULL,NULL);
// only if null stream and * requested
if (!stream && !ref && !strcmp (pat,"*") && (s = sm_read (&sdb)))
{
do
if (imap_valid (s)) mm_lsub (stream,NULL,s,NULL);
while (s = sm_read (&sdb)); // until no more subscriptions
}
#endif
}
/* IMAP find list of mailboxes
* Accepts: mail stream
* reference
* pattern to search
* list flag
* string to scan
*/
void imap_list_work (MAILSTREAM *stream,char *ref,char *pat,long list, char *contents)
{
char *s, prefix[MAILTMPLEN], mbx[MAILTMPLEN];
IMAPARG *args[4],aref,apat,acont;
// Must have an open stream (JOK).
if (!(stream && LOCAL && stream->transStream)) return;
// have a reference?
if (ref && *ref)
{
if (strlen (ref) >= sizeof (prefix))
return;
strcpy (prefix, ref); // build prefix
LOCAL->prefix = prefix; // note prefix
}
else
{
LOCAL->prefix = NIL; // no prefix
}
if (contents)
{ // want to do a scan?
if (LOCAL->use_scan)
{ // can we?
args[0] = &aref; args[1] = &apat; args[2] = &acont; args[3] = NIL;
aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
apat.type = LISTMAILBOX; apat.text = (void *) pat;
acont.type = ASTRING; acont.text = (void *) contents;
imap_send (stream,"SCAN",args);
}
else mm_log ("Scan not valid on this IMAP server",WARN);
}
else if (LEVELIMAP4 (stream))
{// easy if IMAP4
args[0] = &aref; args[1] = &apat; args[2] = NIL;
aref.type = ASTRING; aref.text = (void *) (ref ? ref : "");
apat.type = LISTMAILBOX; apat.text = (void *) pat;
imap_send (stream,list ? "LIST" : "LSUB",args);
}
else if (LEVEL1176 (stream))
{// convert to IMAP2 format wildcard
// kludgy application of reference
if (ref && *ref)
sprintf (mbx,"%s%s",ref,pat);
else
strcpy (mbx,pat);
for (s = mbx; *s; s++)
{
if (*s == '%') *s = '*';
}
args[0] = &apat; args[1] = NIL;
apat.type = LISTMAILBOX; apat.text = (void *) mbx;
if (!(list && // if list, try IMAP2bis, then RFC-1176 form
strcmp (imap_send (stream,"FIND ALL.MAILBOXES",args)->key,"BAD")) ||
!strcmp (imap_send (stream,"FIND MAILBOXES",args)->key,"BAD"))
{
LOCAL->rfc1176 = NIL; // must be RFC-1064
}
}
LOCAL->prefix = NIL; // no more prefix
}
/* IMAP subscribe to mailbox
* Accepts: mail stream
* mailbox to add to subscription list
* Returns: T on success, NULL on failure
*/
long imap_subscribe (MAILSTREAM *stream,char *mailbox)
{
return imap_manage (stream,mailbox,"Subscribe Mailbox",NULL);
}
/* IMAP unsubscribe to mailbox
* Accepts: mail stream
* mailbox to delete from manage list
* Returns: T on success, NULL on failure
*/
long imap_unsubscribe (MAILSTREAM *stream,char *mailbox)
{
return imap_manage (stream,mailbox,"Unsubscribe Mailbox",NULL);
}
/* IMAP create mailbox
* Accepts: mail stream
* mailbox name to create
* Returns: T on success, NULL on failure
*/
long imap_create (MAILSTREAM *stream,char *mailbox)
{
return imap_manage (stream,mailbox,"Create",NULL);
}
/* IMAP delete mailbox
* Accepts: mail stream
* mailbox name to delete
* Returns: T on success, NULL on failure
*/
long imap_delete (MAILSTREAM *stream,char *mailbox)
{
return imap_manage (stream,mailbox,"Delete",NULL);
}
/* IMAP rename mailbox
* Accepts: mail stream
* old mailbox name
* new mailbox name
* Returns: T on success, NULL on failure
*/
long imap_rename (MAILSTREAM *stream,char *old,char *newname)
{
return imap_manage (stream,old,"Rename",newname);
}
/* IMAP manage a mailbox
* Accepts: mail stream
* mailbox to manipulate
* command to execute
* optional second argument
* Returns: T on success, NULL on failure
*/
long imap_manage (MAILSTREAM *stream,char *mailbox,char *command,char *arg2)
{
MAILSTREAM *st = stream;
IMAPPARSEDREPLY *reply;
long ret = NULL;
char mbx[MAILTMPLEN],mbx2[MAILTMPLEN];
IMAPARG *args[3],ambx,amb2;
ambx.type = amb2.type = ASTRING; ambx.text = (void *) mbx;
amb2.text = (void *) mbx2;
args[0] = &ambx; args[1] = args[2] = NULL;
// Must have a valid open stream.
if (!(stream && stream && stream->transStream))
return NIL;
// Validate network stuff.
if ( imapmail_valid_net (stream, &imapdriver, NIL) )
{
// JOK Don't use what was put into mbx. Assume caller specified a correct mailbox name.
strcpy (mbx, mailbox);
// Also, if second argument, use arg2 as passed in.
if (arg2)
{
strcpy (mbx2, arg2);
args[1] = &amb2; /* second arg present? */
}
// JOK
reply = imap_send (stream,command,args);
if (reply)
{
ret = imap_OK (stream, reply);
mm_log (reply->text,ret ? (long) NIL : IMAP_ERROR);
}
}
return ret;
}
/* IMAP status
* Accepts: mail stream
* mailbox name
* status flags
* Returns: T on success, NULL on failure
*/
long imap_status (MAILSTREAM *stream,char *mbx,long flags)
{
IMAPARG *args[3],ambx,aflg;
char tmp[MAILTMPLEN];
NETMBX mb;
long ret = NIL;
// Must have a valid and open stream (JOK)!!
if (!(stream && LOCAL && stream->transStream))
return NIL;
// must have a mailbox name
if (!mbx) return NIL;
// Copy stuff into NETMBX.
imapmail_valid_net_parse (stream, &mb);
// use the name of the mailbox passed in, please - JDB 060899
strcpy(mb.mailbox, mbx);
/* can't use stream if not IMAP4rev1, STATUS,
* or halfopen and right host
*/
if ((!(LEVELSTATUS (stream) || stream->halfopen) ||
strcmp (ucase (strcpy (tmp,imap_host (stream))),
ucase (mb.host))))
{
// JOK - fail,
return NIL;
}
args[0] = &ambx;args[1] = NIL;/* set up first argument as mailbox */
ambx.type = ASTRING; ambx.text = (void *) mb.mailbox;
if (LEVELSTATUS (stream))
{
/* have STATUS command? */
aflg.type = FLAGS; aflg.text = (void *) tmp;
args[1] = &aflg; args[2] = NIL;
tmp[0] = tmp[1] = '\0'; /* build flag list */
if (flags & SA_MESSAGES) strcat (tmp," MESSAGES");
if (flags & SA_RECENT) strcat (tmp," RECENT");
if (flags & SA_UNSEEN) strcat (tmp," UNSEEN");
if (flags & SA_UIDNEXT) strcat (tmp,LEVELIMAP4rev1 (stream) ?
"UIDNEXT" : " UID-NEXT");
if (flags & SA_UIDVALIDITY) strcat (tmp,LEVELIMAP4rev1 (stream) ?
"UIDVALIDITY" : " UID-VALIDITY");
tmp[0] = '(';
strcat (tmp,")");
/* send "STATUS mailbox flag" */
if (imap_OK (stream,imap_send (stream,"STATUS",args)))
ret = T;
}
// JOK - Don't do imap2 status.
return T;
}
/* IMAP authenticate
* Accepts: stream to authenticate
* parsed network mailbox structure
* scratch buffer
* place to return user name
* Returns: T on success, NULL on failure
*/
unsigned long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
{
unsigned long trial,ua;
char tag[16];
AUTHENTICATOR *at;
IMAPPARSEDREPLY *reply = nil;
for (ua = LOCAL->use_auth; stream->transStream && ua;)
{
if (!(at = mail_lookup_auth(find_rightmost_bit (&ua) + 1))) fatal ("Authenticator logic bug!");
trial = 0;
do
{
sprintf (tag,"A%05ld",stream->gensym++); // gensym a new tag
sprintf (tmp,"%s AUTHENTICATE %s",tag,at->name); // build command
if (imap_soutr (stream,tmp) && (*at->client) (imap_challenge,imap_response,mb,stream,&trial,usr))
{
// This replaces original (JOK).
while (1)
{
reply = imap_reply (stream, tag);
if (!reply) break;
if (strcmp(reply->tag, tag)) imap_soutr(stream,"*");
else break;
}
#ifdef JOK_ORIGINAL // (JOK)
while (strcmp ((reply = imap_reply (stream,tag))->tag,tag))
imap_soutr (stream,"*");
#endif
// done if got success response
if (imap_OK (stream,reply)) return T;
mm_log (reply->text,WARN);
}
}
while (stream->transStream && !LOCAL->byeseen && trial && (trial < imap_maxlogintrials));
}
if (PrefIsSet(PREF_IMAP_STUPID_PASSWORD))
{
// the login failed for some reason. Wipe the password.
InvalidatePasswords(false,false,false);
}
else
{
// invalidate the password, if we got a real reply back from the server. The password was bad.
if (reply && (!reply->fake) && (strcmp(reply->key,"BAD") || strcmp(reply->key,"NO")))
InvalidatePasswords(false,false,false);
}
return NULL; // ran out of authenticators
}
/* IMAP login
* Accepts: stream to login
* parsed network mailbox structure
* scratch buffer
* place to return user name
* Returns: T on success, NULL on failure
*/
unsigned long imap_login (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr)
{
unsigned long trial = 0;
IMAPPARSEDREPLY *reply;
IMAPARG *args[3];
IMAPARG ausr,apwd;
//Must have a stream
if (!stream) return NIL;
ausr.type = apwd.type = ASTRING;
ausr.text = (void *) usr;
apwd.text = (void *) tmp;
args[0] = &ausr; args[1] = &apwd; args[2] = NULL;
while (stream->transStream && !LOCAL->byeseen && (trial < imap_maxlogintrials))
{
tmp[0] = 0; // get password
// automatic if want anonymous access
if (mb->anoflag || stream->anonymous)
{
strcpy (usr,"anonymous");
strcpy (tmp,net_localhost (stream->transStream));
trial = imap_maxlogintrials;
}
else // otherwise get the user's login information.
{
mm_login (mb,usr,tmp,trial++);
if (!tmp[0]) // user refused to give a password
{
mm_log ("Login aborted",IMAP_ERROR);
return NULL;
}
}
// send "LOGIN usr tmp"
if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args)))
{
// login successful, note if anonymous
stream->anonymous = strcmp (usr,"anonymous") ? NULL : T;
// If we have succeeded, tell that to the stream
stream->bAuthenticated = TRUE;
return T; // successful login
}
mm_log (reply->text,WARN);
}
// Make sure the stream knows.
stream->bAuthenticated = FALSE;
stream->bSelected = FALSE;
if (PrefIsSet(PREF_IMAP_STUPID_PASSWORD))
{
// the login failed for some reason. Wipe the password.
InvalidatePasswords(false,false,false);
}
else
{
// invalidate the password, if we got a real reply back from the server. The password was bad.
if (reply && (!reply->fake) && (strcmp(reply->key,"BAD") || strcmp(reply->key,"NO")))
InvalidatePasswords(false,false,false);
}
mm_log ("Incorrect password specified.",IMAP_ERROR);
return NULL;
}
/* Get challenge to authenticator in binary
* Accepts: stream
* pointer to returned size
* Returns: challenge or NULL if not challenge
*/
//
// NOTE: (JOK) - This function returns a "ready response" from the server, already
// base64 decoded. That is, the response from the server that follows the "+" sign.
// NOTE: This returns allocated memory that must be freed by the caller!!
//
void *imap_challenge (void *s, unsigned long *len)
{
MAILSTREAM *stream = (MAILSTREAM *) s;
IMAPPARSEDREPLY *reply = NIL;
char *p = net_getline (stream->transStream);
// JOK
if (p)
{
reply = imap_parse_reply (stream, p);
if (reply)
{
return strcmp (reply->tag,"+") ? NIL :
rfc822_base64 ((unsigned char *) reply->text, strlen (reply->text), len);
}
}
return NIL;
}
/* Send authenticator response in BASE64
* Accepts: MAIL stream
* string to send
* length of string
* Returns: T if successful, else NULL
*/
long imap_response (void *s,char *response,unsigned long size)
{
MAILSTREAM *stream = (MAILSTREAM *) s;
unsigned long i,j,ret;
char *t,*u;
if (size) // make CRLFless BASE64 string
{
for (t = (char *) rfc822_binary ((void *) response,size,&i),u = t,j = 0;j < i; j++)
if (t[j] > ' ') *u++ = t[j];
*u = '\0'; // tie off string for mm_dlog()
if (stream->debug) mm_dlog (t);
// append CRLF
*u++ = '\015'; *u++ = '\012';
ret = net_sout (stream->transStream,t,i = u - t);
fs_give ((void **) &t);
}
else if (response)
ret = imap_soutr (stream,"");
else // abort requested
ret = imap_soutr (stream,"*");
return ret;
}
/* IMAP close
* Accepts: MAIL stream
* option flags
*/
void imap_close (MAILSTREAM *stream,long options)
{
IMAPPARSEDREPLY *reply;
if (stream && LOCAL) // send "LOGOUT"
{
if (!LOCAL->byeseen) // don't even think of doing it if saw a BYE
{
// expunge silently if requested
if (options & CL_EXPUNGE) imap_send (stream,"EXPUNGE",NULL);
// don't bother waiting for the logout response unless we're being polite
if (!PrefIsSet(PREF_IMAP_POLITE_LOGOUT)) stream->fastLogout = true;
if (stream->transStream && !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NULL))) mm_log (reply->text,WARN);
}
// close NET connection if still open
if (stream->transStream) net_close (stream->transStream);
stream->transStream = NULL;
// reset the stream state
stream->bConnected = FALSE;
stream->bAuthenticated = FALSE;
stream->bSelected = FALSE;
// throw away any list results.
if (stream->fListResultsHandle) DisposeMailboxTree(&(stream->fListResultsHandle));
if (stream->fUIDResults) UID_LL_Zap(&(stream->fUIDResults));
// free up any network data we may have stored
if (stream->fNetData) DisposeHandle(stream->fNetData);
// free up memory
if (LOCAL->user) fs_give ((void **) &LOCAL->user);
if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
// nuke the local data
fs_give ((void **) &stream->local);
}
}
/* IMAP fetch fast information
* Accepts: MAIL stream
* sequence
* option flags
*
* Generally, imap_fetchstructure is preferred
*/
//JDB now returns result
Boolean imap_fast (MAILSTREAM *stream,char *sequence,long flags)
{
char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
IMAPPARSEDREPLY *reply;
IMAPARG *args[3],aseq,aatt;
Boolean result = false;
//Format command
aseq.type = SEQUENCE; aseq.text = (void *) sequence;
aatt.type = ATOM; aatt.text = (LEVELIMAP4 (stream)) ? (void *) "(FLAGS INTERNALDATE RFC822.SIZE UID)" : (void *) "FAST";
args[0] = &aseq; args[1] = &aatt; args[2] = NULL;
reply = imap_send(stream, cmd, args);
if (reply)
{
if (imap_OK(stream, reply))
result = true;
if (!result)
mm_log (reply->text,IMAP_ERROR);
}
return (result);
}
/* IMAP fetch flags
* Accepts: MAIL stream
* sequence
* option flags
*/
//JDB returns result now.
Boolean imap_flags (MAILSTREAM *stream,char *sequence,long flags)
{
char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
IMAPPARSEDREPLY *reply;
IMAPARG *args[3],aseq,aatt;
Boolean result = false;
//Format the command.
aseq.type = SEQUENCE; aseq.text = (void *) sequence;
aatt.type = ATOM;
// JDB 1-11-99 fetch the size of the message along with the flags for efficiency
#ifdef DEBUG
if (stream->flagsRefN > 0) aatt.text = (void *) "FLAGS";
else
#endif
if (!PrefIsSet(PREF_IMAP_NOTHING_BUT_HEAD)) aatt.text = (void *) "(FLAGS RFC822.SIZE)";
else aatt.text = (void *) "FLAGS";
args[0] = &aseq; args[1] = &aatt; args[2] = NULL;
//Send it.
reply = imap_send (stream,cmd,args);
if (reply)
{
if ( imap_OK (stream, reply) )
result = true;
if (!result)
mm_log (reply->text,IMAP_ERROR);
}
return result;
}
/* IMAP fetch structure
* Accepts: MAIL stream
* message # to fetch
* pointer to return body
* option flags
* Returns: envelope of this message, body returned in body value
*
* Fetches the "fast" information as well
*/
// NOTE:
// JOK - This is a new imap_structure!! It fetches just the body structure.
// The parsing routine allocates a BODY and attaches it to the "current elt" in thye
// stream. We check for the body, detaches it from the elt and passes it to the caller.
// The caller MUST free the body when done with it.
// END NOTE
IMAPBODY *imap_structure (MAILSTREAM *stream,unsigned long msgno, long flags)
{
char seq[128],tmp[MAILTMPLEN];
IMAPPARSEDREPLY *reply = NIL;
IMAPARG *args[3],aseq,aatt;
IMAPBODY *b;
MESSAGECACHE *elt = NULL;
// Cannot have a zero msgno.
if (msgno <= 0)
return NULL;
// Initialize: "b" is what's returned.
b = NULL;
// Setup for IMAP call.
args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
aseq.type = SEQUENCE; aseq.text = (void *) seq;
aatt.type = ATOM; aatt.text = NIL;
// IF there is a CurrentElt in the stream, delete it.
if (stream->CurrentElt)
mail_free_elt (&stream->CurrentElt);
// Allocate a new one.
elt = mail_elt (stream);
if (elt)
{
if (flags & FT_UID)
elt->privat.uid = msgno;
else
elt->msgno = msgno;
}
// NOTE: "msgno" can be a UID or a message sequence number.
sprintf (seq,"%lu",msgno); /* initial sequence (UID or msgno) */
// Format command based on server capability.
// NOTE: Can't handle any IMAP version older than imap2bis!!
if (LEVELIMAP4 (stream) && (flags & FT_UID))
{
sprintf (tmp,"(UID BODYSTRUCTURE)");
aatt.text = (void *) tmp; /* do the built command */
if (!imap_OK (stream, reply = imap_send (stream,"UID FETCH",args)))
{
if (reply)
mm_log (reply->text,IMAP_ERROR);
}
}
else if (LEVELIMAP2bis (stream))
{
/* has non-extensive body and no UID. */
sprintf (tmp,"(BODY)");
aatt.text = (void *) tmp; /* do the built command */
if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
{
if (reply)
mm_log (reply->text,IMAP_ERROR);
}
}
// "b" is what's returned.
b = NULL;
// Did we get anything?
if (stream->CurrentElt)
{
// Make sure the UID's or msgno's matched.
if (flags & FT_UID)
{
if (stream->CurrentElt->privat.uid == msgno)
b = stream->CurrentElt->privat.msg.body;
}
else
{
if (stream->CurrentElt->msgno == msgno)
b = stream->CurrentElt->privat.msg.body;
}
// Make sure we detach the body pointer if there was one.
// If it's not our body, we'd want to delete it.
if (b)
stream->CurrentElt->privat.msg.body = NULL;
// Now free the elt.
mail_free_elt (&stream->CurrentElt);
}
return b;
}
/* IMAP fetch message data
* Accepts: MAIL stream
* message number
* section specifier
* offset of first designated byte or 0 to start at beginning
* maximum number of bytes or 0 for all bytes
* lines to fetch if IMAPheader
* flags
* Returns: T on success, NULL on failure
*/
long imap_msgdata (MAILSTREAM *stream, unsigned long msgno, char *sequence, char *section, unsigned long first,unsigned long last,STRINGLIST *lines,long flags)
{
char *t,tmp[MAILTMPLEN],part[40];
char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
IMAPPARSEDREPLY *reply;
IMAPARG *args[5],aseq,aatt,alns,acls;
if (sequence)
{
aseq.type = SEQUENCE;
aseq.text = (void *) sequence;
}
else
{
aseq.type = NUMBER;
aseq.text = (void *) msgno;
}
aatt.type = ATOM; // assume atomic attribute
alns.type = LIST; alns.text = (void *) lines;
acls.type = BODYCLOSE; acls.text = (void *) part;
args[0] = &aseq; args[1] = &aatt; args[2] = args[3] = args[4] = NULL;
part[0] = '\0'; // initially no partial specifier
if (LEVELIMAP4rev1 (stream)) // easy case if IMAP4rev1 server
{
aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
if (lines) // want specific IMAPheader lines?
{
sprintf (tmp,"%s.FIELDS%s",section,(flags & FT_NOT) ? ".NOT" : "");
aatt.text = (void *) tmp;
args[2] = &alns; args[3] = &acls;
}
else
{
aatt.text = (void *) section;
args[2] = &acls;
}
if (first || last) sprintf (part,"<%lu.%lu>",first,last ? last:-1);
}
else if (!strcmp (section,"HEADER")) // IMAPBODY.PEEK[HEADER] becomes RFC822.HEADER
{
if (flags & FT_PEEK) aatt.text = (void *) "RFC822.HEADER";
else
{
mm_notify (stream,"[NOTIMAP4] Can't do non-peeking IMAPheader fetch",WARN);
return NULL;
}
}
else if ((flags & FT_PEEK) && !LEVEL1730 (stream)) // other peeking was introduced in RFC-1730
{
mm_notify (stream,"[NOTIMAP4] Can't do peeking fetch",WARN);
return NULL;
}
else if (!strcmp (section,"TEXT")) // IMAPBODY[TEXT] becomes RFC822.TEXT
{
aatt.text = (void *)((flags & FT_PEEK) ? "RFC822.TEXT.PEEK" : "RFC822.TEXT");
}
else if (!section[0]) // IMAPBODY[] becomes RFC822
{
aatt.text = (void *)((flags & FT_PEEK) ? "RFC822.PEEK" : "RFC822");
}
else if (t = strstr (section,".HEADER")) // nested IMAPheader
{
if (!LEVEL1730 (stream)) // this was introduced in RFC-1730
{
mm_notify (stream,"[NOTIMAP4] Can't do nested IMAPheader fetch",WARN);
return NULL;
}
aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
args[2] = &acls; // will need to close section
aatt.text = (void *) tmp; // convert .HEADER to .0 for RFC-1730 server
strncpy (tmp,section,t-section);
strcpy (tmp+(t-section),".0");
}
else if (strstr (section,".MIME") || strstr (section,".TEXT")) // extended nested text
{
mm_notify (stream,"[NOTIMAP4REV1] Can't do extended body part fetch",WARN);
return NULL;
}
else if (LEVELIMAP2bis (stream)) // nested message
{
aatt.type = (flags & FT_PEEK) ? BODYPEEK : BODYTEXT;
args[2] = &acls; /* will need to close section */
aatt.text = (void *) section;
}
else // ancient server
{
mm_notify (stream,"[NOTIMAP2BIS] Can't do body part fetch",WARN);
return NULL;
}
//Note to self: just what exactly happens to the reply after this? Who cleans it up?
if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) // send the fetch command
{
mm_log (reply->text,IMAP_ERROR);
return NULL; // failure
}
return T;
}
/* IMAP fetch UID
* Accepts: MAIL stream
* message number
* Returns: UID
*/
// NOTE: Modified by JOK.
unsigned long imap_uid (MAILSTREAM *stream,unsigned long msgno)
{
IMAPPARSEDREPLY *reply = NIL;
IMAPARG *args[3],aseq,aatt;
char seq[MAILTMPLEN];
unsigned long uid;
MESSAGECACHE *elt = NULL;
/* IMAP2 didn't have UIDs */
if (!LEVELIMAP4 (stream))
return msgno;
// Cannot have a zero msgno.
if (msgno <= 0)
return msgno;
// Initialize:
uid = 0;
// IF there is a CurrentElt in the stream, delete it.
if (stream->CurrentElt)
mail_free_elt (&stream->CurrentElt);
// Allocate a new one.
elt = mail_elt (stream);
if (elt)
elt->msgno = msgno;
// Setup for IMAP call.
sprintf (seq, "%lu", msgno);
aseq.type = SEQUENCE;
aseq.text = (void *) seq;
aatt.type = ATOM;
aatt.text = (void *) "UID";
args[0] = &aseq;
args[1] = &aatt;
args[2] = NIL;
/* send "FETCH msgno UID" */
if (!imap_OK (stream,reply = imap_send (stream,"FETCH",args)))
{
if (reply)
mm_log (reply->text,IMAP_ERROR);
}
// Did we get anything?
if (stream->CurrentElt)
{
uid = stream->CurrentElt->privat.uid;
// Now free the elt.
mail_free_elt (&stream->CurrentElt);
}
return uid;
}
/* IMAP fetch message number from UID
* Accepts: MAIL stream
* UID
* Returns: message number
*/
// NOTE: Modified by JOK.
unsigned long imap_msgno (MAILSTREAM *stream,unsigned long uid)
{
IMAPPARSEDREPLY *reply = NIL;
IMAPARG *args[3],aseq,aatt;
char seq[MAILTMPLEN];
unsigned long msgno;
MESSAGECACHE *elt = NULL;
/* IMAP2 didn't have UIDs */
if (!LEVELIMAP4 (stream))
return uid;
// Initialize
msgno = 0;
/* have server hunt for UID */
aseq.type = SEQUENCE; aseq.text = (void *) seq;
aatt.type = ATOM; aatt.text = (void *) "UID";
args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
sprintf (seq,"%lu",uid);
// IF there is a CurrentElt in the stream, delete it.
if (stream->CurrentElt)
mail_free_elt (&stream->CurrentElt);
// Allocate a new one.
elt = mail_elt (stream);
if (elt)
elt->privat.uid = uid;
/* send "UID FETCH uid UID" */
if (!imap_OK (stream,reply = imap_send (stream,"UID FETCH",args)))
{
if (reply)
mm_log (reply->text,IMAP_ERROR);
}
// Did we get anything?
if (stream->CurrentElt)
{
// Make sure the uid's matched.
if (stream->CurrentElt->privat.uid == uid)
msgno = stream->CurrentElt->msgno;
// Now free the elt.
mail_free_elt (&stream->CurrentElt);
}
return msgno; /* didn't find the UID anywhere */
}
/* IMAP modify flags
* Accepts: MAIL stream
* sequence
* flag(s)
* option flags
*/
// JDB - added Boolean return to indicate success
Boolean imap_flag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
{
char *cmd;
IMAPPARSEDREPLY *reply;
IMAPARG *args[4],aseq,ascm,aflg;
Boolean result = true;
MESSAGECACHE *elt = NULL;
// Must have a stream
if (!stream) return false;
cmd = (LEVELIMAP4 (stream) && (flags & ST_UID)) ? "UID STORE":"STORE";
aseq.type = SEQUENCE;
aseq.text = (void *) sequence;
ascm.type = ATOM;
ascm.text = (void *)((flags & ST_SET) ? ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ? "+Flags.silent" : "+Flags") : ((LEVELIMAP4 (stream) && (flags & ST_SILENT)) ? "-Flags.silent" : "-Flags"));
aflg.type = FLAGS;
aflg.text = (void *) flag;
args[0] = &aseq;
args[1] = &ascm;
args[2] = &aflg;
args[3] = NULL;
// IF there is a CurrentElt in the stream, delete it.
if (stream->CurrentElt)
mail_free_elt (&stream->CurrentElt);
// Allocate a new one.
elt = mail_elt (stream);
// send "STORE sequence +Flags flag"
if (!imap_OK (stream,reply = imap_send (stream,cmd,args)))
{
result = false;
mm_log (reply->text,IMAP_ERROR);
}
// Did we get anything?
if (stream->CurrentElt)
{
// BUG: Is ST_SILENT is NOT set, we should check to see if the flags were
// returned from the server correctly.
// Now free the elt.
mail_free_elt (&stream->CurrentElt);
}
return (result);
}
/* IMAP search for messages
* Accepts: MAIL stream
* character set
* search program
* option flags
*/
// JOK
// This now just sends the command to the server. Untagged respponses will
// be sent to a callback function.
//
Boolean imap_search (MAILSTREAM *stream,char *charset,SEARCHPGM *pgm,long flags)
{
Boolean result = true;
IMAPPARSEDREPLY *reply;
IMAPARG *args[3],apgm,aseq,aatt;
args[1] = args[2] = NIL;
apgm.type = SEARCHPROGRAM; apgm.text = (void *) pgm;
aseq.type = SEQUENCE;
aatt.type = ATOM;
if (charset)
{
args[0] = &aatt; args[1] = &apgm;
aatt.text = (void *) charset;
}
else
args[0] = &apgm;
/* do the SEARCH */
if (!imap_OK (stream, reply = imap_send (stream, (flags & SE_UID) ? "UID SEARCH" : "SEARCH", args)))
{
if (reply)
{
mm_log (reply->text,IMAP_ERROR);
result = false; // the search failed.
}
}
return (result);
}
/* IMAP ping mailbox
* Accepts: MAIL stream
* Returns: T if stream still alive, else NULL
*/
long imap_ping (MAILSTREAM *stream)
{
return (stream->transStream && /* send "NOOP" */
imap_OK (stream,imap_send (stream,"NOOP",NULL))) ? T : NULL;
}
/* IMAP check mailbox
* Accepts: MAIL stream
*/
void imap_check (MAILSTREAM *stream)
{
/* send "CHECK" */
IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NULL);
mm_log (reply->text,imap_OK (stream,reply) ? (long) NULL : IMAP_ERROR);
}
/* IMAP expunge mailbox
* Accepts: MAIL stream
*/
long imap_expunge (MAILSTREAM *stream)
{
long result = NULL;
/* send "EXPUNGE" */
IMAPPARSEDREPLY *reply = imap_send (stream,"EXPUNGE",NULL);
result = imap_OK (stream,reply) ? (long) NULL : IMAP_ERROR;
mm_log (reply->text,result);
return (result ? NULL : T);
}
/* IMAP copy message(s)
* Accepts: MAIL stream
* sequence
* destination mailbox
* option flags
* Returns: T if successful else NULL
*/
long imap_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long flags)
{
char *cmd = (LEVELIMAP4 (stream) && (flags & CP_UID)) ? "UID COPY" : "COPY";
IMAPPARSEDREPLY *reply;
IMAPARG *args[3],aseq,ambx;
aseq.type = SEQUENCE; aseq.text = (void *) sequence;
ambx.type = ASTRING; ambx.text = (void *) mailbox;
args[0] = &aseq; args[1] = &ambx; args[2] = NULL;
/* send "COPY sequence mailbox" */
if (!imap_OK (stream,reply = imap_send (stream,cmd,args))) {
mm_log (reply->text,IMAP_ERROR);
return NULL;
}
/* check for UIDPLUS responses */
StoreUIDPLUSResponses(stream, reply);
/* delete the messages if the user said to */
if (flags & CP_MOVE) imap_flag (stream,sequence,"\\Deleted",
ST_SET + ((flags & CP_UID) ? ST_UID : NULL));
return T;
}
/* IMAP append message string
* Accepts: mail stream
* destination mailbox
* stringstruct of message to append
* Returns: T on success, NULL on failure
*/
/* Modified by JOK, May, 1997. */
long imap_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
STRING *msg)
{
char tmp[MAILTMPLEN];
long ret = NIL;
IMAPPARSEDREPLY *reply = NIL;
IMAPARG *args[5],ambx,aflg,adat,amsg;
// START JOK
// Must have a valid mailbox
if (!mailbox)
return ret;
// Copy mailbox into tmp.
strcpy (tmp, mailbox);
// END JOK
/* Must have a valid and open stream. (JOK) */
if (IsConnected(stream) && !stream->halfopen)
{
if (imapmail_valid_net (stream, &imapdriver,NIL))
{
ambx.type = ASTRING; ambx.text = (void *) tmp;
aflg.type = FLAGS; aflg.text = (void *) flags;
adat.type = ASTRING; adat.text = (void *) date;
amsg.type = LITERAL; amsg.text = (void *) msg;
if (flags || date)
{ /* IMAP4 form? */
int i = 0;
args[i++] = &ambx;
if (flags) args[i++] = &aflg;
if (date) args[i++] = &adat;
args[i++] = &amsg;
args[i++] = NIL;
reply = imap_send (stream,"APPEND",args);
if ( reply && !strcmp (reply->key,"OK") )
ret = T;
}
/* try IMAP2bis form if IMAP4 form fails */
if (!(ret || (reply && strcmp (reply->key,"BAD"))))
{
args[0] = &ambx; args[1] = &amsg; args[2] = NIL;
if (imap_OK (stream, reply = imap_send (stream,"APPEND",args)))
ret = T;
}
if (!ret)
{
if (reply)
mm_log (reply->text,IMAP_ERROR);
}
}
}
else
mm_log ("Can't access server for append",IMAP_ERROR);
return ret; /* return */
}
/* IMAP garbage collect stream
* Accepts: Mail stream
* garbage collection flags
*/
void imap_gc (MAILSTREAM *stream,long gcflags)
{
unsigned long i;
MESSAGECACHE *elt;
mailcache_t mc = (mailcache_t) mail_parameters (NULL,GET_CACHE,NULL);
// make sure the cache is large enough
(*mc) (stream,stream->nmsgs,CH_SIZE);
if (gcflags & GC_TEXTS) // garbage collect texts?
{
if (!stream->scache) for (i = 1; i <= stream->nmsgs; ++i)
if (elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT))
imap_gc_body (elt->privat.msg.body);
imap_gc_body (stream->body);
}
// gc cache if requested and unlocked
if (gcflags & GC_ELT) for (i = 1; i <= stream->nmsgs; ++i)
if ((elt = (MESSAGECACHE *) (*mc) (stream,i,CH_ELT)) && (elt->lockcount == 1)) (*mc) (stream,i,CH_FREE);
}
/* IMAP garbage collect body texts
* Accepts: body to GC
*/
void imap_gc_body (IMAPBODY *body)
{
PART *part;
if (body) // have a body?
{
if (body->mime.text.data) // flush MIME data
fs_give ((void **) &body->mime.text.data);
// flush text contents
if (body->contents.text.data)
fs_give ((void **) &body->contents.text.data);
body->mime.text.size = body->contents.text.size = 0;
// multipart?
if (body->type == TYPEMULTIPART)
{
for (part = body->nested.part; part; part = part->next) imap_gc_body (&part->body);
}
else if ((body->type == TYPEMESSAGE) && !strcmp (body->subtype,"RFC822")) // MESSAGE/RFC822?
{
imap_gc_body (body->nested.msg->body);
if (body->nested.msg->full.text.data)
fs_give ((void **) &body->nested.msg->full.text.data);
if (body->nested.msg->IMAPheader.text.data)
fs_give ((void **) &body->nested.msg->IMAPheader.text.data);
if (body->nested.msg->text.text.data)
fs_give ((void **) &body->nested.msg->text.text.data);
body->nested.msg->full.text.size = body->nested.msg->IMAPheader.text.size = body->nested.msg->text.text.size = 0;
}
}
}
/* Internal routines */
/* IMAP send atom-string
* Accepts: MAIL stream
* reply tag
* pointer to current position pointer of output bigbuf
* atom-string to output
* string length
* flag if list_wildcards allowed
* Returns: error reply or NULL if success
*/
IMAPPARSEDREPLY *imap_send_astring (MAILSTREAM *stream,char *tag,char **s,
char *t,unsigned long size,long wildok)
{
unsigned long j;
STRING st;
int quoted = size ? NULL : T; // default to not quoted unless empty
for (j = 0; j < size; j++)
switch (t[j])
{
case '\0': // not a CHAR
case '\012': case '\015': // not a TEXT-CHAR
case '"': case '\\': // quoted-specials (IMAP2 required this)
INIT (&st,mail_string,(void *) t,size);
return imap_send_literal (stream,tag,s,&st);
default: // all other characters
if (t[j] > ' ') break; // break if not a CTL
case '*': case '%': // list_wildcards
if (wildok) break; // allowed if doing the wild thing
// atom_specials
case '(': case ')': case '{': case ' ':
quoted = T; // must use quoted string format
break;
}
if (quoted) *(*s)++ = '"'; // write open quote
while (size--) *(*s)++ = *t++; // write the characters
if (quoted) *(*s)++ = '"'; // write close quote
return NULL;
}
/* IMAP send literal
* Accepts: MAIL stream
* reply tag
* pointer to current position pointer of output bigbuf
* literal to output as stringstruct
* Returns: error reply or NULL if success
*/
IMAPPARSEDREPLY *imap_send_literal (MAILSTREAM *stream,char *tag,char **s,STRING *st)
{
IMAPPARSEDREPLY *reply;
unsigned long i = SIZE (st);
sprintf (*s,"{%ld}",i); // write literal count
*s += strlen (*s); // size of literal count
// send the command
reply = imap_sout (stream,tag,LOCAL->tmp,s);
if (strcmp (reply->tag,"+")) // prompt for more data?
{
//No longer
//mail_unlock (stream); // no, give up
return reply;
}
while (i) // dump the text
{
if (!net_sout (stream->transStream,st->curpos,st->cursize))
{
//No longer
//mail_unlock (stream);
return imap_fake (stream,tag,"IMAP connection broken (data)");
}
i -= st->cursize; // note that we wrote out this much
st->curpos += (st->cursize - 1);
st->cursize = 0;
(*st->dtb->next) (st); // advance to next buffer's worth
}
return NULL; // success
}
/* IMAP send search program
* Accepts: MAIL stream
* reply tag
* pointer to current position pointer of output bigbuf
* search program to output
* Returns: error reply or NIL if success
*/
IMAPPARSEDREPLY *imap_send_spgm (MAILSTREAM *stream,char *tag,char **s,
SEARCHPGM *pgm)
{
IMAPPARSEDREPLY *reply;
SEARCHHEADER *hdr;
SEARCHOR *pgo;
SEARCHPGMLIST *pgl;
// char *t = "ALL";
char *t = "";
while (*t)
*(*s)++ = *t++; /* default initial text */
/* message sequences */
if (pgm->msgno)
imap_send_sset (s,pgm->msgno);
if (pgm->uid)
{ /* UID sequence */
for (t = "UID "; *t; *(*s)++ = *t++);
imap_send_sset (s,pgm->uid);
}
/* message sizes */
if (pgm->larger)
{
sprintf (*s,"LARGER %lu",pgm->larger);
*s += strlen (*s);
}
if (pgm->smaller)
{
sprintf (*s,"SMALLER %lu",pgm->smaller);
*s += strlen (*s);
}
/* message flags */
if (pgm->answered) for (t = "ANSWERED "; *t; *(*s)++ = *t++);
if (pgm->unanswered) for (t ="UNANSWERED "; *t; *(*s)++ = *t++);
if (pgm->deleted) for (t ="DELETED "; *t; *(*s)++ = *t++);
if (pgm->undeleted) for (t ="UNDELETED "; *t; *(*s)++ = *t++);
if (pgm->draft) for (t ="DRAFT "; *t; *(*s)++ = *t++);
if (pgm->undraft) for (t ="UNDRAFT "; *t; *(*s)++ = *t++);
if (pgm->flagged) for (t ="FLAGGED "; *t; *(*s)++ = *t++);
if (pgm->unflagged) for (t ="UNFLAGGED "; *t; *(*s)++ = *t++);
if (pgm->recent) for (t ="RECENT "; *t; *(*s)++ = *t++);
if (pgm->old) for (t ="OLD "; *t; *(*s)++ = *t++);
if (pgm->seen) for (t ="SEEN "; *t; *(*s)++ = *t++);
if (pgm->unseen) for (t ="UNSEEN "; *t; *(*s)++ = *t++);
if ((pgm->keyword && /* keywords */
(reply = imap_send_slist (stream,tag,s,"KEYWORD",pgm->keyword))) ||
(pgm->unkeyword &&
(reply = imap_send_slist (stream,tag,s,"UNKEYWORD",pgm->unkeyword))))
return reply;
/* sent date ranges */
if (pgm->sentbefore) imap_send_sdate (s,"SENTBEFORE",pgm->sentbefore);
if (pgm->senton) imap_send_sdate (s,"SENTON",pgm->senton);
if (pgm->sentsince) imap_send_sdate (s,"SENTSINCE",pgm->sentsince);
/* internal date ranges */
if (pgm->before) imap_send_sdate (s,"BEFORE",pgm->before);
if (pgm->on) imap_send_sdate (s,"ON",pgm->on);
if (pgm->since) imap_send_sdate (s,"SINCE",pgm->since);
/* search texts */
if ((pgm->bcc && (reply = imap_send_slist (stream,tag,s,"BCC",pgm->bcc))) ||
(pgm->cc && (reply = imap_send_slist (stream,tag,s,"CC",pgm->cc))) ||
(pgm->from && (reply = imap_send_slist(stream,tag,s,"FROM",pgm->from)))||
(pgm->to && (reply = imap_send_slist (stream,tag,s,"TO",pgm->to))))
return reply;
if ((pgm->subject &&
(reply = imap_send_slist (stream,tag,s,"SUBJECT",pgm->subject))) ||
(pgm->body && (reply = imap_send_slist(stream,tag,s,"BODY",pgm->body)))||
(pgm->text && (reply = imap_send_slist (stream,tag,s,"TEXT",pgm->text))))
return reply;
if (hdr = pgm->IMAPheader) do
{
for (t = "HEADER "; *t; *(*s)++ = *t++);
for (t = hdr->line; *t; *(*s)++ = *t++);
// JOK - Add a space!!
for (t = " "; *t; *(*s)++ = *t++);
if (reply = imap_send_astring (stream,tag,s,hdr->text,
(unsigned long) strlen (hdr->text),NIL))
return reply;
}
while (hdr = hdr->next);
if (pgo = pgm->or) do {
for (t = "OR ("; *t; *(*s)++ = *t++);
if (reply = imap_send_spgm (stream,tag,s,pgm->or->first)) return reply;
for (t = ") ("; *t; *(*s)++ = *t++);
if (reply = imap_send_spgm (stream,tag,s,pgm->or->second)) return reply;
*(*s)++ = ')';
} while (pgo = pgo->next);
if (pgl = pgm->not) do {
for (t = "NOT ("; *t; *(*s)++ = *t++);
if (reply = imap_send_spgm (stream,tag,s,pgl->pgm)) return reply;
*(*s)++ = ')';
}
while (pgl = pgl->next);
return NIL; /* search program written OK */
}
/* IMAP send search set
* Accepts: pointer to current position pointer of output bigbuf
* search set to output
*/
void imap_send_sset (char **s,SEARCHSET *set)
{
char c = 0;
char *t;
// Sanity: Must have at least these.
if (!(set && set->first))
{
ASSERT (0);
return;
}
do { /* run down search set */
if (c)
{
// If last is 0xFFFFFFFF, replace by "*". (JOK)
//
if (set->last == 0xFFFFFFFF)
{
sprintf (*s, "%c%lu:*", c, set->first);
}
else
{
sprintf (*s, set->last ? "%c%lu:%lu" : "%c%lu",c,set->first, set->last);
}
}
else
{
if (set->last == 0xFFFFFFFF)
{
sprintf (*s, "%lu:*",set->first);
}
else
{
sprintf (*s, set->last ? "%lu:%lu" : "%lu",set->first,set->last);
}
}
*s += strlen (*s);
c = ','; /* if there are any more */
}
while (set = set->next);
// (JOK) Add a space after the list.
for (t = " "; *t; *(*s)++ = *t++);
}
/* IMAP send search list
* Accepts: MAIL stream
* reply tag
* pointer to current position pointer of output bigbuf
* name of search list
* search list to output
* Returns: NIL if success, error reply if error
*/
IMAPPARSEDREPLY *imap_send_slist (MAILSTREAM *stream,char *tag,char **s,
char *name,STRINGLIST *list)
{
char *t;
IMAPPARSEDREPLY *reply = NIL;
do {
// (JOK) Space screws up some servers!! *(*s)++ = ' '; /* output name of search list */
for (t = name; *t; *(*s)++ = *t++);
*(*s)++ = ' ';
reply=imap_send_astring (stream,tag,s,list->text.data,list->text.size,NIL);
}
while (!reply && (list = list->next));
return reply;
}
/* IMAP send search date
* Accepts: pointer to current position pointer of output bigbuf
* field name
* search date to output
*/
void imap_send_sdate (char **s,char *name,unsigned short date)
{
sprintf (*s," %s %d-%s-%d",name,date & 0x1f,
months[((date >> 5) & 0xf) - 1],BASEYEAR + (date >> 9));
*s += strlen (*s);
}
/* IMAP send null-terminated string to sender
* Accepts: MAIL stream
* string
* Returns: T if success, else NIL
*/
long imap_soutr (MAILSTREAM *stream,char *string)
{
char tmp[MAILTMPLEN];
if (stream->debug) mm_dlog (string);
return (net_soutr (stream->transStream,strcat (strcpy (tmp,string),"\015\012")));
}
/* IMAP fake reply
* Accepts: MAIL stream
* tag
* text of fake reply
* Returns: parsed reply
*
* Added field to IMAPPARSEDREPLY to distinguish a fake reply from a real one -jdboyd 01/19/00
*/
IMAPPARSEDREPLY *imap_fake (MAILSTREAM *stream,char *tag,char *text)
{
IMAPPARSEDREPLY *reply = nil;
mm_notify (stream,text,BYE); // send bye alert
// don't kill the net stream anymore. Take care of this elsewhere. -JDB
//if (stream->transStream) net_close (stream->transStream);
//stream->transStream = NIL; // farewell, dear NET stream...
// build fake reply string
sprintf (LOCAL->tmp,"%s NO [CLOSED] %s",tag ? tag : "*",text);
// parse and return it
reply = imap_parse_reply (stream,cpystr (LOCAL->tmp));
if (reply) reply->fake = true;
return (reply);
}
/* IMAP message has been expunged
* Accepts: MAIL stream
* message number
*
* Calls external "mail_expunged" function to notify main program
*/
void imap_expunged (MAILSTREAM *stream,unsigned long msgno)
{
// Must have a stream.
if (!stream) return;
// All we do is to pass this to the upper layers.
mail_expunged (stream, msgno);
}
/* IMAP parse data
* Accepts: MAIL stream
* message #
* text to parse
* parsed reply
*
* This code should probably be made a bit more paranoid about malformed
* S-expressions.
*/
void imap_parse_data (MAILSTREAM *stream,unsigned long msgno,char *text,IMAPPARSEDREPLY *reply)
{
char *prop;
MESSAGECACHE *elt = mail_elt (stream);
if (!elt) return;
// Must have a reply ...
if (!reply) return;
++text; // skip past open parenthesis
// parse Lisp-form property list
while (prop = (char *) strtok (text," )"))
{
// point at value
text = (char *) strtok (NIL,"\n");
// parse the property and its value
imap_parse_prop (stream,elt,ucase (prop),&text,reply);
}
}
/* IMAP parse property
* Accepts: MAIL stream
* cache item
* property name
* property value text pointer
* parsed reply
*/
void imap_parse_prop (MAILSTREAM *stream,MESSAGECACHE *elt,char *prop,char **txtptr,IMAPPARSEDREPLY *reply)
{
char *s;
ENVELOPE **env;
IMAPBODY **body;
GETS_DATA md;
// Make sure we have a reply
if (!reply) return;
INIT_GETS (md,stream,elt->msgno,NIL,0,0);
if (!strcmp (prop,"ENVELOPE"))
{
imapenvelope_t ie =
(imapenvelope_t) mail_parameters (NIL,GET_IMAPENVELOPE,NIL);
if (stream->scache) // short cache, flush old stuff
{
mail_free_body (&stream->body);
stream->msgno=elt->msgno; // set new current message number
env = &stream->env; // get pointer to envelope
}
else env = &elt->privat.msg.env;
imap_parse_envelope (stream,env,txtptr,reply);
// do callback if requested
if (ie) (*ie) (stream,elt->msgno,*env);
}
else if (!strcmp (prop,"FLAGS"))
{
imap_parse_flags (stream,elt,txtptr);
// JOK (6/23/97) Pass the elt to top level to record the info.
// Do this only if the uid has already been obtained and flags are valid.
if (elt->privat.uid && elt->valid)
{
mm_elt_flags (stream, elt);
// After we have notified the upper layers, reset the elt's status
// because it mey be re-used.
elt->privat.uid = 0;
elt->valid = NIL;
}
}
else if (!strcmp (prop,"INTERNALDATE"))
{
if (s = imap_parse_string (stream,txtptr,reply,NIL,NIL))
{
if (!mail_parse_date (elt,s))
{
sprintf (LOCAL->tmp,"Bogus date: %.80s",s);
mm_log (LOCAL->tmp,WARN);
}
fs_give ((void **) &s);
}
}
else if (!strcmp (prop,"UID")) // unique identifier
{
elt->privat.uid = strtoul (*txtptr,txtptr,10);
// JOK (6/23/97) Pass the elt to top level to record the info.
// Do this only if the uid has already been obtained and flags are valid.
if (elt->privat.uid && elt->valid)
{
mm_elt_flags (stream, elt);
// After we have notified the upper layers, reset the elt's status
// because it mey be re-used.
elt->privat.uid = 0;
elt->valid = NIL;
}
// return the UID in the stream if we're chunking headers
if (stream->chunkHeaders)
{
stream->headerUID = elt->privat.uid;
}
}
else if (!strcmp (prop,"RFC822.HEADER") || !strcmp (prop,"BODY[HEADER]") || !strcmp (prop,"BODY[0]"))
{
if (elt->privat.msg.IMAPheader.text.data)
fs_give ((void **) &elt->privat.msg.IMAPheader.text.data);
md.what = "HEADER";
elt->privat.msg.IMAPheader.text.data = imap_parse_string (stream,txtptr,reply,&md,&elt->privat.msg.IMAPheader.text.size);
}
else if (!strcmp (prop,"RFC822.SIZE"))
elt->rfc822_size = strtoul (*txtptr,txtptr,10);
else if (!strcmp (prop,"RFC822.TEXT") || !strcmp (prop,"BODY[TEXT]"))
{
if (elt->privat.msg.text.text.data)
fs_give ((void **) &elt->privat.msg.text.text.data);
md.what = "TEXT";
elt->privat.msg.text.text.data = imap_parse_string (stream,txtptr,reply,&md,&elt->privat.msg.text.text.size);
}
else if (!strcmp (prop,"RFC822") || !strcmp (prop,"BODY[]"))
{
if (elt->privat.msg.full.text.data)
fs_give ((void **) &elt->privat.msg.full.text.data);
md.what = "";
elt->privat.msg.full.text.data = imap_parse_string (stream,txtptr,reply,&md,&elt->privat.msg.full.text.size);
}
else if (prop[0] == 'B' && prop[1] == 'O' && prop[2] == 'D' && prop[3] == 'Y')
{
s = cpystr (prop+4); // copy segment specifier
if (stream->scache) // short cache, flush old stuff
{
if (elt->msgno != stream->msgno)
{
/* losing real bad here */
mail_free_envelope (&stream->env);
sprintf (LOCAL->tmp,"Body received for %lu when current is %lu",
elt->msgno,stream->msgno);
mm_log (LOCAL->tmp,WARN);
stream->msgno = elt->msgno;
}
body = &stream->body; /* get pointer to body */
}
else body = &elt->privat.msg.body;
imap_parse_body (&md,body,s,txtptr,reply);
fs_give ((void **) &s);
}
else
{
sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
mm_log (LOCAL->tmp,WARN);
}
}
/* Parse RFC822 message IMAPheader
* Accepts: MAIL stream
* envelope to parse into
* IMAPheader as sized text
*/
void imap_parse_header (MAILSTREAM *stream,ENVELOPE **env,SIZEDTEXT *hdr)
{
ENVELOPE *nenv;
// parse what we can from this IMAPheader
rfc822_parse_msg (&nenv,NIL,hdr->data,hdr->size,NIL,imap_host (stream));
if (*env) // need to merge this IMAPheader into envelope?
{
if (!(*env)->newsgroups) // need Newsgroups?
{
(*env)->newsgroups = nenv->newsgroups;
nenv->newsgroups = NIL;
}
if (!(*env)->followup_to) // need Followup-To?
{
(*env)->followup_to = nenv->followup_to;
nenv->followup_to = NIL;
}
if (!(*env)->references) // need References?
{
(*env)->references = nenv->references;
nenv->references = NIL;
}
mail_free_envelope (&nenv);
}
else *env = nenv; // otherwise set it to this
}
/* IMAP parse envelope
* Accepts: MAIL stream
* pointer to envelope pointer
* current text pointer
* parsed reply
*
* Updates text pointer
*/
void imap_parse_envelope (MAILSTREAM *stream,ENVELOPE **env,char **txtptr,IMAPPARSEDREPLY *reply)
{
ENVELOPE *oenv = *env;
char c = *((*txtptr)++); // grab first character
// ignore leading spaces
while (c == ' ') c = *((*txtptr)++);
switch (c) // dispatch on first character
{
case '(': // if envelope S-expression
*env = mail_newenvelope (); // parse the new envelope
(*env)->date = imap_parse_string (stream,txtptr,reply,NIL,NIL);
(*env)->subject = imap_parse_string (stream,txtptr,reply,NIL,NIL);
(*env)->from = imap_parse_adrlist (stream,txtptr,reply);
(*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
(*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
(*env)->to = imap_parse_adrlist (stream,txtptr,reply);
(*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
(*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
(*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,NIL,NIL);
(*env)->message_id = imap_parse_string (stream,txtptr,reply,NIL,NIL);
if (oenv) // need to merge old envelope?
{
(*env)->newsgroups = oenv->newsgroups;
oenv->newsgroups = NIL;
(*env)->followup_to = oenv->followup_to;
oenv->followup_to = NIL;
(*env)->references = oenv->references;
oenv->references = NIL;
mail_free_envelope(&oenv); // free old envelope
}
if (**txtptr != ')')
{
sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
}
else ++*txtptr; // skip past delimiter
break;
case 'N': // if NIL
case 'n':
++*txtptr;
++*txtptr;
break;
default:
sprintf (LOCAL->tmp,"Not an envelope: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
break;
}
}
/* IMAP parse address list
* Accepts: MAIL stream
* current text pointer
* parsed reply
* Returns: address list, NIL on failure
*
* Updates text pointer
*/
ADDRESS *imap_parse_adrlist (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply)
{
ADDRESS *adr = NIL;
char c = **txtptr; // sniff at first character
// ignore leading spaces
while (c == ' ') c = *++*txtptr;
++*txtptr; // skip past open paren
switch (c)
{
case '(': // if envelope S-expression
adr = imap_parse_address (stream,txtptr,reply);
if (**txtptr != ')') // need to merge old envelope?
{
sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
}
else ++*txtptr; // skip past delimiter
break;
case 'N': //If NIL
case 'n':
++*txtptr;
++*txtptr;
break;
default:
sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
break;
}
return adr;
}
/* IMAP parse address
* Accepts: MAIL stream
* current text pointer
* parsed reply
* Returns: address, NIL on failure
*
* Updates text pointer
*/
ADDRESS *imap_parse_address (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply)
{
ADDRESS *adr = NIL;
ADDRESS *ret = NIL;
ADDRESS *prev = NIL;
char c = **txtptr; /* sniff at first address character */
switch (c)
{
case '(': // if envelope S-expression
while (c == '(') // recursion dies on small stack machines
{
++*txtptr; // skip past open paren
if (adr) prev = adr; // note previous if any
adr = mail_newaddr (); // instantiate address and parse its fields
adr->personal = imap_parse_string (stream,txtptr,reply,NIL,NIL);
adr->adl = imap_parse_string (stream,txtptr,reply,NIL,NIL);
adr->mailbox = imap_parse_string (stream,txtptr,reply,NIL,NIL);
adr->host = imap_parse_string (stream,txtptr,reply,NIL,NIL);
if (**txtptr != ')') // handle trailing paren
{
sprintf (LOCAL->tmp,"Junk at end of address: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
}
else ++*txtptr; // skip past close paren
c = **txtptr; // set up for while test
// ignore leading spaces in front of next
while (c == ' ') c = *++*txtptr;
if (!ret) ret = adr; // if first time note first adr
// if previous link new block to it
if (prev) prev->next = adr;
}
break;
case 'N': //Nil
case 'n':
*txtptr += 3; // bump past NIL
break;
default:
sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
break;
}
return ret;
}
/* IMAP parse flags
* Accepts: current message cache
* current text pointer
*
* Updates text pointer
*/
void imap_parse_flags (MAILSTREAM *stream,MESSAGECACHE *elt,char **txtptr)
{
char *flag;
char c = '\0';
Str255 sentFlag;
// read the flag that we'll treat as //Sent from the settings ...
GetRString(sentFlag, IMAP_SENT_FLAG);
if (sentFlag[0])
{
sentFlag[sentFlag[0] + 1] = 0;
ucase(sentFlag);
}
elt->valid = T; // mark have valid flags now
elt->user_flags = NIL; // zap old flag values
elt->seen = elt->deleted = elt->flagged = elt->answered = elt->recent = elt->sent = NIL;
#ifdef DEBUG
if (stream->flagsRefN > 0)
{
char *s = *txtptr;
long count = strlen(*txtptr);
while ((s < *txtptr + count) && (*s != ')')) s++;
if (*s==')') s++;
count = s - *txtptr;
// write the line to the spool file
if (count)
{
AWrite(stream->flagsRefN,&count,*txtptr);
FSWriteP(stream->flagsRefN,Cr);
}
}
#endif
while (c != ')') // parse list of flags
{
// point at a flag
while (*(flag = ++*txtptr) == ' ');
// scan for end of flag
while (**txtptr != ' ' && **txtptr != ')') ++*txtptr;
c = **txtptr; // save delimiter
**txtptr = '\0'; // tie off flag
if (!*flag) break; // null flag
else if (*ucase (flag) == '\\') // if starts with \ must be sys flag
{
if (sentFlag[0] && !strcmp (flag,sentFlag+1)) elt->sent = T;
else if (!strcmp (flag,"\\SEEN")) elt->seen = T;
else if (!strcmp (flag,"\\DELETED")) elt->deleted = T;
else if (!strcmp (flag,"\\FLAGGED")) elt->flagged = T;
else if (!strcmp (flag,"\\ANSWERED")) elt->answered = T;
else if (!strcmp (flag,"\\RECENT")) elt->recent = T;
else if (!strcmp (flag,"\\DRAFT")) elt->draft = T;
}
else // otherwise user flag
elt->user_flags |= imap_parse_user_flag (stream,flag);
}
++*txtptr; // bump past delimiter
mm_flags (stream,elt->msgno); // make sure top level knows
}
/* IMAP parse user flag
* Accepts: MAIL stream
* flag name
* Returns: flag bit position
*/
unsigned long imap_parse_user_flag (MAILSTREAM *stream,char *flag)
{
char tmp[MAILTMPLEN];
long i;
// sniff through all user flags
for (i = 0; i < NUSERFLAGS; ++i)
if (stream->user_flags[i] && !strcmp (flag,ucase (strcpy (tmp,stream->user_flags[i])))) return (1 << i); // found it!
return (unsigned long) 0; // not found
}
/* IMAP parse string
* Accepts: MAIL stream
* current text pointer
* parsed reply
* mailgets data
* returned string length
* Returns: string
*
* Updates text pointer
*/
char *imap_parse_string (MAILSTREAM *stream,char **txtptr, IMAPPARSEDREPLY *reply,GETS_DATA *md, unsigned long *len)
{
char *st;
char *string = NIL;
unsigned long i,j,k;
char c = **txtptr; // sniff at first character
mailgets_t mg = (mailgets_t) mail_parameters (stream,GET_GETS,NIL);
readprogress_t rp = (readprogress_t) mail_parameters (NIL,GET_READPROGRESS,NIL);
while (c == ' ') c = *++*txtptr; // ignore leading spaces
st = ++*txtptr; // remember start of string
switch (c)
{
case '"': // if quoted string
i = 0; // initial byte count
while (**txtptr != '"') // search for end of string
{
if (**txtptr == '\\') ++*txtptr;
++i; // bump count
++*txtptr; // bump pointer
}
++*txtptr; // bump past delimiter
// JOK - If we have a mailgets, do that instead.
if (md && mg)
{
// Allocate a ParenStr data object to send data to caller.
ParenStrData strData;
md->flags |= MG_COPY;/* otherwise flag need to copy */
strData.s = st;
strData.size = i;
(*mg) (str_getbuffer, &strData, i, md);
// Doesn't return a value.
string = NULL;
}
/* else must copy into free storage */
else
{
string = (char *) fs_get ((size_t) i + 1);
if (!string) return (NULL); //fs_get returns nil if it fails
for (j = 0; j < i; j++) // copy the string
{
if (*st == '\\') ++st; // quoted character
string[j] = *st++;
}
string[j] = '\0'; // tie off string
if (len) *len = i; // set return value too
}
break;
case 'N': // if NIL
case 'n':
++*txtptr; // bump past "I"
++*txtptr; // bump past "L"
if (len) *len = 0;
break;
case '{': // if literal string
// get size of string
i = strtoul (*txtptr,txtptr,10);
if (len) *len = i; // set return value
if (md && mg) // have special routine to slurp string?
{
if (md->first) // partial fetch?
{
md->first--; // restore origin octet
md->last = i; // number of octets that we got
}
else md->flags |= MG_COPY; // otherwise flag need to copy
string = (*mg) (net_getbuffer,stream->transStream,i,md);
}
else // must slurp into free storage
{
string = (char *) fs_get ((size_t) i + 1);
if (!string) return (NULL); //fs_get returns nil if it fails
*string = '\0'; // init in case getbuffer fails
if (rp) for (k = 0; j = min ((long) MAILTMPLEN,(long) i); i -= j)
{
net_getbuffer (stream->transStream,j,string + k);
(*rp) (md,k += j);
}
else net_getbuffer (stream->transStream,i,string);
}
fs_give ((void **) &reply->line);
// get new reply text line
reply->line = net_getline (stream->transStream);
if (stream->debug) mm_dlog (reply->line);
*txtptr = reply->line; // set text pointer to point at it
break;
default:
sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,*txtptr);
mm_log (LOCAL->tmp,WARN);
if (len) *len = 0;
break;
}
return string;
}
/* IMAP parse body structure or contents
* Accepts: mailgets_data
* pointer to body pointer
* pointer to segment
* current text pointer
* parsed reply
*
* Updates text pointer, stores body
*/
void imap_parse_body (GETS_DATA *md,IMAPBODY **body,char *seg,char **txtptr,IMAPPARSEDREPLY *reply)
{
char *s;
unsigned long size;
STRINGLIST *stl = NIL;
char *tmp = ((IMAPLOCAL *) md->stream->local)->tmp;
MESSAGECACHE *elt;
// Get the stream's "CurrentElt" (JOK)
// This will allocate one if necessary.
elt = mail_elt (md->stream);
if (!elt)
return;
/* dispatch based on type of data */
switch (*seg++)
{
case 'S': /* extensible body structure */
if (strcmp (seg,"TRUCTURE"))
{
sprintf (tmp,"Bad body fetch: %.80s",seg);
mm_log (tmp,WARN);
return;
}
/* falls through */
case '\0': /* body structure */
mail_free_body (body); /* flush any prior body */
/* instantiate and parse a new body */
imap_parse_body_structure (md->stream, *body = mail_newbody(), txtptr, reply);
break;
// JOK: 4/24/98 - Added .PEEK because the Novell groupwise server returns BODY.PEEK!!!
case '.':
ucase (seg); /* make sure uppercase */
// Better be this:
if (strncmp (seg,"PEEK[", 5))
{
sprintf (tmp,"Bad body fetch: %.80s",seg);
mm_log (tmp,WARN);
return;
}
// Othersize, go pass PEEK[ and fall through:
seg += 5;
case '[': /* body section text */
ucase (seg); /* make sure uppercase */
/* header lines case? */
if (!(s = strchr(seg,']')))
{
/* skip leading nesting */
for (s = seg; *s && (isdigit (*s) || (*s == '.')); s++);
/* better be one of these */
if (strcmp (s,"HEADER.FIELDS") && strcmp (s,"HEADER.FIELDS.NOT"))
{
sprintf (tmp,"Unterminated section specifier: %.80s",seg);
mm_log (tmp,WARN);
return;
}
/* get list of headers */
if (!(stl = imap_parse_stringlist (md->stream,txtptr,reply)))
{
sprintf (tmp,"Bogus header field list: %.80s",*txtptr);
mm_log (tmp,WARN);
return;
}
// JOK - We don't really need the string list!!!
mail_free_stringlist (&stl);
// END JOK
/* make sure terminated */
if (**txtptr != ']')
{
sprintf (tmp,"Unterminated header section specifier: %.80s",*txtptr);
mm_log (tmp,WARN);
mail_free_stringlist (&stl);
return;
}
/* point after the text */
if (*txtptr = strchr (s = *txtptr,' '))
*(*txtptr)++ = '\0';
}
*s++ = '\0'; /* tie off section specifier */
/* partial specifier? */
if (*s == '<')
{
md->first = strtoul (s+1,&s,10) + 1;
// Some servers spit back a <x.y> in the partial specifiers.
// They are both wrong and stupid. See RFC 2060, the FETCH response. -jdboyd 8/19/99
if (*s == '.')
{
s++; // skip over the period
while (isdigit(*s)) s++; // and any numbers that follow.
}
/* make sure properly terminated */
if ((*s == NULL) || (*s++ != '>'))
{
sprintf (tmp,"Unterminated partial data specifier: %.80s",s-1);
mm_log (tmp,WARN);
mail_free_stringlist (&stl);
return;
}
}
/* make sure no junk follows */
if (*s)
{
sprintf (tmp,"Junk after section specifier: %.80s",s);
mm_log (tmp,WARN);
mail_free_stringlist (&stl);
return;
}
md->what = seg; /* get the body section text */
// JOK - Calling "imap_parse_string()" will send the data to the caller.
s = NULL; // So we won't try to free it.
imap_parse_string (md->stream, txtptr, reply, md, &size);
// done if partial
if (md->first || md->last)
{
mail_free_stringlist (&stl);
return;
}
// JOK - Ignore the rest.
break;
default: /* bogon */
sprintf (tmp,"Bad body fetch: %.80s",seg);
mm_log (tmp,WARN);
return;
} // switch
}
/* IMAP parse body structure
* Accepts: MAIL stream
* body structure to write into
* current text pointer
* parsed reply
*
* Updates text pointer
*/
void imap_parse_body_structure (MAILSTREAM *stream,IMAPBODY *body,char **txtptr,IMAPPARSEDREPLY *reply)
{
int i;
char *s;
PART *part = NIL;
char c;
// Must have these ...
if (!stream || !body || !txtptr || !(*txtptr)) return;
// grab first character
c = *((*txtptr)++);
// ignore leading spaces
while (c == ' ') c = *((*txtptr)++);
// dispatch on first character
switch (c)
{
case '(': // body structure list
if (**txtptr == '(') // multipart body?
{
body->type= TYPEMULTIPART; // yes, set its type
// instantiate new body part
do
{
if (part) part = part->next = mail_newbody_part ();
else body->nested.part = part = mail_newbody_part ();
// parse it
imap_parse_body_structure (stream,&part->body,txtptr,reply);
// ignore possible spaces until the next '(' (JOK)
while (**txtptr == ' ') ++(*txtptr);
} while (**txtptr == '('); // for each body part
if (body->subtype = imap_parse_string (stream,txtptr,reply,NIL,NIL))
ucase (body->subtype);
else
{
// Set it to "Multipart/Mixed" (JOK)
// body->subtype = cpystr ("Mixed");
mm_log ("Missing multipart subtype",WARN);
}
// multipart parameters
if (**txtptr == ' ')
body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
// disposition
if (**txtptr == ' ')
imap_parse_disposition (stream,body,txtptr,reply);
// language
if (**txtptr == ' ')
body->language = imap_parse_language (stream,txtptr,reply);
while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
// validate ending
if (**txtptr != ')')
{
sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
}
else ++*txtptr; // skip past delimiter
}
else
{
// not multipart, parse type name
if (**txtptr == ')')
{
// empty body?
++*txtptr; // bump past it
break; // and punt
}
body->type = TYPEOTHER; // assume unknown type
body->encoding = ENCOTHER; // and unknown encoding
// parse type
if (s = ucase (imap_parse_string (stream,txtptr,reply,NIL,NIL)))
{
for (i=0;(i<=TYPEMAX) && body_types[i] && strcmp(s,body_types[i]);i++);
if (i <= TYPEMAX)
{
// only if found a slot
body->type = (unsigned short)i; // set body type
if (body_types[i]) fs_give ((void **) &s);
else body_types[i]=s; // assign empty slot
}
}
// parse subtype
body->subtype = ucase (imap_parse_string (stream,txtptr,reply,NIL,NIL));
// parse parameter
body->parameter = imap_parse_body_parameter (stream,txtptr,reply);
// parse id
body->id = imap_parse_string (stream,txtptr,reply,NIL,NIL);
// parse description
body->description = imap_parse_string (stream,txtptr,reply,NIL,NIL);
if (s = ucase (imap_parse_string (stream,txtptr,reply,NIL,NIL)))
{
// search for body encoding
for (i = 0; (i <= ENCMAX) && body_encodings[i] && strcmp (s,body_encodings[i]); i++);
if (i > ENCMAX) body->type = ENCOTHER;
else
{ // only if found a slot
body->encoding = (unsigned short) i; // set body encoding
if (body_encodings[i]) fs_give ((void **) &s);
else body_encodings[i] = s;
}
#ifdef WINDERZ
//
// If the subtype is binhex and the encoding is read as just text,
// this should be decoded with binhex. Since we only pass an encoding type to
// "FetchAttachmentContentsToFile" below, set an appropriate encoding flag.
//
CRString szBinhex (IDS_MIME_BINHEX);
CString szSubtype = body->subtype;
if ( (body->subtype != NULL) &&
(!strnicmp ( (LPCSTR)szSubtype, (LPCSTR)szBinhex, szBinhex.GetLength() ) ) )
{
if ( body->encoding == ENC7BIT || body->encoding == ENCOTHER )
{
body->encoding = ENCBINHEX;
}
}
#endif
}
// parse size of contents in bytes
body->size.bytes = strtoul (*txtptr,txtptr,10);
// possible extra stuff
switch (body->type)
{
case TYPEMESSAGE: // message envelope and body
if (strcmp (body->subtype,"RFC822")) break;
body->nested.msg = mail_newmsg ();
imap_parse_envelope (stream,&body->nested.msg->env,txtptr,reply);
body->nested.msg->body = mail_newbody ();
imap_parse_body_structure(stream,body->nested.msg->body,txtptr,reply);
// drop into text case
// size in lines
case TYPETEXT:
body->size.lines = strtoul (*txtptr,txtptr,10);
break;
// otherwise nothing special
default:
break;
}
// if extension data
if (**txtptr == ' ') body->md5 = imap_parse_string (stream,txtptr,reply,NIL,NIL);
// disposition
if (**txtptr == ' ') imap_parse_disposition (stream,body,txtptr,reply);
// language
if (**txtptr == ' ') body->language = imap_parse_language (stream,txtptr,reply);
while (**txtptr == ' ') imap_parse_extension (stream,txtptr,reply);
// validate ending
if (**txtptr != ')')
{
sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
}
else
{
++*txtptr; // skip past delimiter
// ignore possible spaces until the next '(' (JOK)
while (**txtptr == ' ') ++(*txtptr);
}
}
break;
case 'N': // if NIL
case 'n':
++*txtptr; // bump past "I"
++*txtptr; // bump past "L"
break;
default: // otherwise quite bogus
sprintf (LOCAL->tmp,"Bogus body structure: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
break;
}
}
/* IMAP parse body parameter
* Accepts: MAIL stream
* current text pointer
* parsed reply
* Returns: body parameter
* Updates text pointer
*/
PARAMETER *imap_parse_body_parameter (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply)
{
PARAMETER *ret = NIL;
PARAMETER *par = NIL;
char c,*s;
// ignore leading spaces
while ((c = *(*txtptr)++) == ' ');
// parse parameter list
if (c == '(')
{
while (c != ')')
{
// append new parameter to tail
if (ret) par = par->next = mail_newbody_parameter ();
else ret = par = mail_newbody_parameter ();
if(!(par->attribute=imap_parse_string (stream,txtptr,reply,NIL,NIL)))
{
mm_log ("Missing parameter attribute",WARN);
par->attribute = cpystr ("UNKNOWN");
}
if (!(par->value = imap_parse_string (stream,txtptr,reply,NIL,NIL)))
{
sprintf (LOCAL->tmp,"Missing value for parameter %.80s",par->attribute);
mm_log (LOCAL->tmp,WARN);
par->value = cpystr ("UNKNOWN");
}
switch (c = **txtptr) // see what comes after
{
case ' ': // flush whitespace
while ((c = *++*txtptr) == ' ');
break;
case ')': // end of attribute/value pairs
++*txtptr; // skip past closing paren
break;
default:
sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
break;
}
}
}
// empty parameter, must be NIL
else if (((c == 'N') || (c == 'n')) && ((*(s = *txtptr) == 'I') || (*s == 'i')) && ((s[1] == 'L') || (s[1] == 'l'))) *txtptr += 2;
else
{
sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,(*txtptr) - 1);
mm_log (LOCAL->tmp,WARN);
}
return ret;
}
/* IMAP parse body disposition
* Accepts: MAIL stream
* body structure to write into
* current text pointer
* parsed reply
*/
void imap_parse_disposition (MAILSTREAM *stream,IMAPBODY *body,char **txtptr,IMAPPARSEDREPLY *reply)
{
switch (*++*txtptr)
{
case '(':
++*txtptr; // skip open paren
body->disposition.type = imap_parse_string (stream,txtptr,reply,NIL,NIL);
body->disposition.parameter =
imap_parse_body_parameter (stream,txtptr,reply);
if (**txtptr != ')') // validate ending
{
sprintf (LOCAL->tmp,"Junk at end of disposition: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
}
else ++*txtptr; // skip past delimiter
break;
case 'N': // if NIL
case 'n':
++*txtptr; // bump past "N"
++*txtptr; // bump past "I"
++*txtptr; // bump past "L"
break;
default:
sprintf (LOCAL->tmp,"Unknown body disposition: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
// try to skip to next space
while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr);
break;
}
}
/* IMAP parse body language
* Accepts: MAIL stream
* current text pointer
* parsed reply
* Returns: string list or NIL if empty or error
*/
STRINGLIST *imap_parse_language (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply)
{
unsigned long i;
char *s;
STRINGLIST *ret = NIL;
// language is a list
if (*++*txtptr == '(') ret = imap_parse_stringlist (stream,txtptr,reply);
else if (s = imap_parse_string (stream,txtptr,reply,NIL,&i))
{
(ret = mail_newstringlist ())->text.data = s;
ret->text.size = i;
}
return ret;
}
/* IMAP parse string list
* Accepts: MAIL stream
* current text pointer
* parsed reply
* Returns: string list or NIL if empty or error
*/
STRINGLIST *imap_parse_stringlist (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply)
{
STRINGLIST *stl = NIL;
STRINGLIST *stc;
char c,*s;
char *t = *txtptr;
// parse the list
if (*t++ == '(')
{
while (*t != ')')
{
if (stl) stc = stc->next = mail_newstringlist ();
else stc = stl = mail_newstringlist ();
// atom
if ((*t != '{') && (*t != '"') && (s = strpbrk (t," )")))
{
c = *s; // note delimiter
*s = '\0'; // tie off atom and copy it
stc->text.size = strlen (stc->text.data = cpystr (t));
if (c == ' ') t = ++s; // another atom follows
else *(t = s) = c; // restore delimiter
}
// string
else if (!(stc->text.data = imap_parse_string (stream,&t,reply,NIL,&stc->text.size)))
{
sprintf (LOCAL->tmp,"Bogus string list member: %.80s",t);
mm_log (LOCAL->tmp,WARN);
mail_free_stringlist (&stl);
break;
}
}
}
if (stl) *txtptr = ++t; // update return string
return stl;
}
/* IMAP parse unknown body extension data
* Accepts: MAIL stream
* current text pointer
* parsed reply
*
* Updates text pointer
*/
void imap_parse_extension (MAILSTREAM *stream,char **txtptr,IMAPPARSEDREPLY *reply)
{
unsigned long i,j;
switch (*++*txtptr) // action depends upon first character
{
case '(':
while (**txtptr != ')') imap_parse_extension (stream,txtptr,reply);
++*txtptr; // bump past closing parenthesis
break;
case '"': // if quoted string
while (*++*txtptr != '"') if (**txtptr == '\\') ++*txtptr;
++*txtptr; // bump past closing quote
break;
case 'N': // if NIL
case 'n':
++*txtptr; // bump past "N"
++*txtptr; // bump past "I"
++*txtptr; // bump past "L"
break;
case '{': // get size of literal
++*txtptr; // bump past open squiggle
if (i = strtoul (*txtptr,txtptr,10)) do
net_getbuffer (stream->transStream,j = min (i,(long)IMAPTMPLEN),LOCAL->tmp); // was "max", which makes little sense. -jdboyd 030304
while (i -= j);
// get new reply text line
reply->line = net_getline (stream->transStream);
if (stream->debug) mm_dlog (reply->line);
*txtptr = reply->line; // set text pointer to point at it
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
strtoul (*txtptr,txtptr,10);
break;
default:
sprintf (LOCAL->tmp,"Unknown extension token: %.80s",*txtptr);
mm_log (LOCAL->tmp,WARN);
// try to skip to next space
while ((*++*txtptr != ' ') && (**txtptr != ')') && **txtptr);
break;
}
}
/* IMAP return host name
* Accepts: MAIL stream
* Returns: host name
*/
char *imap_host (MAILSTREAM *stream)
{
/* return host name on stream if open */
return (LOCAL && stream->transStream) ? net_host (stream->transStream) : ".NO-IMAP-CONNECTION.";
}
/* IMAP open
* Accepts: stream to open
* Returns: stream to use on success, NULL on failure
*/
MAILSTREAM *imap_open (MAILSTREAM *stream)
{
unsigned long i;
unsigned long alive;
char *s;
char tmp[MAILTMPLEN];
char userName[MAILTMPLEN];
NETMBX mb;
IMAPPARSEDREPLY *reply = NULL;
// return prototype for OP_PROTOTYPE call
if (!stream) return ((MAILSTREAM *)&imapdriver);
// fill in the mb structure from the command line.
imapmail_valid_net_parse(stream,&mb);
userName[0] = NULL; // Username initially empty
if (LOCAL) // if stream opened earlier by us
{
// if the stream is not autenticated at this point, close it and re-open it.
if (!stream->bAuthenticated) imap_close(stream,nil);
// if hosts are different, close the old one. Note, mb.host is a pstring
else if (((mb.host[0] && imap_host(stream)) && !pstrincmp(mb.host,imap_host(stream),mb.host[0]))
|| (mb.user[0] && LOCAL->user && strcmp(mb.user,LOCAL->user)))
{
sprintf(tmp,"Closing connection to %s",imap_host(stream));
if (!stream->silent) mm_log(tmp,(long) NULL);
imap_close(stream,NULL);
}
else if (stream->transStream)
{
// else recycle if still alive
i = stream->silent;
stream->silent = true;
// alive = imap_ping(stream); // learn if stream still alive
alive = true; // imapconnections.c has already determined this stream is still alive. - JDB 160799
stream->silent = i;
if (alive)
{
sprintf(tmp,"Reusing connection to %s",mb.host);
if (LOCAL->user) sprintf(tmp + strlen (tmp),"/user=%s",LOCAL->user);
if (!stream->silent) mm_log(tmp,(long) NULL);
}
else imap_close(stream,NULL);
}
} // if a re-open
// in case /debug switch given
if (mb.dbgflag) stream->debug = true;
// open new connection if no recycle
if (!LOCAL)
{
stream->local = fs_get(sizeof (IMAPLOCAL));
if (!stream->local) return NULL;
FillInLocal :
// assume IMAP2bis server
LOCAL->imap2bis = LOCAL->rfc1176 = true;
if (!stream->transStream) // didn't get rimap?
{
unsigned long prt;
// use the given port, or the default port if non specified.
if(mb.port)
prt = mb.port;
else
#ifdef ESSL
if(GetPrefLong(PREF_SSL_IMAP_SETTING) & esslUseAltPort)
prt = GetRLong(IMAP_SSL_PORT);
else
#endif
prt = IMAPTCPPORT;
s = mb.host;
// try to open ordinary connection
if ((stream->transStream = net_open(stream,s,"imap",prt))
&& !imap_OK(stream,reply = imap_reply(stream,NULL)))
{
mm_log (reply->text,IMAP_ERROR);
return NULL;
}
}
if (stream->transStream) // if have a connection
{
// non-zero if not preauthenticated
if (( reply->key != NULL ) && ( i = strcmp(reply->key,"PREAUTH" )))
userName[0] = '\0';
// get server capabilities
reply = imap_send (stream,"CAPABILITY",NULL);
#ifdef ESSL
if(reply && imap_OK(stream,reply) && ShouldUseSSL(stream->transStream) && !(stream->transStream->ESSLSetting & esslSSLInUse))
{
// OK, there are several cases here.
// (1) Server doesn't offer TLS
// (a) User has required TLS -- error
// (b) TLS is optional - do nothing
// (2) Server has offered TLS --> fire it up!
// (a) ESSLStartSSL succeeeds --> We're good to go
// (b) ESSLStartSSL fails
// (1) User has required TLS - error
// (2) TLS is optional - continue w/o TLS
Boolean sslRequired = 0 == ( stream->transStream->ESSLSetting & esslOptional );
if (!LOCAL->use_tls) // No TLS for this server!
{
if ( sslRequired )
{
ComposeStdAlert ( Note, ALRTStringsStrn+NO_SERVER_SSL );
goto DoSSLErr;
}
}
else
{
IMAPPARSEDREPLY *sslReply;
// starttls
sslReply = imap_send (stream,"STARTTLS",NULL);
if(!sslReply || !imap_OK(stream, sslReply) || (ESSLStartSSL(stream->transStream) != noErr))
{
DoSSLErr :
if ( sslRequired )
{
net_close(stream->transStream);
stream->transStream = NULL;
mm_log(GetRString(tmp, SSL_ERR_STRING)+1, IMAP_ERROR);
return NIL;
}
}
else if(stream->transStream->ESSLSetting & esslSSLInUse)
{
WriteZero(LOCAL, sizeof(IMAPLOCAL));
goto FillInLocal;
}
}
}
#endif
if (reply)
{
// need to authenticate?
if (stream->transStream
&& i
&& !((LOCAL->use_auth && !(mb.anoflag || stream->anonymous) && !PrefIsSet(PREF_IMAP_DONT_AUTHENTICATE)) ? imap_auth (stream,&mb,tmp,userName) : imap_login (stream,&mb,tmp,userName)))
{
// Close the stream if we failed to authenticate.
if (stream->transStream) net_close (stream->transStream);
stream->transStream = NULL;
return NIL; // authentication failed
}
// We succeeded. And we are now authenticated.
stream->bAuthenticated = true;
}
else
{
// User must have quit.
if (stream->transStream) net_close(stream->transStream);
stream->transStream = NULL;
return NIL; // authentication failed
}
}
}
if (stream->transStream) // still have a connection?
{
stream->perm_seen = stream->perm_deleted = stream->perm_answered = stream->perm_draft = LEVELIMAP4(stream) ? NULL : true;
stream->perm_user_flags = LEVELIMAP4(stream) ? NULL : 0xffffffff;
stream->sequence++;
if ((i = net_port(stream->transStream)) & 0xffff0000) i = imap_defaultport ? imap_defaultport : IMAPTCPPORT;
// record user name
if (!LOCAL->user && userName[0]) LOCAL->user = cpystr(userName);
sprintf (tmp,LOCAL->user ? "{%s:%lu/imap/user=%s}" : "{%s:%lu/imap}", net_host(stream->transStream),i,LOCAL->user);
if (!stream->halfopen) // wants to open a mailbox?
{
IMAPARG *args[2];
IMAPARG ambx;
ambx.type = ASTRING;
ambx.text = (void *) mb.mailbox;
args[0] = &ambx; args[1] = NULL;
reply = imap_send (stream,stream->rdonly ? "EXAMINE": "SELECT",args);
if (reply && imap_OK(stream,reply))
{
// We've succeeded.
stream->bSelected = TRUE;
strcat(tmp,mb.mailbox);
// note if server said it was readonly
stream->rdonly = !strncmp(ucase (reply->text),"[READ-ONLY]",11);
if (!stream->nmsgs && !stream->silent) mm_log ("Mailbox is empty",(long) NULL);
}
else // failed
{
// We're not selected
stream->bSelected = FALSE;
mm_log (reply->text,IMAP_ERROR);
if (imap_closeonerror) return NULL;
stream->halfopen = true; /* let him keep it half-open */
}
}
if (stream->halfopen) // half-open connection?
{
// Not selected
stream->bSelected = FALSE;
strcat(tmp,"<no_mailbox>");
// make sure dummy message counts
mail_exists(stream,(long) 0);
mail_recent(stream,(long) 0);
}
fs_give ((void **) &stream->mailbox);
stream->mailbox = cpystr (tmp);
}
/* success if stream open */
return stream->transStream ? stream : NULL;
}
/* IMAP get reply
* Accepts: MAIL stream
* tag to search or NIL if want a greeting
* Returns: parsed reply, never NIL
*/
IMAPPARSEDREPLY *imap_reply (MAILSTREAM *stream,char *tag)
{
IMAPPARSEDREPLY *reply;
if (!stream) return (0);
while (imap_connected(stream) && stream->transStream)
{
// parse reply from server
if (reply = imap_parse_reply(stream,net_getline(stream->transStream)))
{
if (!strcmp (reply->tag,"+")) return reply;
else if (!strcmp (reply->tag,"*"))
{
imap_parse_unsolicited (stream,reply);
if (!tag) return reply; /* return if just wanted greeting */
}
else // tagged data
{
if (tag && !strcmp (tag,reply->tag)) return reply;
sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s", reply->tag,reply->key,reply->text);
mm_log (LOCAL->tmp,WARN);
}
}
}
return imap_fake(stream,tag,"IMAP connection broken (server response)");
}
/* IMAP check for OK response in tagged reply
* Accepts: MAIL stream
* parsed reply
* Returns: T if OK else NIL
*/
long imap_OK(MAILSTREAM *stream, IMAPPARSEDREPLY *reply)
{
// OK - operation succeeded
if (!strcmp (reply->key, "OK") || (!strcmp (reply->tag,"*") && !strcmp (reply->key,"PREAUTH"))) return T;
// NO - operation failed
else if (strcmp (reply->key,"NO"))
{
// BAD - operation rejected
if (!strcmp (reply->key,"BAD"))
sprintf (LOCAL->tmp,"IMAP error: %.80s",reply->text);
else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",reply->key,reply->text);
mm_log (LOCAL->tmp,WARN); /* log the sucker */
}
else
{
// we received a NO response. See if we got an ALERT response along with it and handle it -jdboyd
if (!strncmp (reply->text,"[ALERT]", 7))
mm_alert(stream, reply->text);
}
return NIL;
}
/* IMAP parse reply
* Accepts: MAIL stream
* text of reply
* Returns: parsed reply, or NIL if can't parse at least a tag and key
*/
IMAPPARSEDREPLY *imap_parse_reply(MAILSTREAM *stream, char *text)
{
if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
if (!(LOCAL->reply.line = text)) // NIL text means the stream died
{
// Don't close the connection to the server anymore. We'll reuse it later.
//if (stream->transStream) net_close (stream->transStream);
//stream->transStream = NIL;
return NIL;
}
if (stream->debug) mm_dlog (LOCAL->reply.line);
LOCAL->reply.key = NIL; // init fields in case error
LOCAL->reply.text = NIL;
LOCAL->reply.fake = false;
if (!(LOCAL->reply.tag = (char *) strtok (LOCAL->reply.line," ")))
{
mm_log ("IMAP server sent a blank line",WARN);
return NIL;
}
// non-continuation replies
if (strcmp (LOCAL->reply.tag,"+")) // parse key
{
if (!(LOCAL->reply.key = (char *) strtok (NIL," "))) //determine what is missing
{
sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",LOCAL->reply.tag);
mm_log (LOCAL->tmp,WARN); // pass up the barfage
return NIL; // can't parse this text */
}
ucase (LOCAL->reply.key); // make sure key is upper case
if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n"))) //get text as well, allow empty text
LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
}
else // special handling of continuation
{
LOCAL->reply.key = "BAD"; // so it barfs if not expecting continuation
if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n")))
LOCAL->reply.text = "Ready for more command";
}
return (&LOCAL->reply);
}
/* IMAP send command
* Accepts: MAIL stream
* command
* argument list
* Returns: parsed reply
*/
#define MAXSEQUENCE 1000
IMAPPARSEDREPLY *imap_send(MAILSTREAM *stream, char *cmd, IMAPARG *args[])
{
IMAPPARSEDREPLY *reply;
IMAPARG *arg,**arglst;
STRINGLIST *list;
char c,*s,*t,tag[16];
#ifdef DEBUG // For debugging (JDB)
char *tmp_buf = LOCAL->tmp;
#endif
if (!stream->transStream) return imap_fake (stream,tag,"No-op dead stream");
//No longer needed. Already locked at this point.
//mail_lock (stream); // lock up the stream
sprintf (tag,"A%05ld",stream->gensym++); // gensym a new tag
for (s = LOCAL->tmp,t = tag; *t; *s++ = *t++);
*s++ = ' '; // delimit and write command
for (t = cmd; *t; *s++ = *t++);
if (arglst = args) while (arg = *arglst++)
{
*s++ = ' '; // delimit argument with space
switch (arg->type)
{
case ATOM: // atom
for (t = (char *) arg->text; *t; *s++ = *t++);
break;
case NUMBER: // number
sprintf (s,"%lu",(unsigned long) arg->text);
s += strlen (s);
break;
case FLAGS: // flag list as a single string
if (*(t = (char *) arg->text) != '(')
{
*s++ = '('; // wrap parens around string
while (*t) *s++ = *t++;
*s++ = ')'; // wrap parens around string
}
else while (*t) *s++ = *t++;
break;
case ASTRING: // atom or string, must be literal?
if (reply = imap_send_astring(stream,tag,&s,arg->text,(unsigned long) strlen (arg->text),NULL))
return reply;
break;
case LITERAL: // literal, as a stringstruct
if (reply = imap_send_literal(stream,tag,&s,arg->text)) return reply;
break;
case LIST: // list of strings
list = (STRINGLIST *) arg->text;
c = '(';
do
{
*s++ = c; // write prefix character
if (reply = imap_send_astring (stream,tag,&s,list->text.data, list->text.size,NULL)) return reply;
c = ' '; // prefix character for subsequent strings
}
while (list = list->next);
*s++ = ')'; // close list
break;
case SEARCHPROGRAM: // search program
if (reply = imap_send_spgm(stream,tag,&s,arg->text)) return reply;
break;
case BODYTEXT: /* body section */
// JOK - Now put "(" around BODY fetches (11/7/97).
// Some servers expect () even with one fetch argument.
for (t = "(BODY["; *t; *s++ = *t++);
for (t = (char *) arg->text; *t; *s++ = *t++);
break;
case BODYPEEK: /* body section */
// JOK - Now put "(" around BODY fetches (11/7/97).
for (t = "(BODY.PEEK["; *t; *s++ = *t++);
for (t = (char *) arg->text; *t; *s++ = *t++);
break;
case BODYCLOSE: /* close bracket and possible length */
s[-1] = ']'; /* no leading space */
for (t = (char *) arg->text; *t; *s++ = *t++);
// JOK - Now put ")" around BODY fetches (11/7/97).
for (t = ")"; *t; *s++ = *t++);
break;
case SEQUENCE: // sequence
// JOK - Don't recurse - send the complete sequence
// falls through
case LISTMAILBOX: // astring with wildcards
if (reply = imap_send_astring (stream,tag,&s, (char *)arg->text, (unsigned long) strlen(arg->text),T))
{
return reply;
}
break;
default:
fatal ("Unknown argument type in imap_send()!");
}
}
// send the command
reply = imap_sout(stream,tag,LOCAL->tmp,&s);
//no longer
//mail_unlock(stream); // unlock stream
return reply;
}
/* IMAP send buffered command to sender
* Accepts: MAIL stream
* reply tag
* string
* pointer to string tail pointer
* Returns: reply
*/
IMAPPARSEDREPLY *imap_sout(MAILSTREAM *stream, char *tag, char *base, char **s)
{
IMAPPARSEDREPLY *reply;
if (stream->debug) // output debugging telemetry
{
**s = '\0';
mm_dlog (base);
}
*(*s)++ = '\015'; // append CRLF
*(*s)++ = '\012';
**s = '\0';
// send the command, parse the reply unless we're doing a fast logout
if (net_sout(stream->transStream,base,*s - base) && !(stream->fastLogout))
reply = imap_reply (stream,tag);
else
reply = imap_fake (stream,tag,"IMAP connection broken (command)");
*s = base; /* restart buffer */
stream->fastLogout = false; // reset this just in case
return reply;
}
/* IMAP parse and act upon unsolicited reply
* Accepts: MAIL stream
* parsed reply
*/
//Modified by JDB
void imap_parse_unsolicited (MAILSTREAM *stream,IMAPPARSEDREPLY *reply)
{
unsigned long i = 0;
unsigned long msgno;
char *s,*t;
char *keyptr,*txtptr;
// Must have a stream, and a reply
if (!stream || !reply) return;
// create a NEW CurrentElt in the stream
if (stream->CurrentElt) mail_free_elt (&stream->CurrentElt);
stream->CurrentElt = mail_elt (stream);
// see if key is a number
msgno = strtoul (reply->key,&s,10);
// if non-numeric
if (*s)
{
if (!strcmp (reply->key,"FLAGS"))
{
// flush old user flags if any
while ((i < NUSERFLAGS) && stream->user_flags[i])
fs_give ((void **) &stream->user_flags[i++]);
i = 0; // add flags
if (s = (char *) strtok (reply->text+1," )"))
do
if (*s != '\\') stream->user_flags[i++] = cpystr (s);
while (s = (char *) strtok (NIL," )"));
}
else if (!strcmp (reply->key,"SEARCH"))
{
// only do something if have text
if (reply->text && (t = (char *) strtok (reply->text," ")))
do
{
i = atol (t);
if (!LOCAL->uidsearch) mail_elt (stream)->searched = T;
mm_searched (stream,i);
} while (t = (char *) strtok (NIL," "));
}
else if (!strcmp (reply->key,"STATUS"))
{
MAILSTATUS status;
char *txt;
switch (*reply->text) // mailbox is an astring
{
case '"': // quoted string?
case '{': // literal?
txt = reply->text; // status data is in reply
t = imap_parse_string (stream,&txt,reply,NIL,NIL);
break;
default: // must be atom
t = cpystr (reply->text);
if (txt = strchr (t,' ')) *txt++ = '\0';
break;
}
// JDB 060899, txt seems to point to " (", extra space after STATUS
if (*txt == ' ') txt++;
if (t && txt && (*txt++ == '(') && (s = strchr (txt,')')) && (s - txt) && !s[1])
{
char *scan = nil;
*s = '\0'; // tie off status data
// initialize data block
status.flags = status.messages = status.recent = status.unseen = status.uidnext = status.uidvalidity = 0;
ucase (txt); // do case-independent match
while (*txt && (s = strchr (txt,' ')))
{
*s++ = '\0'; // tie off status attribute name
i = strtoul (s,&s,10);// get attribute value
if (!strcmp (txt,"MESSAGES"))
{
status.flags |= SA_MESSAGES;
status.messages = i;
}
else if (!strcmp (txt,"RECENT"))
{
status.flags |= SA_RECENT;
status.recent = i;
}
else if (!strcmp (txt,"UNSEEN"))
{
status.flags |= SA_UNSEEN;
status.unseen = i;
}
else if (!strcmp (txt,"UIDNEXT") || !strcmp (txt,"UID-NEXT"))
{
status.flags |= SA_UIDNEXT;
status.uidnext = i;
}
else if (!strcmp (txt,"UIDVALIDITY")|| !strcmp (txt,"UID-VALIDITY"))
{
status.flags |= SA_UIDVALIDITY;
status.uidvalidity = i;
}
// next attribute
txt = (*s == ' ') ? s + 1 : s;
}
// was:
// strcpy (strchr (strcpy (LOCAL->tmp,stream->mailbox),'}') + 1,t);
strcpy (LOCAL->tmp,stream->mailbox);
scan = strchr(LOCAL->tmp, '}');
if (scan) strcpy (scan+1,t);
// pass status to main program
mm_status (stream,LOCAL->tmp,&status);
}
fs_give ((void **) &t);
}
else if ((!strcmp (reply->key,"LIST") || !strcmp (reply->key,"LSUB")) && (*reply->text == '(') && (s = strchr (reply->text,')')) && (s[1] == ' '))
{
char delimiter = '\0';
*s++ = '\0'; // tie off attribute list
// parse attribute list
if (t = (char *) strtok (reply->text+1," "))
do
{
if (!strcmp (ucase (t),"\\NOINFERIORS")) i |= LATT_NOINFERIORS;
else if (!strcmp (t,"\\NOSELECT")) i |= LATT_NOSELECT;
else if (!strcmp (t,"\\MARKED")) i |= LATT_MARKED;
else if (!strcmp (t,"\\UNMARKED")) i |= LATT_UNMARKED;
else if (LOCAL->use_children && !strcmp (t,"\\HASNOCHILDREN")) i |= LATT_HASNOCHILDREN;
// ignore extension flags
} while (t = (char *) strtok (NIL," "));
switch (*++s) // process delimiter
{
case 'N': // NIL
case 'n':
s += 4; // skip over NIL<space>
break;
case '"': // have a delimiter
delimiter = (*++s == '\\') ? *++s : *s;
s += 3; // skip over <delimiter><quote><space>
}
// need to prepend a prefix?
if (LOCAL->prefix)
strcpy (LOCAL->tmp,LOCAL->prefix);
else
LOCAL->tmp[0] = '\0';// no prefix needed
// need to do string parse?
if ((*s == '"') || (*s == '{'))
{
strcat (LOCAL->tmp,t = imap_parse_string (stream,&s,reply,NIL,NIL));
fs_give ((void **) &t);
}
else strcat(LOCAL->tmp,s); // atom is easy
if (reply->key[1] == 'S') mm_lsub (stream,delimiter,LOCAL->tmp,i);
else mm_list (stream,delimiter,LOCAL->tmp,i);
}
else if (!strcmp (reply->key,"MAILBOX"))
{
if (LOCAL->prefix) sprintf (t = LOCAL->tmp,"%s%s",LOCAL->prefix,reply->text);
else t = reply->text;
mm_list (stream,NIL,t,NIL);
}
else if (!strcmp (reply->key,"OK") || !strcmp (reply->key,"PREAUTH"))
{
if ((*reply->text == '[') && (t = strchr (s = reply->text + 1,']')) && ((i = t - s) < IMAPTMPLEN))
{
// get text code
strncpy (LOCAL->tmp,s,(size_t) i);
LOCAL->tmp[i] = '\0'; // tie off text
if (!strcmp (ucase (LOCAL->tmp),"READ-ONLY")) stream->rdonly = T;
else if (!strcmp (LOCAL->tmp,"READ-WRITE")) stream->rdonly = NIL;
else if (!strncmp (LOCAL->tmp,"UIDVALIDITY ",12))
{
stream->uid_validity = strtoul (LOCAL->tmp+12,NIL,10);
return;
}
else if (!strncmp (LOCAL->tmp,"PERMANENTFLAGS (",16))
{
if (LOCAL->tmp[i-1] == ')') LOCAL->tmp[i-1] = '\0';
stream->perm_seen = stream->perm_deleted = stream->perm_answered = stream->perm_draft = stream->kwd_create = NIL;
stream->perm_user_flags = NIL;
if (s = strtok (LOCAL->tmp+16," "))
do
{
if (!strcmp (s,"\\SEEN")) stream->perm_seen = T;
else if (!strcmp (s,"\\DELETED")) stream->perm_deleted = T;
else if (!strcmp (s,"\\FLAGGED")) stream->perm_flagged = T;
else if (!strcmp (s,"\\ANSWERED")) stream->perm_answered = T;
else if (!strcmp (s,"\\DRAFT")) stream->perm_draft = T;
else if (!strcmp (s,"\\*")) stream->kwd_create = T;
else stream->perm_user_flags |= imap_parse_user_flag (stream,s);
} while (s = strtok (NIL," "));
return;
}
else if (!strncmp (LOCAL->tmp,"ALERT", 5)) // see if this is an [ALERT] -jdboyd
mm_alert(stream, reply->text);
}
mm_notify (stream,reply->text,(long) NIL);
}
else if (!strcmp (reply->key,"NO"))
{
if (reply->text && !strncmp (reply->text,"[ALERT]", 7)) // see if an ALERT response was included -jdboyd
mm_alert(stream, reply->text);
if (!stream->silent) mm_notify (stream,reply->text,WARN);
}
else if (!strcmp (reply->key,"BYE"))
{
if (reply->text && !strncmp (reply->text,"[ALERT]", 7)) // see if an ALERT response was included -jdboyd
mm_alert(stream, reply->text);
LOCAL->byeseen = T; // note that a BYE seen
if (!stream->silent) mm_notify (stream,reply->text,BYE);
}
else if (!strcmp (reply->key,"BAD"))
{
if (reply->text && !strncmp (reply->text,"[ALERT]", 7)) // see if an ALERT response was included -jdboyd
mm_alert(stream, reply->text);
mm_notify (stream,reply->text,IMAP_ERROR);
}
else if (!strcmp (reply->key,"CAPABILITY"))
{
// only do something if have text
if (reply->text && (t = (char *) strtok (ucase (reply->text)," ")))
do
{
if (!strcmp (t,"IMAP4"))
LOCAL->imap4 = T;
else if (!strcmp (t,"IMAP4REV1"))
LOCAL->imap4rev1 = T;
else if (!strcmp (t,"SCAN"))
LOCAL->use_scan = T;
else if (!strcmp (t,"STARTTLS"))
LOCAL->use_tls = T;
else if (!strncmp (t,"AUTH",4) && ((t[4] == '=') || (t[4] == '-')) && (i = mail_lookup_auth_name(t+5)) && (--i < MAXAUTHENTICATORS))
LOCAL->use_auth |= (1 << i);
else if (!strcmp (t,"CHILDREN"))
LOCAL->use_children = T;
else if (!strcmp (t,"UIDPLUS"))
{
LOCAL->uidplus = T;
// Trust servers that advertise UIDPLUS do enough of it to support SpamWatch.
IMAPSpamWatchSupported(true, true);
}
// unsupported IMAP4 extension
else if (!strcmp (t,"STATUS"))
LOCAL->use_status = T;
// ignore other capabilities
} while (t = (char *) strtok (NIL," "));
}
else
{
sprintf (LOCAL->tmp,"Unexpected unsolicited message: %.80s",reply->key);
mm_log (LOCAL->tmp,WARN);
}
}
else // if numeric, a keyword follows
{
// deposit null at end of keyword
keyptr = ucase ((char *) strtok (reply->text," "));
// and locate the text after it
txtptr = (char *) strtok (NIL,"\n");
// now take the action
// change in size of mailbox
if (!strcmp (keyptr,"EXISTS")) mail_exists (stream,msgno);
else if (!strcmp (keyptr,"RECENT")) mail_recent (stream,msgno);
else if (!strcmp (keyptr,"EXPUNGE")) imap_expunged (stream,msgno);
else if (!strcmp (keyptr,"FETCH"))
{
imap_parse_data (stream,msgno,txtptr,reply);
// take care of minimal headers.
if (stream->chunkHeaders && stream->headerUID) SaveMinimalHeader(stream);
}
else if (!strcmp (keyptr,"STORE")) imap_parse_data (stream,msgno,txtptr,reply);
else if (strcmp (keyptr,"COPY"))
{
sprintf (LOCAL->tmp,"Unknown message data: %lu %.80s",msgno,keyptr);
mm_log (LOCAL->tmp,WARN);
}
}
}
/*
* imap_connected - see if we're connected
*
* Accepts: MAIL stream
* Returns: true if connected, NULL if not.
*/
Boolean imap_connected (MAILSTREAM *stream)
{
if (stream && LOCAL && stream->transStream && !TransError(stream->transStream) && stream->bConnected && !CommandPeriod) return true;
else return NULL;
}
/* IMAP fetch RFC822.SIZE
* Accepts: MAIL stream
* message uid
* Returns: size, or 0 if error.
*/
// NOTE: Added by JOK.
// FUNCTION
// Fetch the RFC822.SIZE of the message.
// Return size, or 0 if error.
// END FUNCTION
unsigned long imap_rfc822size (MAILSTREAM *stream, unsigned long msgno, long flags)
{
IMAPPARSEDREPLY *reply = NIL;
IMAPARG *args[3],aseq,aatt;
char *cmd = (LEVELIMAP4 (stream) && (flags & FT_UID)) ? "UID FETCH":"FETCH";
char seq[MAILTMPLEN];
unsigned long rfc822size = 0;
MESSAGECACHE *elt = NULL;
// Cannot have a zero msgno.
if (msgno <= 0)
return msgno;
// Initialize:
rfc822size = 0;
// IF there is a CurrentElt in the stream, delete it.
if (stream->CurrentElt)
mail_free_elt (&stream->CurrentElt);
stream->CurrentElt = NULL;
// Allocate a new one.
elt = mail_elt (stream);
if (elt)
{
if (flags & FT_UID)
elt->privat.uid == msgno;
else
elt->msgno = msgno;
}
else
return 0;
// Setup for IMAP call.
sprintf (seq, "%lu", msgno);
aseq.type = SEQUENCE;
aseq.text = (void *) seq;
aatt.type = ATOM;
aatt.text = (void *) "RFC822.SIZE";
args[0] = &aseq;
args[1] = &aatt;
args[2] = NIL;
/* send "FETCH msgno UID" */
if (!imap_OK (stream,reply = imap_send (stream, cmd, args)))
{
if (reply) mm_log (reply->text,WARN);
return 0;
}
// Did we get anything?
if (stream->CurrentElt)
{
// Make sure the uid's matched.
if (flags & FT_UID)
{
if (stream->CurrentElt->privat.uid == msgno)
rfc822size = stream->CurrentElt->rfc822_size;
}
else
{
if (stream->CurrentElt->msgno == msgno)
rfc822size = stream->CurrentElt->rfc822_size;
}
// Now free the elt.
mail_free_elt (&stream->CurrentElt);
}
return rfc822size;
}
/* IMAP fetch envelope. */
// NOTE:
// JOK - This is a new driver member. It fetches just the message envelope.
// The parsing routine allocates an ENVELOPE structure and attaches it to the
// stream's "current elt". We check for the envelope, detaches it from the stream and passes it to the caller.
// The caller MUST free the structure when done with it.
// END NOTE
/* Accepts: MAIL stream
* message # to fetch
* option flags
* Returns: Returns an allocated ENVELOPE structure.
*
*/
ENVELOPE *imap_envelope (MAILSTREAM *stream,unsigned long msgno, long flags)
{
char seq[128],tmp[MAILTMPLEN];
ENVELOPE *env;
IMAPPARSEDREPLY *reply = NIL;
IMAPARG *args[3],aseq,aatt;
MESSAGECACHE *elt = NULL;
// Cannot have a zero msgno.
if (msgno <= 0)
return NULL;
// Initialize: "env" is what's returned.
env = NULL;
args[0] = &aseq; args[1] = &aatt; args[2] = NIL;
aseq.type = SEQUENCE; aseq.text = (void *) seq;
aatt.type = ATOM; aatt.text = NIL;
// IF there is a CurrentElt in the stream, delete it.
if (stream->CurrentElt)
mail_free_elt (&stream->CurrentElt);
// Allocate a new one.
elt = mail_elt (stream);
if (elt)
{
if (flags & FT_UID)
elt->privat.uid = msgno;
else
elt->msgno = msgno;
}
// NOTE: "msgno" can be a UID or a message sequence number.
sprintf (seq,"%lu",msgno); /* initial sequence (UID or msgno) */
// Format command based on server capability.
// NOTE: Can't handle any IMAP version older than imap2bis!!
if (LEVELIMAP4 (stream) && (flags & FT_UID))
{
sprintf (tmp,"(UID ENVELOPE)");
aatt.text = (void *) tmp; /* do the built command */
if (!imap_OK (stream, reply = imap_send (stream,"UID FETCH",args)))
{
if (reply)
mm_log (reply->text,IMAP_ERROR);
}
}
else if (LEVELIMAP2bis (stream))
{
/* has non-extensive body and no UID. */
sprintf (tmp,"(BODY)");
aatt.text = (void *) tmp; /* do the built command */
if (!imap_OK (stream, reply = imap_send (stream,"FETCH",args)))
{
if (reply)
mm_log (reply->text,IMAP_ERROR);
}
}
// "env" is what's returned.
env = NULL;
// Did we get anything?
if (stream->CurrentElt)
{
// Make sure the UID's or msgno's matched.
if (flags & FT_UID)
{
if (stream->CurrentElt->privat.uid == msgno)
env = stream->CurrentElt->privat.msg.env;
}
else
{
if (stream->CurrentElt->msgno == msgno)
env = stream->CurrentElt->privat.msg.env;
}
// Make sure we detach the body pointer if there was one.
// If it's not our env, we'd want to delete it.
if (env)
stream->CurrentElt->privat.msg.env = NULL;
// Now free the elt.
mail_free_elt (&stream->CurrentElt);
}
return env;
}
/************************************************************************
* StoreUIDPLUSResponses - Store the UIDPLUS responses given in a reply.
* Currently works for COPYUID only.
************************************************************************/
OSErr StoreUIDPLUSResponses(MAILSTREAM *stream, IMAPPARSEDREPLY *reply)
{
OSErr err = noErr;
char *s,*e;
// Does this reply contain COPYUID responses?
if (!strncmp(ucase (reply->text),"[COPYUID",8))
{
// There are COPYUID reponses available
IMAPSpamWatchSupported(true, true);
// RFC 2359
// First number in the COPYUID response in the UIDVALIDITY of the destination mailbox
s = reply->text + 9;
e = strchr(s, ' ');
if (e)
{
// make sure the UIDVALIDITY of the destination mailbox hasn't changed on us.
// if it has, we'll need to discard the responses we've received so far. They're invalid.
VerifyUIDValidity(stream, s, e-s);
// next block is the range of messages transferred
e++;
if (*e)
{
e = strchr(e, ' ');
if (e)
{
// Last block is the range of new UIDs
s = ++e;
e = strchr(s, ']');
if (e)
{
UIDStringToUIDs(s, e-s, &(stream->UIDPLUSResponse));
}
}
}
}
}
else
{
// UID COPY was executed, but no COPYUID repsonse was detected.
IMAPSpamWatchSupported(false, true);
}
return (err);
}
/************************************************************************
* VerifyUIDValidity - zap the UIDPlus responses up to now if we
* find the UIDVALIDITY of the mailbox has changed on us.
************************************************************************/
void VerifyUIDValidity(MAILSTREAM *stream, char *pUids, int len)
{
Str255 scratch;
int newUV;
MakePStr(scratch, pUids, MIN(len, sizeof(scratch)));
StringToNum(scratch, &newUV);
// first time?
if (stream->UIDPLUSuv == 0)
stream->UIDPLUSuv = newUV;
else if (stream->UIDPLUSuv != newUV)
{
// Zap Resposes so far, store new uidvalidity
AccuZap(stream->UIDPLUSResponse);
AccuInit(&stream->UIDPLUSResponse);
stream->UIDPLUSuv = newUV;
}
// else
// they match
}
/************************************************************************
* UIDStringToUIDs - given a UID set, add the individual UIDs to the
* accumulator passed in.
************************************************************************/
OSErr UIDStringToUIDs(char *pUids, int len, Accumulator *pAccu)
{
OSErr err = noErr;
long start, stop;
char *pEnd, *pUidEnd = pUids + len;
Str255 scratch;
// only handles x and x:y type sets at this point
pEnd = strchr(pUids, ':');
if (pEnd)
{
MakePStr(scratch, pUids, pEnd-pUids);
StringToNum(scratch, &start);
pUids = pEnd + 1;
}
MakePStr(scratch, pUids, pUidEnd - pUids);
StringToNum(scratch, &stop);
// no range was specified
if (!pEnd)
start = stop;
for (;(err == noErr) && (start <= stop); start++)
err = AccuAddPtr(pAccu, &start, sizeof(long));
return (err);
}